OpenCV C + + case practice III "QR code detection"

preface

This article will use OpenCV C + + for QR code detection.

1, QR code detection


Firstly, we must preprocess the image and extract the image contour through gray, filtering, binarization and other operations. Here I also added morphological operations to eliminate noise and effectively connect rectangular areas.

	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);

	Mat blur;
	GaussianBlur(gray, blur, Size(3, 3), 0);

	Mat bin;
	threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

	//Eliminate edge burr through Size (5,1) open operation
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1));
	Mat open;
	morphologyEx(bin, open, MORPH_OPEN, kernel);

	//Through the Size (21,1) closed operation, the rectangular regions can be effectively connected to facilitate the extraction of rectangular regions
	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);


The figure shows the effect obtained after a series of image processing. Then we need to extract the contour of the image to find the rectangular area where the QR code is located.

	//Use RETR_EXTERNAL find outermost contour
	vector<vector<Point>>MaxContours;
	findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	for (int i = 0; i < MaxContours.size(); i++)
	{
		Mat mask = Mat::zeros(src.size(), CV_8UC3);
		mask = Scalar::all(255);

		double area = contourArea(MaxContours[i]);

		//Find the rectangular area where the QR code is located through the area threshold
		if (area > 6000 && area < 100000)
		{
			//Calculate minimum circumscribed rectangle
			RotatedRect MaxRect = minAreaRect(MaxContours[i]);
			//Calculate the minimum external rectangle aspect ratio
			double ratio = MaxRect.size.width / MaxRect.size.height;

			if (ratio > 0.8 && ratio < 1.2)
			{
				Rect MaxBox = MaxRect.boundingRect();

				//rectangle(src, Rect(MaxBox.tl(), MaxBox.br()), Scalar(255, 0, 255), 2);
				//Cut out the rectangular area from the original image
				Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));

				ROI.copyTo(mask(MaxBox));

				ROI_Rect.push_back(mask);

			}

		}

	}

From the following code snippet, we can find out the rectangular area where the QR code is located, and pull out these areas and save them for the following identification work.

//Find the rectangular area where the QR code is located
void Find_QR_Rect(Mat src, vector<Mat>&ROI_Rect)
{
	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);

	Mat blur;
	GaussianBlur(gray, blur, Size(3, 3), 0);

	Mat bin;
	threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

	//Eliminate edge burr through Size (5,1) open operation
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1));
	Mat open;
	morphologyEx(bin, open, MORPH_OPEN, kernel);
	//Through the Size (21,1) closed operation, the rectangular regions can be effectively connected to facilitate the extraction of rectangular regions
	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);


	//Use RETR_EXTERNAL find outermost contour
	vector<vector<Point>>MaxContours;
	findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	for (int i = 0; i < MaxContours.size(); i++)
	{
		Mat mask = Mat::zeros(src.size(), CV_8UC3);
		mask = Scalar::all(255);

		double area = contourArea(MaxContours[i]);

		//Find the rectangular area where the QR code is located through the area threshold
		if (area > 6000 && area < 100000)
		{
			//Calculate minimum circumscribed rectangle
			RotatedRect MaxRect = minAreaRect(MaxContours[i]);
			//Calculate the minimum external rectangle aspect ratio
			double ratio = MaxRect.size.width / MaxRect.size.height;

			if (ratio > 0.8 && ratio < 1.2)
			{
				Rect MaxBox = MaxRect.boundingRect();

				//rectangle(src, Rect(MaxBox.tl(), MaxBox.br()), Scalar(255, 0, 255), 2);
				//Cut out the rectangular area from the original image
				Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));

				ROI.copyTo(mask(MaxBox));

				ROI_Rect.push_back(mask);

			}

		}

	}

}


As shown in the figure, this is the QR code rectangle found. Only one of them is shown here.

2, QR code recognition

1. Find the contour hierarchy through findContours

	//Used to store the detected QR code
	vector<vector<Point>>QR_Rect;
	
	//Traverse all found rectangular areas
	for (int i = 0; i < ROI_Rect.size(); i++)
	{
		Mat gray;
		cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY);

		Mat bin;
		threshold(gray, bin, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);

		//Through hierarchy, RETR_TREE finds the hierarchical relationship between contours
		vector<vector<Point>>contours;
		vector<Vec4i>hierarchy;
		findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);

		//Parent profile index
		int ParentIndex = -1;
		int cn = 0;

		//Three "loops" for storing QR code rectangles
		vector<Point>rect_points;
		for (int i = 0; i < contours.size(); i++)
		{
			//hierarchy[i][2] != -1 indicates that the contour has sub contour cn, which is used to count the contour in "back"
			if (hierarchy[i][2] != -1 && cn == 0)
			{
				ParentIndex = i;
				cn++;
			}
			else if (hierarchy[i][2] != -1 && cn == 1)
			{
				cn++;
			}
			else if (hierarchy[i][2] == -1)
			{
				//initialization
				ParentIndex = -1;
				cn = 0;
			}

			//If the contour has sub contours and there are level 2 sub contours, it is deemed that 'back' is found
			if (hierarchy[i][2] != -1 && cn == 2)
			{
				drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1);

				RotatedRect rect;

				rect = minAreaRect(contours[ParentIndex]);

				rect_points.push_back(rect.center);

			}

		}
	}

