Schoolwork/컴퓨터그래픽스 및 비젼

[Python] 07주차 과제 - 번호 인식을 위한 전처리(이진화)

FATKITTY 2020. 9. 5. 16:28
반응형

 목표

- 번호 인식을 위한 전처리로써 자동차 번호판 영상에 대한 이진화 처리

 

 세부목표:

- 이 과제에서는 자동차 번호판을 정상의 형태로 변형한 후

- 이진화 하고

- 모폴로지 연산을 이용하여 스무싱 한다.

 기타:

- 주요한 과정에 대하여 module 함수를 사용해도 무방합니다.

- 필요한 경우 각 단계에서 *적절한* 최적화 가 필요합니다. 여러분의 최적화 노력을 PPT에 간략하게 설명하기 바랍니다.

- 기타 세부적인 사항은 합리적으로 가정하시기 바랍니다.

 

 과제의 내용

1. 번호판 영상을 준비한다.

A. 정상이 아닌 비스듬히 촬영한다.

 

2. 번호판 영상을 정상으로 변환한다.

A. 이전 과제의 코드를 이용하여 투영변환

B. 영상의 크기는 번호판 보다 가로 세로가 10% 더 크게 되도록 조정할 것.

 

3. 이진화

A. 오츄 또는 adaptive 등 적절한 thresholding 기법 을 사용할 것

B. 파라미터 최적화가 필요함.  

 

4. 스무싱 및 잡음제거

A. 이진 모폴로지 연산을 이용하여 잡음제거 및 윤곽을 스무싱할 것

B. 적절한 크기와 형태의 SE, 연산의 종류를 선택

 

5. 다음 단계는 번호 인식임.

A. 전처리는 인식을 위하여 깨끗한 이진 영상을 준비하기 위한 목적임.

B. 번호 인식을 위한 알고리즘을 1가지를 조사하여 별도의 PPT(발표 길이5 -10분, 자동플레이 및 오디오 설명)로 요약하세요.

C. 인식은 프로그램 안해도 됩니다.

 

6. 각 단계 마다 컬러, 그레이, 이진 영상 등에 적절한 잡음 제거 등의 필터링이 적용되어야 합니다.

과제내용

먼저 저번 주 과제의 코드를 활용해서 비스듬히 촬영된 동영상 속 번호판의 네 모서리 좌표를 구합니다. 프로그램을 실행할 때 마다 매번 모서리들을 클릭하기 번거로우니, 편의성을 위해 출력된 값을 배열에 미리 저장하고 다음 단계들을 실행했습니다.

 

정상 투영변환한 결과입니다. 변환된 결과가 원본 영상의 번호판보다 가로, 세로 각각 10%씩 크게 되도록 조정하라고 명시되어 있었지만, 그렇게 변환하니 너무 작아서 불편했습니다. 그래서 가로, 세로를 각각 2배씩 크게 설정했습니다.
다음으로는 이진화 과정입니다. 투영변환된 결과를 여러가지 thresholding 기법을 사용해서 이진화를 시도해봤습니다. 왼쪽부터 차례대로 THRESH_BINARY, ADAPTIVE_THRESH_MEAN_C, ADAPTIVE_THRESH_GAUSSIAN_C 의 결과입니다. 첫번째는 노이즈는 가장 적지만 글자들이 얇아졌고, 세번째는 두번째와 결과가 비슷해 보이지만 자세히 보니 글자에 구멍들이 많이 있습니다. 두번째가 가장 글자들의 모양이 잘 보존되고 선명해서, 두번째 thresholding 기법을 선택하여 사용했습니다.
이번 단계에서는 앞에서 구한 이진화가 된 번호판 이미지의 노이즈를 제거하고 윤곽을 스무싱 해줍니다. Structuring element를 적절히 설정해줘야 하는데, 처음엔 2x2 정사각형으로 설정 후 결과를 살펴봤습니다. 노이즈가 거의 제거되지 않는 걸 확인할 수 있었습니다.
이번엔 4x4 사이즈의 정사각형 structuring element 를 적용한 결과입니다. closing과 close and open 을 보시면 2x2 보다 확실히 노이즈가 많이 제거 됐습니다. close and open 은 closing 과 opening을 차례로 적용한 결과입니다. 하지만 노이즈 제거가 완벽하진 않아서 한 단계 더 높여봤습니다.
5x5 로 사이즈를 좀 더 크게 해보니, 노이즈 제거가 거의 완벽히 됐습니다.
여기서 closing 과 close and open 의 결과가 똑같아 보이지만, 자세히 확대해보니 오른쪽의 글자가 미세하지만 조금 더 선명해진 것을 확인 할 수 있습니다. 특히 한글 ‘버’ 부분이 깨짐없이 부드러워졌습니다. 그래서 최종적으로 close and open 을 결과 이미지로써 선택했습니다.
최종 결과 입니다. 왼쪽부터 차례대로 투영변환, 이진화, 잡음제거와 스무싱 의 결과물입니다. 위에 나사 부분은 어쩔수 없지만, 번호판의 흰 배경 부분에 나타났던 노이즈는 다 제거가 되었고, 또한 글자들이 선명합니다. 이로써 번호인식을 위한 전처리가 완료되었습니다.

