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

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

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

1 二维码特性

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

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

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

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

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

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

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

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

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

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

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

2 使用opencv识别二维码

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

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

 1 int QrParse::FindQrPoint(Mat& srcImg, vector<vector<Point>>& qrPoint)
 2 {
 3     //彩色图转灰度图
 4     Mat src_gray;
 5     cvtColor(srcImg, src_gray, CV_BGR2GRAY);
 6     namedWindow("src_gray");
 7     imshow("src_gray", src_gray);
 8
 9     //二值化
10     Mat threshold_output;
11     threshold(src_gray, threshold_output, 0, 255, THRESH_BINARY | THRESH_OTSU);
12     Mat threshold_output_copy = threshold_output.clone();
13     namedWindow("Threshold_output");
14     imshow("Threshold_output", threshold_output);
15
16     //调用查找轮廓函数
17     vector<vector<Point> > contours;
18     vector<Vec4i> hierarchy;
19     findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0));
20
21     //通过黑色定位角作为父轮廓,有两个子轮廓的特点,筛选出三个定位角
22     int parentIdx = -1;
23     int ic = 0;
24
25     for (int i = 0; i < contours.size(); i++)
26     {
27         if (hierarchy[i][2] != -1 && ic == 0)
28         {
29             parentIdx = i;
30             ic++;
31         }
32         else if (hierarchy[i][2] != -1)
33         {
34             ic++;
35         }
36         else if (hierarchy[i][2] == -1)
37         {
38             ic = 0;
39             parentIdx = -1;
40         }
41
43         //有两个子轮廓才是二维码的顶点
44         if (ic >= 2)
45         {
46             bool isQr = QrParse::IsQrPoint(contours[parentIdx], threshold_output_copy);
47
48             //保存找到的三个黑色定位角
49             if (isQr)
50                 qrPoint.push_back(contours[parentIdx]);
51
52             ic = 0;
53             parentIdx = -1;
54         }
55     }
56
57     return 0;
58 }

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

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

黑白比例判断函数:

  1 //横向和纵向黑白比例判断
  2 bool QrParse::IsQrColorRate(cv::Mat& image, int flag)
  3 {
  4     bool x = IsQrColorRateX(image, flag);
  5     if (!x)
  6         return false;
  7     bool y = IsQrColorRateY(image, flag);
  8     return y;
  9 }
 10 //横向黑白比例判断
 11 bool QrParse::IsQrColorRateX(cv::Mat& image, int flag)
 12 {
 13     int nr = image.rows / 2;
 14     int nc = image.cols * image.channels();
 15
 16     vector<int> vValueCount;
 17     vector<uchar> vColor;
 18     int count = 0;
 19     uchar lastColor = 0;
 20
 21     uchar* data = image.ptr<uchar>(nr);
 22     for (int i = 0; i < nc; i++)
 23     {
 24         vColor.push_back(data[i]);
 25         uchar color = data[i];
 26         if (color > 0)
 27             color = 255;
 28
 29         if (i == 0)
 30         {
 31             lastColor = color;
 32             count++;
 33         }
 34         else
 35         {
 36             if (lastColor != color)
 37             {
 38                 vValueCount.push_back(count);
 39                 count = 0;
 40             }
 41             count++;
 42             lastColor = color;
 43         }
 44     }
 45
 46     if (count != 0)
 47         vValueCount.push_back(count);
 48
 49     if (vValueCount.size() < 5)
 50         return false;
 51
 52     //横向黑白比例1:1:3:1:1
 53     int index = -1;
 54     int maxCount = -1;
 55     for (int i = 0; i < vValueCount.size(); i++)
 56     {
 57         if (i == 0)
 58         {
 59             index = i;
 60             maxCount = vValueCount[i];
 61         }
 62         else
 63         {
 64             if (vValueCount[i] > maxCount)
 65             {
 66                 index = i;
 67                 maxCount = vValueCount[i];
 68             }
 69         }
 70     }
 71
 72     //左边 右边 都有两个值,才行
 73     if (index < 2)
 74         return false;
 75     if ((vValueCount.size() - index) < 3)
 76         return false;
 77
 78     //黑白比例1:1:3:1:1
 79     float rate = ((float)maxCount) / 3.00;
 80
 81     cout << "flag:" << flag << " ";
 82
 83     float rate2 = vValueCount[index - 2] / rate;
 84     cout << rate2 << " ";
 85     if (!IsQrRate(rate2))
 86         return false;
 87
 88     rate2 = vValueCount[index - 1] / rate;
 89     cout << rate2 << " ";
 90     if (!IsQrRate(rate2))
 91         return false;
 92
 93     rate2 = vValueCount[index + 1] / rate;
 94     cout << rate2 << " ";
 95     if (!IsQrRate(rate2))
 96         return false;
 97
 98     rate2 = vValueCount[index + 2] / rate;
 99     cout << rate2 << " ";
100     if (!IsQrRate(rate2))
101         return false;
102
103     return true;
104 }
105 //纵向黑白比例判断 省略
106 bool QrParse::IsQrColorRateY(cv::Mat& image, int flag)
bool QrParse::IsQrRate(float rate)
{
     //大概比例 不能太严格
    return rate > 0.6 && rate < 1.9;
}

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

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

 1 // pointDest存放调整后的三个点,三个点的顺序如下
 2 // pt0----pt1
 3 //
 4 // pt2
 5 bool QrParse::AdjustQrPoint(Point* pointSrc, Point* pointDest)
 6 {
 7     bool clockwise;
 8     int index1[3] = { 2,1,0 };
 9     int index2[3] = { 0,2,1 };
10     int index3[3] = { 0,1,2 };
11
12     for (int i = 0; i < 3; i++)
13     {
14         int *n = index1;
15         if(i==0)
16             n = index1;
17         else if (i == 1)
18             n = index2;
19         else
20             n = index3;
21
22         double angle = QrParse::Angle(pointSrc[n[0]], pointSrc[n[1]], pointSrc[n[2]], clockwise);
23         if (angle > 80 && angle < 99)
24         {
25             pointDest[0] = pointSrc[n[2]];
26             if (clockwise)
27             {
28                 pointDest[1] = pointSrc[n[0]];
29                 pointDest[2] = pointSrc[n[1]];
30             }
31             else
32             {
33                 pointDest[1] = pointSrc[n[1]];
34                 pointDest[2] = pointSrc[n[0]];
35             }
36             return true;
37         }
38     }
39     return true;
40 }

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

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

