http://blog.csdn.net/dcrmg/article/details/52132313
使用OpenCV+Zbar組合可以很容易的識別圖片中的二維碼,特別是標準的二維碼,這裏標準指的是二維碼成像清晰,圖片中二維碼的空間佔比在40%~100%之間,這樣標準的圖片,Zbar識別起來很容易,不需要Opencv額外的處理。
下邊這個例程演示兩者配合對條形碼和二維碼的識別:
- #include "zbar.h"
- #include "cv.h"
- #include "highgui.h"
- #include <iostream>
- using namespace std;
- using namespace zbar; //添加zbar名稱空間
- using namespace cv;
- int main(int argc,char*argv[])
- {
- ImageScanner scanner;
- scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
- Mat image = imread(argv[1]);
- Mat imageGray;
- cvtColor(image,imageGray,CV_RGB2GRAY);
- int width = imageGray.cols;
- int height = imageGray.rows;
- uchar *raw = (uchar *)imageGray.data;
- Image imageZbar(width, height, "Y800", raw, width * height);
- scanner.scan(imageZbar); //掃描條碼
- Image::SymbolIterator symbol = imageZbar.symbol_begin();
- if(imageZbar.symbol_begin()==imageZbar.symbol_end())
- {
- cout<<"查詢條碼失敗,請檢查圖片!"<<endl;
- }
- for(;symbol != imageZbar.symbol_end();++symbol)
- {
- cout<<"類型:"<<endl<<symbol->get_type_name()<<endl<<endl;
- cout<<"條碼:"<<endl<<symbol->get_data()<<endl<<endl;
- }
- imshow("Source Image",image);
- waitKey();
- imageZbar.set_data(NULL,0);
- return 0;
- }
二維碼:
這樣“標準的”二維碼是Zbar非常拿手的,能準確快速的檢測出來,包括在條形碼外有部分其他信息的,也是小菜一碟:
Zbar很省心,我們還是可以爲它做點什麼的,比如在一些情況下,需要把條形碼裁剪出來,這就涉及到條形碼位置的定位,這篇文章準備記錄一下如何定位條形碼,在定位之後再把裁剪出來的條形碼區域丟給Zbar識別讀碼。
方法一. 水平、垂直方向投影
- #include "zbar.h"
- #include "cv.h"
- #include "highgui.h"
- #include <iostream>
- using namespace std;
- using namespace zbar; //添加zbar名稱空間
- using namespace cv;
- //***********************************************
- // 函數通過水平和垂直方向投影,找到兩個方向上投影的交叉矩形,定位到條形碼/二維碼
- // int threshodValue 投影的最少像素單位
- // int binaryzationValue 原圖像閾值分割值
- //***********************************************
- Rect DrawXYProjection(const Mat image,Mat &imageOut,const int threshodValue,const int binaryzationValue);
- int main(int argc,char*argv[])
- {
- Mat image = imread(argv[1]);
- Mat imageCopy=image.clone();
- Mat imageGray,imagOut;
- cvtColor(image,imageGray,CV_RGB2GRAY);
- Rect rect(0,0,0,0);
- rect= DrawXYProjection(image,imagOut,image.rows/10,100);
- Mat roi=image(rect);
- //畫出條形碼的矩形框
- rectangle(imageCopy,Point(rect.x,rect.y),Point(rect.x+rect.width,rect.y+rect.height),Scalar(0,0,255),2);
- imshow("Source Image",image);
- imshow("水平垂直投影",imagOut);
- imshow("Output Image",roi);
- imshow("Source Image Rect",imageCopy);
- waitKey();
- return 0;
- }
- Rect DrawXYProjection(const Mat image,Mat &imageOut,const int threshodValue,const int binaryzationValue)
- {
- Mat img=image.clone();
- if(img.channels()>1)
- {
- cvtColor(img,img,CV_RGB2GRAY);
- }
- Mat out(img.size(),img.type(),Scalar(255));
- imageOut=out;
- //對每一個傳入的圖片做灰度歸一化,以便使用同一套閾值參數
- normalize(img,img,0,255,NORM_MINMAX);
- vector<int> vectorVertical(img.cols,0);
- for(int i=0;i<img.cols;i++)
- {
- for(int j=0;j<img.rows;j++)
- {
- if(img.at<uchar>(j,i)<binaryzationValue)
- {
- vectorVertical[i]++;
- }
- }
- }
- //列值歸一化
- int high=img.rows/6;
- normalize(vectorVertical,vectorVertical,0,high,NORM_MINMAX);
- for(int i=0;i<img.cols;i++)
- {
- for(int j=0;j<img.rows;j++)
- {
- if(vectorVertical[i]>threshodValue)
- {
- line(imageOut,Point(i,img.rows),Point(i,img.rows-vectorVertical[i]),Scalar(0));
- }
- }
- }
- //水平投影
- vector<int> vectorHorizontal(img.rows,0);
- for(int i=0;i<img.rows;i++)
- {
- for(int j=0;j<img.cols;j++)
- {
- if(img.at<uchar>(i,j)<binaryzationValue)
- {
- vectorHorizontal[i]++;
- }
- }
- }
- normalize(vectorHorizontal,vectorHorizontal,0,high,NORM_MINMAX);
- for(int i=0;i<img.rows;i++)
- {
- for(int j=0;j<img.cols;j++)
- {
- if(vectorHorizontal[i]>threshodValue)
- {
- line(imageOut,Point(img.cols-vectorHorizontal[i],i),Point(img.cols,i),Scalar(0));
- }
- }
- }
- //找到投影四個角點座標
- vector<int>::iterator beginV=vectorVertical.begin();
- vector<int>::iterator beginH=vectorHorizontal.begin();
- vector<int>::iterator endV=vectorVertical.end()-1;
- vector<int>::iterator endH=vectorHorizontal.end()-1;
- int widthV=0;
- int widthH=0;
- int highV=0;
- int highH=0;
- while(*beginV<threshodValue)
- {
- beginV++;
- widthV++;
- }
- while(*endV<threshodValue)
- {
- endV--;
- widthH++;
- }
- while(*beginH<threshodValue)
- {
- beginH++;
- highV++;
- }
- while(*endH<threshodValue)
- {
- endH--;
- highH++;
- }
- //投影矩形
- Rect rect(widthV,highV,img.cols-widthH-widthV,img.rows-highH-highV);
- return rect;
- }
通過圖像在水平和垂直方向上的投影,按照一定的閾值,找到二維碼所在位置,剪切出來用於下一步Zbar條碼識別。當然這個方法只能識別出背景簡單的圖片中的二維碼。
條形碼效果:
水平、垂直投影
檢出條形碼區域
二維碼效果:
方法二.梯度運算
- #include "core/core.hpp"
- #include "highgui/highgui.hpp"
- #include "imgproc/imgproc.hpp"
- using namespace cv;
- int main(int argc,char *argv[])
- {
- Mat image,imageGray,imageGuussian;
- Mat imageSobelX,imageSobelY,imageSobelOut;
- image=imread(argv[1]);
- //1. 原圖像大小調整,提高運算效率
- resize(image,image,Size(500,300));
- imshow("1.原圖像",image);
- //2. 轉化爲灰度圖
- cvtColor(image,imageGray,CV_RGB2GRAY);
- imshow("2.灰度圖",imageGray);
- //3. 高斯平滑濾波
- GaussianBlur(imageGray,imageGuussian,Size(3,3),0);
- imshow("3.高斯平衡濾波",imageGuussian);
- //4.求得水平和垂直方向灰度圖像的梯度差,使用Sobel算子
- Mat imageX16S,imageY16S;
- Sobel(imageGuussian,imageX16S,CV_16S,1,0,3,1,0,4);
- Sobel(imageGuussian,imageY16S,CV_16S,0,1,3,1,0,4);
- convertScaleAbs(imageX16S,imageSobelX,1,0);
- convertScaleAbs(imageY16S,imageSobelY,1,0);
- imageSobelOut=imageSobelX-imageSobelY;
- imshow("4.X方向梯度",imageSobelX);
- imshow("4.Y方向梯度",imageSobelY);
- imshow("4.XY方向梯度差",imageSobelOut);
- //5.均值濾波,消除高頻噪聲
- blur(imageSobelOut,imageSobelOut,Size(3,3));
- imshow("5.均值濾波",imageSobelOut);
- //6.二值化
- Mat imageSobleOutThreshold;
- threshold(imageSobelOut,imageSobleOutThreshold,180,255,CV_THRESH_BINARY);
- imshow("6.二值化",imageSobleOutThreshold);
- //7.閉運算,填充條形碼間隙
- Mat element=getStructuringElement(0,Size(7,7));
- morphologyEx(imageSobleOutThreshold,imageSobleOutThreshold,MORPH_CLOSE,element);
- imshow("7.閉運算",imageSobleOutThreshold);
- //8. 腐蝕,去除孤立的點
- erode(imageSobleOutThreshold,imageSobleOutThreshold,element);
- imshow("8.腐蝕",imageSobleOutThreshold);
- //9. 膨脹,填充條形碼間空隙,根據核的大小,有可能需要2~3次膨脹操作
- dilate(imageSobleOutThreshold,imageSobleOutThreshold,element);
- dilate(imageSobleOutThreshold,imageSobleOutThreshold,element);
- dilate(imageSobleOutThreshold,imageSobleOutThreshold,element);
- imshow("9.膨脹",imageSobleOutThreshold);
- vector<vector<Point>> contours;
- vector<Vec4i> hiera;
- //10.通過findContours找到條形碼區域的矩形邊界
- findContours(imageSobleOutThreshold,contours,hiera,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);
- for(int i=0;i<contours.size();i++)
- {
- Rect rect=boundingRect((Mat)contours[i]);
- rectangle(image,rect,Scalar(255),2);
- }
- imshow("10.找出二維碼矩形區域",image);
- waitKey();
- }
原圖像
平滑濾波
水平和垂直方向灰度圖像的梯度差
閉運算、腐蝕、膨脹後通過findContours找到條形碼區域的矩形邊界
二維碼:
原圖:
平衡濾波
梯度和
閉運算、腐蝕、膨脹後通過findContours找到條形碼區域的矩形邊界
二維碼校正:
二維碼和車牌識別基本都會涉及到圖像的校正,主要是形變和傾斜角度的校正,一種二維碼的畸變如下圖:
這個碼用微信掃了一下,識別不出來,但是用Zbar還是可以準確識別的~~。
這裏介紹一種二維碼校正方法,通過定位二維碼的4個頂點,利用仿射變換校正。基本思路:濾波->二值化->膨脹(腐蝕)操作->形態學邊界->尋找直線->定位交點->仿射變換校正->Zbar識別。
濾波、二值化:
腐蝕操作:
形態學邊界:
尋找直線:
角點定位:
仿射變換校正:
Zbar識別:
Code實現:
- #include "zbar.h"
- #include "cv.h"
- #include "highgui.h"
- #include <iostream>
- using namespace std;
- using namespace zbar; //添加zbar名稱空間
- using namespace cv;
- int main(int argc,char*argv[])
- {
- Mat imageSource=imread(argv[1],0);
- Mat image;
- imageSource.copyTo(image);
- GaussianBlur(image,image,Size(3,3),0); //濾波
- threshold(image,image,100,255,CV_THRESH_BINARY); //二值化
- imshow("二值化",image);
- Mat element=getStructuringElement(2,Size(7,7)); //膨脹腐蝕核
- //morphologyEx(image,image,MORPH_OPEN,element);
- for(int i=0;i<10;i++)
- {
- erode(image,image,element);
- i++;
- }
- imshow("腐蝕s",image);
- Mat image1;
- erode(image,image1,element);
- image1=image-image1;
- imshow("邊界",image1);
- //尋找直線 邊界定位也可以用findContours實現
- vector<Vec2f>lines;
- HoughLines(image1,lines,1,CV_PI/150,250,0,0);
- Mat DrawLine=Mat::zeros(image1.size(),CV_8UC1);
- for(int i=0;i<lines.size();i++)
- {
- float rho=lines[i][0];
- float theta=lines[i][1];
- Point pt1,pt2;
- double a=cos(theta),b=sin(theta);
- double x0=a*rho,y0=b*rho;
- pt1.x=cvRound(x0+1000*(-b));
- pt1.y=cvRound(y0+1000*a);
- pt2.x=cvRound(x0-1000*(-b));
- pt2.y=cvRound(y0-1000*a);
- line(DrawLine,pt1,pt2,Scalar(255),1,CV_AA);
- }
- imshow("直線",DrawLine);
- Point2f P1[4];
- Point2f P2[4];
- vector<Point2f>corners;
- goodFeaturesToTrack(DrawLine,corners,4,0.1,10,Mat()); //角點檢測
- for(int i=0;i<corners.size();i++)
- {
- circle(DrawLine,corners[i],3,Scalar(255),3);
- P1[i]=corners[i];
- }
- imshow("交點",DrawLine);
- int width=P1[1].x-P1[0].x;
- int hight=P1[2].y-P1[0].y;
- P2[0]=P1[0];
- P2[1]=Point2f(P2[0].x+width,P2[0].y);
- P2[2]=Point2f(P2[0].x,P2[1].y+hight);
- P2[3]=Point2f(P2[1].x,P2[2].y);
- Mat elementTransf;
- elementTransf= getAffineTransform(P1,P2);
- warpAffine(imageSource,imageSource,elementTransf,imageSource.size(),1,0,Scalar(255));
- imshow("校正",imageSource);
- //Zbar二維碼識別
- ImageScanner scanner;
- scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
- int width1 = imageSource.cols;
- int height1 = imageSource.rows;
- uchar *raw = (uchar *)imageSource.data;
- Image imageZbar(width1, height1, "Y800", raw, width * height1);
- scanner.scan(imageZbar); //掃描條碼
- Image::SymbolIterator symbol = imageZbar.symbol_begin();
- if(imageZbar.symbol_begin()==imageZbar.symbol_end())
- {
- cout<<"查詢條碼失敗,請檢查圖片!"<<endl;
- }
- for(;symbol != imageZbar.symbol_end();++symbol)
- {
- cout<<"類型:"<<endl<<symbol->get_type_name()<<endl<<endl;
- cout<<"條碼:"<<endl<<symbol->get_data()<<endl<<endl;
- }
- namedWindow("Source Window",0);
- imshow("Source Window",imageSource);
- waitKey();
- imageZbar.set_data(NULL,0);
- return 0;
- }