命名空间

OpenCV 的 C++ API 里的所有类和函数都是定义在 cv 命名空间中的。因此,你可以通过两种方式使用这些类和函数:

  1. 使用 cv:: 前缀:

1
2
3
4
#include "opencv2/core/core.hpp"
...
cv::Mat H = cv::findHomography(points1, points2, CV_RANSAC, 5);
...

  1. 使用全局命名空间:

1
2
3
4
5
#include "opencv2/core/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, CV_RANSAC, 5 );
...

不过这种方案可能会导致部分命名和 STL 或其他库的命名产生冲突。不过这依然可以使用前缀的方法解决。

1
2
3
4
Mat a(100, 100, CV_32F);
randu(a, Scalar::all(1), Scalar::all(std::rand()));
cv::log(a, a);
a /= std::log(2.);

Mat

用于储存图像(或其他数组型的数据)的容器。

声明

一维数组

1
Mat a_mat;

将声明一个大小为 0 的 Mat 对象。也可以直接指定初始大小:

1
Mat a_mat(nrows, ncols, type[, fillValue]);

这里的 type 是矩阵数据类型。格式为:

1
CV_<bit_depth>(S|U|F)C<number_of_channels>

1
2
3
S = 符号整型
U = 无符号整型
F = 浮点型

根据这个可以灵活定义多种数据类型,例如:

  • CV_8U:8位无符号整型单通道矩阵,等价于 CV_8UC1
  • CV_8UC3:8位无符号整型三通道矩阵;
  • CV_32FC2:32位浮点型双通道矩阵。

要获得某个 Mat 对象的数据类型,可以使用 Mat:type() 函数。

高维数组

1
2
3
// create a 100x100x100 8-bit array
int sz[] = {100, 100, 100};
Mat bigCube(3, sz, CV_8U, Scalar::all(0));

其他创建方法

  1. 使用 cv::create() 函数

1
2
Mat a_mat;
a_mat.create(nrows, ncols, type);

  1. 通过赋值操作复制一个 Mat 对象。

这种方法只是简单的复制头指针的地址,因此得到的 Mat 对象与原来的 Mat 对象共享同样的数据空间,整个赋值过程只是一个 \(O(1)\) 操作。或者通过 Mat::copyTo()Mat::clone() 操作拷贝一个 Mat 对象,这种方法得到的 Mat 对象申请了新的空间并拷贝了原 Mat 对象的数据。详见 内存管理机制

  1. 声明一个 Mat 对象,将其头指针并指向另一个数组对象的部分元素。

同样也是个 \(O(1)\) 操作。对该 Mat 对象操作会影响原来的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// add the 5-th row, multiplied by 3 to the 3rd row
M.row(3) = M.row(3) + M.row(5)*3;
// now copy the 7-th column to the 1-st column
// M.col(1) = M.col(7); // this will not work
Mat M1 = M.col(1);
M.col(7).copyTo(M1);
// create a new 320x240 image
Mat img(Size(320,240),CV_8UC3);
// select a ROI
Mat roi(img, Rect(10,10,100,100));
// fill the ROI with (0,255,0) (which is green in RGB space);
// the original 320x240 image will be modified
roi = Scalar(0,255,0);

  1. 声明一个 Mat 对象,并将头指针指向用户分配的数据。

1
2
double m[3][3] = { {a, b, c}, {d, e, f}, {g, h, i} };
Mat M = Mat(3, 3, CV_64F, m).inv();

将 C API 的 CvMat 和 IplImage 转换到 Mat 也是用到这个方式:

1
2
3
Mat img = imread("image.jpg");
IplImage img1 = img;
CvMat m = img;

注意上面的命令并没有拷贝数据。

  1. 使用类 Matlab 风格的数组初始化函数 zeros()ones()eye()

1
2
// create a double-precision identity martix and add it to M.
M += Mat::eye(M.rows, M.cols, CV_64F);

  1. 使用逗号分割的初始化方式:

