opencv

자동차 번호판 추출하기

꼰대코더 2025. 2. 15. 22:04

 

아이디어

1. 흑백으로 변환하여 contour 를 실시

2. approxPolyDPcontour 결과로 부터 꼭지점이 4개(4개의 코너)인 것 중 가장 면적이 넓은 contour를 기억

approxPolyDP 알고리즘은 정확한 형태를 추출하는게 아니라 contours를 간단화 하는 것이다.
contour의 형태에 적게 공헌하는 포인트들은 제거되고 형태에 공헌을 많이 하는 코너들은 남게 되는 것이다.
옵션의 epsilon 은 작을수록 제거되는 포인트들은 줄게되고 큰값 일수록 많은 포인트들이 제거되어 간단화가 크게 이루워 진다.

3. boundingRect 로 사각형의 좌표와 사이즈를 추출

 

주의)

노이즈가 심한 경우 contour결과에 영향을 줄 수 있으니 필터링에 신경을 더 써야 할 경우가 있다.

번호판 보다 더 큰 contour가 있을 수 있으니 고정 이미지라면 정해진 사이즈에 근접한 contour 를 선택하면 된다.

cv::Mat img = cv::imread("d:\\plate.jpg");
# gray 변환
cv::Mat gray;
cv::cvtColor(img, gray, CV_BGR2GRAY);
# 노이즈를 완화해 주자
cv::GaussianBlur(gray, gray, cv::Size(3, 3), 0.0);
# 흑백이미지 변환 -> 사진이면 빛의 영향으로 밝기가 고르지 못하니 11x11 내의 이진화 방법을 사용
cv::Mat thresh;
cv::adaptiveThreshold(gray, thresh, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 11, 2);

# contour 실시 -> https://eldercoder.tistory.com/63 참조

std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
# 번호판외부에도 닫혀진 contour가 있을 수 있으니 이번엔 모든 contour를 뽑아내자 ( CV_RETR_LIST )
cv::findContours(thresh, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

# 최대 면적을 갖는 contour index 를 기억하기 위한 변수
double maxArea = 0.0;
size_t maxAreaIdx = 0;

for (size_t i = 0; i < contours.size(); i++) {
        std::vector<std::vector<cv::Point>> points;
        # contour결과의 포인트들로 부터 대충의 형태를 추출
        cv::approxPolyDP(cv::Mat(contours[i]), points, 0.01 * cv::arcLength(contours[i], true), true);
        # 꼭지점이 4개

        if (points.size() == 4) {
            double area = cv::contourArea(contours[i]);
            if (area > maxArea  ) {
                maxArea   = area;
                maxAreaIdx  = i;
            }
        }
}

#  해당 contour 로 부터 좌표와 사이즈 추출
cv::Rect rect = cv::boundingRect(contours[ maxAreaIdx]);
cv::rectangle(img, rect, cv::Scalar(0, 0, 255), 3);

cv::imwrite("d:\\result.png", img);

return 0;

 

Python

import numpy as np
import cv2
import pytesseract
import matplotlib.pyplot as plt

img = cv2.imread('/home/muthu/Documents/3r9OQ.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,11,2)

contours,h = cv2.findContours(thresh,1,2)

largest_rectangle = [0,0]
for cnt in contours:
    approx = cv2.approxPolyDP(cnt,0.01*cv2.arcLength(cnt,True),True)
    if len(approx)==4: 
        area = cv2.contourArea(cnt)
        if area > largest_rectangle[0]:
            largest_rectangle = [cv2.contourArea(cnt), cnt, approx]

x,y,w,h = cv2.boundingRect(largest_rectangle[1])
roi=img[y:y+h,x:x+w]

plt.imshow(roi, cmap = 'gray')
plt.show()