简体   繁体   English

在 Python 中将 PPM 图像转换为 ASCII 艺术

[英]PPM image to ASCII art in Python

I have to make a program that reads in a file from the command line and covert it to ASCII art.我必须制作一个从命令行读入文件并将其转换为 ASCII 艺术的程序。 I am using PPM format and here is a link to the project .我使用的是 PPM 格式,这里是项目的链接。

Here is what I have so far:这是我到目前为止所拥有的:

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])

I an getting an error that says 'int' object is not iterable, is there is an easy way to fix this and how would someone recommend printing this out while preserving the image.我收到一个错误,说“int”对象不可迭代,是否有一种简单的方法可以解决这个问题,以及有人建议如何在保留图像的同时打印出来。

And the last thing I am unsure about is how to preserve the width and height of the picture.我不确定的最后一件事是如何保留图片的宽度和高度。

Any help would be greatly appreciated.任何帮助将不胜感激。

You can use image-to-ansi.py for the conversion.您可以使用image-to-ansi.py进行转换。

First, download image-to-ansi.py :首先,下载image-to-ansi.py

wget https://gist.githubusercontent.com/klange/1687427/raw/image-to-ansi.py

Save this script as ppmimage.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):
    """ http://stackoverflow.com/a/434314/309483 """
    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")

You can test the script like this (this assumes you have netpbm and imagemagick installed):您可以像这样测试脚本(假设您安装了netpbmimagemagick ):

convert -resize $(($COLUMNS*2))x$(($LINES*2)) logo: pnm:- | pnmtoplainpnm | python3 ppmimage.py

On my machine, it looks like this:在我的机器上,它看起来像这样:

Xterm 中显示的 ImageMagick 徽标

Here you have your code modified and working.在这里,您的代码已修改并正常工作。
It is not optimal, it does not take into account the presence of more or less comments in the header and there is not exception handling but it is a start.它不是最佳的,它没有考虑标题中或多或少的注释,也没有异常处理,但它是一个开始。

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')

These are the ppm figure and the ASCII Art result这些是 ppm 数字和 ASCII 艺术结果

在此处输入图片说明

And the micro ppm file I used for the example:以及我用于示例的微型 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

I wrote one of these in C# a while ago and I calculated the character to use with this formula:不久前,我用 C# 编写了其中一个,并计算了要与此公式一起使用的字符:

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

As for the width and height, you could average every two lines of vertical pixels to halve the height.至于宽度和高度,您可以平均每两行垂直像素将高度减半。

Edit 1: in response to comment: The basic idea is that it scales your RGB value from 0 to 255 to 0 to the length of the array and uses that as the index.编辑 1:回应评论:基本思想是它将 RGB 值从 0 到 255 缩放到 0 到数组的长度,并将其用作索引。

Edit 2: Updated to correct that I was ignoring your grayscale normalization.编辑 2:更新以纠正我忽略了您的灰度归一化。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM