天天看點

【OpenCV】有關記憶體釋放的一些問題

原文出處:http://blog.sina.com.cn/s/blog_67a7426a0101czyr.html

本文是對原文的轉載、整理和總結補充(結尾)

1、記憶體洩露

記憶體洩露是說沒有釋放已經不能使用的記憶體,這裡一般指堆的記憶體才需要顯示的釋放。比如用malloc,calloc,realloc,new配置設定的記憶體是在堆上的,需要用free,delete顯示的回收。記憶體洩露最明顯的一是程式很慢,在運作程式時你可以啟動任務管理器,會看到程式占用的記憶體一直“砰砰砰”的往上漲:

【OpenCV】有關記憶體釋放的一些問題

最後直接崩潰,或者你關閉程式的時候也會異常退出,出現

Debug Assertion Failed!

Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)

之類的問題。

【OpenCV】有關記憶體釋放的一些問題

除了new的對象我們知道要delete。OpenCV中使用cvCreateImage()建立一個IplImage*,以及使用cvCreateMat()建立一個CvMat*,都需要cvReleaseImage()  cvReleaseMat()顯示的釋放

[cpp] view plaincopy

  1. IplImage* subImg=cvCreateImage( cvSize((img->width)*scale,(img->height)*scale), 8, 3 );  
  2. CvMat *tempMat=cvCreateMat((img->width)*scale,(maxFace->height)*scale,CV_MAKETYPE(image->depth,image->nChannels));  
  3. cvReleaseImage(&subImg);  
  4. cvReleaseMat(&tempMat);  

另外一些函數要用到 CvSeq*來存放結果(通常這些都要用cvCreateMemStorage()事先配置設定一塊記憶體CvMemStorage*),都要是釋放掉相應的記憶體,這是很難找的。

比如從二值圖像中尋找輪廓的函數cvFindContours():

[cpp] view plaincopy

  1. CvMemStorage* m_storage=cvCreateMemStorage(0);  
  2. CvSeq * m_contour=0;  
  3. cvFindContours( img, m_storage, &m_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));  
  4. //釋放記憶體  
  5. cvReleaseMemStorage(&m_storage);  

以及人臉識别中檢測人臉的函數:

[cpp] view plaincopy

  1. CvMemStorage* m_storage=cvCreateMemStorage(0);  
  2. CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name, 0, 0, 0 );  
  3. CvSeq* faces = cvHaarDetectObjects( img, cascade, m_storage,1.1, 2, 0,cvSize(30, 30) );  
  4. //釋放記憶體  
  5. cvReleaseMemStorage( &faces->storage);  
  6. cvReleaseHaarClassifierCascade( &cascade );  

注意這裡我們可以使用

cvReleaseMemStorage( &faces->storage);

來釋放m_storate,也可以使用:

cvReleaseMemStorage(&m_storage);

釋放記憶體,這是等效的,但一定不要用兩次!!

2、一塊記憶體多次釋放

對應沒有釋放記憶體,對應就是一個記憶體釋放多次,如同上面的 cvReleaseMemStorage用了兩次。可能報錯的地方:

[cpp] view plaincopy

  1. __declspec(noinline)  
  2. void __cdecl _CRT_DEBUGGER_HOOK(int _Reserved)  
  3. {  
  4.     (_Reserved);  
  5.     _debugger_hook_dummy = 0;  
  6. }  

或者: Unhandled exception at XXXXXXXXXX in XXX.exe: XXXXXXXXXXX: 堆已損壞。 

【OpenCV】有關記憶體釋放的一些問題

除了上述的MemStorge問題,使用cvQueryFrame()取出CvCapture*每幀圖像,隻需在最後釋放CvCapture*,不需要釋放IplImage*

[cpp] view plaincopy

  1. CvCapture* pCapture = cvCreateCameraCapture(-1);  
  2. IplImage* pFrame=cvQueryFrame( pCapture );  
  3. cvReleaseCapture(&pCapture);  

*這篇是以前寫的,其實還是建議大家用C++接口的OpenCV,記憶體問題很少了~

=================================================================================================================

OpenCV中的記憶體洩露問題(cvLoadImage,cvCloneImage)

在做項目的過程中,使用OpenCV經常會出現一些記憶體洩露問題,自己編寫的程式出現問題還情有可原,但若是庫函數調用和使用時出現,卻很令我惱火。花了好長時間和實踐的經驗告訴我應該客服它。下面把一些檢測出的問題進行化解。(可能是水準不夠,這些函數使用不當,望高手指點)

cvLoadImage函數:

可能大家還覺察不出來,但我深有體會,在程式中這個函數使用一次兩次感覺不來,但在處理序列圖像循環調用這個函數時,記憶體洩露的可能讓你目瞪口呆!

【OpenCV】有關記憶體釋放的一些問題

即使你在最後使用cvReleaseImage(&pImg);進行了釋放,實驗證明:視乎不能成功釋放。

解決方法:

使用CvvImage類代替。并且使用CvvImage類的Load函數。

使用過程大概如下:

//變量定義:

CvvImage pSrcImg;

IplImag *pSrcImgCopy ;//使用IplImag變量做個拷貝。畢竟IplImag 類處理友善。

//擷取圖像:

pSrcImg.Load(str);//str為Cstring類型的圖像檔案名

pSrcImgCopy = pSrcImg.GetImage();//拷貝出pSrcImg的圖像資料。

//釋放記憶體

pSrcImg變量不需要每次釋放,因為每次Load時是覆寫以前的記憶體區域。pSrcImgCopy 同樣。

不過在程式結束時要釋放,以免産生記憶體洩露或者别人以為你忘了。

cvReleaseImage(&pSrcImgCopy );

pSrcImg.Destroy();

不過要正确釋放pSrcImgCopy 時,聲明時必須create下:

pSrcImgCopy = cvCreateImage(cvSize(IMGWIDHT,IMGHEIGHT),IPL_DEPTH_8U, 3);

//IMGWIDHT,IMGHEIGHT為圖像寬和高。

cvCloneImage函數:

這個函數也會出現記憶體洩露!雖然可以釋放,但程式複雜不知道在那裡釋放,因為它每次拷貝是制作圖像的完整拷貝包括頭、ROI和資料。不會覆寫以前的内容。每次使用時編譯器會配置設定記憶體空間。一個752*480大小的圖像,每次洩露的記憶體大約為1M。

解決方法:

使用cvCopy函數代替。

cvCopy(pSrcImg,pImg,NULL);//代替 pImg = cvCloneImage(pSrcImg);

pImg初始化時必須配置設定空間,否則上述函數不能執行。

pImg = cvCreateImage(cvSize(IMGWIDHT,IMGHEIGHT),IPL_DEPTH_8U, 3);

最近一直在用opencv編寫算法程式,但是cvCloneImage、cvCopyImage和cvCloneMat、cvCopyMat這幾個函數讓我痛苦了好一陣子,程式代碼沒有任何問題,但是就是得不到結果,在子函數中傳回值根本不是我想要的,由于代碼挺龐大的,一直沒找到問題出在哪裡,于是設定一個個斷點,通過步步調試,終于發現問題出在了cvCloneImage、cvCopyImage和cvCloneMat、cvCopyMat這幾個函數的誤用,cvCloneImage與cvCloneMat是在指派的同時會開辟一個新的空間給定義的變量,cvCopyImage與cvCopyMat隻複制值,并不會配置設定一個空間給指派對象,是以cvCloneImage與cvCloneMat隻适合用于變量開始定義,千萬不要用在算法進行中間,否則會産生一個新的位址空間,會将指派對象的指針位址改變,這樣會導緻整個程式有不可預測的錯誤發生,最明顯的就是你本來想把子函數中的新變量值送回上一層函數,但是由于指針的指向已經改變,是以傳回後的值并不會改變。在程式中間進行複制時候建議使用cvCopyImage與cvCopyMat。

是以當使用opencv函數時候,不同函數實作同一個功能,但是一定要注意他們之間的差別,不然會讓你很痛苦,尋找這種錯誤真的很煩人。

http://benson.is-programmer.com/posts/21042.html

  戲劇性階段一:問題的出現 

最近在使用opencv的時候,發現在圖像函數部分,opencv的記憶體管理存在一定問題。在使用IplImage的圖像cvcloneImage()後,調用cvReleaseImage()時,記憶體并不能全部釋放。在實時視訊處理程式中,伴随程式運作,很容易造成系統記憶體消耗殆盡。

