自動繪圖AI:程序如何畫出動漫美少女

說明

  本文發佈時間較早,查看最新動態請關注 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
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

視頻

  演示視頻:黑白線條畫 (Bilibili)

  演示視頻:古典人物畫 (Bilibili)

附錄

  GitHub:EDGameEngine.AutoDraw

  早期博客:程序如何實現自動繪圖 

  早期博客:更優秀的自動繪圖程序

  創意分享:兒童塗鴉趕上程序繪圖 

相關文章
相關標籤/搜索