一维码由一组规则排列的黑色线条、白色线条以及对应的字符组成。对倾斜的(没有严重形变)一维码的角度校正,可以根据其黑白相间、排列规则的特点,计算傅里叶频谱,通过傅里叶频谱中直线的倾斜角度计算空间域图像一维码需校正的角度。
先贴出来待校正的一维码和其傅里叶频谱图:

傅里叶频谱中亮度值代表了频率变化的强弱,直线的方向代表了频率变化的方向。上图傅里叶频谱中最亮的那条线就是与一维码黑白相间条纹相垂直的方向,找到这条线的角度,就可以计算出一维码的校正角度。
校正步骤:
1. 计算图像X,Y方向上梯度图像,并求和,突出图像边缘信息
2. 离散傅里叶变换,画出一维码的频谱图
3. 霍夫变换定位到傅里叶频谱图中直线,获得直线角度
4. 计算一维码需校正角度,通过仿射变换,校正图像
X、Y方向梯度和:

傅里叶频谱:

阈值化,保留频率变化最明显的分量:

Hough直线定位:

一维码角度校正:

Zbar识别:

Code 实现:
#include "highgui/highgui.hpp" #include "imgproc/imgproc.hpp"
int main(int argc,char *argv[]) Mat image,imageGray,imageGuussian; Mat imageSobelX,imageSobelY,imageSobelOut; imageGray=imread(argv[1],0); imshow("Source Image",image); GaussianBlur(imageGray,imageGuussian,Size(3,3),0); //水平和垂直方向灰度图像的梯度和,使用Sobel算子 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("XY方向梯度和",imageSobelOut); Mat srcImg =imageSobelOut; //宽高扩充,非必须,特定的宽高可以提高傅里叶运算效率 int opWidth = getOptimalDFTSize(srcImg.rows); int opHeight = getOptimalDFTSize(srcImg.cols); copyMakeBorder(srcImg, padded, 0, opWidth-srcImg.rows, 0, opHeight-srcImg.cols, BORDER_CONSTANT, Scalar::all(0)); Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F)}; magnitude(planes[0], planes[1], planes[0]); magMat += Scalar::all(1); log(magMat, magMat); //对数变换,方便显示 magMat = magMat(Rect(0, 0, magMat.cols & -2, magMat.rows & -2)); Mat q0(magMat, Rect(0, 0, cx, cy)); Mat q1(magMat, Rect(0, cy, cx, cy)); Mat q2(magMat, Rect(cx, cy, cx, cy)); Mat q3(magMat, Rect(cx, 0, cx, cy)); normalize(magMat, magMat, 0, 1, CV_MINMAX); Mat magImg(magMat.size(), CV_8UC1); magMat.convertTo(magImg,CV_8UC1,255,0); //HoughLines查找傅里叶频谱的直线,该直线跟原图的一维码方向相互垂直 threshold(magImg,magImg,180,255,CV_THRESH_BINARY); float pi180 = (float)CV_PI/180; Mat linImg(magImg.size(),CV_8UC3); HoughLines(magImg,lines,1,pi180,100,0,0); int numLines = lines.size(); for(int l=0; l<numLines; l++) float aa=(theta/CV_PI)*180; 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(linImg,pt1,pt2,Scalar(255,0,0),3,8,0); imshow("Hough直线",linImg); float angelD=180*theta/CV_PI-90; Point center(image.cols/2, image.rows/2); Mat rotMat = getRotationMatrix2D(center,angelD,1.0); Mat imageSource = Mat::ones(image.size(),CV_8UC3); warpAffine(image,imageSource,rotMat,image.size(),1,0,Scalar(255,255,255));//仿射变换校正图像 imshow("角度校正",imageSource); 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, width1 * 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); imageZbar.set_data(NULL,0);
|