본문 바로가기
  • 우당탕탕속의 잔잔함
Programming/Computer Vision

[OCR] [Tesseract - 4] Tesseract로 OCR 수행 후 특정 Text 추출

by zpstls 2023. 1. 18.
반응형

 

 

이전 포스트에서는 Contour를 통해 OCR하고자 하는 영역을 Crop 하고 OCR을 수행하도록 하였고 그런대로 좋은 결과를 얻을 수 있었습니다.

그러나 Image를 Text로 바꾸기만 하였을 뿐 Text Data를 딱히 이용하지는 못했습니다.

 

이번 포스트에서는 이러한 기능을 구현해 보고자 합니다.

 

우선, 이전 포스트 내용은 다음 링크를 참고해주세요.

 

[OCR] [Tesseract - 3] Image Processing 진행 후 Tesseract로 OCR 수행

이전 포스트에서 Tesseract를 이용하여 OCR을 수행했습니다. 깨끗한 이미지에서는 OCR이 제대로 수행되었지만 실생활에서 사용되는 이미지들에서는 OCR이 제대로 수행되지 않았습니다. 이번 포스트

mj-thump-thump-story.tistory.com

 

 

이제 본격적으로 포스팅을 시작해 보도록 하겠습니다.

이전 포스팅에서 사용했던 다음과 같은, 영수증 영역만 추출된 이미지를 이용할 것입니다. 

Cropped Receipt

위와 같은 이미지에 Blur와 Morphology, Sobel, Threshold, Erode 등의 Image Processing 기법을 적용하여 영수증의 Text 영역만 추출하도록 합니다. 코드는 다음과 같습니다.

import numpy as np


def onChange(pos):
    pass

cv2.namedWindow("Trackbar Windows")
cv2.createTrackbar("rectKernelX", "Trackbar Windows", 1, 500, onChange)
cv2.createTrackbar("rectKernelY", "Trackbar Windows", 1, 500, onChange)
cv2.createTrackbar("sqKernelX", "Trackbar Windows", 1, 500, onChange)
cv2.createTrackbar("sqKernelY", "Trackbar Windows", 1, 500, onChange)
cv2.createTrackbar("GaussianBlurV", "Trackbar Windows", 0, 255, onChange)

cv2.setTrackbarPos("rectKernelX", "Trackbar Windows", 12)
cv2.setTrackbarPos("rectKernelY", "Trackbar Windows", 5)
cv2.setTrackbarPos("sqKernelX", "Trackbar Windows", 14)
cv2.setTrackbarPos("sqKernelY", "Trackbar Windows", 3)
cv2.setTrackbarPos("GaussianBlurV", "Trackbar Windows", 0)

