zoukankan      html  css  js  c++  java
  • 基于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        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         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)二维码相邻区域定位

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

    后记

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

  • 相关阅读:
    yum安装工具的理解
    Linux防火墙
    Python的优雅写法
    Python的time模块
    Python中根据提供的日期,返回是一年中的第几天
    观察者模式
    数据插入INSERT
    RSA加密、解密、签名、校验签名
    js的apply和call
    js插件编程-tab框
  • 原文地址:https://www.cnblogs.com/yuanchenhui/p/opencv_qr.html
Copyright © 2011-2022 走看看