1
2
// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);

内存管理机制

Mat 使用计数指针的方式来实现智能指针,以使得多个 Mat 对象可以共享同一份数据,这样可以大幅节省空间。我们可以看看它的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CV_EXPORTS Mat
{
public:
// ... a lot of methods ...
...
/*! includes several bit-fields:
- the magic signature
- continuity flag
- depth
- number of channels
*/
int flags;
//! the array dimensionality, >= 2
int dims;
//! the number of rows and columns or (-1, -1) when the array has more than 2 dimensions
int rows, cols;
//! pointer to the data
uchar* data;
//! pointer to the reference counter;
// when array points to user-allocated data, the pointer is NULL
int* refcount;
// other members
...
};

其中 refcount 指针用来对该对象的引用次数计数。当引用次数减少到 0 时,说明此时的数据已经没有用了,可以放心的删除,从而释放空间。但这样可能带来的一个问题:对其中一个Mat的数据操作,有可能会对其他指向同一块数据的 Mat 产生灾难性的影响。

因此,如果想要重新拷贝一份原有的 Mat 的数据,不要直接赋值,而应该是用 Mat::copyTo()Mat::clone() 操作:

1
2
Mat img = imread("image.jpg");
Mat img1 = img.clone();

要强制释放某个 Mat 对象所指向的数据,可以使用 Mat::release() 函数。

矩阵操作

OpenCV 所支持的矩阵操作如下(其中 AB 代表 Mat 类型,s 代表 Scalar 类型,alpha 代表 Double 类型):

  • Addition, subtraction, negation: A+B, A-B, A+s, A-s, s+A, s-A, -A
  • Scaling: A*alpha
  • Per-element multiplication and division: A.mul(B), A/B, alpha/A
  • Matrix multiplication: A*B
  • Transposition: A.t() (means \(A^T\))
  • Matrix inversion and pseudo-inversion, solving linear systems and least-squares problems: A.inv([method]) (\(\sim A^{-1}\)) , A.inv([method])*B (\(\sim X: AX=B\))
  • Comparison: A cmpop B, A cmpop alpha, alpha cmpop A, where cmpop is one of : >, >=, ==, !=, <=, <. The result of comparison is an 8-bit single channel mask whose elements are set to 255 (if the particular element or pair of elements satisfy the condition) or 0.
  • Bitwise logical operations: A logicop B, A logicop s, s logicop A, ~A, where logicop is one of : &, |, ^.
  • Element-wise minimum and maximum: min(A, B), min(A, alpha), max(A, B), max(A, alpha)
  • Element-wise absolute value: abs(A)
  • Cross-product, dot-product: A.cross(B) A.dot(B)
  • Any function of matrix or matrices and scalars that returns a matrix or a scalar, such as norm, mean, sum, countNonZero, trace, determinant, repeat, and others.
  • Matrix initializers ( Mat::eye(), Mat::zeros(), Mat::ones() ), matrix comma-separated initializers, matrix constructors and operators that extract sub-matrices (see Mat description).
  • Mat_<destination_type>() constructors to cast the result to the proper type.

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
// compute pseudo-inverse of A, equivalent to A.inv(DECOMP_SVD)
SVD svd(A);
Mat pinvA = svd.vt.t()*Mat::diag(1./svd.w)*svd.u.t();
// compute the new vector of parameters in the Levenberg-Marquardt algorithm
x -= (A.t()*A + lambda*Mat::eye(A.cols,A.cols,A.type())).inv(DECOMP_CHOLESKY)*(A.t()*err);
// sharpen image using "unsharp mask" algorithm
Mat blurred; double sigma = 1, threshold = 5, amount = 1;
GaussianBlur(img, blurred, Size(), sigma, sigma);
Mat lowConstrastMask = abs(img - blurred) < threshold;
Mat sharpened = img*(1+amount) + blurred*(-amount);
img.copyTo(sharpened, lowContrastMask);

Comments