天天看點

Python 實作圖檔轉字元畫

Python 實作圖檔轉字元畫

文章目錄

  • ​​Python 實作圖檔轉字元畫​​
  • ​​項目背景:​​
  • ​​項目原理:​​
  • ​​處理指令行參數​​
  • ​​處理圖檔:​​

項目背景:

本項目用 50 行 Python 代碼完成圖檔轉字元畫小工具。通過本項目将學習到 Python 基礎,pillow 庫的使用,argparse 庫的使用

知識點:

  1. Python 基礎
  2. pillow 庫的使用
  3. argparse 庫的使用

項目環境

  • Python 3.5
  • pillow 5.1.0

PIL 是一個 Python 圖像處理庫,是本課程使用的重要工具,使用下面的指令來安裝 pillow(PIL)庫:

pip install --upgrade pip
pip install pillow      

項目原理:

字元畫是一系列字元的組合,我們可以把字元看作是比較大塊的像素,一個字元能表現一種顔色(為了簡化可以這麼了解),字元的種類越多,可以表現的顔色也越多,圖檔也會更有層次感。

問題來了,我們是要轉換一張彩色的圖檔,這麼多的顔色,要怎麼對應到單色的字元畫上去?這裡就要介紹灰階值的概念了。

灰階值:指黑白圖像中點的顔色深度,範圍一般從0到255,白色為255,黑色為0,故黑白圖檔也稱灰階圖像。

另外一個概念是 RGB 色彩:

RGB色彩模式是工業界的一種顔色标準,是通過對紅®、綠(G)、藍(B)三個顔色通道的變化以及它們互相之間的疊加來得到各式各樣的顔色的,RGB即是代表紅、綠、藍三個通道的顔色,這個标準幾乎包括了人類視力所能感覺的所有顔色,是目前運用最廣的顔色系統之一。- 來自百度百科介紹

我們可以使用灰階值公式将像素的 RGB 值映射到灰階值(注意這個公式并不是一個真實的算法,而是簡化的 sRGB IEC61966-2.1 公式,真實的公式更複雜一些,不過在我們的這個應用場景下并沒有必要):

gray = 0.2126 * r + 0.7152 * g + 0.0722 * b      

這樣就好辦了,我們可以建立一個不重複的字元清單,灰階值小(暗)的用清單開頭的符号,灰階值大(亮)的用清單末尾的符号。

首先導入必要的庫,argparse 庫是用來管理指令行參數輸入的

from PIL import Image
import argparse      
處理指令行參數

我們首先使用 argparse 處理指令行參數,目标是擷取輸入的圖檔路徑、輸出字元畫的寬和高以及輸出檔案的路徑:

# 首先,建構指令行輸入參數處理 ArgumentParser 執行個體
parser = argparse.ArgumentParser()

# 定義輸入檔案、輸出檔案、輸出字元畫的寬和高
parser.add_argument('file')     #輸入檔案
parser.add_argument('-o', '--output')   #輸出檔案
parser.add_argument('--width', type = int, default = 80) #輸出字元畫寬
parser.add_argument('--height', type = int, default = 80) #輸出字元畫高

# 解析并擷取參數
args = parser.parse_args()

# 輸入的圖檔檔案路徑
IMG = args.file

# 輸出字元畫的寬度
WIDTH = args.width

# 輸出字元畫的高度
HEIGHT = args.height

# 輸出字元畫的路徑
OUTPUT = args.output      

實作 RGB 值轉字元的函數

首先将 RGB 值轉為灰階值,然後使用灰階值映射到字元清單中的某個字元。

下面是我們的字元畫所使用的字元集,一共有 70 個字元,為了友善寫入到實驗環境中,可以使用實驗環境右邊工具欄上的​​剪切闆​​​将以下代碼内容拷貝到實驗環境中,注意需要使用右鍵複制和拷貝,不要使用 ​

​Ctrl-C/Ctrl-V​

​ 快捷鍵。字元的種類與數量可以自己根據字元畫的效果反複調試:

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")      

下面是 RGB 值轉字元的函數,注意 alpha 值為 0 的時候表示圖檔中該位置為空白:

def get_char(r,g,b,alpha = 256):

    # 判斷 alpha 值
    if alpha == 0:
        return ' '

    # 擷取字元集的長度,這裡為 70
    length = len(ascii_char)
    
    # 将 RGB 值轉為灰階值 gray,灰階值範圍為 0-255
    gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)

    # 灰階值範圍為 0-255,而字元集隻有 70
    # 需要進行如下處理才能将灰階值映射到指定的字元上
    unit = (256.0 + 1)/length
    
    # 傳回灰階值對應的字元
    return ascii_char[int(gray/unit)]      
