如今網上關於如何實現字元圖像多為一種方法,那就是
1、構造一行從密集到稀疏的"符號"集合(比如:$@B%8&WM#*oahkbdpqw mZO0QLCJUYXzcvunxrjft/|()1{}[]?-_ +~<>i!lI;:,"^`. )。
2、圖像轉為灰度(像素信息便只有一個數值),像素值範圍為 0~255 的整數,共256個值。於是計算(像素值 / 256)得出一個像素值在像素範圍中的相對位置,也就是百分比值。
3、最終在「符號「集合中找到相同相對位置(百分比值)的那個符號,輸出它。此為網上大部分的實現方法,但是通過觀看結果(圖像或者視頻)展示發現,此方法存在一些問題,如輪廓比較模糊,在圖片整體為暗色調時,整個字元圖像會變成一團漿糊。
4、給予以上發現,作者嘗試使用新的方法實現字元圖像。在輔修學位論文階段偶然發現同學使用過「聚類」手段,可以根據不同城市的某個經濟數據達到城市特徵歸類的目的。於是開始思考是否可以借鑒聚類手段,根據像素數值大小的特徵,將它們分為不同種類,最暗的部分使用較為密集的「數字」表示,次暗的陰影部分使用 「-」 橫槓表示, 明亮部分可以使用 「.」 點號或者空白表示。
5、不足:首先只是機械地設定聚類數量為3或5,其次只是機械地將「黑暗、陰影、明亮」的分界設定為(最終類別 / 聚類數量)中最靠近0.33和0.66的兩個分界點。原因在於無法通過程序評估不同「黑暗、陰影、明亮」的分界設定下最終字元圖像的優劣程度,也就是無法有效利用更多個聚類類別數量。於是乎,作者暫時限定聚類數量和分界設置。但是相較於網上已經流傳的方法來講,應該可以認為效果有了較大的提升。
① 引言
② 效果展示
③ 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站視屏鏈接:https://www.bilibili.com/video/av42009998 (將視頻所有幀的字元畫合成為字元視頻)
看到這兒了,點贊啊喂~~~~~~~