elevne's Study Note

OpenCV 활용 (+ EasyOCR) 본문

Machine Learning/CV

OpenCV 활용 (+ EasyOCR)

elevne 2022. 11. 15. 22:23

저번에 EasyOCR을 알아본데에 이어서, 이번에는 이미지 내 객체의 윤곽선을 바탕으로 이미지를 추출해내는 방법과, 그 이미지에 EasyOCR을 적용하는 것을 실습해보았다. 우선 필요한 라이브러리들을 Import 해준다.

 

 

 

from matplotlib import pyplot as plt
from imutils.perspective import four_point_transform
from imutils.contours import sort_contours
import imutils
from easyocr import Reader
import cv2
import requests
import numpy as np
from PIL import ImageFont, ImageDraw, Image

 

 

 

OpenCV는 Open Source Computer Vision의 약자로, 이미지 처리에 사용할 수 있는 오픈소스 라이브러리이다. 이와 같이 사용되는 imutils 라이브러리는 OpenCV가 제공하는 기능 중 복잡하거나 사용성이 떨어지는 부분을 보완해준다고 한다. E또, 같이 사용할 수 있는 라이브러리로 pillow(PIL) 라이브러리가 있다. 이 또한 이미지 분석 및 처리를 도와주는 라이브러리이다.

 

 

 

셀에서 이미지를 아래와 같은 코드를 사용하여 확인할 수 있다.

 

 

 

def plt_imshow(title='Image', img=None, figsize=(8 ,5)):
      if len(img.shape) < 3:
          rgbImg = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
      else:
          rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
      plt.imshow(rgbImg)
      plt.title(title)
      plt.xticks([]), plt.yticks([])
      plt.show()

 

 

 

기본적으로는 matplotlib의 imshow() 함수를 사용하여 이미지를 띄우는 것이긴 하나, 그 이전에 cv2.cvtColor 함수를 사용하여 이미지에 대한 처리를 진행한다. 우선 img.shape 의 길이를 구하여 이미지의 차원을 알아보아, 흑백 형태여서 3차원 미만일 경우에는 cvtColor 함수의 인자로 COLOR_GRAY2RGB를 넣어주고 그 외의 경우(3차원 이상일 경우)에는, openCV의 imread(읽은 이미지) 파일은 BGR 형태로 읽기 때문에 이는 COLOR_BGR2RGB 를 인자로 넣어주어 형식을 맞춰준다. 

(image.shape을 했을 때 나오는 3개의 정보는 순서대로 height, width, channel 이다.)

 

 

 

그 다음, 다음과 같은 코드를 통해서 이미지 내에서 윤곽을 그리고 윤곽 내의 영역을 가져올 수 있다.

 

 

 

def outline(image, ksize=(5,5), min_threshold=75, max_threshold=200):
 
  image = imutils.resize(image, width=150)
  ratio = org_image.shape[1] / float(image.shape[1])
 
  gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  blurred = cv2.GaussianBlur(gray, ksize, 0)
  edged = cv2.Canny(blurred, min_threshold, max_threshold)
 
  cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  cnts = imutils.grab_contours(cnts)
  cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
 
  findCnt = None
  
  for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    if len(approx) == 4:
      findCnt = approx
      break
 
  if findCnt is None:
    raise Exception(("Could not find outline."))
 
  output = image.copy()
  cv2.drawContours(output, [findCnt], -1, (0, 255, 0), 2)
  transform_image = four_point_transform(org_image, findCnt.reshape(4, 2) * ratio)
 
  plt_imshow("Transform", transform_image)
  return transform_image

 

 

위 함수에 대해서 알아보자면, 우선 imutil.resize() 함수를 사용하여 이미지를 resizing 해준다. 그 후 본래 이미지의 width 정보를 resizing 해준 이미지의 width 값으로 나누어서 크기 차이의 비율을 구해줄 수 있다.

 

 

그 이후 진행되는 것은 모서리를 찾기 위한 연산이다. 우선 resize 한 이미지를 cvtColor(image, cv2.COLOR_BGR2GRAY) 함수에 넣어주어 Gray Scale로 변환을 시켜준다. 그 후 여기에 GaussianBlur 함수가 사용된다. Gaussia Blurring(가우시안 블러링)에 대해 알아보기 전 Averaging Blurring에 대해 알아보아야 한다. 이는 OpenCV에서 Convolution을 거치는 대신 사용할 수 있게끔 제공되는 기능이라고 하는데, 아래와 같이 3 * 3 범위를 인자로 넘겨주면 주변 3 * 3 범위 내의 픽셀의 평균을 결과 이미지의 픽셀값으로 지정하는 기법이다.

 

 

 

averaging = cv2.blur(img, (3, 3))

 

 

 