The overall idea of the above code segment is: firstly, contour detection is carried out through image preprocessing,
Through hierarchy, RETR_TREE finds the hierarchical relationship between contours. Judge whether the contour has sub contour according to whether hierarchy[i][2] is - 1. If the contour has sub contours, several sub contours are counted. If the contour has sub contours and there are level 2 sub contours, it is considered that 'back' is found. On the hierarchical relationship of the outline, you can find the information on Baidu and understand the principle.

//Identify whether the found rectangular area is a QR code
int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect)
{
	//Used to store the detected QR code
	vector<vector<Point>>QR_Rect;
	
	//Traverse all found rectangular areas
	for (int i = 0; i < ROI_Rect.size(); i++)
	{
		Mat gray;
		cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY);

		Mat bin;
		threshold(gray, bin, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);

		//Through hierarchy, RETR_TREE finds the hierarchical relationship between contours
		vector<vector<Point>>contours;
		vector<Vec4i>hierarchy;
		findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);

		//Parent profile index
		int ParentIndex = -1;
		int cn = 0;

		//Three "loops" for storing QR code rectangles
		vector<Point>rect_points;
		for (int i = 0; i < contours.size(); i++)
		{
			//hierarchy[i][2] != -1 indicates that the contour has sub contour cn, which is used to count the contour in "back"
			if (hierarchy[i][2] != -1 && cn == 0)
			{
				ParentIndex = i;
				cn++;
			}
			else if (hierarchy[i][2] != -1 && cn == 1)
			{
				cn++;
			}
			else if (hierarchy[i][2] == -1)
			{
				//initialization
				ParentIndex = -1;
				cn = 0;
			}

			//If the contour has sub contours and there are level 2 sub contours, it is deemed that 'back' is found
			if (hierarchy[i][2] != -1 && cn == 2)
			{
				drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1);

				RotatedRect rect;

				rect = minAreaRect(contours[ParentIndex]);

				rect_points.push_back(rect.center);

			}

		}

		//Connect the 'return' found
		for (int i = 0; i < rect_points.size(); i++)
		{
			line(canvas, rect_points[i], rect_points[(i + 1) % rect_points.size()], Scalar::all(255), 5);
		}

		QR_Rect.push_back(rect_points);

	}

	
	return QR_Rect.size();

}

From the above code segment, we can identify the QR code. The effect is shown in the figure.

3, 2D code drawing

	//Frame the location of the QR code
	Mat gray;
	cvtColor(canvas, gray, COLOR_BGR2GRAY);

	vector<vector<Point>>contours;
	findContours(gray, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	Point2f points[4];

	for (int i = 0; i < contours.size(); i++)
	{
		RotatedRect rect = minAreaRect(contours[i]);
		
		rect.points(points);

		for (int j = 0; j < 4; j++)
		{
			line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2);
		}

	}


The final effect is shown in the figure.


#include<iostream>
#include<opencv2/core.hpp>
#include<opencv2/imgproc.hpp>
#include<opencv2/highgui.hpp>
using namespace std;
using namespace cv;


