在做圖片文字分割的時候,常用的方法有兩種。一種是投影法,適用於排版工整,字間距行間距比較寬裕的圖像;還有一種是用OpenCV的輪廓檢測,適用於文字不規則排列的圖像。
1. 思路
一開始想偷個懶,直接用OpenCV的模型,結果發現效果不佳。文字出現了過度分割的問題,部分文字甚至沒有被識別:

於是只好使用傳統方法,投影法。對文字圖片作橫向和縱向投影,即通過統計出每一行像素個數,和每一列像素個數,來分割文字。代碼參考https://www.cnblogs.com/zxy-joy/p/10687152.html,但是對於古籍來說,需要做一些修改。比如,古籍文字書寫在習慣是從上到下的,所以說在掃描的時候應該掃描列投影,在掃描行投影,搞定這次簡單的操作順序修改以后,分割結果如下:

很顯然,雖然說沒有出現過度分割的問題,但是由於字體有大有小,有的地方兩個字被合起來識別成了一個字。那么很顯然,只要把這些地方再進行一次列投影,把它們再度拆分成兩個字,問題不就解決了么。添加代碼:
# 再進行一次列掃描 DcropImg = cropImg[H_start[pos]:H_end[pos], 0:w] d_h, d_w = DcropImg.shape # cv2.imshow("dcrop", DcropImg) sec_V = getVProjection(DcropImg) c1, c2 = scan(sec_V, 0) if len(c1) > len(c2): c2.append(d_w) # cv2.waitKey(0) if len(c1) == 1: Position.append([V_start[i],H_start[pos],V_end[i],H_end[pos]]) else: for x in range(len(c1)): Position.append([V_start[i]+c1[x], H_start[pos],V_start[i]+c2[x], H_end[pos]])
2. 優化
對單行文本做列掃描,很容易出現過度分割的問題。因為只有一行,會掃描到很多沒有像素點的列,最終就會出現這種情況:

為了避免這種過度分割的情況,可以添加一個檢測兩個分割之間距離的代碼,使距離較近的分割進行合並。
x = 1 while x < len(c1): if c1[x] - c2[x-1] < 12: c2.pop(x-1) c1.pop(x) x -= 1 x += 1
3. 代碼
再通過添加一些屬性來限制一個字的最大長度寬度、兩個字之間的最小間距,來避免過度分割,最終效果如下:

雖然仍然存在一些小瑕疵,但是總體效果還算不錯。
詳細代碼如下:
import cv2 import numpy as np HIOG = 50 VIOG = 3 Position = [] '''水平投影''' def getHProjection(image): hProjection = np.zeros(image.shape,np.uint8) # 獲取圖像大小 (h,w)=image.shape # 統計像素個數 h_ = [0]*h for y in range(h): for x in range(w): if image[y,x] == 255: h_[y]+=1 #繪制水平投影圖像 for y in range(h): for x in range(h_[y]): hProjection[y,x] = 255 # cv2.imshow('hProjection2',cv2.resize(hProjection, None, fx=0.3, fy=0.5, interpolation=cv2.INTER_AREA)) # cv2.waitKey(0) return h_ def getVProjection(image): vProjection = np.zeros(image.shape,np.uint8); (h,w) = image.shape w_ = [0]*w for x in range(w): for y in range(h): if image[y,x] == 255: w_[x]+=1 for x in range(w): for y in range(h-w_[x],h): vProjection[y,x] = 255 # cv2.imshow('vProjection',cv2.resize(vProjection, None, fx=1, fy=0.1, interpolation=cv2.INTER_AREA)) # cv2.waitKey(0) return w_ def scan(vProjection, iog, pos = 0): start = 0 V_start = [] V_end = [] for i in range(len(vProjection)): if vProjection[i] > iog and start == 0: V_start.append(i) start = 1 if vProjection[i] <= iog and start == 1: if i - V_start[-1] < pos: continue V_end.append(i) start = 0 return V_start, V_end def checkSingle(image): h = getHProjection(image) start = 0 end = 0 for i in range(h): pass if __name__ == "__main__": # 讀入原始圖像 origineImage = cv2.imread('test_data/test2.jpg') # 圖像灰度化 #image = cv2.imread('test.jpg',0) image = cv2.cvtColor(origineImage,cv2.COLOR_BGR2GRAY) # cv2.imshow('gray',image) # 將圖片二值化 retval, img = cv2.threshold(image,127,255,cv2.THRESH_BINARY_INV) # kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # img = cv2.erode(img, kernel) # cv2.imshow('binary',cv2.resize(img, None, fx=0.3, fy=0.3, interpolation=cv2.INTER_AREA)) #圖像高與寬 (h,w)=img.shape #垂直投影 V = getVProjection(img) start = 0 V_start = [] V_end = [] # 對垂直投影水平分割 V_start, V_end = scan(V, HIOG) if len(V_start) > len(V_end): V_end.append(w-5) # 分割行,分割之后再進行列分割並保存分割位置 for i in range(len(V_end)): #獲取行圖像 if V_end[i] - V_start[i] < 30: continue cropImg = img[0:h, V_start[i]:V_end[i]] # cv2.imshow('cropImg',cropImg) # cv2.waitKey(0) #對行圖像進行垂直投影 H = getHProjection(cropImg) H_start, H_end = scan(H, VIOG, 40) if len(H_start) > len(H_end): H_end.append(h-5) for pos in range(len(H_start)): # 再進行一次列掃描 DcropImg = cropImg[H_start[pos]:H_end[pos], 0:w] d_h, d_w = DcropImg.shape # cv2.imshow("dcrop", DcropImg) sec_V = getVProjection(DcropImg) c1, c2 = scan(sec_V, 0) if len(c1) > len(c2): c2.append(d_w) x = 1 while x < len(c1): if c1[x] - c2[x-1] < 12: c2.pop(x-1) c1.pop(x) x -= 1 x += 1 # cv2.waitKey(0) if len(c1) == 1: Position.append([V_start[i],H_start[pos],V_end[i],H_end[pos]]) else: for x in range(len(c1)): Position.append([V_start[i]+c1[x], H_start[pos],V_start[i]+c2[x], H_end[pos]]) #根據確定的位置分割字符 for m in range(len(Position)): cv2.rectangle(origineImage, (Position[m][0]-5,Position[m][1]-5), (Position[m][2]+5,Position[m][3]+5), (0 ,0 ,255), 2) cv2.imshow('image',cv2.resize(origineImage, None, fx=0.6, fy=0.6, interpolation=cv2.INTER_AREA)) cv2.waitKey(0)
4. 總結
果然,在面對具體問題時,一個再優秀的普適模型往往都不如優化的比較好的傳統方法。就像調參得當的網絡,再具體問題上往往比一些十分優秀的網絡模型效果還要好一樣。
