OpenCV探索之路(十一):轮廓查找和多边形包围轮廓

Canny一类的边缘检测算法可以根据像素之间的差异,检测出轮廓边界的像素,但它没有将轮廓作为一个整体。所以要将轮廓提起出来,就必须将这些边缘像素组装成轮廓。

OpenCV中有一个很强大的函数,它可以从二值图像中找到轮廓:findContours函数。

有时我们还需要把找到的轮廓画出来,那就要用到函数drawContours了。

findContours函数和那就要用到函数drawContours函数一般配套使用。

#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void main() 
{
    Mat original = imread("test5.jpg");
    namedWindow("My original");
    imshow("My original", original);
    Mat gray = original;
    cvtColor(gray, gray, CV_RGB2GRAY);//灰度化

    int thresh_size = (100 / 4) * 2 + 1; //自适应二值化阈值
    adaptiveThreshold(gray, gray, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, thresh_size, thresh_size / 3);
    //morphologyEx(gray, gray, MORPH_OPEN, Mat());//形态学开运算去噪点

    imshow("gray", gray);

    vector<vector<Point> > contours;
    findContours(gray, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //找轮廓
    vector<vector<Point>> contours1;
    for (int i = 0; i < contours.size(); ++i)
    {
        contours1.push_back(contours[i]);
    }

    Mat hole(gray.size(), CV_8U, Scalar(0)); //遮罩图层
    drawContours(hole, contours1, -1, Scalar(255), CV_FILLED); //在遮罩图层上,用白色像素填充轮廓,得到MASK
    namedWindow("My hole");
    imshow("My hole", hole);
    Mat crop(original.rows, original.cols, CV_8UC3);
    original.copyTo(crop, hole);//将原图像拷贝进遮罩图层
    namedWindow("My warpPerspective");
    imshow("My warpPerspective", crop);
    waitKey(0);
}

右下角的图就是提取出来的轮廓图,真的是非常精准。不过精准只是因为原图的形状比较简单,如果遇到复杂图片,那情况就不太乐观了。
1093303-20170522185131710-178501457.png

1093303-20170522185146851-1495786172.png

使用多边形把轮廓包围

在实际应用中,常常会有将检测到的轮廓用多边形表示出来的需求。比如在一个全家福中,我想用一个矩形框将我自己的头像框出来,这样就需要这方面的知识了。

OpenCv这方面的函数总结如下:

  • 返回指定点集最外部矩形边界:boundingRect()
  • 寻找给定的点集可旋转的最小包围矩形:minAreaRect()
  • 寻找最小包围圆形:minEnclosingCircle()
  • 用椭圆拟合二维点集:fitEllipse()
  • 逼近多边形曲线:approxPolyDP()

下面给出这些函数用法的综合案例。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// 函数声明
void thresh_callback(int, void*);

/** @主函数 */
int main(int argc, char** argv)
{
    /// 载入原图像, 返回3通道图像
    src = imread("test5.jpg", 1);

    /// 转化成灰度图像并进行平滑
    cvtColor(src, src_gray, CV_BGR2GRAY);
    blur(src_gray, src_gray, Size(3, 3));

    /// 创建窗口
    char* source_window = "Source";
    namedWindow(source_window, CV_WINDOW_AUTOSIZE);
    imshow(source_window, src);

    createTrackbar(" Threshold:", "Source", &thresh, max_thresh, thresh_callback);
    thresh_callback(0, 0);

    waitKey(0);
    return(0);
}

/** @thresh_callback 函数 */
void thresh_callback(int, void*)
{
    Mat threshold_output;
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;

    /// 使用Threshold检测边缘
    threshold(src_gray, threshold_output, thresh, 255, THRESH_BINARY);
    /// 找到轮廓
    findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

    /// 多边形逼近轮廓 + 获取矩形和圆形边界框
    vector<vector<Point> > contours_poly(contours.size());
    vector<Rect> boundRect(contours.size());
    vector<Point2f>center(contours.size());
    vector<float>radius(contours.size());

    for (int i = 0; i < contours.size(); i++)
    {
        approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
        boundRect[i] = boundingRect(Mat(contours_poly[i]));
        minEnclosingCircle(contours_poly[i], center[i], radius[i]);
    }


    /// 画多边形轮廓 + 包围的矩形框 + 圆形框
    Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);
    for (int i = 0; i< contours.size(); i++)
    {
        Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        drawContours(drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point());
        rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0);
        circle(drawing, center[i], (int)radius[i], color, 2, 8, 0);
    }

    /// 显示在一个窗口
    namedWindow("Contours", CV_WINDOW_AUTOSIZE);
    imshow("Contours", drawing);
}

