주뇽's 저장소

Pytesseract와 OpenCV를 이용하여 명함스캐너 만들기 본문

ComputerVision/OCR

Pytesseract와 OpenCV를 이용하여 명함스캐너 만들기

뎁쭌 2023. 10. 21. 11:46
728x90
반응형

2023.10.21 - [ComputerVision/OpenCV] - 이미지 처리를 위한 Python OpenCV사용법_1

2023.10.21 - [ComputerVision/OpenCV] - 이미지 처리를 위한 Python OpenCV사용법_2

 

이미지 처리를 위한 Python OpenCV사용법_2

Step 1 이미지 변형 이진화 원하는 값만을 걸러내기 위하여 이미지를 오로지 흑과 백으로만 표현하는 것 임계값(threshold) import cv2 img = 'test.jpeg' img = cv2.imread(img) GRAY = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) re

jypark1111.tistory.com

 

이미지 처리를 위한 Python OpenCV사용법_1

OpenCV ?? OpenCV : 다양한 이미지/영상 처리를 위한 Python 오픈소스 라이브러리이며 뿐만 아니라 BSD(Berkeley Software Distribution) 라이센서를 따르기 때문에 상업적으로 사용이 가능하다! 참고로 OpenCV는 RG

jypark1111.tistory.com

MAC 사용
brew install tesseract
pip install pytesseract

Step1. 이미지 읽어와서 가장자리 탐색

1. 일반적인 경우

import cv2
from cv2 import imshow
import numpy as np
from google.colab.patches import cv2_imshow


def Imgcontour(image_path):
    img = cv2.imread(image_path, cv2.IMREAD_COLOR)
    GRAY = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    edge = cv2.Canny(GRAY, 100,200)
    contours , hierarchy = cv2.findContours(edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    ### 이진화된 이미지만 넘겨줘야 함

    # cv2.imshow("Edge", edge)
    cv2_imshow(edge) #-- colab에서 출력하기 위함
    cv2.drawContours(img, contours, -1, (0,255,0), 1)
    # cv2.imshow("Contours", img)
    cv2_imshow(img)#-- colab에서 출력하기 위함

    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    image_path = "/content/business-card-318427_1920 2.jpg"
    Imgcontour(image_path)

2. 찍고자하는 명함 또는 문서가 구겨진 경우 도형을 근사화 하는 방법

from google.colab.patches import cv2_imshow
import cv2
from cv2 import imshow
import numpy as np


def Imgcontour(image_path):
    img = cv2.imread(image_path, cv2.IMREAD_COLOR)
    copy_img = img.copy()
    copy_img_1 = img.copy()
    GRAY = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    edge = cv2.Canny(GRAY, 100,200)
    contours , hierarchy = cv2.findContours(edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    Cnt = contours[0]

    cv2.drawContours(img, [Cnt],0 , (0,255,0), 2)
    cv2.drawContours(copy_img_1, contours[1],0 ,(0,255,0), 2)
    epsilon = 0.1 * cv2.arcLength(Cnt, True)
    ### contour의 둘레의 길이를 확인하고 , 폐곡선 여부 확인 후 근사정확도(10퍼센트) 작을 수록 원본과 비슷함
    approx = cv2.approxPolyDP(Cnt, epsilon, True)
    cv2.drawContours(copy_img, [approx],0 , (0,255,0), 2)

    # cv2.imshow("edge", edge)
    # cv2.imshow("img", img)
    # cv2.imshow("copy_img", copy_img)
    # cv2.imshow("copy_img_1", copy_img_1)
    cv2_imshow(edge)
    cv2_imshow(img)
    cv2_imshow(copy_img)
    cv2_imshow(copy_img_1)

    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    image_path = "/content/ocr_test2.jpg"
    Imgcontour(image_path)

Step2. 탐색한 가장자리를 이용하여 투영변환

자동으로 좌표 구하는 방법을 찾아야 한다..

import cv2
from cv2 import warpPerspective
import numpy as np

def wrapPerspective():
    img = cv2.imread('ocr.jpeg')
    TL = [100,200]
    TR = [200,300]
    BL = [300,400]
    BR = [200,400]

    pts1 = np.float32( [TL, TR, BL, BR])

    w1 = abs(BL[0] - BR[0])
    w2 = abs(TR[0] - TL[0])
    h1 = abs(TR[0] - BR[0])
    h2 = abs(TL[0] - BL[0])

    min_w = min([w1,w2]) ## 최소 너비
    min_h = min([h1,h2]) ## 최소 높이

    pts2 = np.float32([[0,0], [min_w-1,0], 
                       [min_w-1, min_h-1], [0,min_h-1]])
    M = cv2.getPerspectiveTransform(pts1, pts2)
    result = warpPerspective(img, M, (int(min_w), int(min_h)))

    cv2.imshow("OG img", img)
    cv2.imshow("Result", result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    wrapPerspective()                                      

Step3. 이진화를 이용하여 조명값 제거

import cv2

def adaptive_threshold():
    img = cv2.imread('img.jpeg', cv2.IMREAD_GRAYSCALE)

    blur = cv2.GaussianBlur(img, (7,7), 0)
    result_without_blur = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 21,10)
    result_with_blur = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 21,10)
    cv2.imshow('Without Blur', result_without_blur)
    cv2.imshow('With Blur', result_with_blur)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    adaptive_threshold()

Step4. 합치기

1. 가장자리 검출

import cv2

img = cv2.imread('img.jpeg')
og_img = img.copy()
GRAY = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
GRAY = cv2.GaussianBlur(GRAY, (3,3), 0)
edged = cv2.Canny(GRAY, 75,200)

cv2.imshow("IMG", img)
cv2.imshow("EDGED", edged)
cv2.waitKey(0)
cv2.destroyAllWindows()

2. 검출된 가장자리를 이용하여 외곽 찾기

from pickle import TRUE
import cv2

img = cv2.imread('img.jpeg')
ratio = 800.0/img.shape[0]
dim = (int(img.shape[1] * ratio), 800)
img = cv2.resize(img, dim, interpolation= cv2.INTER_AREA)
og_img = img.copy()

GRAY = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
GRAY = cv2.GaussianBlur(GRAY, (5,5), 0)
edged = cv2.Canny(GRAY, 50,200)



cnts, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key =cv2.contourArea, reverse= True)[:5]
## 반환받은 cnt중 면적인 큰 순서대로 5번까지 반환 

