你的位置:首页 > ASP.net教程

[ASP.net教程]自动绘图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 FunctionEnd Class''' <summary>''' 线条扫描方式''' </summary>Public Enum ScanMode  ''' <summary>  ''' 矩形扫描  ''' </summary>  Rect  ''' <summary>  ''' 圆形扫描  ''' </summary>  CircleEnd 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 SubEnd 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创意分享:儿童涂鸦遇上程序绘图