自动绘图AI:程序如何画出动漫美少女

说明

  本文发布较早,了解最新动态,请查看 GitHub 项目。(2024 年 4 月 注)

准备

  全新的图形引擎与 AI 算法,高效流畅地绘出任何一副美丽的图像。

  IDE:VisualStudio

  Language:VB.NET / C#

  Graphics:AutoPaint.NET

第一节 背景

  背景是图画里衬托主体事物的景象。

图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 = {-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

视频

附录

  GitHub:EDGameEngine.AutoDraw

  早期博客:程序如何实现自动绘图 

  早期博客:更优秀的自动绘图程序

  创意分享:儿童涂鸦遇上程序绘图 

posted @ 2016-09-26 22:13  ExperDot  阅读(12823)  评论(12编辑  收藏  举报