C/C++的OpenCV 进行轮廓提取
使用 C/C++的OpenCV 进行轮廓提取
轮廓可以简单地描述为连接所有具有相同颜色或强度的连续点(沿着边界)的曲线。轮廓是形状分析以及对象检测和识别的有用工具。OpenCV 提供了非常方便的函数来查找和绘制轮廓。
本文将指导您完成使用 C++ 和 OpenCV 库从图像中提取轮廓的过程。
预备知识
在开始之前,请确保您已经安装了 OpenCV,并且您的 C++ 开发环境已经配置好可以链接 OpenCV 库。
通常,我们需要包含以下头文件:
#include <opencv2/opencv.hpp> // 包含所有核心和contrib模块
#include <iostream>
#include <vector> // 用于存储轮廓
为方便起见,我们也会使用 cv
和 std
命名空间:
using namespace cv;
using namespace std;
轮廓提取的步骤
轮廓提取通常涉及以下步骤:
- 加载图像:读取源图像。
- 预处理:
- 转换为灰度图:轮廓检测通常在灰度图像上进行。
- 高斯模糊 (可选):减少噪声,有助于获得更清晰的轮廓。
- 二值化:将灰度图像转换为二值图像(黑白图像)。这是
findContours
函数所必需的。常用的方法有简单阈值处理、自适应阈值处理或 Canny 边缘检测。
- 查找轮廓:使用
cv::findContours
函数。 - 绘制轮廓 (可选):使用
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_contoursvector<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> // 用于存储轮廓
为方便起见,我们也会使用 cv
和 std
命名空间:
using namespace cv;
using namespace std;
轮廓提取的步骤
轮廓提取通常涉及以下步骤:
- 加载图像:读取源图像。
- 预处理:
- 转换为灰度图:轮廓检测通常在灰度图像上进行。
- 高斯模糊 (可选):减少噪声,有助于获得更清晰的轮廓。
- 二值化:将灰度图像转换为二值图像(黑白图像)。这是
findContours
函数所必需的。常用的方法有简单阈值处理、自适应阈值处理或 Canny 边缘检测。
- 查找轮廓:使用
cv::findContours
函数。 - 绘制轮廓 (可选):使用
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_contoursvector<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 还提供了丰富的函数来分析这些轮廓的属性,为更高级的图像分析任务(如形状识别、对象跟踪等)奠定了基础。
相关文章:
C/C++的OpenCV 进行轮廓提取
使用 C/C的OpenCV 进行轮廓提取 轮廓可以简单地描述为连接所有具有相同颜色或强度的连续点(沿着边界)的曲线。轮廓是形状分析以及对象检测和识别的有用工具。OpenCV 提供了非常方便的函数来查找和绘制轮廓。 本文将指导您完成使用 C 和 OpenCV 库从图像…...

计算机网络总结(物理层,链路层)
目录 第一章 概述 1.基本概念 2.- C/S模式,B/S模式,P2P模式 3.- LAN,WAN,MAN,PAN的划分 4.电路交换与分组交换,数据报交换和虚电路交换 第二章 物理层 1.信号编码:不归零编码,曼切斯特编码 2.几种复用技术的特…...

TIGER - 一个轻量高效的语音分离模型,支持人声伴奏分离、音频说话人分离等 支持50系显卡 本地一键整合包下载
TIGER 是一种轻量级语音分离模型,通过频段分割、多尺度及全频帧建模有效提取关键声学特征。该项目由来自清华大学主导研发,通过频率带分割、多尺度以及全频率帧建模的方式,有效地提取关键声学特征,从而实现高效的语音分离。 TIGER…...
yolov8,c++案例汇总
文章目录 引言多目标追踪案例人体姿态估计算法手势姿态估计算法目标分割算法 引言 以下案例,基于c,ncnn,yolov8既可以在windows10/11上部署, 也可以在安卓端部署, 也可以在嵌入式端部署, 服务器端可支持部署封装为DLL,支持c/c#/java端调用 多目标追踪案例 基于yolov8, ncnn,…...

无人机降落伞设计要点难点及原理!
一、设计要点 1. 伞体结构与折叠方式 伞体需采用轻量化且高强度的材料(如抗撕裂尼龙或芳纶纤维),并通过多重折叠设计(如三重折叠缝合)减少展开时的阻力,同时增强局部承力区域的强度。 伞衣的几何参数&am…...

