欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 幼教 > c/c++的findcontours崩溃解决方案

c/c++的findcontours崩溃解决方案

2025/9/18 16:26:45 来源:https://blog.csdn.net/m0_54069809/article/details/148040059  浏览:    关键词:c/c++的findcontours崩溃解决方案

解决 Windows 平台 OpenCV findContours 崩溃:一种更稳定的方法

许多在 Windows 平台上使用 OpenCV 的开发者可能会在使用 findContours 函数时,遇到令人头疼的程序崩溃问题。尽管网络上流传着多种解决方案,但它们并非总能根治此问题。
当时我也是挨个排查才找到原来是findcontours的崩溃,他奔溃的在内存上,有些图不崩溃有些图必崩溃搞得很莫名其妙,今天就来讲讲我的解决方案

常见的“药方”包括:

  1. 修改项目配置:配置属性 -> 常规 -> 项目默认值 -> MFC的使用 -> 在共享DLL中使用MFC
  2. 调整C/C++代码生成选项:C/C++ -> 代码生成 -> 运行库 -> 多线程DLL(/MD)
  3. 代码层面规范:例如 vector 使用 cv::vectorvector<vector<Point>> 声明时预分配空间等。

然而,现实情况是,许多开发者尝试上述方法后,问题依旧。即便少数情况下问题得到偶然解决,程序在迁移到不同环境或 OpenCV 版本时,仍可能面临兼容性风险。

本文将深入剖析此问题的潜在原因,并提供一个更可靠的定制化实现方案。

探究崩溃的根源

为了有效地解决 findContours 引发的异常,理解其内部机制至关重要。cv::findContours 的C++接口实际上是对底层C语言风格函数 cvFindContours(或其变体 cvFindContours_Impl)的一层封装。

一个关键的观察点是:直接调用C语言风格的 cvFindContours 函数往往能够正常运行,这暗示问题很可能出在C++封装层对数据结构的处理上。

仔细研读 cv::findContours 的源码(尽管具体实现可能随版本略有差异),我们会注意到其处理输出参数 _contours(通常是 std::vector<std::vector<cv::Point>> 类型)的方式:

// OpenCV findContours 源码示意片段
void cv::findContours( InputOutputArray _image, OutputArrayOfArrays _contours,OutputArray _hierarchy, int mode, int method, Point offset )
{// ... (一系列检查和准备工作) ...// _contours 的内存分配与数据填充,示意如下:// _contours.create(total, 1, 0, -1, true); // 为所有轮廓的集合分配概要空间// ...// for( i = 0; i < total; i++, ++it )// {//     CvSeq* c = *it;//     // ...//     _contours.create((int)c->total, 1, CV_32SC2, i, true); // 为单个轮廓分配空间//     Mat ci = _contours.getMat(i); // 获取该轮廓对应的 Mat 头//     cvCvtSeqToArray(c, ci.ptr()); // 将 CvSeq 数据拷贝到 Mat 指向的内存// }// ...
}

上述代码片段揭示了潜在的风险点:OpenCV 在为 _contours 分配内存时,尤其是后续通过 _contours.getMat(i) 获取 Mat 对象并用 cvCvtSeqToArray 填充数据时,它可能对 std::vector<std::vector<cv::Point>> 的内部内存布局做出了某些假设。这种直接将 CvSeq 中的数据拷贝到由 Mat 管理的内存区域,如果该内存区域未能被 std::vector 正确识别和管理,就可能导致内存损坏。

推测原因为:

  1. _contours.create() 方法可能不完全适用于 std::vector 这种复杂类型的内存分配和初始化。
  2. std::vector 的数据存储并非总是能被简单地视为一块连续内存区域,并允许通过外部指针直接进行填充,特别是对于嵌套的 vector

这种不匹配的操作极易破坏 std::vector 的内部状态,最终导致程序在后续访问这些轮廓数据时发生崩溃。

更稳健的解决方案:自定义封装 cvFindContours

既然底层的 cvFindContours 函数相对稳定,那么我们可以绕过 cv::findContours 中可能存在问题的内存操作,通过重新封装 cvFindContours 来实现一个更安全、更可控的轮廓查找函数。

以下是提供的自定义 findContours 函数实现,它直接调用C接口并手动管理 std::vector 的数据填充:

