OpenCV入門筆記(七) 文字區域的提取
前面我們已經學了一些OpenCV中基本的圖片處理的知識,可以拿來做一些小應用。比如怎樣從一張圖片中,把文字圈出來。這一步驟對OCR(Optical Character Recognition)非常有用,因為一般的OCR引擎只是拿來識別文字,並沒有對圖片做預處理,因此精度可能會收到圖片質量影響。
當然,我們這裡只是粗略的查詢文字區域,並沒有進一步地處理圖片。而且對背景複雜,或者文字旋轉角度過於傾斜的情況也無法自適應,因此只能給大家做參考。若要實用到專案中,還有很多工作要做。
效果圖
比如我們有下面的一篇文章的截圖,想把其中的文字區域全部找出來。
當然這裡的截圖只有文字和白花花的背景,效果會非常好。綠色的的矩形框的是我們根據檢測到的文字區域,手動畫出來的。
原理
那麼我們是怎麼做到檢測到區域的呢?
首先,我們會注意到,文字區域和其他的圖片背景很不一樣。我們用膨脹處理圖片,讓文字變成一塊塊大區域,然後識別整塊的輪廓,用矩形去框住這個輪廓。
這個程式分三個子函式,detect(檢測),preprocess(圖片預處理),findTextRegion(查詢和篩選文字區域)。即main函式呼叫detect函式去實際完成文字區域檢測。detect函式又分成preprocess和findTextRegion兩個步驟來做。
見下面的序列圖,可能會清晰點。
mainmaindetectdetectpreprocesspreprocessfindTextRegionfindTextRegion檢測文字區域返回檢測到的文字矩形Sobel,二值化,膨脹和腐蝕Morphology方法預處理圖片返回預處理後的圖片輪廓檢測,去掉面積小的,過長的查詢和篩選文字區域返回區域box的座標1. Detect
先來看main函式和Detect函式
def detect(img):
# 1. 轉化成灰度圖
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 形態學變換的預處理,得到可以查詢矩形的圖片
dilation = preprocess(gray)
# 3. 查詢和篩選文字區域
region = findTextRegion(dilation)
# 4. 用綠線畫出這些找到的輪廓
for box in region:
cv2.drawContours(img, [box], 0 , (0, 255, 0), 2)
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", img)
# 帶輪廓的圖片
cv2.imwrite("contours.png", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
# 讀取檔案
imagePath = sys.argv[1]
img = cv2.imread(imagePath)
detect(img)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
2. Preprocess
利用數學形態學(Morphology)進行預處理。
這個過程很重要,是文字區域檢測效果好壞的核心程式碼,尤其是一下幾個引數:
- 膨脹的核函式大小,這裡用了
30 x 9
,可以調節 - 腐蝕的核函式大小,這裡用了
24 x 6
,可以調節
def preprocess(gray):
# 1. Sobel運算元,x方向求梯度
sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize = 3)
# 2. 二值化
ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY)
# 3. 膨脹和腐蝕操作的核函式
element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 9))
element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (24, 6))
# 4. 膨脹一次,讓輪廓突出
dilation = cv2.dilate(binary, element2, iterations = 1)
# 5. 腐蝕一次,去掉細節,如表格線等。注意這裡去掉的是豎直的線
erosion = cv2.erode(dilation, element1, iterations = 1)
# 6. 再次膨脹,讓輪廓明顯一些
dilation2 = cv2.dilate(erosion, element2, iterations = 3)
# 7. 儲存中間圖片
cv2.imwrite("binary.png", binary)
cv2.imwrite("dilation.png", dilation)
cv2.imwrite("erosion.png", erosion)
cv2.imwrite("dilation2.png", dilation2)
return dilation2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
3. findTextRegion
def findTextRegion(img):
region = []
# 1. 查詢輪廓
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 2. 篩選那些面積小的
for i in range(len(contours)):
cnt = contours[i]
# 計算該輪廓的面積
area = cv2.contourArea(cnt)
# 面積小的都篩選掉
if(area < 1000):
continue
# 輪廓近似,作用很小
epsilon = 0.001 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 找到最小的矩形,該矩形可能有方向
rect = cv2.minAreaRect(cnt)
print "rect is: "
print rect
# box是四個點的座標
box = cv2.cv.BoxPoints(rect)
box = np.int0(box)
# 計算高和寬
height = abs(box[0][1] - box[2][1])
width = abs(box[0][0] - box[2][0])
# 篩選那些太細的矩形,留下扁的
if(height > width * 1.2):
continue
region.append(box)
return region
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
完整程式碼
加上標頭檔案,把幾個函式合併以後,貼在這裡。注意開頭要寫明用utf8編碼,不然中文註釋可能不會被系統識別。而且Python沒有花括號來控制流程,所以對看不見的Tab縮排很敏感,寫程式碼的時候要規範。
直接在終端裡敲下面的命令,既可以執行
python textDetection.py ./pic/1.png
程式碼:textDetection.py
# coding:utf8
import sys
import cv2
import numpy as np
def preprocess(gray):
# 1. Sobel運算元,x方向求梯度
sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize = 3)
# 2. 二值化
ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU+cv2.THRESH_BINARY)
# 3. 膨脹和腐蝕操作的核函式
element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 9))
element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (24, 6))
# 4. 膨脹一次,讓輪廓突出
dilation = cv2.dilate(binary, element2, iterations = 1)
# 5. 腐蝕一次,去掉細節,如表格線等。注意這裡去掉的是豎直的線
erosion = cv2.erode(dilation, element1, iterations = 1)
# 6. 再次膨脹,讓輪廓明顯一些
dilation2 = cv2.dilate(erosion, element2, iterations = 3)
# 7. 儲存中間圖片
cv2.imwrite("binary.png", binary)
cv2.imwrite("dilation.png", dilation)
cv2.imwrite("erosion.png", erosion)
cv2.imwrite("dilation2.png", dilation2)
return dilation2
def findTextRegion(img):
region = []
# 1. 查詢輪廓
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 2. 篩選那些面積小的
for i in range(len(contours)):
cnt = contours[i]
# 計算該輪廓的面積
area = cv2.contourArea(cnt)
# 面積小的都篩選掉
if(area < 1000):
continue
# 輪廓近似,作用很小
epsilon = 0.001 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 找到最小的矩形,該矩形可能有方向
rect = cv2.minAreaRect(cnt)
print "rect is: "
print rect
# box是四個點的座標
box = cv2.cv.BoxPoints(rect)
box = np.int0(box)
# 計算高和寬
height = abs(box[0][1] - box[2][1])
width = abs(box[0][0] - box[2][0])
# 篩選那些太細的矩形,留下扁的
if(height > width * 1.2):
continue
region.append(box)
return region
def detect(img):
# 1. 轉化成灰度圖
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 形態學變換的預處理,得到可以查詢矩形的圖片
dilation = preprocess(gray)
# 3. 查詢和篩選文字區域
region = findTextRegion(dilation)
# 4. 用綠線畫出這些找到的輪廓
for box in region:
cv2.drawContours(img, [box], 0, (0, 255, 0), 2)
cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", img)
# 帶輪廓的圖片
cv2.imwrite("contours.png", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
# 讀取檔案
imagePath = sys.argv[1]
img = cv2.imread(imagePath)
detect(img)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108