20250526给荣品PRO-RK3566的Android13单独编译boot.img
./build.sh init ./build.sh -K ./build.sh kernel 20250526给荣品PRO-RK3566的Android13单独编译boot.img 2025/5/26 15:25 缘起:需要给荣品PRO-RK3566的Android13单独编译内核,但是不想编译整个系统。于是: 如果特调试某些特别的改动/文件…...
vue3项目动态路由的相关配置踩坑记录
1.路由文件中引入store的报错解决 import { useUserStore } from /stores/user // 错误:此时 Pinia 未初始化const store useUserStore() // 报错 解决方案: import pinia,{ useUserStore } from /stores/user 或者在路由前置守卫中调用useUserSto…...
git子模块--命令--列表版
Git子模块指令查询手册 一、基本操作指令 添加子模块 git submodule add <仓库地址> [路径] 添加子模块并生成.gitmodules。 克隆含子模块项目 git clone --recursive <主仓库地址> 克隆主仓库及所有子模块。 初始化子模块 git submodule init 将.gitmodules…...
C++(4)
四、模板与容器 1. 模板 1.1 函数模板 #include <iostream> using namespace std;// 函数模板声明 template<typename T> // 也可使用 class T add(T a, T b) {return a b; }int main() {string a "hello";string b "world";cout <&…...

构建版本没mac上传APP方法
在苹果开发者的app store connect上架Ios应用的时候,发现需要使用xode等软件来上传iOS的APP。 但是不管是xcode也好,transporter也好,还是命令行工具也好,都必须安装在mac电脑才能使用,。 假如没有mac电脑࿰…...