for c in cnts:
    ## 순차적으로 탐색
    peri = cv2.arcLength(c, True)
    ## 컨투어의 길이를 반환
    approx = cv2.approxPolyDP(c, 0.03 * peri, True)
    ## 길이의 오차 2퍼센트로 도형을 근사화
    if len(approx) == 4:
        ## 근사화한 도형의 꼭지점이 4개라면 그것이 문서의 외곽
        screenCnt = approx
        break

cv2.drawContours(img, [screenCnt], -1, (0,255,0), 2)
cv2.imshow("IMG", img)
cv2.imshow("edged", edged)
cv2.waitKey(0)
cv2.destroyAllWindows()

3. 네 개의 꼭지점을 이용하여 투영변환

import numpy as np
import cv2

def order_points(pts):
    rect = np.zeros((4, 2), dtype="float32")

    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]

    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    return rect

def four_point_transform(image, pts):
    rect = order_points(pts)
    (tl, tr, br, bl) = rect

    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))

    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))

    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype="float32")

    M = cv2.getPerspectiveTransform(rect, dst)
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))

    return warped

img = cv2.imread('ocr.jpeg')
ratio = 800.0/img.shape[0]
dim = (int(img.shape[1] * ratio), 800)
img = cv2.resize(img, dim, interpolation= cv2.INTER_AREA)
og_img = img.copy()



GRAY = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
GRAY = cv2.GaussianBlur(GRAY, (3,3), 0)
edged = cv2.Canny(GRAY, 70,200)



cnts, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key =cv2.contourArea, reverse= True)[:5]
## 반환받은 cnt중 면적인 큰 순서대로 5번까지 반환 
check = False
for c in cnts:
    ## 순차적으로 탐색
    peri = cv2.arcLength(c, True)
    ## 컨투어의 길이를 반환
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    ## 길이의 오차 2퍼센트로 도형을 근사화
    if len(approx) == 4:
        ## 근사화한 도형의 꼭지점이 4개라면 그것이 문서의 외곽
        screenCnt = approx
        check = True
        break
