1. 程式人生 > >OpenCV入門筆記(七) 文字區域的提取

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