zoukankan      html  css  js  c++  java
  • (OpenCV+Qt)的QR码精确定位与识别完全解析(精度可达±0.1mm,±0.1°)

    一、介绍

    算法功能:
    对QR码进行x,y方向定位和旋转角度计算,并获取QR码的二进制内容
    算法优势:
    1.计算速度快,可达4-7ms(使用cpu i7-8750)。
    2.定位精度高,x,y方向精度为±1mm,转角精度为±0.1°(使用某宝几十元彩色相机,30w像素,噪声较为严重)。
    3.采用自动阈值方法,对光照不敏感。
    4.采用不规则四边形轮廓提取和网格划分,支持二维码翻转识别,最大翻转倾角可达45°。
    5.对QR码的规模自动计算,可用于不同行列数的QR码。

    先看几张效果图
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    二、思路和代码(共10步)

    阅读前注意!!!本算法使用C++11和Qt共同开发,某些数据类型(如字符串QString、链表QList)为Qt专属类型,可能需要稍加改动后才能供读者使用。
    本文重在分享思路,若需要源码,请在评论中留言。

    1.彩色图转灰度图+高斯滤波

    在这里插入图片描述
    也可以不进行滤波,如果使用滤波算法,核不推荐过大(此处采用Size(1, 1))。

    1 Mat src_gray;
    2 cvtColor(srcImg1, src_gray, CV_BGR2GRAY); //彩色图转灰度图
    3 GaussianBlur(src_gray, src_gray, Size(1, 1),2, 2, BORDER_DEFAULT); //高斯滤波
    4 imshow("1.彩色图转灰度图+高斯滤波", src_gray);

    2.二值化(Otsu自动阈值)

    在这里插入图片描述

    1 Mat threshold_output;
    2 threshold(src_gray, threshold_output, 0, 255, THRESH_BINARY|THRESH_OTSU); //Otsu 二值化
    3 imshow("2.二值化(Otsu自动阈值)", threshold_output);

    3.形态学滤波(开运算+闭运算)

    开运算和开运算是基于几何运算的滤波器。
    开运算(先腐蚀,后膨胀)能够除去孤立的小点,毛刺和小桥,而总的位置和形状不变。闭运算(先膨胀,后腐蚀)能够填平小湖(即小孔),弥合小裂缝,而总的位置和形状不变。
    参数MORPH_ELLIPSE表示使用椭圆形运算核,可使处理后的边界较为圆滑。
    在这里插入图片描述

    1 Mat threshold_output_copy = threshold_output.clone();
    2 Mat element = getStructuringElement(MORPH_ELLIPSE, Size(3, 3)); 
    3 morphologyEx(threshold_output_copy, threshold_output_copy, MORPH_OPEN, element); //开运算
    4 morphologyEx(threshold_output_copy, threshold_output_copy, MORPH_CLOSE, element); //闭运算
    5 imshow("3.开运算+闭运算", threshold_output_copy);

    4.边缘检测

    使用Canny算子进行边缘检测,为下一步提取轮廓做准备。
    在这里插入图片描述

    1 vector<vector<Point> > contours;
    2 vector<Vec4i> hierarchy;
    3 Mat canny_output;
    4 Canny( threshold_output_copy, canny_output, 1, 3, 7,true );  //Canny检测
    5 imshow("4.Canny边缘检测", canny_output);

    5.轮廓提取

    使用findContours函数对边缘提取后的图像进行轮廓提取。
    在这里插入图片描述

     1 Mat image=canny_output.clone();
     2 findContours(image,contours,hierarchy,RETR_TREE,CHAIN_APPROX_SIMPLE,Point());
     3 Mat Contours=Mat::zeros(image.size(),CV_8UC1);  
     4 //绘制 //contours[i]代表的是第i个轮廓,contours[i].size()代表的是第i个轮廓上所有的像素点数
     5 for(int i=0;i<contours.size();i++)
     6     for(int j=0;j<contours[i].size();j++){
     7         Point P=Point(contours[i][j].x,contours[i][j].y);
     8         Contours.at<uchar>(P)=255;
     9         }  
    10 imshow("5.findContours轮廓提取",Contours); //轮廓

    6.筛选出三层独立包含特点的轮廓

    在这里插入图片描述

     1 QList<vector<vector<Point>>> qrPointList;//每个节点表示一个回形点集
     2 vector<vector<Point>> qrPoint;
     3 Mat mat6=Contours.clone();
     4 cvtColor(mat6, mat6, CV_GRAY2BGR);
     5 int Parindex[3]={-1,-1,-1};
     6 for (int i = 0; i < contours.size(); i++){
     7         if (hierarchy[i][3] != -1 && hierarchy[i][2] == -1){
     8             Parindex[0]=hierarchy[i][3];
     9             if (hierarchy[(Parindex[0])][3] != -1){
    10                 Parindex[1]=hierarchy[(Parindex[0])][3];
    11                 if (hierarchy[(Parindex[1])][3] != -1){
    12                     Parindex[2]=hierarchy[(Parindex[1])][3];
    13                     if (hierarchy[(Parindex[2])][3] != -1){
    14                         if(!(i-1==Parindex[0]&&Parindex[0]-1==Parindex[1]&&Parindex[1]-1==Parindex[2]))
    15                             continue; //都是独生轮廓
    16                         qrPoint.push_back(contours[i]);
    17                         qrPoint.push_back(contours[i-2]);
    18                         qrPoint.push_back(contours[i-4]);
    19                         for(int i=0;i<qrPoint.size();i++)
    20                             for(int j=0;j<qrPoint[i].size();j++)
    21                                 circle(mat6,qrPoint[i][j],2,Scalar(0,255,0),-1);
    22                         qrPointList.push_back(qrPoint);
    23                         qrPoint.clear();
    24                     }
    25                 }
    26             }
    27         }
    28 }
    29 imshow("6.检测出的三层轮廓",mat6); //轮廓

    7.若筛选出的三层轮廓数目大于3,进一步筛选,最终只保留三个

    此处代码可自行编写,我的思路为:
    1.将每组三层轮廓分别拟合最小外接矩形,然后根据同心度、三层周长比例(3:5:7)、最小允许周长进行初步筛选。
    2.对剩下的进行最终筛选,筛选标准为正确的QR码三个角元素应有一定的平行度,尺寸也相近。
    注意!!!此段代码12,13行分别根据轮廓点集计算出最小外接多边形和最小外接矩形,由于镜头与QR码可能存在倾角,故后面使用最小外接四边形进行定位,精度更高。
    要注意两者的区别,其中approxPolyDP的精度设定非常重要,精度不宜过高(此处选择精度为5,值越小精度越高),否则会使得拟合出的四边形边数过多。

      1 QList<Point> pointList; //存储角点中心
      2     QList<RotatedRect> RectList; //存储角元素最外层矩形
      3     QList<vector<Point>> OutquadList; //存储最外层拟合四边形角点
      4     vector<bool> qrPointListEnable(qrPointList.size()); //筛选时使用
      5     for (int L=0;L<qrPointList.size(); L++)//遍历每个可能的图元
      6     {
      7         qrPoint=qrPointList.at(L);      
      8         vector<vector<Point>> contours_poly(qrPoint.size());
      9         vector<RotatedRect> minRect(qrPoint.size()); //存储了嵌套的最小外接矩形*****
     10         vector<Point2f> rect_center(qrPoint.size());
     11         for (int i = 0; i < qrPoint.size(); i++){
     12             approxPolyDP(Mat(qrPoint[i]), contours_poly[i], 5, true);//用指定精度逼近多边形曲线
     13             minRect[i] = minAreaRect(Mat(qrPoint[i])); //得到最小外接矩形
     14             rect_center[i]=minRect[i].center; //得到最小外接矩形中心
     15         }
     16         //根据同心度筛选
     17         for (int i = 0; i < minRect.size()-1; i++){
     18             Point P1=Point(rect_center[i].x,rect_center[i].y);
     19             Point P2=Point(rect_center[i+1].x,rect_center[i+1].y);
     20             float ConcenError_Set=(minRect[i].size.width+minRect[i].size.height)/12; //***最大允差设定***
     21             if( sqrt(pow(P1.x-P2.x,2)+pow(P1.y-P2.y,2)) > ConcenError_Set  ){
     22                 qrPointListEnable[L]=false;
     23                 break; }
     24             else
     25                 qrPointListEnable[L]=true;
     26         }
     27         if(!qrPointListEnable[L])continue;
     28 
     29         //根据三层周长比例进行筛选(3:5:7)
     30         for (int i = 0; i < minRect.size()-1; i++) {
     31             float circum1=(minRect[i].size.width+minRect[i].size.height)*2;
     32             float circum2=(minRect[i+1].size.width+minRect[i+1].size.height)*2;
     33             if( circum1/circum2>=0.5 && circum1/circum2<=0.8 )  //***周长比例设定***
     34                 qrPointListEnable[L]=true;
     35             else{
     36                 qrPointListEnable[L]=false;
     37                 break; }
     38         }
     39         if(!qrPointListEnable[L])continue;
     40 
     41         //周长不能过小
     42         for (int i = 0; i < minRect.size(); i++){
     43             float circum=(minRect[i].size.width+minRect[i].size.height)*2;
     44             float circum_Set=20;  //***有效周长最小值设定***
     45             if( circum >= circum_Set )
     46                 qrPointListEnable[L]=true;
     47             else{
     48                 qrPointListEnable[L]=false;
     49                 break; }
     50         }
     51         if(!qrPointListEnable[L])continue;
     52 
     53         //筛选完毕!!!筛选出的个数可能为任意自然数
     54         for (int i = 0; i<qrPoint.size(); i++){
     55             Point2f rect_points[4];
     56             minRect[i].points(rect_points);
     57             if(i==2)
     58                 RectList.push_back(minRect[i]);        //RectList赋值   筛选后的最外层外接矩形
     59             bool exsit=false;
     60             Point P=Point(rect_center[i].x,rect_center[i].y);
     61             for(int j=0;j<pointList.size();j++){
     62                 if( fabs(pointList.at(j).x-P.x)<10 && fabs(pointList.at(j).y-P.y)<10 ){
     63                     exsit=true; break; }
     64             }
     65             if(!exsit||pointList.size()==0)
     66                 pointList.append(P);                   //pointList赋值    筛选后的三层同心中心点
     67             if(i==2)
     68                 OutquadList.append(contours_poly[i]);  //OutquadList赋值    最外层外接四边形
     69         }
     70     }
     71 
     72 //8    //最终筛选,保留可能性最大的三个角点和轮廓
     73     if(RectList.size()>3){
     74         QList<float> RectSizeErrorList; //尺寸误差
     75         for(int i=0;i<RectList.size();i++){
     76             float RectSizeError=0;
     77             float RectSize1=( RectList.at(i).size.width + RectList.at(i).size.height )*2;
     78             for(int j=0;j<RectList.size();j++ && j!=i){
     79                 float RectSize2=( RectList.at(j).size.width + RectList.at(j).size.height )*2;
     80                 float Error= fabs( RectSize1 - RectSize2 );
     81                 RectSizeError+=Error;
     82             }
     83             RectSizeErrorList.append(RectSizeError);
     84         }
     85         QList<float> RectAngleErrorList; //角度误差
     86         for(int i=0;i<RectList.size();i++){
     87             float RectAngleError=0;
     88             float RectAngle1=RectList.at(i).angle;
     89             for(int j=0;j<RectList.size();j++ && j!=i){
     90                 float RectAngle2=RectList.at(j).angle;
     91                 float Error= fabs( RectAngle1 - RectAngle2 );
     92                 RectAngleError+=Error;
     93             }
     94             RectAngleErrorList.append(RectAngleError);
     95         }
     96         QList<float> RectErrorList; //综合误差
     97         for(int i=0;i<RectList.size();i++)
     98             RectErrorList.append(RectSizeErrorList.at(i)+RectAngleErrorList.at(i));
     99         for(int i=RectErrorList.size()-2;i>=0;i--) //根据综合误差 对 矩形链表 进行排序(从小到大)
    100             for(int j=0;j<=i;j++){
    101                 if(RectErrorList.at(j+1)<RectErrorList.at(j)){
    102                     RectErrorList.swap(j+1,j);
    103                     RectList.swap(j+1,j);
    104                     pointList.swap(j+1,j);
    105                     OutquadList.swap(j+1,j);
    106                 }
    107             }
    108         //剔除误识别点
    109         while(RectList.size()>3)   RectList.removeLast();
    110         while(pointList.size()>3)  pointList.removeLast();
    111         while(OutquadList.size()>3)  OutquadList.removeLast();
    112     }
    113     else if(RectList.size()<3)
    114     {
    115         std::string text = "NULL";
    116         available=false;
    117         int font_face = cv::FONT_HERSHEY_COMPLEX;
    118         Point origin;
    119         double font_scale = 1;
    120         int thickness = 2;
    121         origin.x = 50; origin.y = 40;
    122         cv::putText(srcImgF, text, origin, font_face, font_scale, cv::Scalar(0, 0, 255), thickness, 8, 0);
    123     }
    124 /*************************************重要代码段******************************************
    125 *至此已有     RectList:   type=QList<RotatedRect>     size=3 //存储角元素最外层最小外接矩形
    126 *            pointList:  type=QList<Point>           size=3 //存储角点中心
    127 *            OutquadList:type=QList<vector<Point>>   size=3 //存储最外层拟合四边形角点
    128 ***************************************************************************************/

    8.对QR码三个角元中心点进行排序(左上0,右上1,左下2)

    我的思路:将三个中心点连接为三角形,分别计算每个对应内角,最接近90度的即为左上角点,然后以左上角点为起点,其余两个点为终点作出两个向量,计算两向量夹角,通过夹角正负确定其余两个角点
    在这里插入图片描述

     1 //对角点和矩形进行位置排序(左上:1   右上:2   左下:3)
     2         QList<float> angleList;
     3         for(int i=0;i<pointList.size();i++) //计算每个点的内角
     4         {
     5             float angle=0;
     6             Point thispoint=pointList.at(i); //本点
     7             Point otherpoint[2]; //其余两个点
     8             if(i==0){
     9                 otherpoint[0] = pointList.at(1);
    10                 otherpoint[1] = pointList.at(2);}
    11             else if(i==1){
    12                 otherpoint[0] = pointList.at(0);
    13                 otherpoint[1] = pointList.at(2);}
    14             else{
    15                 otherpoint[0] = pointList.at(0);
    16                 otherpoint[1] = pointList.at(1);}
    17             float a=sqrt( pow(thispoint.x-otherpoint[1].x,2) + 
    18                           pow(thispoint.y-otherpoint[1].y,2) ); //边a(otherpoint[0]的对边)
    19             float b=sqrt( pow(otherpoint[0].x-otherpoint[1].x,2) + 
    20                           pow(otherpoint[0].y-otherpoint[1].y,2) ); //边b(thispoint的对边)
    21             float c=sqrt( pow(thispoint.x-otherpoint[0].x,2) + 
    22                           pow(thispoint.y-otherpoint[0].y,2) ); //边c(otherpoint[1]的对边)
    23             angle=acos( ( a*a + c*c -b*b ) / (2*a*c) )*180/M_PI;
    24             angleList.append(angle);
    25         }
    26         for(int i=angleList.size()-2;i>=0;i--) //确定0号点位置
    27             for(int j=0;j<=i;j++)
    28             {
    29                 float error1=fabs(angleList.at(j)-90);
    30                 float error2=fabs(angleList.at(j+1)-90);
    31                 if(error2 < error1){
    32                     angleList.swap(j+1,j);
    33                     pointList.swap(j+1,j);
    34                     RectList.swap(j+1,j);}
    35             }
    36         float Angle=getAngelOfTwoVector(pointList.at(1),pointList.at(2),pointList.at(0)); //以0为中心,2到1的角度
    37         if(Angle<0) //确定1,2号点位置
    38             pointList.swap(1,2);

    9.对QR码进行精确定位

    第一步:获取三个角元共12个角点(通过之前的拟合四边形结果)
    第二步:计算出第四个角元的四个角点
    第三步:计算出四个中心点,进一步计算出QR码中心点
    第四步:计算出QR码正方向,并绘制箭头进行表示。
    在这里插入图片描述

      1 //粗略计算QR码中心
      2         Point2f P0;
      3         P0.x = ( pointList.at(1).x + pointList.at(2).x ) / 2;
      4         P0.y = ( pointList.at(1).y + pointList.at(2).y ) / 2;
      5 
      6         //取出OutquadList的4*3个角点 到 cornerPointList
      7         vector<Point> cornerPointList;
      8         for(int i=0;i<OutquadList.size();i++){
      9             vector<Point> points(OutquadList.at(i).size());
     10             points=OutquadList.at(i);
     11             for(int j=0;j<points.size();j++)
     12                 cornerPointList.push_back(points[j]);
     13         }
     14         //针对cornerPointList的防抖算法
     15         //antiShake(cornerPointList);
     16         
     17         //按一定规则对这12个点重新排序
     18         sortNeartofar(cornerPointList,0,12,pointList.at(0));
     19         sortNeartofar(cornerPointList,4,12,pointList.at(1));
     20         vector<Point> cornerPointList_0;
     21         vector<Point> cornerPointList_1;
     22         vector<Point> cornerPointList_2;
     23         for(int i=0;i<4;i++){
     24             cornerPointList_0.push_back(cornerPointList[i]);
     25             cornerPointList_1.push_back(cornerPointList[i+4]);
     26             cornerPointList_2.push_back(cornerPointList[i+8]);
     27         }
     28         Point P0_0=getFarestPoint(cornerPointList_0,P0);
     29         Point P0_3=getNearestPoint(cornerPointList_0,P0);
     30         Point P0_2=getFarestPoint(cornerPointList_0,pointList.at(1));
     31         Point P0_1=getNearestPoint(cornerPointList_0,pointList.at(1));
     32         Point P1_1=getFarestPoint(cornerPointList_1,P0);
     33         Point P1_2=getNearestPoint(cornerPointList_1,P0);
     34         Point P1_3=getFarestPoint(cornerPointList_1,pointList.at(0));
     35         Point P1_0=getNearestPoint(cornerPointList_1,pointList.at(0));
     36         Point P2_2=getFarestPoint(cornerPointList_2,P0);
     37         Point P2_1=getNearestPoint(cornerPointList_2,P0);
     38         Point P2_3=getFarestPoint(cornerPointList_2,pointList.at(0));
     39         Point P2_0=getNearestPoint(cornerPointList_2,pointList.at(0));
     40         Point P3_0=CrossPoint(P1_0,P1_2,P2_0,P2_1);
     41         Point P3_1=CrossPoint(P1_1,P1_3,P2_0,P2_1);
     42         Point P3_2=CrossPoint(P1_0,P1_2,P2_2,P2_3);
     43         Point P3_3=CrossPoint(P1_1,P1_3,P2_2,P2_3);
     44         circle(srcImgF, P0_0, 4,Scalar(0,255,255),4,1);
     45         circle(srcImgF, P0_1, 4,Scalar(0,255,255),4,1);
     46         circle(srcImgF, P0_2, 4,Scalar(0,255,255),4,1);
     47         circle(srcImgF, P0_3, 4,Scalar(0,255,255),4,1);
     48         circle(srcImgF, P1_0, 4,Scalar(0,255,255),4,1);
     49         circle(srcImgF, P1_1, 4,Scalar(0,255,255),4,1);
     50         circle(srcImgF, P1_2, 4,Scalar(0,255,255),4,1);
     51         circle(srcImgF, P1_3, 4,Scalar(0,255,255),4,1);
     52         circle(srcImgF, P2_0, 4,Scalar(0,255,255),4,1);
     53         circle(srcImgF, P2_1, 4,Scalar(0,255,255),4,1);
     54         circle(srcImgF, P2_2, 4,Scalar(0,255,255),4,1);
     55         circle(srcImgF, P2_3, 4,Scalar(0,255,255),4,1);
     56         circle(srcImgF, P3_0, 2,Scalar(0,255,255),4,1);
     57         circle(srcImgF, P3_1, 2,Scalar(0,255,255),4,1);
     58         circle(srcImgF, P3_2, 2,Scalar(0,255,255),4,1);
     59         circle(srcImgF, P3_3, 2,Scalar(0,255,255),4,1);
     60 
     61         //计算4个中心点
     62         Point2f P0_C,P1_C,P2_C,P3_C;
     63         P0_C.x=float(P0_0.x+P0_1.x+P0_2.x+P0_3.x)/float(4);
     64         P0_C.y=float(P0_0.y+P0_1.y+P0_2.y+P0_3.y)/float(4);
     65         P1_C.x=float(P1_0.x+P1_1.x+P1_2.x+P1_3.x)/float(4);
     66         P1_C.y=float(P1_0.y+P1_1.y+P1_2.y+P1_3.y)/float(4);
     67         P2_C.x=float(P2_0.x+P2_1.x+P2_2.x+P2_3.x)/float(4);
     68         P2_C.y=float(P2_0.y+P2_1.y+P2_2.y+P2_3.y)/float(4);
     69         P3_C.x=float(P3_0.x+P3_1.x+P3_2.x+P3_3.x)/float(4);
     70         P3_C.y=float(P3_0.y+P3_1.y+P3_2.y+P3_3.y)/float(4);
     71 
     72         //重新赋值pointLists, size变化:size=3 -> size=4
     73         QList<Point2f> poin2ftList;
     74         poin2ftList.clear();
     75         poin2ftList.append(P0_C);
     76         poin2ftList.append(P1_C);
     77         poin2ftList.append(P2_C);
     78         poin2ftList.append(P3_C);
     79 
     80         //重新计算中点
     81         P0.x = ( poin2ftList.at(0).x + poin2ftList.at(1).x
     82                + poin2ftList.at(2).x + poin2ftList.at(3).x) / 4;
     83         P0.y = ( poin2ftList.at(0).y + poin2ftList.at(1).y
     84                + poin2ftList.at(2).y + poin2ftList.at(3).y) / 4;
     85 
     86         //绘制三角形连线
     87         line(srcImgF,poin2ftList.at(0),poin2ftList.at(1),Scalar(0,255,255),1);
     88         line(srcImgF,poin2ftList.at(1),poin2ftList.at(2),Scalar(0,255,255),2);
     89         line(srcImgF,poin2ftList.at(0),poin2ftList.at(2),Scalar(0,255,255),1);
     90         line(srcImgF,poin2ftList.at(0),poin2ftList.at(3),Scalar(0,255,255),2);
     91         line(srcImgF,poin2ftList.at(1),poin2ftList.at(3),Scalar(0,255,255),1);
     92         line(srcImgF,poin2ftList.at(2),poin2ftList.at(3),Scalar(0,255,255),1);
     93 
     94         //计算屏幕中心点
     95         Point2f PScreen0;
     96         PScreen0.x = float(cols) / float(2);
     97         PScreen0.y = float(rows) / float(2);
     98 
     99         //绘制二维码正方向箭头
    100         Point2f P0_C_1_C;
    101         P0_C_1_C.x = ( poin2ftList.at(0).x + poin2ftList.at(1).x ) / float(2);
    102         P0_C_1_C.y = ( poin2ftList.at(0).y + poin2ftList.at(1).y ) / float(2);
    103         Point2f PFront;
    104         PFront.x = ( P0_C_1_C.x + P0_C_1_C.x-P0.x );
    105         PFront.y = ( P0_C_1_C.y + P0_C_1_C.y-P0.y );
    106         drawArrow( srcImgF, P0, PFront, 17, 15, Scalar(0,255,0), 4, 4);
    107 
    108         Point2f PX; //X轴正方向
    109         PX.x = P0.x+10;
    110         PX.y = P0.y;
    111 
    112         float side01=sqrt( pow(P0_0.x-P1_1.x,2) + pow(P0_0.y-P1_1.y,2) ); //边01
    113         float side12=sqrt( pow(P1_1.x-P2_2.x,2) + pow(P1_1.y-P2_2.y,2) ); //边12
    114         float side23=sqrt( pow(P2_2.x-P3_3.x,2) + pow(P2_2.y-P3_3.y,2) ); //边23
    115         float side30=sqrt( pow(P3_3.x-P0_0.x,2) + pow(P3_3.y-P0_0.y,2) ); //边30
    116         float QRMeatrue=QRrealSize*4/(side01+side12+side23+side30);
    117 
    118         QRX=(P0.x-PScreen0.x)*QRMeatrue; //QR码在x方向偏差(像素)
    119         QRY=(PScreen0.y-P0.y)*QRMeatrue; //QR码在y方向偏差(像素)
    120         QRAngle=getAngelOfTwoVector(P0_C_1_C,PX,P0); //QR码正方向相对于X轴正方向的夹角
     1 float getAngelOfTwoVector(Point2f pt1, Point2f pt2, Point2f c)
     2 {
     3     float theta = atan2(pt1.x - c.x, pt1.y - c.y) - atan2(pt2.x - c.x, pt2.y - c.y);
     4     if (theta > CV_PI)
     5         theta -= 2 * CV_PI;
     6     if (theta < -CV_PI)
     7         theta += 2 * CV_PI;
     8     theta = theta * 180.0 / CV_PI;
     9     return theta;
    10 }

    10.对QR码进行内容识别

    思路:
    第一步:对QR码内容区域分别进行网格化处理,定位到每个色块中心点
    第二步:判断利用之前步骤中二值化后的图像,计算每个色块的灰度(灰度值0代表色块为黑色,灰度值255代表色块为白色),获取QR码的二进制内容。
    第三步:对QR码二进制内容进行解码,得到真正的内容。
    这里我实现了一个简易的冗余校验功能,即对二维码信息重复四次,分别存储在QR码四个不同的区域,以此来保证数据的稳定性。
    在这里插入图片描述
    用到的函数:
    1.任意四边形网格化函数

     1 //网格计算函数 //左上为P0,P0-P3顺时针或逆时针排列  //mode=1,2:返回网格角点,网格中点  //upex等为向外扩展参数
     2 vector<Point> Thread_CameraBelow::Gridding(Point P0,Point P1,Point P2,Point P3,
     3                        int rows,int cols,int mode,
     4                        int upex,int downex,int leftex,int rightex)
     5 {
     6     vector<Point> pointvector01; //在P0和P1方向上创建等距点
     7     vector<Point> pointvector12; //在P1和P2方向上创建等距点
     8     vector<Point> pointvector32; //在P3和P2方向上创建等距点
     9     vector<Point> pointvector03; //在P0和P3方向上创建等距点
    10     vector<Point> pointvector; //
    11     if(mode==1) //返回格式为网格角点
    12     {
    13         for(int j=0-leftex; j<cols+1+rightex; j++){
    14             Point point;
    15             point.x = P0.x+(P1.x-P0.x)*j/cols;
    16             point.y = P0.y+(P1.y-P0.y)*j/cols;
    17             pointvector01.push_back(point);
    18         }
    19         for(int j=0-upex; j<rows+1+downex; j++){
    20             Point point;
    21             point.x = P1.x+(P2.x-P1.x)*j/rows;
    22             point.y = P1.y+(P2.y-P1.y)*j/rows;
    23             pointvector12.push_back(point);}
    24         for(int j=0-leftex; j<cols+1+rightex; j++){
    25             Point point;
    26             point.x = P3.x+(P2.x-P3.x)*j/cols;
    27             point.y = P3.y+(P2.y-P3.y)*j/cols;
    28             pointvector32.push_back(point);
    29         }
    30         for(int j=0-upex; j<rows+1+downex; j++){
    31             Point point;
    32             point.x = P0.x+(P3.x-P0.x)*j/rows;
    33             point.y = P0.y+(P3.y-P0.y)*j/rows;
    34             pointvector03.push_back(point);
    35         }
    36         for(int i=0; i<rows+1+downex+upex; i++) //依次求得交叉点
    37             for(int j=0;j<cols+1+rightex+leftex;j++){
    38                 Point point=CrossPoint(pointvector01.at(j),pointvector32.at(j),
    39                                        pointvector03.at(i),pointvector12.at(i));
    40                 pointvector.push_back(point);
    41             }
    42     }
    43     else if(mode==2)
    44     {
    45         for(int j=(0-leftex)*2; j<(cols+rightex)*2+1; j++){
    46             Point point;
    47             point.x = P0.x+(P1.x-P0.x)*j/(cols*2);
    48             point.y = P0.y+(P1.y-P0.y)*j/(cols*2);
    49             pointvector01.push_back(point);
    50         }
    51         for(int j=(0-upex)*2; j<(rows+downex)*2+1; j++){
    52             Point point;
    53             point.x = P1.x+(P2.x-P1.x)*j/(rows*2);
    54             point.y = P1.y+(P2.y-P1.y)*j/(rows*2);
    55             pointvector12.push_back(point);}
    56         for(int j=(0-leftex)*2; j<(cols+rightex)*2+1; j++){
    57             Point point;
    58             point.x = P3.x+(P2.x-P3.x)*j/(cols*2);
    59             point.y = P3.y+(P2.y-P3.y)*j/(cols*2);
    60             pointvector32.push_back(point);
    61         }
    62         for(int j=(0-upex)*2; j<(rows+downex)*2+1; j++){
    63             Point point;
    64             point.x = P0.x+(P3.x-P0.x)*j/(rows*2);
    65             point.y = P0.y+(P3.y-P0.y)*j/(rows*2);
    66             pointvector03.push_back(point);
    67         }
    68         for(int i=0; i<(rows+downex+upex)*2+1; i++) //依次求得交叉点
    69             for(int j=0;j<(cols+rightex+leftex)*2+1;j++){
    70                 if( i%2==1 && j%2==1 ){
    71                     Point point=CrossPoint(pointvector01.at(j),pointvector32.at(j),
    72                                            pointvector03.at(i),pointvector12.at(i));
    73                     pointvector.push_back(point);
    74                 }
    75             }
    76     }
    77     return pointvector;
    78 }

    2.交点计算函数

     1 //计算两直线交点
     2 Point Thread_CameraBelow::CrossPoint(Point P1, Point P2, Point P3, Point P4)
     3 {
     4     Point pt;
     5     double x1=P1.x,y1=P1.y;
     6     double x2=P2.x,y2=P2.y;
     7     double x3=P3.x,y3=P3.y;
     8     double x4=P4.x,y4=P4.y;
     9     double D = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);
    10     if (D == 0){
    11         pt.x=0;
    12         pt.y=0;
    13     }
    14     else{
    15         pt.x = ((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4))/D;
    16         pt.y = ((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4))/D;
    17     }
    18     return pt;
    19 }

    11.小结

    这个QR码识别算法,还有一些有待改进的地方:如只考虑了图像翻转,而没有考虑图像畸变的影响等等

  • 相关阅读:
    【APUE】Chapter15 Interprocess Communication
    【APUE】Chapter14 Advanced I/O
    【APUE】Chapter5 Standard I/O Library
    【APUE】Chapter4 File and Directories
    【APUE】Chapter3 File I/O
    【APUE】Chapter1 UNIX System Overview
    【APUE】Chapter13 Daemon Processes
    【APUE】Chapter10 Signals
    Hive之数据类型
    Hive之内置函数
  • 原文地址:https://www.cnblogs.com/ybqjymy/p/13902042.html
Copyright © 2011-2022 走看看