close_thresh_copy = np.empty((), dtype=np.uint8)
while cv2.waitKey(1) != ord('q') :
    rectKernelX = cv2.getTrackbarPos("rectKernelX", "Trackbar Windows")
    rectKernelY = cv2.getTrackbarPos("rectKernelY", "Trackbar Windows")
    sqKernelX = cv2.getTrackbarPos("sqKernelX", "Trackbar Windows")
    sqKernelY = cv2.getTrackbarPos("sqKernelY", "Trackbar Windows")
    GaussianBlurV = cv2.getTrackbarPos("GaussianBlurV", "Trackbar Windows")

    if GaussianBlurV % 2 == 0 :
        GaussianBlurV += 1
    if rectKernelX <= 1:
        rectKernelX = 1
    if rectKernelY <= 1:
        rectKernelY = 1
    if sqKernelX <= 1:
        sqKernelX = 1
    if sqKernelY <= 1:
        sqKernelY = 1

    gray = cv2.cvtColor(receipt, cv2.COLOR_BGR2GRAY)
    (H, W) = gray.shape

    rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (rectKernelX, rectKernelY))
    sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (sqKernelX, sqKernelY))

    gray = cv2.GaussianBlur(gray, (GaussianBlurV, GaussianBlurV), 0)
    blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, rectKernel)

    grad = cv2.Sobel(blackhat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
    grad = np.absolute(grad)
    (minVal, maxVal) = (np.min(grad), np.max(grad))
    grad = (grad - minVal) / (maxVal - minVal)
    grad = (grad * 255).astype("uint8")

    grad = cv2.morphologyEx(grad, cv2.MORPH_CLOSE, rectKernel)
    thresh = cv2.threshold(grad, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]

    close_thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
    close_thresh = cv2.erode(close_thresh, None, iterations=2)
    close_thresh_copy = close_thresh.copy()

    cv2.imshow("Blackhat", blackhat)
    cv2.imshow("Gradient", grad)
    cv2.imshow("Rect Close", thresh)
    cv2.imshow("Square Close", close_thresh)

 

Track Bar를 조정하면서 수행한 결과는 다음과 같습니다.

Result Of Processing

위와 같이 Text 영역만 White Pixel로 추출될 수 있게 되었습니다. 이 영역을 기반으로 OCR을 수행할 영역을 표시하는 코드는 다음과 같습니다.

from imutils.contours import sort_contours


cnts = cv2.findContours(close_thresh_copy, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sort_contours(cnts, method="top-to-bottom")[0]

roi_list = []
roi_title_list = []

margin = 5
receipt_grouping = receipt.copy()
for c in cnts:
    (x, y, w, h) = cv2.boundingRect(c)

    color = (0, 0, 255)
    roi = receipt[y - margin:y + h + margin, x - margin:x + w + margin]
    roi_list.append(roi)
    roi_title_list.append("Roi_{}".format(len(roi_list)))

    cv2.rectangle(receipt_grouping, (x - margin, y - margin), (x + w + margin, y + h + margin), color, 1)
    cv2.imshow("Grouping Image", receipt_grouping)​

앞서 얻은 close_thres Image에서 Contour를 추출합니다. 그리고 이러한 Contour 뭉치의 Boundary Position과 Width, Height를 boundingRect() 함수를 통해 구합니다.

구한, Boundary에 임의의 Margin을 부여하고 각각의 ROI를 계산하여 저장합니다. 그리고 Receipt Image에 Draw 합니다.

 

위와 같은 과정을 수행하면 다음과 같은 결과를 얻을 수 있습니다.

Extract ROI

Text 영역만 잘 Grouping 된 것을 확인할 수 있습니다. 추출한 각각의 ROI를 OCR을 수행하기 위해 다음과 같은 코드를 수행시킵니다.

roi_title_list_index = 0
for roi in roi_list:
    gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
    threshold_roi = cv2.threshold(gray_roi, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
    #threshold_roi = imutils.resize(threshold_roi, width=300)

    cv2.imshow(roi_title_list[roi_title_list_index], threshold_roi)
    roi_title_list_index += 1

    options = "--psm 4"
    roi_text = pytesseract.image_to_string(threshold_roi, config=options)
    print("[OCR Result] = "roi_text)

ROI Image를 Gray Scale로 변경한 후 Threshold를 통해 이진화시킵니다. 그리고 이 결과 Image를 Tesseract에 넣습니다. 수행 결과는 다음과 같습니다.

OCR With ROI

결과가 별로 좋지 않습니다. 인식 자체가 되지 못한 Text들이 너무 많습니다.

ROI를 추출할 때 Margin을 좀 더 늘려주고 ROI Image의 크기를 늘려주면 다음과 같은 결과를 얻을 수 있습니다.

OCR With ROI - increasing roi, resize

이전보다는 인식된 결과 값들이 늘어나긴 했지만, 여전히 전체 Receipt Image를 OCR 했던 결과보다 좋지 않습니다. 

다만, 이미지 Size와 ROI 영역을 조정할 때마다 결과가 조금씩 달라집니다. 따라서 Parameter들을 적절히 조절해 보면 좋은 결과를 얻을 수 있지 않을까 싶습니다.

 

 

 

 

 

인식률은 우선, 여기까지만 하고 넘어가도록 하겠습니다.

이번에는 OCR 된 Data에 정규식을 부여하여 추출하고자 하는 Data만 추출할 수 있도록 해볼 것입니다.

다음과 같은 정규식을 통해 가격, 총액, 전화번호, 팩스, 이메일 Data를 추출할 수 있습니다.

prices = re.findall(r"(?:CHF )([0-9\.\-+_]+\.[0-9\.\-+_]+)", roi_text)
total_price = re.findall(r"(?:Total: CHF = )([0-9\.\-+_]+\.[0-9\.\-+_]+)", roi_text)
tel = re.findall(r'(?:Tel.: )([\+\(]?[0-9][0-9 .\-\(\)]{8,}[0-9])', roi_text)
fax = re.findall(r'(?:Fax.: )([\+\(]?[0-9][0-9 .\-\(\)]{8,}[0-9])', roi_text)
emails = re.findall(r"(?:E-mail: )([a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+)", roi_text)

Extract Specific Text

물론, 이 정규식의 전제는 OCR이 정확히 수행되었다는 것을 기반으로 합니다. 가령 "."가 ","이 되더라도 위 정규식을 제대로 동작하지 않을 것입니다. 예외 상황을 둔다면 실행되게는 만들 수 있겠지만 예외가 너무 많아지면 의미가 없겠죠...

 

 

4번의 포스팅을 통해 Tesseract를 이용한 OCR의 기초적인 부분을 수행해 보았습니다. 결과가 그리 만족스럽지는 않지만, 이는 차차 개선해나가야 할 문제일 것입니다. 다만, 확실한 것은 OCR을 굉장히 간단하게 수행할 수 있었다는 것입니다.

Tesseract 이외의 여러 OpenSource들이 있을 것입니다. 이들을 사용해 보면서 성능을 향상시킬 수 있도록 연구를 해보아야겠습니다.

 

이번 포스팅, 그리고 Tesseract와 관련된 Test는 여기서 마무리하도록 하겠습니다. 

 

 

반응형

댓글