亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

剖析c/c++的findcontours崩潰完美解決方案

 更新時間:2025年05月22日 09:32:58   作者:whoarethenext  
許多在Windows平臺上使用OpenCV的開發(fā)者可能會在使用findContours 函數(shù)時,遇到令人頭疼的程序崩潰問題,本文將深入剖析此問題的潛在原因,并提供一個更可靠的定制化實現(xiàn)方案,感興趣的朋友一起看看吧

解決 Windows 平臺 OpenCV findContours 崩潰:一種更穩(wěn)定的方法

許多在 Windows 平臺上使用 OpenCV 的開發(fā)者可能會在使用 findContours 函數(shù)時,遇到令人頭疼的程序崩潰問題。盡管網(wǎng)絡(luò)上流傳著多種解決方案,但它們并非總能根治此問題。
當時我也是挨個排查才找到原來是findcontours的崩潰,他奔潰的在內(nèi)存上,有些圖不崩潰有些圖必崩潰搞得很莫名其妙,今天就來講講我的解決方案

常見的“藥方”包括:

  • 修改項目配置:配置屬性 -> 常規(guī) -> 項目默認值 -> MFC的使用 -> 在共享DLL中使用MFC;
  • 調(diào)整C/C++代碼生成選項:C/C++ -> 代碼生成 -> 運行庫 -> 多線程DLL(/MD);
  • 代碼層面規(guī)范:例如 vector 使用 cv::vector,vector<vector<Point>> 聲明時預(yù)分配空間等。

然而,現(xiàn)實情況是,許多開發(fā)者嘗試上述方法后,問題依舊。即便少數(shù)情況下問題得到偶然解決,程序在遷移到不同環(huán)境或 OpenCV 版本時,仍可能面臨兼容性風險。

本文將深入剖析此問題的潛在原因,并提供一個更可靠的定制化實現(xiàn)方案。

探究崩潰的根源

為了有效地解決 findContours 引發(fā)的異常,理解其內(nèi)部機制至關(guān)重要。cv::findContours 的C++接口實際上是對底層C語言風格函數(shù) cvFindContours(或其變體 cvFindContours_Impl)的一層封裝。

一個關(guān)鍵的觀察點是:直接調(diào)用C語言風格的 cvFindContours 函數(shù)往往能夠正常運行,這暗示問題很可能出在C++封裝層對數(shù)據(jù)結(jié)構(gòu)的處理上。

仔細研讀 cv::findContours 的源碼(盡管具體實現(xiàn)可能隨版本略有差異),我們會注意到其處理輸出參數(shù) _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 的內(nèi)存分配與數(shù)據(jù)填充,示意如下:
    // _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); // 獲取該輪廓對應(yīng)的 Mat 頭
    //     cvCvtSeqToArray(c, ci.ptr()); // 將 CvSeq 數(shù)據(jù)拷貝到 Mat 指向的內(nèi)存
    // }
    // ...
}

上述代碼片段揭示了潛在的風險點:OpenCV 在為 _contours 分配內(nèi)存時,尤其是后續(xù)通過 _contours.getMat(i) 獲取 Mat 對象并用 cvCvtSeqToArray 填充數(shù)據(jù)時,它可能對 std::vector<std::vector<cv::Point>> 的內(nèi)部內(nèi)存布局做出了某些假設(shè)。這種直接將 CvSeq 中的數(shù)據(jù)拷貝到由 Mat 管理的內(nèi)存區(qū)域,如果該內(nèi)存區(qū)域未能被 std::vector 正確識別和管理,就可能導(dǎo)致內(nèi)存損壞。

推測原因為:

  • _contours.create() 方法可能不完全適用于 std::vector 這種復(fù)雜類型的內(nèi)存分配和初始化。
  • std::vector 的數(shù)據(jù)存儲并非總是能被簡單地視為一塊連續(xù)內(nèi)存區(qū)域,并允許通過外部指針直接進行填充,特別是對于嵌套的 vector。

這種不匹配的操作極易破壞 std::vector 的內(nèi)部狀態(tài),最終導(dǎo)致程序在后續(xù)訪問這些輪廓數(shù)據(jù)時發(fā)生崩潰。

更穩(wěn)健的解決方案:自定義封裝 cvFindContours

