当前位置 博文首页 > 唯有自己强大:opencv——轮廓发现与轮廓(二值图像)分析

    唯有自己强大:opencv——轮廓发现与轮廓(二值图像)分析

    作者:唯有自己强大 时间:2021-05-22 18:37

    引言

    二值图像分析最常见的一个主要方式就是轮廓发现轮廓分析,其中轮廓发现的目的是为轮廓分析做准备,经过轮廓分析我们可以得到轮廓各种有用的属性信息。

    这里顺带提下边缘检测,和轮廓提取的区别:

    边缘检测主要是通过一些手段检测数字图像中明暗变化剧烈(即梯度变化比较大)像素点,偏向于图像中像素点的变化。如canny边缘检测,结果通常保存在和源图片一样尺寸和类型的边缘图中。 
    轮廓检测指检测图像中的对象边界,更偏向于关注上层语义对象。如OpenCV中的findContours()函数, 它会得到每一个轮廓并以点向量方式存储,除此也得到一个图像的拓扑信息,即一个轮廓的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的索引编号。 


    一,轮廓的发现与绘制


     在OpenCV里面利用findContours()函数和drawContours()函数实现这一功能。

    • findContours()函数
    void findContours(
    InputArray          image,
    OutputArrayOfArrays contours,
    OutputArray         hierarchy,
    int                 mode,
    int                 method,
    Point               offset = Point()
                      )

    参数一: image,输入图像、八位单通道的,背景为黑色的二值图像。(一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像)

    参数二:contours,输出轮廓图像。是一个向量,向量的每个元素都是一个轮廓。因此,这个向量的每个元素仍是一个向量。即:

               vector<vector<Point> > contours;

    参数三:hierarchy,输出各个轮廓的继承关系。hierarchy也是一个向量,长度和contours相等,每个元素和contours的元素对应。hierarchy的每个元素是一个包含四个整型数的向量。即:

               vector<Vec4i> hierarchy;

    参数四:mode,检测轮廓的方法。有四种方法:

    1. RETR_EXTERNAL:只检测外轮廓。忽略轮廓内部的洞。
    2. RETR_LIST:检测所有轮廓,但不建立继承(包含)关系。
    3. RETR_TREE:检测所有轮廓,并且建立所有的继承(包含)关系。
    4. RETR_CCOMP:检测所有轮廓,但是仅仅建立两层包含关系。

    参数五:method,每个轮廓的编码信息。也有四种(常用前两种)

    1. CHAIN_APPROX_NONE:把轮廓上所有的点存储。
    2. CHAIN_APPROX_SIMPLE:只存储轮廓上的拐点。
    3. CHAIN_APPROX_TC89_L1,CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法

    参数六: Point,偏移量。默认为0

    注意该函数将白色区域当作前景物体。所以findContours()函数是黑色背景下找白色轮廓。(重要!!!)

    • drawContours()函数
    drawContours(
          InputOutputArray  binImg, // 输出图像
          OutputArrayOfArrays  contours,//  全部发现的轮廓对象
          Int contourIdx// 轮廓索引号,-1表示绘制所有轮廓
          const Scalar & color,// 绘制时候颜色
          int  thickness,// 绘制线宽,-1表示填充轮廓内部
          int  lineType,// 线的类型LINE_8
          InputArray hierarchy,// 拓扑结构图
          int maxlevel,// 最大层数, 0只绘制当前的,1表示绘制绘制当前及其内嵌的轮廓
          Point offset = Point()// 轮廓位移,可选

    二,轮廓分析(二值图像分析)


     在得到图像的轮廓以后,我们就可以进行轮廓分析。经过轮廓分析我们可以得到轮廓各种有用的属性信息、常见的如下: 

    • ??计算轮廓面积 : 

    contourArea(contour, oriented = False)  //计算轮廓的面积
    
    参数说明:contour为输入的单个轮廓值;oriented:轮廓方向,默认值false。
    如果为true,该函数返回一个带符号的面积,其正负取决于轮廓的方向(顺时针还是逆时针)。
    如果是默认值false,则面积以绝对值的形式返回.
    根据这个特性可以根据面积的符号来确定轮廓的位置。
    • ??计算轮廓周长:
    arcLength(contour, closed)   //  计算轮廓的周长
    
    参数说明:contour为输入的单个轮廓值,closed表示轮廓是否封闭(true为封闭,false为不封闭)
    • ??计算几何矩与中心距: moments()
      Moments m = moments(contours[t]); //获取轮廓的距
    //计算轮廓质心
        double cx = m.m10 / m.m00;
        double cy = m.m01 / m.m00;
    • ??轮廓的外接矩形:

    轮廓的外接矩形有两种,如下图,绿色的叫外接矩形boundingRect(),表示不考虑旋转并且能包含整个轮廓的矩形。蓝色的叫最小外接矩形minAreaRect(),考虑了旋转

    1??外接矩形Rect boundingRect(InputArray points)

    输入参数points可以一系列点的集合,对轮廓来说就是该轮廓的点集 返回结果是一个正矩形,包含以下信息:

    • 矩形左上角的坐标(rect.x,rect.y)
    • 矩形的宽和高(rect.width,rect.height)
    Rect  rect = boundingRect(Mat(contours[i]));//获取轮廓外接正矩形
    rectangle(src, rect, (0, 0, 255), 2, 8, 0);

    2??最小外接矩形minAreaRect()

    输入参数points可以一系列点的集合,对轮廓来说就是该轮廓的点集 返回结果是一个旋转矩形,包含下面的信息:

    • 旋转矩形的中心坐标(rect.center)
    • 旋转矩形的宽和高(rect.size.width,rect.size.height)
    • 旋转矩形的角度(rect.angle)
    RotatedRect rect = minAreaRect(contours[i]);//获取轮廓最小外接矩形
    Point2f P[4];
    rect.points(P);//获取四顶点坐标
    for (int j = 0; j <= 3; j++)
    {
        line(src, P[j], P[(j + 1) % 4], Scalar(0,0,255), 1);//依次连线
    }
    • ??最小外接圆/拟合圆:minEnclosingCircle()
    void minEnclosingCircle(InputArray points, Point2f& center, float& radius);
    points,输入的二维点集,可以是 vector 或 Mat 类型。
    center,圆的输出圆心。
    radius,圆的输出半径。
     
    例如:
        findContours(bin_img, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
        //寻找包裹轮廓的最小圆
        vector<Point2f>centers(contours.size());//圆心个数
        vector<float>radius(contours.size());//半径个数
        for (int i = 0; i < contours.size(); i++) 
    {
    //寻找并绘制最小圆 minEnclosingCircle(contours[i], centers[i], radius[i]); circle(src, centers[i], radius[i], scalar(0,0,255), 2); }
    • ??拟合椭圆:fitEllipse() 
    RotatedRect fitEllipse(InputArray points);
    //唯一一个参数是输入的二维点集,可以是 vector 或 Mat 类型。
    例如:
    // 轮廓发现与绘制
    vector<vector<Point>> contours;
    findContours(binary, contours,  RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
    for (size_t t = 0; t < contours.size(); t++)
     {
    // 拟合椭圆
    RotatedRect rrt = fitEllipse(contours[t]);
    ellipse(src, rrt, Scalar(0, 0, 255), 2, 8);
    }
    imshow("contours", src);
    • ??拟合直线:fitLine()

    OpenCV中直线拟合正是基于最小二乘法实现的。其函数将计算出的直线信息存放在 line 中,(为Vec4f 类型)。line[0]、line[1] 存放的是直线的方向向量,float cosθ = oneline[0]; float sinθ = oneline[1]。line[2]、line[3] 存放的是直线上一个点的坐标。

    实现直线拟合的API如下:

    void   fitLine(
        InputArray points,  //输入待拟合的二维点的数组或vector
        OutputArray line,   //输出直线,Vec4f (2d)或Vec6f (3d)的vector
        int distType,          //距离类型
        double param,      //距离参数(一般设为0)
        double reps,        //径向的精度参数(一般设为0.01)
        double aeps        //角度精度参数(一般设为0.01)
                  )
    distType(距离类型)有六种参数:(DIST_L2就是最小二乘法)

    opencv实现:

    Mat src = imread("D:/opencv练习图片/直线拟合.png");
        imshow("原图片", src);
        // 去噪声与二值化
        Mat dst, gray, binary;
        Canny(src, binary, 80, 160, 3, false);
        imshow("canny二值化", binary);
        Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
        dilate(binary, binary, k);
        // 轮廓发现与绘制
        vector<vector<Point>> contours;
        findContours(binary, contours,  RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
        for (size_t t = 0; t < contours.size(); t++) {
            // 最大外接轮廓
            Rect rect = boundingRect(contours[t]);
            int m = max(rect.width, rect.height);
            if (m < 30) continue;
            // 直线拟合
            Vec4f oneline;
            fitLine(contours[t], oneline, DIST_L1, 0, 0.01, 0.01);
            float cosθ = oneline[0];
            float sinθ = oneline[1];
            float x0 = oneline[2];
            float y0 = oneline[3];
    
            // 直线参数斜率k与截矩b
            float k = sinθ / cosθ; //求tanθ,也就是斜率
            float b = y0 - k * x0;
    
            float x = 0;
            float y = k * x + b;
            line(src, Point(x0, y0), Point(x, y), Scalar(0, 0, 255), 2, 8, 0);
        }
        imshow("结果", src);

    • ??轮廓的凸包:convexHull()

    凸包(Convex Hull)是一个计算几何(图形学)中常见的概念。简单来说,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它是能包含点集中所有点的。

    理解物体形状或轮廓的一种比较有用的方法便是计算一个物体的凸包,然后计算其凸缺陷(convexity defects)。

    convexHull (   InputArray  points,         /输入的二维点集,Mat类型数据即可
                   OutputArray     hull,       //输出参数,用于输出找到的凸包
                   bool   clockwise = false,   //操作方向,为True时,输出的凸包为顺时针方向,否则为逆时针方向
                   bool  returnPoints = true   //凸包的返回形式,默认值为true,此时返回点坐标的形式,否则返回对应点的索引值
                )

    凸包检测原理:

     opencv实现:

        Mat src = imread("D:/opencv练习图片/凸包检测.jpg");
        imshow("原图片", src);
        // 二值化
        Mat dst, gray, binary;
        cvtColor(src, gray, COLOR_BGR2GRAY);
        threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
        // 形态学去除干扰
        Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
        morphologyEx(binary, binary, MORPH_OPEN, k);
        imshow("binary", binary);
        // 轮廓发现与绘制
        vector<vector<Point>> contours;
        findContours(binary, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point());
        for (size_t t = 0; t < contours.size(); t++) {
            vector<Point> hull;
            convexHull(contours[t], hull);//凸包检测
            bool isHull = isContourConvex(contours[t]);//判断轮廓是否为凸包
            printf("test convex of the contours %s", isHull ? "Y" : "N");
            int len = hull.size();
            //绘制凸包
            for (int i = 0; i < hull.size(); i++) {
                circle(src, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);//
                line(src, hull[i%len], hull[(i + 1) % len], Scalar(0, 0, 255), 2, 8, 0);//线
            }
        }
        imshow("凸包检测", src);

    • ??多边形逼近-逼近真实形状:approxPolyDP()

    轮廓的多边形逼近指的是:使用多边形来近似表示一个轮廓。 多边形逼近的目的是为了减少轮廓的顶点数目。 多边形逼近的结果依然是一个轮廓,只是这个轮廓相对要粗旷一些。 

    void approxPolyDP(        InputArray curve,        //输入曲线,一般是由图像的轮廓点组成的点集
                              OutputArray approxCurve, //表示输出的逼近后多边形的点集(类型与输入曲线的类型相同)
                              double epsilon,          //轮廓逼近的顶点距离真实轮廓曲线的最大距离,该值越小表示越逼近真实轮廓
                              bool closed              //表示输出的多边形是否封闭
                      )

     opencv实现:

        Mat src = imread("D:/opencv练习图片/多边形逼近.png");
        Mat dstImage_3(src.size(), CV_8UC3, Scalar(0));
        Mat dstImage_6(src.size(), CV_8UC3, Scalar(0));
        Mat dstImage_10(src.size(), CV_8UC3, Scalar(0));
        imshow("原图片", src);
        // 二值化
        Mat dst, gray, binary;
        cvtColor(src, gray, COLOR_BGR2GRAY);
        threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
        // 形态学去除干扰
        Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
        morphologyEx(binary, binary, MORPH_OPEN, k);
        imshow("binary