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



posted on 2016-08-05 23:11  未雨愁眸  阅读(1334)  评论(0编辑  收藏  举报