OpenCV仪表数据识别(五):数字分割提取

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ZhtSunday/article/details/52131514

本篇介绍数字的自动分割。将每行的数字单独分割出来才能够一个一个识别。

1.方法

  1. 进行腐蚀操作,去除图片中的杂点。
  2. 膨胀,保证一个数字中数码管相互连接(由于是数码管,有时会出现两管之间不连接的情况)
  3. 使用cvFindContours查找各个数字边缘
  4. 分别建立各个轮廓的轮廓矩
  5. 将每个矩形切割出来,并单独存为一个图像

旋转后的行图片如图,一共有4行,这里就只贴一行了。
RotateRow
分割后的数字图片如图:

Num1(1) Num1(2) Num1(3) Num1(4) Num1(5)

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;

猜你喜欢

转载自blog.csdn.net/ZhtSunday/article/details/52131514