#include <opencv2/opencv.hpp> // 根据需要包含具体的头文件,如 imgproc.hpp, core.hpp
#include <vector>// 注意:此函数签名和实现源自您提供的原始代码
void findContours_custom(const cv::Mat& src,std::vector<std::vector<cv::Point>>& contours,std::vector<cv::Vec4i>& hierarchy,int retr,int method,cv::Point offset = cv::Point())
{contours.clear(); // 清空输出hierarchy.clear();// 根据OpenCV版本处理CvMat,您提供的代码片段如下:
#if CV_VERSION_REVISION <= 6 // 注意:CV_VERSION_REVISION 是较老版本OpenCV的宏CvMat c_image = src; // 在旧版本中,cv::Mat可以直接转换为CvMat// 但请注意,cvFindContours可能会修改图像,所以最好使用副本// CvMat c_image = src.clone(); 这样更安全
#else// 对于较新的OpenCV版本 (3.x, 4.x)cv::Mat mutable_src = src.clone(); // cvFindContours会修改输入图像,务必使用副本CvMat c_image = cvMat(mutable_src.rows, mutable_src.cols, mutable_src.type(), mutable_src.data);c_image.step = static_cast<int>(mutable_src.step[0]); // 显式转换size_t到intc_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (mutable_src.flags & cv::Mat::CONTINUOUS_FLAG);
#endifcv::MemStorage storage(cvCreateMemStorage(0)); // 创建内存存储区CvSeq* _ccontours = nullptr; // C风格的轮廓序列指针// 根据OpenCV版本调用cvFindContours,您提供的代码片段如下:
#if CV_VERSION_REVISION <= 6cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y));
#elsecvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y)); // CvPoint构造方式一致
#endifif (!_ccontours) // 如果没有找到轮廓{contours.clear(); // 再次确保清空hierarchy.clear();// storage 会在 cv::MemStorage 对象析构时自动释放return;}// 使用 cvTreeToNodeSeq 获取所有轮廓的扁平序列,这对于后续处理(尤其是层级结构)更方便cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));size_t total = all_contours.size();contours.resize(total); // 为轮廓数据预分配空间hierarchy.resize(total); // 为层级数据预分配空间cv::SeqIterator<CvSeq*> it = all_contours.begin();for (size_t i = 0; i < total; ++i, ++it){CvSeq* c = *it;// 将轮廓的颜色(CvContour的成员)设置为其索引,用于后续层级信息的链接reinterpret_cast<CvContour*>(c)->color = static_cast<int>(i);int count = c->total; // 当前轮廓包含的点数if (count > 0) {// 您提供的原始代码中使用 new int[] 来中转点坐标int* data = new int[static_cast<size_t>(count * 2)]; // 分配临时内存存储x,y坐标对cvCvtSeqToArray(c, data, CV_WHOLE_SEQ); // 将CvSeq中的点集数据拷贝到data数组contours[i].reserve(count); // 为当前轮廓的点集预分配空间for (int j = 0; j < count; ++j) {contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1]));}delete[] data; // 释放临时内存}}// 填充层级信息 (hierarchy)it = all_contours.begin(); // 重置迭代器for (size_t i = 0; i < total; ++i, ++it){CvSeq* c = *it;// 通过之前设置的 color (即索引) 来获取层级关系int h_next = c->h_next ? reinterpret_cast<CvContour*>(c->h_next)->color : -1;int h_prev = c->h_prev ? reinterpret_cast<CvContour*>(c->h_prev)->color : -1;int v_next = c->v_next ? reinterpret_cast<CvContour*>(c->v_next)->color : -1; // 第一个子轮廓int v_prev = c->v_prev ? reinterpret_cast<CvContour*>(c->v_prev)->color : -1; // 父轮廓hierarchy[i] = cv::Vec4i(h_next, h_prev, v_next, v_prev);}// storage 会在 cv::MemStorage 对象析构时自动释放,无需显式调用 cvReleaseMemStorage
}

自定义函数的关键改进点:

  • 直接调用C接口:函数核心是调用 cvFindContours,避免了C++封装层中可疑的内存操作。
  • 安全的内存管理:使用 cv::MemStorage 为C函数管理内存。
  • 显式数据转换与填充
    • 通过 cvTreeToNodeSeq 获取所有轮廓的扁平列表,这简化了迭代和层级构建。
    • std::vector<std::vector<cv::Point>> contoursstd::vector<cv::Vec4i> hierarchy 调用 resize 进行预分配。
    • 对于每个 CvSeq,您的原始方案是先用 cvCvtSeqToArray 将点数据读入一个临时的 int 数组 data,然后再遍历这个 data 数组,逐点构造 cv::Point 对象并 push_back 到对应的 contours[i] 中。这种方式虽然多了一步中转,但确保了 std::vector 完全自主地管理其元素的内存。
  • 层级信息构建:通过在第一次遍历轮廓时将 CvContourcolor 成员设置为其在 all_contours序列中的索引,然后在第二次遍历时利用这个索引来正确构建 hierarchy 向量。

这种方法虽然代码量稍多,但给予了开发者对内存操作更大的控制权,从而有效规避了标准 cv::findContours C++ 接口在特定情况下可能引发的内存问题。

测试用例

下面是一个使用上述 findContours_custom 函数的C++示例程序。

