C++ OpenCV实现二维码检测功能

 更新时间:2022年01月12日 10:56:40   作者:Zero___Chen  
这篇文章主要介绍了如何利用C++ OpenCV实现二维码检测功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

前言

本文将使用OpenCV C++ 进行二维码检测。

一、二维码检测

首先我们要先将图像进行预处理,通过灰度、滤波、二值化等操作提取出图像轮廓。在这里我还添加了形态学操作,消除噪点,有效将矩形区域连接起来。

	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);

	//通过Size(5,1)开运算消除边缘毛刺
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1));
	Mat open;
	morphologyEx(bin, open, MORPH_OPEN, kernel);

	//通过Size(21,1)闭运算能够有效地将矩形区域连接 便于提取矩形区域
	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);

 

如图为经过一系列图像处理之后得到的效果。之后我们需要对该图进行轮廓提取,找到二维码所在的矩形区域。

	//使用RETR_EXTERNAL找到最外轮廓
	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]);

		//通过面积阈值找到二维码所在矩形区域
		if (area > 6000 && area < 100000)
		{
			//计算最小外接矩形
			RotatedRect MaxRect = minAreaRect(MaxContours[i]);
			//计算最小外接矩形宽高比
			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);
				//将矩形区域从原图抠出来
				Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));

				ROI.copyTo(mask(MaxBox));

				ROI_Rect.push_back(mask);

			}

		}

	}

由以下代码段我们就可以很好的找出二维码所在的矩形区域,并将这些区域抠出来保存以便进行下面的识别工作。

//找到二维码所在的矩形区域
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);

	//通过Size(5,1)开运算消除边缘毛刺
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1));
	Mat open;
	morphologyEx(bin, open, MORPH_OPEN, kernel);
	//通过Size(21,1)闭运算能够有效地将矩形区域连接 便于提取矩形区域
	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);


	//使用RETR_EXTERNAL找到最外轮廓
	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]);

		//通过面积阈值找到二维码所在矩形区域
		if (area > 6000 && area < 100000)
		{
			//计算最小外接矩形
			RotatedRect MaxRect = minAreaRect(MaxContours[i]);
			//计算最小外接矩形宽高比
			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);
				//将矩形区域从原图抠出来
				Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));

				ROI.copyTo(mask(MaxBox));

				ROI_Rect.push_back(mask);

			}

		}

	}

}

如图所示,这是找到的二维码矩形。这里只展示其中之一。

二、二维码识别

通过findContours找到轮廓层级关系

	//用于存储检测到的二维码
	vector<vector<Point>>QR_Rect;
	
	//遍历所有找到的矩形区域
	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);

		//通过hierarchy、RETR_TREE找到轮廓之间的层级关系
		vector<vector<Point>>contours;
		vector<Vec4i>hierarchy;
		findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);

		//父轮廓索引
		int ParentIndex = -1;
		int cn = 0;

		//用于存储二维码矩形的三个“回”
		vector<Point>rect_points;
		for (int i = 0; i < contours.size(); i++)
		{
			//hierarchy[i][2] != -1 表示该轮廓有子轮廓  cn用于计数“回”中第几个轮廓
			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)
			{
				//初始化
				ParentIndex = -1;
				cn = 0;
			}

			//如果该轮廓存在子轮廓,且有2级子轮廓则认定找到‘回'
			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);

			}

		}
	}

以上代码段的整体思路为:首先经过图像预处理进行轮廓检测,

通过hierarchy、RETR_TREE找到轮廓之间的层级关系。根据hierarchy[i][2]是否为-1判断该轮廓是否有子轮廓。若该轮廓存在子轮廓,则统计有几个子轮廓。如果该轮廓存在子轮廓,且有2级子轮廓则认定找到‘回’ 。关于轮廓的层级关系,大家可以自行百度查找资料,理解一下其中原理。

//对找到的矩形区域进行识别是否为二维码
int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect)
{
	//用于存储检测到的二维码
	vector<vector<Point>>QR_Rect;
	
	//遍历所有找到的矩形区域
	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);

		//通过hierarchy、RETR_TREE找到轮廓之间的层级关系
		vector<vector<Point>>contours;
		vector<Vec4i>hierarchy;
		findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);

		//父轮廓索引
		int ParentIndex = -1;
		int cn = 0;

		//用于存储二维码矩形的三个“回”
		vector<Point>rect_points;
		for (int i = 0; i < contours.size(); i++)
		{
			//hierarchy[i][2] != -1 表示该轮廓有子轮廓  cn用于计数“回”中第几个轮廓
			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)
			{
				//初始化
				ParentIndex = -1;
				cn = 0;
			}

			//如果该轮廓存在子轮廓,且有2级子轮廓则认定找到‘回'
			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);

			}

		}

		//将找到地‘回'连接起来
		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();

}

由以上代码段,我们就可以识别出二维码。效果如图所示。

三、二维码绘制

	//框出二维码所在位置
	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);
		}

	}

最终效果如图所示。

四、源码

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


