前言 因工作需要,需要定位图片中的二维码;我遂查阅了相关资料,也学习了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, , , 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(, )); //通过黑色定位角作为父轮廓,有两个子轮廓的特点,筛选出三个定位角
int parentIdx = -;
int ic = ; for (int i = ; i < contours.size(); i++)
{
if (hierarchy[i][] != - && ic == )
{
parentIdx = i;
ic++;
}
else if (hierarchy[i][] != -)
{
ic++;
}
else if (hierarchy[i][] == -)
{
ic = ;
parentIdx = -;
} {
bool isQr = QrParse::IsQrPoint(contours[parentIdx], threshold_output_copy); //保存找到的三个黑色定位角
if (isQr)
qrPoint.push_back(contours[parentIdx]); ic = ;
parentIdx = -;
}
} return ;
}

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

bool QrParse::IsQrPoint(vector<Point>& contour, Mat& img)
{
//最小大小限定
RotatedRect rotatedRect = minAreaRect(contour);
if (rotatedRect.size.height < || rotatedRect.size.width < )
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 / ;
int nc = image.cols * image.channels(); vector<int> vValueCount;
vector<uchar> vColor;
int count = ;
uchar lastColor = ; uchar* data = image.ptr<uchar>(nr);
for (int i = ; i < nc; i++)
{
vColor.push_back(data[i]);
uchar color = data[i];
if (color > )
color = ; if (i == )
{
lastColor = color;
count++;
}
else
{
if (lastColor != color)
{
vValueCount.push_back(count);
count = ;
}
count++;
lastColor = color;
}
} if (count != )
vValueCount.push_back(count); if (vValueCount.size() < )
return false; //横向黑白比例1:1:3:1:1
int index = -;
int maxCount = -;
for (int i = ; i < vValueCount.size(); i++)
{
if (i == )
{
index = i;
maxCount = vValueCount[i];
}
else
{
if (vValueCount[i] > maxCount)
{
index = i;
maxCount = vValueCount[i];
}
}
} //左边 右边 都有两个值,才行
if (index < )
return false;
if ((vValueCount.size() - index) < )
return false; //黑白比例1:1:3:1:1
float rate = ((float)maxCount) / 3.00; cout << "flag:" << flag << " "; float rate2 = vValueCount[index - ] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false; rate2 = vValueCount[index - ] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false; rate2 = vValueCount[index + ] / rate;
cout << rate2 << " ";
if (!IsQrRate(rate2))
return false; rate2 = vValueCount[index + ] / 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[] = { ,, };
int index2[] = { ,, };
int index3[] = { ,, }; for (int i = ; i < ; i++)
{
int *n = index1;
if(i==)
n = index1;
else if (i == )
n = index2;
else
n = index3; if (angle > && angle < )
{
pointDest[] = pointSrc[n[]];
if (clockwise)
{
pointDest[] = pointSrc[n[]];
pointDest[] = pointSrc[n[]];
}
else
{
pointDest[] = pointSrc[n[]];
pointDest[] = pointSrc[n[]];
}
return true;
}
}
return true;
}

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

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

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

4)二维码相邻区域定位

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

