• 基础学习笔记之opencv(22):learning OpenCV书中一个连通域处理函数


      

      前言

      在图像处理过程中,经常会遇到这样一部分图像,图像的整体部分如果人来看的话一眼就能看出,但是它的内部由于有各种小缺口,导致断开了,这样在计算机“眼”里就被认为是断开的,为了使图像达到适应人眼的感觉,需要将这些缺口和断开的口给连接上去,这就需要用到计算机图形学中的连通域处理技术。本文给出一个简单的连通域处理函数,当然这个函数是来自OpenCV著名教程Learning OpenCV中,只不过它的接口是基于c版本的OpenCV,而到目前为止,基于C++接口的OpenCV已经是主流,所以我将其接口改成了c++版的,但是其内部一些代码基本没有动它。

      开发环境:OpenCV2.4.3+QtCreator2.5.1

      实验基础

      首先来看这个连通域处理函数的形式:

      void ConnectedComponents(Mat &mask_process,  int poly1_hull0,  float perimScale,  int number = 0,  Rect &bounding_box = Rect(),  Point &contour_centers = Point(-1, -1));

      参数mask表示的是需要进行连通域处理二值图像。

      参数poly1_hull0表示轮廓边缘是否采用多边形拟合,如果该参数为1,则表示采用多边形拟合,否则采用凸包拟合。

      参数perimScale是用来将那些小的轮廓去掉,那些小的轮廓时指它的周长小于(mask长+宽)/perimScale。当然你在其内部代码也可以该为面积来判断。

      参数num表示实际需要处理最多的轮廓的个数(如果输入的mask有多个轮廓的话),这里的处理是指计算出这些轮廓的外接矩形和中心点。默认值为0,表示函数内部不需要处理这些外接矩形和中心点。

      参数bbs表示的是处理完后对应轮廓的外接矩形,默认值为Rect(),表示不需要返回这些外接矩形。

      参数centers表示处理完后对应轮廓的中心点坐标,默认值为Point(-1, -1),表示不需要返回这些中心点。

      C/C++知识点总结:

      如果一些函数需要默认值的话,可以直接在函数定义的时候指定,该指定并不一定是具体的某个值,也可以是空值等等。另外在函数内部实现时,有时候要注意默认值的特殊性。

      实验结果

      所需处理原始图像的灰度图:

      

      其对应的mask图像:

      

      使用多项式拟合的连通域处理后图像:

      

      使用凸包集拟合的连通域处理后的图像:

      

      实验代码及注释(附录有工程code下载地址):

      main.cpp:

      

    #include <iostream>
    #include <opencv.hpp>
    
    
    using namespace cv;
    using namespace std;
    
    //Just some convienience macros
    #define CV_CVX_WHITE    CV_RGB(0xff,0xff,0xff)
    #define CV_CVX_BLACK    CV_RGB(0x00,0x00,0x00)
    
    
    void ConnectedComponents(Mat &mask_process, int poly1_hull0, float perimScale, int number = 0,
                             Rect &bounding_box = Rect(), Point &contour_centers = Point(-1, -1))
    {
        /*下面4句代码是为了兼容原函数接口,即内部使用的是c风格,但是其接口是c++风格的*/
        IplImage *mask = &mask_process.operator IplImage();
        int *num = &number;
        CvRect *bbs = &bounding_box.operator CvRect();
        CvPoint *centers = &contour_centers.operator CvPoint();
        static CvMemStorage*    mem_storage    = NULL;
        static CvSeq*            contours    = NULL;
        //CLEAN UP RAW MASK
            //开运算作用:平滑轮廓,去掉细节,断开缺口
            cvMorphologyEx( mask, mask, NULL, NULL, CV_MOP_OPEN, 1 );//对输入mask进行开操作,CVCLOSE_ITR为开操作的次数,输出为mask图像
            //闭运算作用:平滑轮廓,连接缺口
            cvMorphologyEx( mask, mask, NULL, NULL, CV_MOP_CLOSE, 1 );//对输入mask进行闭操作,CVCLOSE_ITR为闭操作的次数,输出为mask图像
        //FIND CONTOURS AROUND ONLY BIGGER REGIONS
            if( mem_storage==NULL ) mem_storage = cvCreateMemStorage(0);
                else cvClearMemStorage(mem_storage);
            //CV_RETR_EXTERNAL=0是在types_c.h中定义的,CV_CHAIN_APPROX_SIMPLE=2也是在该文件中定义的
            CvContourScanner scanner = cvStartFindContours(mask,mem_storage,sizeof(CvContour),CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);
            CvSeq* c;
            int numCont = 0;
            //该while内部只针对比较大的轮廓曲线进行替换处理
            while( (c = cvFindNextContour( scanner )) != NULL )
            {
                double len = cvContourPerimeter( c );
                double q = (mask->height + mask->width) /perimScale;   //calculate perimeter len threshold
                if( len < q ) //Get rid of blob if it's perimeter is too small
                {
                    cvSubstituteContour( scanner, NULL );    //用NULL代替原来的那个轮廓
                }
                else //Smooth it's edges if it's large enough
                {
                    CvSeq* c_new;
                    if(poly1_hull0) //Polygonal approximation of the segmentation
                        c_new = cvApproxPoly(c,sizeof(CvContour),mem_storage,CV_POLY_APPROX_DP, 2,0);
                    else //Convex Hull of the segmentation
                        c_new = cvConvexHull2(c,mem_storage,CV_CLOCKWISE,1);
                    cvSubstituteContour( scanner, c_new ); //最开始的轮廓用凸包或者多项式拟合曲线替换
                    numCont++;
                }
            }
            contours = cvEndFindContours( &scanner );    //结束轮廓查找操作
        // PAINT THE FOUND REGIONS BACK INTO THE IMAGE
            cvZero( mask );
            IplImage *maskTemp;
            //CALC CENTER OF MASS AND OR BOUNDING RECTANGLES
            if(*num != 0)
            {
                int N = *num, numFilled = 0, i=0;
                CvMoments moments;
                double M00, M01, M10;
                maskTemp = cvCloneImage(mask);
                for(i=0, c=contours; c != NULL; c = c->h_next,i++ )        //h_next为轮廓序列中的下一个轮廓
                {
                    if(i < N) //Only process up to *num of them
                    {
                        //CV_CVX_WHITE在本程序中是白色的意思
                        cvDrawContours(maskTemp,c,CV_CVX_WHITE, CV_CVX_WHITE,-1,CV_FILLED,8);
                        //Find the center of each contour
                        if(centers != &cvPoint(-1, -1))
                        {
                            cvMoments(maskTemp,&moments,1);    //计算mask图像的最高达3阶的矩
                            M00 = cvGetSpatialMoment(&moments,0,0); //提取x的0次和y的0次矩
                            M10 = cvGetSpatialMoment(&moments,1,0); //提取x的1次和y的0次矩
                            M01 = cvGetSpatialMoment(&moments,0,1); //提取x的0次和y的1次矩
                            centers[i].x = (int)(M10/M00);    //利用矩的结果求出轮廓的中心点坐标
                            centers[i].y = (int)(M01/M00);
                        }
                        //Bounding rectangles around blobs
                        if(bbs != &CvRect())
                        {
                            bbs[i] = cvBoundingRect(c); //算出轮廓c的外接矩形
                        }
                        cvZero(maskTemp);
                        numFilled++;
                    }
                    //Draw filled contours into mask
                    cvDrawContours(mask,c,CV_CVX_WHITE,CV_CVX_WHITE,-1,CV_FILLED,8); //draw to central mask
                } //end looping over contours
                *num = numFilled;
                cvReleaseImage( &maskTemp);
            }
            //ELSE JUST DRAW PROCESSED CONTOURS INTO THE MASK
            else
            {
                for( c=contours; c != NULL; c = c->h_next )
                {
                    cvDrawContours(mask,c,CV_CVX_WHITE, CV_CVX_BLACK,-1,CV_FILLED,8);
                }
            }
    }
    
    int main()
    {
        Mat src, mask;
        src = imread("test.png", 0);    //以灰度图像读入
        imshow("src", src);
    
        mask = src > 0;     //转换为二值图像
        imshow("mask", mask);
    
        ConnectedComponents(mask, 1, 8.0, 1, Rect(), Point(-1, -1));    //采用多边形拟合处理
        imshow("out1", mask);
    
        ConnectedComponents(mask, 0, 8.0, 1, Rect(), Point(-1, -1));    //c采用凸包进行处理
        imshow("out2", mask);
        waitKey(0);
    
        return 0;
    }

      实验总结: 通过该连通域处理,能够达到一定效果。

      参考资料:

      Bradski, G. and A. Kaehler (2008). Learning OpenCV: Computer vision with the OpenCV library, O'Reilly Media.

      附录:实验工程code下载地址

  • 相关阅读:
    c++中的复合类型
    c++获取随机数
    静态成员数据和静态成员函数
    c++之window.h
    算法之美---100幅由程序生成的图像,总有一幅让你感到惊艳[上]
    分形的程序实现
    使用异或运算交换两个任意类型变量
    游戏中角色曲线行走的算法
    算法之美---由计算机生成的图像
    数学图形之肾形
  • 原文地址:https://www.cnblogs.com/tornadomeet/p/2813534.html
Copyright © 2020-2023  润新知