SoFunction
Updated on 2025-05-22

Perfect solution for findingcontours crash in C/C++

Solve Windows Platform OpenCVfindContoursCrash: A more stable way

Many developers using OpenCV on Windows platforms may usefindContoursWhen 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 effectivelyfindContoursIt is crucial to understand the internal mechanism of the exception caused.cv::findContoursThe 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 stylecvFindContoursFunctions often work normally, which implies that the problem is most likely in the C++ encapsulation layer's processing of data structures.

Read carefullycv::findContoursThe 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 &lt; 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_contoursWhen allocating memory, especially afterwards_contours.getMat(i)GetMatUse objects togethercvCvtSeqToArrayWhen populating the data, it maystd::vector<std::vector<cv::Point>>The internal memory layout makes certain assumptions. This will directlyCvSeqThe data in theMatmanaged memory area, if the memory area cannot bestd::vectorCorrect identification and management may lead to memory corruption.

The reason is speculated to be:

  • _contours.create()The method may not be fully applicable tostd::vectorThis complex type of memory allocation and initialization.
  • std::vectorThe 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::vectorThe 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 bottomcvFindContoursThe function is relatively stable, so we can bypass itcv::findContoursMemory operations that may have problems in the memory, by re-encapsulatingcvFindContoursTo implement a safer and more controllable outline lookup function.

The following are the customizations providedfindContoursFunction implementation, it directly calls the C interface and manages it manuallystd::vectorData population:

#include <opencv2//Include specific header files as needed, such as ,#include &lt;vector&gt;
// Note: This function signature and implementation comes from the original code you providevoid findContours_custom(const cv::Mat&amp; src,
                         std::vector&lt;std::vector&lt;cv::Point&gt;&gt;&amp; contours,
                         std::vector&lt;cv::Vec4i&gt;&amp; 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&lt;int&gt;(mutable_src.step[0]); // explicitly convert size_t to int    c_image.type = (c_image.type &amp; ~cv::Mat::CONTINUOUS_FLAG) | (mutable_src.flags &amp; 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 &lt;= 6
    cvFindContours(&amp;c_image, storage, &amp;_ccontours, sizeof(CvContour), retr, method, cvPoint(, ));
