Python中将PPM图片转换为ASCII艺术

8

我需要编写一个程序,从命令行读取文件并将其转换为ASCII艺术格式。我正在使用PPM格式,这里有一个链接指向该项目:项目链接

以下是我目前的进展:

import sys

def main(filename):
    image = open(filename)
    #reads through the first three lines
    color = image.readline().splitlines()
    size_width, size_height = image.readline().split()
    max_color = image.readline().splitlines()

    #reads the body of the file
    pixels = image.read().split()
    red = 0
    green = 0
    blue = 0
    r_g_b_value = []
    #pulls out the values of each tuple and coverts it to its grayscale value 
    for i in pixels:
        if i !=  "\n" or " ":
            if len(i) == 3:
                red = int(i[0]) * .3
                green = int(i[1]) * .59
                blue = int(i[2]) * .11
            elif len(i) == 2:
                red == int(i[0]) * .3
                green == int(i[1]) *.59
                blue == 0
            elif len(i) == 1:
                red == int(i[0]) * .3
                green == 0
                blue == 0

            r_g_b_value = [red + green + blue]

            character = []
        for j in len(r_g_b_value):
            if int(j) <= 16:
                character = " "
            elif int(j) > 16 and int(j) <= 32:
                character = "."
            elif int(j) > 32 and int(j) <= 48:
                character = ","
            elif int(j) > 48 and int(j) <= 64:
                charcter = ":"
            elif int(j) > 64 and int(j) <= 80:
                character = ";"
            elif int(j) > 80 and int(j) <= 96:
                character = "+"
            elif int(j) > 96 and int(j) <= 112:
                character = "="
            elif int(j) > 112 and int(j) <= 128:
                character = "o"
            elif int(j) > 128 and int(j) <= 144:
                character = "a"
            elif int(j) > 144 and int(j) <= 160:
                character = "e"
            elif int(j) > 160 and int(j) <= 176:
                character = "0"
            elif int(j) > 176 and int(j) <= 192:
                character = "$"
            elif int(j) > 192 and int(j) <= 208:
                character = "@"
            elif int(j) > 208 and int(j) <= 224:
                character = "A"
            elif int(j) > 224 and int(j) <= 240:
                character = "#"
            else:
                character = "M"

            grayscale = character
            print(grayscale)

main(sys.argv[1])

我得到了一个错误提示,说 'int' 对象不是可迭代的,有没有简单的方法来解决这个问题,同时如何推荐在保留图片的情况下打印出它。
另外我不确定如何保留图片的宽度和高度。
非常感谢任何帮助。

我在这里迷失了方向,似乎当我打印r_g_b_value时,它每行只打印一个值。 - asmith
我在思考如果我使用for j in range(r_g_b__values)并且接着使用一系列的if/else语句将j转换为整数,然后将值设置为可用字符列表中的值,这样做是否可行,以及最好的保存字符的方法是什么。是创建一个新列表吗? - asmith
如果您仍然遇到错误,完整的回溯信息将会很有帮助。 - David Eyk
我已经解决了错误,但您有没有想法在保持图像的同时打印出此字符? - asmith
3个回答

9
你可以使用 image-to-ansi.py 进行转换。
首先,下载 image-to-ansi.py:
wget https://gist.githubusercontent.com/klange/1687427/raw/image-to-ansi.py

将此脚本保存为ppmimage.py:
# Parses a PPM file and loads it into image-to-ansi.py
import re, itertools

sep = re.compile("[ \t\r\n]+")

def chunks(iterable,size):
    """ https://dev59.com/VHRC5IYBdhLWcg3wCMc6#434314 """
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

""" Emulates the Image class from PIL and some member functions (`getpixel`, `size`). """
class Image:
    """ This class emulates the PIL Image class, and it can parse "plain" PPM's.
        You can use PIL instead. """
    @staticmethod
    def fromstdin():
        return Image()
    def __init__(self): # http://netpbm.sourceforge.net/doc/ppm.html
        self.entities = sep.split("\n".join(list(filter(lambda x: not x.startswith("#"), sys.stdin.read().strip().split("\n")))))
        self.size = tuple(list(map(int,self.entities[1:3])))
        self.high = int(self.entities[3]) # z.b. 255
        self.pixels = list(map(lambda x: tuple(map(lambda y: int(int(y) / self.high * 255), x)), list(chunks(self.entities[4:], 3))))
    def getpixel(self, tupl):
        x = tupl[0]
        y = tupl[1]
        pix = self.pixels[y*self.size[0]+x]
        return pix

