如何挽救鑑黃師的職業生涯 - Python繪製畫素圖

景略集智發表於2018-02-05

歡迎訪問集智主站:集智,通向智慧時代的引擎

注意!

本文所有程式碼均可在集智原貼中執行除錯,有需要的同學可以點選這裡前往原貼



01. ASCII藝術

ASCII的全稱是American Standard Code for Information Interchange,即美國資訊交換標準碼。是由軍用電報編碼發展而來,併成為最通用的現代計算機編碼系統。

在顯示卡還不能摧毀航母戰鬥群的年代,計算機還主要用來計算導彈彈道和衛星軌道,其圖形處理能力是非常弱的,甚至還不如今天高階一點的示波器。但這並不能阻止人類對美的追求,正如四萬年前的莽荒也沒有耽誤拉斯科洞窟壁畫的誕生。

拉斯科洞窟壁畫

在我剛上網那陣,有個流傳很廣的帖子,是教你觀看命令列裡的《星球大戰》,而這部星戰正是由ASCII編碼中的字元構成的,這被稱作ASCII art

ASCII星球大戰

在鬥圖代替打字、點播變成直播甚至VR/AR的今天,圖形的處理已經不再是瓶頸,反而成為了新的增長點。道高一尺魔高一丈,技術的進步也帶來了有害資訊,比如廣大家長朋友們特別關注的色情資訊。剛開始色情的鑑定是由人工完成的,李迪同志就是在《暴走大事件》中扮演鑑黃師唐馬儒而一炮走紅。

但是,基於人工智慧的影象識別也在飛速進步,自動鑑黃已經投入實用,鑑黃師的職業前景就面臨著嚴重的威脅!

02. 字元畫

把一張照片轉換為字元畫,大致需要三步:

  1. 將圖片尺寸壓縮到字元畫所能接受的量級;
  2. 彩色圖轉換為灰度圖,灰度是一個0-255的數值;
  3. 建立灰度值與字符集之間的對映關係。

早年間以上步驟還需要專門做一個小軟體來完成,而現在只需要簡單的程式碼就可以直接在瀏覽器中實現。

典型的字元畫,看視窗邊框大概就知道年代

因為字元畫的本質是“字元”,可以用文字編輯器開啟,對於計算機來說,他們和其他的字元沒有任何區別,“畫”的性質只是由人類的想象力後天賦予的。所以,如果你用字元畫的形式傳播春宮圖,還是需要唐馬儒。

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:網路圖片地址,就是你想要轉換的圖片

操作示例:

當你在網上看到一張圖片,右鍵-複製圖片地址。

http://weibo.com/kaiser0730

將圖片地址賦值予變數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)
複製程式碼

摘掉眼鏡看效果更佳。

Sublime編輯器裡的成像效果


推薦閱讀

“女生科技體驗節” TensorFlow Workshop

這評論有毒!——文字分類的一般套路

我做了一個叫“瑟曦”的機器人,可是她動不動就想讓格雷果爵士弄死我。

相關文章