python中opencv實(shí)現(xiàn)文字分割的實(shí)踐
圖片文字分割的時(shí)候,常用的方法有兩種。一種是投影法,適用于排版工整,字間距行間距比較寬裕的圖像;還有一種是用OpenCV的輪廓檢測(cè),適用于文字不規(guī)則排列的圖像。
投影法
對(duì)文字圖片作橫向和縱向投影,即通過統(tǒng)計(jì)出每一行像素個(gè)數(shù),和每一列像素個(gè)數(shù),來分割文字。
分別在水平和垂直方向?qū)︻A(yù)處理(二值化)的圖像某一種像素進(jìn)行統(tǒng)計(jì),對(duì)于二值化圖像非黑即白,我們通過對(duì)其中的白點(diǎn)或者黑點(diǎn)進(jìn)行統(tǒng)計(jì),根據(jù)統(tǒng)計(jì)結(jié)果就可以判斷出每一行的上下邊界以及每一列的左右邊界,從而實(shí)現(xiàn)分割的目的。
算法步驟:
- 使用水平投影和垂直投影的方式進(jìn)行圖像分割,根據(jù)投影的區(qū)域大小尺寸分割每行和每塊的區(qū)域,對(duì)原始圖像進(jìn)行二值化處理。
- 投影之前進(jìn)行圖像灰度學(xué)調(diào)整做膨脹操作
- 分別進(jìn)行水平投影和垂直投影
- 根據(jù)投影的長(zhǎng)度和高度求取完整行和塊信息
橫板文字-小票文字分割
#小票水平分割 import cv2 import numpy as np img = cv2.imread(r"C:\Users\An\Pictures\1.jpg") cv2.imshow("Orig Image", img) # 輸出圖像尺寸和通道信息 sp = img.shape print("圖像信息:", sp) sz1 = sp[0] # height(rows) of image sz2 = sp[1] # width(columns) of image sz3 = sp[2] # the pixels value is made up of three primary colors print('width: %d \n height: %d \n number: %d' % (sz2, sz1, sz3)) gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) retval, threshold_img = cv2.threshold(gray_img, 120, 255, cv2.THRESH_BINARY_INV) cv2.imshow("threshold_img", threshold_img) # 水平投影分割圖像 gray_value_x = [] for i in range(sz1): white_value = 0 for j in range(sz2): if threshold_img[i, j] == 255: white_value += 1 gray_value_x.append(white_value) print("", gray_value_x) # 創(chuàng)建圖像顯示水平投影分割圖像結(jié)果 hori_projection_img = np.zeros((sp[0], sp[1], 1), np.uint8) for i in range(sz1): for j in range(gray_value_x[i]): hori_projection_img[i, j] = 255 cv2.imshow("hori_projection_img", hori_projection_img) text_rect = [] # 根據(jù)水平投影分割識(shí)別行 inline_x = 0 start_x = 0 text_rect_x = [] for i in range(len(gray_value_x)): if inline_x == 0 and gray_value_x[i] > 10: inline_x = 1 start_x = i elif inline_x == 1 and gray_value_x[i] < 10 and (i - start_x) > 5: inline_x = 0 if i - start_x > 10: rect = [start_x - 1, i + 1] text_rect_x.append(rect) print("分行區(qū)域,每行數(shù)據(jù)起始位置Y:", text_rect_x) # 每行數(shù)據(jù)分段 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (13, 3)) dilate_img = cv2.dilate(threshold_img, kernel) cv2.imshow("dilate_img", dilate_img) for rect in text_rect_x: cropImg = dilate_img[rect[0]:rect[1],0:sp[1]] # 裁剪圖像y-start:y-end,x-start:x-end sp_y = cropImg.shape # 垂直投影分割圖像 gray_value_y = [] for i in range(sp_y[1]): white_value = 0 for j in range(sp_y[0]): if cropImg[j, i] == 255: white_value += 1 gray_value_y.append(white_value) # 創(chuàng)建圖像顯示水平投影分割圖像結(jié)果 veri_projection_img = np.zeros((sp_y[0], sp_y[1], 1), np.uint8) for i in range(sp_y[1]): for j in range(gray_value_y[i]): veri_projection_img[j, i] = 255 cv2.imshow("veri_projection_img", veri_projection_img) # 根據(jù)垂直投影分割識(shí)別行 inline_y = 0 start_y = 0 text_rect_y = [] for i in range(len(gray_value_y)): if inline_y == 0 and gray_value_y[i] > 2: inline_y = 1 start_y = i elif inline_y == 1 and gray_value_y[i] < 2 and (i - start_y) > 5: inline_y = 0 if i - start_y > 10: rect_y = [start_y - 1, i + 1] text_rect_y.append(rect_y) text_rect.append([rect[0], rect[1], start_y - 1, i + 1]) cropImg_rect = threshold_img[rect[0]:rect[1], start_y - 1:i + 1] # 裁剪圖像 cv2.imshow("cropImg_rect", cropImg_rect) # cv2.imwrite("C:/Users/ThinkPad/Desktop/cropImg_rect.jpg",cropImg_rect) # break # break # 在原圖上繪制截圖矩形區(qū)域 print("截取矩形區(qū)域(y-start:y-end,x-start:x-end):", text_rect) rectangle_img = cv2.rectangle(img, (text_rect[0][2], text_rect[0][0]), (text_rect[0][3], text_rect[0][1]), (255, 0, 0), thickness=1) for rect_roi in text_rect: rectangle_img = cv2.rectangle(img, (rect_roi[2], rect_roi[0]), (rect_roi[3], rect_roi[1]), (255, 0, 0), thickness=1) cv2.imshow("Rectangle Image", rectangle_img) key = cv2.waitKey(0) if key == 27: print(key) cv2.destroyAllWindows()
小票圖像二值化結(jié)果如下:
小票圖像結(jié)果分割如下:
豎版-古文文字分割
對(duì)于古籍來說,古籍文字書寫在習(xí)慣是從上到下的,所以說在掃描的時(shí)候應(yīng)該掃描列投影,在掃描行投影。
1.原始圖像進(jìn)行二值化
使用水平投影和垂直投影的方式進(jìn)行圖像分割,根據(jù)投影的區(qū)域大小尺寸分割每行和每塊的區(qū)域,對(duì)原始圖像進(jìn)行二值化處理。
原始圖像:
二值化后的圖像:
2.圖像膨脹
投影之前進(jìn)行圖像灰度學(xué)調(diào)整做膨脹操作,選取適當(dāng)?shù)暮耍瑢?duì)圖像進(jìn)行膨脹處理。
3.垂直投影
定位該行文字區(qū)域:
數(shù)值不為0的區(qū)域就是文字存在的地方(即二值化后白色部分的區(qū)域),為0的區(qū)域就是每行之間相隔的距離。
1、如果前一個(gè)數(shù)為0,則記錄第一個(gè)不為0的坐標(biāo)。
2、如果前一個(gè)數(shù)不為0,則記錄第一個(gè)為0的坐標(biāo)。形象的說就是從出現(xiàn)第一個(gè)非空白列到出現(xiàn)第一個(gè)空白列這段區(qū)域就是文字存在的區(qū)域。
通過以上規(guī)則就可以找出每一列文字的起始點(diǎn)和終止點(diǎn),從而確定每一列的位置信息。
垂直投影結(jié)果:
通過上面的垂直投影,根據(jù)其白色小山峰的起始位置就可以界定出每一列的起始位置,從而把每一列分割出來。
4.水平投影
根據(jù)投影的長(zhǎng)度和高度求取完整行和塊信息
通過水平投影可以獲得每一個(gè)字符左右的起始位置,這樣也就可以獲得到每一個(gè)字符的具體坐標(biāo)位置,即一個(gè)矩形框的位置。
import cv2 import numpy as np import os img = cv2.imread(r"C:\Users\An\Pictures\3.jpg") save_path=r"E:\crop_img\result" #圖像分解的每一步保存的地址 crop_path=r"E:\crop_img\img" #圖像切割保存的地址 cv2.imshow("Orig Image", img) # 輸出圖像尺寸和通道信息 sp = img.shape print("圖像信息:", sp) sz1 = sp[0] # height(rows) of image sz2 = sp[1] # width(columns) of image sz3 = sp[2] # the pixels value is made up of three primary colors print('width: %d \n height: %d \n number: %d' % (sz2, sz1, sz3)) gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) retval, threshold_img = cv2.threshold(gray_img, 120, 255, cv2.THRESH_BINARY_INV) cv2.imshow("threshold_img", threshold_img) cv2.imwrite(os.path.join(save_path,"threshold_img.jpg"),threshold_img) # 垂直投影分割圖像 gray_value_y = [] for i in range(sz2): white_value = 0 for j in range(sz1): if threshold_img[j, i] == 255: white_value += 1 gray_value_y.append(white_value) print("", gray_value_y) #創(chuàng)建圖像顯示垂直投影分割圖像結(jié)果 veri_projection_img = np.zeros((sp[0], sp[1], 1), np.uint8) for i in range(sz2): for j in range(gray_value_y[i]): veri_projection_img[j, i] = 255 cv2.imshow("veri_projection_img", veri_projection_img) cv2.imwrite(os.path.join(save_path,"veri_projection_img.jpg"),veri_projection_img) text_rect = [] # 根據(jù)垂直投影分割識(shí)別列 inline_y = 0 start_y = 0 text_rect_y = [] for i in range(len(gray_value_y)): if inline_y == 0 and gray_value_y[i]> 30: inline_y = 1 start_y = i elif inline_y == 1 and gray_value_y[i] < 30 and (i - start_y) > 5: inline_y = 0 if i - start_y > 10: rect = [start_y - 1, i + 1] text_rect_y.append(rect) print("分列區(qū)域,每列數(shù)據(jù)起始位置Y:", text_rect_y) # 每列數(shù)據(jù)分段 # kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (13, 3)) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) dilate_img = cv2.dilate(threshold_img, kernel) cv2.imshow("dilate_img", dilate_img) cv2.imwrite(os.path.join(save_path,"dilate_img.jpg"),dilate_img) for rect in text_rect_y: cropImg = dilate_img[0:sp[0],rect[0]:rect[1]] # 裁剪圖像y-start:y-end,x-start:x-end sp_x = cropImg.shape # 垂直投影分割圖像 gray_value_x = [] for i in range(sp_x[0]): white_value = 0 for j in range(sp_x[1]): if cropImg[i, j] == 255: white_value += 1 gray_value_x.append(white_value) # 創(chuàng)建圖像顯示水平投影分割圖像結(jié)果 hori_projection_img = np.zeros((sp_x[0], sp_x[1], 1), np.uint8) for i in range(sp_x[0]): for j in range(gray_value_x[i]): veri_projection_img[i, j] = 255 # cv2.imshow("hori_projection_img", hori_projection_img) # 根據(jù)水平投影分割識(shí)別行 inline_x = 0 start_x = 0 text_rect_x = [] ind=0 for i in range(len(gray_value_x)): ind+=1 if inline_x == 0 and gray_value_x[i] > 2: inline_x = 1 start_x = i elif inline_x == 1 and gray_value_x[i] < 2 and (i - start_x) > 5: inline_x = 0 if i - start_x > 10: rect_x = [start_x - 1, i + 1] text_rect_x.append(rect_x) text_rect.append([start_x - 1, i + 1,rect[0], rect[1]]) cropImg_rect = threshold_img[start_x - 1:i + 1,rect[0]:rect[1]] # 裁剪二值化圖像 crop_img=img[start_x - 1:i + 1,rect[0]:rect[1]] #裁剪原圖像 # cv2.imshow("cropImg_rect", cropImg_rect) # cv2.imwrite(os.path.join(crop_path,str(ind)+".jpg"),crop_img) # break # break # 在原圖上繪制截圖矩形區(qū)域 print("截取矩形區(qū)域(y-start:y-end,x-start:x-end):", text_rect) rectangle_img = cv2.rectangle(img, (text_rect[0][2], text_rect[0][0]), (text_rect[0][3], text_rect[0][1]), (255, 0, 0), thickness=1) for rect_roi in text_rect: rectangle_img = cv2.rectangle(img, (rect_roi[2], rect_roi[0]), (rect_roi[3], rect_roi[1]), (255, 0, 0), thickness=1) cv2.imshow("Rectangle Image", rectangle_img) cv2.imwrite(os.path.join(save_path,"rectangle_img.jpg"),rectangle_img) key = cv2.waitKey(0) if key == 27: print(key) cv2.destroyAllWindows()
分割結(jié)果如下:
從分割的結(jié)果上看,基本上實(shí)現(xiàn)了圖片中文字的分割。但由于中文結(jié)構(gòu)復(fù)雜性,對(duì)于一些文字的分割并不理想,字會(huì)出現(xiàn)過度分割、有粘連的兩個(gè)字會(huì)出現(xiàn)分割不夠的現(xiàn)象??梢詮膱D像預(yù)處理(圖像腐蝕膨脹),邊界判斷閾值的調(diào)整等方面進(jìn)行優(yōu)化。
到此這篇關(guān)于python中opencv實(shí)現(xiàn)文字分割的實(shí)踐的文章就介紹到這了,更多相關(guān)opencv 文字分割內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在python3中使用shuffle函數(shù)要注意的地方
今天小編就為大家分享一篇在python3中使用shuffle函數(shù)要注意的地方,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-02-02用python wxpy管理微信公眾號(hào)并利用微信獲取自己的開源數(shù)據(jù)
這篇文章主要介紹了用python wxpy管理微信公眾號(hào)并利用微信獲取自己的開源數(shù)據(jù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07pandas取dataframe特定行列的實(shí)現(xiàn)方法
大家在使用Python進(jìn)行數(shù)據(jù)分析時(shí),經(jīng)常要使用到的一個(gè)數(shù)據(jù)結(jié)構(gòu)就是pandas的DataFrame,本文介紹了pandas取dataframe特定行列的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Django模板報(bào)TemplateDoesNotExist異常(親測(cè)可行)
這篇文章主要介紹了Django模板報(bào)TemplateDoesNotExist異常(親測(cè)可行),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Python實(shí)現(xiàn)字符串逆序輸出功能示例
這篇文章主要介紹了Python實(shí)現(xiàn)字符串逆序輸出功能,結(jié)合具體實(shí)例形式分析了Python針對(duì)字符串的遍歷、翻轉(zhuǎn)、排序等相關(guān)操作技巧,需要的朋友可以參考下2017-06-06Django上使用數(shù)據(jù)可視化利器Bokeh解析
這篇文章主要介紹了Django上使用數(shù)據(jù)可視化利器Bokeh解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07Pandas 解決dataframe的一列進(jìn)行向下順移問題
今天小編就為大家分享一篇Pandas 解決dataframe的一列進(jìn)行向下順移問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-12-12