一. 条码图像处理过程
图像的大致处理流程如下:
1. 将彩色图转变成灰度图
2. 对灰度图均衡化
3. 将灰度图转换成二值图
4. 对二值图进行腐蚀
5. 识别边界检测轮廓
6. 对每一个轮廓包围的区域进行特征识别,判断是否是条码区域。
7. 对条码区域测量解码。
二.代码解读
bool TestBarCode()
{
InitCodeMap();
IplImage* src = cvLoadImage(strPictureName,0); //装载图像
if( !src )
{
printf("打开文件失败,请重新打开"); //读取失败返回
return false;
}
cvNamedWindow("src",1);
cvShowImage("src",src);
IplImage * dst = cvCreateImage( cvGetSize(src), 8, 1 ); //创建图像
IplImage * dst1 = cvCreateImage( cvGetSize(src), 8, 1 );//创建图像
//进行图像平滑,并转成灰度图像
cvSmooth(src,dst1,CV_BLUR,3, 3);
Process(dst1); //处理的核心在这
cvWaitKey(0); //等待按键
//释放图像分配的内存
cvReleaseImage(&src);
cvReleaseImage(&dst);
cvReleaseImage(&dst1);
cvDestroyWindow("src");
cvDestroyWindow("dest");
cvDestroyWindow("cpImg");
return true;
}
这段代码是处理的主代码,对图片载入并平滑处理。然后调用了Process函数做进一步处理。
//型体分割,获取感兴趣的区域
void Process(IplImage * pImg)
{
//转换成二值图
IplImage * dst = cvCreateImage( cvGetSize(pImg), 8, 1 );
cvThreshold(pImg,dst,100,200,CV_THRESH_BINARY_INV);//将灰度图转成二值图
cvNamedWindow("ThImage",1);
cvShowImage("ThImage",dst);
//膨胀
IplImage * dst2 = cvCreateImage( cvGetSize(pImg), 8, 1 );
IplConvKernel * element = cvCreateStructuringElementEx( 5, 5, 1, 1, CV_SHAPE_ELLIPSE, 0);
cvDilate(dst,dst2,element,3);//图像膨胀
cvNamedWindow("DilateImage",1);
cvShowImage("DilateImage",dst2);
//检测轮廓
CvMemStorage * storage = cvCreateMemStorage(0);
CvSeq * contour = 0;
int n = cvFindContours( dst2, storage, &contour, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
double maxArea = 20;
IplImage* pContourImg = cvCreateImage(cvGetSize(dst2), IPL_DEPTH_8U, 3);
//初始化为黑色
cvZero(pContourImg);
int k = 0;
while(contour)
{
double area=fabs(cvContourArea(contour, CV_WHOLE_SEQ)); //求轮廓包含的面积
if(area > 100) //当面积小于100时的忽略
{
if(k++ < 8)
{
cvDrawContours(pContourImg,contour,cvScalarAll(255),cvScalarAll(0),0,CV_FILLED);
CvRect rect = cvBoundingRect(contour,1); //获取外接矩形
cvRectangleR(pContourImg,rect,CV_RGB(0,255,0)); //绘制外接矩形
CvBox2D box = cvMinAreaRect2(contour,NULL); //求取轮廓最小的外接矩形
IplImage * cpImg = CreateAndCopyBox2D(dst,box);
if(cpImg)
{
if(FutrueFind(cpImg))
{
string str = DecodeImage(cpImg);
cvNamedWindow("cpImg",1);
cvShowImage("cpImg",cpImg);
}
else cvReleaseImage(&cpImg);
}
}
}
contour=contour->h_next;
}
cvReleaseMemStorage(&storage);
}
Process是核心处理代码,首先对图像进行了二值化,这样能去除更多的干扰。二值化是使用了CV_THRESH_BINARY_INV参数,对图像中黑白色反转。这样条码部分呈了白色。然后对其膨胀,膨胀的目的是将条码说在的整个区域编程白色,之后的轮廓识别能将其划分在一个轮廓里。
cvFindContours查找了轮廓,将其保存在contour列表之中。之后就是遍历整个列表,遍历时,首先使用cvMinAreaRect2对每一个轮廓求取其最小外接矩形,他返回一个CvBox2D数据,其中包含有矩形的大小,中心点坐标,还有一个角度。利用这个角度可以可以知道条码型位信息,这个角度就是对裁剪后的感兴趣区域旋转的角度。
CreateAndCopyBox2D对遍历的每个区域进行了裁剪和旋转。这样就得到了型位是正位的区域图片,之后要做的事情就是识别图片特征,判断是否是条码区域,若是就会对图片的解码。
FutrueFind承担了图片特征识别的任务。
//特征识别,找到条码区域
bool FutrueFind(IplImage * pImg)
{
double max = 0;
double min = 0;
vector<double> ds;
HistY(pImg,ds);
double avgY = DataAvg(ds);
ds.clear();
HistX(pImg,ds);
double avgX = DataAvg(ds);
if(avgX/avgY > 5)return true;
return false;
}
特征识别是一个图形处理系统的核心技术。这里使用了简单的特征识别,目前还不是很健壮。在一维条码的x,y方向上强度特征会有所不同,首先统计出X和Y方向的强度值,然后求取相邻强度值差值的绝对值,在对这些值求平均值。这样得到了avgY和avgX,经多个条码观察发现avgX都在avgY的5倍以上。
string DecodeImage(IplImage * pImg)
{
vector<int> ds;
double color = 0;
int h = pImg->height/2;
for(int i = 0; i<pImg->width; i++)
{
double d = cvGet2D(pImg,h,i).val[0];
if(0 == i)color = d;
else
{
if(abs(color -d) > 100)
{
ds.push_back(i);
}
color = d;
}
}
printf("decode size :%d\n",ds.size());
//计算宽度系列
vector<int> wids;
double w = 0;
for(int i = 0; i<ds.size(); i++)
{
double d = ds[i];
if(0== i)w = d;
else
{
wids.push_back(d - w);
w = d;
}
}
wids.pop_back();
printf("width size :%d\n",wids.size());
int min = FindMin(wids);
vector<int> tgs;
DevTh(wids,tgs,min);
string code = Getcode(tgs);
printf("code :%s\n",code.c_str());
return code;
}
DecodeImage函数解码条码,他基于像素为单位对条码宽度测量,再找出最小宽度,之后再将其转换成条码识别的标注序列,和编码表比较获得条码值。