本篇介绍数字的自动分割。将每行的数字单独分割出来才能够一个一个识别。
1.方法
- 进行腐蚀操作,去除图片中的杂点。
- 膨胀,保证一个数字中数码管相互连接(由于是数码管,有时会出现两管之间不连接的情况)
- 使用cvFindContours查找各个数字边缘
- 分别建立各个轮廓的轮廓矩
- 将每个矩形切割出来,并单独存为一个图像
旋转后的行图片如图,一共有4行,这里就只贴一行了。
分割后的数字图片如图:
2.代码
先给出全部函数代码,之后再详细解释。
/**************数字分割子程序**************/
//函数名称:int CutNum(IplImage *RotateRow,int row)
//功能:将每行的数字进行分割
//入口参数:*RotateRow,row
//出口参数:数字个数Num
//生成:数字图像Num(row).1.2.3...
/***************************************/
int CutNum(IplImage *RotateRow,int row)
{
cvThreshold(RotateRow,RotateRow,50,255,CV_THRESH_BINARY);
cvErode(RotateRow,RotateRow,0,1);
cvDilate(RotateRow,RotateRow,0,2);
//对每列图像查找边缘
CvMemStorage *OutlineSto=cvCreateMemStorage();
CvSeq *Outlineseq=NULL;
IplImage *TmpImage=cvCloneImage(RotateRow);
int Num=cvFindContours(TmpImage,OutlineSto,&Outlineseq,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
//确定轮廓顺序
int *Rect_x=new int[Num];
int x=0;
for (CvSeq *c=Outlineseq;c!=NULL;c=c->h_next)
{
CvRect RectBound=cvBoundingRect(c,0);
Rect_x[x]=RectBound.x;
x++;
}
qsort(Rect_x,Num,sizeof(int),cmp);
//分割数字并单独存储
for(CvSeq *c=Outlineseq;c!=NULL;c=c->h_next)
{
CvRect RectBound=cvBoundingRect(c,0);
CvRect CutRect=cvRect(RectBound.x-4,RectBound.y-4,RectBound.width+8,RectBound.height+8);
IplImage *ImgNo=cvCreateImage(cvSize(CutRect.width,CutRect.height),IPL_DEPTH_8U,1);
cvSet(ImgNo,cvScalar(0),0); //将图像填充为黑色
cvSetImageROI(RotateRow,CutRect);
cvCopy(RotateRow,ImgNo);
cvResetImageROI(RotateRow);
int col=0;
for(int i=0;i<Num;i++)
{
if(Rect_x[i]==RectBound.x)
{
col=i;
break;
}
}
//为图像存储路径分配内存
char *SavePath=(char *)malloc(30*sizeof(char));
if (SavePath==NULL)
{
printf("分配内存失败");
exit(1);
}
sprintf(SavePath,"C:\\picture\\biao2\\Num%d(%d).jpg",row,col+1);
//表示第row行,第col+1个
cvSaveImage(SavePath,ImgNo);
free(SavePath);
SavePath=NULL;
cvReleaseImage(&ImgNo);
}
delete Rect_x;
cvReleaseMemStorage(&OutlineSto);
cvReleaseImage(&TmpImage);
return Num;
}
3.解析
(1).腐蚀膨胀
- 首先,由于在分割每行的数字之前要读取行图片,前面已经说过,为了保险,最好重新进行二值化。
另外为了消除图片上的杂点,要进行腐蚀。腐蚀可以理解为向内收缩,图片上散落的杂点会被消除。
void cvErode( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );
src:输入图像.
dst:输出图像.
element:用于腐蚀的结构元素。若为 NULL, 则使用 3×3 长方形的结构元素
iterations:腐蚀的次数为了保证一个数字上的数码管粘连,要进行膨胀操作。
void cvDilate( const CvArr* src, CvArr* dst, IplConvKernel* element=NULL, int iterations=1 );
src:输入图像。
dst:输出图像。
element:结构元素。若为 NULL, 则使用默认的3×3 长方形,锚点在中心的结构元素,进行膨胀运算。
iterations:膨胀的次数
//二值化
cvThreshold(RotateRow,RotateRow,50,255,CV_THRESH_BINARY);
//腐蚀
cvErode(RotateRow,RotateRow,0,1);
//膨胀
cvDilate(RotateRow,RotateRow,0,2);
(2).边缘查找
- 用cvFindContours函数查找边缘。预先需要建立存储轮廓的容器“storage”和用于指向第一个输出轮廓的“Outlineseq”。
int cvFindContours(
CvArr* image,
CvMemStorage* storage,
CvSeq** first_contour,
int header_size=sizeof(CvContour),
int mode=CV_RETR_LIST,
int method=CV_CHAIN_APPROX_SIMPLE,
CvPoint offset=cvPoint(0,0) );
image:8比特单通道的源二值图像。
storage:返回轮廓的容器。
first_contour:输出参数,用于存储指向第一个外接轮廓。
header_size:header序列的尺寸。
如果选择method = CV_CHAIN_CODE, 则header_size >= sizeof(CvChain);
其他,则header_size >= sizeof(CvContour)。
mode:
CV_RETR_EXTERNAL:只检索最外面的轮廓;
CV_RETR_LIST:检索所有的轮廓,并将其放入list中;
CV_RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
CV_RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次。
method:边缘近似方法(除了CV_RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:
CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
CV_CHAIN_APPROX_NONE:将所有的连码点,转换成点。
CV_CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法
的一种。
CV_LINK_RUNS:通过连接水平段的1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。
offset:偏移量
//对每列图像查找边缘
CvMemStorage *OutlineSto=cvCreateMemStorage();
CvSeq *Outlineseq=NULL;
IplImage *TmpImage=cvCloneImage(RotateRow);
int Num=cvFindContours(TmpImage,OutlineSto,&Outlineseq,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
(3).轮廓排序
- 由于在后期识别的时候需要根据数字出现的顺序整合结果,所以保证获取的数字图像顺序与图中一致是很重要的。数字顺序的保证在于数字图片存储的时候按照数字x坐标的大小顺序进行有序命名和存储。首先需要确定各个轮廓外接矩形的x坐标顺序。
- 先获取每个轮廓外接矩形x坐标,再用
qsort
对数组排序。
//确定轮廓顺序
int *Rect_x=new int[Num];
int x=0;
for (CvSeq *c=Outlineseq;c!=NULL;c=c->h_next)
{
CvRect RectBound=cvBoundingRect(c,0);
Rect_x[x]=RectBound.x;
x++;
}
qsort(Rect_x,Num,sizeof(int),cmp);
(4).数字分割并存储
- 在循环中用ImgNo来临时存储从图中拷贝出来的数字图像。
- 每个循环中为数字图像按Rect_x数组中存储的x坐标顺序为数字分配存储路径。数字所在行数也用row做出区分(row为函数输入变量之一)。
//分割数字并单独存储
for(CvSeq *c=Outlineseq;c!=NULL;c=c->h_next)
{
CvRect RectBound=cvBoundingRect(c,0);
//设定拷贝区域的大小为CutRect。为方便识别,比外接矩形长宽各多出8像素
CvRect CutRect=cvRect(RectBound.x-4,RectBound.y-4,RectBound.width+8,RectBound.height+8);
//用ImgNo临时存储
IplImage *ImgNo=cvCreateImage(cvSize(CutRect.width,CutRect.height),IPL_DEPTH_8U,1);
cvSet(ImgNo,cvScalar(0),0); //将图像填充为黑色
//设定感兴趣区域
cvSetImageROI(RotateRow,CutRect);
cvCopy(RotateRow,ImgNo);
cvResetImageROI(RotateRow);
//查找该外接矩形的序号
int col=0;
for(int i=0;i<Num;i++)
{
if(Rect_x[i]==RectBound.x)
{
col=i;
break;
}
}
//为图像存储路径分配内存
char *SavePath=(char *)malloc(30*sizeof(char));
if (SavePath==NULL)
{
printf("分配内存失败");
exit(1);
}
sprintf(SavePath,"C:\\picture\\biao2\\Num%d(%d).jpg",row,col+1);
//表示第row行,第col+1个
cvSaveImage(SavePath,ImgNo);
free(SavePath);
SavePath=NULL;
cvReleaseImage(&ImgNo);
}
- 最后释放,返回每行数字数Num。
delete Rect_x;
cvReleaseMemStorage(&OutlineSto);
cvReleaseImage(&TmpImage);
return Num;