OpenCV 자동차 번호판 문자 분할 실현(C++)

이전 번호판 포 지 셔 닝 에서 번호판 의 위 치 를 얻 었 고 번호판 을 추출 했다.우리 의 최종 목적 은 자동차 번호판 인식 을 하 는 것 이다.그 전에 문 자 를 분할 하여 모든 문 자 를 식별 할 수 있 도록 해 야 한다.마지막 으로 이 를 연결 하면 완전한 자동차 번호 이다.자동차 번호판 의 포 지 셔 닝 에 대해 이 글 을 볼 수 있다.OpenCV 번호판 위치 추적(C++).본 고 에서 사용 한 사진 도 여기 서 나온다.
먼저 원 도 를 살 펴 보 자.

가장 왼쪽 에 있 는 한 자 는 원래 상하 이 인 데 캡 처 할 때 오른쪽 에 있 는 작은 부분 만 얻 었 다.이것 은 원래 의 그림 과 얻 는 방법 과 관련 이 있다.천,상하 이...이런 좌우 로 분 리 된 글 자 는 이런 문제 가 자주 발생 하고 방법 을 최적화 한 후에 해결 할 수 있 으 며 여 기 는 잠시 토론 하지 않 는 다.
뒤의 글 자 는 모두 완전 하고 문자 분할 과정 은 영향 을 받 지 않 습 니 다.먼저 일반적인 작업 을 하고 처리 하기 편리 하도록 그 레이스 케 일 그림 으로 바 꿉 니 다.

분할 하 는 방법 은 한 가지 가 아니다.가장 간단 한 것 은 인공 성분 을 많이 넣 고 대체적인 너비 에 따라 다시 미세 조정 하여 절단 하 는 것 이다.그러나 이렇게 하면 가장 빠 른 것 처럼 보이 지만 사실은 원가 가 가장 높 고 단일 한 그림 에 만 적합 하기 때문에 이런 잘못 사용 이 낮 고 자동 적 이지 못 한 방법 은 고려 하지 않 는 다.
현재 저 는 두 가지 다른 방법 을 사 용 했 습 니 다.하 나 는 가장자리 검 사 를 한 다음 에 윤곽 을 검 측 하고 문자 의 윤곽 특징 에 따라 문 자 를 선별 하 는 것 입 니 다.다른 하 나 는 픽 셀 값 판단 입 니 다.주로 픽 셀 수량 에 따라 수평 맵 을 사용 하여 폭 을 절단 합 니 다.수직 맵 은 높이 가 기본적으로 일치 하기 때문에 필요 하지 않 습 니 다.방법 은 수평 맵 과 같 습 니 다.
두 가지 방법 을 나 는 모두 뒤에 써 서 필요 에 따라 스스로 복제 했다.픽 셀 값 으로 판단 하려 면 그 레이스 케 일 을 이치 화 그림 으로 바 꾸 고 한도 값 으로 분할 하면 된다.첫 번 째 윤곽 으로 나 누 는 방법 을 사용 하면 그 레이스 케 일과 이치 화 그림 이 모두 가능 하 며 결 과 는 별 차이 가 없다.
윤곽 을 검사 하여 분할 하 다.
테두리 검출
그림 에 대해 테두리 검 사 를 실시 합 니 다.여 기 는 Canny 테두리 검 사 를 사 용 했 습 니 다.처 리 된 결 과 는 다음 과 같 습 니 다.

모든 글자 의 가장자리 가 그 려 진 것 을 볼 수 있 고 그 다음 에 모든 글자 의 윤곽 을 얻 을 수 있다.
검 측 윤곽
find Contours()를 사용 하여 모든 윤곽 을 추출 한 다음 원 그림 에 효 과 를 보 여 줍 니 다.

모든 글자 가 틀 에 박 힌 것 이 아니 라 내부 와 이미지 에 나타 난 특수 한 부분의 윤곽 도 있 음 을 알 수 있 습 니 다.그 다음 에 우 리 는 모든 글자 의 대략적인 크기 에 따라 우리 가 원 하 는 결 과 를 선별 할 것 입 니 다.

이렇게 보면 성공 한 것 같 지 않 습 니까?그리고 윤곽 위치 에 따라 모든 글 자 를 추출 하면 됩 니 다.그러나 여기 서 모든 윤곽 의 앞 뒤 순 서 는 반드시 이미지 의 위치 가 아 닙 니 다.여기 서 저 는 모든 윤곽 왼쪽 상단 횡 좌표 x 의 크기 로 정렬 합 니 다.
전체 코드:

#include <iostream> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <map>

using namespace std;
using namespace cv;

int main() {
 Mat img = imread("number.jpg");
 Mat gray_img;
 //       
 cvtColor(img, gray_img, CV_BGR2GRAY);
 //     
 Mat img_gau;
 GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0);
 //     
 Mat img_seg;
 threshold(img_gau, img_seg, 0, 255, THRESH_BINARY + THRESH_OTSU);
 //     ,    
 Mat img_canny;
 Canny(img_seg, img_canny, 200, 100);
 vector<vector<Point>> contours;
 vector<Vec4i> hierarchy;
 findContours(img_canny, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point());
 int size = (int)(contours.size());
 //          
 vector<int> num_order;
 map<int, int> num_map;
 for (int i = 0; i < size; i++) {
 //       
 Rect number_rect = boundingRect(contours[i]);
 int width = number_rect.width;
 int height = number_rect.height;
 //          ,        
 if (width > img.cols/10 && height > img.rows/2) {
 rectangle(img_seg, number_rect.tl(), number_rect.br(), Scalar(255, 255, 255), 1, 1, 0);
 num_order.push_back(number_rect.x);
 num_map[number_rect.x] = i;
 }
 }
 //        
 sort(num_order.begin(), num_order.end());
 for (int i = 0; i < num_order.size(); i++) {
 Rect number_rect = boundingRect(contours[num_map.find(num_order[i])->second]);
 Rect choose_rect(number_rect.x, 0, number_rect.width, gray_img.rows);
 Mat number_img = gray_img(choose_rect);
 imshow("number" + to_string(i), number_img);
 // imwrite("number" + to_string(i) + ".jpg", number_img);
 }
 imshow("    ", gray_img);
 waitKey(0);
 return 0;
}
픽 셀 값 판단 분할
분할 방법:먼저 각 열의 픽 셀 값 이 0 보다 큰 픽 셀 개수 가 5 개 를 초과 하 는 지 판단 할 때 이 열 은 숫자 가 있다 고 생각 하고 각 열의 픽 셀 이 5 보다 큰 지 기록 하여 하나의 배열 을 만 듭 니 다.