모든 픽셀에 같은 가중치를 부여하는 Averaging Blurring과 다르게 Gaussian Blurring은 중심에 있는 픽셀에 높은 가중치를 부여한다고 한다. Gaussian Distribution 함수를 근사하여 생성한 필터 마스크를 사용하는 필터링 기법이라고 한다. 아래 코드와 같이 적어줄 수 있는데, 3 번째 파라미터를 0으로 지정하면 지정한 Kernel의 크기에 맞춰 시그마를 계산해서 사용한다고 한다. Canny(캐니)로 에지를 검출하기 전에 노이즈를 제거하기 위해 사용된다.

 

 

 

gaussian = cv2.GaussianBlur(gray, (5, 5), 0)

 

 

 

위 코드에서는 5, 5 커널 사이즈로, Gray Scaled Image에 Gaussian Blurring을 진행하였다. 그 후, Canny 함수를 사용하였다. Canny는 OpenCV에 있는 Edge 검출 함수로, 이미지의 Edge 만을 되돌려준다. 아래와 같이 사용해줄 수 있다.

 

 

 

edge = cv2.Canny(blurred, min_threshold, max_threshold)

 

 

 

인자로 주어진 min_threshold와 max_threshold 값을 적절히 조정해주는 과정이 필요하다. 두 인자는 각각 엣지 여부를 판단하는 최소/최대 임계값을 뜻한다. 위 함수에서는 min value를 75, max value는 200으로 지정하여 사용하였다. 

 

 

 

이제 이렇게 Edge를 얻어내었으니, Contours를 찾을 단계이다. Contour(컨투어)은 등고선을 의미하는데, 이는 지형의 높이가 같은 영역을 하나의 선으로 표시한 것을 뜻하는 것이다. 위 함수에서는 findContours 함수를 사용하였는데, 인자로 이미지와 cv2.RETR_EXTERNAL, cv2.CHIAN_APPROX_SIMPLE 을 넣어주었다. 의미하는 바는 다음과 같다. cv2.RETR_ 로 시작하는 것은 외곽선 검출 모드를 지정하는 것인데, EXTERNAL을 뒤에 붙여주면 외곽 윤곽선만 검출하고 계층구조를 구성하지 않는다. 그 다음으로 RETR_APPROX_ 로 시작하는 부분은 외곽선 근사화 방법을 지정해주는 것으로, SIMPLE을 붙여주면 Contour의 수평, 수직, 대각선 방향의 점은 모두 버리고 끝 점만 남겨둔다. 이렇게 얻은 값을 출력해보면 결과는 다음과 같이 array 형식으로 나오게된다.

 

 

 