舉例來說,看下面的一個最簡單代碼:

#include"cv.h"

#include"highgui.h"

#pragma comment(lib,"cv.lib")

#pragma comment(lib,"highgui.lib")

#pragma comment(lib,"cxcore.lib")

int _tmain(int argc, _TCHAR* argv[])

{

 CvCapture* capture = cvCreateCameraCapture(0);

 IplImage* frame;

 cvNamedWindow("ExampleShow",CV_WINDOW_AUTOSIZE);

 while(1)

 {

  frame = cvQueryFrame(capture);

  if(!frame)

   break;

  cvShowImage("ExampleShow",frame);

  char c = cvWaitKey(33);

  if(c == 27)

    break;

 }

 cvReleaseCapture(&capture);

 cvDestroyWindow("ExampleShow");

 return 0;

}

運作程式,此時,打開資料總管,可以看到其所占的“記憶體使用”一直保持穩定。而如果,簡單修改下上面的程式,改變如下:

#include"cv.h"

#include"highgui.h"

#pragma comment(lib,"cv.lib")

#pragma comment(lib,"highgui.lib")

#pragma comment(lib,"cxcore.lib")

int _tmain(int argc, _TCHAR* argv[])

{

CvCapture* capture = cvCreateCameraCapture(0);

IplImage* frame;

IplImage* clImage;

cvNamedWindow("ExampleShow",CV_WINDOW_AUTOSIZE);

cvNamedWindow("Example_Clone",CV_WINDOW_AUTOSIZE);

while(1)

{

frame = cvQueryFrame(capture);

if(!frame)

break;

cvShowImage("ExampleShow",frame);

clImage = cvCreateImage(cvSize(frame->width,frame->height),frame->depth,frame->nChannels);

clImage = cvCloneImage(frame);

cvShowImage("Example_Clone",clImage);

char c = cvWaitKey(33);

if(c == 27)

break;

cvReleaseImage(&clImage);

}

cvReleaseCapture(&capture);

cvDestroyWindow("ExampleShow");

cvDestroyWindow("Example_Clone");

return 0;

}

同樣,運作程式,打開資料總管,可以看到“記憶體使用”中,該程式的記憶體使用量在不斷增加。雖然,程式中對拷貝的圖像進行了釋放,但是,事實上,卻沒有看到多少效果!

雖然發現了這個問題,也在網絡上看到相關的這個問題的讨論,試驗了幾種方法,發現并不work。在此提出問題,繼續探索吧。

戲劇性階段二:“改良方法”的出現

在網絡上看到,除了cvCloneImage()還有cvLoadImage()也有記憶體洩露問題。最終的有效解決辦法是使用cvCopy()來替換代碼中的cvCloneImage(),這時候,不會出現記憶體不斷遞增的情況。而cvLoadImage()可以CvvImage類的圖像裝載函數,然後拷貝到目标圖像即可。

戲劇性三:真正原因的捕獲和分析 

記憶體無法釋放的原因分析:

今天偶然想起,在觀察上面的代碼時,發現在其中存在一句:clImage = cvCreateImage(cvSize(frame->width,frame->height),frame->depth,frame->nChannels);

這句是向記憶體申請一片空間,用于存放目的圖像的空間。造成記憶體洩露的真正原因是這句。

在使用cvCloneImage()的時候,其實是對源圖像指針所指向的圖像頭、資料、ROI等進行了一個完全的拷貝,放在一個新的記憶體區域,函數結果使得目标圖像指向新的記憶體,而原來用cvCreateImage()所配置設定的區域沒有被正确釋放,成為一片“懸挂位址區域”。在後面調用cvReleaseImage()的時候,釋放的是後面其所指向的區域。

是以,要避免這種情況的出現,

一種方法是:可以在cvCloneImage()前,先調用cvReleaseImage()來釋放之前配置設定的位址區域。然後執行克隆函數cvCloneImage()操作。也可以在前面不配置設定空間,直接調用克隆操作。