//Find the rectangular area where the QR code is located
void Find_QR_Rect(Mat src, vector<Mat>&ROI_Rect)
{
	Mat gray;
	cvtColor(src, gray, COLOR_BGR2GRAY);

	Mat blur;
	GaussianBlur(gray, blur, Size(3, 3), 0);

	Mat bin;
	threshold(blur, bin, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);

	//Eliminate edge burr through Size (5,1) open operation
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1));
	Mat open;
	morphologyEx(bin, open, MORPH_OPEN, kernel);
	//Through the Size (21,1) closed operation, the rectangular regions can be effectively connected to facilitate the extraction of rectangular regions
	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);


	//Use RETR_EXTERNAL find outermost contour
	vector<vector<Point>>MaxContours;
	findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	for (int i = 0; i < MaxContours.size(); i++)
	{
		Mat mask = Mat::zeros(src.size(), CV_8UC3);
		mask = Scalar::all(255);

		double area = contourArea(MaxContours[i]);

		//Find the rectangular area where the QR code is located through the area threshold
		if (area > 6000 && area < 100000)
		{
			//Calculate minimum circumscribed rectangle
			RotatedRect MaxRect = minAreaRect(MaxContours[i]);
			//Calculate the minimum external rectangle aspect ratio
			double ratio = MaxRect.size.width / MaxRect.size.height;

			if (ratio > 0.8 && ratio < 1.2)
			{
				Rect MaxBox = MaxRect.boundingRect();

				//rectangle(src, Rect(MaxBox.tl(), MaxBox.br()), Scalar(255, 0, 255), 2);
				//Cut out the rectangular area from the original image
				Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));

				ROI.copyTo(mask(MaxBox));

				ROI_Rect.push_back(mask);

			}

		}

	}

}


//Identify whether the found rectangular area is a QR code
int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect)
{
	//Used to store the detected QR code
	vector<vector<Point>>QR_Rect;
	
	//Traverse all found rectangular areas
	for (int i = 0; i < ROI_Rect.size(); i++)
	{
		Mat gray;
		cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY);

		Mat bin;
		threshold(gray, bin, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);

		//Through hierarchy, RETR_TREE finds the hierarchical relationship between contours
		vector<vector<Point>>contours;
		vector<Vec4i>hierarchy;
		findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);

		//Parent profile index
		int ParentIndex = -1;
		int cn = 0;

		//Three "loops" for storing QR code rectangles
		vector<Point>rect_points;
		for (int i = 0; i < contours.size(); i++)
		{
			//hierarchy[i][2] != -1 indicates that the contour has sub contour cn, which is used to count the contour in "back"
			if (hierarchy[i][2] != -1 && cn == 0)
			{
				ParentIndex = i;
				cn++;
			}
			else if (hierarchy[i][2] != -1 && cn == 1)
			{
				cn++;
			}
			else if (hierarchy[i][2] == -1)
			{
				//initialization
				ParentIndex = -1;
				cn = 0;
			}

			//If the contour has sub contours and there are level 2 sub contours, it is deemed that 'back' is found
			if (hierarchy[i][2] != -1 && cn == 2)
			{
				drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1);

				RotatedRect rect;

				rect = minAreaRect(contours[ParentIndex]);

				rect_points.push_back(rect.center);

			}

		}

		//Connect the 'return' found
		for (int i = 0; i < rect_points.size(); i++)
		{
			line(canvas, rect_points[i], rect_points[(i + 1) % rect_points.size()], Scalar::all(255), 5);
		}

		QR_Rect.push_back(rect_points);

	}

	
	return QR_Rect.size();

}

int main()
{

	Mat src = imread("6.png");

	if (src.empty())
	{
		cout << "No image data!" << endl;
		system("pause");
		return 0;
	}

	vector<Mat>ROI_Rect;
	Find_QR_Rect(src, ROI_Rect);

	Mat canvas = Mat::zeros(src.size(), src.type());
	int flag = Dectect_QR_Rect(src, canvas, ROI_Rect);
	//imshow("canvas", canvas);

	if (flag <= 0)
	{
		cout << "Can not detect QR code!" << endl;	
		system("pause");
		return 0;
	}

	cout << "Detected" << flag << "A QR code." << endl;


	//Frame the location of the QR code
	Mat gray;
	cvtColor(canvas, gray, COLOR_BGR2GRAY);

	vector<vector<Point>>contours;
	findContours(gray, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	Point2f points[4];

	for (int i = 0; i < contours.size(); i++)
	{
		RotatedRect rect = minAreaRect(contours[i]);
		
		rect.points(points);

		for (int j = 0; j < 4; j++)
		{
			line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2);
		}

	}


	imshow("source", src);
	waitKey(0);
	destroyAllWindows();

	system("pause");
	return 0;
}

summary

In this paper, OpenCV C + + is used for QR code detection. The key steps are as follows.
1. Image preprocessing, filter out the rectangular area where the QR code is located, and pick out the area for subsequent recognition.
2. The contour of the screened rectangular area is detected to judge their previous hierarchical relationship, so as to identify the QR code.
3. Finally, according to the detected QR code "back" word, it can be drawn.

The above is my overall idea of QR code detection and recognition. Welcome to exchange and learn together. If there are deficiencies, you are welcome to correct.

Keywords: C++ OpenCV Computer Vision

Added by kylevisionace02 on Thu, 04 Nov 2021 12:18:18 +0200