OpenCV中踩过的坑系列 01- Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)

目录

 

函数介绍

opencv中内存管理

踩坑举例

参考资料


函数介绍

函数  Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);的声明:

    /** @overload
    @param rows Number of rows in a 2D array.
    @param cols Number of columns in a 2D array.
    @param type Array type. Use CV_8UC1, ..., CV_64FC4 to create 1-4 channel matrices, or
    CV_8UC(n), ..., CV_64FC(n) to create multi-channel (up to CV_CN_MAX channels) matrices.
    @param data Pointer to the user data. Matrix constructors that take data and step parameters do not
    allocate matrix data. Instead, they just initialize the matrix header that points to the specified
    data, which means that no data is copied. This operation is very efficient and can be used to
    process external data using OpenCV functions. The external data is not automatically deallocated, so
    you should take care of it.
    @param step Number of bytes each matrix row occupies. The value should include the padding bytes at
    the end of each row, if any. If the parameter is missing (set to AUTO_STEP ), no padding is assumed
    and the actual step is calculated as cols*elemSize(). See Mat::elemSize.
    */
    Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

rows 二维数组的行数 。

cols 二维数组的列数。

type 数组的类型。使用CV_8UC1, ..., CV_64FC4创建一个1-4通道的矩阵,或者使用 CV_8UC(n), ..., CV_64FC(n)创建一个多通道的矩阵。

data 指向用户数据的指针。矩阵构造器将会取数据和步长参数不分配矩阵数据。取而代之的是,仅仅初始化矩阵的头去指向具体的数据,这意味数据不会被拷贝。这个操作将会非常高效,并且可以用opencv的函数来处理外部的数据。值得注意的是,这些外部的数据不会被自动释放。

step 矩阵每行占据的字节数。这个值必须包含每行补齐的数据。如果不提供这个参数,就假设没有补齐,实际的步长就是等于cols*elemSize()。

这个函数在使用的时候有两点需要注意:

1.这个函数执行时,只是初始化了mat的头,不拷贝数据,也就是使mat->data等于这个外部数据的data。

2.外部数据data需要另外释放,opencv的内存管理不会对这一部分内存进行释放。

此处的外部用户数据应该是指从摄像头中采集的数据那种外部数据,而不是opencv中的mat.

那如果从一个opencv的Mat中获取的data,并且这个Mat是局部对象,会发生什么呢??

 

opencv中内存管理

opencv中的内存管理是通过refCount引用计数器机制来实现的。当Mat对象构造时,会对refCount加一,当Mat对象析构时,会对refCount减一。然后在退出当前作用域时,判断refCount的值是否为零,若为零则对data对应的内存进行释放,不为0就不释放。(大概是这么理解,详细内部原理没有仔细研究)。

opencv提供两种复制的方式:深拷贝和浅拷贝 。在发生拷贝构造函数和operator=函数时候,采用的是浅拷贝。copyto()和clone()函数发生的是深拷贝。浅拷贝时,会对最原始的那个mat的refCount进行加减操作,而深拷贝是是另外单独分配内存,相当于有了一个新的refCount。

关于refCount,在opencv2中是Mat对象的refcount(详见参考资料1),在opencv3和4中修改为UMatData的refcount,这个UMatData在Mat中是作为成员变量存在的:

struct CV_EXPORTS UMatData
{
    enum MemoryFlag { COPY_ON_MAP=1, HOST_COPY_OBSOLETE=2,
        DEVICE_COPY_OBSOLETE=4, TEMP_UMAT=8, TEMP_COPIED_UMAT=24,
        USER_ALLOCATED=32, DEVICE_MEM_MAPPED=64,
        ASYNC_CLEANUP=128
    };
    UMatData(const MatAllocator* allocator);
    ~UMatData();

    // provide atomic access to the structure
    void lock();
    void unlock();

    bool hostCopyObsolete() const;
    bool deviceCopyObsolete() const;
    bool deviceMemMapped() const;
    bool copyOnMap() const;
    bool tempUMat() const;
    bool tempCopiedUMat() const;
    void markHostCopyObsolete(bool flag);
    void markDeviceCopyObsolete(bool flag);
    void markDeviceMemMapped(bool flag);

    const MatAllocator* prevAllocator;
    const MatAllocator* currAllocator;
    int urefcount;
    int refcount;
    uchar* data;
    uchar* origdata;
    size_t size;

