自动绘图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 ...
随机推荐
- nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配 ...
- Hawk 5.1 数据导入和导出
除了一般的数据库导入导出,Hawk还支持从文件导入和导出,支持的文件类型包括: Excel CSV(逗号分割文本文件) TXT (制表符分割文本文件) Json xml Excel 目前来看,Exce ...
- 快速了解微信小程序的使用,一个根据小程序的框架开发的todos app
微信官方已经开放微信小程序的官方文档和开发者工具.前两天都是在看相关的新闻来了解小程序该如何开发,这两天官方的文档出来之后,赶紧翻看了几眼,重点了解了一下文档中框架与组件这两个部分,然后根据简易教程, ...
- JS鼠标事件大全 推荐收藏
一般事件 事件 浏览器支持 描述 onClick HTML: 2 | 3 | 3.2 | 4 Browser: IE3 | N2 | O3 鼠标点击事件,多用在某个对象控制的范围内的鼠标点击 onDb ...
- linux常用查看硬件设备信息命令
转载:http://blog.chinaunix.net/uid-26782198-id-3242120.html # uname -a # 查看内核/操作系统/CPU信息 ...
- Vue.js——60分钟组件快速入门(上篇)
组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢?组件可以扩展HTML ...
- 我的MYSQL学习心得(三) 查看字段长度
我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(五) 运 ...
- deepsooncms在Ubuntu 14.04上部署教程
deepsooncms在Ubuntu 14.04上部署教程 一.安装mono1.在命令行运行sudo apt-key adv --keyserver keyserver.ubuntu.com --re ...
- 初探领域驱动设计(2)Repository在DDD中的应用
概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...
- Redux初见
说到redux可能我们都先知道了react,但我发现,关于react相关的学习资料很多,也有各种各样的种类,但是关于redux简单易懂的资料却比较少. 这里记录一下自己的学习理解,希望可以简洁易懂,入 ...