如何解决大模型返回的JSON数据前后加上```的情况
环境说明 springboot 应用使用dashscope-sdk-java对接阿里百练 deepseek v3模型 问题表现 已经指定了输出json格式,但指令不明确,输出JSON格式的写法如下 注:提示词一开始是能正常功能的,但过了几天就出现了异常,原…...
本地处理 + GPU 加速 模糊视频秒变 4K/8K 修复视频老旧素材
各位数码小达人们!你们知道吗,今天我要给大家介绍一款超厉害的工具——Video2X。它就像是一个神奇的魔法棒,能把低分辨率的视频、GIF和图像变成高清甚至4K的,而且画质细节一点都不会损失! 先来说说它的核心功能。第一…...

服务器异常数据问题解决 工具(tcpdump+wireshark+iptables)
问题: 某天一客户反馈,后台页面上显示的设备数据异常增长。现场实际只有2w台设备安装了助手(客户端),但是后台显示有16w的助手设备,并且还在持续且快速的增长。这些数据会被加载到缓存,时间久了,服务端程序…...

综合实现案例 LVS keepalived mysql 等
基于企业级高可用架构的 Linux 案例,整合 Nginx、HTTPS、LVS、Keepalived、MySQL 等服务,实现 Web 服务的负载均衡、高可用性及数据持久化。 案例场景:高可用 Web服务架构 目标 构建高可用 Web 集群,支持负载均衡和故障自动切换…...

【QT】对话框dialog类封装
【QT】对话框dialog类封装 背景要点采用对输入框的信号监测实现端口和IP有效 实现 背景 在Qt 6.8.1 (MSVC 2022, x86_64)中进行编写,需要实现IP和端口号输入的弹窗,实现的方式有2种,其一,采用UI绘制,然后进行界面加载…...

2025/5/26 学习日记 基本/扩展正则表达式 linux三剑客之grep
在 Linux 系统中,正则表达式(Regular Expression可用于匹配、查找和替换符合特定模式的文本。根据语法和功能的不同,正则表达式可分为 基础正则表达式(BRE) 和 扩展正则表达式(ERE)。 基础正则…...

【后端高阶面经:消息队列篇】29、Kafka高性能探秘:零拷贝、顺序写与分区并发实战
一、 顺序写入:磁盘性能的极致挖掘 Kafka的高性能本质上源于对磁盘顺序访问的深度优化。 传统随机写入的磁盘操作需要磁头频繁寻道,机械硬盘的随机写性能通常仅为100IOPS左右,而Kafka通过追加日志(Append-Only Log)模式,将所有消息按顺序写入分区文件,使磁盘操作转化为…...
Spring Boot企业级开发五大核心功能与高级扩展实战
前言 在企业级应用开发中,Spring Boot已成为事实上的Java开发标准。本文将从企业实际需求出发,深入剖析Spring Boot五大必用核心功能,并扩展讲解三项高级开发技能,帮助开发者掌握构建健壮、高效、易维护的企业级应用的必备技术。…...
在SpringBoot项目中策略模式的使用
使用策略模式之前的代码 Overridepublic void updateExam(String id, ExamUpdateDTO examUpdateDTO) {logger.info("Service: 修改考试场次, ID: {}, 数据: {}", id, examUpdateDTO);Exam existingExam mongoDBUtils.findById(id, Exam.class);if (existingExam nu…...

在 Docker 中启动 Jupyter Notebook
文章目录 一、创建容器二、Conda安装三、安装 Jupyter四、启动 Jupyter五、注册内核来使用虚拟环境小结 一、创建容器 可以先查看宿主机8888端口是否被占用,无输出,表明端口未被任何进程占用,如果有LISTEN,可能在创建容器的时候需…...
IP 地址反向解析(IP反查域名)原理与应用
一、IP 地址反向解析的原理与技术细节 IP 地址反向解析(Reverse IP Lookup)是一种将 IP 地址映射回其关联域名或主机名的网络技术,与常见的正向 DNS 解析(将域名解析为 IP 地址)形成互补。这一过程在网络安全研究、漏…...

CodeTop之LRU缓存
题目链接 146. LRU 缓存 - 力扣(LeetCode) 题目解析 算法原理 我们使用双向链表哈希表的形式来模拟缓存机制 首先我们要自己实现一个双链表, 自己写一个内部类, 这个内部类记录了key,value,prev,next(前驱和后继), 后续我们就通过这个内部类来构造双…...

uboot常用命令之eMMC/SD卡命令
eMMC和SD卡(TF卡)是同一类设备,以下命令二者是通用,本章节主要以eMMC举例说明命令的使用。 使用help mmc可以看到mmc相关命令列表以及其对应命令用法: > help mmc 一、mmc dev 使用mmc list可以看到当前系统挂载的所有mmc设备ÿ…...
【Kafka】编写消费者开发模式时遇到‘未解析的引用‘SIGUSR1’’
在编写消费者开发模式时,不要用简单的consumer,会导致消费数据不全的情况,需要用ConsumerGroup。 代码可以参考官方实例:https://github.com/Shopify/sarama/tree/main/examples/consumergroup 问题描述: 编写消费者开…...
DeepSeek 赋能教育游戏化:AI 重构学习体验的技术密码
目录 一、引言:教育游戏化与 DeepSeek 的相遇二、DeepSeek 技术剖析2.1 核心架构2.2 关键技术 三、教育游戏化设计的奥秘3.1 概念与意义3.2 常见方法与元素3.3 成功案例借鉴 四、DeepSeek 在教育游戏化设计中的多面应用4.1 个性化学习路径打造4.2 智能教学辅助工具4…...
Docker run命令-p参数详解
端口映射基础语法 docker run -p <宿主机端口>:<容器端口> 操作示例 docker run -d --restartalways --namespug -p 5000:80 registry.aliyuncs.com/openspug/spug参数解析 -d:后台运行容器--restartalways:设置容器自动重启--namespug&…...

知识宇宙-学习篇:学编程为什么从C语言开始学起?
名人说:博观而约取,厚积而薄发。——苏轼《稼说送张琥》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、C语言的历史地位与影响力1. 编程语言的"鼻祖"2. 现代技术的基础 二、…...

Mybatis-入门程序、 数据库连接池、XML映射配置文件、MybatisX
一. Mybatis 1. Mybatis是一款优秀的持久层框架,用于简化jdbc的开发 2. Mybatis本是Apache的一个开源项目iBatis,2010年这个项目有Apache迁移到了Google code,并且改名为MyBatis,2013年11月迁移到Github 3.官网:MyBat…...
互联网大厂Java求职面试:Spring Cloud微服务架构设计中的挑战与解决方案
互联网大厂Java求职面试:Spring Cloud微服务架构设计中的挑战与解决方案 面试场景设定 郑薪苦是一位拥有丰富实战经验的Java开发者,他正在参加一场由某知名互联网大厂的技术总监主持的面试。这场面试将围绕Spring Cloud微服务架构展开,涵盖…...

BUUCTF [ZJCTF 2019]EasyHeap
前置知识点: unlink知识点和手法-CSDN博客 [ZJCTF 2019]EasyHeap [ZJCTF 2019]EasyHeap 1.准备 2.ida分析 main函数 int __fastcall __noreturn main(int argc, const char **argv, const char **envp) {int n3; // eaxchar buf[8]; // [rsp0h] [rbp-10h] BYREFunsigned …...