Abstract  Keywords Opencv   C++   Qrcode   Opencv   Qrcode   C++   Citation Yao Qing-sheng.基于Opencv 识别、定位二维码 (C++版).FUTURE & CIVILIZATION Natural/Social Philosophy & Infomation Sciences,20240808. https://yaoqs.github.io/20240808/ji-yu-opencv-shi-bie-ding-wei-er-wei-ma-c-ban/  转载自基于opencv 识别、定位二维码 (c++版) 
前言 因工作需要,需要定位图片中的二维码;我遂查阅了相关资料,也学习了opencv开源库。通过一番努力,终于很好的实现了二维码定位。本文将讲解如何使用opencv定位二维码。
定位二维码不仅仅是为了识别二维码;还可以通过二维码对图像进行水平纠正以及相邻区域定位。定位二维码,不仅需要图像处理相关知识,还需要分析二维码的特性,本文先从二维码的特性讲起。
二维码特性 二维码在设计之初就考虑到了识别问题,所以二维码有一些特征是非常明显的。二维码有三个“回“”字形图案,这一点非常明显。中间的一个点位于图案的左上角,如果图像偏转,也可以根据二维码来纠正。
思考题:为什么是三个点,而不是一个、两个或四个点。
一个点:特征不明显,不易定位。不易定位二维码倾斜角度。
两个点:两个点的次序无法确认,很难确定二维码是否放正了。
四个点:无法确定4个点的次序,从而无法确定二维码是否放正了。
识别二维码,就是识别二维码的三个点,逐步分析一下这三个点的特性
每个点有两个轮廓。就是两个口,大“口”内部有一个小“口”,所以是两个轮廓。 如果把这个“回”放到一个白色的背景下,从左到右,或从上到下画一条线。这条线经过的图案黑白比例大约为:黑白比例为1:1:3:1:1。 如何找到左上角的顶点?这个顶点与其他两个顶点的夹角为90度。 通过上面几个步骤,就能识别出二维码的三个顶点,并且识别出左上角的顶点。
使用opencv识别二维码 查找轮廓,筛选出三个二维码顶点 opencv一个非常重要的函数就是查找轮廓,就是可以找到一个图中的缩所有的轮廓,“回”字形图案是一个非常的明显的轮廓,很容易找到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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, 0 , 255 , 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 (0 , 0 ));          int  parentIdx = -1 ;     int  ic = 0 ;     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 )         {             ic = 0 ;             parentIdx = -1 ;         }                45          {47                            if  (isQr)                 qrPoint.push_back (contours[parentIdx]);             ic = 0 ;             parentIdx = -1 ;         }     }     return  0 ; } 
找到了两个轮廓的图元,需要进一步分析是不是二维码顶点,用到如下函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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++;          bool  result = IsQrColorRate (cropImg, flag);     return  result; } 
黑白比例判断函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 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 / 2 ;     int  nc = image.cols * image.channels ();     vector<int > vValueCount;     vector<uchar> vColor;     int  count = 0 ;     uchar lastColor = 0 ;     uchar* data = image.ptr <uchar>(nr);     for  (int  i = 0 ; i < nc; i++)     {         vColor.push_back (data[i]);         uchar color = data[i]; 28           if  (i == 0 )         {             lastColor = color;             count++;         }         else          {             if  (lastColor != color)             {                 vValueCount.push_back (count);                 count = 0 ;             }             count++;             lastColor = color;         }     }     if  (count != 0 )         vValueCount.push_back (count);     if  (vValueCount.size () < 5 )         return  false ;          int  index = -1 ;     int  maxCount = -1 ;     for  (int  i = 0 ; i < vValueCount.size (); i++)     {         if  (i == 0 )         {             index = i;             maxCount = vValueCount[i];         }         else          {             if  (vValueCount[i] > maxCount)             {                 index = i;                 maxCount = vValueCount[i];             }         }     }            float  rate = ((float )maxCount) / 3.00 ;     cout << "flag:"  << flag << " " ;     float  rate2 = vValueCount[index - 2 ] / rate;     cout << rate2 << " " ;     if  (!IsQrRate (rate2))         return  false ;     rate2 = vValueCount[index - 1 ] / rate;     cout << rate2 << " " ;     if  (!IsQrRate (rate2))         return  false ;     rate2 = vValueCount[index + 1 ] / rate;     cout << rate2 << " " ;     if  (!IsQrRate (rate2))         return  false ;     rate2 = vValueCount[index + 2 ] / 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 ; } 
确定三个二维码顶点的次序 通过如下原则确定左上角顶点:二维码左上角的顶点与其他两个顶点的夹角为90度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 / pointDest存放调整后的三个点,三个点的顺序如下 bool  QrParse::AdjustQrPoint (Point* pointSrc, Point* pointDest)     bool  clockwise;     int  index1[3 ] = { 2 ,1 ,0  };     int  index2[3 ] = { 0 ,2 ,1  };     int  index3[3 ] = { 0 ,1 ,2  };     for  (int  i = 0 ; i < 3 ; i++)     {         int  *n = index1;         if (i==0 )             n = index1;         else  if  (i == 1 )             n = index2;         else               n = index3;         23          if  (angle > 80  && angle < 99 )         {             pointDest[0 ] = pointSrc[n[2 ]];             if  (clockwise)             {                 pointDest[1 ] = pointSrc[n[0 ]];                 pointDest[2 ] = pointSrc[n[1 ]];             }             else              {                 pointDest[1 ] = pointSrc[n[1 ]];                 pointDest[2 ] = pointSrc[n[0 ]];             }             return  true ;         }     }     return  true ; } 
通过二维码对图片矫正。 图片有可能是倾斜的,倾斜夹角可以通过pt0与pt1连线与水平线之间的夹角确定。二维码的倾斜角度就是整个图片的倾斜角度,从而可以对整个图片进行水平矫正。
1 2 3 4 5 6 7 8 9 Point hor (pointAdjust[0 ].x+300 ,pointAdjust[0 ].y)  ; double  qrAngle = QrParse::Angle (pointAdjust[1 ], hor, pointAdjust[0 ], clockwise);Mat drawingRotation = Mat::zeros (Size (src.cols,src.rows), CV_8UC3); double  rotationAngle = clockwise? -qrAngle:qrAngle;Mat affine_matrix = getRotationMatrix2D (pointAdjust[0 ], rotationAngle, 1.0 ); warpAffine (src, drawingRotation, affine_matrix, drawingRotation.size ());
二维码相邻区域定位 一般情况下,二维码在整个图中的位置是确定的。识别出二维码后,根据二维码与其他图的位置关系,可以很容易的定
后记 作者通过查找大量资料,仔细研究了二维码的特征,从而找到了识别二维码的方法。网上也有许多识别二维码的方法,但是不够严谨。本文是将二维码的多个特征相结合来识别,这样更准确。这种识别方法已应用在公司的产品中,识别效果还是非常好的
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!