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

Part11.  图像的轮廓

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

11.1 轮廓的基本概念

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

轮廓和边缘的区别是:

  • 轮廓是连续的,边缘可以是连续的,也可以是离散的。

  • 轮廓是完整的,边缘可以是完整的,也可以是不完整的。

  • 轮廓可以有各种形状,边缘通常是线性的。

21.2 轮廓发现和轮廓提取

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

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

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

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

#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;
}

展示原图

f49c839d6000026d9299f9faef93e14d.jpeg
苹果的原图.png

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

d8a49c447babc6cd93c9fff92d225081.jpeg
hsv.png

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

21e51690d8a1a282c78d96b145c3240d.jpeg
二值化.png

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

24cb3aa7cc12e0d0d9c0020cc3bc013f.jpeg
形态学操作.png

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

71fb4f6dfda2b11d630aa3e6bd1278e6.jpeg
找到有效的苹果.png

Part22.  轮廓特征的分类

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

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

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

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

Part33.  轮廓的基础特征

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

  • 轮廓面积 contourArea()

  • 轮廓周长 arcLength()

  • 轮廓外接矩形 boundingRect()

  • 轮廓最小外接矩形 minAreaRect()

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

#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;
}

执行结果:

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
0e67e2cfdb005d416ed89e730469ec85.jpeg
回形针.png

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

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

#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;
}
4191c7d50c4e7c359ca5d5f2e1420314.jpeg
外接矩形和最小外接矩形.png

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

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

43.2 凸包

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

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

  • 平面的一个子集 S 被称为是“凸”的,当且仅当对于任意两点 p,s ∈S,线段 ps 都完全属于S。

  • 一个点集 P 的凸包CH(P),就是包含 P 的最小凸集——即包含P的所有凸集的交。

凸包的性质:

  • 凸包是凸集。

  • 凸包的周长是最小的。

  • 凸包的面积是最小的。

  • 凸包的质心是所有点的质心的均值。

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

#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;
}

执行结果:

isHull = 0
isHull = 0
839c49a8d37aff3f5352be75dfbee988.jpeg
凸包分析1.png
f4655399d510ab8ae076c4096f2b890c.jpeg
凸包分析2.png

Part44. 总结

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

Java与Android技术栈】公众号

关注 Java/Kotlin 服务端、桌面端 、Android 、机器学习、端侧智能

更多精彩内容请关注:

猜你喜欢

转载自blog.csdn.net/SLFq6OF5O7aH/article/details/135301120