Python綜合應用:教你用字符打印一張懷舊風格的照片

1. 前言
第一次在學校機房裏見到計算機,仍是上古時期。計算機型號大概是LASER-310吧,有點記不清了。那會兒,顯示器仍是單色的,只能顯示文本,每行最多顯示80個字符。想看圖片,印象中只能用針式打印機打印在兩側穿孔的寬行打印紙上,每一個像素用一個字符表示,不一樣的字符表明不一樣的灰度,就像下圖這個樣子。有沒有感受到濃郁古風呢?其實,隨便一張照片,十幾行Python代碼,你也能夠打印出這樣的效果,還能夠保存成文件。下面,我就一步一步地演示一下。python

2. 打開圖片,轉爲灰度模式

Python用於圖像處理的模塊有不少,最經常使用的當屬PIL和PyOpenCV了。本案使用PIL模塊來打開圖像:數組

1 >>> from PIL import Image
2 >>> im = Image.open('xufive.jpg')
3 >>> im.size
4 (979, 1248)
5 >>> im.mode
6 'RGB'

im就是打開的圖像對象,im.size是圖像的分辨率,im.mode是圖像模式。咱們知道,計算機圖像有不少種顏色模式,RGB是最多見的彩色圖像模式。打印字符圖片的話,須要將RGB模式轉爲灰度模式:編輯器

1 >>> im = im.convert('L')
2 >>> im.mode
3 'L'

3. 改變分辨率

打印字符圖片,須要考慮顯示器每行顯示的字符個數。假定屏幕水平分辨率爲1920,每一個字符寬度佔8個像素,每行能夠顯示240個字符。綜合考量,咱們設定每行顯示120個字符。這就須要咱們將灰度圖片的寬度設置爲120個像素,那麼圖像高度的像素數height應爲:ui

 

1 width = 120
2 height = int(width*im.size[1]/im.size[0])

 

按照新的分辨率生成圖像對象:spa

 

1 >>> im = im.resize((width, height))
2 >>> im.size
3 (120, 152)

 

4. 反白處理
灰度模式下,每一個像素的值域範圍是0~255,共有256級灰度。考慮到屏幕背景色多是深色的,也多是淺色的,咱們須要提供圖像反白處理的手段。所謂反白處理,就是用灰度最大值255減去每個像素的灰度值做爲該像素新的灰度值。遍歷每個像素,當然能夠實現反白,但速度會很慢。本案使用NumPy數組的廣播技術,能夠顯著提高處理速度。咱們先把PIL圖像對象轉成NumPy數組:命令行

 

1 >>> import numpy as np
2 >>> arr = np.array(im)
3 >>> arr.shape
4 (152, 120)
5 >>> arr.dtype
6 dtype('uint8'

 

須要特別說明的是,PIL對象的圖像分辨率是120x152,表示圖像寬度120像素,高度152像素;轉成NumPy數組以後,數組的shape則是(152,120),表示圖像有152行(對應高度),120列(對應寬度)。雖然PIL對象和NumPy數組關於行列的概念不一致,但表達的物理意義是相同的。code

利用NumPy數組的廣播技術實現反白處理,只需一行代碼,而且瞬間完成:對象

1 arr = 255 - arr

 

5. 肯定灰度-字符映射表

在顯示器上,字符是由點陣組成的。每一個字符的亮點(或暗點)不一樣,能夠用來表示不一樣的灰度。本案使用了下面8個字符表示不一樣的灰度:blog

 

1 >>> chs = np.array([' ', '.', '-', '+', '=', '*', '#', '@'])
2 >>> chs.dtype
3 dtype('<U1')

 

8個不一樣的字符,只能表示8級灰度,所以須要將像素的256級灰度值轉換爲8級:圖片

 

1 >>> arr = arr/32
2 >>> arr = arr.astype(np.uint8)
3 >>> arr.min(), arr.max()
4 (0, 7)

 

6. 灰度轉字符

接下來須要將值域範圍在0~7之間的每個像素轉爲灰度-字符映射表中對應的字符。一樣的,咱們能夠用兩層嵌套的循環結構來完成,不過更好的選擇是用NumPy數組的矢量化特性來實現。本例展現了NumPy數組很是少見的一種應用方式,我不多見到有人這樣應用。

 

1 >>> arr = chs[arr]
2 >>> arr.shape
3 (152, 120)
4 >>> arr.dtype
5 dtype('<U1')

 

7. 打印

有了上述鋪墊,打印天然是水到渠成了:

 

1 >>> for i in range(arr.shape[0]):
2     for j in range(arr.shape[1]):
3         print(arr[i,j], end='')
4     print()

 

8. 保存爲文件

若是在顯示終端上打印不方便觀看的話,還能夠將字符數據保存成文件:

 

1 >>> with open('xufive.txt', 'w') as fp:
2     for line in arr.tolist():
3         fp.write(''.join(line))
4         fp.write('\n')

 

 下圖是輸出到文本文件,在編輯器中顯示的效果。

 

 

9. 完整代碼

在不一樣的運行環境中,最終圖像顯示的寬高比和原圖會有差別。爲了抵消差別,我在下面的代碼中增長了一個矯正係數k,能夠經過調整這個參數,得到滿意的顯示效果。

 

# -*- coding: utf-8 -*-

from PIL import Image
import numpy as np

def print_photo(photo_file, width=120, k=1.0, reverse=False, outfile=None):
    """打印照片,默認120個字符寬度"""
    
    im = Image.open(photo_file).convert('L') # 打開圖片文件,轉爲灰度格式
    height = int(k*width*im.size[1]/im.size[0]) # 打印圖像高度,k爲矯正係數,用於矯正不一樣終端環境像素寬高比
    arr = np.array(im.resize((width, height ))) # 轉爲NumPy數組
    if reverse: # 反色處理
        arr = 255 - arr
    
    chs = np.array([' ', '.', '-', '+', '=', '*', '#', '@']) #灰度-字符映射表
    arr= chs[(arr/32).astype(np.uint8)] # 灰度轉爲對應字符
    
    if outfile:
        with open(outfile, 'w') as fp:
            for row in arr.tolist():
                fp.write(''.join(row))
                fp.write('\n')
    else:
        for i in range(arr.shape[0]): # 逐像素打印
            for j in range(arr.shape[1]):
                print(arr[i,j], end=' ')
            print()

if __name__ == '__main__':
    print_photo('xufive.jpg', width=360, k=0.5, outfile='xufive.txt')

 

 下圖是在命令行窗口顯示的效果。

 

 

 更多精彩文章及源碼關注公衆號python社區營

相關文章
相關標籤/搜索