1. 程式人生 > >Opencv+Zbar二維碼識別(標準條形碼/二維碼識別)

Opencv+Zbar二維碼識別(標準條形碼/二維碼識別)

使用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找到條形碼區域的矩形邊界