一、案例
最近使用一款工业相机循环取图,使用相机自带SDK,结果出现了内存泄漏现象,原因是SDK提供的接口没有对其开辟的内存进行释放,而程序里一直在调用该接口,直到后来看到接口文档里的一段注释才恍然大悟,泄漏代码如下:
void grabImg(cv::Mat &grab_img)
{
if (pDevice == NULL)
{
return;
}
else
{
try
{
Arena::IImage* pImage = pDevice->GetImage(2000);//获取图像
/*问题就是出在下面这个接口上,这个接口是对图像进行格式转换,
转换后的图像是存在一块新开辟的内存上,需要程序手动释放,
由于一直在调用该接口,导致内存不断泄漏*/
auto pConverted = Arena::ImageFactory::Convert(pImage,PIXEL_FORMAT);//转换为RGB
grab_img = cv::Mat((int)pConverted->GetHeight(), (int)pConverted->GetWidth(), CV_8UC3, (uint8_t*)pConverted->GetData());
pDevice->RequeueBuffer(pImage);
}
catch (const std::exception&)
{
}
}
}
文档注释:
- Convert converts an image (Arena::IImage) to a select pixel
- format. In doing so, it creates a completely new image, similar to a
- deep copy but with a different pixel format. Images created with the
- image factory must be destroyed (Arena::ImageFactory::Destroy) when no
- longer needed; otherwise, memory will leak.
大意就每次都会创建一个新的图片,类似“深拷贝”,不用时一定要调用destroyed来释放!
更改后代码如下:
grabImg(cv::Mat &grab_img)
{
if (pDevice == NULL)
{
return;
}
else
{
try
{
Arena::IImage* pImage = pDevice->GetImage(2000);//获取图像
auto pConverted = Arena::ImageFactory::Convert(
pImage,
PIXEL_FORMAT);//转换为RGB
grab_img = cv::Mat((int)pConverted->GetHeight(), (int)pConverted->GetWidth(), CV_8UC3, (uint8_t*)pConverted->GetData()).clone();//将图片复制到自己定义的内存(可由opencv自动管理)
pDevice->RequeueBuffer(pImage);
Arena::ImageFactory::Destroy(pConverted);//手动释放原来图片内存
}
catch (const std::exception&)
{
}
}
}
二、总结
从上面的案例中我总结出了2个知识点
1.内存谁开辟谁释放,比如上面案例中内存是SDK里的接口开辟的,就必须要调用它提供的接口来释放,否则会泄漏
2.内存管理机制
对于上面的案例,我每次都会调用下面这行代码:
grab_img = cv::Mat((int)pConverted->GetHeight(), (int)pConverted->GetWidth(), CV_8UC3, (uint8_t*)pConverted->GetData()).clone();
起初我比较疑惑,clone()也是“深拷贝”,为何它就不会造成内存泄漏呢?答案是opencv 3.0以后的版本加入了内存管理机制,这个机制在当你不再使用某块内存时,会自动帮你释放掉它,当然前提是该内存也是由opencv帮你开辟的。
这个机制工作大概如下:
1)Mat大概可以分为2个部分,指针域和数据域,指针域指向的是实际的图片内存,而数据域存储了该图片的一些相关信息,比如长宽信息。
2)当创建一块内存时,还是看下面这行代码
grab_img = cv::Mat((int)pConverted->GetHeight(), (int)pConverted->GetWidth(), CV_8UC3, (uint8_t*)pConverted->GetData()).clone();
grab_img 的指针便指向了一块内存,这块内存由clone()函数来开辟,存储了实际的照片数据,并且伴随而生的还有一个计数器counter,counter = 1,表示有一个指针指向了该内存,当后面的程序继续有别的指针指向该内存时counter ++,反之有一个指针不再指向它时,counter --,在counter 为0时,内存被释放。在上面这个案例中,虽然每次都会调用clone开辟一块新内存,但是grab_img每次都会指向最新的内存,而旧的内存由于没有指针再指向它,便被opencv的管理单元自动回收,所以避免了内存泄漏问题。所以以后如果开辟内存,一定要搞清楚有没有自动管理机制,何时被开辟,何时应该被释放。