OpenCV 矩阵运算小结

这份总结适用于OpenCV 2 以上版本,官方详尽的文档在 这里

概述

OpenCV 里的矩阵分静态和动态两种:

  • 动态就是 Mat 类,已经实现了引用计数,编程时几乎不用考虑内存问题;你可以将Mat看作一个——包含矩阵内存区域的指针和矩阵的大小、类型等信息。
  • 静态以 Matx 类为代表,需要程序员自己开辟内存区域,其存储方式和数组完全一样,没有额外的存储空间,矩阵自身的信息都以模板的方式静态编译了。

对于能确定大小和类型的矩阵,尽量使用静态的存储方式。

Matx \ Vec

创建时需要给定模板参数(类型、行数、列数),例如:

1
Matx<float, 3, 3> matrix;

Vec 向量就是列数为 1 的 Matxs

1
template<typename _Tp, int n> class Vec : public Matx<_Tp, n, 1> {...};

可以从 Mat 初始化,也可以用 ones(), zeros(), eye() 等等

1
2
Matx<float, 10, 1> ui = mat.col(0);
Matx<float, 10, 1> wi = Matx<float, 10, 1>::ones();

可以执行一些简单的运算,例如加减乘除和 mul(), dot()

1
2
3
cv::Matx31f r1, r2, r3;
r2 -= r1.dot(r2) * r1;
r2 *= (1.f / norm(r2));

稍复杂的就要转换成 Mat 来操作了,这里给个简单的例子

1
r3 = Mat(r1, false).cross(r2);

Mat

其实 Mat 才是矩阵运算的主角,各种矩阵运算都与这个类相关。但是 Mat 是动态分配的,涉及到内存分配释放的代价,请自己权衡。

前面提到,Mat 是一个指针加上数据类型、步长等信息,为了减少不必要的拷贝,OpenCV 中经常会出现两个 Mat 指向同一片内存区域(比如 ROI 就用到了这个特性)。这时候,对他们的修改会相互影响。

Mat 也可以从给定的指针初始化,这种情况下,Mat 不去管理内存。

常用的构造函数

1
2
3
4
Mat(int rows, int cols, int type)
Mat(int ndims, const int* sizes, int type)
// 上面是动态分配内存,下面是用已有的内存
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)

例如

1
cv::Mat(LANDMARK_NUM, 2, CV_32F, (void *)landmarks.data());

静态构造方法,以 zeors() 为例

1
2
Mat::zeros(int rows, int cols, int type)
Mat::zeros(int ndims, const int* sz, int type)

此外还有 Mat::ones(), Mat::eye()

MatExpr

这是 Mat 运算中经常出现的一个类,可以理解成还未计算的矩阵表达式。它和 Mat 可以隐式地相互转换。这一点很方便,但要小心

1
2
3
cv::Mat A, B, C;
A = B; // 调用 `operator=(Mat)`,A 指向 B 的内存区域,A 的内存区域引用数减一
A = B + C; // 调用 `operator=(MatExpr)`,A 的数据被写成 B + C 的结果

如果要实现矩阵拷贝,应该用

1
B.copyTo(A);

其他

用以下函数取出一个 ROI,返回值类型仍是 Mat,共享对应的内存区域。

1
2
3
4
Mat::row(int x)
Mat::col(int x)
Mat::rowRange(int startrow, int endrow)
Mat::colRange(int startcol, int endcol)

resize 把一个矩阵改变形状而不改变实际内存数据

1
Mat::resize(size_t sz)