既然底層的 cvFindContours 函數(shù)相對穩(wěn)定,那么我們可以繞過 cv::findContours 中可能存在問題的內(nèi)存操作,通過重新封裝 cvFindContours 來實現(xiàn)一個更安全、更可控的輪廓查找函數(shù)。

以下是提供的自定義 findContours 函數(shù)實現(xiàn),它直接調(diào)用C接口并手動管理 std::vector 的數(shù)據(jù)填充:

#include <opencv2/opencv.hpp> // 根據(jù)需要包含具體的頭文件,如 imgproc.hpp, core.hpp
#include <vector>
// 注意:此函數(shù)簽名和實現(xiàn)源自您提供的原始代碼
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();
// 根據(jù)OpenCV版本處理CvMat,您提供的代碼片段如下:
#if CV_VERSION_REVISION <= 6 // 注意:CV_VERSION_REVISION 是較老版本OpenCV的宏
    CvMat c_image = src; // 在舊版本中,cv::Mat可以直接轉(zhuǎn)換為CvMat
                         // 但請注意,cvFindContours可能會修改圖像,所以最好使用副本
                         // CvMat c_image = src.clone(); 這樣更安全
#else
    // 對于較新的OpenCV版本 (3.x, 4.x)
    cv::Mat mutable_src = src.clone(); // cvFindContours會修改輸入圖像,務(wù)必使用副本
    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]); // 顯式轉(zhuǎn)換size_t到int
    c_image.type = (c_image.type & ~cv::Mat::CONTINUOUS_FLAG) | (mutable_src.flags & cv::Mat::CONTINUOUS_FLAG);
#endif
    cv::MemStorage storage(cvCreateMemStorage(0)); // 創(chuàng)建內(nèi)存存儲區(qū)
    CvSeq* _ccontours = nullptr; // C風格的輪廓序列指針
// 根據(jù)OpenCV版本調(diào)用cvFindContours,您提供的代碼片段如下:
#if CV_VERSION_REVISION <= 6
    cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y));
#else
    cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y)); // CvPoint構(gòu)造方式一致
#endif
    if (!_ccontours) // 如果沒有找到輪廓
    {
        contours.clear(); // 再次確保清空
        hierarchy.clear();
        // storage 會在 cv::MemStorage 對象析構(gòu)時自動釋放
        return;
    }
    // 使用 cvTreeToNodeSeq 獲取所有輪廓的扁平序列,這對于后續(xù)處理(尤其是層級結(jié)構(gòu))更方便
    cv::Seq<CvSeq*> all_contours(cvTreeToNodeSeq(_ccontours, sizeof(CvSeq), storage));
    size_t total = all_contours.size();
    contours.resize(total); // 為輪廓數(shù)據(jù)預(yù)分配空間
    hierarchy.resize(total); // 為層級數(shù)據(jù)預(yù)分配空間
    cv::SeqIterator<CvSeq*> it = all_contours.begin();
    for (size_t i = 0; i < total; ++i, ++it)
    {
        CvSeq* c = *it;
        // 將輪廓的顏色(CvContour的成員)設(shè)置為其索引,用于后續(xù)層級信息的鏈接
        reinterpret_cast<CvContour*>(c)->color = static_cast<int>(i);
        int count = c->total; // 當前輪廓包含的點數(shù)
        if (count > 0) {
            // 您提供的原始代碼中使用 new int[] 來中轉(zhuǎn)點坐標
            int* data = new int[static_cast<size_t>(count * 2)]; // 分配臨時內(nèi)存存儲x,y坐標對
            cvCvtSeqToArray(c, data, CV_WHOLE_SEQ); // 將CvSeq中的點集數(shù)據(jù)拷貝到data數(shù)組
            contours[i].reserve(count); // 為當前輪廓的點集預(yù)分配空間
            for (int j = 0; j < count; ++j) {
                contours[i].push_back(cv::Point(data[j * 2], data[j * 2 + 1]));
            }
            delete[] data; // 釋放臨時內(nèi)存
        }
    }
    // 填充層級信息 (hierarchy)
    it = all_contours.begin(); // 重置迭代器
    for (size_t i = 0; i < total; ++i, ++it)
    {
        CvSeq* c = *it;
        // 通過之前設(shè)置的 color (即索引) 來獲取層級關(guān)系
        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 對象析構(gòu)時自動釋放,無需顯式調(diào)用 cvReleaseMemStorage
}