//找到二维码所在的矩形区域
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);

	//通过Size(5,1)开运算消除边缘毛刺
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 1));
	Mat open;
	morphologyEx(bin, open, MORPH_OPEN, kernel);
	//通过Size(21,1)闭运算能够有效地将矩形区域连接 便于提取矩形区域
	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(21, 1));
	Mat close;
	morphologyEx(open, close, MORPH_CLOSE, kernel1);


	//使用RETR_EXTERNAL找到最外轮廓
	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]);

		//通过面积阈值找到二维码所在矩形区域
		if (area > 6000 && area < 100000)
		{
			//计算最小外接矩形
			RotatedRect MaxRect = minAreaRect(MaxContours[i]);
			//计算最小外接矩形宽高比
			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);
				//将矩形区域从原图抠出来
				Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));

				ROI.copyTo(mask(MaxBox));

				ROI_Rect.push_back(mask);

			}

		}

	}

}


//对找到的矩形区域进行识别是否为二维码
int Dectect_QR_Rect(Mat src,Mat &canvas,vector<Mat>&ROI_Rect)
{
	//用于存储检测到的二维码
	vector<vector<Point>>QR_Rect;
	
	//遍历所有找到的矩形区域
	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);

		//通过hierarchy、RETR_TREE找到轮廓之间的层级关系
		vector<vector<Point>>contours;
		vector<Vec4i>hierarchy;
		findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);

		//父轮廓索引
		int ParentIndex = -1;
		int cn = 0;

		//用于存储二维码矩形的三个“回”
		vector<Point>rect_points;
		for (int i = 0; i < contours.size(); i++)
		{
			//hierarchy[i][2] != -1 表示该轮廓有子轮廓  cn用于计数“回”中第几个轮廓
			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)
			{
				//初始化
				ParentIndex = -1;
				cn = 0;
			}

			//如果该轮廓存在子轮廓,且有2级子轮廓则认定找到‘回'
			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);

			}

		}

		//将找到地‘回'连接起来
		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 << "检测到" << flag << "个二维码。" << endl;


	//框出二维码所在位置
	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;
}

总结

本文使用OpenCV C++进行二维码检测,关键步骤有以下几点。

1、图像预处理,筛选出二维码所在的矩形区域,并将该区域抠出来进行后续的识别工作。

2、对筛选出的矩形区域进行轮廓检测,判断它们之前的层级关系,以此来识别二维码。

3、最后根据检测到的二维码“回”字,将其绘制出来就可以了。

以上就是C++ OpenCV实现二维码检测功能的详细内容,更多关于C++ OpenCV二维码检测的资料请关注脚本之家其它相关文章!

相关文章

  • 浅谈使用Rapidxml 库遇到的问题和分析过程(分享)

    浅谈使用Rapidxml 库遇到的问题和分析过程(分享)

    下面小编就为大家带来一篇浅谈使用Rapidxml 库遇到的问题和分析过程(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • C语言小程序 杨辉三角示例代码

    C语言小程序 杨辉三角示例代码

    输入要显示的杨辉三角的行数,会打印出金字塔型的杨辉三角,不过行数太多的话,效果不太好,可以再调整一下格式控制
    2013-07-07
  • C++基于回溯法解决八皇后问题示例

    C++基于回溯法解决八皇后问题示例

    这篇文章主要介绍了C++基于回溯法解决八皇后问题,简单描述了八皇后问题,以及回溯法的原理与解决八皇后问题的相关操作技巧,需要的朋友可以参考下
    2017-11-11
  • c/c++内存分配大小实例讲解

    c/c++内存分配大小实例讲解

    在本篇文章里小编给大家整理了一篇关于c/c++内存分配大小实例讲解内容,有需要的朋友们可以跟着学习参考下。
    2021-11-11
  • C语言数据结构中树与森林专项详解

    C语言数据结构中树与森林专项详解

    这篇文章主要介绍了C语言数据结构中树与森林,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-11-11
  • C/C++ 实现简易HTTP服务器的示例

    C/C++ 实现简易HTTP服务器的示例

    这篇文章主要介绍了C/C++ 实现简易HTTP服务器的示例,帮助大家更好的理解和学习C/C++编程,感兴趣的朋友可以了解下
    2020-10-10
  • C/C++函数的调用约定的使用

    C/C++函数的调用约定的使用

    本文主要介绍了C/C++函数的调用约定的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • C++中函数重载实例详解

    C++中函数重载实例详解

    这篇文章主要介绍了C++中函数重载实例详解的相关资料,需要的朋友可以参考下
    2017-03-03
  • C语言详解冒泡排序实现

    C语言详解冒泡排序实现

    冒泡排序是一种简单的排序算法,它也是一种稳定排序算法。其实现原理是重复扫描待排序序列,并比较每一对相邻的元素,当该对元素顺序不正确时进行交换。一直重复这个过程,直到没有任何两个相邻元素可以交换,就表明完成了排序
    2022-04-04
  • vs code 配置python虚拟环境的方法

    vs code 配置python虚拟环境的方法

    这篇文章主要介绍了vs code 配置python虚拟环境的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03

最新评论