本文發佈時間較早,查看最新動態請關注 GitHub 項目。(2019 年 6 月 注)html
全新的圖形引擎與 AI 算法,高效流暢地繪出任何一副美麗的圖像。git
IDE:VisualStudiogithub
Language:VB.NET / C#算法
Graphics:EDGameEngineide
背景是圖畫裏陪襯主體事物的景象。oop
圖1-1 先畫個藍藍的天空優化
藍天、白雲和大地,程序最擅長這種色調單一的塗抹了。this
輪廓是物體的外周或圖形的外框。spa
圖2-2 勾勒人物和衣飾輪廓3d
如今 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 = {-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
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
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 }
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; } } }
演示視頻:黑白線條畫 (Bilibili)
演示視頻:古典人物畫 (Bilibili)
GitHub:EDGameEngine.AutoDraw
早期博客:程序如何實現自動繪圖
早期博客:更優秀的自動繪圖程序
創意分享:兒童塗鴉趕上程序繪圖