License plate recognition based on OpenCV (1. License plate image recognition)

This article and the following are used for the learning of license plate recognition of the open source project OpenCV! ! !

The process of license plate recognition can be divided into four steps: 1) license plate image cutting; 2) license plate image classification; 3) license plate character cutting; 4) license plate character classification;

1) License plate image cutting: 

a. Convert the original image into a grayscale image, which can remove the external noise generated by multi-channel;

b. sobel filtering, an important feature of license plate segmentation is that there are many vertical edges in the license plate. In order to extract vertical edges, the first-order vertical derivative of sobel is used;

c. Thresholding, applying an OSTU method to automatically obtain a threshold filter to obtain a binary image;

d. Close operation morphology, connect all areas with a large number of edges, delete the blank areas between the edges, and connect the license plate areas;

e. Filled with water, all license plates have a uniform background color. Use the flood fill algorithm to get an accurate trim of the rotation matrix. The flood fill function fills connected regions to the mask image with color, starting from the seed. The filled pixels are compared with the seed points. If the pixel value is x, seed-low<=x<=seed+up, the position will be filled. Once the mask image for clipping is obtained, and thus the smallest bounding rectangle of the mask image points is obtained, check the rectangle size again. For each mask, the white pixel obtains the position and uses the minAreaRect function to retrieve the closest trimmed area;

f. Affine transformation, used to remove the rotation of the detected rectangular area;

g. Extract the rectangle;

h. Resized to a uniform size, histogram equalized, the extracted rectangular images cannot be used well for training and classification because they do not have the same size. Also, each image contains different lighting conditions, increasing the difference between them.

/* imageSlicer.cpp */
#include <iostream>
#include <opencv2/opencv.hpp>
#include <time.h>

using namespace std;
using namespace cv;

//Filter the area and aspect ratio of the rotated rectangle
bool areaDetection(RotatedRect rectArea)
{
	float error = 0.4;//Allow error rate
	const float width_height = 4.7272;//Get the aspect ratio of the Spanish license plate 57200

	int min_area = 25 * 25 * width_height;//Get the maximum and minimum area of ​​the allowed rectangle
	int max_area = 100 * 100 * width_height;

	float min_value = width_height*(1 - error);//Get the maximum and minimum aspect ratio of the allowed rectangle 1.890-6.6180
	float max_value = width_height*(1 + error);

	int rect_area = rectArea.size.width*rectArea.size.height;//Calculate the enclosing area and aspect ratio of the rotatable rectangle
	float rect_value = rectArea.size.width / rectArea.size.height;

	rect_value = rect_value<1 ? 1 / rect_value : rect_value;

	return rect_area>min_area && rect_area<max_area && rect_value>min_value && rect_value<max_value;
}