image_to_ansi = __import__("image-to-ansi") # __import__ because of minuses in filename. From https://gist.github.com/1687427

if __name__ == '__main__':
    import sys
    #import Image
    im = Image.fromstdin() # use Image.open from PIL if using PIL
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            p = im.getpixel((x,y))
            h = "%2x%2x%2x" % (p[0],p[1],p[2])
            short, rgb = image_to_ansi.rgb2short(h)
            sys.stdout.write("\033[48;5;%sm " % short)
        sys.stdout.write("\033[0m\n")
    sys.stdout.write("\n")

您可以这样测试脚本(假设您已经安装了netpbmimagemagick): convert -resize $(($COLUMNS*2))x$(($LINES*2)) logo: pnm:- | pnmtoplainpnm | python3 ppmimage.py 在我的机器上,它看起来像这样: ImageMagick logo shown in Xterm

4

在这里,您已经修改并使代码工作。
它不是最优的,它没有考虑到头部有更多或更少的注释,并且没有异常处理,但这是一个开始。

import sys
import numpy as np

RGBS = range(16, 255, 16)
CHARS = [' ', '.', ',', ':', ';', '+', '=', 'o',
         'a', 'e', '0', '$', '@', 'A', '#']
FACTORS = [.3, .59, .11]

def main(filename):
    image = open(filename)
    #reads header lines
    color = image.readline()
    _ = image.readline()
    size_width, size_height = image.readline().split()
    max_color = image.readline()

    size_width = int(size_width)
    max_color = int(max_color)

    #reads the body of the file
    data = [int(p) for p in image.read().split()]
    #converts to array and reshape
    data = np.array(data)
    pixels = data.reshape((len(data)/3, 3))
    #calculate rgb value per pixel
    rgbs = pixels * FACTORS
    sum_rgbs = rgbs.sum(axis=1)
    rgb_values = [item * 255 / max_color for item in sum_rgbs]

    grayscales = []
    #pulls out the value of each pixel and coverts it to its grayscale value 
    for indx, rgb_val in enumerate(rgb_values):
        #if max width, new line
        if (indx % size_width) == 0 : grayscales.append('\n')    

        for achar, rgb in zip(CHARS, RGBS):
            if rgb_val <= rgb:
                character = achar
                break
            else:
                character = 'M'

        grayscales.append(character)

    print ''.join(grayscales)

main('test.ppm')

以下是ppm数字和ASCII艺术结果: enter image description here 以下是我用于示例的微型ppm文件:
P3
# test.ppm
4 4
15
 0  0  0    0  0  0    0  0  0   15  0 15
 0  0  0    0 15  7    0  0  0    0  0  0
 0  0  0    0  0  0    0 15  7    0  0  0
15  0 15    0  0  0    0  0  0    0  0  0

这个回答中提供的解决方案不支持颜色。请参考我的答案以牺牲对可怕的Windows控制台的支持并获得颜色支持。 - Janus Troelsen
@JanusTroelsen 谢谢,但请注意OP在询问ASCII艺术而非ANSI艺术。 - joaquin

0

我之前用C#写过其中一个,我使用了这个公式来计算要使用的字符:

index_into_array = (int)(r_g_b_value * (chars_array_length / (255.0)));

关于宽度和高度,您可以对每两行垂直像素取平均值来减半高度。
编辑1:回应评论: 基本思路是将RGB值从0到255缩放到0到数组长度,并将其用作索引。
编辑2:更新以更正我忽略了您的灰度归一化。

我对编程还不是很熟练,所以我不确定我是否完全理解了。我将可用列表设置为chars_array_length,然后使用您发布的函数,那么我需要打印该值以获取字符吗?还是我需要做其他事情。再次感谢您的帮助。 - asmith
@asmith 更新了答案以进一步解释。 - 0x5f3759df
我再次为我的技能不足道歉,但我遇到了这个错误文件“other.py”,第40行,在<module>中: main(sys.argv[1]) 文件“other.py”,第36行,在main中: current_character = character_choices[int((r_g_b_value * (len(character_choices) /(255.0*3))))] TypeError: can't multiply sequence by non-int of type 'float' - asmith
是的,我不会Python,你需要自己将我发布的算法转换为Python代码,或者更新你的问题或提出另一个问题。 - 0x5f3759df

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接