OpenCV 笔记(14):图像的轮廓和轮廓的基础特征

1. 图像的轮廓

在该系列第三篇文章中,曾经简单地介绍过轮廓和轮廓发现。

1.1 轮廓的基本概念

图像的轮廓 是指图像中具有相同颜色灰度值的连续点的曲线。轮廓和边缘是有联系的,边缘是轮廓的基础,轮廓是边缘的连续集合。

轮廓和边缘的区别是:

  • 轮廓是连续的,边缘可以是连续的,也可以是离散的。
  • 轮廓是完整的,边缘可以是完整的,也可以是不完整的。
  • 轮廓可以有各种形状,边缘通常是线性的。

1.2 轮廓发现和轮廓提取

轮廓发现是指在图像中找到所有可能的轮廓。

轮廓提取是指从图像中找到所有有效的轮廓和轮廓的具体信息。

轮廓发现是轮廓提取的前提,轮廓提取在轮廓发现的基础上进一步提取轮廓的形状和位置信息等等。

下面的代码,经过一系列操作找到二值图像的有效轮廓后,获取这些轮廓的最小外接矩形,最后用线在原图中框出这些外接矩形,从而在原图中找到比较明显的苹果。

cpp 复制代码
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

bool ascendSort(vector<Point> a,vector<Point> b)
{
    return contourArea(a) > contourArea(b);
}

int main(int argc, char **argv) {
    Mat src = imread(".../apple.jpg");
    imshow("src", src);

    Mat hsv,edge;
    cvtColor(src, hsv, cv::COLOR_BGR2HSV); // BGR 转换到 HSV 色彩空间
    imshow("hsv", hsv);

    cv::Scalar lower_red(0, 43, 46);
    cv::Scalar upper_red(10, 255, 255); // 定义红色的 HSV 范围

    Mat mask;
    inRange(hsv, lower_red, upper_red, mask); // 通过 inRange 函数实现二值化
    imshow("mask", mask);

    Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 15));
    morphologyEx(mask, mask, MORPH_CLOSE, kernel); // 形态学操作
    morphologyEx(mask, mask, MORPH_OPEN, kernel);  // 形态学操作
    imshow("morphology", mask);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;

    findContours(mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    sort(contours.begin(), contours.end(), ascendSort);//ascending sort

    for (size_t i = 0; i< contours.size(); i++) {
        double area = contourArea(contours[i]);

        if (area < 22000) {
            continue;
        }
        cout << "area = " << area << endl;
        RotatedRect rrt = minAreaRect(contours[i]);

        Point2f pt[4];
        rrt.points(pt);
        line(src, pt[0], pt[1], Scalar(255, 0, 0), 8, 8);
        line(src, pt[1], pt[2], Scalar(255, 0, 0), 8, 8);
        line(src, pt[2], pt[3], Scalar(255, 0, 0), 8, 8);
        line(src, pt[3], pt[0], Scalar(255, 0, 0), 8, 8);
    }

    imshow("result", src);

    waitKey(0);
    return 0;
}

展示原图

将原图转换成 HSV 类型,用于提取特定颜色。

通过 inRange 函数实现二值化。inRange 函数用于将图像中的像素值限制在指定的范围内,它会将满足条件的像素设置为 255,不满足条件的像素设置为 0,从而形成一个二值图像。

对二值图像进行一些形态学的操作,便于后续的轮廓分析。

通过 findContours() 函数进行轮廓发现。最后,筛选出有效的轮廓,并获取最小外接矩形,用线画出在原图上展示出来。

2. 轮廓特征的分类

图像的轮廓特征可以分为以下几类:

  • 基础特征:面积、周长、质心、凸包、最小外接矩形等。这些特征可以直接从轮廓序列中计算得到。

  • 矩特征:Hu 矩、中心矩、惯性矩等。这些特征可以用于描述轮廓的形状和大小。

  • 几何特征:最小闭合圆、拟合椭圆等。这些特征可以用于描述轮廓的几何形状。

