OpenCV的基础数据类型主要分为三类:
下表为cv::Point类支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | cv::Point2i p; cv::Point3f p; |
复制构造函数 | cv::Point3f p2( p1 ); |
值构造函数 | cv::Point2i p( x0, x1 ); cv::Point3d p( x0, x1, x2 ); |
转换为固定向量类 | (cv::Vec3f) p; |
成员访问 | p.x; p.y; // 对于三维点类:p.z |
点乘 | float x = p1.dot( p2 ) |
双精度点乘 | double x = p1.ddot( p2 ) |
叉乘 | p1.cross( p2 ) // 仅三维点类拥有 |
判断一个点p是否在矩形r内 | p.inside( r ) // 仅二维点类拥有 |
cv: : Scalar是四维点类。cv:: Scalar 类有些与四元向量相关的特殊成员函数。下表为cv::Scalar类支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | cv::Scalar s; |
复制构造函数 | cv::Scalar s2( s1 ); |
值构造函数 | cv::Scalar s( x0 ); cv::Scalar s( x0, x1, x2, x3 ); |
元素相乘 | s1.mul( s2 ); |
(四元数)共轭 | s.conj(); // (returns cv::Scalar(s0,-s1,-s2,-s2)) |
(四元数)真值判断 | s.isReal(); // (returns true iff s1s2s3==0) |
cv::Size类在实际操作时与 Point 类相似,而且可以与Point类互相转换。这两者之间主要的区别在于Point 类的数据成员是x,y,而Size类对应的成员是width和height。Size类的三个别名分别为cv::Size,cv::Size2i, cv: :Size2f。前面两个是等价的,表示整数类型。下表是cv::Size类支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | cv::Size sz; cv::Size2i sz; cv::Size2f sz; |
复制构造函数 | cv::Size sz2( sz1 ); |
值构造函数 | cv::Size2f sz( w, h ); |
成员访问 | sz.width; sz.height; |
计算面积 | sz.area(); |
下表为cv::Rect类支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | cv::Rect r; |
复制构造函数 | cv::Rect r2( r1 ); |
值构造函数 | cv::Rect( x, y, w, h ); |
由原点及面积构造 | cv::Rect( p, sz ); |
由左上及右下对角点构造 | cv::Rect( p1, p2 ); |
成员访问 | r.x; r.y; r.width; r.height; |
计算面积 | r.area(); |
获取左上角点 | r.tl(); |
获取右下角点 | r.br(); |
判定点p是否在矩形r内 | r.contains( p ); |
矩形r1与矩形r2的交集 | cv::Rect r3 = r1 & r2; r1 &= r2 |
同时包含r1和r2的面积最小的矩形 | cv::Rect r3 = r1; r1 |= r2; |
平移矩形rx个数量 | cv::Rect rx = r + x; r += x; |
将矩形扩大rs | cv::Rect rs = r + s; r += s; |
比较矩形r1和r2是否相等 | bool eq = (r1 == r2); |
比较矩形r1和r2是否不相等 | bool ne = (r1 != r2); |
cv::RotatedRect 类是OpenCV 中少数底层没有使用模板的C++接口类之一。同时,它是包含一个中心点cv::Point2f 个大小cv: : Size2f和一个角度float 。其浮点类型 (float) 的角度代表图形绕中心点旋转的角度。下表为cv::RotatedRect支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | cv::RotatedRect rr(); |
复制构造函数 | cv::RotatedRect rr2( rr1 ); |
通过两个角点构造 | cv::RotatedRect( p1, p2 ); |
值构造函数,通过一个点,一个大小,一个角度构造 | cv::RotatedRect rr( p, sz, theta ) ; |
获取成员 | rr.center; rr.size; rr.angle; |
返回四个角点 | rr.points( pts[4] ); |
cv::Matx<>
是轻量级矩阵模块类,最大支持6x6矩阵,在运行时不需要动态分布内存,执行效率更高。固定矩阵类的矩阵名称格式为cv::Matx{1,2,…}{1,2,…}{f,d},其中数字为1到6中的任何数,尾部的字母f表示float,d表示double。如cv::Matx33f表示3*3的数据类型为float的矩阵,同cv::Matx<3,3,float>。下表为cv::Matx支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | cv::Matx33f m33f; cv::Matx43d m43d; |
复制构造函数 | cv::Matx22d m22d( n22d ); |
值构造函数 | cv::Matx21f m(x0,x1); cv::Matx44d m(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15); |
同元矩阵 | m33f = cv::Matx33f::all( x ); |
全零矩阵 | m23d = cv::Matx23d::zeros(); |
全一矩阵 | m16f = cv::Matx16f::ones(); |
创建单位矩阵 | m33f = cv::Matx33f::eye();// 主对角线全为1的单位矩阵 |
创建一个斜对角线矩阵 | m31f = cv::Matx33f::diag();// 创建一个3x1对角线矩阵 |
创建一个具有均匀分布项的矩阵 | m33f = cv::Matx33f::randu( min, max ); |
创建一个正态分布的矩阵 | m33f = cv::Matx33f::nrandn( mean, variance ); |
成员访问 | m( i, j ), m( i ); |
单代数运算 | m * a; a * m; m / a; |
比较 | m1 == m2; m1 != m2; |
点乘 | m1.dot( m2 ); //单精度点乘 |
点乘 | m1.ddot( m2 ); //双精度点乘 |
重构矩阵 | m91f = m33f.reshape<9,1>(); |
类型转换 | m44f = (Matx44f) m44d |
获取在(i, j)处的2 x 2子矩阵 | m44f.get_minor<2, 2>( i, j ); |
获取矩阵第i行 | m14f = m44f.row( i ); |
获取矩阵第j列 | m41f = m44f.col( j ); |
获取矩阵对角 | m41f = m44f.diag(); |
计算转置 | n44f = m44f.t(); |
矩阵求逆 | n44f = m44f.inv( method ); //默认求逆方法为cv::DECOMP_LU |
解线性系统 | m31f = m33f.solve( rhs31f, method ) m32f = m33f.solve<2>( rhs32f, method ); // 默认求解方法为DECOMP_LU |
逐像素相乘 | m1.mul( m2 ); |
相比于STL的向量类,模版类cv::Vec<>
具有固定的长度,被称为固定向量向量类(FIxed Vector Class),固定向量类的大小在编译时就已经知道,因此在运行时无需动态的分配内存。固定向量类继承至固定矩阵类,名称格式为cv::vec{2,3,4,6}{b,s,w,I,f,d}
,其中b表示uchar
,s表示short int
,w表示unsigned short
,I表示int
。下表为cv::Vec
支持的操作:
操作 | 示例 |
---|---|
默认构造函数 | Vec2s v2s; Vec6f v6f; // 等等 |
复制构造函数 | Vec3f u3f( v3f ); |
值构造函数 | Vec2f v2f(x0,x1); Vec6d v6d(x0,x1,x2,x3,x4,x5); |
成员访问 | v4f[ i ]; v3w( j ); // (operator() 及 operator[]均可使用 |
向量叉乘 | v3f.cross( u3f ); |
OpenCV的复数类与STL复数类模板complex<> 不一样,但是与之兼容,可以相互转换。它们最大的区别在于成员获取。STL类中,虚部或者实部是通过成员函数real() imag() 获取的,而在OpenCV 中,直接通过成员变量re和im获取。另外,cv::Complexf cv::Complexd分别为单精度和双精度复数的别名。下表为复数类cv::Complex支持的操作:
操作 | 示例 |
---|---|
构造函数 | cv::Complexf z1; cv::Complexd z2; |
复制构造函数 | cv::Complexf z2( z1 ); |
值构造函数 | cv::Complexd z1(re0); cv::Complexd(re0,im1) ; |
复制构造函数 | cv::Complexf u2f( v2f ); |
成员访问 | z1.re; z1.im; |
共轭复数 | z2 = z1.conj(); |
用于退出算法的停止条件。终止条件的形式要么是达到允许的有限迭代次数(称为COUNT或MAX_ITER) ,要么是某种形式的误差参数(如果接近于如此程度,就可以退出,称为EPS,即epsilon 的简称) 。常见的做法是通过构造函数:TermCriteria(int type, int maxCount, double epsilon)
进行设置。变量type设置为 cv::TermCriteria::COUNT
或者cv::TermCriteria::EPS
,也可以将两个条件并(使用位运算符:|)在一起。如果终止条件包含cv::TermCriteria::COUNT
就是告诉算法在maxCount次迭代之后终止。如果终止条件包含cv::TermCriteria::EPS
,就是告诉算法在与算法收敛相关的某些度量降到epsilon以下后终止。
该类用于确定一个连续的整数序列。cv::Range
对象有start
和end
两个元素。一般使用构造函数cv::Range(int start, int end)
进行设定,范围包含初始值start,不包含终止值end。如,cv::Range rng(0, 4)
包含0,1,2,3,但不包含4。
使用size()
函数可以得到该类对象所含的元素数量,上述rng的元素数量为4,即rng.size()
等于4。该类使用empty()
判断range是否含有元素。cv::Range::all()
可以用于获得对象可用范围。
智能指针 (smart pointer) 是C++ 中的类型。这个指针允许我们创建一个对象指针的引用,所有这些引用都会被计数,且其是线程安全的。当引用超出范围,智能指针的引用计数就会减少。一旦所有的引用(指针的实例)消失,这个对象将自动清理(释放)。可以通过类似似cv::Ptr< Matx33f > p(new cv: :Matx33f) 或者 cv::Ptr< Matx33f > p = makePtr< cv::Matx33f() > 的形式实现。以下是其一些成员函数介绍:
OpenCV使用异常来处理错误。OpenCV定义了自己的异常类型cv::exception
,它派生自STL异常类std::exception
。实际上,这种异常类型并没有什么特别之处,只是存在于cv::
命名空间中,从而与同样派生自std::exception
的其他对象区别开来。
类型cv::Exception
有成员code
、err
、func
、file
和line
,它们分别是一个数字错误代码、一个表示生成异常的错误性质的字符串、发生错误的函数的名称、发生错误的文件以及一个表示该文件中发生错误所在的行数。err
、func
和file
都是STL字符串。OpenCV有以下内置宏用于抛出自己的异常:
printf
格式的字符串和参数代替固定的描述DataType<>
本身是一个模板,因此传递的实际对象是该模板的特殊化。这是c++的一个特性。以下为DataType
定义的模板类的示例:
template class DataType
{
typedef _Tp value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum
{
generic_type = 1,
depth = -1,
channels = 1,
fmt = 0,
type = CV_MAKETYPE(depth, channels)
};
};
float
类型定义的cv::DataType<>
:
template<> class DataType
{
public:
typedef float value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum
{
generic_type = 0, // 在core.hpp中所有类型都是零
depth = DataDepth::value, // OpenCV使用的数据类型标识符,cv::DataDepth::value解析为常量CV_32F
channels = 1, // channels为1,因为float仅仅是一个数
fmt = DataDepth::fmt, // cv::DataDepth::fmt 解析为f
type = CV_MAKETYPE(depth, channels) // type解析为CV_32FC1
};
};
关于DataType<>的重要一点是传达更复杂结构的性质。这是必不可少的,例如,允许算法以一种与传入数据类型无关的方式实现(即,使用内省来决定如何处理传入数据的算法)。
cv::Rect
实际上是模板的别名,其对应的模板名为cv::Rect_<>
。因此,这个模板可以被特殊化为cv::DataType
或者cv::DataType< Rect_
。对于cv::DataType
,其所有的元素都是整数,模板形参Tp的所有实例化都会解析为int。以下为cv::Rect<>实例化cv::DataType<>的例子(它本身包含一个尚未特化的类型Tp):
template class DataType >
{
public:
typedef Rect_<_Tp> value_type;
typedef Rect_::work_type> work_type;
typedef _Tp channel_type;
typedef Vec vec_type;
enum
{
generic_type = 0,
depth = DataDepth::value,
channels = 4,
fmt = ((channels-1)<<8) + DataDepth::fmt,
type = CV_MAKETYPE(depth, channels)
};
};
上述例子中,value_type
在编译时用来表示cv::DataType<>
的名称(即Rect
)。work_type
表示cv::DataType<>
是由什么类型的数据组成的,如cv::DataType
的work_type
为int
。channel_type
为int
类型,表示多通道对象。vec_type
用来描述如何将它表示成cv::Vec<>
类型对象。如cv::DataType
会解析成cv::Vec
。
对于运行时常量:generic_type
仍然是0,depth
是CV_32S
,channel
是4(因为实际上有4个数,所以这里的channel
为4,上述的cv::Vec<>
的大小也为4),fmt
解析成0x3069(因为i是0x69),type
则解析成CV_32SC4
。
在OpenCV中,支持cv::Scalar
,cv::Vec
,cv::Matx
以及STL中的std::vector<>
这些小的数组了欸行,另外还支持cv::Mat
和cvSparseMat这类大型数组类型。OpenCV定义了cv::InputArray
,cv::OutputArray
,cv::InputOutputArray
支持上述数组类型以就简化接口。cv::InputArray
是一个const的只读常量,cv::OutputArray
是一个可变量引用。当cv::InputArray
或cv::OutputArray
可传递cv::noArray()
以表示其输入或输出为空的数组。
工具函数包含数学操作、测试、错误生成、内存与线程管理、优化及其他工具函数。
工具和函数和系统函数:
函数名称 | 描述 |
---|---|
cv::alignPtr() | 对齐指针到给定的字节数 |
cv::alignSize() | 将缓冲区大小对齐到给定的字节数 |
cv::allocate() | 分配一个c风格的对象数组 |
cvCeil()a | 将浮点数x转化为不小于x的最接近的整数 |
cv::cubeRoot() | 计算一个数字的立方根 |
cv::CV_Assert() | 如果给定条件不为真,抛出异常 |
CV_Error() | 宏构建cv::Exception(从一个固定字符串)并抛出它 |
CV_Error_() | 宏构建cv::Exception(从格式化字符串)并抛出它 |
cv::deallocate() | 释放一个c风格的对象数组 |
cv::error() | 指示错误并抛出异常 |
cv::fastAtan2() | 计算二维向量的方向,以度为单位 |
cv::fastFree() | 释放内存缓冲区 |
cv::fastMalloc() | 分配对齐的内存缓冲区 |
cvFloor() | 将浮点数x转化为不大于x的最接近的整数 |
cv::format() | 使用类似于sprintf的格式创建STL字符串 |
cv::getCPUTickCount() | 从内部CPU定时器获得滴答计数 |
cv::getNumThreads() | 统计OpenCV当前使用的线程数 |
cv::getOptimalDFTSize() | 计算传递给cv::DFT()的数组的最佳大小 |
cv::getThreadNum() | 获取当前线程的索引 |
cv::getTickCount() | 从系统中获取滴答数 |
cv::getTickFrequency() | 获取每秒的节拍数(参见cv::getTickCount()) |
cvIsInf() | 检查浮点数x是否为无穷大 |
cvIsNaN() | 检查浮点数x是否是“非数字” |
cvRound() | 将浮点数x取最接近的整数 |
cv::setNumThreads() | 设置OpenCV使用的线程数 |
cv::setUseOptimized() | 启用或禁用优化代码的使用(SSE2等) |
cv::useOptimized() | 获取优化代码启用状态(参见cv:: setuseoptimization ()) |
template T* cv::alignPtr( // 返回T*类型的对齐指针
T* ptr, // 未对齐指针
int n = sizeof(T) // 对齐到块大小,为2的幂
);
给定任意类型的指针,该函数按照以下计算方法计算同一类型的对齐指针:
(T*)(((size_t)ptr + n + 1) & -n)
size_t cv::alignSize( // 返回大于等于sz且能被n整除的大小
size_t sz, // 缓冲区大小
int n = sizeof(T) // 对齐到块大小,为2的幂
);
给定一个数字n(通常是sizeof()的返回值)和一个缓冲区的大小sz, cv::alignSize()
计算该缓冲区的大小,以便包含数量大小为n的对象,即大于或等于sz但能被n整除的最小数字。使用以下公式:
(sz + n-1) & -n
template T* cv::allocate( // 返回指向已分配缓冲区的指针
size_t sz // 缓冲区大小,是sizeof(T)的倍数
);
函数cv::allocate()
的作用类似于new
的数组形式,它分配了一个c风格的数组,包含n个T类型对象,为每个对象调用默认构造函数,并返回指向数组中第一个对象的指针。
template void cv::deallocate(
T* ptr, // 需要释放的缓冲区的指针
size_t sz // 缓冲区的大小,是sizeof(T)的倍数
);
函数cv::deallocate()
的作用类似于数组形式的delete
,用于释放C风格的数组,包含n个T类型对象,并为每个对象调用析构函数。cv::deallocate()
用于释放用cv::allocate()
分配的对象。传递给cv::deallocate()
的元素数量n
必须与cv::allocate()
最初分配的对象数量相同。
float cv::fastAtan2( // 返回二维向量角度
float y, // y
float x // x
);
这个函数计算(x,y)
的arctan
值,并返回从原点到指定点的角度。计算结果的范围为[0.0, 360.0),包括0.0但不包括360.0。
int cvCeil( // 返回大于或等于x的最小整数,int >= x
float x // 输入32位浮点数
);
给定一个浮点数x
, cvCeil()
计算不小于x的最小整数。如果输入值在32位整数可表示的范围之外,则结果为未定义的浮点型变量。
float cv::cubeRoot( // 返回32位浮点数
float x // 输入32位浮点数
);
该函数计算参数x的立方根。若x为负数,则返回结果也为负数。
// example
CV_Assert( x!=0 )
CV_Assert()
是一个宏,在运行时计算传递进来的表达式结果,如果该表达式的计算结果为False(或0),则抛出异常。CV_Assert()
宏在任何运行模式下都生效。对于CV_DbgAssert()
,则只在调试模式下运行才会生效。
void cv::error(
const cv::Exception& ex // 抛出的异常
);
这个函数主要是从CV_Error()
和CV_Error_()
调用的。如果代码是在非调试版本中编译的,则在运行时抛出ex
异常。如果代码是在调试版本中编译的,则在运行时引发内存访问违规,使得调试器可以正常访问执行堆栈和所有的参数。
一般情况下我们不直接调用cv::error()
,而是依赖于宏CV_Error()
和CV_Error_()
来为抛出错误异常。这些宏获取我们需要显示的异常信息,并将产生的异常传递给cv::error()
。
void cv::fastFree(
void* ptr // 需要释放的缓冲区的指针
);
该函数用于回收cv::fastMalloc()
分配的缓冲区。
void* cv::fastMalloc( // 返回分配的缓冲区指针
size_t size // 要分配的缓冲区大小
);
cv::fastMalloc()
相较于malloc()
速度更快而且会进行缓冲区内存大小对其,如传递的缓冲区大小超过16字节,则返回的缓冲区将以16字节的边界对齐。
int cvFloor( // 返回不大于x的最大整数 int <= x
float x // 输入332位浮点数
};
给定一个浮点数x
, cv::Floor()
计算不大于x
的最大整数。如果输入值超出32位整数所代表的范围,则结果未定义。
string cv::format( // 返回STL字符串 STL-string
const char* fmt, // 格式化字符串, 如sprintf()
... // vargs, 如sprintf()
);
该函数本质上与标准库中的sprintf()相同,但它不要求调用者提供字符缓冲区,而是构造一个STL字符串对象并返回该对象。该函数对Exception()构造函数(参数中包含STL字符串)格式化错误消息特别方便。
int64 cv::getCPUTickCount( void ); // CPU滴答计数
在多核系统中,一个线程可以在一个核上休眠,而在另一个核上唤醒,因此随后两次调用cv::getCPUTickCount()的结果之间的差异可能会造成误差,或者完全没有意义。另外该函数适合初始化随机数生成器。
int cv::getNumThreads( void ); // 当前OpenCV使用的线程总数
返回当前OpenCV正在使用的线程数。
int cv::getOptimalDFTSize( int n ); // 用于离散傅里叶变换的最佳数组大小, >= n
函数cv::getOptimalDFTSize()
将已经传递给cv::dft()
的数组的大小作为参数,并返回应该传递给cv::dft()
的数组的大小。OpenCV使用这个信息来创建一个更大的数组,可将数据复制到其中,然后用0来填充剩下的数据。
int cv::getThreadNum( void ); // 当前线程的序号
若OpenCV库是用OpenMP支持编译的,该函数将返回当前执行的线程的索引(从零开始)。
int64 cv::getTickCount( void ); // 返回当前CPU滴答计数
该函数返回相对于某些体系结构相关时间的滴答计数。而节拍率也依赖于体系结构和操作系统,每一滴答计数的时间可以通过cv::getTickFrequency()
计算。对于大多数计时应用程序来说,这个函数比cv::getCPUTickCount()
更好,因为它不受底层问题的影响,比如线程运行在哪个核心上,以及CPU频率的自动调节(大多数现代处理器出于电源管理的原因会这对CPU的频率进行调节)。
double cv::getTickFrequency( void ); // 滴答计数频率,即每秒钟的滴答计数频次
当cv::getTickCount()
用于计时分析时,滴答计数通常与CPU架构或系统体系结构有关。函数cv::getTickFrequency()
计算每秒钟的滴答频次。
int cvIsInf( double x ); // 如果x为无穷大则返回1
如果x
为正负无穷,则cvIsInf()
的返回值为1,否则为0。无穷大测试由IEEE754
标准提供。
int cvIsNan( double x ); // 如果x不是一个数就返回1
如果x
不是一个数则返回1,否则返回0。NaN
“非数”测试由IEEE754
标准提供。
int cvRound( double x ); // 返回一个最接近x的整数
给定一个浮点数x
, cvRound()
计算并返回最接近x
的整数。如果输入值在32位整数可表示的范围之外,则结果为未定义。在OpenCV 3.0中有重载cvRound(float x)
(如cvFloor
和cvCeil
),其在ARM上更快。
void cv::setNumThreads( int nthreads ); // 设置OpenCV可以使用的线程数
当OpenCV使用OpenMP支持编译时,该函数设置OpenCV将在并行OpenMP块中使用的线程数。线程数的默认值是CPU上逻辑核的数量(假设有四个核,每个核带有两个超线程,那么默认情况下将有八个线程),如果nthreads设置为0,线程数将返回默认值。
void cv::setUseOptimized( bool on_off ); // 如果为false, 关闭优化操作
早期版本的OpenCV依赖于外部库(如IPP, Intel性能原语库)用于访问高性能的优化,如SSE2指令,后来的版本越来越多地将优化代码包含在OpenCV本身中。默认情况下,这些优化操作的使用是启用的,除非在编译库时禁用了优化。我们可以随时通过cv::setUseOptimized()
打开或关闭这些优化的使用。
当其它例程(在任何线程上)正在运行时不能调用cv::setUseOptimized()
。
bool cv::useOptimized( void ); // 优化开启时返回true
当优化启用时,返回True否则返回False。
类似于STL、Boost以及其它库,OpenCV 2.1及更新版本建立在模板元编程风格上。这种类型的库设计在最终代码的质量和速度,以及灵活性都比较好。
OpenCV中常用模板参数如下表所示:
功能 | 描述 |
---|---|
cv::Point_ | 由一对T类型对象组成的点。 |
cv::Rect_ | 位置、宽度和高度,全部为T类型。 |
cv::Vec |
H个类型为T的对象的数组。 |
cv::Matx |
H*W个类型为T的对象的数组。 |
cv::Scalar_ | 四个类型为T的对象组成的集合(与cv::Vec |
cv::Mat
是整个OpenCV库c++实现的中心。OpenCV库中的绝大多数函数都是cv::Mat
类的成员,接受cv::Mat
作为参数,或者以cv::Mat
作为返回值。
cv::Mat
类用于表示任意维数的密集数组。密集意味着数组中都有一一对应的数据值,即使其数值为零,大多数图像都以密集数组的形式存储。另一种是稀疏数组cv::SparseMat
,在稀疏数组中,通常只存储非零数值,当许多数值为零时采用稀疏数组进行存储,可以极大地节省存储空间。常见使用稀疏数组的情况为直方图,对于许多直方图,大多数数值都为0。
cv::Mat
类可以作为任意维数的数组。其数据可以看做是以按照栅格扫描顺序存储的n维数组。在一维数组中,元素是顺序的。这意味着在一维数组巾,元素是按顺序排列的;而在 个二维数组中,数据按行组织,每一行也按顺序排列,对于三维数组来说,所有的通道都被行填充,每一个通道同样按顺序排列。
所有的矩阵都含有一个表示它所包含数组类型的flag
,一个表示其维度的dims
,分别表示行和列的数目的rows
和cols
(在dims
大于2的时候无效) ,一个指示数据存储位置的指针data
,以及一个表示该内存区域有多少个引用的refcount
,类似于cv: :Ptr<>
的引用计数器。cv: :Mat
像智能指针一样管理内存区域。数据实体data
的结构被step[]
所描述。数据数组按如下公式排列数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEnDlot9-1657066618361)(/image-20220622074616574.png)]
对于一个二维数组,其公式可以简化如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWiQdSqD-1657066618363)(/image-20220622074809184.png)]
cv: :Mat
中的元素可以是一个简单的数字,也可以是多个数字。在包含多个数字的时候,它就被称为"多通道数组"。从定义上来说,一个数组的元素是许多带有数值的向量的 部分。比如说,一个数组也许会被声明为二维三通道32位浮点型数组,在这个情况下,数组的元素就是大小为12字节的32位浮点数据。当在内存中储存的时候,数组的行可能不是绝对按顺序存储的,由于数据对齐(数据对齐是为了内存的存取效率)的原因,每一行之间可能存在一个很小的间隔。
可以通过实例化变量cv: :Mat
来创建数组,通过这种方式创建的数组没有大小和数据类型。但可以再次使用成员函数create()
来申请一个内存区域。一个create()
的重载是通过指定行数和列数以及数据类型来配置二维数组的规模。数组的类型(type
) 决定了它含有什么样的元素。 一个有效的数据类型需要同时指明数据的类型和通道数。所有这些数据类型都在库的头文件中声明,包括CV_{8U 16S 16U 32S 32F 64F}C{1 3}
的多种组合。比如,CV_32F_C3
表示3通道的32位浮点数据。OpenCV 允许定义超过3通道的数据类型,需妥调用函数CV{8U,16S,32S,32F,64F}C()这些函数只有一个参数,表明通道数,如CV_8UC(3) 等效于CV_8UC3,由于没有宏CV 8UC7,必须使用函数飞CV_8UC(7)。
数组创建示例:
cv::Mat m;
// Create data area for 3 rows and 10 columns of 3-channel 32-bit floats
m.create( 3, 10, CV_32FC3 );
// Set the values in the 1st channel to 1.0, the 2nd to 0.0, and the 3rd to 1.0
m.setTo( cv::Scalar( 1.0f, 0.0f, 1.0f ) );
等效于:
cv::Mat m(3, 10, CV_32FC3, cv::Scalar(1.0f, 0.0f, 1.0f ));
以下为所有cv::Mat
构造函数的列表,一般常用的只是其中几个:
cv::Mat
构造函数(非复制构造函数)
构造函数 | 描述 |
---|---|
cv::Mat; | 默认构造函数 |
cv::Mat( int rows, int cols, int type ); | 指定类型的二维数组 |
cv::Mat( int rows, int cols, int type, const Scalar& s); | 指定类型的二维数组,并指定初始化值 |
cv::Mat( int rows, int cols, int type, void* data, size_t step=AUTO_STEP); | 指定类型的二维数组,并指定预先存储的数据指针 |
cv::Mat( cv::Size sz, int type ); | 指定类型的二维数组(大小由sz指定) |
cv::Mat( cv::Size sz, int type, const Scalar& s ); | 指定类型的二维数组,并指定初始化值(数组大小由sz指定) |
cv::Mat( cv::Size sz, int type, void* data, size_t step=AUTO_STEP); | 指定类型的二维数组,并指定预先存储的数据(数组大小由sz指定) |
cv::Mat( int dims, const int* sizes, int type); | 按类型划分的多维数组 |
cv::Mat( int ndims, const int* sizes, int type, const Scalar& s); | 指定初始值的多维数组 |
cv::Mat( int ndims, const int* sizes,int type, void* data, size_t step=AUTO_STEP ); | 使用预先存在的数据按类型划分的多维数组 |
cv::Mat
复制构造函数
构造函数 | 描述 |
---|---|
cv::Mat( const Mat& mat ); | 复制构造函数 |
cv::Mat( const Mat& mat, const cv::Range& rows, const cv::Range& cols ); | 仅从输入的mat中复制指定行列数据的复制构造函数 |
cv::Mat( const Mat& mat, const cv::Rect& roi ); | 仅从输入的mat感兴趣区域中复制数据的复制构造函数 |
cv::Mat( const Mat& mat, const cv::Range* ranges ); | 仅从输入的mat中复制指定范围的数据的复制构造函数 |
cv::Mat( const cv::MatExpr& expr ); | 从其他矩阵的线性代数表述式中生成新矩阵的复制构造函数 |
针对OpenCV 2.1以往版本的数据类型的cv::Mat构造函数
构造函数 | 描述 |
---|---|
cv::Mat( const CvMat* old, bool copyData=false ); | 从旧的CvMat结构中构造一个新的对象并选择是否深拷贝数据 |
cv::Mat( const IplImage* old, bool copyData=false ); | 从旧的IplImage结构中构造一个新的对象并选择是否拷贝数据 |
cv::Mat模板构造函数
构造函数 | 描述 |
---|---|
cv::Mat( const cv::Vec |
构造一个同v::Vec |
cv::Mat( const cv::Matx |
构造一个同cv::Matx |
cv::Mat( const std::vector& vec, bool copyData=true ); | 构造一个同STL std::vector所指定的数据类型为T、大小为vec元素个数的一维数组 |
构造cv::Mat的静态函数
函数 | 描述 |
---|---|
cv::Mat::zeros( rows, cols, type ); | 创建一个大小为rows × cols并指定数据类型(如CV_32F等)的全零矩阵 |
cv::Mat::ones( rows, cols, type ); | 创建一个大小为rows × cols并指定数据类型(如CV_32F等)的全一矩阵 |
cv::Mat::eye( rows, cols, type ); | 创建一个大小为rows × cols并指定数据类型(如CV_32F等)的单位矩阵 |
使用cv::Mat::ones()
或cv::Mat::eye()
创建一个多维数组时,只有第一通道会设置为1,其余通道保持为0。
访问一个数组元素的方法主要有两种,分别为通过位置访问以及迭代器访问。以下为使用行列位置访问对应元素数据的两个例子。
访问单通道数组元素:
cv::Mat m = cv::Mat::eye( 10, 10, 32FC1 );
printf(
"Element (3,3) is %f\n",
m.at(3,3)
);
访问多通道数组:
cv::Mat m = cv::Mat::eye( 10, 10, 32FC2 );
printf(
"Element (3,3) is (%f,%f)\n",
m.at(3,3)[0],
m.at(3,3)[1]
);
当使用at<>()
的模板函数访问多维数组时,优先使用cv::Vec
对象。
创建并访问由更复杂类型(如复数)组成的数组:
cv::Mat m = cv::Mat::eye( 10, 10, cv::DataType::type );
printf(
"Element (3,3) is %f + i%f\n",
m.at(3,3).re,
m.at(3,3).im,
);
at<>()访问器函数的变体
示例 | 描述 |
---|---|
M.at( i ); | 整形数组M中的元素i |
M.at( i, j ); | 浮点型数组M中的元素(i, j) |
M.at( pt ); | 整形矩阵M中处于(pt.x, pt.y)的元素 |
M.at( i, j, k ); | 三维浮点型矩阵M中处于(I, J, K)位置的元素 |
M.at( idx ); | 无符号字符数组M中位于idx[]所索引的n维位置的元素 |
ptr<>()访问元素
示例 | 描述 |
---|---|
mtx.ptr(3) | 返回mtx的指向第三行第一个元素第一个(浮点)通道的指针 |
mtx.ptr |
返回mtx的指向第三行第一个元素第一个(浮点)通道的指针 |
使用ptr<>()
指针访问数组是最快的一种方式,且可以通过指针的方式向指定位置写入数据。C风格的数据指针data也是一种指针访问方式。成员函数isContinuous()
可以判断数组是否连续的,如果数组是连续的,则可以通过获取第一行第一个元素的指针,然后再遍历整个数组。
OpenCVcv::Mat
提供另一种序列存储方式的迭代器机制。包括一个用于只读(const)数组的cv::MatConstIterator<>
和一个用于非只读(non-const)数组的cv::MatIterator<>
。cv::Mat
的成员函数begin()
和end()
可以返回这种迭代器对象,使用迭代器可以自动处理连续的内存区域和非连续的内存区域,在任一种维度的数组中都可有效使用。
使用迭代器计算三通道数组中“最长”元素(一个三维向量域)示例:
int sz[3] = { 4, 4, 4 };
cv::Mat m( 3, sz, CV_32FC3 ); // 一个4×4×4的三维数组
cv::randu( m, -1.0f, 1.0f ); // 填充从-1.0到1.0的随机数字
float max = 0.0f; // L2范数的最小可能值
cv::MatConstIterator it = m.begin();
while( it != m.end() )
{
len2 = (*it)[0]*(*it)[0]+(*it)[1]*(*it)[1]+(*it)[2]*(*it)[2];
if( len2 > max ) max = len2;
it++;
}
cv::NaryMatIterator
要求被迭代的数组有相同的几何结构(维度以及每一个维度的范围)。该迭代器不会返回一个用于迭代的单独元素,而通过返回一堆数组进行N-ary迭代器操作,这些返回的数组也称为“面”(plane)。一个面表示输入数组有连续内存的部分(一般来说是一维或二维的片段)。
一个多维数组的求和,计算m0和m1的和,并将结果放在m2中。
const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
cv::Mat n_mat( 3, n_mat_sz, CV_32FC1 );
cv::RNG rng;
rng.fill( n_mat, cv::RNG::UNIFORM, 0.f, 1.f );
const cv::Mat* arrays[] = { &n_mat, 0 };
cv::Mat my_planes[1];
cv::NAryMatIterator it( arrays, my_planes );
// On each iteration, it.planes[i] will be the current plane of the
// i-th array from 'arrays'.
float s = 0.f; // Total sum over all planes
int n = 0; // Total number of planes
for (int p = 0; p < it.nplanes; p++, ++it) {
s += cv::sum(it.planes[0])[0];
n++;
}
使用N-ary对两个数组求和
const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
cv::Mat n_mat0( 3, n_mat_sz, CV_32FC1 );
cv::Mat n_mat1( 3, n_mat_sz, CV_32FC1 );
cv::RNG rng;
rng.fill( n_mat0, cv::RNG::UNIFORM, 0.f, 1.f );
rng.fill( n_mat1, cv::RNG::UNIFORM, 0.f, 1.f );
const cv::Mat* arrays[] = { &n_mat0, &n_mat1, 0 };
cv::Mat my_planes[2];
cv::NAryMatIterator it( arrays, my_planes );
float s = 0.f; // Total sum over all planes in both arrays
int n = 0; // Total number of planes
for(int p = 0; p < it.nplanes; p++, ++it) {
s += cv::sum(it.planes[0])[0];
s += cv::sum(it.planes[1])[0];
n++;
}
使用N-ary对两个数组求方根
/// compute dst[*] = pow(src1[*], src2[*]) //
const Mat* arrays[] = { src1, src2, dst, 0 };
float* ptrs[3];
NAryMatIterator it(arrays, (uchar**)ptrs);//先*(ptrs)从数组中取出float*,再通过uchar*(float*)将float型指针转换为uchar型指针
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
for( size_t j = 0; j < it.size; j++ )
{
ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
}
}
通过块访问数组元素
示例 | 描述 |
---|---|
m.row( i ); | m中第i行数组 |
m.col( j ); | m中第j列数组 |
m.rowRange( i0, i1 ); | m中第i0行到第i1-1行所构成的数组 |
m.rowRange( cv::Range( i0, i1 ) ); | m中第i0行到第i1-1行所构成的数组 |
m.colRange( j0, j1 ); | m中第j0列到第j1-1列所构成的数组 |
m.colRange( cv::Range( j0, j1 ) ); | m中第j0列到第j1-1列所构成的数组 |
m.diag( d ); | m中偏移为d的对角线所组成的数组 |
m( cv::Range(i0,i1), cv::Range(j0,j1) ); | m中从点(i0, j0)到点(i1-1, j1-1)所包含数据构成的数组 |
m( cv::Rect(i0,i1,w,h) ); | m中从点(i0, j0)到点(i0+w-1, j0+h-1)所包含数据构成的数组 |
m( ranges ); | m中依据ranges[0]到ranges[ndim-1]所索引区域构成的数组 |
以上通过块访问数组元素的方式不会将对应块的数据重新复制,而是创建了一个新的数组头,并且分配data指针、step数组以及其他一些东西。如表达式m2=m.row(3)
,通过m2可以访问m中的第三行数据,如果修改了m2中得到的数据,也会修改到位于m中对应位置的数据。
m.diag(int shift)
返回的数组指向矩阵m的对角元素。当shift为0时返回主对角线元素,shift为正数时,相对于主对角线向数组上半部分偏移。shift为负数时,相对于主对角线向下半部分偏移。
数组于数组之间的赋值不会新申请内存,而矩阵之间的运算或通过矩阵表达式获得的结果需要新申请内存进行保存。如m1=m2
仅是引用数据,m=m1+m2
则会新申请内存存储m中的数据。
矩阵表达式可用的运算操作
示例 | 描述 |
---|---|
m0 + m1, m0 – m1; | 矩阵的加法和减法 |
m0 + s; m0 – s; s + m0, s – m1; | 矩阵和单个元素的加和减 |
-m0; | 矩阵取反 |
s * m0; m0 * s; | 按单元素缩放矩阵 |
m0.mul( m1 ); m0/m1; | 按元素将m0和m1相乘;按元素将m0和m1相除 |
m0 * m1; | m0和m1进行矩阵乘法 |
m0.inv( method ); | 对m0矩阵求逆(默认使用DECONP_LU) |
m0.t(); | 对m0举证求转置 |
m0>m1; m0>=m1; m0==m1; m0<=m1; m0按元素进行比较,返回元素为0或255的uchar类型矩阵 |
|
m0&m1; m0|m1; m0^m1; ~m0; m0&s; s&m0; m0|s; s|m0; m0^s; s^m0; |
矩阵和矩阵之间或者矩阵和单个元素之间按位进行逻辑操作 |
min(m0,m1); max(m0,m1); min(m0,s); min(s,m0); max(m0,s); max(s,m0); |
矩阵和矩阵之间或矩阵和单个元素之间按元素取最大或者最小值 |
cv::abs( m0 ); | 对m0按元素取绝对值 |
m0.cross( m1 ); m0.dot( m1 ); | 向量叉乘和点乘操作(叉乘只适用于3×1矩阵) |
cv::Mat::eye( Nr, Nc, type ); cv::Mat::zeros( Nr, Nc, type ); cv::Mat::ones( Nr, Nc, type ); |
用于返回规定类型的N×N矩阵的静态方法 |
**饱和转换(saturation casting)**用于处理无符号等类型数据溢出问题,可以自动检查是否存在溢出的情况并将结果值转换为相对最小或最大的有效值。这一操作的模板函数是cv::saturate_cast<>()
数值准换示例
uchar& Vxy = m0.at( y, x );
Vxy = cv::saturate_cast((Vxy-128)*2 + 128));
cv::Mat类的更多成员函数
示例 | 描述 |
---|---|
m1 = m0.clone(); | 将m0中数据都克隆到m1中 |
m0.copyTo( m1 ); | 将m0中的数据复制到m1中(等同m1 = m0.clone()) |
m0.copyTo( m1, mask ); | 和m0.copyTo( m1 )一样,但只复制mask所指示的区域 |
m0.convertTo( m1, type, scale, offset ); | 转换m0中元素的类型(比如CV_32F)并且在尺度变换(默认为1)和增加偏置(默认为0)之后赋值给m1 |
m0.assignTo( m1, type ); | 只在内部使用(集成在convertTo中) |
m0.setTo( s, mask ); | 将m0中所有元素设置为s,如果存在mask,则只对mask指示区域进行操作 |
m0.reshape( chan, rows ); | 改变二维数组的有效形状,chan和rows变量为0时表示不做更改 |
m0.push_back( s ); | 扩展一个m×1矩阵,并在最后插入元素s |
m0.push_back( m1 ); | m×n的矩阵m0扩展k行,并将m1中的数据复制到这些心扩展的行中,m1大小必须为k×n |
m0.pop_back( n ); | 从m×n大小的矩阵移除n行(n默认是1) |
m0.locateROI( size, offset ); | 将m0的整个大小写入cv::size;如果m0是一个更大的矩阵的一部分,将起始角的位置写入cv::Point& offset |
m0.adjustROI( t, b, l, r ); | 调整视图的大小,分别扩展顶部t个像素,底部b个像素,左边l个像素,右边r个像素 |
m0.total(); | 计算数组素的数目(不包括通道) |
m0.isContinuous(); | 以字节为单位返回m0中元素的大小(例如,一个三通道的浮点矩阵将返回12字节) |
m0.elemSize() | 返回m0的字节长度(比如三通道浮点矩阵将返回12) |
m0.elemSize1(); | 以字节为单位返回m0子元素的大小(例如,一个三通道的浮点矩阵将返回4字节) |
m0.type(); | 返回m0元素的类型(比如CV_32FC3) |
m0.depth(); | 返回m0各个通道的类型(如CV_32F) |
m0.channels(); | 返回m0元素中的通道数 |
m0.size(); | 返回m0的大小,返回cv::Size()对象 |
m0.empty(); | 当数组中没有元素时返回true(例如,m0.total == 0或m0.data == NULL) |
当数组中非零数目的数量较多时使用cv::SparseMat
。稀疏数组只存储有数据的部分,因此可以节约大量内存。稀疏数组的缺点在于计算速度较慢(基于每个元素进行计算),但如果已知哪些操作不需要进行则可以节约计算时间。cv::SparseMat
使用哈希表存储非零元素或计算之后为0的元素。
稀疏数组和稠密数组之间最重要的区别在于如何访问元素。稀疏数组提供四种访问机制:cv::SparseMat::ptr()
, cv::SparseMat::ref()
, cv::SparseMat::value()
, 以及cv::Sparse Mat::find()
。
cv::SparseMat::ptr()
简单模板:
uchar* cv::SparseMat::ptr( int i0, bool createMissing, size_t* hashval=0 );
上述模板用于访问一维数组。第一个参数i0
是所请求元素的索引,参数createMissing
指示如果该元素还没有在数组中是否创建该元素。当调用cv::SparseMat::ptr()
时,如果该元素已经在数组中定义,它将返回一个指向该元素的指针,如果该元素没有定义,则返回NULL。如果createMissing
参数为真,该元素将被创建,并返回一个有效的指向该元素的非null
指针。cv::SparseMat
的底层数据表示是一个哈希表,在哈希表中查找对象需要两个步骤:首先,计算哈希键(在本例中通过索引计算)。然后搜索与该键关联的列表,这个列表一般会很短(理想情况下只有一个元素),所以查找对象的主要计算成本是哈希键的计算。如果哈希键已经计算过了(如cv::SparseMat::hash()
),那么不再进行计算以节省时间。如果参数hashval为默认参数NULL
,将计算哈希键,否则当作提供的哈希键进行使用。
函数cv::SparseMat::ptr()
返回一个指向无符号字符(即uchar*)的指针,通常需要将其转换为数组对应数据类型的指针。
访问器模板函数SparseMat::ref<>()
用于返回数组中特定元素的引用。其与SparseMat::ptr()
一样,可以接受一个、两个或三个索引,或者一个指向索引数组的指针,支持一个可选的指向在查找中使用的哈希值的指针。SparseMat::ref<>()
是一个模板函数,需要指定被引用对象的类型。例如,数组是CV_32F
类型,则可如下调用SparseMat::ref<>()
:
a_sparse_mat.ref<float>( i0, i1 ) += 1.0f;
除了与稠密矩阵有许多相同的操作外,稀疏矩阵还有一些独有的操作。如下表:
示例 | 描述 |
---|---|
cv::SparseMat sm; | 创建一个不进行初始化的稀疏矩阵 |
cv::SparseMat sm( 3, sz, CV_32F ); | 创建一个类型为float的三维稀疏矩阵,其维数由sz给出 |
cv::SparseMat sm( sm0 ); | 从现有稀疏矩阵sm0拷贝副本以创建一个新的稀疏矩阵 |
cv::SparseMat( m0, try1d ); | 从现有的密集矩阵m0创建一个稀疏矩阵,如果bool try1d为真,且稠密矩阵为n × 1或1 × n,则将m0转换为一维稀疏矩阵 |
cv::SparseMat( &old_sparse_mat ); | 从一个2.1版本之前的C风格稀疏矩阵CvSparseMat创建一个心的稀疏矩阵 |
CvSparseMat* old_sm = (cv::SparseMat*) sm; | 转换操作将创建一个2.1版本之前的C风格稀疏矩阵CvSparseMat对象并且所有数据都会被复制到新对象中,最后返回对象的指针 |
size_t n = sm.nzcount(); | 返回sm中非0元素数量 |
size_t h = sm.hash( i0 ); size_t h = sm.hash( i0, i1 ); size_t h = sm.hash( i0, i1, i2 ); size_t h = sm.hash( idx ); |
返回一维稀疏矩阵中索引i0所指向数据的哈希值; 返回二维稀疏矩阵中索引i0,i1所指向数据的哈希值; 返回三维稀疏矩阵中索引i0,i1,i2所指向数据的哈希值; 返回多维稀疏矩阵中索引idx数组所指向数据的哈希值 |
sm.ref( i0 ) = f0; sm.ref( i0, i1 ) = f0; sm.ref( i0, i1, i2 ) = f0; sm.ref( idx ) = f0; |
设置一维稀疏矩阵中索引i0所指向元素的值为f0; 设置二维稀疏矩阵中索引i0,i1所指向元素的值为f0; 设置三维稀疏矩阵中索引i0,i1,i2所指向元素的值为f0; 设置多维稀疏矩阵中索引为idx数组所指向元素的值为f0 |
f0 = sm.value( i0 ); f0 = sm.value( i0, i1 ); f0 = sm.value( i0, i1, i2 ); f0 = sm.value( idx ); |
设置一维稀疏矩阵中索引i0所指向元素的值为f0; 设置二维稀疏矩阵中索引i0,i1所指向元素的值为f0; 设置三维稀疏矩阵中索引i0,i1,i2所指向元素的值为f0; 设置多维稀疏矩阵中索引为idx数组所指向元素的值为f0 |
p0 = sm.find( i0 ); p0 = sm.find( i0, i1 ); p0 = sm.find( i0, i1, i2 ); p0 = sm.find( idx ); |
将一维稀疏矩阵中索引i0指向的元素赋值给p0; 将二维稀疏矩阵中索引i0,i1所指向的元素赋值给p0; 将三维稀疏矩阵中索引i0,i1,i2 所指向的元素赋值给p0; 多维稀疏矩阵中索引为数组idx所指向的元素赋值给p0 |
sm.erase( i0, i1, &hashval ); sm.erase( i0, i1, i2, &hashval ); sm.erase( idx, &hashval ); |
移除工维稀疏矩阵中索引为 (i0,i1) 的元素; 移除三维稀疏矩阵中紫引为 (i0, i1, i2) 的元素; 移除多维稀疏矩阵中索引为数组idx 的元素 |
cv::SparseMatIterator it = sm.begin(); | 创建一个浮点型稀疏矩阵迭代器it 并指向sm第一个元素 |
cv::SparseMatIterator it_end = sm.end(); | 创建一个无符号字符型稀疏矩阵迭代器itjnd并将其初始化指向数组sm 的最后一个元素的后一个元素 |
使用模板cv::Mat_<>以及cv::SparseMat_<>的目的是不必在使用其成员函数的时候调用其模板形式:
如:
cv::Mat m( 10, 10, CV_32FC2 );
m.at< Vec2f >( i0, i1 ) = cv::Vec2f( x, y );
cv::Mat_ m( 10, 10 );
m.at( i0, i1 ) = cv::Vec2f( x, y );
// 或者...
m( i0, i1 ) = cv::Vec2f( x, y );