//     1    
 int pixrow[1000];
 for (int i = 0; i < roi_col - 1; i++) {
 for (int j = 0; j < roi_row - 1; j++) {
 pix = img_threadhold.at<uchar>(j, i);
 pixrow[i] = 0;
 if (pix > 0) {
 pixrow[i] = 1;
 break;
 }
 }
 }
 //        ,      
 for (int i = 2; i < roi_col - 1 - 2; i++) {
 if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) >= 3) {
 pixrow[i] = 1;
 }
 else if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) <= 1) {
 pixrow[i] = 0;
 }
 }
그 다음 에 픽 셀 이 0 과 1 의 연속 적 인 길 이 를 기록 하여 문자 의 너 비 를 계산 하고 마지막 으로 너비 의 크기 로 문 자 를 선별 합 니 다.

//       
 int count = 0;
 bool flage = false;
 for (int i = 0; i < roi_col - 1; i++) {
 pix = pixrow[i];
 if (pix == 1 && !flage) {
 flage = true;
 position1[count] = i;
 continue;
 }
 if (pix == 0 && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 if (i == (roi_col - 2) && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 }
분 할 된 결과:

전체 코드:

#include <iostream> 
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>

using namespace std;
using namespace cv;

int main() {
 Mat img = imread("number.jpg");
 Mat gray_img;
 //       
 cvtColor(img, gray_img, CV_BGR2GRAY);
 //     
 Mat img_gau;
 GaussianBlur(gray_img, img_gau, Size(3, 3), 0, 0);
 //     
 Mat img_threadhold;
 threshold(img_gau, img_threadhold, 0, 255, THRESH_BINARY + THRESH_OTSU);
 //         
 int roi_col = img_threadhold.cols, roi_row = img_threadhold.rows, position1[50], position2[50], roi_width[50];
 uchar pix;
 //     1    
 int pixrow[1000];
 for (int i = 0; i < roi_col - 1; i++) {
 for (int j = 0; j < roi_row - 1; j++) {
 pix = img_threadhold.at<uchar>(j, i);
 pixrow[i] = 0;
 if (pix > 0) {
 pixrow[i] = 1;
 break;
 }
 }
 }
 //        ,      
 for (int i = 2; i < roi_col - 1 - 2; i++) {
 if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) >= 3) {
 pixrow[i] = 1;
 }
 else if ((pixrow[i - 1] + pixrow[i - 2] + pixrow[i + 1] + pixrow[i + 2]) <= 1) {
 pixrow[i] = 0;
 }
 }
 //       
 int count = 0;
 bool flage = false;
 for (int i = 0; i < roi_col - 1; i++) {
 pix = pixrow[i];
 if (pix == 1 && !flage) {
 flage = true;
 position1[count] = i;
 continue;
 }
 if (pix == 0 && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 if (i == (roi_col - 2) && flage) {
 flage = false;
 position2[count] = i;
 count++;
 }
 }
 //         
 for (int n = 0; n < count; n++) {
 roi_width[n] = position2[n] - position1[n];
 }
 //      、   ,             
 int max = roi_width[0], max_index = 0;
 int min = roi_width[0], min_index = 0;
 for (int n = 1; n < count; n++) {
 if (max < roi_width[n]) {
 max = roi_width[n];
 max_index = n;
 }
 if (min > roi_width[n]) {
 min = roi_width[n];
 min_index = n;
 }
 }
 int index = 0;
 int new_roi_width[50];
 for (int i = 0; i < count; i++) {
 if (i == min_index || i == max_index) {}
 else {
 new_roi_width[index] = roi_width[i];
 index++;
 }
 }
 //           
 int avgre = (int)((new_roi_width[count - 3] + new_roi_width[count - 4] + new_roi_width[count - 5]) / 3.0);
 //         ,      
 int licenseX[10], licenseW[10], licenseNum = 0;
 int countX = 0;
 for (int i = 0; i < count; i++) {
 if (roi_width[i] >(avgre - 8) && roi_width[i] < (avgre + 8)) {
 licenseX[licenseNum] = position1[i];
 licenseW[licenseNum] = roi_width[i];
 licenseNum++;
 countX++;
 continue;
 }
 if (roi_width[i] > (avgre * 2 - 10) && roi_width[i] < (avgre * 2 + 10)) {
 licenseX[licenseNum] = position1[i];
 licenseW[licenseNum] = roi_width[i];
 licenseNum++;
 }
 }

 //     
 Mat number_img = Mat(Scalar(0));
 for (int i = 0; i < countX; i++) {
 Rect choose_rect(licenseX[i], 0, licenseW[i], gray_img.rows);
 number_img = gray_img(choose_rect);
 imshow("number" + to_string(i), number_img);
 // imwrite("number" + to_string(i) + ".jpg", number_img);
 }
 waitKey(0);
 return 0;
}
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기