Solve Windows Platform OpenCVfindContours
Crash: A more stable way
Many developers using OpenCV on Windows platforms may usefindContours
When you are in a function, you encounter a headache program crash problem. Although there are many solutions circulating on the Internet, they don’t always cure this problem.
At that time, I also checked one by one to find out that it was the crash of findcontours. It crashed in memory. Some pictures did not crash and some pictures would crash. It was very inexplicable. Today I will talk about my solution.
Common "prescriptions" include:
- Modify project configuration: Configuration properties -> General -> Project defaults -> Use of MFC -> Use of MFC in shared DLLs;
- Adjust the C/C++ code generation options: C/C++ -> Code generation -> Running Library -> Multi-threaded DLL (/MD);
- Code-level specifications: For example, vector uses cv::vector, vector<vector<Point>> to preallocate space, etc.
However, the reality is that many developers still have problems after trying the above method. Even if the problem is solved by chance in a few cases, programs may still face compatibility risks when migrating to different environments or versions of OpenCV.
This article will analyze the potential causes of this problem in depth and provide a more reliable customized implementation solution.
Explore the root of the collapse
To solve it effectivelyfindContours
It is crucial to understand the internal mechanism of the exception caused.cv::findContours
The C++ interface is actually a function of the underlying C language stylecvFindContours
(or its variantcvFindContours_Impl
) a layer of packaging.
A key observation point is: direct call to C language stylecvFindContours
Functions often work normally, which implies that the problem is most likely in the C++ encapsulation layer's processing of data structures.
Read carefullycv::findContours
The source code of the code (although the specific implementation may vary slightly with the version), we will notice that it processes the output parameters_contours
(usuallystd::vector<std::vector<cv::Point>>
Type) way:
// OpenCV findContours source code schematic fragmentvoid cv::findContours( InputOutputArray _image, OutputArrayOfArrays _contours, OutputArray _hierarchy, int mode, int method, Point offset ) { // ... (A series of inspections and preparations) ... // The memory allocation and data filling of _contours are shown as follows: // _contours.create(total, 1, 0, -1, true); // Allocate summary space for collections of all contours // ... // for( i = 0; i < total; i++, ++it ) // { // CvSeq* c = *it; // // ... // _contours.create((int)c->total, 1, CV_32SC2, i, true); // Allocate space for a single contour // Mat ci = _contours.getMat(i); // Get the Mat header corresponding to this outline // cvCvtSeqToArray(c, ()); // Copy the CvSeq data to the memory pointed to by Mat // } // ... }
The above code snippet reveals potential risk points: OpenCV is_contours
When allocating memory, especially afterwards_contours.getMat(i)
GetMat
Use objects togethercvCvtSeqToArray
When populating the data, it maystd::vector<std::vector<cv::Point>>
The internal memory layout makes certain assumptions. This will directlyCvSeq
The data in theMat
managed memory area, if the memory area cannot bestd::vector
Correct identification and management may lead to memory corruption.
The reason is speculated to be:
-
_contours.create()
The method may not be fully applicable tostd::vector
This complex type of memory allocation and initialization. -
std::vector
The data storage can not always be simply considered a continuous memory area and allows direct filling through external pointers, especially for nestedvector
。
This mismatch operation is extremely easy to breakstd::vector
The internal state of the program ultimately causes the program to crash when subsequent access to these contour data.
More robust solution: Custom packagecvFindContours
Since it's at the bottomcvFindContours
The function is relatively stable, so we can bypass itcv::findContours
Memory operations that may have problems in the memory, by re-encapsulatingcvFindContours
To implement a safer and more controllable outline lookup function.
The following are the customizations providedfindContours
Function implementation, it directly calls the C interface and manages it manuallystd::vector
Data population:
#include <opencv2//Include specific header files as needed, such as ,#include <vector> // Note: This function signature and implementation comes from the original code you providevoid 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()) { (); // Clear the output (); // Process CvMat according to the OpenCV version, the code snippet you provide is as follows:#if CV_VERSION_REVISION <= 6 // Note: CV_VERSION_REVISION is a macro of older versions of OpenCV CvMat c_image = src; // In the old version, cv::Mat can be directly converted to CvMat // But please note that cvFindContours may modify the image, so it is better to use a copy // CvMat c_image = (); This is safer#else // For newer OpenCV versions (, ) cv::Mat mutable_src = (); // cvFindContours will modify the input image, be sure to use a copy 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]); // explicitly convert size_t to int c_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (mutable_src.flags & cv::Mat::CONTINUOUS_FLAG); #endif cv::MemStorage storage(cvCreateMemStorage(0)); // Create memory storage area CvSeq* _ccontours = nullptr; // C-style contour sequence pointer// Call cvFindContours according to the OpenCV version, the code snippet you provide is as follows:#if CV_VERSION_REVISION <= 6 cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(, )); #else cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(, )); // The CvPoint construction method is consistent#endif if (!_ccontours) // If no outline is found { (); // Make sure to clear again (); // storage will be automatically released when the cv::MemStorage object is destructed return; } // Use cvTreeToNodeSeq to get flat sequences of all contours, which is more convenient for subsequent processing (especially hierarchical structures) cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage)); size_t total = all_contours.size(); (total); // Preallocate space for contour data (total); // Preallocate space for hierarchical data cv::SeqIterator<CvSeq*> it = all_contours.begin(); for (size_t i = 0; i < total; ++i, ++it) { CvSeq* c = *it; // Set the color of the outline (a member of CvContour) to its index for linking subsequent hierarchical information reinterpret_cast<CvContour*>(c)->color = static_cast<int>(i); int count = c->total; // The number of points contained in the current outline if (count > 0) { // The original code you provided uses new int[] to transfer point coordinates int* data = new int[static_cast<size_t>(count * 2)]; // Allocate temporary memory storage x and y coordinate pairs cvCvtSeqToArray(c, data, CV_WHOLE_SEQ); // Copy the point set data in CvSeq to the data array contours[i].reserve(count); // Preallocate space for the point set of the current outline for (int j = 0; j < count; ++j) { contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1])); } delete[] data; // Release temporary memory } } // Fill in hierarchy it = all_contours.begin(); // Reset the iterator for (size_t i = 0; i < total; ++i, ++it) { CvSeq* c = *it; // Get hierarchical relationships through the previously set color (i.e. index) 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; // The first sub-contour int v_prev = c->v_prev ? reinterpret_cast<CvContour*>(c->v_prev)->color : -1; // Parent profile hierarchy[i] = cv::Vec4i(h_next, h_prev, v_next, v_prev); } // storage will be automatically released when the cv::MemStorage object is destructed, without explicitly calling cvReleaseMemStorage}
Key improvements to custom functions:
-
Directly call C interface: The core of the function is the call
cvFindContours
, avoid suspicious memory operations in the C++ encapsulation layer. -
Secure memory management:use
cv::MemStorage
Manage memory for C functions. -
Explicit data conversion and fill:
- pass
cvTreeToNodeSeq
Get a flat list of all outlines, which simplifies iterative and hierarchical construction. - for
std::vector<std::vector<cv::Point>> contours
andstd::vector<cv::Vec4i> hierarchy
Callresize
Make pre-allocated. - For each
CvSeq
, your original solution is to use it firstcvCvtSeqToArray
Read point data into a temporaryint
Arraydata
, and then iterate over thisdata
Array, point by point constructioncv::Point
Object andpush_back
Go to the correspondingcontours[i]
middle. - Although this method has one more step forward, it ensures
std::vector
Manages the memory of its elements completely autonomously.
- pass
-
Hierarchical information construction: By traversing the outline for the first time
CvContour
ofcolor
The member is set to itsall_contours
Index in the sequence, then use this index to correctly construct on the second traversalhierarchy
vector.
Although this method has a little more code, it gives developers greater control over memory operations, thus effectively evading the standardscv::findContours
Memory issues that C++ interfaces may cause in certain situations.
Test cases
Here is one that uses the abovefindContours_custom
A C++ sample program for functions.
test_custom_findcontours_cn.cpp
:
#include <opencv2/> #include <opencv2/> #include <opencv2/core/types_c.h> // For C language structures such as CvMat, CvSeq#include <opencv2/imgproc/types_c.h> // For CV_*, CvContour, CvPoint, etc.#include <iostream> #include <vector> // --- [Paste the findContours_custom function code provided above to this point] ---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()) { (); (); #if CV_VERSION_REVISION <= 6 cv::Mat mutable_src_for_c_api = (); // Prepare a modifyable copy for the old API CvMat c_image = mutable_src_for_c_api; #else cv::Mat mutable_src_for_c_api = (); 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); #endif cv::MemStorage storage(cvCreateMemStorage(0)); CvSeq* _ccontours = nullptr; #if CV_VERSION_REVISION <= 6 cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(, )); #else cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(, )); #endif if (!_ccontours) { (); (); return; } cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage)); size_t total = all_contours.size(); (total); (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 function code ends] ---int main() { // 1. Create a sample binary image cv::Mat image = cv::Mat::zeros(300, 300, CV_8UC1); // Black background // Draw a white outer rectangle cv::rectangle(image, cv::Rect(30, 30, 240, 240), cv::Scalar(255), cv::FILLED); // Draw a black rectangle inside the outer rectangle (forming a "hole") cv::rectangle(image, cv::Rect(80, 80, 140, 140), cv::Scalar(0), cv::FILLED); // Draw a separate small white rectangle cv::rectangle(image, cv::Rect(10, 10, 50, 50), cv::Scalar(255), cv::FILLED); if (()) { std::cerr << "Error: Cannot create sample image." << std::endl; return -1; } // 2. Prepare the output container std::vector<std::vector<cv::Point>> contours_vec; std::vector<cv::Vec4i> hierarchy_vec; // 3. Call the custom findContours_custom function // Use cv::RETR_TREE to test the hierarchy findContours_custom(image, contours_vec, hierarchy_vec, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); // 4. Output result std::cout << "The number of contours found by custom functions: " << contours_vec.size() << std::endl; for (size_t i = 0; i < contours_vec.size(); ++i) { std::cout << "Outline #" << i << ": " << contours_vec[i].size() << " dots. "; std::cout << "Level information: " << hierarchy_vec[i] << std::endl; } // 5. Optional: Display the result image cv::Mat contour_output_image = cv::Mat::zeros((), CV_8UC3); cv::RNG rng(12345); // Used to generate random colors for (size_t i = 0; i < contours_vec.size(); i++) { // Randomly select a color for each outline cv::Scalar color = cv::Scalar((0, 256), (0, 256), (0, 256)); // Draw outlines cv::drawContours(contour_output_image, contours_vec, (int)i, color, 2, cv::LINE_8, hierarchy_vec, 0); } cv::imshow("Original Test Image", image); cv::imshow("Detected profile (Custom functions)", contour_output_image); cv::waitKey(0); // Wait for button return 0; }
Compile and run examples (using g++):
g++ test_custom_findcontours_cn.cpp -o test_custom_findcontours_cn $(pkg-config --cflags --libs opencv4) ./test_custom_findcontours_cn
(If your OpenCV version is not 4, orpkg-config
Not configured correctly, please adjust accordinglyopencv4
foropencv
or your actual library name and path).
This test case creates a simple image containing a nested structure, callfindContours_custom
Function, prints the number of detected contours and their hierarchy information, and finally visualizes the detection results. In Windows environments that may have caused crashes, this custom function should run stably.
By adopting this custom packaging strategy, developers can more calmly deal with the stability problems that may arise in OpenCV on a specific platform and ensure the reliability of the contour detection function.
This is the article about the C/c++ crash solution. For more related C++ findcontours crash content, please search for my previous articles or continue browsing the following related articles. I hope everyone will support me in the future!