cnts = cv2.findContours(edge.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(cnts)

 

 

 

findContours

 

 

그 후 imutils의 grab_contours 함수안에 위 정보를 넣어준다. 이 함수는 cv2.findContours 함수와 주로 같이 사용되는데, 이는 OpenCV2, 3을 구분하지 않고 cnts 안의 Contours 정보를 반환한다고 한다. sorted 함수로 내림차순으로 정렬해준 이후 for 문을 사용하여 4개의 꼭지점을 갖는 도형을 검출해낸다. 

 

 

 

arcLength 함수는 Contour의 둘레 길이를 반환하게 되는데, 이 때 True를 인자로 넣어주면 Contour이 폐곡선임을 알리느 것이고, False를 넣으면 Contour가 열려있는 호임을 아리게 된다. 이를 통해 얻은 값을 approxyPolyDP 안에 넣어주게 되는데 이 함수는 이미지의 윤곽점을 압축하여 다각형으로 근사하기 위해 사용된다. 이를 통해 윤곽선의 근사 다각형을 검출할 수 있는 것이다.

 

 

 

# 다른 예)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_TC89_KCOS)

for contour in contours:
    epsilon = cv2.arcLength(contour, True) * 0.02
    approx_poly = cv2.approxPolyDP(contour, epsilon, True)

 

 

 

위에서 sorted 함수로 Contours 정보를 내림차순으로 정렬해주었기에 제일 첫 번째 사각형을 영역으로 판단하고 Break 해주는 방식으로 코드가 작성되었다. 혹은, 다각형 검출이 되지 않을 경우에는 Exception 이 raise 되도록 작성되었다.

 

 

 

그 후, cv2.drawContours 함수 안에 이미지 원본과 사각형 영역을 리스트 안에 넣은 것, -1 (image에 실제로 그릴 Contour 인덱스 파라미터인데, 이 값이 음수이면 모든 Contour을 그린다고 한다.), (0, 255, 0) (image에 그릴 Contour 선의 BGR 색상 값), 2 (Contour 선의 두께)를 인자로 넣어주었다.

 

 

 

transform_image = four_point_transform(org_image, findCnt.reshape(4, 2)* ratio)

 

 

 

위 함수에서는 마지막으로 four_point_transform 함수를 사용해주었다. 인자로 Original Input Image, 얻은 Contour 사각형을 2차원 (4,2) shape으로 reshape 해준 것에 resized image와의 ratio를 적용해준 것, 2개를 넣어준다. 위 함수로 간단하게 해당하는 영역의 이미지를 추출해낼 수 있는 것이다.

 

 

이제 이미지를 잘라올 준비는 다 되었다. 실제로 진행하기 전에, OpenCV에서는 한국어를 자동으로 지원해주지 않기 때문에 이를 처리할 함수가 필요하다고 한다. 아래와 같이 작성해줄 수 있다.

 

 

 

def putText(cv_img, text, x, y, color=(0, 0, 0), font_size=20):
  font = ImageFont.truetype('/content/drive/MyDrive/Colab Notebooks/BMEULJIROTTF.ttf', font_size)
  img = Image.fromarray(cv_img)
  draw = ImageDraw.Draw(img)
  draw.text((x, y), text, font=font, fill=color)
  cv_img = np.array(img)
  
  return cv_img

 

 

 

Image를 array 형태로 받을 예정이기에 Image.fromarray 함수를 통해 이미지를 load 한다. fromarray 함수는 np.array() 형태 배열로 되어있는 이미지 배열을 PIL 이미지로 변환해준다. ImageDraw.Draw() 안에 image를 넣어주고, draw.text 함수를 사용하여 지정한 font로 글을 넣을 수 있게끔 한다. 그 후, 다시 이를 np.array 형태로 반환해주는 함수이다.

 

 

 

url = 'https://helpx.adobe.com/content/dam/help/ko/acrobat/how-to/scan-paper-documents-searchable-pdf/jcr%3Acontent/main-pars/image/searchable-pdf-step1.jpg.img.jpg'
image_nparray = np.asarray(bytearray(requests.get(url).content), dtype=np.uint8)
org_image = cv2.imdecode(image_nparray, cv2.IMREAD_COLOR) 
plt_imshow("원본", org_image)
 
IMG = make_scan_image(org_image, width=200, ksize=(5, 5), min_threshold=20, max_threshold=100)

 

 

 

마지막으로 request 라이브러리를 사용하여 이미지 주소를 통해 이미지를 받아오고, numpy.asarray 함수를 통해 np.array 형태로 만들어준다. Binary 형태로 있는 이미지 데이터를 읽는 함수로 cv2.imdecode 함수를 사용해주는데, 이는 1D array 인 array를 3D-array로 만들어줄 수 있다고 한다. (인자로는 cv2.IMREAD_COLOR)을 넣어준다. 그 후 위에서 만든 함수에 이를 넣어주면 결과를 확인할 수 있을 것이다.

 

 

결과는 아래와 같다.

 

 

 

원본

 

 

추출 데이터

 

 

 

이후, 이 추출 데이터에서 저번에 사용해본 EasyOCR을 사용할 수 있을 것이다! easyOCR에서 readtext(img) 으로 간단하게 결과를 얻어온 이후, 위에서 만든 putText 함수를 사용해 간단하게 Bouding box와 text 정보를 넣어줄 수 있을 것이다.

 

 

 

for (bbox, text, prob) in result:
  (tl, tr, br, bl) = bbox
  tl = (int(tl[0]), int(tl[1]))
  tr = (int(tr[0]), int(tr[1]))
  br = (int(br[0]), int(br[1]))
  bl = (int(bl[0]), int(bl[1]))
  cv2.rectangle(IMG, tl, br, (0, 255, 0), 2)
  IMG = putText(IMG, text, tl[0], tl[1] - 60, (0, 255, 0), 50)

 

 

 

위 코드들은 다양한 프로젝트에서 유용하게 사용할 수 있을 것 같다..! 잘 기억해둬야겠다.

 

 

 

출처:

https://yunwoong.tistory.com/76

https://scribblinganything.tistory.com/494

https://ballentain.tistory.com/50

https://m.blog.naver.com/samsjang/220516822775

https://deep-learning-study.tistory.com/232

https://076923.github.io/posts/Python-opencv-3/

https://engineer-mole.tistory.com/243

https://webnautes.tistory.com/1255

https://bkshin.tistory.com/entry/OpenCV-22-%EC%BB%A8%ED%88%AC%EC%96%B4Contour

https://deep-learning-study.tistory.com/231

https://m.blog.naver.com/samsjang/220516697251

https://076923.github.io/posts/Python-opencv-21/

'Machine Learning > CV' 카테고리의 다른 글

EAST & CRAFT (EasyOCR)  (0) 2022.11.13
Object Detection (IoU, mAP)  (0) 2022.10.30
Keras-OCR 사용해보기  (0) 2022.10.29