Ascii art是一种使用连续排列的ascii字符进行图形设计的技术。它可以显示在任意的文本框中。ascii art出现于上世纪70年代,最初是当时电脑显示技术不发达时用于显示简单图像的一种娱乐。后来逐渐流行开来,有了专门以此为兴趣的艺术家和研究者。

⣿⣿⣿⣿⣿⣿⢟⣡⣴⣶⣶⣦⣌⡛⠟⣋⣩⣬⣭⣭⡛⢿⣿⣿⣿⣿
⣿⣿⣿⣿⠋⢰⣿⣿⠿⣛⣛⣙⣛⠻⢆⢻⣿⠿⠿⠿⣿⡄⠻⣿⣿⣿
⣿⣿⣿⠃⢠⣿⣿⣶⣿⣿⡿⠿⢟⣛⣒⠐⠲⣶⡶⠿⠶⠶⠦⠄⠙⢿
⣿⠋⣠⠄⣿⣿⣿⠟⡛⢅⣠⡵⡐⠲⣶⣶⣥⡠⣤⣵⠆⠄⠰⣦⣤⡀
⠇⣰⣿⣼⣿⣿⣧⣤⡸⢿⣿⡀⠂⠁⣸⣿⣿⣿⣿⣇⠄⠈⢀⣿⣿⠿
⣰⣿⣿⣿⣿⣿⣿⣿⣷⣤⣈⣙⠶⢾⠭⢉⣁⣴⢯⣭⣵⣶⠾⠓⢀⣴
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣉⣤⣴⣾⣿⣿⣦⣄⣤⣤⣄⠄⢿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠈⢿
⣿⣿⣿⣿⣿⣿⡟⣰⣞⣛⡒⢒⠤⠦⢬⣉⣉⣉⣉⣉⣉⣉⡥⠴⠂⢸
⠻⣿⣿⣿⣿⣏⠻⢌⣉⣉⣩⣉⡛⣛⠒⠶⠶⠶⠶⠶⠶⠶⠶⠂⣸⣿
⣥⣈⠙⡻⠿⠿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⠿⠛⢉⣠⣶⣶⣿⣿
⣿⣿⣿⣶⣬⣅⣒⣒⡂⠈⠭⠭⠭⠭⠭⢉⣁⣄⡀⢾⣿⣿⣿⣿⣿⣿

在R#语言之中使用ASCII Art函数

在R#语言的基础环境之中,已经默认内置了对图像抽取特征转换为ASCII字符串的功能函数,可以直接使用。首先来祭出我们今天进行图像处理代码测试的主角,原始图像大概是长这样子的:

然后我们将图像通过下面的一段R#脚本进行处理,转换为纯ASCII字符串用于显示上面的图像内容:

require(graphics2D);

"1537192287563.jpg"
|> readImage
|> resizeImage(factor = 1)
|> asciiArt
|> writeLines(con = "bilibili.txt")
;

将上面的脚本所产生的纯文本文件使用Visual Studio打开,优雅!

ASCII Art的方法实现

执行上面的转换过程,如果大家查看asciiArt函数的原始代码的话,其实会发现就只进行了两个简单的图像处理步骤就可以了:

''' <summary>
''' convert bitmap to ascii characters
''' </summary>
''' <param name="image"></param>
''' <returns></returns>
<ExportAPI("asciiArt")>
<RApiReturn(GetType(String))>
Public Function asciiArt(image As Object, Optional charSet As String = "+-*.", Optional env As Environment = Nothing) As Object
    Dim bitmap As Image

    If image Is Nothing Then
        Return Internal.debug.stop("the required bitmap data can not be nothing!", env)
    ElseIf TypeOf image Is Image Then
        bitmap = DirectCast(image, Image)
    ElseIf TypeOf image Is Bitmap Then
        bitmap = CType(DirectCast(image, Bitmap), Image)
    Else
        Return Message.InCompatibleType(GetType(Bitmap), image.GetType, env)
    End If

    Dim font As New Font(FontFace.Consolas, 10)
    Dim pixels As WeightedChar() = charSet.GenerateFontWeights(font)

    Return bitmap _
        .GetBinaryBitmap() _
        .Convert2ASCII(pixels)
End Function

首先第一个步骤,就是对原始图像调用函数GetBinaryBitmap进行二值化处理。通过这一步处理,我们可以将目标图像中的一些特征轮廓信息给提取出来,方便进行ASCII字符串转换。在进行二值化处理之后,就可以通过简单的调用Convert2ASCII函数来产生表示整幅二值化的图片的文本字符串数据了。

图像至ASCII字符串转换的原理

如果我们观察我们的ASCII字符的话,会发现:在一个字符所占据的一个方格内,不同的字符所占的面积是不一样的。有些字符所占据的面积比较大,整个方格的像素会比较多,显得会比较黑。有些字符所占据的面积比较小,整个方格中的像素数量会稍微少一些,显得比较白。举个例子,例如字符A与<space>字符之间所使用到的像素点的数量肯定会不一样,A的像素点数量肯定多余空格的像素点数量。我们近似的将像素点所使用的数量的多少用来表示原始图像上的一个像素点的亮度信息,就可以将一幅图转换为对应的ASCII字符串了。那,在ASCII art图像转换程序之中,就是依据这个原理来进行图像的近似转换操作。

在Ascii art转换程序之中,存在有这样子的一个字符对亮度的映射对象:

''' <summary>
''' a pixel char
''' </summary>
Public Class WeightedChar

    ''' <summary>
    ''' a char that represent a pixel on the source bitmap
    ''' </summary>
    ''' <returns></returns>
    Public Property Character As String
    Public Property CharacterImage As Bitmap
    ''' <summary>
    ''' the gray scale value
    ''' </summary>
    ''' <returns></returns>
    Public Property Weight As Double

    Public Overrides Function ToString() As String
        Return $"{Character} ({Weight})"
    End Function

    <MethodImpl(MethodImplOptions.AggressiveInlining)>
    Friend Shared Function getDefaultCharSet() As [Default](Of  WeightedChar())
        Return CharSet.GenerateFontWeights()
    End Function
