C/C++的OpenCV 进行轮廓提取

使用 C/C++的OpenCV 进行轮廓提取

轮廓可以简单地描述为连接所有具有相同颜色或强度的连续点(沿着边界)的曲线。轮廓是形状分析以及对象检测和识别的有用工具。OpenCV 提供了非常方便的函数来查找和绘制轮廓。

本文将指导您完成使用 C++ 和 OpenCV 库从图像中提取轮廓的过程。

预备知识

在开始之前,请确保您已经安装了 OpenCV,并且您的 C++ 开发环境已经配置好可以链接 OpenCV 库。

通常,我们需要包含以下头文件:

#include <opencv2/opencv.hpp> // 包含所有核心和contrib模块
#include <iostream>
#include <vector> // 用于存储轮廓

为方便起见,我们也会使用 cvstd 命名空间:

using namespace cv;
using namespace std;

轮廓提取的步骤

轮廓提取通常涉及以下步骤:

  1. 加载图像:读取源图像。
  2. 预处理
    • 转换为灰度图:轮廓检测通常在灰度图像上进行。
    • 高斯模糊 (可选):减少噪声,有助于获得更清晰的轮廓。
    • 二值化:将灰度图像转换为二值图像(黑白图像)。这是 findContours 函数所必需的。常用的方法有简单阈值处理、自适应阈值处理或 Canny 边缘检测。
  3. 查找轮廓:使用 cv::findContours 函数。
  4. 绘制轮廓 (可选):使用 cv::drawContours 函数在图像上可视化找到的轮廓。

1. 加载图像和预处理

首先,我们加载图像并进行必要的预处理,将其转换为二值图像。