下面两张不同阈值的效果图把检测到的轮廓分别用多边形、圆形、矩形框出来了。
1093303-20170522185205554-245649086.png

1093303-20170522185218835-497505854.png

图像的矩

图像的矩到底是什么?

矩是概率与统计中的一个概念,是随机变量的一种数字特征。

有点抽象,简而言之,矩就是图像的特征信息,比如大小、位置、方向等。

OpenCV提供了一些函数来计算图像的矩:

  • 矩的重心、主轴、面积等特征计算:moments()
  • 计算轮廓面积:contourArea()
  • 计算轮廓长度:arcLength()

下面的程序,使用了两种方法计算轮廓面积,第一种使用了moments()函数(程序里的mu[i].m00),第二种使用了contourAra()函数进行面积计算,大家可以看一下两种方法计算出来的面积有没有差别。

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// 函数声明
void thresh_callback(int, void*);

/** @主函数 */
int main()
{
    /// 读入原图像, 返回3通道图像数据
    src = imread("lol10.jpg");

    /// 把原图像转化成灰度图像并进行平滑
    cvtColor(src, src_gray, CV_BGR2GRAY);
    blur(src_gray, src_gray, Size(3, 3));

    /// 创建新窗口
    char* source_window = "Source";
    namedWindow(source_window, CV_WINDOW_AUTOSIZE);
    imshow(source_window, src);

    createTrackbar(" Canny thresh:", "Source", &thresh, max_thresh, thresh_callback);
    thresh_callback(0, 0);

    waitKey(0);
    return(0);
}

/** @thresh_callback 函数 */
void thresh_callback(int, void*)
{
    Mat canny_output;
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;

    /// 使用Canndy检测边缘
    Canny(src_gray, canny_output, thresh, thresh * 2, 3);
    /// 找到轮廓
    findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));

    /// 计算矩
    vector<Moments> mu(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        mu[i] = moments(contours[i], false);
    }

    ///  计算中心矩:
    vector<Point2f> mc(contours.size());
    for (int i = 0; i < contours.size(); i++)
    {
        mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
    }

    /// 绘制轮廓
    Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3);
    for (int i = 0; i< contours.size(); i++)
    {
        Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
        circle(drawing, mc[i], 4, color, -1, 8, 0);
    }

    /// 显示到窗口中
    namedWindow("Contours", CV_WINDOW_AUTOSIZE);
    imshow("Contours", drawing);

    /// 通过m00计算轮廓面积并且和OpenCV函数比较
    printf("\t Info: Area and Contour Length \n");
    for (int i = 0; i< contours.size(); i++)
    {
        printf(" * Contour[%d] - Area (M_00) = %.2f - Area OpenCV: %.2f - Length: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength(contours[i], true));
        Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());
        circle(drawing, mc[i], 4, color, -1, 8, 0);
    }
}

1093303-20170522185311007-1785928353.png

1093303-20170522185334257-1485809029.png

1093303-20170522185402382-1576624606.png

从结果看来,两种方法计算得到的面积是一样的。
1093303-20170522185651429-314400016.png

