자동차 번호판 분석, 이미지추출/저장, 텍스트 추출 프로그램
2022.05.27
C++ 차량 번호판 분석 프로그램
한글 인식은 개선 필요.
// Main.cpp
#include "Main.h"
#include "Main_function.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
string plateNum = "";
string result_plateNum, result_image;
Mat image, image0, image_wrap, image1, image2, image3, image4, image5, image6, image7, image8, image9, image10; // input image
Mat imgGrayscaleScene, imgThreshScene;
RNG rng;
//plateNum = "09.jpg";
//SHOW_STEPS = 1;
//SHOW_RESULT = 1;
///*
if (argv[1]) {
if (argv[1] == string("help")) {
cout << "exe [image file path/name] [option]" << endl;
cout << "[option] log : 진행 과정의 이미지를 보여줌" << endl;
cout << "[option] result : 결과이미지를 현재 경로에 저장" << endl;
cout << "[option] reslog : 진행 과정의 이미지를 보여줌 & 결과이미지를 현재 경로에 저장" << endl;
return(0);
}
else {
plateNum = argv[1];
}
}
else {
cout << "파라미터(이미지파일)가 없습니다." << endl;
cout << "ex) car_plate_detection.exe help" << endl;
return(0);
}
// 출력결과 파일명
if (argv[2]) {
if (argv[2] == string("log")) {
SHOW_STEPS = 1;
}
else if (argv[2] == string("result") || argv[2] == string("res")) {
SHOW_RESULT = 1;
}
else if (argv[2] == string("reslog") || argv[2] == string("logres")) {
SHOW_STEPS = 1;
SHOW_RESULT = 1;
}
else {
result_image = argv[2];
}
}
//*/
if (SHOW_STEPS == 1) {
destroyAllWindows();
// cv::namedWindow("win");
// cv::moveWindow("win", 0, 20);
cv::namedWindow("win0");
cv::moveWindow("win0", 400, 20);
cv::namedWindow("win1");
cv::moveWindow("win1", 800, 20);
cv::namedWindow("win2");
cv::moveWindow("win2", 1200, 20);
cv::namedWindow("win3");
cv::moveWindow("win3", 0, 300);
cv::namedWindow("win4");
cv::moveWindow("win4", 400, 300);
cv::namedWindow("win5");
cv::moveWindow("win5", 800, 300);
cv::namedWindow("win6");
cv::moveWindow("win6", 1200, 300);
cv::namedWindow("win7");
cv::moveWindow("win7", 0, 800);
cv::namedWindow("win8");
cv::moveWindow("win8", 400, 800);
cv::namedWindow("win9");
cv::moveWindow("win9", 800, 800);
cv::namedWindow("win10");
cv::moveWindow("win10", 1200, 800);
cv::namedWindow("win11");
cv::moveWindow("win11", 1200, 900);
}
image = cv::imread(plateNum); // open image
if (image.empty()) {
cout << plateNum << "는 이미지 파일이 아닙니다." << endl;
return(0);
}
bool blnKNNTrainingSuccessful = loadKNNDataAndTrainKNN();
//resize(image, image0, cv::Size(1200, 935), 0, 0, cv::INTER_AREA);
//resize(image, image0, cv::Size(), 1.5, 1.5);
//Rect rect(80, 120, 640, 300);
//image0 = image(rect);
int img_width = image.size().width;
int img_height = image.size().height;
int img_width2, img_height2;
Point2f inputQuad[4];
Point2f outputQuad[4];
Mat lambda(2, 4, CV_32FC1);
Mat input, output;
for (int loop = 0; loop <= 12; loop++) {
if (loop == 0) {
//image0 = image.clone();
img_height2 = 300;
img_width2 = getWidth(img_width, img_height, img_height2);
resize(image, image0, cv::Size(img_width2, img_height2), 0, 0, cv::INTER_AREA);
}
else if (loop == 1) {
img_height2 = 400;
img_width2 = getWidth(img_width, img_height, img_height2);
resize(image, image0, cv::Size(img_width2, img_height2), 0, 0, cv::INTER_AREA);
}
else if (loop == 2) {
// 사진 하단 양쪽 늘임
input = image0.clone();
lambda = Mat::zeros(input.rows, input.cols, input.type());
inputQuad[0] = Point2f(0, 0);
inputQuad[1] = Point2f(input.cols, 0);
inputQuad[2] = Point2f(input.cols, input.rows);
inputQuad[3] = Point2f(0, input.rows);
outputQuad[0] = Point2f(0, 0);
outputQuad[1] = Point2f(input.cols, 0);
outputQuad[2] = Point2f(input.cols + 100, input.rows);
outputQuad[3] = Point2f(-100, input.rows);
lambda = getPerspectiveTransform(inputQuad, outputQuad);
warpPerspective(input, output, lambda, output.size());
image0 = output.clone();
image_wrap = image0.clone();
}
else if (loop == 3) {
// 사진 좌측 위아래 늘임
input = image_wrap.clone();
outputQuad[0] = Point2f(-120, -80);
outputQuad[1] = Point2f(input.cols, 0);
outputQuad[2] = Point2f(input.cols, input.rows);
outputQuad[3] = Point2f(0, input.rows + 150);
lambda = getPerspectiveTransform(inputQuad, outputQuad);
warpPerspective(input, output, lambda, output.size());
image0 = output.clone();
}
else if (loop == 4) {
// 사진 우측 위아래 늘임
input = image_wrap.clone();
outputQuad[0] = Point2f(0, 0);
outputQuad[1] = Point2f(input.cols + 120, -80);
outputQuad[2] = Point2f(input.cols, input.rows + 150);
outputQuad[3] = Point2f(0, input.rows);
lambda = getPerspectiveTransform(inputQuad, outputQuad);
warpPerspective(input, output, lambda, output.size());
image0 = output.clone();
}
else if (loop == 5) {
// 사진 좌측 하단 늘임
input = image_wrap.clone();
outputQuad[0] = Point2f(0, 100);
outputQuad[1] = Point2f(input.cols, 0);
outputQuad[2] = Point2f(input.cols, input.rows);
outputQuad[3] = Point2f(0, input.rows + 350);
lambda = getPerspectiveTransform(inputQuad, outputQuad);
warpPerspective(input, output, lambda, output.size());
image0 = output.clone();
}
else if (loop == 6) {
// 사진 우측 하단 늘임
input = image_wrap.clone();
outputQuad[0] = Point2f(0, 0);
outputQuad[1] = Point2f(input.cols, 100);
outputQuad[2] = Point2f(input.cols, input.rows + 350);
outputQuad[3] = Point2f(0, input.rows);
lambda = getPerspectiveTransform(inputQuad, outputQuad);
warpPerspective(input, output, lambda, output.size());
image0 = output.clone();
}
else if (loop == 7) {
img_height2 = 600;
img_width2 = getWidth(img_width, img_height, img_height2);
resize(image, image0, cv::Size(img_width2, img_height2), 0, 0, cv::INTER_AREA);
int pos_x = image0.size().width / 2 - 300;
int pos_y = img_height2 / 2 - 100;
Rect rect(pos_x, pos_y, 600, 400);
image0 = image0(rect);
}
else if (loop == 8) {
img_height2 = 800;
img_width2 = getWidth(img_width, img_height, img_height2);
resize(image, image0, cv::Size(img_width2, img_height2), 0, 0, cv::INTER_AREA);
int pos_x = image0.size().width / 2 - 300;
int pos_y = img_height2 / 2 - 100;
Rect rect(pos_x, pos_y, 600, 400);
image0 = image0(rect);
}
else if (loop == 9) {
img_height2 = 1000;
img_width2 = getWidth(img_width, img_height, img_height2);
resize(image, image, cv::Size(img_width2, img_height2), 0, 0, cv::INTER_AREA);
Rect rect(0, 700, img_width2, 300);
image0 = image(rect);
}
else if (loop == 10) {
Rect rect(0, 500, img_width2, 300);
image0 = image(rect);
}
else if (loop == 11) {
Rect rect(0, 300, img_width2, 300);
image0 = image(rect);
}
else if (loop == 12) {
Rect rect(0, 100, img_width2, 300);
image0 = image(rect);
}
if (SHOW_STEPS == 1) {
cout << "try: " << loop << endl;
}
if (SHOW_STEPS == 1) {
// cv::imshow("win", image); // 원본 이미지
cv::imshow("win0", image0); // 리사이즈 이미지
}
cvtColor(image0, image1, CV_BGR2GRAY); // gray-color 변환
if (SHOW_STEPS == 1) {
cv::imshow("win1", image1);
}
preprocess(image0, image2, image3);
if (SHOW_STEPS == 1) {
cv::imshow("win2", image2);
cv::imshow("win3", image3); // adaptive threshold(문턱값) / 영상 이진화
cv::waitKey(0);
}
image4 = cv::Mat(image3.size(), CV_8UC3, SCALAR_BLACK);
int intCountOfPossibleChars = 0;
vector<vector<cv::Point> > contours;
vector<PossiblePlate> vectorOfPossiblePlates;
vector<PossibleChar> vectorOfPossibleChars;
findContours(image3, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); // find all contours
//findContours(image3, contours, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);
//findContours(image3, contours, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
for (unsigned int i = 0; i < contours.size(); i++) { // for each contour
drawContours(image4, contours, i, SCALAR_WHITE);
PossibleChar possibleChar(contours[i]);
if (checkIfPossibleChar(possibleChar)) { // if contour is a possible char, note this does not compare to other chars (yet) . . .
intCountOfPossibleChars++; // increment count of possible chars
vectorOfPossibleChars.push_back(possibleChar); // and add to vector of possible chars
}
}
if (SHOW_STEPS == 1) {
cv::imshow("win4", image4); // contours(윤곽선1)
}
//vector<PossibleChar> vectorOfPossibleCharsInScene = findPossibleCharsInScene(image3);
vector<vector<PossibleChar> > vectorOfVectorsOfMatchingCharsInScene = findVectorOfVectorsOfMatchingChars(vectorOfPossibleChars);
if (SHOW_STEPS == 1) {
image5 = cv::Mat(image4.size(), CV_8UC3, SCALAR_BLACK);
for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInScene) {
int intRandomBlue = rng.uniform(0, 256);
int intRandomGreen = rng.uniform(0, 256);
int intRandomRed = rng.uniform(0, 256);
vector<vector<cv::Point> > contours;
for (auto &matchingChar : vectorOfMatchingChars) {
contours.push_back(matchingChar.contour);
//cout << "char: " << matchingChar.contour << endl;
}
cv::drawContours(image5, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
}
cv::imshow("win5", image5);
cv::waitKey(0);
}
for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInScene) {
PossiblePlate possiblePlate = extractPlate(image0, vectorOfMatchingChars);
if (possiblePlate.imgPlate.empty() == false) {
vectorOfPossiblePlates.push_back(possiblePlate);
}
}
if (SHOW_STEPS == 1) {
for (unsigned int i = 0; i < vectorOfPossiblePlates.size(); i++) {
cv::Point2f p2fRectPoints[4];
vectorOfPossiblePlates[i].rrLocationOfPlateInScene.points(p2fRectPoints);
for (int j = 0; j < 4; j++) {
cv::line(image5, p2fRectPoints[j], p2fRectPoints[(j + 1) % 4], SCALAR_RED, 2);
}
cv::imshow("win6", image5);
cv::imshow("win7", vectorOfPossiblePlates[i].imgPlate);
cv::waitKey(0);
}
}
// 추출된 번호판 숫자 인식
for (auto &possiblePlate : vectorOfPossiblePlates) {
int img_height2 = 100;
int img_width2 = getWidth(possiblePlate.imgPlate.cols, possiblePlate.imgPlate.rows, img_height2);
preprocess(possiblePlate.imgPlate, possiblePlate.imgGrayscale, possiblePlate.imgThresh);
resize(possiblePlate.imgThresh, possiblePlate.imgThresh, cv::Size(img_width2, img_height2), 0, 0, cv::INTER_AREA);
// threshold again to eliminate any gray areas
cv::threshold(possiblePlate.imgThresh, possiblePlate.imgThresh, 0.0, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU);
if (SHOW_STEPS == 1) {
cv::imshow("win8", possiblePlate.imgThresh); // 5d
}
vector<PossibleChar> vectorOfPossibleCharsInPlate = findPossibleCharsInPlate(possiblePlate.imgGrayscale, possiblePlate.imgThresh);
if (SHOW_STEPS == 1) {
image6 = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
contours.clear();
for (auto &possibleChar : vectorOfPossibleCharsInPlate) {
contours.push_back(possibleChar.contour);
}
cv::drawContours(image6, contours, -1, SCALAR_WHITE);
cv::imshow("win9", image6); // 6
}
vector<vector<PossibleChar> > vectorOfVectorsOfMatchingCharsInPlate = findVectorOfVectorsOfMatchingChars(vectorOfPossibleCharsInPlate);
if (SHOW_STEPS == 1) {
image7 = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
contours.clear();
for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) {
int intRandomBlue = rng.uniform(0, 256);
int intRandomGreen = rng.uniform(0, 256);
int intRandomRed = rng.uniform(0, 256);
for (auto &matchingChar : vectorOfMatchingChars) {
contours.push_back(matchingChar.contour);
}
cv::drawContours(image7, contours, -1, cv::Scalar((double)intRandomBlue, (double)intRandomGreen, (double)intRandomRed));
}
cv::imshow("win10", image7); // 7
}
if (vectorOfVectorsOfMatchingCharsInPlate.size() == 0) { // if no groups of matching chars were found in the plate
possiblePlate.strChars = ""; // set plate string member variable to empty string
//possiblePlate.strChars.clear();
continue; // go back to top of for loop
}
for (auto &vectorOfMatchingChars : vectorOfVectorsOfMatchingCharsInPlate) { // for each vector of matching chars in the current plate
sort(vectorOfMatchingChars.begin(), vectorOfMatchingChars.end(), PossibleChar::sortCharsLeftToRight); // sort the chars left to right
vectorOfMatchingChars = removeInnerOverlappingChars(vectorOfMatchingChars); // and eliminate any overlapping chars
}
unsigned int intLenOfLongestVectorOfChars = 0;
unsigned int intIndexOfLongestVectorOfChars = 0;
for (unsigned int i = 0; i < vectorOfVectorsOfMatchingCharsInPlate.size(); i++) {
if (vectorOfVectorsOfMatchingCharsInPlate[i].size() > intLenOfLongestVectorOfChars) {
intLenOfLongestVectorOfChars = vectorOfVectorsOfMatchingCharsInPlate[i].size();
intIndexOfLongestVectorOfChars = i;
}
}
// suppose that the longest vector of matching chars within the plate is the actual vector of chars
vector<PossibleChar> longestVectorOfMatchingCharsInPlate = vectorOfVectorsOfMatchingCharsInPlate[intIndexOfLongestVectorOfChars];
if (SHOW_STEPS == 1) {
image8 = cv::Mat(possiblePlate.imgThresh.size(), CV_8UC3, SCALAR_BLACK);
contours.clear();
for (auto &matchingChar : longestVectorOfMatchingCharsInPlate) {
contours.push_back(matchingChar.contour);
}
cv::drawContours(image8, contours, -1, SCALAR_WHITE);
cv::imshow("win11", image8);
}
possiblePlate.strChars = recognizeCharsInPlate(possiblePlate.imgThresh, longestVectorOfMatchingCharsInPlate, result_image);
if (possiblePlate.strChars != "") {
replace(possiblePlate.strChars, " ", "");
int len = possiblePlate.strChars.size();
if (SHOW_STEPS == 1) {
cout << "[ " << possiblePlate.strChars << " ]" << endl;
}
if (len == 6) {
result_plateNum = possiblePlate.strChars;
break;
}
}
}
if (result_plateNum != "") {
cout << result_plateNum << endl;
break;
}
cv::waitKey(0);
}
return(0);
}