另外一種方法,如果使用cvCopy()函數操作,由于該函數并不會對圖像指針配置設定空間,是以需要先自己用cvCreateImage()配置設定一段區域,然後調用拷貝函數cvCopy(),來對圖像指派。這樣最後釋放的是圖像指針所指的位址區域。這兩種方法都不會出現記憶體洩露的問題了。

 1. cvCloneImage()

        ......

        IplImag *img = cvCreateImage( cvSize(frame->width,frame->hight), frame->depth, frame->nChannels );

        img = cvCloneImage(frame);

    這裡出現記憶體洩露,因為調用cvCloneImage()之前已經用cvCreateImage()為圖像配置設定了記憶體空間,而cvCloneImage()函數是對源圖像的所有資料的拷貝,包括圖像頭、資料、ROI等,這就導緻原來配置設定的記憶體空間變成了記憶體碎片,造成記憶體洩露。.解決方法如下:

        IplImag *img = cvCloneImage(frame);

    或 

        IplImag *img = cvCreateImage( cvSize(frame->width,frame->hight), frame->depth, frame->nChannels );

        cvReleaseImage(&img);

        img = cvCloneImage(frame);

    或

        IplImag *img = cvCreateImage( cvSize(frame->width,frame->hight), frame->depth, frame->nChannels );

        img = cvCloneImage(frame);

        cvCopy(frame,img,NULL);

    2. cvGetCols()、cvGetRows()

    ......

    CvMat *srcMat = cvCreateMat(width, height, CV_8UC3);  // 建立一個三通道無符号整數類型的矩陣

    cvGetCols(frame,srcMat,0,width);

    這裡出現記憶體洩露,因為cvGetCols()、cvGetRows() 是為目标矩陣配置設定一塊新的資料記憶體區域,如果目标矩陣的資料區域之前已經配置設定了記憶體,則會原始資料記憶體區域将變成記憶體碎片,造成記憶體洩露。解決方法如下:

        CvMat *srcMat = cvCreateMat(width, height, CV_8UC3);  // 建立一個三通道無符号整數類型的矩陣

        cvReleaseData(srcMat);    // 先釋放目标矩陣的資料區

        cvGetCols(frame,srcMat,0,width);

    或者

         cvCreateMatHeader(width, height, CV_8UC3);

         cvGetCols(frame,srcMat,0,width);

    3. IplImag*、CvMat*、CvHistogram* 等結構體指針在使用後要釋放。

         IplImage *frame= cvCreateImage(cvSize(width,height),8,1);

         CvMat *srcMat = cvCreateMat(width, height, CV_8U);

         ......

         CvHistogram *hist = cvCreateHist(1,&histSize,CV_HIST_ARRAY,ranges,1);

         ......

         cvReleaseMat(&srcMat);

         cvReleaseImage(&frame);  

         cvReleaseHist(&hist);

總結:

1.new的對象要delete。

2.Mat類型是自動記憶體管理,不需要顯式釋放(當然也可以手動調用release()方法強制Mat矩陣資料釋放);

OpenCV中使用cvCreateImage()建立一個IplImage*,使用cvCreateMat()建立一個CvMat*,都需要cvReleaseImage(&cvmat) cvReleaseMat(&iplimage)顯示的釋放;

3.一些函數要用到 CvSeq*來存放結果(通常這些都要用cvCreateMemStorage()事先配置設定一塊記憶體CvMemStorage*),都要是釋放掉相應的記憶體                                           cvReleaseMemStorage(&m_storage); 

4.使用cvQueryFrame()取出CvCapture*每幀圖像,隻需在最後釋放CvCapture*,不需要釋放IplImage*。                                                                            cvReleaseCapture(&pCapture); 

5.cvLoadImage函數,使用CvvImage類代替。并且使用CvvImage類的Load函數。

6.cvCloneImage函數,使用cvCopy函數代替。       cvReleaseImage(&img);

7.cvCloneMat、cvCopyMat

8.與CvMat *用的cvGetCols()、cvGetRows()。 CvMat *srcMat = cvCreateMat(width, height, CV_8UC3);                                                                     cvReleaseData(srcMat); 

9. IplImag*、CvMat*、CvHistogram* 等結構體指針在使用後要釋放。

                       cvReleaseImage(&);   cvReleaseMat(&);   cvReleaseHist(&);