c++ 基于opencv 识别、定位二维码

前言

因工作需要,需要定位图片中的二维码;我遂查阅了相关资料,也学习了opencv开源库。通过一番努力,终于很好的实现了二维码定位。本文将讲解如何使用opencv定位二维码。

定位二维码不仅仅是为了识别二维码;还可以通过二维码对图像进行水平纠正以及相邻区域定位。定位二维码,不仅需要图像处理相关知识,还需要分析二维码的特性,本文先从二维码的特性讲起。

1 二维码特性

二维码在设计之初就考虑到了识别问题,所以二维码有一些特征是非常明显的。

二维码有三个“回“”字形图案,这一点非常明显。中间的一个点位于图案的左上角,如果图像偏转,也可以根据二维码来纠正。

思考题:

为什么是三个点,而不是一个、两个或四个点。

一个点:特征不明显,不易定位。不易定位二维码倾斜角度。

两个点:两个点的次序无法确认,很难确定二维码是否放正了。

四个点:无法确定4个点的次序,从而无法确定二维码是否放正了。

识别二维码,就是识别二维码的三个点,逐步分析一下这三个点的特性

1 每个点有两个轮廓。就是两个口,大“口”内部有一个小“口”,所以是两个轮廓。

2 如果把这个“回”放到一个白色的背景下,从左到右,或从上到下画一条线。这条线经过的图案黑白比例大约为:黑白比例为1:1:3:1:1。

3 如何找到左上角的顶点?这个顶点与其他两个顶点的夹角为90度。

通过上面几个步骤,就能识别出二维码的三个顶点,并且识别出左上角的顶点。

2 使用opencv识别二维码

1)查找轮廓,筛选出三个二维码顶点

opencv一个非常重要的函数就是查找轮廓,就是可以找到一个图中的缩所有的轮廓,“回”字形图案是一个非常的明显的轮廓,很容易找到。

int QrParse::FindQrPoint(Mat& srcImg, vector<vector<Point>>& qrPoint) {
	//彩色图转灰度图
	Mat src_gray;
	cvtColor(srcImg, src_gray, CV_BGR2GRAY);
	namedWindow("src_gray");
	imshow("src_gray", src_gray);
	//二值化
	Mat threshold_output;
	threshold(src_gray, threshold_output, 0, 255, THRESH_BINARY | THRESH_OTSU);
	Mat threshold_output_copy = threshold_output.clone();
	namedWindow("Threshold_output");
	imshow("Threshold_output", threshold_output);
	//调用查找轮廓函数
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));
	//通过黑色定位角作为父轮廓,有两个子轮廓的特点,筛选出三个定位角
	int parentIdx = -1;
	int ic = 0;
	for (int i = 0; i < contours.size(); i++) {
		if (hierarchy[i][2] != -1 && ic == 0) {
			parentIdx = i;
			ic++;
		} else if (hierarchy[i][2] != -1) {
			ic++;
		} else if (hierarchy[i][2] == -1) {
			ic = 0;
			parentIdx = -1;
		} {
			bool isQr = QrParse::IsQrPoint(contours[parentIdx], threshold_output_copy);
			//保存找到的三个黑色定位角
			if (isQr)
			qrPoint.push_back(contours[parentIdx]);
			ic = 0;
			parentIdx = -1;
		}
	}
	return 0;
}

找到了两个轮廓的图元,需要进一步分析是不是二维码顶点,用到如下函数:

bool QrParse::IsQrPoint(vector<Point>& contour, Mat& img)
{
 //最小大小限定
 RotatedRect rotatedRect = minAreaRect(contour);
 if (rotatedRect.size.height < 10 || rotatedRect.size.width < 10)
 return false;

 //将二维码从整个图上抠出来
 cv::Mat cropImg = CropImage(img, rotatedRect);
 int flag = i++;

 //横向黑白比例1:1:3:1:1
 bool result = IsQrColorRate(cropImg, flag);
 return result;
}

黑白比例判断函数:

//横向和纵向黑白比例判断
bool QrParse::IsQrColorRate(cv::Mat& image, int flag)
{
 bool x = IsQrColorRateX(image, flag);
 if (!x)
 return false;
 bool y = IsQrColorRateY(image, flag);
 return y;
}
//横向黑白比例判断
bool QrParse::IsQrColorRateX(cv::Mat& image, int flag)
{
 int nr = image.rows / 2;
 int nc = image.cols * image.channels();

 vector<int> vValueCount;
 vector<uchar> vColor;
 int count = 0;
 uchar lastColor = 0;

 uchar* data = image.ptr<uchar>(nr);
 for (int i = 0; i < nc; i++)
 {
 vColor.push_back(data[i]);
 uchar color = data[i];
 if (color > 0)
 color = 255;

 if (i == 0)
 {
 lastColor = color;
 count++;
 }
 else
 {
 if (lastColor != color)
 {
 vValueCount.push_back(count);
 count = 0;
 }
 count++;
 lastColor = color;
 }
 }

 if (count != 0)
 vValueCount.push_back(count);

 if (vValueCount.size() < 5)
 return false;

 //横向黑白比例1:1:3:1:1
 int index = -1;
 int maxCount = -1;
 for (int i = 0; i < vValueCount.size(); i++)
 {
 if (i == 0)
 {
 index = i;
 maxCount = vValueCount[i];
 }
 else
 {
 if (vValueCount[i] > maxCount)
 {
 index = i;
 maxCount = vValueCount[i];
 }
 }
 }

 //左边 右边 都有两个值,才行
 if (index < 2)
 return false;
 if ((vValueCount.size() - index) < 3)
 return false;

 //黑白比例1:1:3:1:1
 float rate = ((float)maxCount) / 3.00;

 cout << "flag:" << flag << " ";

 float rate2 = vValueCount[index - 2] / rate;
 cout << rate2 << " ";
 if (!IsQrRate(rate2))
 return false;

 rate2 = vValueCount[index - 1] / rate;
 cout << rate2 << " ";
 if (!IsQrRate(rate2))
 return false;

 rate2 = vValueCount[index + 1] / rate;
 cout << rate2 << " ";
 if (!IsQrRate(rate2))
 return false;

 rate2 = vValueCount[index + 2] / rate;
 cout << rate2 << " ";
 if (!IsQrRate(rate2))
 return false;

 return true;
}
//纵向黑白比例判断 省略
bool QrParse::IsQrColorRateY(cv::Mat& image, int flag)
bool QrParse::IsQrRate(float rate)
{
 //大概比例 不能太严格
 return rate > 0.6 && rate < 1.9;
}

2)确定三个二维码顶点的次序

通过如下原则确定左上角顶点:二维码左上角的顶点与其他两个顶点的夹角为90度。

// pointDest存放调整后的三个点,三个点的顺序如下
// pt0----pt1
//
// pt2
bool QrParse::AdjustQrPoint(Point* pointSrc, Point* pointDest) {
	bool clockwise;
	int index1[3] = {
		2,1,0
	}
	;
	int index2[3] = {
		0,2,1
	}
	;
	int index3[3] = {
		0,1,2
	}
	;
	for (int i = 0; i < 3; i++) {
		int *n = index1;
		if(i==0)
		n = index1; else if (i == 1)
		n = index2; else
		n = index3;
		if (angle > 80 && angle < 99) {
			pointDest[0] = pointSrc[n[2]];
			if (clockwise) {
				pointDest[1] = pointSrc[n[0]];
				pointDest[2] = pointSrc[n[1]];
			} else {
				pointDest[1] = pointSrc[n[1]];
				pointDest[2] = pointSrc[n[0]];
			}
			return true;
		}
	}
	return true;
}

3)通过二维码对图片矫正。

图片有可能是倾斜的,倾斜夹角可以通过pt0与pt1连线与水平线之间的夹角确定。二维码的倾斜角度就是整个图片的倾斜角度,从而可以对整个图片进行水平矫正。

