最近师兄跟我提到我二维码定位,参考了许多大佬的程序,写了这个小程序

目的:

用opencv的库实现QRcode定位

环境:

  • Windows 10
  • VS2015
  • opencv3.4.0

基本原理
下图为二维码的其中一个黑色正方形,二维码定位主要是根据这个正方形的位置进行定位识别
这个正方形提供了两个特征:

  • 该正方形有三个轮廓特征,因此我们可以找到一个符合该特征的轮廓,便可以节省许多操作。如一个父轮廓内含有两个子轮廓。因此我们需要建立轮廓等级hierarchy
  • 该正方形的黑白比例为1:1:3:1:1。

下面我介绍一下RotatedRect类的angle参数:
这幅图是我认为最符合RotatedRect类的angle的描述。我的程序需要将黑色正方形旋转回正,因此需要了解这个angle 、width和heigh。
各算法输出图像
1.滤波、直方图均值化、二值化

这张图的三个黑色框非常明显

2.识别位置、填充、连通三个区域

3.将图片找轮廓 找出最小包围矩形,即可框出大致二维码位置

代码实现


#include "opencv2/opencv.hpp"using namespace cv;
using namespace std;Mat transformCorner(Mat src, RotatedRect rect);
bool isCorner(Mat &image);
double Rate(Mat &count);
int main()
{Mat src = imread("QRcode.jpg");if (!src.data)return -1;double start = (double)getTickCount();Mat srcGray, canvas;            //canvas为画布 将找到的定位特征画出来Mat canvasGray;    //pyrDown(src, src);  //图片过大时使用canvas = Mat::zeros(src.size(), CV_8UC3);/*灰度滤波直方图均值化 提高对比度*/cvtColor(src, srcGray, COLOR_BGR2GRAY);blur(srcGray, srcGray, Size(3, 3));equalizeHist(srcGray, srcGray);/*阈值根据实际情况 如视图中已找不到特征 可适量调整*/threshold(srcGray, srcGray, 50, 255, THRESH_BINARY);imshow("threshold", srcGray);/*contours是第一次寻找轮廓*//*contours2是筛选出的轮廓*/vector<vector<Point>> contours, contours2;vector<Vec4i> hierarchy;findContours(srcGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);int ic = 0;int parentIdx = -1;/*个人认为: 下面程序为寻找出有两个子轮廓的父轮廓*//*挺好用的 几乎筛选出来了*/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){parentIdx = -1;ic = 0;}if (ic >= 2){contours2.push_back(contours[parentIdx]);//drawContours(canvas, contours, parentIdx, Scalar(0, 0, 255), 1, 8);ic = 0;parentIdx = -1;}}vector<Point> center_all;  //center_all获取特性中心for (int i = 0; i < contours2.size(); i++){//drawContours(canvas, contours, i, Scalar(0, 255, 0), 2);double area = contourArea(contours2[i]);if (area < 100)continue;/*控制高宽比*/RotatedRect rect = minAreaRect(Mat(contours2[i]));double w = rect.size.width;double h = rect.size.height;double rate = min(w, h) / max(w, h);if (rate > 0.85)   {Mat image = transformCorner(src, rect); //返回旋转后的图片if (isCorner(image)){Point2f points[4];rect.points(points);for (int i = 0; i < 4; i++)line(src, points[i], points[(i + 1) % 4], Scalar(0, 255, 0), 2);drawContours(canvas, contours2, i, Scalar(0, 0, 255), -1);center_all.push_back(rect.center);}}}/*连接三个黑色正方形区域,将其变成一个轮廓,即可用最小矩形框选*/for (int i = 0; i < center_all.size(); i++){line(canvas, center_all[i], center_all[(i+1) % center_all.size()], Scalar(255, 0, 0),3);}vector<vector<Point>> contours3;cvtColor(canvas, canvasGray, COLOR_BGR2GRAY);findContours(canvasGray, contours3, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);/*原程序是没有这个设置 因此但输入图片无二维码时容易报错*/for (int i = 0; i < contours3.size(); i++){RotatedRect rect = minAreaRect(contours3[i]);Point2f boxpoint[4];rect.points(boxpoint);for (int i = 0; i < 4; i++)line(src, boxpoint[i], boxpoint[(i + 1) % 4], Scalar(0, 0, 255), 3);}imshow("src", src);imshow("canvas", canvas);//imshow("srcGray", srcGray);double    time = ((double)getTickCount() - start) / getTickFrequency();cout << "time:"<< time<< endl;waitKey(0);return 0;
}
/******************************************************************该变换是假设照片是在一个平面且垂直与摄像头(即二维码没有仿射或投影 仅仅是平移和旋转)
*******************************************************************/
Mat transformCorner(Mat src, RotatedRect rect)
{Point center = rect.center;   //旋转中心//circle(src, center, 2, Scalar(0, 0, 255), 2);//Size sz = Size(rect.size.width, rect.size.height);Point TopLeft = Point(cvRound(center.x),cvRound(center.y)) - Point(rect.size.height/2,rect.size.width/2);  //旋转后的目标位置TopLeft.x = TopLeft.x > src.cols ? src.cols : TopLeft.x;TopLeft.x = TopLeft.x < 0 ? 0 : TopLeft.x;TopLeft.y = TopLeft.y > src.rows ? src.rows : TopLeft.y;TopLeft.y = TopLeft.y < 0 ? 0 : TopLeft.y;//Point ButtonRight = (Point)center - Point(rect.size.width, rect.size.height);Rect RoiRect = Rect(TopLeft.x, TopLeft.y, rect.size.width, rect.size.height);   //抠图必备矩形double angle = rect.angle;        //旋转角度Mat mask,roi,dst;                //dst是被旋转的图片 roi为输出图片 mask为掩模Mat image;                       //被旋转后的图片Size sz = src.size();             //旋转后的尺寸mask = Mat::zeros(src.size(), CV_8U);/************************************为掩模上色 一般为白色因为RotatedRect 类型的矩形不容易调取内像素 (主要是我不太懂)因此我把矩形的四个顶点当成轮廓 再用drawContours填充************************************/vector<Point> contour;Point2f points[4];rect.points(points);for (int i = 0; i < 4; i++)contour.push_back(points[i]);vector<vector<Point>> contours;contours.push_back(contour);drawContours(mask, contours, 0, Scalar(1), -1);/*抠图,然后围绕中心矩阵中心旋转*/src.copyTo(dst, mask);//roi = dst(RoiRect);Mat M = getRotationMatrix2D(center, angle, 1);warpAffine(dst, image, M, sz);roi = image(RoiRect);//imshow("image", image);return roi;
}/***************判断输入图像的最底层轮廓是否有特征*********************/
bool isCorner(Mat &image)
{/*******dstCopy作用是防止findContours修改dstGray*******//*******dstGray后面还需要抠图**************************/Mat mask,dstGopy;Mat dstGray;mask = image.clone();cvtColor(image, dstGray, COLOR_BGR2GRAY);threshold(dstGray, dstGray, 100, 255, THRESH_BINARY_INV);  //阈值根据情况而定dstGopy = dstGray.clone();  //备份vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(dstGopy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);for (int i = 0; i < contours.size(); i++){//cout << i << endl;if (hierarchy[i][2] == -1 && hierarchy[i][3]){Rect rect = boundingRect(Mat(contours[i]));rectangle(image, rect, Scalar(0, 0, 255), 2);/******************由图可知最里面的矩形宽度占总宽的3/7***********************/if (rect.width < mask.cols*2/ 7)      //2/7是为了防止一些微小的仿射continue;if (Rate(dstGray(rect)) > 0.75)       //0.75是我测试几张图片的经验值 可根据情况设置(测试数量并不多){rectangle(mask, rect, Scalar(0, 0, 255), 2);return true;}}}//imshow("dstGray", image);//imshow("mask", dstGray);return  false;
}/********统计像素点*****/
double Rate(Mat &count)
{int number = 0;int allpixel = 0;for (int row = 0; row < count.rows; row++){for (int col = 0; col < count.cols; col++){if (count.at<uchar>(row, col) == 255){number++;}allpixel++;}}cout << (double)number / allpixel  << endl;return (double)number / allpixel;
}

