目录
4,图像截取 Mat(const Mat&, const Rect&)
一,Mat基础数据结构
1,Mat的数据成员
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;
UMatData* u;
MatSize size;
MatStep step;
其中flags、u指针、step在下面的章节。
成员dims是维数,当维数是2时,成员rows和cols才有意义。
data是图像的数据指针。
2,flags
以下宏来自opencv-4.2.0\modules\gapi\include\opencv2\gapi\own\cvdefs.hpp中的源代码。
按照从低到高位分别是:
(1)深度 depth()
#define CV_CN_SHIFT 3
#define CV_DEPTH_MAX (1 << CV_CN_SHIFT)
#define CV_8U 0
#define CV_8S 1
#define CV_16U 2
#define CV_16S 3
#define CV_32S 4
#define CV_32F 5
#define CV_64F 6
#define CV_16F 7
#define CV_MAT_DEPTH_MASK (CV_DEPTH_MAX - 1)
#define CV_MAT_DEPTH(flags) ((flags) & CV_MAT_DEPTH_MASK)
即flags的前3位存的是8种深度。
后缀表示数据类型,U unsigned S signed F float
inline
int Mat::depth() const
{
return CV_MAT_DEPTH(flags);
}
depth函数用来获取深度。
(2)通道数 channels()
#define CV_CN_MAX 512
#define CV_CN_SHIFT 3
#define CV_MAT_CN_MASK ((CV_CN_MAX - 1) << CV_CN_SHIFT)
#define CV_MAT_CN(flags) ((((flags) & CV_MAT_CN_MASK) >> CV_CN_SHIFT) + 1)
最少1通道,最多513个通道,即flag的低3位是深度,接下来9位是通道数-1。
inline
int Mat::channels() const
{
return CV_MAT_CN(flags);
}
channels函数用来获取通道数。
CV_8U和CV_8UC1都等于0(表示1通道)
(3)图像类型 type()
#define CV_MAT_TYPE_MASK (CV_DEPTH_MAX*CV_CN_MAX - 1)
#define CV_MAT_TYPE(flags) ((flags) & CV_MAT_TYPE_MASK)
flag的前12位是type,由深度和通道数组合而成。
type() == (channels()-1) * 8 + depth()
(4)flag第13-14位
暂无用途
(5)判断连续 isContinuous()
#define CV_MAT_CONT_FLAG_SHIFT 14
#define CV_MAT_CONT_FLAG (1 << CV_MAT_CONT_FLAG_SHIFT)
#define CV_IS_MAT_CONT(flags) ((flags) & CV_MAT_CONT_FLAG)
#define CV_IS_CONT_MAT CV_IS_MAT_CONT
即flag的第15位,判断整个mat所有像素是否是连续存储。
inline
bool Mat::isContinuous() const
{
return (flags & CONTINUOUS_FLAG) != 0;
}
(6)子图标志 isSubmatrix()
#define CV_SUBMAT_FLAG_SHIFT 15
#define CV_SUBMAT_FLAG (1 << CV_SUBMAT_FLAG_SHIFT)
#define CV_IS_SUBMAT(flags) ((flags) & CV_MAT_SUBMAT_FLAG)
CV_MAT_SUBMAT_FLAG找不到定义,应该就是CV_SUBMAT_FLAG
flag的第16位,判断图像是不是另外一个图像的子图。
SUBMATRIX_FLAG = CV_SUBMAT_FLAG
inline
bool Mat::isSubmatrix() const
{
return (flags & SUBMATRIX_FLAG) != 0;
}
(7)magic signature
flags的高16位是magic signature,用来区分Mat的类型
3,UMatData
Mat对象包含了一个UMatData的结构体指针:UMatData* u;
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;
};
不同的Mat对象共享一个内存块时,u指针是同一个值,而u中的refcount是引用计数。
4,step
step是关于内存分布的记录值。
struct CV_EXPORTS MatStep
{
MatStep();
explicit MatStep(size_t s);
const size_t& operator[](int i) const;
size_t& operator[](int i);
operator size_t() const;
MatStep& operator = (size_t s);
size_t* p;
size_t buf[2];
protected:
MatStep& operator = (const MatStep&);
};
p指针其实是个数组,其中记录着每一维度的内存地址间距。
如二维图像p->{100,1},则2行的间距是100字节,行内2个元素的间距是1字节。
MatStep重载了[],所以常用调用方式是:
Mat img;
cout << img.step[0];
二,Mat常用函数
1,Mat类的create函数
opencv-4.2.0\modules\core\src\matrix.cpp中的create函数:
void Mat::create(int d, const int* _sizes, int _type)
{
int i;
CV_Assert(0 <= d && d <= CV_MAX_DIM && _sizes);
_type = CV_MAT_TYPE(_type);
if( data && (d == dims || (d == 1 && dims <= 2)) && _type == type() )
{
if( d == 2 && rows == _sizes[0] && cols == _sizes[1] )
return;
for( i = 0; i < d; i++ )
if( size[i] != _sizes[i] )
break;
if( i == d && (d > 1 || size[1] == 1))
return;
}
int _sizes_backup[CV_MAX_DIM]; // #5991
if (_sizes == (this->size.p))
{
for(i = 0; i < d; i++ )
_sizes_backup[i] = _sizes[i];
_sizes = _sizes_backup;
}
release();
if( d == 0 )
return;
flags = (_type & CV_MAT_TYPE_MASK) | MAGIC_VAL;
setSize(*this, d, _sizes, 0, true);
if( total() > 0 )
{
MatAllocator *a = allocator, *a0 = getDefaultAllocator();
#ifdef HAVE_TGPU
if( !a || a == tegra::getAllocator() )
a = tegra::getAllocator(d, _sizes, _type);
#endif
if(!a)
a = a0;
try
{
u = a->allocate(dims, size, _type, 0, step.p, ACCESS_RW /* ignored */, USAGE_DEFAULT);
CV_Assert(u != 0);
}
catch (...)
{
if (a == a0)
throw;
u = a0->allocate(dims, size, _type, 0, step.p, ACCESS_RW /* ignored */, USAGE_DEFAULT);
CV_Assert(u != 0);
}
CV_Assert( step[dims-1] == (size_t)CV_ELEM_SIZE(flags) );
}
addref();
finalizeHdr(*this);
}
void Mat::create(const std::vector<int>& _sizes, int _type)
{
create((int)_sizes.size(), _sizes.data(), _type);
}
第一个函数入参_sizes是一个数组,常见的是2个数,即{_rows, _cols},函数会调用allocate函数来分配内存。
第二个函数是个重载,传入的是vector而不是数组。
opencv-4.2.0\modules\core\include\opencv2\core\mat.inl.hpp 中的create函数:
inline
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);
}
inline
void Mat::create(Size _sz, int _type)
{
create(_sz.height, _sz.width, _type);
}
第一个函数是调用上面的函数。
第二个函数是调用第一个函数。
2,Mat类的copyTo函数
opencv-4.2.0\modules\core\src\copy.cpp里面的源代码:
/* dst = src */
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.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);
}
}
大概扫了一眼,主要是调出参的create函数,然后用memcpy做深拷贝。
3,Mat类的=运算符
opencv-4.2.0\modules\core\include\opencv2\core\mat.inl.hpp里面的源代码:
inline
Mat& Mat::operator = (const Mat& m)
{
if( this != &m )
{
if( m.u )
CV_XADD(&m.u->refcount, 1);
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;
}
其中最核心的一句:
data = m.data;
直接把data指针拷贝过来,不拷贝数据。
4,图像截取 Mat(const Mat&, const Rect&)
opencv\opencv-4.2.0\modules\core\src\matrix.cpp里面的源代码:
Mat::Mat(const Mat& m, const Rect& roi)
: flags(m.flags), dims(2), rows(roi.height), cols(roi.width),
data(m.data + roi.y*m.step[0]),
datastart(m.datastart), dataend(m.dataend), datalimit(m.datalimit),
allocator(m.allocator), u(m.u), size(&rows)
{
CV_Assert( m.dims <= 2 );
size_t esz = CV_ELEM_SIZE(flags);
data += roi.x*esz;
CV_Assert( 0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols &&
0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows );
if( u )
CV_XADD(&u->refcount, 1);
if( roi.width < m.cols || roi.height < m.rows )
flags |= SUBMATRIX_FLAG;
step[0] = m.step[0]; step[1] = esz;
updateContinuityFlag();
if( rows <= 0 || cols <= 0 )
{
release();
rows = cols = 0;
}
}
只进行指针运算,没有深拷贝操作,所以几乎不耗时。参考Mat的内存结构
截取对象的u指针和原对象的u指针是一样的,所以他们是对同一块内存进行引用计数。
5,imwrite
opencv-4.2.0\modules\imgcodecs\src\loadsave.cpp里面的源代码:
static const size_t CV_IO_MAX_IMAGE_PARAMS = cv::utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PARAMS", 50);
static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
const std::vector<int>& params, bool flipv )
{
bool isMultiImg = img_vec.size() > 1;
std::vector<Mat> write_vec;
ImageEncoder encoder = findEncoder( filename );
if( !encoder )
CV_Error( Error::StsError, "could not find a writer for the specified extension" );
for (size_t page = 0; page < img_vec.size(); page++)
{
Mat image = img_vec[page];
CV_Assert(!image.empty());
CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 );
Mat temp;
if( !encoder->isFormatSupported(image.depth()) )
{
CV_Assert( encoder->isFormatSupported(CV_8U) );
image.convertTo( temp, CV_8U );
image = temp;
}
if( flipv )
{
flip(image, temp, 0);
image = temp;
}
write_vec.push_back(image);
}
encoder->setDestination( filename );
CV_Assert(params.size() <= CV_IO_MAX_IMAGE_PARAMS*2);
bool code = false;
try
{
if (!isMultiImg)
code = encoder->write( write_vec[0], params );
else
code = encoder->writemulti( write_vec, params ); //to be implemented
}
catch (const cv::Exception& e)
{
std::cerr << "imwrite_('" << filename << "'): can't write data: " << e.what() << std::endl << std::flush;
}
catch (...)
{
std::cerr << "imwrite_('" << filename << "'): can't write data: unknown exception" << std::endl << std::flush;
}
// CV_Assert( code );
return code;
}
bool imwrite( const String& filename, InputArray _img,
const std::vector<int>& params )
{
CV_TRACE_FUNCTION();
CV_Assert(!_img.empty());
std::vector<Mat> img_vec;
if (_img.isMatVector() || _img.isUMatVector())
_img.getMatVector(img_vec);
else
img_vec.push_back(_img.getMat());
CV_Assert(!img_vec.empty());
return imwrite_(filename, img_vec, params, false);
}
imwrite函数的第三个参数不太常用,是个vector参数列表,里面不能超过100个元素。
三,其他基础数据结构
1,图像尺寸上限
opencv-4.2.0\modules\imgcodecs\src\loadsave.cpp里面的源代码:
static const size_t CV_IO_MAX_IMAGE_WIDTH = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_WIDTH", 1 << 20);
static const size_t CV_IO_MAX_IMAGE_HEIGHT = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_HEIGHT", 1 << 20);
static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30);
宽高都不超过100万,且像素总数不超过10亿
尺寸校验函数:
static Size validateInputImageSize(const Size& size)
{
CV_Assert(size.width > 0);
CV_Assert(static_cast<size_t>(size.width) <= CV_IO_MAX_IMAGE_WIDTH);
CV_Assert(size.height > 0);
CV_Assert(static_cast<size_t>(size.height) <= CV_IO_MAX_IMAGE_HEIGHT);
uint64 pixels = (uint64)size.width * (uint64)size.height;
CV_Assert(pixels <= CV_IO_MAX_IMAGE_PIXELS);
return size;
}
2,Size
typedef Size_<int> Size2i;
typedef Size_<int64> Size2l;
typedef Size_<float> Size2f;
typedef Size_<double> Size2d;
typedef Size2i Size;
Size_是个模板类,只有width和height2个数据成员。
3,***Array
modules\core\include\opencv2\core\mat.hpp
(1)InputArray
typedef const _InputArray& InputArray;
typedef InputArray InputArrayOfArrays;
_InputArray类有3个数据成员:
public:
template<typename _Tp> _InputArray(const Mat_<_Tp>& m);
Mat getMat(int idx=-1) const;
protected:
int flags;
void* obj;
Size sz;
void init(int _flags, const void* _obj);
void init(int _flags, const void* _obj, Size _sz);
obj指针用来指向图像。
构造函数很简单,直接把Mat对象强转成void指针:
inline _InputArray::_InputArray(const Mat& m) { init(MAT+ACCESS_READ, &m); }
inline Mat _InputArray::getMat(int i) const
{
if( kind() == MAT && i < 0 )
return *(const Mat*)obj;
return getMat_(i);
}
getMat是把obj强转回Mat对象。
(2)OutputArray
typedef const _OutputArray& OutputArray;
typedef OutputArray OutputArrayOfArrays;
_OutputArray类继承了_InputArray类,没有新增数据成员。
功能是类似的:
inline _OutputArray::_OutputArray(Mat& m) { init(MAT+ACCESS_WRITE, &m); }
(3)InputOutputArray
typedef const _InputOutputArray& InputOutputArray;
typedef InputOutputArray InputOutputArrayOfArrays;
_InputOutputArray类继承了_OutputArray类,没有新增数据成员。
功能是类似的:
inline _InputOutputArray::_InputOutputArray(Mat& m) { init(MAT+ACCESS_RW, &m); }