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 ());
二维码相邻区域定位 一般情况下,二维码在整个图中的位置是确定的。识别出二维码后,根据二维码与其他图的位置关系,可以很容易的定
后记 作者通过查找大量资料,仔细研究了二维码的特征,从而找到了识别二维码的方法。网上也有许多识别二维码的方法,但是不够严谨。本文是将二维码的多个特征相结合来识别,这样更准确。这种识别方法已应用在公司的产品中,识别效果还是非常好的
转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!