1. 程式人生 > >FindContours()函式使用時導致的程式崩潰問題的解決方案

FindContours()函式使用時導致的程式崩潰問題的解決方案

最近通過使用opencv做標誌牌檢測時,涉及到了opencv庫中findContours函式的呼叫,在對該函式使用時出現了程式崩潰的問題,花了兩天的時間才解決的該問題。
下面先對findContour函式進行簡單的介紹,再次介紹一下碰到findcontours導致程式崩潰時,網路上出現的一些的解決方案,最後介紹一下我自己碰到的該問題時的解決方案。

首先,在這裡先簡單的對findContours函式進行簡單的介紹一下吧。

findContours函式,這個函式的原型為:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierar-
chy, int mode, int method, Point offset=Point())
引數說明:
第一個引數:輸入影象image必須為一個2值單通道影象。
第二個引數:contours引數為檢測的輪廓陣列,每一個輪廓用一個point型別的vector表示。
第三個引數:hiararchy引數和輪廓個數相同,每個輪廓contours[ i ]對應4個hierarchy元素hierarchy[ i ][ 0 ] ~hierarchy[ i ][ 3 ],分別表示後一個輪廓、前一個輪廓、父輪廓、內嵌輪廓的索引編號,如果沒有對應項,該值設定為負數。
第四個引數:mode表示輪廓的檢索模式。有以下幾種模式:
1.CV_RETR_EXTERNAL表示只檢測外輪廓
2.CV_RETR_LIST檢測的輪廓不建立等級關係
3.CV_RETR_CCOMP建立兩個等級的輪廓,上面的一層為外邊界,裡面的一層為內孔的邊界資訊。如果內孔內還有一個連通物體,這個物體的邊界也在頂層。(簡單的說就是檢測雙層輪廓)
4.CV_RETR_TREE建立一個等級樹結構的輪廓。具體參考contours.c這個demo
第五個引數:method為輪廓的近似辦法,主要有以下幾種:
1.CV_CHAIN_APPROX_NONE儲存所有的輪廓點,相鄰的兩個點的畫素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
2.CV_CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點座標,例如一個矩形輪廓只需4個點來儲存輪廓資訊
3.CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似演算法
第六個引數:offset表示代表輪廓點的偏移量,可以設定為任意值。對ROI影象中找出的輪廓,並要在整個影象中進行分析時,這個引數還是很有用的。一般設定為預設值point(0,0).

findContours函式的呼叫小示例如下:

vector<vector<Point>>contours;//輪廓陣列,也可以這樣寫  vector<Mat>contours;
findContours(Dst, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
//CV_RETR_CCOMP引數用來檢測雙層輪廓```

//注:在檢測雙層輪廓時,若只提取內輪廓時,可以使用以下語句對內輪廓進行篩選出來
語句如下:
if (hierarchy[i][3] < 0)
{
continue;
}

通過對該引數的判斷就能將內輪廓篩選出來。
FindContours函式先介紹這麼多了。現在看一下我這篇文章的重點吧。
我在程式中使用如下:

vector<Vec4i>hierarchy;//可以用來過濾內外輪廓
vector<vector<Point>>contours;//輪廓陣列,也可以這樣寫  vector<Mat>contours;
findContours(Dst, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);

我現在一個工程中使用該函式對單張影象進行輪廓檢測,並沒有出現程式崩潰問題,但是對視訊中每幀影象進行輪廓檢測時,在檢測第三幀影象時,程式直接崩潰,將該語句遮蔽後,程式則可以正常執行並沒有問題。
我的執行環境為VS2015+Opencv2.4.11
FindContours函數出現的呼叫異常問題如下:
這裡寫圖片描述


錯誤:File: minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp
Line: 980
Expression: __acrt_first_block == header
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.

以下是網上一些大神給出的解決方案(然而並不能解決我的問題)
方案一:
如果使用vector<vector<Point> > contours;作為findContours的引數,在執行時會得到
Assertion failed (mtype == type0 || (CV_MAT_CN(mtype) == CV_MAT_CN(type0) && ((1 << type0) & f…
原因是標準庫裡有std::vector 和 Point 和findContours裡要用到的vector和Point不是一回事所以,宣告的時候要用cv::vector和cv::Point就可以了。

方案二:
“修改了當前程式的vc執行庫配置,問題解決。具體方法是:專案-屬性-配置屬性-C/C++-程式碼生成-執行庫,將其改為“多執行緒除錯(/MTd)”。”

方案三:
在配置屬性->常規->MFC的使用中,將在靜態庫中使用MFC改為在共享DLL中使用MFC。

方案四:
將程式改為:

vector<Mat> contours(100); 
Mat hierarchy;
findContours( BW, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE );

方案五:
當一個DLL採用靜態的方式連結到C執行時庫時,會建立一個相對於該DLL的堆(Heap),而如果採用共享的方式連結到C執行時庫的時候則使用的是應用程式的堆記憶體。而_CrtIsValidHeapPointer()在 DEBug模式下將確保傳入的地址在本地的堆記憶體中。 因此就有理由相信,真有可能是靜態連結的問題。所以,我立即嘗試將:
專案–屬性–配置屬性–常規–MFC的使用– 選擇在共享DLL中使用MFC ;同時,
專案–屬性–配置屬性–C/C++–程式碼生成–執行庫–選擇 多執行緒DLL(/MD)。

方案六:

這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

方案七:
這裡寫圖片描述

通過以上可以看出,網路中從來都不缺少該領域的大神,給出的方案也是非常之多,然而並不能解決我的問題。

以下是我對於我自己程式出現的問題給出的解決方案:

通過對出現的錯誤:__acrt_first_block == header可以大致的知道是堆記憶體出現的問題,堆區一般都是用來申請分配動態陣列時才會使用,而申請動態陣列用的最多的就是使用關鍵字new[]進行申請分配。而我在程式中並未使用new,哪來的堆區的使用呢,通過查詢資料瞭解到vector可以動態分配記憶體,因此問題極可能就出現在這上面。通過查閱資料瞭解到是vector析構異常導致的問題,可以借鑑這篇文章看一下http://www.aiuxian.com/article/p-1722238.html。原文部分如下:
大概是因為 dll 如果靜態連結了執行時庫,dll 就會擁有獨立於應用程式堆(也稱作local heap)的執行時堆例項。此時在 dll 外部就不能訪問此 local heap,所以也就有上面所出現的異常啦。MSDN 中也有介紹:
  The _CrtIsValidHeapPointer function is used to ensure that a specific memory address is within the local heap. The local heap refers to the heap created and managed by a particular instance of the C run-time library. If a dynamic-link library (DLL) contains a static link to the run-time library, it has its own instance of the run-time heap, and therefore its own heap, independent of the application’s local heap. When _DEBUG is not defined, calls to _CrtIsValidHeapPointer are removed during preprocessing.
程式崩潰在當析構一個帶有vector成員函式物件的時候,在析構vector時,會出現這個錯誤,大致原因是因為析構的時候找不到vector分配的空間。
一行一行檢視程式碼發現,物件裡面的points2, status等vector變數是在calcOpticalFlowPyrLK(img1, img2, points1, points2, status, similarity, window_size, level, term_criteria, lambda, 0); 函式中分配的,即opencv的dll,所以當物件進行析構的時候,因為不能訪問此local heap所以會有異常崩潰。

我的解決方法:
在呼叫opencv的函式之前,自己進行空間的分配。
通過閱讀該片文章之後,瞭解到vector 析構異常 opencv Assert _CrtIsValidHeapPointer,只要在呼叫opencv的函式之前,自己進行空間的手動分配。於是,我對程式進行部分修改,修改程式如下:

vector<Vec4i>hierarchy(10000);
vector<Mat>contours(10000);//手動分配記憶體空間大小
findContours(Dst, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
//注意,記憶體空間不已設定過大,否則也會導致程式崩潰.**

至此,我的程式中呼叫findContours函式時出現的程式崩潰問題已經完美地解決。雖然這次bug除錯花了兩天多的時間才解決,但是,讓我知道對於程式出現同類問題,網上給出的方案不一定能解決你碰到的問題,還是要學會具體問題具體分析,不能將網上的方案進行生搬硬套,這樣對於你解決問題是沒有任何幫助的。最重要的是對於出現問題以及對於問題的解決方案要養成記錄的好習慣,這樣不僅可以在網路上實現交流共享,有助於他人學習借鑑,還可以有助於我們下次碰到該類問題時,能快速的解決問題。