//二维码倾斜角度
Point hor(pointAdjust[0].x+300,pointAdjust[0].y); //水平线
double qrAngle = QrParse::Angle(pointAdjust[1], hor, pointAdjust[0], clockwise);

//以二维码左上角点为中心 旋转
 Mat drawingRotation = Mat::zeros(Size(src.cols,src.rows), CV_8UC3);
 double rotationAngle = clockwise? -qrAngle:qrAngle;
 Mat affine_matrix = getRotationMatrix2D(pointAdjust[0], rotationAngle, 1.0);//求得旋转矩阵
 warpAffine(src, drawingRotation, affine_matrix, drawingRotation.size());

4)二维码相邻区域定位

一般情况下,二维码在整个图中的位置是确定的。识别出二维码后,根据二维码与其他图的位置关系,可以很容易的定位别的图元。

后记

作者通过查找大量资料,仔细研究了二维码的特征,从而找到了识别二维码的方法。网上也有许多识别二维码的方法,但是不够严谨。本文是将二维码的多个特征相结合来识别,这样更准确。这种识别方法已应用在公司的产品中,识别效果还是非常好的。

以上就是c++ 基于opencv 识别、定位二维码的详细内容,更多关于c++ opencv 识别、定位二维码的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++&&Opencv实现控制台字符动画的方法

    原理概述 首先利用opencv获取到图片中特定像素点的颜色 根据颜色所处的范围选择不同的字符 再在控制台的特定位置打印即可 重点就是获取像素点的颜色 获取图像中某像素点的颜色图片载入变量 opencv中可使用Mat类型来储存图片 Mat img; img = imread("图片路径"); 将图片转化为灰度图 为什么要把图片转为灰度图呢? 主要是为了使图片颜色单一 减少后面条件判断的工作量 不过也可以不去做这一步的处理 Mat gimg; //img转化为灰度图后输出到gimg中 cv

  • 基于OpenCV和C++ 实现图片旋转

    图片旋转,本质上是对旋转后的图片中每个像素点计算在原图的位置.然后照搬过来就好. (多说一句,如果计算出来在原图中的位置不是整数而是小数,因为像素点个数都是整数,就需要小数到整数的转换.这个转换过程是有讲究的,需要用到插值:最近邻插值.双线性插值等等.这里我使用的是最简单的最近邻插值,即对小数四舍五入成整数,C/C++ 实现四舍五入见 这里 ) 图形图像课上一般会介绍旋转变换矩阵,其中 t 为需要旋转的角度,[x'; y']是变换后坐标(其中分号表示上下关系): 即表示为:[x'; y'] =

  • C++使用OpenCV实现证件照蓝底换成白底功能(或其他颜色如红色)详解

    本文实例讲述了C++使用OpenCV实现证件照蓝底换成白底功能(或其他颜色如红色).分享给大家供大家参考,具体如下: 今天刚好老师要办点事情,老师唯一的一张证件照是蓝色的,但是需要的底色是白色的,于是乎,好久不折腾的PS也忘记了,还好旁边的刚来的小学弟懂一点, 在那里慢慢的帮老师一点点的处理,PS在边缘的地方效果还真不咋地,确实是一门技术活. 于是我就想OpenCV能不能实现呢?一搜百度第一篇就是,但是人家转成红色,然后我又对HSV颜色空间不是很懂,最后在一个学习群里 但是文中未对HSV那一块做

  • C++ opencv ffmpeg图片序列化实现代码解析

    0.如果路径中存在空格,用""把路径包括起来 1.使用ffmpeg命令 ffmpeg -y -framerate 10 -start_number 1 -i E:\Image\Image_%d.bmp E:\test.mp4 -y 表示输出时覆盖输出目录已存在的同名文件 -framerate 10 表示视频帧率 -start_number 1 表示图片序号从1开始 -i E:\Image\Image_%d.bmp 表示图片输入流格式 2.c++ 实现 ffmpeg命令 2.1.syst

  • win10环境下C++ vs2015编译opencv249的教程

    打开CMake,设置源文件路径,和生成路径,第一步我新建的build和buildwin32 我之前用过了,所以这里我另外建了一个文件夹 test来演示. 然后点击Configure,会出现编译器的选项,Visual studio 14 2015生成win32的解决方案,Visual studio 14 2015 win64生成x64的解决方案.这里只演示前一种. 提示 Configuring done,红色的可选项我没有修改过: 3. 接下里点击Generate,提示Generate done.

  • OpenCV实现车牌字符分割(C++)

    之前的车牌定位中已经获取到了车牌的位置,并且对车牌进行了提取.我们最终的目的是进行车牌识别,在这之前需要将字符进行分割,方便对每一个字符进行识别,最后将其拼接后便是完整的车牌号码.关于车牌定位可以看这篇文章: OpenCV车牌定位(C++),本文使用的图片也是来自这里. 先来看一看原图: 最左边的汉字本来是 沪,截取时只获得了右边一点点的部分,这与原图和获取方法都有关,对于 川.沪- 这一类左右分开的字会经常发生这类问题,对方法进行优化后可以解决,这里暂时不进行讨论. 后面的字都是完整的,字符分

  • OpenCV实现车牌定位(C++)

    最近开始接触 C++ 了,就拿一个 OpenCV 小项目来练练手.在车牌自动识别系统中,从汽车图像的获取到车牌字符处理是一个复杂的过程,本文就以一个简单的方法来处理车牌定位. 我国的汽车牌照一般由七个字符和一个点组成,车牌字符的高度和宽度是固定的,分别为90mm和45mm,七个字符之间的距离也是固定的12mm,点分割符的直径是10mm. 使用的图片是从百度上随便找的(侵删),展示一下原图和灰度图: #include <iostream> #include <opencv2/highgui

  • C++使用opencv处理两张图片的帧差

    本文为大家分享了使用opencv处理两张图片帧差的具体代码,供大家参考,具体内容如下 这个程序是两张图片做帧差,用C++实现的,把不同的地方用框框起来,仔细读一下程序,应该还是蛮简单的哈哈,opencv处理图片的基础. opencv配置不用我说了吧,源码cmake编译,然后导入vs即可. #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int mai

  • opencv3/C++关于移动对象的轮廓的跟踪详解

    使用opencv提供的背景去除算法(KNN或高斯混合模型GMM)去除背景,然后将获取的目标二值化后通过筛选目标轮廓获得目标位置. #include<opencv2/opencv.hpp> using namespace cv; //基于移动对象的轮廓的跟踪 int main() { Mat frame; bool flag = true; VideoCapture capture; capture.open(0); if (!capture.isOpened()) { printf("

  • C++ opencv实现车道线识别

    本文实例为大家分享了C++ opencv实现车道线识别的具体代码,供大家参考,具体内容如下 先上图 1. 2. (一)目前国内外广泛使用的车道线检测方法主要分为两大类: (1) 基于道路特征的车道线检测: (2) 基于道路模型的车道线检测. 基于道路特征的车道线检测作为主流检测方法之一,主要是利用车道线与道路环境的物理特征差异进行后续图像的分割与处理,从而突出车道线特征,以实现车道线的检测.该方法复杂度较低,实时性较高,但容易受到道路环境干扰. 基于道路模型的车道线检测主要是基于不同的二维或三维

  • opencv3/C++ 实现SURF特征检测

    SURF即Speeded Up Robust Features加速鲁棒特征: SURF可以用于对象定位和识别.人脸识别.3D重建.对象跟踪和提取兴趣点等. 工作原理: 1.选择图像中POI(Points of Interest) Hessian Matrix; 2.在不同的尺度空间发现关键点,非最大信号压制; 3.发现特征点方法.旋转不变性要求; 4.生成特征向量; 类SURF中成员函数create()参数说明: static Ptr<SURF> create( double hessianT

随机推荐