Object Detection

직접 쓴 손 글씨(숫자) 인식하기 - 글씨 추출 및 검출

jwjwvison 2021. 8. 27. 22:58

 이번 포스팅에서는 이전 포스팅에 이어서 종이에 쓰인 글씨를 따로 추출하는 방법에 대해서 알아보겠다.

 

 먼저 이미지의 윤곽선을 추출한다.

# cv2.findCountours() function changed from OpenCV3 to openCV4: not it have only two parameters instead of 3
# 윤곽선을 찾아낸다
cvMajorVersion=cv2.__version__.split('.')[0]
print('OpenCV version:',cvMajorVersion)

# check for contours on thresh
if int(cvMajorVersion) >=4:
    contours,hierarchy = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
else:
    imageContours,contours,hierarchy=cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)

 

 이미지에 쓰인 숫자에 사각형을 그려본다.

img_digits=[]
img_origin=img.copy()
margin=10

# loop to check if any possible contour is found 
# 경계들을 배열로 가져와서 바운더리 사각형을 구한다
for contour in contours:
    x,y,w,h=cv2.boundingRect(contour)
    
    # 너무 작은 이미지는 무시한다
    if w*h < 100: 
        continue
        
    # Y and X [y-margin:y+h+margin,x-margin:x+margin]
    img_digits.append(img_origin[y-margin:y+h+margin,x-margin:x+w+margin])
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
    print(x,x+w,y,y+h)
    
plt.imshow(img)

 

 사각형 안에 있는 글씨들만 이미지로 추출해보자.

for i in range(0,len(img_digits)):
    plt.subplot(1,len(img_digits),i+1)
    plt.imshow(img_digits[i],cmap='Greys',interpolation='nearest')
plt.tight_layout()
plt.show()

 

 사전 학습된 모델이 input image를 (28,28)로 학습되어있기 때문에 위 이미지들의 사이즈를 조절해준다.

SIZE=28

for i in range(0,len(img_digits)):
    plt.subplot(1,len(img_digits),i+1)
    plt.imshow(cv2.resize(img_digits[i],(SIZE,SIZE)),cmap='Greys',interpolation='nearest')
plt.tight_layout()
plt.show()

 

 그다음 미리 학습된 모델을 불러온다.

import tensorflow as tf

model=tf.keras.models.load_model('digits_model.h5')
model.summary()

 

 마지막으로 위의 세 사진을 예측해본다.

for i in range(len(img_digits)):
    plt.subplot(1,len(img_digits),i+1)
    num=cv2.resize(img_digits[i],(SIZE,SIZE))[:,:,1] # 색 channel중 한가지만 사용한다
    num=255-num # 색을 반전시킨다
    num=num.astype('float32') / 255.0
    plt.imshow(num,cmap='Greys',interpolation='nearest')
    result=model.predict(np.array([num]))
    
    result_number=np.argmax(result)
    plt.title(result_number)
    performance=[val for val in result[0]]
    print(performance)
plt.tight_layout()
plt.show()

 뭔가 예측을 하긴 하는데 정확하지가 않다.

 

 다음 코드는 내장 카메라를 통해 실시간으로 종이에 쓰여진 숫자를 인식해보는 코드이다.

import cv2
import numpy as np
from tkinter import *
from PIL import Image
from PIL import ImageTk
import tensorflow as tf
import time

model=tf.keras.models.load_model('digits_model.h5')
default_src=0  # 기본 카메라
title_name='MNIST Hand Write Digits Recognition Video'
SZ=28
frame_width=300
frame_height=300
cap=cv2.VideoCapture(default_src)

def start():
    # initialize the video stream and allow the camera sensor to
    # warmup
    global cap
    default_src = int(srcSpin.get())
    #cap = VideoStream(usePiCamera=1).start()
    cap = cv2.VideoCapture(default_src)
    #time.sleep(2.0)
    detectAndDisplay()

def detectAndDisplay():
    _, frame = cap.read()
    width = frame.shape[1]
    height = frame.shape[0]
    width_ratio = frame_width / width
    height_ratio = frame_height / height

    frame = cv2.resize(frame, (frame_width, frame_height))
    # hsv transform - value = gray image
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    hue, saturation, value = cv2.split(hsv)

    # kernel to use for morphological operations
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))

    # applying topHat operations
    topHat = cv2.morphologyEx(value, cv2.MORPH_TOPHAT, kernel)

    # applying blackHat operations
    blackHat = cv2.morphologyEx(value, cv2.MORPH_BLACKHAT, kernel)

    # add and subtract between morphological operations
    add = cv2.add(value, topHat)
    subtract = cv2.subtract(add, blackHat)

    # applying gaussian blur on subtract image
    blur = cv2.GaussianBlur(subtract, (5, 5), 0)

    # thresholding
    thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 19, 9)
    #cv2.imshow('thresh', thresh)
    
    # 윤곽선을 구한다
    contours, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    

    img_digits = []
    positions = []
    margin = 30

    img_origin = frame.copy()

    for contour in contours:
      x, y, w, h = cv2.boundingRect(contour)

      # Ignore small sections
      if w * h < 2400: continue
      y_position = y-margin
      if(y_position < 0): y_position = 0
      x_position = x-margin
      if(x_position < 0): x_position = 0
      img_roi = thresh[y_position:y+h+margin, x_position:x+w+margin] # 윤곽선 부분의 이미지를 자른다 threshholding 한 이미지를 이용
      num = cv2.resize(img_roi, (SZ,SZ))
      num = num.astype('float32') / 255.
      
      result = model.predict(np.array([num]))
      result_number = np.argmax(result)
      cv2.rectangle(frame, (x-margin, y-margin), (x+w+margin, y+h+margin), (0, 255, 0), 2)
      
      text = "Number is : {} ".format(result_number)
      cv2.putText(frame, text, (margin, frame_height-margin), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

    cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    img = Image.fromarray(cv2image)
    imgtk = ImageTk.PhotoImage(image=img)
    lmain.imgtk = imgtk
    lmain.configure(image=imgtk)
    lmain.after(10, detectAndDisplay) # 새로운 frame이 들어오면 계속 재귀적으로 실행한다


#main
main = Tk()
main.title(title_name)
main.geometry()

#Graphics window
label=Label(main, text=title_name)
label.config(font=("Courier", 18))
label.grid(row=0,column=0,columnspan=4)
srcLabel=Label(main, text='Video Source : ')
srcLabel.grid(row=1,column=0)
srcVal  = IntVar(value=default_src)
srcSpin = Spinbox(main, textvariable=srcVal,from_=0, to=5, increment=1, justify=RIGHT)
srcSpin.grid(row=1, column=1)

Button(main,text="Start", height=2,command=lambda:start()).grid(row=1, column=2, columnspan=2, sticky=(W, E))
imageFrame = Frame(main)
imageFrame.grid(row=2,column=0,columnspan=4)
  
#Capture video frames
lmain = Label(imageFrame)
lmain.grid(row=0, column=0)

main.mainloop()  #Starts GUI