基于opencv和zbar的二维码定位与解码

简介:最近研究了一下二维码的定位与解码,看了很多大神的博客文章,现在将总结的方法与心得体会写在博客中。源程序见本博客的最下方。


一、二维码的结构

通过很多博客文章的介绍,我们可以知道二维码有三个匹配模式区域,又叫PositionDetectionPattern。这三个区域位于二维码的三个顶点(下图中左下、左上、右上顶点)。与大多数方法类似,我们也是通过寻找这三个匹配模式区域来对二维码进行定位。

基于opencv和zbar的二维码定位与解码_第1张图片

二、使用opencv对二维码进行定位


下面我们使用opencv对下图中的二维码进行定位。我们主要使用opencv中的findContours()函数,顾名思义,其主要作用便是在二值图像中寻找轮廓。

基于opencv和zbar的二维码定位与解码_第2张图片
vector> contours;
vector hireachy;
findContours(binary, contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE,
Point());

输入的第一个参数为二值图像;

第二个参数为轮廓,函数检测到的轮廓存在该参数中;

第三个参数表示图像中轮廓的拓扑信息,我们主要是根据轮廓的拓扑信息来对二维码的匹配模式区域进行定位;每一个轮廓contours[ i ]对应4个hierarchy元素hierarchy [ i ][ 0 ]~hierarchy [ i ][ 3 ],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果没有对应项,对应的hierarchy [ i ] 值设置为负数。

第四个参数表示轮廓的检测模式,RETR_TREE表示提取所有轮廓,建立网状的轮廓结构,对检测到的轮廓建立等级关系。


现在我们把所有检测到的轮廓在原图中画出进行观察,如下图所示。


基于opencv和zbar的二维码定位与解码_第3张图片

注意观察,在匹配模式区域,我们可以看出轮廓是有包围关系的。即在该区域,有一个稍微大一点的矩形包围这两个小一些的矩形,三个矩形的中心基本上是同一个点。其实这样一个约束关系是比较强,于是我们就根据这个约束关系对查找到的轮廓进行筛选,再根据轮廓的边长以及边长比例进行筛选,最终我们成功找到了二维码的匹配模式区域,结果如下图所示。



基于opencv和zbar的二维码定位与解码_第4张图片

补充

在上述方法的基础上,我们增加了一个筛选函数

void check_center(vector >c, vector& index)

第一个参数为上述方法筛选出来的轮廓;

第二个参数为一个索引向量;


我们假设二维码附近区域的背景比较干净,干扰较少。

该函数的作用是,首先提取每一个轮廓的中心,然后分别求每两个中心间的距离,最后将距离最短的两个中心的索引 和 距离第二短的两个中心的索引值 赋予 索引向量index


我们是基于这样的假设 如果图片中有类似于二维码三个顶点这样的图形,只要不是集中在一起出现,那么其中心间的距离很大可能高于二维码三个顶点间的距离,则会被check_center()函数排除掉。


当然这种方法的有效性还有待验证,仅供参考!


比较严谨的筛选方法可以参考下面这篇博客:


https://blog.callmewhy.com/2016/04/23/opencv-find-qrcode-position/


三、使用zbar进行识别


上一步,我们已经找到了二维码的三个匹配模式区域,并且为每一个区域生成了一个矩形。现在我们定义一个vector类型的对象final,将每一个矩形的四个顶点都添加进这个对象中,在根据final对象生成一个ROI区域。


Rect ROI=boundingRect(final);

当使用zbar包进行二维码的解码时,不需要图片中二维码的区域为100%。二维码的区域占图片比例的50~100%时,使用zbar包便能够成功解码二维码的信息。因此,这大大增加了算法对二维码定位的容错率。我们无需对二维进行精准定位,只需要保证整个二维码出现在我们的定位区域中并且比例高于50%


