zoukankan      html  css  js  c++  java
  • 自动绘图AI:程序如何画出动漫美少女

    说明

      本文发布较早,查看最新动态,请关注 GitHub 项目。(2020 年 1 月 注)

    准备

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

      IDE:VisualStudio

      Language:VB.NET / C#

      Graphics:EDGameEngine

    第一节 背景

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

    图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

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

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

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

  • 相关阅读:
    hadoop-处理小文件
    hadoop 文件合并
    hadoop multipleoutputs
    超酷的 Vim 搜索技巧
    linux中DHCP服务配置文件/etc/dhcpd.conf详细说明
    cobbler启动问题
    MYSQL 5.5.32的单机多实例部署
    自动化运维之Cobbler自动化部署安装操作系统
    运维自动化之Cobbler系统安装使用详解[good]
    Cobbler自动部署主机系统
  • 原文地址:https://www.cnblogs.com/experdot/p/5779593.html
Copyright © 2011-2022 走看看