處理圖檔:

完成上面的代碼之後,我們進入到最後一個步驟,對圖檔進行處理。

這一個步驟我們放入到 ​

​if __name__ == '__main__':​

​ 代碼塊中(表示如果 ascii.py 被當作 python 子產品 import 的時候,這部分代碼不會被執行)。圖檔的處理步驟如下:

  1. 首先使用 PIL 的 Image.open 打開圖檔檔案,獲得對象 im
  2. 使用 PIL 庫的 im.resize() 調整圖檔大小對應到輸出的字元畫的寬度和高度,注意這個函數第二個參數使用 Image.NEAREST,表示輸出低品質的圖檔。
  3. 周遊提取圖檔中每行的像素的 RGB 值,調用 getchar 轉成對應的字元
  4. 将所有的像素對應的字元拼接在一起成為一個字元串 txt
  5. 列印輸出字元串 txt
  6. 如果執行時配置了輸出檔案,将打開檔案将 txt 輸出到檔案,如果沒有,則預設輸出到 ​

    ​output.txt​

    ​ 檔案

這個過程中需要注意的是調用 getchar 時候的參數是通過 PIL 庫的 getpixel 擷取的,見如下代碼:

char = get_char(*im.getpixel((j,i)))      

其中 ​

​im.getpixel((j,i))​

​​ 擷取得到坐标 ​

​(j,i)​

​​ 位置的 RGB 像素值(有的時候會包含 alpha 值),傳回的結果是一個元組,例如 ​

​(1,2,3)​

​​ 或者 ​

​(1,2,3,0)​

​​。我們使用 ​

​*​

​ 可以将元組作為參數傳遞給 get_char,同時元組中的每個元素都對應到 get_char 函數的每個參數。

該部分的代碼實作如下(注意 name 和 main 前後都是兩個下劃線):

if __name__ == '__main__':
    
    # 打開并調整圖檔的寬和高
    im = Image.open(IMG)
    im = im.resize((WIDTH,HEIGHT), Image.NEAREST)

    # 初始化輸出的字元串
    txt = ""
    
    # 周遊圖檔中的每一行
    for i in range(HEIGHT):
        # 周遊該行中的每一列
        for j in range(WIDTH):
            # 将 (j,i) 坐标的 RGB 像素轉為字元後添加到 txt 字元串
            txt += get_char(*im.getpixel((j,i)))
        # 周遊完一行後需要增加換行符
        txt += '\n'
    # 輸出到螢幕
    print(txt)
    
    # 字元畫輸出到檔案
    if OUTPUT:
        with open(OUTPUT,'w') as f:
            f.write(txt)
    else:
        with open("output.txt",'w') as f:
            f.write(txt)      

下面是 項目 的完整代碼,供參考:

# -*- coding=utf-8 -*-

from PIL import Image
import argparse

#指令行輸入參數處理
parser = argparse.ArgumentParser()

parser.add_argument('file')     #輸入檔案
parser.add_argument('-o', '--output')   #輸出檔案
parser.add_argument('--width', type = int, default = 80) #輸出字元畫寬
parser.add_argument('--height', type = int, default = 80) #輸出字元畫高

#擷取參數
args = parser.parse_args()

IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")

# 将256灰階映射到70個字元上
def get_char(r,g,b,alpha = 256):
    if alpha == 0:
        return ' '
    length = len(ascii_char)
    gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)

    unit = (256.0 + 1)/length
    return ascii_char[int(gray/unit)]

if __name__ == '__main__':

    im = Image.open(IMG)
    im = im.resize((WIDTH,HEIGHT), Image.NEAREST)

    txt = ""

    for i in range(HEIGHT):
        for j in range(WIDTH):
            txt += get_char(*im.getpixel((j,i)))
        txt += '\n'

    print(txt)
    
    #字元畫輸出到檔案
    if OUTPUT:
        with open(OUTPUT,'w') as f:
            f.write(txt)
    else:
        with open("output.txt",'w') as f:
            f.write(txt)      

運作準備環境:

Python 實作圖檔轉字元畫

運作準備的圖檔:

Python 實作圖檔轉字元畫

運作轉化成果

Python 實作圖檔轉字元畫