int main(int argc, char** argv) {
    // 1. 加载图像
    // const char* filename = argc >= 2 ? argv[1] : "shapes.png"; // 从命令行参数或默认读取
    Mat src = imread("your_image.png", IMREAD_COLOR); // 请替换为您的图片路径
    if (src.empty()) {
        cout << "无法加载图像: " << "your_image.png" << endl;
        return -1;
    }
    imshow("Original Image", src);

    // 2. 转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("Grayscale Image", gray);

    // 3. (可选)高斯模糊以减少噪声
    Mat blurred;
    GaussianBlur(gray, blurred, Size(5, 5), 0); // 使用5x5的核
    imshow("Blurred Image", blurred);

    // 4. 二值化图像
    // 方法一:使用阈值处理
    Mat binary_thresh;
    // threshold(blurred, binary_thresh, 100, 255, THRESH_BINARY_INV); // 根据图像调整阈值,THRESH_BINARY_INV使物体为白色,背景为黑色
    // 或者使用Otsu's方法自动确定阈值
    threshold(blurred, binary_thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
    imshow("Threshold Binary Image", binary_thresh);

    // 方法二:使用Canny边缘检测(Canny的输出已经是二值图像)
    Mat canny_output;
    Canny(blurred, canny_output, 50, 150); // 调整低阈值和高阈值
    imshow("Canny Edges", canny_output);

    // 为 findContours 选择一个二值图像作为输入
    // 通常,如果物体是实心的,阈值图像效果好;如果只关心边缘,Canny图也可以。
    // 注意:findContours会修改输入的二值图像,所以如果之后还需要它,请传递一个副本。
    Mat input_for_contours = binary_thresh.clone(); // 或者 canny_output.clone();

重要提示cv::findContours 函数会修改输入的二值图像。如果您希望保留原始的二值图像,请传递它的一个副本(例如,使用 .clone())。

2. 查找轮廓 (cv::findContours)

一旦有了二值图像,就可以使用 cv::findContours 函数来查找轮廓。

cv::findContours 函数原型:

void findContours( InputOutputArray image, OutputArrayOfArrays contours,
                   OutputArray hierarchy, int mode,
                   int method, Point offset = Point());
  • image: 输入的单通道8位二值图像。此图像会被函数修改。
  • contours: 检测到的轮廓。每个轮廓都是一个 std::vector<cv::Point>。因此,contours 是一个 std::vector<std::vector<cv::Point>>
  • hierarchy: 可选的输出向量 ( std::vector<cv::Vec4i> ),包含图像的拓扑结构。每个元素 hierarchy[i] 对应于 contours[i],包含4个索引:[Next, Previous, First_Child, Parent]
  • mode: 轮廓检索模式。
    • RETR_EXTERNAL: 只检索最外层的轮廓。
    • RETR_LIST: 检索所有轮廓,但不建立任何层次关系(所有轮廓都在同一级别)。
    • RETR_CCOMP: 检索所有轮廓并将它们组织成两级层次结构:外部边界和内部孔洞边界。
    • RETR_TREE: 检索所有轮廓并重建完整的层次结构。
  • method: 轮廓逼近方法。
    • CHAIN_APPROX_NONE: 存储轮廓上的所有点。
    • CHAIN_APPROX_SIMPLE: 压缩水平、垂直和对角线段,只保留它们的端点。例如,矩形的轮廓将只由4个点组成。
    • CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS: 应用Teh-Chin链逼近算法中的一种。
  • offset: 可选的轮廓点偏移量。如果您的轮廓是从图像的ROI中提取的,然后希望在整个图像上下文中分析它们,这将非常有用。
    // ... 接上文 input_for_contours

    vector<vector<Point>> contours; // 用于存储所有轮廓
    vector<Vec4i> hierarchy;       // 用于存储轮廓的层级信息

    // 查找轮廓
    findContours(input_for_contours, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
    // 使用 RETR_EXTERNAL 如果你只想找最外层轮廓
    // findContours(input_for_contours, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

    cout << "找到的轮廓数量: " << contours.size() << endl;

3. 理解轮廓层级 (hierarchy)

hierarchy 向量中的每个元素 Vec4i 对应于 contours 中的一个轮廓,并存储了4个整数:
hierarchy[i] = [Next_contour_idx, Previous_contour_idx, First_child_contour_idx, Parent_contour_idx]

  • Next_contour_idx: 与当前轮廓在同一层级上的下一个轮廓的索引。
  • Previous_contour_idx: 与当前轮廓在同一层级上的上一个轮廓的索引。
  • First_child_contour_idx: 当前轮廓的第一个子轮廓的索引。
  • Parent_contour_idx: 当前轮廓的父轮廓的索引。

如果对应的轮廓不存在,则索引值为 -1。

例如,使用 RETR_TREE 可以帮助您理解嵌套的轮廓(例如,一个形状内部的孔洞)。

4. 绘制轮廓 (cv::drawContours)

找到轮廓后,通常希望将它们可视化。cv::drawContours 函数用于此目的。

cv::drawContours 函数原型:

void drawContours( InputOutputArray image, InputArrayOfArrays contours,
                   int contourIdx, const Scalar& color,
                   int thickness = 1, int lineType = LINE_8,
                   InputArray hierarchy = noArray(), int maxLevel = INT_MAX,
                   Point offset = Point() );
  • image: 要在其上绘制轮廓的目标图像(通常是原始彩色图像的副本或新的空白图像)。
  • contours: 所有找到的轮廓,从 findContours 获得。
  • contourIdx: 指示要绘制哪个轮廓的参数。如果为负数(例如 -1),则绘制所有轮廓。
  • color: 轮廓的颜色。
  • thickness: 轮廓线的粗细。如果为负数或 FILLED,则轮廓内部被填充。
  • lineType: 线的连接类型 (例如 LINE_8, LINE_AA 抗锯齿线)。
  • hierarchy: 可选的层级信息,如果只想绘制特定层级的轮廓。
  • maxLevel: 仅当提供了 hierarchy 时才考虑此参数。0 表示只绘制指定的轮廓,1 表示绘制指定轮廓及其所有子轮廓,依此类推。
  • offset: 可选的轮廓点偏移量。
    // ... 接上文 contours 和 hierarchy

    // 创建一个空白图像用于绘制轮廓,或在原图副本上绘制
    Mat drawing = Mat::zeros(input_for_contours.size(), CV_8UC3); // 创建一个黑色的画布
    // 或者在原图上绘制
    // Mat drawing = src.clone();


    for (size_t i = 0; i < contours.size(); i++) {
        Scalar color = Scalar(rand() % 256, rand() % 256, rand() % 256); // 随机颜色
        // 绘制单个轮廓
        // drawContours(drawing, contours, static_cast<int>(i), color, 2, LINE_8, hierarchy, 0);
        // 或者绘制所有轮廓
    }
    // 绘制所有轮廓,使用一种颜色
    drawContours(drawing, contours, -1, Scalar(0, 255, 0), 2, LINE_8, hierarchy, 100);


    imshow("Contours", drawing);

5. 轮廓分析 (简介)

找到轮廓后,可以进行各种分析:

  • 计算轮廓面积: double area = contourArea(contours[i]);
  • 计算轮廓周长: double perimeter = arcLength(contours[i], true); (true表示轮廓是闭合的)
  • 轮廓近似: approxPolyDP(contours[i], approx_curve, epsilon, true); 将轮廓逼近为具有较少顶点的多边形。
  • 获取边界框: Rect bounding_rect = boundingRect(contours[i]);
  • 获取最小外接圆: Point2f center; float radius; minEnclosingCircle(contours[i], center, radius);
  • 形状匹配, 凸包, 几何矩 等。

这些分析可以用于过滤轮廓(例如,只保留面积大于某个阈值的轮廓)或识别特定形状。

完整示例代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main(int argc, char** argv) {
    // 1. 加载图像
    const char* filename = "your_image.png"; // 请替换为您的图片路径
    Mat src = imread(filename, IMREAD_COLOR);
    if (src.empty()) {
        cout << "无法加载图像: " << filename << endl;
        return -1;
    }
    imshow("Original Image", src);

    // 2. 转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    // 3. 高斯模糊
    Mat blurred;
    GaussianBlur(gray, blurred, Size(5, 5), 0);

    // 4. 二值化 (Otsu's 方法)
    Mat binary_thresh;
    threshold(blurred, binary_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU); // 物体为白色,背景黑色
    // 如果findContours需要白色物体在黑色背景上,请确保二值化正确
    // 如果物体是黑色的,背景是白色的,可能需要 THRESH_BINARY_INV
    // 或者确保Canny的输出是白色边缘在黑色背景上
    imshow("Binary Image for Contours", binary_thresh);


    // --- 查找轮廓 ---
    // 注意:findContours会修改输入的二值图像binary_thresh,所以如果之后还需要它,请使用它的副本
    Mat binary_copy = binary_thresh.clone();
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;

    // RETR_EXTERNAL 只查找最外层轮廓
    // RETR_TREE 查找所有轮廓并建立完整的层级结构
    findContours(binary_copy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);

    cout << "找到的轮廓数量: " << contours.size() << endl;

    // --- 绘制轮廓 ---
    // 创建一个彩色图像用于绘制轮廓 (在原始图像的副本上绘制)
    Mat drawing = src.clone();
    // 或者在一个新的黑色画布上绘制
    // Mat drawing = Mat::zeros(binary_thresh.size(), CV_8UC3);


    for (size_t i = 0; i < contours.size(); i++) {
        Scalar color;
        // 根据层级给内外轮廓不同颜色 (简单示例)
        if (hierarchy[i][3] == -1) { // 最外层轮廓 (没有父轮廓)
            color = Scalar(0, 0, 255); // 红色
        } else {
            color = Scalar(0, 255, 0); // 绿色 (内部轮廓)
        }
        drawContours(drawing, contours, static_cast<int>(i), color, 2, LINE_8, hierarchy, 0);
    }
    // 或者一次性绘制所有轮廓,用统一颜色
    // drawContours(drawing, contours, -1, Scalar(255,0,0), 2);


    imshow("Contours", drawing);

    // --- 轮廓分析示例:过滤小轮廓并绘制其边界框 ---
    Mat drawing_filtered = src.clone();
    for (size_t i = 0; i < contours.size(); i++) {
        double area = contourArea(contours[i]);
        if (area > 100) { // 过滤掉面积小于100的轮廓
            Scalar color = Scalar(rand() % 256, rand() % 256, rand() % 256);
            drawContours(drawing_filtered, contours, static_cast<int>(i), color, 2);

            Rect bounding_rect = boundingRect(contours[i]);
            rectangle(drawing_filtered, bounding_rect.tl(), bounding_rect.br(), Scalar(0,255,255), 2);
        }
    }
    imshow("Filtered Contours with Bounding Boxes", drawing_filtered);


    cout << "按任意键退出..." << endl;
    waitKey(0);
    destroyAllWindows();

    return 0;
}

编译与运行

假设您已正确安装 OpenCV,可以使用 g++ 编译上述代码:

g++ your_code_file.cpp -o contour_extraction $(pkg-config --cflags --libs opencv4)
./contour_extraction your_image.png

(如果 pkg-config --libs opencv4 不起作用,请根据您的 OpenCV 版本和安装方式调整链接器标志,例如 opencv 或特定模块如 opencv_core opencv_imgproc opencv_highgui opencv_imgcodecs)。确保替换 "your_image.png" 为您要测试的图像路径。

总结

轮廓提取是计算机视觉中的一项基础且强大的技术。通过结合适当的预处理步骤和 cv::findContours 函数,您可以有效地从图像中分离出感兴趣的对象或区域的边界。cv::drawContours 使得可视化这些轮廓变得简单。此外,OpenCV 还提供了丰富的函数来分析这些轮廓的属性,为更高级的图像分析任务(如形状识别、对象跟踪等)奠定了基础。


```markdown
# 使用 C++/OpenCV 进行轮廓提取

轮廓可以简单地描述为连接所有具有相同颜色或强度的连续点(沿着边界)的曲线。轮廓是形状分析以及对象检测和识别的有用工具。OpenCV 提供了非常方便的函数来查找和绘制轮廓。

本文将指导您完成使用 C++ 和 OpenCV 库从图像中提取轮廓的过程。

## 预备知识

在开始之前,请确保您已经安装了 OpenCV,并且您的 C++ 开发环境已经配置好可以链接 OpenCV 库。

通常,我们需要包含以下头文件:

```cpp
#include <opencv2/opencv.hpp> // 包含所有核心和contrib模块
#include <iostream>
#include <vector> // 用于存储轮廓

为方便起见,我们也会使用 cvstd 命名空间:

using namespace cv;
using namespace std;

轮廓提取的步骤

轮廓提取通常涉及以下步骤:

  1. 加载图像:读取源图像。
  2. 预处理
    • 转换为灰度图:轮廓检测通常在灰度图像上进行。
    • 高斯模糊 (可选):减少噪声,有助于获得更清晰的轮廓。
    • 二值化:将灰度图像转换为二值图像(黑白图像)。这是 findContours 函数所必需的。常用的方法有简单阈值处理、自适应阈值处理或 Canny 边缘检测。
  3. 查找轮廓:使用 cv::findContours 函数。
  4. 绘制轮廓 (可选):使用 cv::drawContours 函数在图像上可视化找到的轮廓。

1. 加载图像和预处理

首先,我们加载图像并进行必要的预处理,将其转换为二值图像。

int main(int argc, char** argv) {
    // 1. 加载图像
    // const char* filename = argc >= 2 ? argv[1] : "shapes.png"; // 从命令行参数或默认读取
    Mat src = imread("your_image.png", IMREAD_COLOR); // 请替换为您的图片路径
    if (src.empty()) {
        cout << "无法加载图像: " << "your_image.png" << endl;
        return -1;
    }
    imshow("Original Image", src);

    // 2. 转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("Grayscale Image", gray);

    // 3. (可选)高斯模糊以减少噪声
    Mat blurred;
    GaussianBlur(gray, blurred, Size(5, 5), 0); // 使用5x5的核
    imshow("Blurred Image", blurred);

    // 4. 二值化图像
    // 方法一:使用阈值处理
    Mat binary_thresh;
    // threshold(blurred, binary_thresh, 100, 255, THRESH_BINARY_INV); // 根据图像调整阈值,THRESH_BINARY_INV使物体为白色,背景为黑色
    // 或者使用Otsu's方法自动确定阈值
    threshold(blurred, binary_thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
    imshow("Threshold Binary Image", binary_thresh);

    // 方法二:使用Canny边缘检测(Canny的输出已经是二值图像)
    Mat canny_output;
    Canny(blurred, canny_output, 50, 150); // 调整低阈值和高阈值
    imshow("Canny Edges", canny_output);

    // 为 findContours 选择一个二值图像作为输入
    // 通常,如果物体是实心的,阈值图像效果好;如果只关心边缘,Canny图也可以。
    // 注意:findContours会修改输入的二值图像,所以如果之后还需要它,请传递一个副本。
    Mat input_for_contours = binary_thresh.clone(); // 或者 canny_output.clone();

重要提示cv::findContours 函数会修改输入的二值图像。如果您希望保留原始的二值图像,请传递它的一个副本(例如,使用 .clone())。

2. 查找轮廓 (cv::findContours)

一旦有了二值图像,就可以使用 cv::findContours 函数来查找轮廓。

cv::findContours 函数原型:

void findContours( InputOutputArray image, OutputArrayOfArrays contours,
                   OutputArray hierarchy, int mode,
                   int method, Point offset = Point());
  • image: 输入的单通道8位二值图像。此图像会被函数修改。
  • contours: 检测到的轮廓。每个轮廓都是一个 std::vector<cv::Point>。因此,contours 是一个 std::vector<std::vector<cv::Point>>
  • hierarchy: 可选的输出向量 ( std::vector<cv::Vec4i> ),包含图像的拓扑结构。每个元素 hierarchy[i] 对应于 contours[i],包含4个索引:[Next, Previous, First_Child, Parent]
  • mode: 轮廓检索模式。
    • RETR_EXTERNAL: 只检索最外层的轮廓。
    • RETR_LIST: 检索所有轮廓,但不建立任何层次关系(所有轮廓都在同一级别)。
    • RETR_CCOMP: 检索所有轮廓并将它们组织成两级层次结构:外部边界和内部孔洞边界。
    • RETR_TREE: 检索所有轮廓并重建完整的层次结构。
  • method: 轮廓逼近方法。
    • CHAIN_APPROX_NONE: 存储轮廓上的所有点。
    • CHAIN_APPROX_SIMPLE: 压缩水平、垂直和对角线段,只保留它们的端点。例如,矩形的轮廓将只由4个点组成。
    • CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS: 应用Teh-Chin链逼近算法中的一种。
  • offset: 可选的轮廓点偏移量。如果您的轮廓是从图像的ROI中提取的,然后希望在整个图像上下文中分析它们,这将非常有用。
    // ... 接上文 input_for_contours

    vector<vector<Point>> contours; // 用于存储所有轮廓
    vector<Vec4i> hierarchy;       // 用于存储轮廓的层级信息

    // 查找轮廓
    findContours(input_for_contours, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
    // 使用 RETR_EXTERNAL 如果你只想找最外层轮廓
    // findContours(input_for_contours, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

    cout << "找到的轮廓数量: " << contours.size() << endl;

3. 理解轮廓层级 (hierarchy)

hierarchy 向量中的每个元素 Vec4i 对应于 contours 中的一个轮廓,并存储了4个整数:
hierarchy[i] = [Next_contour_idx, Previous_contour_idx, First_child_contour_idx, Parent_contour_idx]

  • Next_contour_idx: 与当前轮廓在同一层级上的下一个轮廓的索引。
  • Previous_contour_idx: 与当前轮廓在同一层级上的上一个轮廓的索引。
  • First_child_contour_idx: 当前轮廓的第一个子轮廓的索引。
  • Parent_contour_idx: 当前轮廓的父轮廓的索引。

如果对应的轮廓不存在,则索引值为 -1。

例如,使用 RETR_TREE 可以帮助您理解嵌套的轮廓(例如,一个形状内部的孔洞)。

4. 绘制轮廓 (cv::drawContours)

找到轮廓后,通常希望将它们可视化。cv::drawContours 函数用于此目的。

cv::drawContours 函数原型:

void drawContours( InputOutputArray image, InputArrayOfArrays contours,
                   int contourIdx, const Scalar& color,
                   int thickness = 1, int lineType = LINE_8,
                   InputArray hierarchy = noArray(), int maxLevel = INT_MAX,
                   Point offset = Point() );
  • image: 要在其上绘制轮廓的目标图像(通常是原始彩色图像的副本或新的空白图像)。
  • contours: 所有找到的轮廓,从 findContours 获得。
  • contourIdx: 指示要绘制哪个轮廓的参数。如果为负数(例如 -1),则绘制所有轮廓。
  • color: 轮廓的颜色。
  • thickness: 轮廓线的粗细。如果为负数或 FILLED,则轮廓内部被填充。
  • lineType: 线的连接类型 (例如 LINE_8, LINE_AA 抗锯齿线)。
  • hierarchy: 可选的层级信息,如果只想绘制特定层级的轮廓。
  • maxLevel: 仅当提供了 hierarchy 时才考虑此参数。0 表示只绘制指定的轮廓,1 表示绘制指定轮廓及其所有子轮廓,依此类推。
  • offset: 可选的轮廓点偏移量。
    // ... 接上文 contours 和 hierarchy

    // 创建一个空白图像用于绘制轮廓,或在原图副本上绘制
    Mat drawing = Mat::zeros(input_for_contours.size(), CV_8UC3); // 创建一个黑色的画布
    // 或者在原图上绘制
    // Mat drawing = src.clone();


    for (size_t i = 0; i < contours.size(); i++) {
        Scalar color = Scalar(rand() % 256, rand() % 256, rand() % 256); // 随机颜色
        // 绘制单个轮廓
        // drawContours(drawing, contours, static_cast<int>(i), color, 2, LINE_8, hierarchy, 0);
        // 或者绘制所有轮廓
    }
    // 绘制所有轮廓,使用一种颜色
    drawContours(drawing, contours, -1, Scalar(0, 255, 0), 2, LINE_8, hierarchy, 100);


    imshow("Contours", drawing);

5. 轮廓分析 (简介)

找到轮廓后,可以进行各种分析:

  • 计算轮廓面积: double area = contourArea(contours[i]);
  • 计算轮廓周长: double perimeter = arcLength(contours[i], true); (true表示轮廓是闭合的)
  • 轮廓近似: approxPolyDP(contours[i], approx_curve, epsilon, true); 将轮廓逼近为具有较少顶点的多边形。
  • 获取边界框: Rect bounding_rect = boundingRect(contours[i]);
  • 获取最小外接圆: Point2f center; float radius; minEnclosingCircle(contours[i], center, radius);
  • 形状匹配, 凸包, 几何矩 等。

这些分析可以用于过滤轮廓(例如,只保留面积大于某个阈值的轮廓)或识别特定形状。

完整示例代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;

int main(int argc, char** argv) {
    // 1. 加载图像
    const char* filename = "your_image.png"; // 请替换为您的图片路径
    Mat src = imread(filename, IMREAD_COLOR);
    if (src.empty()) {
        cout << "无法加载图像: " << filename << endl;
        return -1;
    }
    imshow("Original Image", src);

    // 2. 转换为灰度图
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    // 3. 高斯模糊
    Mat blurred;
    GaussianBlur(gray, blurred, Size(5, 5), 0);

    // 4. 二值化 (Otsu's 方法)
    Mat binary_thresh;
    threshold(blurred, binary_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU); // 物体为白色,背景黑色
    // 如果findContours需要白色物体在黑色背景上,请确保二值化正确
    // 如果物体是黑色的,背景是白色的,可能需要 THRESH_BINARY_INV
    // 或者确保Canny的输出是白色边缘在黑色背景上
    imshow("Binary Image for Contours", binary_thresh);


    // --- 查找轮廓 ---
    // 注意:findContours会修改输入的二值图像binary_thresh,所以如果之后还需要它,请使用它的副本
    Mat binary_copy = binary_thresh.clone();
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;

    // RETR_EXTERNAL 只查找最外层轮廓
    // RETR_TREE 查找所有轮廓并建立完整的层级结构
    findContours(binary_copy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);

    cout << "找到的轮廓数量: " << contours.size() << endl;

    // --- 绘制轮廓 ---
    // 创建一个彩色图像用于绘制轮廓 (在原始图像的副本上绘制)
    Mat drawing = src.clone();
    // 或者在一个新的黑色画布上绘制
    // Mat drawing = Mat::zeros(binary_thresh.size(), CV_8UC3);


    for (size_t i = 0; i < contours.size(); i++) {
        Scalar color;
        // 根据层级给内外轮廓不同颜色 (简单示例)
        if (hierarchy[i][3] == -1) { // 最外层轮廓 (没有父轮廓)
            color = Scalar(0, 0, 255); // 红色
        } else {
            color = Scalar(0, 255, 0); // 绿色 (内部轮廓)
        }
        drawContours(drawing, contours, static_cast<int>(i), color, 2, LINE_8, hierarchy, 0);
    }
    // 或者一次性绘制所有轮廓,用统一颜色
    // drawContours(drawing, contours, -1, Scalar(255,0,0), 2);


    imshow("Contours", drawing);

    // --- 轮廓分析示例:过滤小轮廓并绘制其边界框 ---
    Mat drawing_filtered = src.clone();
    for (size_t i = 0; i < contours.size(); i++) {
        double area = contourArea(contours[i]);
        if (area > 100) { // 过滤掉面积小于100的轮廓
            Scalar color = Scalar(rand() % 256, rand() % 256, rand() % 256);
            drawContours(drawing_filtered, contours, static_cast<int>(i), color, 2);

            Rect bounding_rect = boundingRect(contours[i]);
            rectangle(drawing_filtered, bounding_rect.tl(), bounding_rect.br(), Scalar(0,255,255), 2);
        }
    }
    imshow("Filtered Contours with Bounding Boxes", drawing_filtered);


    cout << "按任意键退出..." << endl;
    waitKey(0);
    destroyAllWindows();

    return 0;
}

编译与运行

假设您已正确安装 OpenCV,可以使用 g++ 编译上述代码:

g++ your_code_file.cpp -o contour_extraction $(pkg-config --cflags --libs opencv4)
./contour_extraction your_image.png

(如果 pkg-config --libs opencv4 不起作用,请根据您的 OpenCV 版本和安装方式调整链接器标志,例如 opencv 或特定模块如 opencv_core opencv_imgproc opencv_highgui opencv_imgcodecs)。确保替换 "your_image.png" 为您要测试的图像路径。

总结

轮廓提取是计算机视觉中的一项基础且强大的技术。通过结合适当的预处理步骤和 cv::findContours 函数,您可以有效地从图像中分离出感兴趣的对象或区域的边界。cv::drawContours 使得可视化这些轮廓变得简单。此外,OpenCV 还提供了丰富的函数来分析这些轮廓的属性,为更高级的图像分析任务(如形状识别、对象跟踪等)奠定了基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值