歡迎訪問集智主站:集智,通向智慧時代的引擎
注意!
本文所有程式碼均可在集智原貼中執行除錯,有需要的同學可以點選這裡前往原貼
01. ASCII藝術
ASCII的全稱是American Standard Code for Information Interchange,即美國資訊交換標準碼。是由軍用電報編碼發展而來,併成為最通用的現代計算機編碼系統。
在顯示卡還不能摧毀航母戰鬥群的年代,計算機還主要用來計算導彈彈道和衛星軌道,其圖形處理能力是非常弱的,甚至還不如今天高階一點的示波器。但這並不能阻止人類對美的追求,正如四萬年前的莽荒也沒有耽誤拉斯科洞窟壁畫的誕生。
在我剛上網那陣,有個流傳很廣的帖子,是教你觀看命令列裡的《星球大戰》,而這部星戰正是由ASCII編碼中的字元構成的,這被稱作ASCII art
在鬥圖代替打字、點播變成直播甚至VR/AR的今天,圖形的處理已經不再是瓶頸,反而成為了新的增長點。道高一尺魔高一丈,技術的進步也帶來了有害資訊,比如廣大家長朋友們特別關注的色情資訊。剛開始色情的鑑定是由人工完成的,李迪同志就是在《暴走大事件》中扮演鑑黃師唐馬儒而一炮走紅。
但是,基於人工智慧的影象識別也在飛速進步,自動鑑黃已經投入實用,鑑黃師的職業前景就面臨著嚴重的威脅!
02. 字元畫
把一張照片轉換為字元畫,大致需要三步:
- 將圖片尺寸壓縮到字元畫所能接受的量級;
- 彩色圖轉換為灰度圖,灰度是一個0-255的數值;
- 建立灰度值與字符集之間的對映關係。
早年間以上步驟還需要專門做一個小軟體來完成,而現在只需要簡單的程式碼就可以直接在瀏覽器中實現。
因為字元畫的本質是“字元”,可以用文字編輯器開啟,對於計算機來說,他們和其他的字元沒有任何區別,“畫”的性質只是由人類的想象力後天賦予的。所以,如果你用字元畫的形式傳播春宮圖,還是需要唐馬儒。
03. Python擴充套件庫
針對字元畫生成的基礎功能,Python已經內建了很多優秀的擴充套件庫,可以在此基礎上直接呼叫,而無需重複製造輪子。
-
相簿PIL(Python Imaging Library)
基本的影象處理功能。
-
網庫urllib
獲取網路資源,如下載網上的圖片。
from PIL import Image # 影象處理模組
from urllib import request #網路請求模組
複製程式碼
沒有必要把0-255的灰度值一一對應為不同的字元,一般十幾個也就足夠了。這裡做如下定義:
ASCII_CHARS = [' ', '#', '?', '%', '.', '+', '.', '*', ':', ',', '@']
複製程式碼
這裡將最低的灰度段對映為' '
(空格),也就是原圖中空白或接近空白的部分,在字元畫中也會會變成空白;而原圖的黑點則變成@
。
讀者朋友可以在後文的開放程式碼環境中想要定義自己的字元,並可以傳入任意網路圖片做實驗。
04. 圖片預處理
4K屏已經逐漸普及,現在差不多的電腦顯示器也能支援2K解析度,如果把每個畫素點都變成一個字元,那出來的圖片實在是太大了。
所以首先要對源圖片進行壓縮,再轉換為灰度模式,即丟擲色彩資訊。
# 壓縮圖片
def scale_image(image, new_width=60):
(original_width, original_height) = image.size # 獲取原圖尺寸
aspect_ratio = original_height/float(original_width) * 0.5 # 計算高寬比,因為輸出文字有2倍行距,所以乘0.5維持高寬比
new_height = int(aspect_ratio * new_width)
new_image = image.resize((new_width, new_height))
return new_image
# 灰度模式
def convert_to_grayscale(image):
return image.convert('L') # 呼叫image物件自己的.convert()方法
複製程式碼
05. 圖片到字元
然後是建立圖片(壓縮後)畫素點到字符集的對映關係。
def map_pixels_to_ascii_chars(image, range_width=25):
# 將每個畫素根據其灰度值對映為一個字元,每個字元對應25個灰度值
pixels_in_image = list(image.getdata()) # 獲取原圖灰度值列表
pixels_to_chars = [ASCII_CHARS[int(pixel_value/range_width)] for pixel_value in pixels_in_image]
# 對於每個畫素點,將其灰度值轉換為列表ASCII_CHARS的索引
return "".join(pixels_to_chars)[/amalthea_sample_code]
複製程式碼
最後綜合前面幾個函式,以文字形式輸出字元畫。
def convert_image_to_ascii(image, new_width=60):
image = scale_image(image, new_width) # 呼叫scale_image()函式,壓縮圖片
image = convert_to_grayscale(image) # convert_to_grayscale()函式,轉換為灰度圖
pixels_to_chars = map_pixels_to_ascii_chars(image) # 對映至字符集
len_pixels_to_chars = len(pixels_to_chars) # 獲取字符集長度
image_ascii = [pixels_to_chars[index: index + new_width] for index in range(0, len_pixels_to_chars, new_width)]
return "\n".join(image_ascii)
def handle_image_conversion(image_filepath, new_width=60):
image = Image.open(image_filepath) # Image.open()開啟源圖片
image_ascii = convert_image_to_ascii(image, new_width) # 呼叫上面的convert_image_to_ascii()函式
print(image_ascii) # 輸出字元畫
複製程式碼
06. 圖源採集
接下來我們可以將任意圖片轉換為字元畫看看效果,為了充分發揚網際網路精神,目前僅支援具有網路地址的圖片(其實是圖片上傳系統還沒做好)。
下面來看一個例項,將一張QQ企鵝的圖示轉換為字元畫,點選執行即可得到字元畫。
from PIL import Image
ASCII_CHARS = [' ', '#', '?', '%', '.', '+', '.', '*', ':', ',', '@']
def scale_image(image, new_width=60):
"""Resizes an image preserving the aspect ratio.
"""
(original_width, original_height) = image.size
aspect_ratio = original_height/float(original_width) * 0.5
new_height = int(aspect_ratio * new_width)
new_image = image.resize((new_width, new_height))
return new_image
def convert_to_grayscale(image):
return image.convert('L')
def map_pixels_to_ascii_chars(image, range_width=25):
pixels_in_image = list(image.getdata())
pixels_to_chars = [ASCII_CHARS[int(pixel_value/range_width)] for pixel_value in
pixels_in_image]
return "".join(pixels_to_chars)
def convert_image_to_ascii(image, new_width=60):
image = scale_image(image, new_width)
image = convert_to_grayscale(image)
pixels_to_chars = map_pixels_to_ascii_chars(image)
len_pixels_to_chars = len(pixels_to_chars)
image_ascii = [pixels_to_chars[index: index + new_width] for index in
range(0, len_pixels_to_chars, new_width)]
return "\n".join(image_ascii)
def handle_image_conversion(image_filepath, new_width=60):
image = Image.open(image_filepath)
image_ascii = convert_image_to_ascii(image, new_width)
print(image_ascii)
from urllib import request
image_file_path = 'image2ascii.jpg' # 圖片的本地名稱
image_url = "http://upload.wikimedia.org/wikipedia/en/thumb/9/9c/Tencent_QQ.png/64px-Tencent_QQ.png" # 圖片的網路地址
request.urlretrieve(image_url, image_file_path) # 將網路圖片下載到本地,並重新命名
handle_image_conversion(image_file_path) # 啟動handle_image_conversion()這個總函式
複製程式碼
07. 開放空間
上節的例子仍然保留了預設的字符集和預設影象寬度60
,接下來的部分留給讀者自由發揮,可以通過修改如下引數獲得自己的字元畫:
new_width
:字元畫的尺寸(寬的字元數)ASCII_CHARS
:字符集image_url
:網路圖片地址,就是你想要轉換的圖片
操作示例:
當你在網上看到一張圖片,右鍵-複製圖片地址。
將圖片地址賦值予變數image_url
,點選執行即可。
from PIL import Image
from urllib import request
def scale_image(image, new_width=60):
(original_width, original_height) = image.size
aspect_ratio = original_height/float(original_width) * 0.5
new_height = int(aspect_ratio * new_width)
new_image = image.resize((new_width, new_height))
return new_image
def convert_to_grayscale(image):
return image.convert('L')
def map_pixels_to_ascii_chars(image, range_width=25):
pixels_in_image = list(image.getdata())
pixels_to_chars = [ASCII_CHARS[int(pixel_value/range_width)] for pixel_value in
pixels_in_image]
return "".join(pixels_to_chars)
def convert_image_to_ascii(image, new_width=60):
image = scale_image(image, new_width)
image = convert_to_grayscale(image)
pixels_to_chars = map_pixels_to_ascii_chars(image)
len_pixels_to_chars = len(pixels_to_chars)
image_ascii = [pixels_to_chars[index: index + new_width] for index in
range(0, len_pixels_to_chars, new_width)]
return "\n".join(image_ascii)
def handle_image_conversion(image_filepath, new_width=60):
image = Image.open(image_filepath)
image_ascii = convert_image_to_ascii(image, new_width)
print(image_ascii)
image_file_path = 'image2ascii.jpg' # 圖片的本地名稱
new_width = 60 # 字元畫寬幾個字元
ASCII_CHARS = [' ', '#', '?', '%', '.', '+', '.', '*', ':', ',', '@'] # 字符集
image_url = "http://upload.wikimedia.org/wikipedia/en/thumb/9/9c/Tencent_QQ.png/64px-Tencent_QQ.png" # 圖片的網路地址
request.urlretrieve(image_url, image_file_path)
handle_image_conversion(image_file_path, new_width)
複製程式碼
摘掉眼鏡看效果更佳。