自定義函數(shù)的關(guān)鍵改進點:

  • 直接調(diào)用C接口:函數(shù)核心是調(diào)用 cvFindContours,避免了C++封裝層中可疑的內(nèi)存操作。
  • 安全的內(nèi)存管理:使用 cv::MemStorage 為C函數(shù)管理內(nèi)存。
  • 顯式數(shù)據(jù)轉(zhuǎn)換與填充
    • 通過 cvTreeToNodeSeq 獲取所有輪廓的扁平列表,這簡化了迭代和層級構(gòu)建。
    • std::vector<std::vector<cv::Point>> contoursstd::vector<cv::Vec4i> hierarchy 調(diào)用 resize 進行預(yù)分配。
    • 對于每個 CvSeq,您的原始方案是先用 cvCvtSeqToArray 將點數(shù)據(jù)讀入一個臨時的 int 數(shù)組 data,然后再遍歷這個 data 數(shù)組,逐點構(gòu)造 cv::Point 對象并 push_back 到對應(yīng)的 contours[i] 中。
    • 這種方式雖然多了一步中轉(zhuǎn),但確保了 std::vector 完全自主地管理其元素的內(nèi)存。
  • 層級信息構(gòu)建:通過在第一次遍歷輪廓時將 CvContourcolor 成員設(shè)置為其在 all_contours序列中的索引,然后在第二次遍歷時利用這個索引來正確構(gòu)建 hierarchy 向量。

這種方法雖然代碼量稍多,但給予了開發(fā)者對內(nèi)存操作更大的控制權(quán),從而有效規(guī)避了標準 cv::findContours C++ 接口在特定情況下可能引發(fā)的內(nèi)存問題。

測試用例

下面是一個使用上述 findContours_custom 函數(shù)的C++示例程序。

test_custom_findcontours_cn.cpp:

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/types_c.h>    // 為了 CvMat, CvSeq 等C語言結(jié)構(gòu)
#include <opencv2/imgproc/types_c.h> // 為了 CV_*, CvContour, CvPoint 等
#include <iostream>
#include <vector>
// --- [粘貼上面提供的 findContours_custom 函數(shù)代碼到這里] ---
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 <= 6
    cv::Mat mutable_src_for_c_api = src.clone(); // 為舊版API準備可修改的副本
    CvMat c_image = mutable_src_for_c_api;
#else
    cv::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);
#endif
    cv::MemStorage storage(cvCreateMemStorage(0));
    CvSeq* _ccontours = nullptr;
#if CV_VERSION_REVISION <= 6
    cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y));
#else
    cvFindContours(&c_image, storage, &_ccontours, sizeof(CvContour), retr, method, cvPoint(offset.x, offset.y));
#endif
    if (!_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 函數(shù)代碼結(jié)束] ---