1 //二维码倾斜角度
2 Point hor(pointAdjust[0].x+300,pointAdjust[0].y); //水平线
3 double qrAngle = QrParse::Angle(pointAdjust[1], hor, pointAdjust[0], clockwise);
4
5 //以二维码左上角点为中心 旋转
6     Mat drawingRotation = Mat::zeros(Size(src.cols,src.rows), CV_8UC3);
7     double rotationAngle = clockwise? -qrAngle:qrAngle;
8     Mat affine_matrix = getRotationMatrix2D(pointAdjust[0], rotationAngle, 1.0);//求得旋转矩阵
9     warpAffine(src, drawingRotation, affine_matrix, drawingRotation.size());

4)二维码相邻区域定位

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

后记

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

原文地址:https://www.cnblogs.com/yuanchenhui/p/opencv_qr.html

时间: 2024-08-29 08:44:19

基于opencv 识别、定位二维码 (c++版)的相关文章

【转】Delphi+Halcon实战一:两行代码识别QR二维码

Delphi+Halcon实战一:两行代码识别QR二维码 感谢网友:绝代双椒( QQ:51536348)的支持 本文是绝代双椒的作品,因为最近在忙zw量化培训,和ziwang.com网站的升级,halcon没时间操作. 不过,随着国内产业升级,机器人行业的发展,Delphi+Halcon的未来,是无可限量的. 其他网友,有halcon这方面作品的,有需要,也可以交给zw转发. 另外,zw正在争取培训机构合作,开办Delphi+Halcon方面的培训的项目,有兴趣的机构可以联系QQ:3578117

在树莓派(Debian系统)上通过usb摄像头扫描识别QR二维码

树莓派(Debian系统)自带Python开发环境IDLE(Python 2.7.3),接上摄像头,就能通过Python实行对QR code的创建和识别: 首先,需要在树莓派上安装如下工具: sudo apt-get install python-imaging sudo apt-get install zbar-tools sudo apt-get install qrencode sudo apt-get install python-pygame 然后创建qrcode.py文件: #!/u

Android 基于google Zxing实现二维码的生成,识别和长按识别的效果

