我们都知道OpenCV中存储图像常用的方法就是用Mat表示. 基本上讲 Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)和一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头的尺寸大数个数量级。
基于这个考虑,OpenCV使用引用计数机制。其思路是让每个 Mat 对象有自己的信息头,但共享同一个矩阵。这通过让矩阵指针指向同一地址而实现。而拷贝构造函数则 只拷贝信息头和矩阵指针 ,而不拷贝矩阵。例如以下一段代码:
Mat A, C; // 只创建信息头部分
A = cv::imread("test.jpg", CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存
Mat B(A); // 使用拷贝构造函数
C = A; // 赋值运算符
以上代码中的所有Mat对象最终都指向同一个也是唯一一个数据矩阵。虽然它们的信息头不同,但通过任何一个对象所做的改变也会影响其它对象。实际上,不同的对象只是访问相同数据的不同途径而已。
现在你也许会问,如果矩阵属于多个 Mat 对象,那么当不再需要它时谁来负责清理?简单的回答是:最后一个使用它的对象。通过引用计数机制来实现。无论什么时候有人拷贝了一个 Mat 对象的信息头,都会增加矩阵的引用次数;反之当一个头被释放之后,这个计数被减一;当计数值为零,矩阵会被清理。
但某些时候你仍会想拷贝矩阵本身(不只是信息头和矩阵指针),这时可以使用函数 clone() 和我们即将讨论的函数 copyTo() 。如下:
cv::Mat testImage = cv::imread("test.jpg"); //读入图片
cv::Mat imageCopy;
testImage.copyTo(imageCopy); //完整拷贝一份testImage
Mat类的两个拷贝函数,copyTo()和clone(),都是进行深复制,也就是会另外开辟一个内存存储被复制的数据区域,对复制得到的新矩阵进行释放release()不会影响原矩阵的数据.
一般情况下使用copyTo函数进行图像(或者矩阵)复制,变量被销毁,那么开辟的空间也会被Mat的析构函数释放. 但是如果copyTo函数的拷贝对象不是局部变量, 而是全局变量或者句柄指针,则出现变量一直存在而每一次复制都会开辟新的空间无法释放,造成内存泄露问题. 我们看一下以下例子, 例子是一个简单的循环队列, 队列元素包含Mat对象:
队列头文件test_queue.h
#ifndef __OPT_QUENE_H__
#define __OPT_QUENE_H__
#include <opencv2/opencv.hpp>
#include <pthread.h>
#define MAX_QUEUE_SIZE 100
typedef struct output_rslt_tag
{
cv::Mat stImage; /* 帧图像 */
unsigned int uiFrameSeq;
}Rslt_s;
typedef struct queue_tag{
int front;
int rear;
Rslt_s data[MAX_QUEUE_SIZE];
pthread_mutex_t q_lock;
pthread_cond_t cond;
}stQueue_s;
/* 初始化 */
int queue_create(void **ppvHandle);
/* 判断是否为空 */
int queue_empty(void *pvHandle);
/* 判断队列是否满 */
int queue_full(void *pvHandle);
/* 求循环队列的长度 */
int queue_length(void *pvHandle);
/* 入队 */
int queue_push(void *pvHandle, Rslt_s *pstOptRslt);
/* 队头元素出队,返回其值 */
int queue_pop(void *pvHandle, Rslt_s *pstOptRslt);
/* 销毁队列 */
int queue_destroy(void *pvHandle);
#endif //__OPT_QUENE_H__
队列源文件: test_queue.cpp
#include "test_queue.h"
#include <stdlib.h>
#include <stdio.h>
#include <cmath>
int queue_create(void **ppvHandle)
{
stQueue_s* pstQueueInit;
pstQueueInit = (stQueue_s*) malloc(sizeof(stQueue_s));
if(pstQueueInit == NULL)
{
printf("Memory allocation error.\nQueue initialization failed.\n");
return -1;
}
pstQueueInit->front = 0;
pstQueueInit->rear = 0;
/**thread lock initial**/
pthread_mutex_init(&(pstQueueInit->q_lock), NULL);
pthread_cond_init(&(pstQueueInit->cond), NULL);
*ppvHandle = pstQueueInit;
return 0;
}
int queue_empty(void *pvHandle)
{
stQueue_s* pstQueue = (stQueue_s*)pvHandle;
/**Return 1 if empty**/
if( pstQueue->front == pstQueue -> rear)
{
return 1;
}
else
{
return 0;
}
}
int queue_full(void *pvHandle)
{
stQueue_s* pstQueue = (stQueue_s*)pvHandle;
/**Return 1 if full**/
if(pstQueue->front == (pstQueue->rear + 1)%MAX_QUEUE_SIZE)
{
return 1;
}
else
{
return 0;
}
}
int queue_length(void *pvHandle)
{
stQueue_s* pstQueue = (stQueue_s*)pvHandle;
return (pstQueue->rear - pstQueue->front + MAX_QUEUE_SIZE)%MAX_QUEUE_SIZE;
}
int queue_push(void *pvHandle, Rslt_s *pstOptRslt)
{
stQueue_s* pstQueue = (stQueue_s*)pvHandle;
if(pstQueue == NULL)
{
printf("Queue is not initialized, exit..\n");
return -1;
}
if(pstOptRslt == NULL)
{
printf("Invalid INPUT element, exit..\n");
return -1;
}
pthread_mutex_lock(&(pstQueue->q_lock));
pstOptRslt->stImage.copyTo(pstQueue->data[pstQueue->rear].stImage); //拷贝图像
pstQueue->data[pstQueue->rear].uiFrameSeq = pstOptRslt->uiFrameSeq;
pstQueue->rear = (pstQueue->rear + 1)%MAX_QUEUE_SIZE;
/**overwrite if queue is full**/
if(pstQueue->rear == pstQueue->front)
{
pstQueue->front = pstQueue->rear + 1;
printf("Warning: Data overwritten, Queue is full.. \n");
}
pthread_cond_signal(&(pstQueue->cond));
pthread_mutex_unlock(&(pstQueue->q_lock));
return 0;
}
int queue_pop(void *pvHandle, Rslt_s *pstOptRslt)
{
stQueue_s* pstQueue = (stQueue_s*)pvHandle;
if(pstQueue->front == pstQueue->rear)
{
printf("Empty queue, exit..\n");
pthread_mutex_unlock(&pstQueue->q_lock);
return AIA_FAIL;
}
if(pstOptRslt == NULL)
{
printf("Invalid INPUT element, exit.. \n");
return AIA_FAIL;
}
pthread_mutex_lock(&pstQueue->q_lock);
(pstQueue->data)[pstQueue->front].stImage.copyTo(pstOptRslt->stImage);
pstOptRslt->uiFrameSeq = (pstQueue->data)[pstQueue->front].uiFrameSeq;
pstQueue->front = (pstQueue->front + 1)%MAX_QUEUE_SIZE;
pthread_mutex_unlock(&(pstQueue->q_lock));
return 0;
}
int queue_destroy(void *pvHandle)
{
stQueue_s* pstQueue = (stQueue_s*)pvHandle;
if(pstQueue != NULL)
{
pthread_mutex_destroy(&(pstQueue->q_lock));
pthread_cond_destroy(&(pstQueue->cond));
free(pstQueue);
pstQueue = NULL;
}
return 0;
}
在queue_push()函数中,pstOptRslt->stImage.copyTo(pstQueue->data[pstQueue->rear].stImage); 直接想句柄指针指向的数组内容拷贝数据,这个数据是在数组之外的空间中申请的新空间,而且由于句柄指针一直存在,这个空间不会释放,直到程序结束. 因此随着程序的持续运行, 不断的进行入队操作, 占用的空间会越来越大,造成内存泄露.