    UMatData::MemoryFlag flags;
    void* handle;
    void* userdata;
    int allocatorFlags_;
    int mapcount;
    UMatData* originalUMatData;
};

 

踩坑举例

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;

#define IN
#define OUT

void func(IN const Mat &src,IN int val, OUT Mat &dst1, OUT Mat &dst2);

int main()
{
	Mat src = (Mat_<Vec3b>(2, 2) << Vec3b(0,2,155), Vec3b(155, 2, 155),
		Vec3b(200, 50, 155), Vec3b(15, 2, 45));

	Mat dst1, dst2;
	func(src, 90, dst1, dst2);

	return 0;
}

void func(IN const Mat &src,IN int val, OUT Mat &dst1, OUT Mat &dst2)
{
	CV_Assert(src.type() == CV_8UC3);

	Mat dst_float;
	src.convertTo(dst_float, CV_32FC3);
	cout << "dst_float.u->refcount: "<< dst_float.u->refcount << endl;

	Mat dst_signed1(src.cols *src.rows, 1, CV_32FC3, dst_float.data);
	Mat dst_signed2 = Mat(src.cols*src.rows, 1, CV_32SC3, Scalar(val,val,val));

	cout << "dst_float.u->refcount: " << dst_float.u->refcount << endl;
	cout << "dst_signed1.u->refcount: " << dst_signed1.u->refcount << endl;
	cout << "dst_signed2.u->refcount: " << dst_signed2.u->refcount << endl;

	dst1 = dst_signed1;
	dst2 = dst_signed2;

	cout << "dst_float.u->refcount: " << dst_float.u->refcount << endl;
	cout << "dst_signed1.u->refcount: " << dst_signed1.u->refcount << endl;
	cout << "dst_signed2.u->refcount: " << dst_signed2.u->refcount << endl;
}

说明一下,这个程序只是为了说明我遇到的坑,并没有其他的实际意义。

首先创建了一个2*2元素为Vec3b的Mat,具体值如下:

然后调用函数void func(IN const Mat &src, int val, OUT Mat &dst1, OUT Mat &dst2);

这个函数执行过程如下:

(1)利用convertTo函数将src(CV_8UC3)转dst_float(CV_32FC3);dst_float为局部对象。

(2)执行Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);函数,dst_signed1将会和dst_float指向同一份数据。

(3)执行 Mat(int rows, int cols, int type, const Scalar& s)函数,创建了一个指定值的mat,然后利用赋值运算符,实现了dst_signed2的浅拷贝。

(4)利用赋值运算符将dst_signed1浅拷贝到dst1,dst_signed2浅拷贝到dst2。

这个程序如果直接运行会出现异常:

为什么会出现这个异常?

将cout << "dst_signed1.u->refcount: " << dst_signed1.u->refcount << endl;注释起来,运行程序

我们可以看到dst_float的计数器一直为1,在执行Mat dst_signed1(src.cols *src.rows, 1, CV_32FC3, dst_float.data);这个之后,它也依然为1,说明这个操作连浅拷贝都算不上,只是单纯的让dst_signed1.data = dst_float.data而已。然而dst_float的作用域只在函数的生存期内,在函数退出时,dst_float的计数器就会置为0,dst_float.data会被释放,dst_signed1指向一片无效的地址,dst1也指向一片无效的地址。。

这个就是我说到的坑。。。

如何解决这个问题呢,其实非常简单,将

dst1 = dst_signed1;

替换为

dst1 = dst_signed1.clone();

即可。这样在输出之前将 dst_float.data拷贝到dst1中,就可以将结果正确的传出了。

至于dst_signed2.u->refcount这个可以自己分析一下。

小结:

使用Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)一定要谨慎:

(1)如果传入的data是一个局部对象的data,后期在传出时必须进行深拷贝才可以传出正确的数据。

(2)如果传入的data是一个用户data,需要自己手动释放掉,如果是opencv的Mat的data会进行自动内存管理。

总之,得根据自己的情况谨慎的使用。

 

参考资料

1.https://blog.csdn.net/historycomputer/article/details/52794494

2.https://stackoverflow.com/questions/28200243/cvmat-refcount-missing-in-opencv-3-0

 

 

 

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值