if check == False:
    cv2.imshow("IMG", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

cv2.drawContours(img, [screenCnt], -1, (0,255,0), 2)
warped = four_point_transform(og_img, screenCnt.reshape(4, 2))
cv2.imshow("IMG", img)
cv2.imshow("warped", warped)
cv2.waitKey(0)
cv2.destroyAllWindows()

전체 코드

#-- 필요 라이브러리 import 

import cv2
import numpy as np
from google.colab.patches import cv2_imshow

#-- 필요 함수 setting
#-- 1. 조명값 이진화 처리 함수
def adaptive_threshold(oriimg): 
    img = cv2.cvtColor(oriimg, cv2.COLOR_BGR2GRAY)

    blur = cv2.GaussianBlur(img, (7,7), 0)
    result_without_blur = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 21,10)
    result_with_blur = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 21,10)
    # cv2_imshow(result_without_blur)
    # cv2_imshow(result_with_blur)
    return result_without_blur

#-- 2. 투영변환 이미지 반환 함수
def order_points(pts):
    rect = np.zeros((4, 2), dtype="float32")

    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]

    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    return rect

def four_point_transform(image, pts):
    rect = order_points(pts)
    (tl, tr, br, bl) = rect

    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
    maxWidth = max(int(widthA), int(widthB))

    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    maxHeight = max(int(heightA), int(heightB))

    dst = np.array([
        [0, 0],
        [maxWidth - 1, 0],
        [maxWidth - 1, maxHeight - 1],
        [0, maxHeight - 1]], dtype="float32")

    M = cv2.getPerspectiveTransform(rect, dst)
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))

    return warped

  #-- 3. 1번 2번 함수를 이용하여 고르지 못한 이미지를 투영변환을 통해 반듯한 이미지로 반환해주는 함수
  def get_warp_img(image_path):
      img = cv2.imread(image_path)
      ratio = 800.0/img.shape[0]
      dim = (int(img.shape[1] * ratio), 800)
      img = cv2.resize(img, dim, interpolation= cv2.INTER_AREA)
      og_img = img.copy()

      GRAY = adaptive_threshold(og_img)
      GRAY = cv2.GaussianBlur(GRAY, (5,5), 0)
      edged = cv2.Canny(GRAY, 50,200)

      cnts, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
      cnts = sorted(cnts, key =cv2.contourArea, reverse= True)[:5]
      ## 반환받은 cnt중 면적인 큰 순서대로 5번까지 반환 
      check = False
      for c in cnts:
          ## 순차적으로 탐색
          peri = cv2.arcLength(c, True)
          ## 컨투어의 길이를 반환
          approx = cv2.approxPolyDP(c, 0.02 * peri, True)
          ## 길이의 오차 2퍼센트로 도형을 근사화
          if len(approx) == 4:
              ## 근사화한 도형의 꼭지점이 4개라면 그것이 문서의 외곽
              screenCnt = approx
              check = True
              break
      if check == False:
          # cv2.imshow("IMG", img) #-- opencv
          cv2_imshow(img) #-- colab
          cv2.waitKey(0)
          cv2.destroyAllWindows()

      cv2.drawContours(img, [screenCnt], -1, (0,255,0), 2)
      warped = four_point_transform(og_img, screenCnt.reshape(4, 2))
      # cv2.imshow("IMG", img)
      # cv2.imshow("warped", warped)
      # cv2_imshow(img)
      # cv2_imshow(warped)
      return warped
#-- 사용
image_path = "자신의 IMG 경로 입력"
ori_img = cv2.imread(image_path)
warp_img = get_warp_img(image_path)
cv2_imshow(ori_img)
cv2_imshow(warp_img)

위 imshow 코드는 colab 기준이며 만약 opencv환경에서는 cv2.imshow("test", img)  방식으로 사용해야 합니다.

명함스캐너.ipynb
0.53MB

#-- 전처리 과정에서 뒷배경이 너무 밝으면 제대로된 4각형을 인식못하기 때문에 배경은 어둡게 해줘야 한다.