总的来说,这份程序还是很多多余步骤,主要是功底不足,如有BUG,欢迎在评论区提出,一起讨论学习!

参考借鉴文档
图片参考网址和部分代码
https://blog.csdn.net/jia20003/article/details/77348170
轮廓判断程序最后的框选二维码程序参考网址https://blog.csdn.net/guanyonglai/article/details/59644437
RotatedRect angle width heigh位置参考网址
https://blog.csdn.net/u011765923/article/details/82708171
这几篇文章给了我一些灵感,可以参考一下

opencv 二维码定位相关推荐

  1. OpenCV 二维码定位与识别

    因为二维码本身含有信息,因此可以作为产品的信息载体,如:产品特征.在工业领域常用在产品入库.分拣和包装上.但常常会因为二维码图像污点.光照不均匀以及二维码图像倾斜等原因,使得二维码的识别正确率低,针对 ...

  2. python opencv 二维码定位识别

    # --coding:utf-8-- from Camera.sdk.Camera import Camerafrom math import sin,cos,radians,fabs import ...

  3. python3 + opencv +pyzbar实时检测二维码 / 定位二维码,并绘制出二维码的框和提取二维码内容

    python3 + opencv +pyzbar实时检测二维码 / 定位二维码,并绘制出二维码的框和提取二维码内容 1 pyzbar二维码检测模块 1.1. pyzbar模块介绍 1.2 pyzbar ...

  4. Opencv的使用小教程3——利用轮廓检测实现二维码定位

    Opencv的使用小教程3--利用轮廓检测实现二维码定位 二维码具有什么特征 实现效果 识别二维码的流程 1.预处理图像 2.寻找轮廓 3.通过寻找到的轮廓确定"回"的位置 4.创 ...

  5. opencv4.0.1 qr二维码定位识别源码详解(一)

    一.概述 opencv4.0版本以后,加入了二维码定位解码的功能,其主要功能基于quirc开源库,下载地址GitHub.约1200行代码,识别与定位占了约800行,解码部分不作赘述,直接调用quric ...

  6. 解决vue3-print-nb打印二维码定位(qrcode.vue) 问题

    解决vue3-print-nb打印二维码定位(qrcode.vue) 问题 在使用vu3-print-nb搭配qrcode.vue产生二维码的时候,我的代码是设置了全部元素都居中显示的,效果如下 &l ...

  7. 使用ROS和AprilTags进行相机定位(二维码定位全流程)

    使用ROS和AprilTags进行相机定位(二维码定位全流程) 配置环境 1 下载虚拟机 2 下载Ubuntu16.07 3 安装虚拟机和Ubuntu 4 安装ROS 5 USB相机驱动 标定.定位 ...

  8. 高速公路二维码定位报警系统

    高速公路正朝着智能化方向发展,已经逐步形成"高速公路智慧化"的科技理念.智慧高速公路提出引入互联网思维和技术,针对传统高速公路系统和管理服务进行重新构造,提升高速公路运营管理水平与 ...

  9. JavaCV/OpenCV 二维码扫描功能

    JavaCV/OpenCV 二维码扫描功能 怎样配置工程就不再赘述,不清楚的读者可以网上查找资料,二维码扫描功能通过JavaCV实现起来还是挺简单的,主要OpenCV中QRCodeDetector提供 ...