#else
    cvFindContours(&amp;c_image, storage, &amp;_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&lt;CvSeq*&gt; 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&lt;CvSeq*&gt; it = all_contours.begin();
    for (size_t i = 0; i &lt; 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&lt;CvContour*&gt;(c)-&gt;color = static_cast&lt;int&gt;(i);
        int count = c-&gt;total; // The number of points contained in the current outline        if (count &gt; 0) {
            // The original code you provided uses new int[] to transfer point coordinates            int* data = new int[static_cast&lt;size_t&gt;(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 &lt; 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 &lt; total; ++i, ++it)
    {
        CvSeq* c = *it;
        // Get hierarchical relationships through the previously set color (i.e. index)        int h_next = c-&gt;h_next ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;h_next)-&gt;color : -1;
        int h_prev = c-&gt;h_prev ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;h_prev)-&gt;color : -1;
        int v_next = c-&gt;v_next ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;v_next)-&gt;color : -1; // The first sub-contour        int v_prev = c-&gt;v_prev ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;v_prev)-&gt;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 callcvFindContours, avoid suspicious memory operations in the C++ encapsulation layer.
  • Secure memory management:usecv::MemStorageManage memory for C functions.
  • Explicit data conversion and fill
    • passcvTreeToNodeSeqGet a flat list of all outlines, which simplifies iterative and hierarchical construction.
    • forstd::vector<std::vector<cv::Point>> contoursandstd::vector<cv::Vec4i> hierarchyCallresizeMake pre-allocated.
    • For eachCvSeq, your original solution is to use it firstcvCvtSeqToArrayRead point data into a temporaryintArraydata, and then iterate over thisdataArray, point by point constructioncv::PointObject andpush_backGo to the correspondingcontours[i]middle.
    • Although this method has one more step forward, it ensuresstd::vectorManages the memory of its elements completely autonomously.
  • Hierarchical information construction: By traversing the outline for the first timeCvContourofcolorThe member is set to itsall_contoursIndex in the sequence, then use this index to correctly construct on the second traversalhierarchyvector.

Although this method has a little more code, it gives developers greater control over memory operations, thus effectively evading the standardscv::findContoursMemory issues that C++ interfaces may cause in certain situations.

Test cases

Here is one that uses the abovefindContours_customA C++ sample program for functions.

test_custom_findcontours_cn.cpp:

#include &lt;opencv2/&gt;
#include &lt;opencv2/&gt;
#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 &lt;iostream&gt;
#include &lt;vector&gt;
// --- [Paste the findContours_custom function code provided above to this point] ---void findContours_custom(const cv::Mat&amp; src,
                         std::vector&lt;std::vector&lt;cv::Point&gt;&gt;&amp; contours,
                         std::vector&lt;cv::Vec4i&gt;&amp; hierarchy,
                         int retr,
                         int method,
                         cv::Point offset = cv::Point())
{
    ();
    ();
#if CV_VERSION_REVISION &lt;= 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&lt;int&gt;(mutable_src_for_c_api.step[0]);
    c_image.type = (c_image.type &amp; ~cv::Mat::CONTINUOUS_FLAG) | (mutable_src_for_c_api.flags &amp; cv::Mat::CONTINUOUS_FLAG);
#endif
    cv::MemStorage storage(cvCreateMemStorage(0));
    CvSeq* _ccontours = nullptr;
#if CV_VERSION_REVISION &lt;= 6
    cvFindContours(&amp;c_image, storage, &amp;_ccontours, sizeof(CvContour), retr, method, cvPoint(, ));
#else
    cvFindContours(&amp;c_image, storage, &amp;_ccontours, sizeof(CvContour), retr, method, cvPoint(, ));
#endif
    if (!_ccontours)
    {
        ();
        ();
        return;
    }
    cv::Seq&lt;CvSeq*&gt; all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));
    size_t total = all_contours.size();
    (total);
    (total);
    cv::SeqIterator&lt;CvSeq*&gt; it = all_contours.begin();
    for (size_t i = 0; i &lt; total; ++i, ++it)
    {
        CvSeq* c = *it;
        reinterpret_cast&lt;CvContour*&gt;(c)-&gt;color = static_cast&lt;int&gt;(i);
        int count = c-&gt;total;
        if (count &gt; 0) {
            int* data = new int[static_cast&lt;size_t&gt;(count * 2)];
            cvCvtSeqToArray(c, data, CV_WHOLE_SEQ);
            contours[i].reserve(count);
            for (int j = 0; j &lt; 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 &lt; total; ++i, ++it)
    {
        CvSeq* c = *it;
        int h_next = c-&gt;h_next ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;h_next)-&gt;color : -1;
        int h_prev = c-&gt;h_prev ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;h_prev)-&gt;color : -1;
        int v_next = c-&gt;v_next ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;v_next)-&gt;color : -1;
        int v_prev = c-&gt;v_prev ? reinterpret_cast&lt;CvContour*&gt;(c-&gt;v_prev)-&gt;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 &lt;&lt; "Error: Cannot create sample image." &lt;&lt; std::endl;
        return -1;
    }
    // 2. Prepare the output container    std::vector&lt;std::vector&lt;cv::Point&gt;&gt; contours_vec;
    std::vector&lt;cv::Vec4i&gt; 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 &lt;&lt; "The number of contours found by custom functions: " &lt;&lt; contours_vec.size() &lt;&lt; std::endl;
    for (size_t i = 0; i &lt; contours_vec.size(); ++i) {
        std::cout &lt;&lt; "Outline #" << i << ": " << contours_vec[i].size() << " dots. ";        std::cout &lt;&lt; "Level information: " &lt;&lt; hierarchy_vec[i] &lt;&lt; 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 &lt; 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-configNot configured correctly, please adjust accordinglyopencv4foropencvor your actual library name and path).

This test case creates a simple image containing a nested structure, callfindContours_customFunction, 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!