本实验所使用的图片中二维码背景比较简单,干扰较少,因此通过上一节所说的方法能够对二维码进行精准定位。实际上使用时,二维码背景较为复杂,通过上述方法只能对二维码进行一个粗略的定位。但是通过我们的观察,二维码大部分情况下可以完整的出现在我们的定位区域中,并且比例高于50%



考虑到二维码的边缘部分有可能出现在定位区域的交界处,造成信息的缺失,无法对二维码进行解码。我们对定位区域进行一定比例的放大,这有利于后面二维码的解码。


最后我们调用zbar包中的函数对二维码进行解码。具体过程见完整源程序。


基于opencv和zbar的二维码定位与解码_第5张图片

源程序如下

#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace cv;
using namespace std;
using namespace zbar;
double getDistance (CvPoint pointO,CvPoint pointA )
{
    double distance;
    distance = powf((pointO.x - pointA.x),2) + powf((pointO.y - pointA.y),2);
    distance = sqrtf(distance);

    return distance;
}

void check_center(vector >c, vector& index)
{
    float dmin1=10000;
    float dmin2=10000;
    for(int i=0;i10)
            {
                if(d10)
                {
                    dmin2=dmin1;
                    dmin1=d;
                    index[2]=index[0];
                    index[3]=index[1];
                    index[0]=i;
                    index[1]=j;

                }
                else
                {
                   dmin2=d;
                   index[2]=i;
                   index[3]=j;
                }
            }
        }
    }
}

int main(int argc, char** argv) {

       Mat src = imread(argv[1]);
       int hight=src.rows;
       int width=src.cols;
       Mat gray, binary;
       cvtColor(src, gray, COLOR_BGR2GRAY);

       threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);

       vector> contours;
       vector hireachy;

       findContours(binary.clone(), contours, hireachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());


       vector found;
        vector> found_contours;
       for (size_t t = 0; t < contours.size(); t++) {
           double area = contourArea(contours[t]);
           if (area < 100) continue;

           RotatedRect rect = minAreaRect(contours[t]);
           // 根据矩形特征进行几何分析
           float w = rect.size.width;
           float h = rect.size.height;
           float rate = min(w, h) / max(w, h);
           Point2f fourPoint2f[4];
           rect.points(fourPoint2f);
           if(w=2)
               {
                   found.push_back(t);
                   found_contours.push_back(contours[t]);
               }
           }
       }

       if(found.size()>=3)
       {
           vector indexs(4,-1);
           check_center(found_contours,indexs);
           vector  final;
           for(int i=0;i<4;i++)
           {

               RotatedRect part_rect=minAreaRect(found_contours[indexs[i]]);
               Point2f p[4];
               part_rect.points(p);
               for (int j=0;j<4;j++)
               {

                   final.push_back(p[j]);
               }
           }

             //region of qr
             Rect ROI=boundingRect(final);
             Point left_top=ROI.tl();
             Point right_down=ROI.br();
             if(left_top.x>=20 || left_top.y>=20 || right_down.x<=width-20 || right_down.y<=hight-20 )
             {
                 ROI=ROI+Point(-20,-20)+Size(40,40);
             }

             if (ROI.tl().x >0 && ROI.tl().y>0 && ROI.br().xget_data() << '"' << endl;
                 }

             }
       }

    imshow("【绘制结束后的图像】", src);
    waitKey(0);
    return 0;
}

CMakeLists.txt文件如下

cmake_minimum_required( VERSION 2.8 )
project( QR )

# 添加c++ 11标准支持
set( CMAKE_CXX_FLAGS "-std=c++11" )

# 寻找OpenCV库
find_package( OpenCV REQUIRED )
# 添加头文件
include_directories( ${OpenCV_INCLUDE_DIRS} )

add_executable( QR_detect QR.cpp )
# 链接OpenCV库
target_link_libraries( QR_detect ${OpenCV_LIBS} zbar )

参考文章

《OpenCV3编程入门》

https://blog.callmewhy.com/2016/04/23/opencv-find-qrcode-position/

你可能感兴趣的:(opencv)