最新文章

  1. Linux优化和目录结构
  2. cocos对象池的使用
  3. UA OPTI501 电磁波 Lorentz Oscillator Model 4 Hilbet变换与Kramers-Konig关系式
  4. svn: Checksum mismatch while updating 'D:\workspace\demo\test\.svn\text-base\test.php.svn-base'
  5. 【转】基于DCMTK的DICOM相关程序编写攻略
  6. python中的编码问题
  7. Python爬虫入门_之urllib2urllib
  8. [转贴]VB函数的列表
  9. JAVA中小型医院信息管理系统源码 医院系统源码
  10. 对计算机排版的分析,针对计算机排版技术问题的解决措施分析
  11. Windwos磁盘管理工具diskpart
  12. EBS 取消PO订单、订单行、订单发运行
  13. Centos jenkins 插件安装失败
  14. Pulse local frame(画出光脉冲三维动态演化过程)
  15. 为什么程序员的业余项目大多都死了?
  16. javaCV入门指南:序章
  17. java虚数复数计算_虚部?从搞懂虚数开始....
  18. AndroidStudio基础线性布局
  19. 金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)-(一千零一拾一元整)输出。
  20. linux安装rar解压程序

热门文章

  1. win10安装Microsoft Office 2016(64位)提示已安装Microsoft Office 2016(32位)
  2. java安卓登入界面代码_android项目——登录界面
  3. Ubuntu中添加新硬盘
  4. 头脑王者对战游戏html5源码,头脑王者分析笔记及微信小程序解包源码
  5. (2020版) 墙裂推荐这十款精选 IntelliJ Idea 插件
  6. tns:无监听 解决之一:由于 linstener.log 贮存过满导致
  7. 2021-01-11小米随身wifi网络创建总是失败是什么问题?
  8. 应用Revit结合CAD图纸生成地形实体和地质模型
  9. android如何实现用户注册功能,Android 实现简单的登录注册功能(SharedPreferences和SQLite)...
  10. 【ICC】icc基本使用步骤(转载)(初稿)