使用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找到条形码区域的矩形边界