后记

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

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

  1. 基于opencv+python的二维码识别

    花了2天时间终于把二维码识别做出来了,不过效果一般,后面会应用在ROS辅助定位上,废话少说先上图: 具体过程参考了这位大神的博客:http://blog.csdn.net/qq_25491201/ar ...

  2. Opencv+Zbar二维码识别(二维码校正)

    二维码和车牌识别基本都会涉及到图像的校正,主要是形变和倾斜角度的校正,一种二维码的畸变如下图: 这个码用微信扫了一下,识别不出来,但是用Zbar还是可以准确识别的~~. 这里介绍一种二维码校正方法,通 ...

  3. zxing 如何识别反转二维码

    说起二维码扫描,估计很多人用的是 zxing 吧. 然而 zxing 虽然好用,但是却有一些坑. 这边分析一下自己实际项目遇到的一个坑. 什么坑呢? 下面举个栗子你就懂了. 这边生成二维码使用的是网络 ...

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

    Delphi+Halcon实战一:两行代码识别QR二维码 感谢网友:绝代双椒( QQ号应原作者要求隐藏了:xxxx6348)的支持 本文是绝代双椒的作品,因为最近在忙zw量化培训,和ziwang.co ...

  5. pytho创建二维码简单版

    pytho创建二维码简单版 import qrcode aa = qrcode.make("https://github.com/phygerr/") aa.save('C:\Us ...

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

      Android 高手进阶(21)  版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请注明出处:http://blog.csdn.net/xiaanming/article/detail ...

  7. 在Android上使用ZXing识别条形码/二维码

    越来越多的手机具备自动对焦的拍摄功能,这也意味着这些手机可以具备条码扫描的功能.......手机具备条码扫描的功能,可以优化购物流程,快速存储电子名片(二维码)等. 本文使用ZXing 1.6实现条码 ...

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

    原文网址:http://blog.csdn.net/xiaanming/article/details/10163203 转载请注明出处:http://blog.csdn.net/xiaanming/ ...

  9. qrcode.js的识别解析二维码图片和生成二维码图片

    qrcode只通过前端就能生成二维码和解析二维码图片, 首先要引入文件qrcode.js,下载地址为:http://static.runoob.com/download/qrcodejs-04f46c ...

随机推荐

  1. MySQL学习——操作自定义函数

    MySQL学习——操作自定义函数 摘要:本文主要学习了使用DDL语句操作自定义函数的方法. 了解自定义函数 是什么 自定义函数是一种与存储过程十分相似的过程式数据库对象.它与存储过程一样,都是由SQL ...

  2. CSS字体属性

    CSS字体属性 CSS Fonts(字体)属性拥有定义字体系列.大小.粗细和文字样式(如斜体) 字体系列 <style type="text/css"> div{ fo ...

  3. YYLable 的使用 以及注意点

    NSString *title = @"不得不说 YYKit第三方框架确实很牛,YYLabel在富文本显示和操作方面相当强大,尤其是其异步渲染,让界面要多流畅有多流畅,这里我们介绍下简单的使 ...

  4. 一文解读5G (转)

    今天要研究的对象,是5G接入网. 什么是接入网?相信不少同学,对这个概念一定不会陌生. 搬出这张移动通信架构图: 接入网,在我们无线通信里,一般指无线接入网,也就是通常所说的RAN(Radio Acc ...

  5. CodeForces-1257D (贪心+双指针)

    题意 https://vjudge.net/problem/CodeForces-1257D 你需要操作m个英雄去打败n只怪物,每个英雄的力量值为pi,可以打败si只怪物:每只怪物的力量值为ai. 当 ...

  6. 2018年蓝桥杯A组C/C++决赛题解

    2018年第九届蓝桥杯A组C/C++决赛题解 点击查看视频题解 点击查看2018年蓝桥杯A组C/C++决赛题目(不含答案) 1:三角形面积 画个图,求三角形面积,可以用外接长方形 - 其他多余区域面积 ...

  7. 2016年蓝桥杯B组C/C++决赛题解

    2016年第七届蓝桥杯B组C/C++决赛题解 2016年蓝桥杯B组C/C++决赛题目(不含答案) 1.一步之遥 枚举解方程,或者套模板解线性方程 #include<bits/stdc++.h&g ...

  8. keeplived+lvs(主从热备+负载均衡)

    本次实验基于DR负载均衡模式(直接路由),设置一个VIP(Virtual IP)为192.168.1.225,用户只需要访问这个IP地址即可获得网页服务.其中,负载均衡主机为192.168.1.221 ...

  9. MySQL学习笔记1——DDL

    DDL 1.数据库 *查看所有数据库:SHOW DATABASES;*切换(选择要操作的)数据库:USE 数据库名;*创建数据库:CREATE DATABASES [IF NOT EXISTS] my ...

  10. LeetCode 674. Longest Continuous Increasing Subsequence最长连续递增序列 (C++/Java)

    题目: Given an unsorted array of integers, find the length of longest continuous increasing subsequenc ...