【OpenCV】Mat 构造函数

关于C++ 的默认函数

默认构造函数,拷贝构造,赋值拷贝,移动拷贝,移动赋值拷贝,默认析构函数。移动拷贝函数和移动赋值拷贝是后面新加的,移动构造函数使右值对象拥有的资源无需赋值即可移动到左值中。

目前的经验来看,这三者主要是应对深拷贝,浅拷贝的问题。这类场景通常是对象持有一个数据指针。

浅拷贝,直接赋值数据指针,那就有两个指针指向同一份数据,这个就引入了很多风险,比如重复释放导致Crash 的问题。

深拷贝,为了解决两个不同的对象持有同一份数据,深拷贝直接把数据复制一份,这样两个对象的数据就没有关联了。但是这样做的缺点就是资源消耗大,比如cv::Mat 的 clone操作。

移动操作,就是把数据直接 move到目标对象,就像剪切粘贴一样,把自己的数据赋值给目标对象,然后把自己的指针置为空。这样就避免了两个对象指向同一份数据的情况。但是这也将导致源数据失效,所有移动也是有代价的,具体取决于场景,比如一些临时对象,就可以用移动操作来转移自己的数据。

具体解释可以看这里: https://learn.microsoft.com/zh-cn/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=msvc-170

大概长这个样子:

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>

class MemoryBlock
{
public:

    // Simple constructor that initializes the resource.
    explicit MemoryBlock(size_t length)
        : _length(length)
        , _data(new int[length])
    {
        std::cout << "In memory block constructor, length = "
            << _length << "." << std::endl;
    }

    // Destructor.
    ~MemoryBlock()
    {
        std::cout << "In memory block destructor, length = "
            << _length << ".";

        if (_data != nullptr)
        {
            std::cout << " Deleting resource.";
            // Delete the resource.
            delete[] _data;
        }

        std::cout << std::endl;
    }

    // Copy constructor.
    MemoryBlock(const MemoryBlock& othre)
        : _length(othre._length)
        , _data(new int[othre._length])
    {
        std::cout << "In memory block copy constructor, length = "
            << othre._length << ". Copying resource." << std::endl;

        std::copy(othre._data, othre._data + _length, _data);
    }

    // Copy assignment operator.
    MemoryBlock& operator=(const MemoryBlock& other)
    {
        std::cout << "In memory block copy assignment operator, length = "
            << other._length << ". Copying resource." << std::endl;
        if (this != &other)
        {
            // Free the existing resource.
            delete[] _data;

            _length = other._length;
            _data = new int[_length];
            std::copy(other._data, other._data + _length, _data);
        }
        return *this;
    }

    // Move copy constructor.
    MemoryBlock(MemoryBlock&& other)
        : _data(nullptr)
        , _length(0)
    {
        std::cout << "In memory block move constructor, length = "
            << other._length << ". Moving resource." << std::endl;
        _data = other._data;
        _length = other._length;
        other._data = nullptr;
        other._length = 0;
    }

    // Move assignment operator.
    MemoryBlock& operator=(MemoryBlock&& other)
    {
        std::cout << "In memory block move assignment operator, length = "
            << other._length << ". Moving resource." << std::endl;
        if (this != &other)
        {
            delete[] _data;
            _data = other._data;
            _length = other._length;
            other._data = nullptr;
            other._length = 0;
        }
        return *this;
    }

    // Retrieves the length of the data resource.
    size_t Length() const {
        return _length;
    }

private:
    size_t _length; // The length of the resource.
    int* _data; // The resource.
};


int main()
{
    // Use move constructor improve performance

    // Create a vector object and add a few elements to it.
    std::vector<MemoryBlock> v;
    v.push_back(MemoryBlock(25));
    v.push_back(MemoryBlock(75));

    // Insert a new element into the second position of the vector.
    v.insert(v.begin() + 1, MemoryBlock(50));
    return 0;
}

OpenCV Mat

