自動繪圖AI:程序如何畫出動漫美少女,ai美少女
序
全新的圖形引擎與AI算法,高效流暢地繪出任何一副美麗的圖像。
IDE:VisualStudio 2015
Language:VB.NET/C#
Graphics:EDGameEngine
第一節 背景
背景是圖畫裡襯托主體事物的景象。
圖1.1 先畫個藍藍的天空
藍天、白雲和大地,程序最擅長這種色調單一的塗抹了
第二節 輪廓
輪廓是物體的外周或圖形的外框。
圖2.2 勾勒人物和衣飾輪廓
現在AI要控制筆觸大小和顏色,讓圖像的主體顯現出來
第三節 光影
光影是物體在光的照射下呈現出明與暗的關系。
圖3.1 光影提升畫面質感
AI可不懂什麼是光影,在上一步的基礎上優化細節即可
第四節 潤色
潤色是增加物體本身及其周圍的色彩。
圖4.1 畫面潤色
這是關鍵一步,AI需要將丟失的顏色細節補缺回來
第五節 成型
大功告成!前面所有的步驟都是為這一步鋪墊。
圖5.1 人物已經栩栩如生啦
事實上AI只進行這一步也可以畫出完整的圖像,但沒有過渡會顯得生硬
第六節 算法
上文的圖片是程序畫的,文字是筆者瞎編的。
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 = {-1, 0, 1, 1, 1, 0, -1, -1}
Dim yArray() As Integer = {-1, -1, -1, 0, 1, 1, 1, 0}
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(0)
Dim yCount As Integer = BolArr.GetUpperBound(1)
Dim CP As New Point(xCount / 2, yCount / 2)
Dim R As Integer = 0
For R = 0 To If(xCount > yCount, xCount, yCount)
For Theat = 0 To Math.PI * 2 Step 1 / 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 > 0 And dy > 0 And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = 1 Then
BolArr(dx, dy) = 0
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
CheckMove(BolArr, dx, dy, 0)
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(0)
Dim yCount As Integer = BolArr.GetUpperBound(1)
For i = 0 To xCount - 1
For j = 0 To yCount - 1
Dim dx As Integer = i
Dim dy As Integer = j
If Not (dx > 0 And dy > 0 And dx < xCount And dy < yCount) Then Continue For
If BolArr(dx, dy) = 1 Then
BolArr(dx, dy) = 0
Me.CreateNewSequence()
Me.AddPoint(New Vector2(dx, dy))
CheckMove(BolArr, dx, dy, 0)
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 > 1000 Then Return
Dim xBound As Integer = bolArr.GetUpperBound(0)
Dim yBound As Integer = bolArr.GetUpperBound(1)
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 = 0 To 7
dx = x + xArray(i)
dy = y + yArray(i)
If Not (dx > 0 And dy > 0 And dx < xBound And dy < yBound) Then
Return
ElseIf bolArr(dx, dy) = 1 Then
bolArr(dx, dy) = 0
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 + 1)
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(0)
Dim yBound As Integer = BolArr.GetUpperBound(1)
For i = 0 To 7
dx = x + xArray(i)
dy = y + yArray(i)
If dx > 0 And dy > 0 And dx < xBound And dy < yBound Then
If BolArr(dx, dy) = 1 Then
ResultValue += 1
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 < 1 Then Exit Sub
Static Mid, PenSize As Single
ReDim Sizes(Points.Count - 1)
For i = 0 To Points.Count - 1
Mid = CSng(Math.Abs(i - Points.Count / 2))
PenSize = 1 - Mid / Points.Count * 2
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 = {
-1,
0,
1,
1,
1,
0,
-1,
-1
};
int[] yArray = {
-1,
-1,
-1,
0,
1,
1,
1,
0
};
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(0);
int yCount = BolArr.GetUpperBound(1);
Point CP = new Point(xCount / 2, yCount / 2);
int R = 0;
for (R = 0; R <= xCount > yCount ? xCount : yCount; R++) {
for (Theat = 0; Theat <= Math.PI * 2; Theat += 1 / R) {
int dx = Convert.ToInt32(CP.X + R * Math.Cos(Theat));
int dy = Convert.ToInt32(CP.Y + R * Math.Sin(Theat));
if (!(dx > 0 & dy > 0 & dx < xCount & dy < yCount))
continue;
if (BolArr[dx, dy] == 1) {
BolArr[dx, dy] = 0;
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
CheckMove(ref BolArr, dx, dy, 0);
NewStart = true;
}
}
}
}
/// <summary>
/// 矩形掃描
/// </summary>
/// <param name="BolArr"></param>
private void ScanRect(int[,] BolArr)
{
int xCount = BolArr.GetUpperBound(0);
int yCount = BolArr.GetUpperBound(1);
for (i = 0; i <= xCount - 1; i++) {
for (j = 0; j <= yCount - 1; j++) {
int dx = i;
int dy = j;
if (!(dx > 0 & dy > 0 & dx < xCount & dy < yCount))
continue;
if (BolArr[dx, dy] == 1) {
BolArr[dx, dy] = 0;
this.CreateNewSequence();
this.AddPoint(new Vector2(dx, dy));
CheckMove(ref BolArr, dx, dy, 0);
NewStart = true;
}
}
}
}
/// <summary>
/// 遞歸循跡算法
/// </summary>
private void CheckMove(ref int[,] bolArr, int x, int y, int StepNum)
{
if (StepNum > 1000)
return;
int xBound = bolArr.GetUpperBound(0);
int yBound = bolArr.GetUpperBound(1);
int dx = 0;
int dy = 0;
int AroundValue = GetAroundValue(ref bolArr, x, y);
//根據點權值軌跡將在當前點斷開
//If AroundValue > 2 AndAlso AroundValue < 8 Then
//Return
//End If
for (i = 0; i <= 7; i++) {
dx = x + xArray[i];
dy = y + yArray[i];
if (!(dx > 0 & dy > 0 & dx < xBound & dy < yBound)) {
return;
} else if (bolArr[dx, dy] == 1) {
bolArr[dx, dy] = 0;
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 + 1);
NewStart = true;
}
}
}
/// <summary>
/// 返回點權值
/// </summary>
private int GetAroundValue(ref int[,] BolArr, int x, int y)
{
int dx = 0;
int dy = 0;
int ResultValue = 0;
int xBound = BolArr.GetUpperBound(0);
int yBound = BolArr.GetUpperBound(1);
for (i = 0; i <= 7; i++) {
dx = x + xArray[i];
dy = y + yArray[i];
if (dx > 0 & dy > 0 & dx < xBound & dy < yBound) {
if (BolArr[dx, dy] == 1) {
ResultValue += 1;
}
}
}
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 < 1)
return;
Sizes = new float[Points.Count];
for (i = 0; i <= Points.Count - 1; i++) {
static_CalcSize_Mid = Convert.ToSingle(Math.Abs(i - Points.Count / 2));
static_CalcSize_PenSize = 1 - static_CalcSize_Mid / Points.Count * 2;
Sizes[i] = static_CalcSize_PenSize;
}
}
}
C#-PointSequence
附錄
相關鏈接
Demo視頻:AutomaticDrawing_人民 (上文的視頻暫不上傳\(^o^)/)
Demo開源:EDGameEngine.Visuals.AutoDraw
相關博客
玩轉你畫我猜(一):程序實現自動繪圖
玩轉你畫我猜(二):更優秀的自動繪圖程序
AR創意分享:兒童塗鴉遇上程序繪圖