End Class

在上面所展示的class对象之中,实现了对character到weight像素点亮度信息之间的映射操作。后面的所有的转换过程就是依据这个映射对应关系的基础上来完成的。

建立映射关系

为了选取字符放置到特定的位置用来表示原始图像中的某一个像素点,我们在最开始需要建立起这个映射关系。这个过程很简单,我们只需要将目标字符绘制在一个临时的图像上,然后计算出所使用到的像素点的数量即可,例如:

<Extension> 
Private Function GetWeight(c As Char, size As SizeF) As Double
    Dim charImage = HelperMethods.DrawText(c.ToString(), Color.Black, Color.White, size)
    Dim totalsum As Double = 0

    Using btm As BitmapBuffer = BitmapBuffer.FromImage(charImage)
        For i As Integer = 0 To btm.Width - 1
            For j As Integer = 0 To btm.Height - 1
                Dim pixel As Color = btm.GetPixel(i, j)
                totalsum += (CInt(pixel.R) + CInt(pixel.G) + CInt(pixel.B)) \ 3
            Next
        Next
    End Using

    ' Weight = (sum of (R+G+B)/3 for all pixels in image) / Area. (Where Area = Width*Height )
    Return totalsum / (size.Height * size.Width)
End Function

转换图像为字符串

* ALGORITHM:
*
*  1- Get target Image size (w=Width,h=Height)
*  2- Create Result Image with white background and size W = w*character_image_width
*                                                        H = h*character_image_height
*  3- Create empty string to hold the text
*
*  4- for (int j=0;j=target_Image_Height;j++)  --> ALL ROWS
*       5- Create row text string
*       for (int i=0;i=target_Image_Width;i++) --> ALL COLUMNS
*          6- Get target pixel weight
*          7- Get closest weight from character list
*          8- Paste character image in position w = i*character_image_width
*                                               h = j*character_image_height
*            ¡¡ Be careful with coordinate system when placing Images !!
*          9- Add (string)character to row text string
*       10- Add row text string to text holding string
*  11 - return resulting Image & Text

那,现在我们已经具有了一系列的已经建立起映射关系的ASCII字符的集合。现在我们就可以开始进行整个的图像转换工作了。首先,我们需要明确的一点就是,我们所有所进行转换的图片都应该是经过二值化处理之后的黑白图像。在之前的映射操作之中,我们采集到了一个字符所占用的一个方格内的像素点利用信息。实际上在这个阶段,我们以相同的方法,将原始图像中的某一个方格内的像素点信息也按照相同的方法采集出来,再取出统计信息与字符相差最小的字符出来是不是就可以实现整个转换过程了呢?答案是的。下面是基于像素点的统计信息进行字符转换的过程,整个实现原理和过程代码都非常简单:

Using blackAndWhite As BitmapBuffer = BitmapBuffer.FromImage(monoImage)
    For j As Integer = 0 To monoImage.Height - 1
        Dim line As New List(Of String)() From {}

        For i As Integer = 0 To monoImage.Width - 1
            ' COLUMN
            Dim pixel As Color = blackAndWhite.GetPixel(i, j)
            Dim targetvalue As Double = (CInt(pixel.R) + CInt(pixel.G) + CInt(pixel.B)) \ 3
            Dim closestchar As WeightedChar = characters _
                .Where(Function(t)
                           Return stdNum.Abs(t.Weight - targetvalue) = characters.Min(Function(e) stdNum.Abs(e.Weight - targetvalue))
                       End Function) _
                .FirstOrDefault()

            Call line.Add(closestchar.Character)
        Next

        Call out.WriteLine(line.JoinBy(""))
    Next
End Using
Latest posts by xie guigang (see all)

Attachments

No responses yet

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注