在OpenCV 的 Mat结构体中,主要包括两部分,一个是Mat的宽高通道等mat信息,一个是真正的Mat数据指针,指向实际的数据位置。这里就涉及到浅拷贝,深拷贝和移动赋值的问题了,比如

cpp 复制代码
cv::Mat img = cv::imread(imgPath);
cv::Mat img1(img);						// 浅拷贝,引用计数 + 1		
cv::Mat img2 = img						// 浅拷贝,引用计数 + 1		
cv::Mat img2 = img.clone();				// 深拷贝,直接把数据复制一份

这里主要讨论 Mat 的构造和赋值方法,忽略Mat本身的一些细节。

https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html

Mat 类成员变量

Member Data Documentation

cpp 复制代码
   /*! includes several bit-fields:
        - the magic signature
        - continuity flag
        - depth
        - number of channels
    */
   int flags;
   //! the matrix dimensionality, >= 2
   int dims;
   //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
   int rows, cols;
   //! pointer to the data
   uchar* data;

   //! helper fields used in locateROI and adjustROI
   const uchar* datastart;
   const uchar* dataend;
   const uchar* datalimit;

   //! custom allocator
   MatAllocator* allocator;
   //! and the standard allocator
   static MatAllocator* getStdAllocator();
   static MatAllocator* getDefaultAllocator();
   static void setDefaultAllocator(MatAllocator* allocator);

   //! internal use method: updates the continuity flag
   void updateContinuityFlag();

   //! interaction with UMat
   UMatData* u;

   MatSize size;
   MatStep step;

这里解释一下什么是 Mat 的 step. Mat 中的step 主要表示矩阵的一行占的字节数,这个值包括每行末尾的填充字节数(如果有的话)。

构造函数

cpp 复制代码
Mat::Mat() CV_NOEXCEPT
    : flags(MAGIC_VAL), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0),
      datalimit(0), allocator(0), u(0), size(&rows), step(0)
{}

