原文出處:http://blog.sina.com.cn/s/blog_67a7426a0101czyr.html
本文是對原文的轉載、整理和總結補充(結尾)
1、記憶體洩露
記憶體洩露是說沒有釋放已經不能使用的記憶體,這裡一般指堆的記憶體才需要顯示的釋放。比如用malloc,calloc,realloc,new配置設定的記憶體是在堆上的,需要用free,delete顯示的回收。記憶體洩露最明顯的一是程式很慢,在運作程式時你可以啟動任務管理器,會看到程式占用的記憶體一直“砰砰砰”的往上漲:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLzUjNy8VN3UTN3UzNzMTMvwVMy8CX1AjMxAjMvw1ckF2bsBXdvwFdl5mLuR2cj5Set1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
最後直接崩潰,或者你關閉程式的時候也會異常退出,出現
Debug Assertion Failed!
Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
之類的問題。
除了new的對象我們知道要delete。OpenCV中使用cvCreateImage()建立一個IplImage*,以及使用cvCreateMat()建立一個CvMat*,都需要cvReleaseImage() cvReleaseMat()顯示的釋放
[cpp] view plaincopy
- IplImage* subImg=cvCreateImage( cvSize((img->width)*scale,(img->height)*scale), 8, 3 );
- CvMat *tempMat=cvCreateMat((img->width)*scale,(maxFace->height)*scale,CV_MAKETYPE(image->depth,image->nChannels));
- cvReleaseImage(&subImg);
- cvReleaseMat(&tempMat);
另外一些函數要用到 CvSeq*來存放結果(通常這些都要用cvCreateMemStorage()事先配置設定一塊記憶體CvMemStorage*),都要是釋放掉相應的記憶體,這是很難找的。
比如從二值圖像中尋找輪廓的函數cvFindContours():
[cpp] view plaincopy
- CvMemStorage* m_storage=cvCreateMemStorage(0);
- CvSeq * m_contour=0;
- cvFindContours( img, m_storage, &m_contour, sizeof(CvContour), CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
- //釋放記憶體
- cvReleaseMemStorage(&m_storage);
以及人臉識别中檢測人臉的函數:
[cpp] view plaincopy
- CvMemStorage* m_storage=cvCreateMemStorage(0);
- CvHaarClassifierCascade* cascade = (CvHaarClassifierCascade*)cvLoad( cascade_name, 0, 0, 0 );
- CvSeq* faces = cvHaarDetectObjects( img, cascade, m_storage,1.1, 2, 0,cvSize(30, 30) );
- //釋放記憶體
- cvReleaseMemStorage( &faces->storage);
- cvReleaseHaarClassifierCascade( &cascade );
注意這裡我們可以使用
cvReleaseMemStorage( &faces->storage);
來釋放m_storate,也可以使用:
cvReleaseMemStorage(&m_storage);
釋放記憶體,這是等效的,但一定不要用兩次!!
2、一塊記憶體多次釋放
對應沒有釋放記憶體,對應就是一個記憶體釋放多次,如同上面的 cvReleaseMemStorage用了兩次。可能報錯的地方:
[cpp] view plaincopy
- __declspec(noinline)
- void __cdecl _CRT_DEBUGGER_HOOK(int _Reserved)
- {
- (_Reserved);
- _debugger_hook_dummy = 0;
- }
或者: Unhandled exception at XXXXXXXXXX in XXX.exe: XXXXXXXXXXX: 堆已損壞。
除了上述的MemStorge問題,使用cvQueryFrame()取出CvCapture*每幀圖像,隻需在最後釋放CvCapture*,不需要釋放IplImage*
[cpp] view plaincopy
- CvCapture* pCapture = cvCreateCameraCapture(-1);
- IplImage* pFrame=cvQueryFrame( pCapture );
- cvReleaseCapture(&pCapture);
*這篇是以前寫的,其實還是建議大家用C++接口的OpenCV,記憶體問題很少了~
=================================================================================================================
OpenCV中的記憶體洩露問題(cvLoadImage,cvCloneImage)
在做項目的過程中,使用OpenCV經常會出現一些記憶體洩露問題,自己編寫的程式出現問題還情有可原,但若是庫函數調用和使用時出現,卻很令我惱火。花了好長時間和實踐的經驗告訴我應該客服它。下面把一些檢測出的問題進行化解。(可能是水準不夠,這些函數使用不當,望高手指點)
cvLoadImage函數:
可能大家還覺察不出來,但我深有體會,在程式中這個函數使用一次兩次感覺不來,但在處理序列圖像循環調用這個函數時,記憶體洩露的可能讓你目瞪口呆!
即使你在最後使用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(&);