3. 轮廓的基础特征

3.1 面积、周长、最小外接矩形

  • 轮廓面积 contourArea()
  • 轮廓周长 arcLength()
  • 轮廓外接矩形 boundingRect()
  • 轮廓最小外接矩形 minAreaRect()

下面的例子获取图中回形针的轮廓,以及轮廓的面积、周长、最小外接矩形等。

cpp 复制代码
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

bool ascendSort(vector<Point> a,vector<Point> b)
{
    return contourArea(a) > contourArea(b);
}

int main(int argc, char **argv) {
    Mat src = imread(".../paperclip.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    imshow("gray", gray);

    threshold(gray,thresh,0,255,THRESH_BINARY_INV | THRESH_OTSU);
    imshow("thresh", thresh);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;

    findContours(thresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    sort(contours.begin(), contours.end(), ascendSort);//ascending sort

    for (size_t i = 0; i< contours.size(); i++) {
        double area = contourArea(contours[i]);
        double length = arcLength(contours[i],true);

        if (area < 1000) {
            continue;
        }
        cout << "area = " << area << ", length = " << length << endl;
        RotatedRect rrt = minAreaRect(contours[i]);// 获取最小外接矩形

        Point2f pt[4];
        rrt.points(pt);
        line(src, pt[0], pt[1], Scalar(255, 0, 0), 8, 8);
        line(src, pt[1], pt[2], Scalar(255, 0, 0), 8, 8);
        line(src, pt[2], pt[3], Scalar(255, 0, 0), 8, 8);
        line(src, pt[3], pt[0], Scalar(255, 0, 0), 8, 8);
        Point  center = rrt.center;
        circle(src, center, 2,Scalar(0, 0, 255), 8, 8); // 绘制最小外接矩形的中心点
    }

    imshow("result", src);

    waitKey(0);
    return 0;
}

执行结果:

ini 复制代码
area = 101573, length = 2461.71
area = 41757.5, length = 1256.08
area = 41348, length = 1152.56
area = 39717.5, length = 1616.13
area = 37503, length = 1230.47
area = 36742.5, length = 1037.21
area = 4142, length = 706.357

外接矩形是指可以包围轮廓所有点的矩形,而最小外接矩形是指包含轮廓中所有点的最小矩形。

下面的例子,获取图中最大轮廓的外接矩形和最小外接矩形,分别用黄色和蓝色表示。

cpp 复制代码
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

bool ascendSort(vector<Point> a,vector<Point> b)
{
    return contourArea(a) > contourArea(b);
}

int main(int argc, char **argv) {
    Mat src = imread(".../fruit.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    threshold(gray,thresh,0,255,THRESH_BINARY | THRESH_OTSU);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(thresh, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    sort(contours.begin(), contours.end(), ascendSort);//ascending sort

    RotatedRect rrt = minAreaRect(contours[0]);// 获取最大轮廓的最小外接矩形

    Point2f pt[4];
    rrt.points(pt);
    line(src, pt[0], pt[1], Scalar(255, 0, 0), 8, 8);
    line(src, pt[1], pt[2], Scalar(255, 0, 0), 8, 8);
    line(src, pt[2], pt[3], Scalar(255, 0, 0), 8, 8);
    line(src, pt[3], pt[0], Scalar(255, 0, 0), 8, 8);

    Rect rect = boundingRect(contours[0]);// 获取最大轮廓的外接矩形
    rectangle(src,rect,Scalar(0, 255, 255), 8, 8);// 绘制外接矩形

    imshow("result", src);

    waitKey(0);
    return 0;
}

通过上述例子可以看到,最小外接矩形能够更精确地描述轮廓的形状和大小。

外接矩形和最小外接矩形有各自的使用场景,例如在对象检测中,可以使用外接矩形来粗略定位物体,而使用最小外接矩形来精确定位物体。

3.2 凸包

凸包(Convex Hull)是计算几何(图形学)中的概念。在一个实数向量空间 V 中,对于给定集合 X,所有包含 X 的凸集的交集 S 被称为 X 的 凸包。

在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。

  • 平面的一个子集 S 被称为是"凸"的,当且仅当对于任意两点 p,s ∈S,线段 ps 都完全属于S。
  • 一个点集 P 的凸包CH(P),就是包含 P 的最小凸集------即包含P的所有凸集的交。

凸包的性质:

  • 凸包是凸集。
  • 凸包的周长是最小的。
  • 凸包的面积是最小的。
  • 凸包的质心是所有点的质心的均值。

OpenCV 提供了 convexHull() 函数 寻找轮廓的凸包以及 isContourConvex() 函数用于判断轮廓是否为凸轮廓。凸轮廓是指所有内角都小于或等于 180 度的轮廓。

cpp 复制代码
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

using namespace std;
using namespace cv;

int main(int argc, char **argv) {
    Mat src = imread(".../hand.jpg");
    imshow("src", src);

    Mat gray,thresh;
    cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    threshold(gray,thresh,0,255,THRESH_BINARY_INV | THRESH_OTSU);
    imshow("thresh", thresh);

    Mat mask;
    Mat kernel = getStructuringElement(MORPH_RECT, Size(31, 31));
    morphologyEx(thresh, mask, MORPH_CLOSE, kernel); // 形态学操作
    imshow("morphology", mask);

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;

    findContours(mask, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

    vector<vector<Point>> hull(contours.size());

    Mat drawing = Mat::zeros(mask.size(),CV_8UC3);
    for (size_t i = 0; i< contours.size(); i++) {
        double area = contourArea(contours[i]);
        if (area < 100) {
            continue;
        }

        convexHull(contours[i], hull[i], false);
        bool isHull = isContourConvex(contours[i]);
        cout << "isHull = " << isHull << endl;
        drawContours(drawing, contours, i, Scalar(0, 0, 255), 8, 8);
        drawContours(drawing, hull, i, Scalar(255, 0, 0), 8, 8);
        drawContours(src, hull, i, Scalar(255, 0, 0), 8, 8);
    }

    imshow("result",src);
    imshow("drawing",drawing);

    waitKey(0);
    return 0;
}

执行结果:

ini 复制代码
isHull = 0
isHull = 0

4. 总结

轮廓的基础特征是计算机视觉中的重要工具,这些特征可以应用于对象检测、形状识别、测量等各种应用场景。后续还会介绍更多的轮廓特征。

相关推荐
alpszero40 分钟前
YOLO11解决方案之物体模糊探索
人工智能·python·opencv·计算机视觉·yolo11
多巴胺与内啡肽.1 小时前
OpenCV进阶操作:风格迁移以及DNN模块解析
人工智能·opencv·dnn
zm-v-159304339862 小时前
解锁遥感数据密码:DeepSeek、Python 与 OpenCV 的协同之力
开发语言·python·opencv
jndingxin3 小时前
OpenCV CUDA 模块中在 GPU 上对图像或矩阵进行 翻转(镜像)操作的一个函数 flip()
人工智能·opencv
码农新猿类4 小时前
初入OpenCV
qt·opencv·计算机视觉
程序小K6 小时前
OpenCV的CUDA模块进行图像处理
图像处理·人工智能·opencv
jndingxin6 小时前
OpenCVCUDA 模块中在 GPU 上对图像或矩阵进行 边界填充(padding)函数copyMakeBorder()
人工智能·opencv
jndingxin18 小时前
OpenCV 的 CUDA 模块中用于将多个单通道的 GpuMat 图像合并成一个多通道的图像 函数cv::cuda::merge
人工智能·opencv·计算机视觉
巷95520 小时前
OpenCV图像金字塔详解:原理、实现与应用
人工智能·opencv·计算机视觉
小草cys1 天前
树莓派4 yolo 11l.pt性能优化后的版本
opencv·计算机视觉·目标跟踪