void Mat::create(int _rows, int _cols, int _type)
{
    _type &= TYPE_MASK;
    if( dims <= 2 && rows == _rows && cols == _cols && type() == _type && data )
        return;
    int sz[] = {_rows, _cols};
    create(2, sz, _type);

release

cpp 复制代码
void Mat::release()
{
	// 这里是有数据,并且refcount = 2 的时候就开始释放
    if( u && CV_XADD(&u->refcount, -1) == 1 )
        deallocate();
    u = NULL;
    datastart = dataend = datalimit = data = 0;
    for(int i = 0; i < dims; i++)
        size.p[i] = 0;
#ifdef _DEBUG
    flags = MAGIC_VAL;
    dims = rows = cols = 0;
    if(step.p != step.buf)
    {
        fastFree(step.p);
        step.p = step.buf;
        size.p = &rows;
    }
#endif
}

void Mat::deallocate()
{
    if(u)
    {
        UMatData* u_ = u;
        u = NULL;
        (u_->currAllocator ? u_->currAllocator : allocator ? allocator : getDefaultAllocator())->unmap(u_);
    }
}

void deallocate(UMatData* u) const CV_OVERRIDE
{
    if(!u)
        return;

    CV_Assert(u->urefcount == 0);
    CV_Assert(u->refcount == 0);
    if( !(u->flags & UMatData::USER_ALLOCATED) )
    {
        fastFree(u->origdata);
        u->origdata = 0;
    }
    delete u;
}

void fastFree(void* ptr)
#endif
{
#if defined HAVE_POSIX_MEMALIGN || defined HAVE_MEMALIGN
    if (isAlignedAllocationEnabled())
    {
        free(ptr);
        return;
    }
#elif defined HAVE_WIN32_ALIGNED_MALLOC
    if (isAlignedAllocationEnabled())
    {
        _aligned_free(ptr);
        return;
    }
#endif
    if(ptr)
    {
        uchar* udata = ((uchar**)ptr)[-1];
        CV_DbgAssert(udata < (uchar*)ptr &&
               ((uchar*)ptr - udata) <= (ptrdiff_t)(sizeof(void*)+CV_MALLOC_ALIGN));
        free(udata);
    }
}

赋值拷贝函数

cpp 复制代码
Mat& Mat::operator=(const Mat& m)
{
    if( this != &m )
    {
        if( m.u )
            CV_XADD(&m.u->refcount, 1);
        // 这个release 是释放自身的,也就是说会把当前的数据给释放掉
        release();
        flags = m.flags;
        if( dims <= 2 && m.dims <= 2 )
        {
            dims = m.dims;
            rows = m.rows;
            cols = m.cols;
            step[0] = m.step[0];
            step[1] = m.step[1];
        }
        else
            copySize(m);
        data = m.data;
        datastart = m.datastart;
        dataend = m.dataend;
        datalimit = m.datalimit;
        allocator = m.allocator;
        u = m.u;
    }
    return *this;
}

移动赋值来拷贝函数

cpp 复制代码
Mat& Mat::operator=(Mat&& m)
{
    if (this == &m)
      return *this;

    release();
    flags = m.flags; dims = m.dims; rows = m.rows; cols = m.cols; data = m.data;
    datastart = m.datastart; dataend = m.dataend; datalimit = m.datalimit; allocator = m.allocator;
    u = m.u;
    if (step.p != step.buf) // release self step/size
    {
        fastFree(step.p);
        step.p = step.buf;
        size.p = &rows;
    }
    if (m.dims <= 2) // move new step/size info
    {
        step[0] = m.step[0];
        step[1] = m.step[1];
    }
    else
    {
        CV_Assert(m.step.p != m.step.buf);
        step.p = m.step.p;
        size.p = m.size.p;
        m.step.p = m.step.buf;
        m.size.p = &m.rows;
    }
// 移动赋值,会把_src的数据置为空
    m.flags = MAGIC_VAL; m.dims = m.rows = m.cols = 0;
    m.data = NULL; m.datastart = NULL; m.dataend = NULL; m.datalimit = NULL;
    m.allocator = NULL;
    m.u = NULL;
    return *this;
}

assignTo

cpp 复制代码
void Mat::assignTo( Mat& m, int _type = -1) const
{
// 如果不转类型,相当于直接浅拷贝,引用计数不加1.
    if( _type < 0 )
        m = *this;
    else
        convertTo(m, _type);
}

copyTo

cpp 复制代码
void Mat::copyTo( OutputArray _dst ) const
{
    CV_INSTRUMENT_REGION();

#ifdef HAVE_CUDA
    if (_dst.isGpuMat())
    {
        _dst.getGpuMat().upload(*this);
        return;
    }
#endif

    int dtype = _dst.type();
    if( _dst.fixedType() && dtype != type() )
    {
        CV_Assert( channels() == CV_MAT_CN(dtype) );
        convertTo( _dst, dtype );
        return;
    }

    if( empty() )
    {
        _dst.release();
        return;
    }

    if( _dst.isUMat() )
    {
   		// 如果 _dst 的size 和 type 和 _src 一样 则不需要申请。否则需要重新申请。
        _dst.create( dims, size.p, type() );
        UMat dst = _dst.getUMat();
        CV_Assert(dst.u != NULL);
        size_t i, sz[CV_MAX_DIM] = {0}, dstofs[CV_MAX_DIM], esz = elemSize();
        CV_Assert(dims > 0 && dims < CV_MAX_DIM);
        for( i = 0; i < (size_t)dims; i++ )
            sz[i] = size.p[i];
        sz[dims-1] *= esz;
        dst.ndoffset(dstofs);
        dstofs[dims-1] *= esz;
        dst.u->currAllocator->upload(dst.u, data, dims, sz, dstofs, dst.step.p, step.p);
        return;
    }

    if( dims <= 2 )
    {
        _dst.create( rows, cols, type() );
        Mat dst = _dst.getMat();
        if( data == dst.data )
            return;

        if( rows > 0 && cols > 0 )
        {
            Mat src = *this;
            Size sz = getContinuousSize2D(src, dst, (int)elemSize());
            CV_CheckGE(sz.width, 0, "");

            const uchar* sptr = src.data;
            uchar* dptr = dst.data;

#if IPP_VERSION_X100 >= 201700
            CV_IPP_RUN_FAST(CV_INSTRUMENT_FUN_IPP(ippiCopy_8u_C1R_L, sptr, (int)src.step, dptr, (int)dst.step, ippiSizeL(sz.width, sz.height)) >= 0)
#endif

            for (; sz.height--; sptr += src.step, dptr += dst.step)
                memcpy(dptr, sptr, sz.width);
        }
        return;
    }

    _dst.create( dims, size, type() );
    Mat dst = _dst.getMat();
    if( data == dst.data )
        return;

    if( total() != 0 )
    {
        const Mat* arrays[] = { this, &dst };
        uchar* ptrs[2] = {};
        NAryMatIterator it(arrays, ptrs, 2);
        size_t sz = it.size*elemSize();

        for( size_t i = 0; i < it.nplanes; i++, ++it )
            memcpy(ptrs[1], ptrs[0], sz);
    }
}

copyTo 用法1

如果 _dst 的size 和 type 和 _src 一样 则不需要申请。否则需要重新申请。所以在实际中我们使用

cpp 复制代码
// 如果_dst 是一个局部变量,那每次拷贝都需要申请一块内存
// 如果_dst 是一个生命周期更长的变量,那每次拷贝就不需要申请内存。实际测试中,这种会省很多时间。
frame.copyTo(_dst);

copyTo 用法2

cpp 复制代码
auto frameAuto = cv::Mat(1080, 720, CV_8SC3);						// 384us
frameCopyTo = cv::Mat(1080, 720, CV_8SC3);							// 339us
frameCopyToGlobal = cv::Mat(1080, 720, CV_8SC3);					// 507us
cv::Mat(1080, 720, CV_8SC3).copyTo(frameCopyToGlobal);				// 647us

为什么给全局的Mat 赋值会耗时更多,因为如果_dst Mat 不为空,切引用为1, 就会多一次对 _dst 的释放。

线程安全

在这里cv::Mat 引入了引用计数的技术,在赋值或浅拷贝的时候,只拷贝mat信息,不拷贝mat 数据。当 引用计数为0的时候,就释放数据。

相关推荐
Terry Cao 漕河泾36 分钟前
SRT3D: A Sparse Region-Based 3D Object Tracking Approach for the Real World
人工智能·计算机视觉·3d·目标跟踪
多猫家庭41 分钟前
宠物毛发对人体有什么危害?宠物空气净化器小米、希喂、352对比实测
人工智能·宠物
AI完全体1 小时前
AI小项目4-用Pytorch从头实现Transformer(详细注解)
人工智能·pytorch·深度学习·机器学习·语言模型·transformer·注意力机制
AI知识分享官1 小时前
智能绘画Midjourney AIGC在设计领域中的应用
人工智能·深度学习·语言模型·chatgpt·aigc·midjourney·llama
程序小旭1 小时前
Objects as Points基于中心点的目标检测方法CenterNet—CVPR2019
人工智能·目标检测·计算机视觉
阿利同学1 小时前
yolov8多任务模型-目标检测+车道线检测+可行驶区域检测-yolo多检测头代码+教程
人工智能·yolo·目标检测·计算机视觉·联系 qq1309399183·yolo多任务检测·多检测头检测
CV-King1 小时前
计算机视觉硬件知识点整理(三):镜头
图像处理·人工智能·python·opencv·计算机视觉
Alluxio官方1 小时前
Alluxio Enterprise AI on K8s FIO 测试教程
人工智能·机器学习
AI大模型知识分享1 小时前
Prompt最佳实践|指定输出的长度
人工智能·gpt·机器学习·语言模型·chatgpt·prompt·gpt-3
十有久诚2 小时前
TaskRes: Task Residual for Tuning Vision-Language Models
人工智能·深度学习·提示学习·视觉语言模型