二维码定位

来源:互联网 发布:大宋豪侠2java 编辑:程序博客网 时间:2024/06/05 16:52

使用opencv确定图片中二维码的位置

背景

确定二维码的位置并识别,有很多开源的例子,但是毕竟不是自己做的,还是想自己一步步学习一下,这里只做了一下工作:

1.确定二维码在图片中的位置,输出其像素坐标。

2.使用透视变换对识别的二维码矩形进行修正,方便之后的二维码识别(这里暂不做识别,有意的同学可以使用Zbar,zxing进行识别)。

在opencv的学习上我还是一个小白,有些考虑不周的地方还请多多指教。

流程图

首先上一张二维码的示意图,这里用abcd指代小矩形,下文会用到。
2

下面是流程图
img8

实现

对每一帧的图像的预处理详见附件,这里直接从Canny提取轮廓之后开始讲述

1. 边缘检测–Canny
Canny(blurImage, edgeTemp, canny_thread_min, canny_thread_max, 3);
2. 提取轮廓–findContours (文末有备注)
vector<vector<Point>> contours;vector<Vec4i> hierarchy;//输入图像,轮廓(点向量形式),轮廓数量,轮廓检索模式,轮廓逼近 findContours(edgeTemp, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
3. 绘制嵌套层次大于5的轮廓 (详情请点击此处了解hierarchy的含义)
vector<int> vector_contours_filter;for (int k, c, i = 0; i < contours.size(); i++){    k = i;    c = 0;    while (hierarchy[k][2] != -1)//表示不是最外面的轮廓    {        k = hierarchy[k][2];        c = c + 1;    }    if (c >= 5)    {        vector_contours_filter.push_back(i);    }}
4. 利用abc三个小矩形的12个顶点做最小包围矩形
//这里直接使用三个小矩形的12个顶点来绘制最小包围矩形Point2f point_minRect12[4];RotatedRect rect12 = minAreaRect(vertex_minRect4);  //vertex_minRect4 -> 装有abc三个小矩形的12个顶点的容器rect12.points(point_minRect12);//返回矩形的四个顶点给vertex  for (int j = 0; j < 4; j++){    line(src1, point_minRect12[j], point_minRect12[(j + 1) % 4], Scalar(0, 0, 255), 3, 8);//非常巧妙的表达式}Point2f midPoint_rect;for (int j = 0; j < 4; j++){    midPoint_rect.x += (point_minRect12[j].x / 4);    midPoint_rect.y += (point_minRect12[j].y / 4);}circle(src1, midPoint_rect, 10, Scalar(0, 255, 0), 3, 8);   //外接矩形的的中心imshow("结果图", src1);

经过这次处理之后,基本上就只剩下abc三个小矩形了。一般情况下,利用这三个小矩形的各个顶点,绘制包围他们的最小矩形(约定包围整个二维码的最小矩形为矩形A),然后求得矩形A的中心点O,这样就得到了像素坐标下的二维码的位置。效果如下图所示:
img3

但是,偶尔会有这种特殊的情况发生
img4 img5

############### 这就很尴尬了。。。##################

虽然这只是个别的情况,但也说明了只利用abc这三个小矩形来确定二维码的位置并不可靠。所以很有必要确定第四个小矩形d的位置,利用其把这个别情况下的二维码的最小包围矩形框纠正回来。

5. 画出第四个小矩形d

已知三个小矩形abc之后,第四个矩形d也就比较容易了。

img6

在第4步中的代码注释中提到vertex_minRect4是装有矩形abc总计12个顶点的容器,遍历这12个顶点,求两点之间的距离,找出距离最远的两个点,就是点a-1和c-3,然后利用求线段中点的公式,如下:

midPoint.x = (P_1.x + P_2.x)/2;midPoint.y = (P_1.y + P_2.y)/2;

来获取中点O的坐标,然后再分别求矩形b的四个点关于中点O的对称点的坐标,就是求线段中点的逆过程嘛。于是乎,矩形d的四个顶点我们就get到了。最后利用这四个矩形abcd的16个顶点,画它们的最小外接矩形,再通过矩形的四个顶点求取二维码的中点,

到这里,二维码在图像中的位置就确定了,是像素坐标哦

上述的那个特殊情况就不存在了Yeah!

6. 提取二维码

使用透视变换,将二维码投影到另一个平面,方便以后的识别

  1. 二维码的四个顶点的坐标:上一步利用abcd四个矩形的16个顶点做最小包围矩形时,可以获得矩形的四个顶点,数组vertexs_minRect_QR表示。

  2. 新的投影点的坐标:新的投影平面中,二维码的坐标可以自己定义,比如

//lenth是二维码的变长Point2f vertex_warp[4];vertex_warp[0] = Point2f(0, float(lenth-1));vertex_warp[1] = Point2f(0, 0);vertex_warp[2] = Point2f(float(lenth-1), 0);vertex_warp[3] = Point2f(float(lenth-1), float(lenth));
  1. 求解透视变换的矩阵
    可以使用opencv库中的getPerspectiveTransform函数获得
//! returns 3x3 perspective transformation for the corresponding 4 point pairs.CV_EXPORTS Mat getPerspectiveTransform( const Point2f src[], const Point2f dst[] );

操作代码如下,由此获得变换矩阵transform

Mat transform = getPerspectiveTransform(vertexs_minRect_QR, vertex_warp);
  1. 透视变换的实现
    奉上透视变换的函数warpPerspective
//! warps the image using perspective transformationCV_EXPORTS_W void warpPerspective( InputArray src, OutputArray dst,                                   InputArray M, Size dsize,                                   int flags=INTER_LINEAR,                                   int borderMode=BORDER_CONSTANT,                                   const Scalar& borderValue=Scalar());

实现

warpPerspective(src, dst, transform, Size(lenth, lenth)); 

结果图
img7
左是识别的效果图,右上是使用透视变换投影到另一个平面的效果,右下是二维码中心在原图中的像素坐标

备注:关于提取轮廓findContours函数的参数说明

函数原型如下:

//! retrieves contours and the hierarchical information from black-n-white image.CV_EXPORTS_W void findContours( InputOutputArray image, OutputArrayOfArrays contours,                              OutputArray hierarchy, int mode,                              int method, Point offset=Point());

其中的参数mode有以下可供选择,在识别二维码中,只能使用CV_RETR_TREE

RETR_EXTERNAL=CV_RETR_EXTERNAL, //!< retrieve only the most external (top-level) contoursRETR_LIST=CV_RETR_LIST, //!< retrieve all the contours without any hierarchical informationRETR_CCOMP=CV_RETR_CCOMP, //!< retrieve the connected components (that can possibly be nested)RETR_TREE=CV_RETR_TREE, //!< retrieve all the contours and the whole hierarchyRETR_FLOODFILL=CV_RETR_FLOODFILL

详情请点击此处了解

原创粉丝点击