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. 总结

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

相关推荐
ctrey_3 小时前
2024-11-1 学习人工智能的Day20 openCV(2)
人工智能·opencv·学习
绕灵儿4 小时前
OpenCV通过指针裁剪图像
人工智能·opencv·计算机视觉
决战春招9 小时前
人工智能之人脸识别(人脸采集人脸识别)
人工智能·opencv·学习·计算机视觉
千秋1000011 小时前
OpenCV—calcHist()函数
人工智能·opencv·计算机视觉
爱写代码的小朋友11 小时前
使用 Python 和 OpenCV 实现实时人脸识别
开发语言·python·opencv
小负不负14 小时前
使用kalibr_calibration标定相机(realsense)和imu(h7min)
数码相机·opencv·计算机视觉
凤枭香14 小时前
python opencv灰度变换
图像处理·人工智能·python·opencv
maxruan15 小时前
cv::Mat初始化、赋值初始化与访问方式
人工智能·opencv·计算机视觉
jndingxin16 小时前
OpenCV视觉分析之目标跟踪(11)计算两个图像之间的最佳变换矩阵函数findTransformECC的使用
opencv·目标跟踪·矩阵
joker_man11 天前
使用Python和OpenCV实现火焰检测
开发语言·python·opencv