自动绘图AI:程序如何画出动漫美少女
说明
本文发布较早,查看最新动态,请关注 GitHub 项目。(2020 年 1 月 注)
准备
全新的图形引擎与 AI 算法,高效流畅地绘出任何一副美丽的图像。
IDE:VisualStudio
Language:VB.NET / C#
Graphics:EDGameEngine
第一节 背景
背景是图画里衬托主体事物的景象。

图1-1 先画个蓝蓝的天空
蓝天、白云和大地,程序最擅长这种色调单一的涂抹了。
第二节 轮廓
轮廓是物体的外周或图形的外框。

图2-2 勾勒人物和衣饰轮廓
现在 AI 要控制笔触大小和颜色,让图像的主体显现出来。
第三节 光影
光影是物体在光的照射下呈现出明与暗的关系。

图3-1 光影提升画面质感
AI 可不懂什么是光影,在上一步的基础上优化细节即可。
第四节 润色
润色是增加物体本身及其周围的色彩。

图4-1 画面润色
这是关键一步,AI需要将丢失的颜色细节补缺回来。
第五节 成型
大功告成!前面所有的步骤都是为这一步铺垫。

图5-1 人物已经栩栩如生啦
事实上 AI 只进行这一步也可以画出完整的图像,但没有过渡会显得生硬。
第六节 算法
算法思路很简单,计算画笔轨迹后一遍遍重绘,感觉上是人类画手的效果。
不再是二值化
因为现在要绘制全彩图像,将图像划分为只有黑和白的效果已经没有什么意义,二值化不再适用
适用的方法是将 RGB 颜色空间划分为若干个颜色子空间,然后逐个处理一幅图像中属于某个子空间的区域
自动循迹
循迹算法没有大的变动,仍是早前博客里贴出的代码
彩色图像线条较短,可以不再计算点周围的权值用来中断轨迹
重绘
程序先选择笔触较大、颜色淡的画笔绘制一遍,然后在这基础上逐步减小笔触并加深色彩
直接按照标准笔触可以一遍成型,但会显得突兀和生硬,毕竟这个AI不是真的在思考如何画一幅图像
Imports System.Numerics
''' <summary>
''' 表示自动循迹并生成绘制序列的AI
''' </summary>
Public Class SequenceAI
''' <summary>
''' 线条序列List
''' </summary>
''' <returns></returns>
Public Property Sequences As List(Of PointSequence)
''' <summary>
''' 扫描方式
''' </summary>
Public Property ScanMode As ScanMode = ScanMode.Rect
Dim xArray() As Integer = {-, , , , , , -, -}
Dim yArray() As Integer = {-, -, -, , , , , }
Dim NewStart As Boolean
''' <summary>
''' 创建并初始化一个可自动生成绘制序列AI的实例
''' </summary>
Public Sub New(BolArr(,) As Integer)
Sequences = New List(Of PointSequence)
CalculateSequence(BolArr)
For Each SubItem In Sequences
SubItem.CalcSize()
Next
End Sub
''' <summary>
''' 新增一个序列
''' </summary>
Private Sub CreateNewSequence()
Sequences.Add(New PointSequence)
End Sub
''' <summary>
''' 在序列List末尾项新增一个点
''' </summary>
Private Sub AddPoint(point As Vector2)
Sequences.Last.Points.Add(point)
End Sub
''' <summary>
''' 计算序列
''' </summary>
Private Sub CalculateSequence(BolArr(,) As Integer)
If ScanMode = ScanMode.Rect Then
ScanRect(BolArr)
Else
ScanCircle(BolArr)
End If
End Sub
''' <summary>
''' 圆形扫描
''' </summary>
''' <param name="BolArr"></param>
Private Sub ScanCircle(BolArr(,) As Integer)
Dim xCount As Integer = BolArr.GetUpperBound()
Dim yCount As Integer = BolArr.GetUpperBound()
Dim CP As New Point(xCount / , yCount / )
Dim R As Integer =
For R = To If(xCount > yCount, xCount, yCount)
For Theat = To Math.PI * Step / R
Dim dx As Integer = CInt(CP.X + R * Math.Cos(Theat))
Dim dy As Integer = CInt(CP.Y + R * Math.Sin(Theat))
If Not (dx > And dy > And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = Then
BolArr(dx, dy) =
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
CheckMove(BolArr, dx, dy, )
NewStart = True
End If
Next
Next
End Sub
''' <summary>
''' 矩形扫描
''' </summary>
''' <param name="BolArr"></param>
Private Sub ScanRect(BolArr(,) As Integer)
Dim xCount As Integer = BolArr.GetUpperBound()
Dim yCount As Integer = BolArr.GetUpperBound()
For i = To xCount -
For j = To yCount -
Dim dx As Integer = i
Dim dy As Integer = j
If Not (dx > And dy > And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = Then
BolArr(dx, dy) =
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
CheckMove(BolArr, dx, dy, )
NewStart = True
End If
Next
Next
End Sub
''' <summary>
''' 递归循迹算法
''' </summary>
Private Sub CheckMove(ByRef bolArr(,) As Integer, ByVal x As Integer, ByVal y As Integer, ByVal StepNum As Integer)
If StepNum > Then Return
Dim xBound As Integer = bolArr.GetUpperBound()
Dim yBound As Integer = bolArr.GetUpperBound()
Dim dx, dy As Integer
Dim AroundValue As Integer = GetAroundValue(bolArr, x, y)
'根据点权值轨迹将在当前点断开
'If AroundValue > 2 AndAlso AroundValue < 8 Then
'Return
'End If
For i = To
dx = x + xArray(i)
dy = y + yArray(i)
If Not (dx > And dy > And dx < xBound And dy < yBound) Then
Return
ElseIf bolArr(dx, dy) = Then
bolArr(dx, dy) =
If NewStart = True Then
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
NewStart = False
Else
Me.AddPoint(New Vector2(dx, dy))
End If
CheckMove(bolArr, dx, dy, StepNum + )
NewStart = True
End If
Next
End Sub
''' <summary>
''' 返回点权值
''' </summary>
Private Function GetAroundValue(ByRef BolArr(,) As Integer, ByVal x As Integer, ByVal y As Integer) As Integer
Dim dx, dy, ResultValue As Integer
Dim xBound As Integer = BolArr.GetUpperBound()
Dim yBound As Integer = BolArr.GetUpperBound()
For i = To
dx = x + xArray(i)
dy = y + yArray(i)
If dx > And dy > And dx < xBound And dy < yBound Then
If BolArr(dx, dy) = Then
ResultValue +=
End If
End If
Next
Return ResultValue
End Function
End Class ''' <summary>
''' 线条扫描方式
''' </summary>
Public Enum ScanMode
''' <summary>
''' 矩形扫描
''' </summary>
Rect
''' <summary>
''' 圆形扫描
''' </summary>
Circle
End Enum
VB.NET-SequenceAI
Imports System.Numerics
''' <summary>
''' 表示由一系列点向量组成的线条
''' </summary>
Public Class PointSequence
Public Property Points As New List(Of Vector2)
Public Property Sizes As Single()
''' <summary>
''' 计算画笔大小
''' </summary>
Public Sub CalcSize()
If Points.Count < Then Exit Sub
Static Mid, PenSize As Single
ReDim Sizes(Points.Count - )
For i = To Points.Count -
Mid = CSng(Math.Abs(i - Points.Count / ))
PenSize = - Mid / Points.Count *
Sizes(i) = PenSize
Next
End Sub
End Class
VB.NET-PointSequence
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Numerics;
/// <summary>
/// 表示自动循迹并生成绘制序列的AI
/// </summary>
public class SequenceAI
{
/// <summary>
/// 线条序列List
/// </summary>
/// <returns></returns>
public List<PointSequence> Sequences { get; set; }
/// <summary>
/// 扫描方式
/// </summary>
public ScanMode ScanMode { get; set; }
int[] xArray = {
-,
,
,
,
,
,
-,
-
};
int[] yArray = {
-,
-,
-,
,
,
,
, };
bool NewStart;
/// <summary>
/// 创建并初始化一个可自动生成绘制序列AI的实例
/// </summary>
public SequenceAI(int[,] BolArr)
{
Sequences = new List<PointSequence>();
CalculateSequence(BolArr);
foreach (object SubItem_loopVariable in Sequences) {
SubItem = SubItem_loopVariable;
SubItem.CalcSize();
}
}
/// <summary>
/// 新增一个序列
/// </summary>
private void CreateNewSequence()
{
Sequences.Add(new PointSequence());
}
/// <summary>
/// 在序列List末尾项新增一个点
/// </summary>
private void AddPoint(Vector2 point)
{
Sequences.Last.Points.Add(point);
}
/// <summary>
/// 计算序列
/// </summary>
private void CalculateSequence(int[,] BolArr)
{
if (ScanMode == ScanMode.Rect) {
ScanRect(BolArr);
} else {
ScanCircle(BolArr);
}
}
/// <summary>
/// 圆形扫描
/// </summary>
/// <param name="BolArr"></param>
private void ScanCircle(int[,] BolArr)
{
int xCount = BolArr.GetUpperBound();
int yCount = BolArr.GetUpperBound();
Point CP = new Point(xCount / , yCount / );
int R = ;
for (R = ; R <= xCount > yCount ? xCount : yCount; R++) {
for (Theat = ; Theat <= Math.PI * ; Theat += / R) {
int dx = Convert.ToInt32(CP.X + R * Math.Cos(Theat));
int dy = Convert.ToInt32(CP.Y + R * Math.Sin(Theat));
if (!(dx > & dy > & dx < xCount & dy < yCount))
continue;
if (BolArr[dx, dy] == ) {
BolArr[dx, dy] = ;
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
CheckMove(ref BolArr, dx, dy, );
NewStart = true;
}
}
}
}
/// <summary>
/// 矩形扫描
/// </summary>
/// <param name="BolArr"></param>
private void ScanRect(int[,] BolArr)
{
int xCount = BolArr.GetUpperBound();
int yCount = BolArr.GetUpperBound();
for (i = ; i <= xCount - ; i++) {
for (j = ; j <= yCount - ; j++) {
int dx = i;
int dy = j;
if (!(dx > & dy > & dx < xCount & dy < yCount))
continue;
if (BolArr[dx, dy] == ) {
BolArr[dx, dy] = ;
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
CheckMove(ref BolArr, dx, dy, );
NewStart = true;
}
}
}
}
/// <summary>
/// 递归循迹算法
/// </summary>
private void CheckMove(ref int[,] bolArr, int x, int y, int StepNum)
{
if (StepNum > )
return;
int xBound = bolArr.GetUpperBound();
int yBound = bolArr.GetUpperBound();
int dx = ;
int dy = ;
int AroundValue = GetAroundValue(ref bolArr, x, y);
//根据点权值轨迹将在当前点断开
//If AroundValue > 2 AndAlso AroundValue < 8 Then
//Return
//End If
for (i = ; i <= ; i++) {
dx = x + xArray[i];
dy = y + yArray[i];
if (!(dx > & dy > & dx < xBound & dy < yBound)) {
return;
} else if (bolArr[dx, dy] == ) {
bolArr[dx, dy] = ;
if (NewStart == true) {
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
NewStart = false;
} else {
this.AddPoint(new Vector2(dx, dy));
}
CheckMove(ref bolArr, dx, dy, StepNum + );
NewStart = true;
}
}
}
/// <summary>
/// 返回点权值
/// </summary>
private int GetAroundValue(ref int[,] BolArr, int x, int y)
{
int dx = ;
int dy = ;
int ResultValue = ;
int xBound = BolArr.GetUpperBound();
int yBound = BolArr.GetUpperBound();
for (i = ; i <= ; i++) {
dx = x + xArray[i];
dy = y + yArray[i];
if (dx > & dy > & dx < xBound & dy < yBound) {
if (BolArr[dx, dy] == ) {
ResultValue += ;
}
}
}
return ResultValue;
}
} /// <summary>
/// 线条扫描方式
/// </summary>
public enum ScanMode
{
/// <summary>
/// 矩形扫描
/// </summary>
Rect,
/// <summary>
/// 圆形扫描
/// </summary>
Circle
}
C#-SequenceAI
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Numerics;
/// <summary>
/// 表示由一系列点向量组成的线条
/// </summary>
public class PointSequence
{
public List<Vector2> Points { get; set; }
public float[] Sizes { get; set; }
float static_CalcSize_Mid;
/// <summary>
/// 计算画笔大小
/// </summary>
float static_CalcSize_PenSize;
public void CalcSize()
{
if (Points.Count < )
return;
Sizes = new float[Points.Count];
for (i = ; i <= Points.Count - ; i++) {
static_CalcSize_Mid = Convert.ToSingle(Math.Abs(i - Points.Count / ));
static_CalcSize_PenSize = - static_CalcSize_Mid / Points.Count * ;
Sizes[i] = static_CalcSize_PenSize;
}
}
}
C#-PointSequence
视频
附录
GitHub:EDGameEngine.AutoDraw
早期博客:程序如何实现自动绘图
早期博客:更优秀的自动绘图程序
创意分享:儿童涂鸦遇上程序绘图
自动绘图AI:程序如何画出动漫美少女的更多相关文章
- 人才需求之Java程序员与AI程序员
据100offer报告显示:2018年Java人才市场「高开低走」的动荡局势.整体求职难度变大,且全年波动更剧烈,淡旺季区别明显.企业发出的Java面邀总数几个季度连续下跌,Q4 甚至比去年同期下降了 ...
- MFC文档(SDI)应用:画图程序(画圆、画线、鼠标事件)
要求 1. 在客户区输出一条顺时针45度的直线.一个正方形.一个大圆: 2. 在客户区输出一个图标: 3. 当按下鼠标左键时,将以鼠标坐标为圆心画直径为20个单位的小圆. 首先设置两个变量,用来保存颜 ...
- 师傅领进门之6步教你跑通一个AI程序!
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由云计算基础发表于云+社区专栏 源码下载地址请点击原文查看. 初学机器学习,写篇文章mark一下,希望能为将入坑者解点惑.本文介绍一些机 ...
- android程序启动画面之Splash总结[转]
方法一: 很多应用都会有一个启动界面.欢迎画面慢慢隐现,然后慢慢消隐.实现这种效果的方法有两种(暂时只发现两种)1.使用两个Activity,程序启动时候load第一张Activity,然后由tick ...
- C++ 之 简单的五子棋AI程序
本人是大一新生,寒假无聊,抱着试试看的心态(没有想到可以完成),写了C++的简单五子棋程序,开心. 下面是效果图: 一.首先讲讲大致思路. 五子棋实现的基础: ...
- 小程序-生成一个小程序码画在canvas画布上生成一张图片分享出去
这个需求我遇到过2次.一次是在识别二维码后跳转到其它页面,另一次是识别二维码后进入到生成小程序码的当前页面. 我有一个梦想,就是成为一名黑客!!!!!! 小程序中js wx.request({ ...
- 用python程序来画花
from turtle import * import time setup(600,800,0,0) speed(0) penup() seth(90) fd(340) seth(0) pendow ...
- 微信小程序css画三角形内有文字
<view class="productStatus"> <span> <em>已上架</em> </span> < ...
- unity美少女动作RPG游戏源码Action-RPG Starter Kit v5.0a
功能完整的ARPG游戏模板 Core Features!! - Combat System - Skill Tree - Enemy AI - Save-Load Game - Shop System ...
随机推荐
- [C#] C# 知识回顾 - 委托 delegate
C# 知识回顾 - 委托 delegate [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6031892.html 目录 What's 委托 委托的属性 ...
- UWP开发之Mvvmlight实践七:如何查找设备(Mobile模拟器、实体手机、PC)中应用的Log等文件
在开发中或者后期测试乃至最后交付使用的时候,如果应用出问题了我们一般的做法就是查看Log文件.上章也提到了查看Log文件,这章重点讲解下如何查看Log文件?如何找到我们需要的Packages安装包目录 ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- 来吧,HTML5之基础标签(下)
<dialog> 标签 定义对话框或窗口. <dialog> 标签是 HTML 5 的新标签.目前只有 Chrome 和 Safari 6 支持 <dialog> ...
- 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)
统计角度窥视模型概念 作者:白宁超 2016年7月18日17:18:43 摘要:写本文的初衷源于基于HMM模型序列标注的一个实验,实验完成之后,迫切想知道采用的序列标注模型的好坏,有哪些指标可以度量. ...
- gulp初学
原文地址:gulp初学 至于gulp与grunt的区别,用过的人都略知一二,总的来说就是2点: 1.gulp的gulpfile.js 配置简单而且更容易阅读和维护.之所以如此,是因为它们的工作方式不 ...
- 深入学习jQuery自定义插件
原文地址:jQuery自定义插件学习 1.定义插件的方法 对象级别的插件扩展,即为jQuery类的实例增加方法, 调用:$(选择器).函数名(参数); $(‘#id’).myPlugin(o ...
- "NHibernate.Exceptions.GenericADOException: could not load an entity" 解决方案
今天,测试一个项目的时候,抛出了这个莫名其妙的异常,然后就开始了一天的调试之旅... 花了很长时间,没有从代码找出任何问题... 那么到底哪里出问题呢? 根据下面那段长长的错误日志: -- ::, ...
- .NET面试题集锦①(Part一)
一.前言部分 文中的问题及答案多收集整理自网络,不保证100%准确,还望斟酌采纳. 1.面向对象的思想主要包括什么? 答:任何事物都可以理解为对象,其主要特征: 继承.封装.多态.特点:代码好维护,安 ...
- BPM配置故事之案例11-操作外部数据源
小明:可以获取ERP数据了-- 老李:哦,这么快?小伙子,我非常看好你,来来,别急着走,再陪我聊会-- 小明:--您老人家不是又要改流程吧? 老李:没有没有,哎嘿嘿嘿,我们这不都是为公司效率着想嘛,这 ...