Code

import cv2
import numpy as np
from matplotlib import pyplot as plt

vid_path = 'car_number_video3.mp4'
cap = cv2.VideoCapture(vid_path)

src = []  # 왼쪽위, 오른쪽위, 오른쪽아래, 왼쪽아래 순으로 각 점의 좌표
src.append([270, 141])
src.append([413, 144])
src.append([393, 256])
src.append([254, 228])

'''
# mouse callback handler
def mouse_handler(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONUP:  # 마우스가 좌클릭을 했을때 (4번의 클릭으로 좌표 얻기)
        img = cv2.imread('carvideo_extracted.jpg')

        src.append([x, y])

        for xx, yy in src:  # 클릭한 곳에 점 그리기
            cv2.circle(img, center=(xx, yy), radius=5, color=(0, 255, 0), thickness=-1)

        cv2.imshow('Car Number', img)

        # perspective transform
        if len(src) == 4:
            src_np = np.array(src, dtype=np.float32)
            print("original points : \n", src_np)
'''

pos_frame = cap.get(cv2.CAP_PROP_POS_FRAMES)
frame_count = 0

while True:

    flag, frame = cap.read()
    frame_count += 1

    if flag:
        # frame을 읽을 준비가 완료되었을 때, 그 해당 frame을 pos_frame에 저장
        cv2.imshow('video', frame)
        pos_frame = cap.get(cv2.CAP_PROP_POS_FRAMES)
    '''
    else:
        # frame 이 준비되지 않았다면 다시 읽기를 시도해본다
        cap.set(cv2.CAP_PROP_POS_FRAMES, pos_frame-1)
        # 다음 frame이 준비될 때까지 기다리기
        cv2.waitKey(1000)
    '''

    if cap.get(cv2.CAP_PROP_POS_FRAMES) == cap.get(cv2.CAP_PROP_FRAME_COUNT):
        # 현재까지 읽은 frame 의 수가 전체 frame 수와 같아진다면 읽기를 중단, 다시 처음으로 돌아감. (반복재생)
        cap.open(vid_path)

    if frame_count == 10:
        # 10번 frame을 이미지 파일로 저장해서 나머지 과정들 처리.
        cv2.imwrite('carvideo_extracted.jpg', frame)
        #cv2.namedWindow('Car Number')
        #cv2.setMouseCallback('Car Number', mouse_handler)  # 마우스 이벤트 전달
        #cv2.imshow('Car Number', frame)

    if cv2.waitKey(1) == 32:
        break
cap.release()
#cv2.destroyAllWindows()

ext_img = cv2.imread('carvideo_extracted.jpg')
# 32비트로 바꿔줌
src_np = np.array(src, dtype=np.float32)

# width, height 길이 계산
width = max(np.linalg.norm(src_np[0] - src_np[1]), np.linalg.norm(src_np[2] - src_np[3]))
height = max(np.linalg.norm(src_np[0] - src_np[3]), np.linalg.norm(src_np[1] - src_np[2]))

# width, height 비율
width_ratio = (width / height)
height_ratio = 1

# 2배 더 크도록 픽셀 크기 맞춤
dst_np = np.array([[0, 0],
                    [int(width_ratio * 200), 0],
                    [int(width_ratio * 200), int(height_ratio * 200)],
                    [0, int(height_ratio * 200)]
                    ], dtype=np.float32)

M = cv2.getPerspectiveTransform(src=src_np, dst=dst_np)
result = cv2.warpPerspective(ext_img, M=M, dsize=(int(width_ratio * 200), height_ratio * 200))
cv2.imshow('Car Number', result)
cv2.waitKey(0)

result_grey = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
#ret, th1 = cv2.threshold(result_grey, 127, 255, cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(result_grey, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, 2)
#th3 = cv2.adaptiveThreshold(result_grey, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 2)
#cv2.imshow('th1', th1)
#cv2.imshow('th2', th2)
#cv2.imshow('th3', th3)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dilation = cv2.dilate(th2, kernel, iterations=1)
erosion = cv2.erode(th2, kernel, iterations=1)
opening = cv2.morphologyEx(th2, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(th2, cv2.MORPH_CLOSE, kernel)
closenopen = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)
#titles = ['image', 'th2', 'dilation', 'erosion', 'opening', 'closing', 'close and open']
#images = [result_grey, th2, dilation, erosion, opening, closing, closenopen]
titles = ['image', 'th2', 'close and open']
images = [result_grey, th2, closenopen]

for i in range(3):
    plt.subplot(1, 3, i+1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

if cv2.waitKey(0) == 32:
    cv2.destroyAllWindows()

 

점수 10/10

 

 

  ❤와 댓글은 큰 힘이 됩니다. 감사합니다 :-)  

반응형