test_custom_findcontours_cn.cpp:

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/types_c.h>    // 为了 CvMat, CvSeq 等C语言结构
#include <opencv2/imgproc/types_c.h> // 为了 CV_*, CvContour, CvPoint 等
#include <iostream>
#include <vector>// --- [粘贴上面提供的 findContours_custom 函数代码到这里] ---
void findContours_custom(const cv::Mat& src,std::vector<std::vector<cv::Point>>& contours,std::vector<cv::Vec4i>& hierarchy,int retr,int method,cv::Point offset = cv::Point())
{contours.clear();hierarchy.clear();#if CV_VERSION_REVISION <= 6cv::Mat mutable_src_for_c_api = src.clone(); // 为旧版API准备可修改的副本CvMat c_image = mutable_src_for_c_api;
#elsecv::Mat mutable_src_for_c_api = src.clone();CvMat c_image = cvMat(mutable_src_for_c_api.rows, mutable_src_for_c_api.cols, mutable_src_for_c_api.type(), mutable_src_for_c_api.data);c_image.step = static_cast<int>(mutable_src_for_c_api.step[0]);c_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (mutable_src_for_c_api.flags & cv::Mat::CONTINUOUS_FLAG);
#endifcv::MemStorage storage(cvCreateMemStorage(0));CvSeq* _ccontours = nullptr;#if CV_VERSION_REVISION <= 6cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y));
#elsecvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y));
#endifif (!_ccontours){contours.clear();hierarchy.clear();return;}cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));size_t total = all_contours.size();contours.resize(total);hierarchy.resize(total);cv::SeqIterator<CvSeq*> it = all_contours.begin();for (size_t i = 0; i < total; ++i, ++it){CvSeq* c = *it;reinterpret_cast<CvContour*>(c)->color = static_cast<int>(i);int count = c->total;if (count > 0) {int* data = new int[static_cast<size_t>(count * 2)];cvCvtSeqToArray(c, data, CV_WHOLE_SEQ);contours[i].reserve(count);for (int j = 0; j < count; ++j) {contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1]));}delete[] data;}}it = all_contours.begin();for (size_t i = 0; i < total; ++i, ++it){CvSeq* c = *it;int h_next = c->h_next ? reinterpret_cast<CvContour*>(c->h_next)->color : -1;int h_prev = c->h_prev ? reinterpret_cast<CvContour*>(c->h_prev)->color : -1;int v_next = c->v_next ? reinterpret_cast<CvContour*>(c->v_next)->color : -1;int v_prev = c->v_prev ? reinterpret_cast<CvContour*>(c->v_prev)->color : -1;hierarchy[i] = cv::Vec4i(h_next, h_prev, v_next, v_prev);}
}
// --- [findContours_custom 函数代码结束] ---int main() {// 1. 创建一个示例二值图像cv::Mat image = cv::Mat::zeros(300, 300, CV_8UC1); // 黑色背景// 绘制一个白色外层矩形cv::rectangle(image, cv::Rect(30, 30, 240, 240), cv::Scalar(255), cv::FILLED);// 在外层矩形内部绘制一个黑色矩形(形成一个“洞”)cv::rectangle(image, cv::Rect(80, 80, 140, 140), cv::Scalar(0), cv::FILLED);// 再绘制一个独立的白色小矩形cv::rectangle(image, cv::Rect(10, 10, 50, 50), cv::Scalar(255), cv::FILLED);if (image.empty()) {std::cerr << "错误:无法创建示例图像。" << std::endl;return -1;}// 2. 准备输出容器std::vector<std::vector<cv::Point>> contours_vec;std::vector<cv::Vec4i> hierarchy_vec;// 3. 调用自定义的 findContours_custom 函数// 使用 cv::RETR_TREE 来测试层级结构findContours_custom(image, contours_vec, hierarchy_vec, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);// 4. 输出结果std::cout << "自定义函数找到的轮廓数量: " << contours_vec.size() << std::endl;for (size_t i = 0; i < contours_vec.size(); ++i) {std::cout << "轮廓 #" << i << ": " << contours_vec[i].size() << " 个点. ";std::cout << "层级信息: " << hierarchy_vec[i] << std::endl;}// 5. 可选: 显示结果图像cv::Mat contour_output_image = cv::Mat::zeros(image.size(), CV_8UC3);cv::RNG rng(12345); // 用于生成随机颜色for (size_t i = 0; i < contours_vec.size(); i++) {// 为每个轮廓随机选择一种颜色cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));// 绘制轮廓cv::drawContours(contour_output_image, contours_vec, (int)i, color, 2, cv::LINE_8, hierarchy_vec, 0);}cv::imshow("原始测试图像", image);cv::imshow("检测到的轮廓 (自定义函数)", contour_output_image);cv::waitKey(0); // 等待按键return 0;
}

编译和运行示例 (使用g++):

g++ test_custom_findcontours_cn.cpp -o test_custom_findcontours_cn $(pkg-config --cflags --libs opencv4)
./test_custom_findcontours_cn

(如果你的 OpenCV 版本不是4,或者 pkg-config 未正确配置,请相应调整 opencv4opencv 或你的实际库名和路径)。

此测试用例会创建一个包含嵌套结构的简单图像,调用 findContours_custom 函数,打印检测到的轮廓数量及其层级信息,并最终将检测结果可视化显示。在之前可能导致崩溃的 Windows 环境下,此自定义函数应该能够稳定运行。

通过采用这种自定义封装策略,开发者可以更从容地应对 OpenCV 在特定平台下可能出现的稳定性问题,确保轮廓检测功能的可靠性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词