<think>嗯,用户想了解OpenCV中的轮廓面积。首先,我需要确认轮廓面积在OpenCV中的定义计算方法。记得OpenCV里有一个函数叫做cv2.contourArea(),可以直接计算轮廓的面积。那这个函数具体是怎么实现的呢?是不是基于格林公式或者某种数值积分方法? 然后,用户可能想知道轮廓面积的应用场景,比如在图像处理中用来过滤小的噪声轮廓,或者计算物体的实际大小。需要解释清楚轮廓面积的意义,比如它代表的是轮廓内部的像素点数,还是实际几何面积。可能还要提到轮廓必须是闭合的,否则结果可能不准确。 还要注意,轮廓的检测方法,比如使用cv2.findContours()时,不同的检索模式可能会影响轮廓是否闭合。比如,如果使用CHAIN_APPROX_SIMPLE,会压缩轮廓的点,但可能不影响面积计算,因为面积计算是基于所有轮廓点的多边形。 另外,是否需要提到轮廓面积矩的关系?比如cv2.moments()里也有面积的计算,但可能cv2.contourArea()的结果一致。可能用户想知道两者的区别,或者什么时候用哪个更合适。 还有,处理轮廓时可能遇到的常见问题,比如轮廓有交叉或者自相交的情况,这时候面积计算是否准确?OpenCV的文档里有没有说明这种情况的处理方式? 可能需要提供一个简单的代码示例,展示如何找到轮廓并计算面积,这样用户能更直观地理解。比如读取图像,二值化,找轮廓,然后遍历每个轮廓计算面积,并打印出来。 还要提醒用户注意轮廓的层级过滤,比如通过面积阈值来筛选出感兴趣的物体。例如,去除面积过小的轮廓,可能是噪声,保留大的轮廓作为目标物体。 有没有特殊情况需要注意?比如轮廓点数量不足,比如只有两个点或者一个点,这时候计算面积是否为零或报错?可能需要说明函数对这些异常情况的处理方式。 最后,是否需要对比其他计算面积的方法,比如像素统计?比如,对于二值图像中的连通区域,可以通过统计白色像素的数量来得到面积,但轮廓面积可能更精确,特别是当轮廓不是严格矩形或者有复杂形状时。 总之,我需要组织这些信息,分步骤解释轮廓面积的概念、计算方法、使用场景以及注意事项,确保用户能清晰理解并正确应用。</think>OpenCV中的轮廓面积是图像处理中常用的基础概念,下面分步骤为你详细解释: 1. **轮廓面积的定义** - 轮廓面积表示轮廓包围的封闭区域的面积大小 - 在OpenCV中,使用`cv2.contourArea()`函数计算 - 计算公式基于格林公式,通过对轮廓顶点的坐标进行积分运算得出 2. **函数语法** ```python area = cv2.contourArea(contour, oriented=False) ``` - `contour`:通过`cv2.findContours()`检测到的轮廓 - `oriented`(可选):布尔值,返回带符号的面积(可判断轮廓方向) 3. **使用步骤示例** ```python import cv2 # 读取图像并二值化 img = cv2.imread('shape.png', 0) _, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 计算每个轮廓面积 for cnt in contours: area = cv2.contourArea(cnt) print(f"轮廓面积:{area} 像素") ``` 4. **关键特性** - 要求轮廓必须是闭合曲线 - 计算结果为浮点数 - 对顺时针逆时针轮廓方向敏感(当`oriented=True`时) - 与`cv2.moments()['m00']`计算结果一致 5. **应用场景** - 目标筛选:通过设置面积阈值过滤小噪声 - 尺寸测量:结合像素实际尺寸计算物体物理面积 - 形状分析:配合其他特征进行目标分类 - 运动追踪:通过面积变化检测物体运动状态 6. **注意事项** - 输入轮廓必须是N×1×2的numpy数组 - 复杂轮廓(自相交/凹多边形)仍能准确计算 - 与像素计数法的差异: $$ \text{轮廓面积} \approx \text{实际几何面积} $$ 像素统计法仅简单计数内部像素点 7. **性能优化** - 预处理时使用`cv2.CHAIN_APPROX_SIMPLE`可减少顶点数量 - 对于简单形状,面积计算误差小于0.1% - 计算速度与轮廓顶点数量成正比 实际应用中建议结合`cv2.boundingRect()`等函数,实现更精准的物体分析。例如: ```python for cnt in contours: if cv2.contourArea(cnt) > 100: # 过滤面积小于100像素的噪声 x,y,w,h = cv2.boundingRect(cnt) cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值