① 引 言

如今網上關於如何實現字元圖像多為一種方法,那就是

1、構造一行從密集到稀疏的"符號"集合(比如:$@B%8&WM#*oahkbdpqw mZO0QLCJUYXzcvunxrjft/|()1{}[]?-_ +~<>i!lI;:,"^`. )。

2、圖像轉為灰度(像素信息便只有一個數值),像素值範圍為 0~255 的整數,共256個值。於是計算(像素值 / 256)得出一個像素值在像素範圍中的相對位置,也就是百分比值。

3、最終在「符號「集合中找到相同相對位置(百分比值)的那個符號,輸出它。此為網上大部分的實現方法,但是通過觀看結果(圖像或者視頻)展示發現,此方法存在一些問題,如輪廓比較模糊,在圖片整體為暗色調時,整個字元圖像會變成一團漿糊。

4、給予以上發現,作者嘗試使用新的方法實現字元圖像。在輔修學位論文階段偶然發現同學使用過「聚類」手段,可以根據不同城市的某個經濟數據達到城市特徵歸類的目的。於是開始思考是否可以借鑒聚類手段,根據像素數值大小的特徵,將它們分為不同種類,最暗的部分使用較為密集的「數字」表示,次暗的陰影部分使用 「-」 橫槓表示, 明亮部分可以使用 「.」 點號或者空白表示。

5、不足:首先只是機械地設定聚類數量為3或5,其次只是機械地將「黑暗、陰影、明亮」的分界設定為(最終類別 / 聚類數量)中最靠近0.33和0.66的兩個分界點。原因在於無法通過程序評估不同「黑暗、陰影、明亮」的分界設定下最終字元圖像的優劣程度,也就是無法有效利用更多個聚類類別數量。於是乎,作者暫時限定聚類數量和分界設置。但是相較於網上已經流傳的方法來講,應該可以認為效果有了較大的提升。


目 錄

① 引言

② 效果展示

③ Python代碼

④ 附


② 效果展示

註:知乎手機客戶端可以直接查看字元圖像的細節信息。網頁版似乎不可以。

秦時明月 娥皇&amp;amp;女英
秦時明月 字元畫的娥皇&amp;amp;女英
功夫熊貓 海報
功夫熊貓 字元畫的海報
秦時明月 年輕的小莊


③ Python代碼

import cv2, random
import numpy as np

def get_str_pic(frame, K=5):
"""
思 路:
--------------
利用 聚類 將像素信息聚為3或5類,顏色最深的一類用數字密集地表示,陰影的一類用
「-」橫槓表示,明亮部分空白表示。

參 數:
--------------
frame
需要傳入的圖片信息。可以是opencv的cv2.imread()得到的數組,也可以是
Pillow的Image.read()。

K
聚類數量,推薦的K為3或5。根據經驗,3或5時可以較為優秀地處理很多圖像了。
若默認的K=5無法很好地表現原圖,請修改為3進行嘗試。若依然無法很好地表現
原圖,請換圖嘗試。 ( -_-|| )

注 意:
--------------
聚類數目理論可以取大於等於3的任意整數。但水平有限,無法自動判斷當生成的
字元圖像可以更好地表現原圖細節時,「黑暗」、「陰影」、」明亮「之間邊界在哪。

所以說由於無法有效利用更大的聚類數量,那麼便先簡單地限制聚類數目為3和5。
"""
if type(frame) != np.ndarray:
frame = np.array(frame)

#if K != 3 and K != 5:
#raise Exception("K需要是3或者5")

height = frame.shape[0]
width = frame.shape[1]
frame_gray = cv2.cvtColor(frame,
cv2.COLOR_BGR2GRAY)

frame_array = frame_gray.reshape((-1,1))
frame_array = np.float32(frame_array)

# 設置相關參數。
criteria = (cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER,
10, 1.0)
flags = cv2.KMEANS_RANDOM_CENTERS
# 得到labels(類別)、centroids(矩心)。
# 如第一行6個像素labels=[0,2,2,1,2,0],則意味著6個像素分別對應著
# 第1個矩心、第3個矩心、第3、2、3、1個矩心。
compactness, labels, centroids = cv2.kmeans(frame_array, K,
None, criteria, 10, flags)
centroids = np.uint8(centroids)

# labels的數個矩心以隨機順序排列,所以需要簡單處理矩心.
centroids = centroids.flatten()
centroids_sorted = sorted(centroids)
# 獲得不同centroids的明暗程度,0最暗
centroids_index = np.array([centroids_sorted.index(value)
for value in centroids])

up = [abs((3*i-2*K)/(3*K)) for i in range(1, 1+K)]
upper_bound = up.index(np.min(up))
low = [abs((3*i-K)/(3*K)) for i in range(1, 1+K)]
lower_bound = low.index(np.min(low))

labels = labels.flatten()
# 將labels轉變為實際的明暗程度列表,0最暗。
labels = centroids_index[labels]
# 列表解析,每2*2個像素挑選出一個,組成(height*width*灰)數組。
labels_picked = [labels[rows*width:(rows+1)*width:2]
for rows in range(0,height,2)]

canvas = np.zeros((3 * height, 3 * width, 3), np.uint8)
canvas.fill(255) # 創建長傳為原圖三倍的白色畫布。

# 因為 字體大小為0.45時,每個數字佔6*6個像素,而白底畫布為原圖三倍
# 所以 需要原圖中每2*2個像素中挑取一個,在白底畫布中由6*6像素大小的數字表示這個像素信息。
y = 8
for rows in labels_picked:
x = 0
for cols in rows:
if cols <= lower_bound:
cv2.putText(canvas, str(random.randint(2,9)),
(x, y), cv2.FONT_HERSHEY_PLAIN, 0.45, 1)
elif cols <= upper_bound:
cv2.putText(canvas, "-", (x, y),
cv2.FONT_HERSHEY_PLAIN, 0.4, 0, 1)
x += 6
y += 6

return canvas

if __name__ == __main__:

img = cv2.imread("2018???138_yexQ8.jpeg")
# 若字元圖像結果不好,可以嘗試更改K為3。
# 若依然無法很好地表現原圖,請換圖嘗試。 -_-||
str_pic = get_str_pic(img, K=5)
cv2.imwrite("result.jpg", str_pic)


④ 附

本文相關內容同時也在bilibili上傳。

B站視屏鏈接:bilibili.com/video/av42 (將視頻所有幀的字元畫合成為字元視頻)

看到這兒了,點贊啊喂~~~~~~~


推薦閱讀:
相關文章