int main() {
    // 1. 創(chuàng)建一個示例二值圖像
    cv::Mat image = cv::Mat::zeros(300, 300, CV_8UC1); // 黑色背景
    // 繪制一個白色外層矩形
    cv::rectangle(image, cv::Rect(30, 30, 240, 240), cv::Scalar(255), cv::FILLED);
    // 在外層矩形內(nèi)部繪制一個黑色矩形(形成一個“洞”)
    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 << "錯誤:無法創(chuàng)建示例圖像。" << std::endl;
        return -1;
    }
    // 2. 準備輸出容器
    std::vector<std::vector<cv::Point>> contours_vec;
    std::vector<cv::Vec4i> hierarchy_vec;
    // 3. 調(diào)用自定義的 findContours_custom 函數(shù)
    // 使用 cv::RETR_TREE 來測試層級結(jié)構(gòu)
    findContours_custom(image, contours_vec, hierarchy_vec, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    // 4. 輸出結(jié)果
    std::cout << "自定義函數(shù)找到的輪廓數(shù)量: " << 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. 可選: 顯示結(jié)果圖像
    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("檢測到的輪廓 (自定義函數(shù))", 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 未正確配置,請相應(yīng)調(diào)整 opencv4opencv 或你的實際庫名和路徑)。

此測試用例會創(chuàng)建一個包含嵌套結(jié)構(gòu)的簡單圖像,調(diào)用 findContours_custom 函數(shù),打印檢測到的輪廓數(shù)量及其層級信息,并最終將檢測結(jié)果可視化顯示。在之前可能導(dǎo)致崩潰的 Windows 環(huán)境下,此自定義函數(shù)應(yīng)該能夠穩(wěn)定運行。

通過采用這種自定義封裝策略,開發(fā)者可以更從容地應(yīng)對 OpenCV 在特定平臺下可能出現(xiàn)的穩(wěn)定性問題,確保輪廓檢測功能的可靠性。

到此這篇關(guān)于c/c++的findcontours崩潰解決方案的文章就介紹到這了,更多相關(guān)c++ findcontours崩潰內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于C語言char與unsigned char的區(qū)別介紹

    基于C語言char與unsigned char的區(qū)別介紹

    本篇文章小編為大家介紹,基于C語言char與unsigned char的區(qū)別介紹。需要的朋友參考下
    2013-04-04
  • 詳解C++實現(xiàn)拓撲排序算法

    詳解C++實現(xiàn)拓撲排序算法

    拓撲排序是對一個有向無環(huán)圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出現(xiàn)在v之前。本文將對其原理進行講解,并且用C++進行實現(xiàn)
    2021-06-06
  • 原創(chuàng)的C語言控制臺小游戲

    原創(chuàng)的C語言控制臺小游戲

    本文給大家分享的是個人原創(chuàng)設(shè)計的一個C語言控制臺小游戲,非常的簡單,但是挺好玩的,推薦給大家,有需要的小伙伴也可以自由擴展下。
    2015-03-03
  • 利用上下文屬性將?C++?對象嵌入?QML?里

    利用上下文屬性將?C++?對象嵌入?QML?里

    這篇文章主要介紹了利用上下文屬性將?C++?對象嵌入?QML里,將?QML?對象加載到?C++?應(yīng)用程序中時,直接嵌入一些可在?QML?代碼中使用的?C++?數(shù)據(jù)會很有用。例如,這使得在嵌入對象上調(diào)用?C++?方法或使用?C++?對象實例作為?QML?視圖的數(shù)據(jù)模型成為可能,下面一起來學習該內(nèi)容吧
    2021-12-12
  • C++排序算法之冒泡排序解析

    C++排序算法之冒泡排序解析

    這篇文章主要介紹了C++排序算法之冒泡排序解析,從左到右,相鄰兩數(shù)兩兩比較,若下標小的數(shù)大于下標大的數(shù)則交換,將最大的數(shù)放在數(shù)組的最后一位,,再次遍歷數(shù)組,將第二大的數(shù),放在數(shù)組倒數(shù)第二的位置,以此類推,直到數(shù)組有序需要的朋友可以參考下
    2023-10-10
  • Qt 使用Poppler實現(xiàn)pdf閱讀器的示例代碼

    Qt 使用Poppler實現(xiàn)pdf閱讀器的示例代碼

    下面小編就為大家分享一篇Qt 使用Poppler實現(xiàn)pdf閱讀器的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • C語言通訊錄管理系統(tǒng)課程設(shè)計

    C語言通訊錄管理系統(tǒng)課程設(shè)計

    這篇文章主要為大家詳細介紹了C語言通訊錄管理系統(tǒng)課程設(shè)計,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • C++新特性詳細分析基于范圍的for循環(huán)

    C++新特性詳細分析基于范圍的for循環(huán)

    C++11這次的更新帶來了令很多C++程序員期待已久的for?range循環(huán),每次看到j(luò)avascript,?lua里的for?range,心想要是C++能有多好,心里別提多酸了。這次C++11不負眾望,再也不用羨慕別家人的for?range了。下面看下C++11的for循環(huán)的新用法
    2022-04-04
  • C++超詳細講解拷貝構(gòu)造函數(shù)

    C++超詳細講解拷貝構(gòu)造函數(shù)

    我們經(jīng)常會用一個變量去初始化一個同類型的變量,那么對于自定義的類型也應(yīng)該有類似的操作,那么創(chuàng)建對象時如何使用一個已經(jīng)存在的對象去創(chuàng)建另一個與之相同的對象呢
    2022-06-06
  • 解析C語言結(jié)構(gòu)體及位段

    解析C語言結(jié)構(gòu)體及位段

    今天小編就為大家分享一篇關(guān)于解析C語言結(jié)構(gòu)體及位段,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12

最新評論