最近项目用到了二维码的生成与识别,之前没有接触这块,然后就上网搜了搜,发现有好多这方面的资源,特别是google Zxing对二维码的封装,实现的已经不错了,可以直接拿过来引用,下载了他们的源码后,只做了少少的改动,就是在Demo中增加了长按识别的功能,网上虽然也有长按识别的Demo,但好多下载下来却无法运行,然后总结了一下,加在了下面的Demo中. 如图所示,引用时直接把用红色圈起来的包放在你项目所对应的文件夹下,当然一些资源文件,比如string.xml里项目的引用你自己添加上就是 当然别忘

Android基于Google Zxing实现二维码/条形码扫描、生成二维码/条形码

 二维码/条形码生成器 二维码/条形码扫描器 一.二维码与条形码工作原理 目前的很多应用上都有扫码功能,当时微信推出二维码扫码功能时,觉得imagine,通过一张简单的图片就能扫描添加还有,还有分享名片功能(也是一张二维码图片,识别扫描). 下面小编将通过文章主要介绍QRCode方面技术. QRCode是被广泛应用的一种二维码,解码速度快.二维码相对于条形码来说,二维码的存储数据量更大,空间利用率高,有一定的容错性. 二维码原理介绍: 二维码是用某种特定的几何图形按一定的规律在平面上分布的黑

【转】Android 基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果--不错

原文网址:http://blog.csdn.net/xiaanming/article/details/10163203 转载请注明出处:http://blog.csdn.net/xiaanming/article/details/10163203 了解二维码这个东西还是从微信中,当时微信推出二维码扫描功能,自己感觉挺新颖的,从一张图片中扫一下竟然能直接加好友,不可思议啊,那时候还不了解二维码,呵呵,然后做项目的时候,老板说要加上二维码扫描功能,然后自己的屁颠屁颠的去百度,google啥的,发现

Android 基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果

了解二维码这个东西还是从微信中,当时微信推出二维码扫描功能.自己感觉挺新颖的,从一张图片中扫一下居然能直接加好友,不可思议啊,那时候还不了解二维码.呵呵,然后做项目的时候.老板说要加上二维码扫描功能.然后自己的屁颠屁颠的去百度,google啥的.发现非常多朋友都有介绍二维码扫描的功能,然后我就跟着人家的介绍自己搞起了二维码扫描功能,跟着人家的帖子,非常快我的项目就增加了扫描二维码的功能,然后自己还非常开心. 随着微信的到来,二维码越来越火爆,随处能看到二维码,比方商城里面,肯德基,餐厅等等.对于

基于zxing的彩色二维码生成与解析

最近正在封装一套基于H5的APP开发平台,而二维码是APP中必不可少的功能. 以前做WEB开发的时候采用的是JS生成条形码和二维码,虽然简洁易用,但是无法添加logo,彩色美化等功能, 由于以前没有接触过图像编程,只好查阅了大量的网络资料,在前人的经验和基础之上,封装了一个基于zxing的工具类, 该工具类目前比较简单,只是实现了普通二维码.logo二维码.彩色二维码和二维码条形码解析几个功能. 根据此工具类可扩展生成LOGO+文字的二维码,暂时不需要没有封装. 采用zxing生成的条形码,无法

基于google Zxing实现二维码、条形码扫描,仿微信二维码扫描效果

随着微信的到来,二维码越来越火爆,随处能看到二维码,比如商城里面,肯德基,餐厅等等,对于二维码扫描我们使用的是google的开源框架Zxing,我们可以去http://code.google.com/p/zxing/下载源码和Jar包,之前我项目中的二维码扫描功能只实现了扫描功能,其UI真的是其丑无比,一个好的应用软件,其UI界面也要被大众所接纳,不然人家就不会用你的软件啦,所以说应用软件功能和界面一样都很重要,例如微信,相信微信UI被很多应用软件所模仿,我也仿照微信扫描二维码效果进行模仿,虽然

Python调用Tesseract-OCR和Zxing完成图片OCR识别和二维码解码

先贴代码: # 1.Install tesseract-ocr*.exe from http://jaist.dl.sourceforge.net/project/tesseract-ocr-alt/tesseract-ocr-setup-3.02.02.exe # 2.Install pillow as"pip install form *.whl" # 3.Install pytesseract as"pip install form *.whl" import