void imgProcess(Mat carImg)
{
	//1. Get the grayscale image
	Mat grayImg, blurImg, sobelImg, threshImg, mopImg;
	cvtColor(carImg, grayImg, COLOR_BGR2GRAY);
	//imshow("Grayscale", grayImg);

	//2. Mean filter
	/*void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1),
	int borderType=BORDER_DEFAULT )*/
	blur(grayImg, blurImg, Size(5, 5));//Mean filter
	//imshow("Mean filter", blurImg);

	//3.sobel edge detection
	/*void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1,
	double delta=0, int borderType=BORDER_DEFAULT )*/
	Sobel(blurImg, sobelImg, CV_8U, 1, 0);//Sobel operator edge detection only seeks the first derivative for the x direction
	//imshow("sobel edge detection", sobelImg);

	//4. Binarization, the OTSU algorithm automatically obtains the threshold to obtain the binarized image
	/*double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)*/
	threshold(sobelImg, threshImg, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
	//imshow("Image binarization", threshImg);

	//5. Close operation, delete the blank area between the edges, and connect the license plate area
	/*Mat getStructuringElement(int shape, Size ksize, Point anchor=Point(-1,-1))*/
	Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));//Define structural elements
	/*void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1),
	int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )*/
	morphologyEx(threshImg, mopImg, CV_MOP_CLOSE, element);
	//imshow("Closed operation", mopImg);

	//6. Find all contours, then filter by area and aspect ratio
	vector<vector<Point>> contours;//Define contours, each contour is a set of points
	/*void findContours(InputOutputArray Img, OutputArrayOfArrays contours, OutputArray hierarchy,
	int mode, int method, Point offset=Point())*/
	findContours(mopImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//Detect the outermost contour
	map<int, RotatedRect> mapCounter;//Define the map container to store the filtered rectangle outline
	for (int i = 0; i < contours.size(); ++i)
	{
		/*void drawContours(InputOutputArray Img, InputArrayOfArrays contours, int contourIdx, const Scalar& color,
		int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )*/
		drawContours(mopImg, contours, i, Scalar(255), 1);//Draw the contour of the image

		/*RotatedRect minAreaRect(InputArray points)*/
		RotatedRect contourRect = minAreaRect(contours[i]);//Given a 2D point set, find the enclosing rectangle of the minimum area point set
		Point2f vertices[4];//Define the four vertices of the rectangle
		contourRect.points(vertices);
		for (int j = 0; j < 4; ++j)
		{
			line(mopImg, vertices[j], vertices[(j + 1) % 4], Scalar(255), 2);//Draw a rectangle surrounding the outline
		}
		if (areaDetection(contourRect))
			mapCounter[i] = contourRect;
	}

	//imshow("Rectangle surrounds all contours", mopImg);
	cout << "轮廓总数: " << contours.size() << endl;
	cout << "Number of contours after filtering:" << mapCounter.size() << endl;

	//Get the starting address of the rectangular outline after map storage and filtering for traversal, and continue to filter
	map<int, RotatedRect>::iterator it = mapCounter.begin();
	while (it != mapCounter.end())
	{
		//7. Draw a rectangle filtered by area and aspect ratio
		//RotatedRect contourRect = it->second;//Get the value corresponding to the key
		//Point2f vertices[4];//Define four vertices
		//contourRect.points(vertices);
		//for (int j = 0; j < 4; ++j)
		//{
		//	line(mopImg, vertices[j], vertices[(j + 1) % 4], Scalar(255), 5);
		//}
		//++it;

		//8. Fill the rectangular outline with water
		RotatedRect contourRect = it->second;//Get the value corresponding to the key
		/*void circle(Mat& img, Point center, int radius, const Scalar& color,
		int thickness=1, int lineType=8, int shift=0)*/
		circle(mopImg, contourRect.center, 3, Scalar(255), -1);//Draw the center of the rectangle

		float minSize = (contourRect.size.width < contourRect.size.height) ? contourRect.size.width : contourRect.size.height;
		minSize = minSize*0.5;//Set the range of random seeds

		srand(time(NULL));
		int seedNum = 10;//Set the random seed number for each mask image
		Mat mask;//Draw a new mask image
		mask.create(carImg.rows + 2, carImg.cols + 2, CV_8UC1);
		mask = Scalar::all(0);
		Scalar newVal = Scalar::all(255);//Fill color
		Rect ccomp;//Redraw the minimum rectangular area
		Scalar loDiff(30, 30, 30);//Maximum value of brightness or color negative difference
		Scalar upDiff(30, 30, 30);//Maximum value of brightness or color positive difference
		//Only consider the pixels in the horizontal and vertical directions of the current pixel + the white fill value is 255 (ignore newVal) + consider the difference between the current pixel and the seed pixel + fill the mask image
		int flags = 4 + (255 << 8) + FLOODFILL_FIXED_RANGE + FLOODFILL_MASK_ONLY;
		for (int j = 0; j < seedNum; ++j)
		{
			/*int floodFill(InputOutputArray Img, InputOutputArray mask, Point seedPoint, Scalar newVal,
			Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )*/
			Point point;
			point.x = contourRect.center.x + rand() % (int)minSize - (int)(minSize / 2);//Get the point near the center of the rectangular area as a seed (yellow)
			point.y = contourRect.center.y + rand() % (int)minSize - (int)(minSize / 2);
			circle(mopImg, point, 1, Scalar(255), -1);//Draw the seed
			int area = floodFill(carImg, mask, point, newVal, &ccomp, loDiff, upDiff, flags);
		}
		//imshow("Flood fill image", mopImg);
		//imshow("mask image", mask);

		//9. Get the bounding rectangle with the smallest area by each image mask
		vector<Point> pointInterest;
		Mat_<uchar>::iterator itMask = mask.begin<uchar>();//Mat's lightweight data type Mat_, the type needs to be specified
		for (; itMask != mask.end<uchar>(); ++itMask)
		{
			if (*itMask == 255)
				pointInterest.push_back(itMask.pos());//Save the coordinates of the white point
		}
		RotatedRect minRect = minAreaRect(pointInterest);//Given a 2D point set, find the enclosing rectangle with the smallest area

		if (areaDetection(minRect))
		{
			//10. Draw a rectangle by continuing to filter
			Point2f minRectPoints[4];
			minRect.points(minRectPoints);
			for (int k = 0; k < 4; k++)
				line(mopImg, minRectPoints[k], minRectPoints[(k + 1) % 4], Scalar(255));

			//11. Affine transformation of the original image
			float width_height = (float)minRect.size.width / (float)minRect.size.height;
			float angle = minRect.angle;
			if (width_height < 1)//Process the license plate with a rotation angle greater than 90 degrees in the image
				angle = angle + 90;
			Mat rotMat = getRotationMatrix2D(minRect.center, angle, 1);//Get the rotation matrix of the rectangle
			Mat warpImg;
			/*void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize,
			int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())*/
			warpAffine(carImg, warpImg, rotMat, carImg.size(), INTER_CUBIC);
			//imshow("Original image affine transformation", warpImg);

			//12. Image cutting
			Size minRectSize = minRect.size;
			if (width_height < 1)
				swap(minRectSize.width, minRectSize.height);
			Mat plateImg;
			/*void getRectSubPix(InputArray Img, Size patchSize, Point2f center,
			OutputArray patch, int patchType=-1 )*/
			getRectSubPix(warpImg, minRectSize, minRect.center, plateImg);
			//imshow("Original license plate", plateImg);

			//13. Adjust the license plate image size to standard 33*144
			Mat resizeImg;
			resizeImg.create(33, 144, CV_8UC3);
			/*void resize(InputArray src, OutputArray dst, Size dsize, double fx=0,
			double fy=0, int interpolation=INTER_LINEAR )*/
			resize(plateImg, resizeImg, resizeImg.size(), 0, 0, INTER_CUBIC);
			imshow("33*144 license plate", resizeImg);
			imwrite("plateOri.jpg", resizeImg);

			//14. Histogram equalization
			Mat histImg;
			cvtColor(resizeImg, histImg, COLOR_BGR2GRAY);
			blur(histImg, histImg, Size(3, 3));
			equalizeHist(histImg, histImg);
			imshow("Histogram equalization license plate", histImg);
			imwrite("plate.jpg", histImg);
		}
		++it;
	}
}

2) License plate image classification

a. SVM

/* imageClassification.cpp */
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;
using namespace ml;

void SVMClassifier(Mat srcImg)
{
	Mat plateImg;
	srcImg.convertTo(plateImg, CV_32FC1);
	plateImg = plateImg.reshape(1, 1);//Convert the image into 1 row and m column features, and automatically calculate the number of columns

	FileStorage fs;
	fs.open("SVM.xml", FileStorage::READ);//Open the xml file

	//1. Get training data
	Mat trainMat, classesMat;
	fs["TrainingData"] >> trainMat;//Read the training dataset and class labels
	fs["classes"] >> classesMat;
	Ptr<TrainData> trainData = TrainData::create(trainMat,ROW_SAMPLE,classesMat);

	//2. Create a classifier and set parameters
	SVM::ParamTypes param;//Set SVM parameters
	SVM::KernelTypes kernel = SVM::LINEAR;
	Ptr<SVM> svm = SVM::create();
	svm->setKernel(kernel);

	//3. Train the classifier
	svm-> trainAuto (trainData);

	//4. Prediction
	int result = svm->predict(plateImg);
	cout << "Prediction result: " << result << endl;
}


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325861287&siteId=291194637