OpenCV C++ n-dimensional dense array class
class CV_EXPORTS Mat
// C++ code:
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...
};
The class Mat
represents an n-dimensional dense numerical
single-channel or multi-channel array. It can be used to store real or
complex-valued vectors and matrices, grayscale or color images, voxel
volumes, vector fields, point clouds, tensors, histograms (though, very
high-dimensional histograms may be better stored in a SparseMat
).
The data layout of the array
M is defined by the array M.step[]
, so that the address
of element (i_0,...,i_(M.dims-1)), where 0 ,
is computed as:
addr(M_(i_0,...,i_(M.dims-1))) = M.data + M.step[0]*i_0 + M.step[1]*i_1
+... + M.step[M.dims-1]*i_(M.dims-1)
In case of a 2-dimensional array, the above formula is reduced to:
addr(M_(i,j)) = M.data + M.step[0]*i + M.step[1]*j
Note that M.step[i] >= M.step[i+1]
(in fact, M.step[i] >=
M.step[i+1]*M.size[i+1]
). This means that 2-dimensional matrices are
stored row-by-row, 3-dimensional matrices are stored plane-by-plane, and so
on. M.step[M.dims-1]
is minimal and always equal to the element
size M.elemSize()
.
So, the data layout in Mat
is fully compatible with
CvMat
, IplImage
, and CvMatND
types
from OpenCV 1.x. It is also compatible with the majority of dense array types
from the standard toolkits and SDKs, such as Numpy (ndarray), Win32
(independent device bitmaps), and others, that is, with any array that uses
*steps* (or *strides*) to compute the position of a pixel. Due to this
compatibility, it is possible to make a Mat
header for
user-allocated data and process it in-place using OpenCV functions.
There are many different ways to create a Mat
object. The most
popular options are listed below:
- Use the
create(nrows, ncols, type)
method or the similar
Mat(nrows, ncols, type[, fillValue])
constructor. A new array of
the specified size and type is allocated. type
has the same
meaning as in the cvCreateMat
method.
For example, CV_8UC1
means a 8-bit single-channel array,
CV_32FC2
means a 2-channel (complex) floating-point array, and
so on.
// C++ code:
// make a 7x7 complex matrix filled with 1+3j.
Mat M(7,7,CV_32FC2,Scalar(1,3));
// and now turn M to a 100x60 15-channel 8-bit matrix.
// The old content will be deallocated
M.create(100,60,CV_8UC(15));
As noted in the introduction to this chapter, create()
allocates
only a new array when the shape or type of the current array are different
from the specified ones.
- Create a multi-dimensional array:
// C++ code:
// create a 100x100x100 8-bit array
int sz[] = {100, 100, 100};
Mat bigCube(3, sz, CV_8U, Scalar.all(0));
It passes the number of dimensions =1 to the Mat
constructor but
the created array will be 2-dimensional with the number of columns set to 1.
So, Mat.dims
is always >= 2 (can also be 0 when the array is
empty).
- Use a copy constructor or assignment operator where there can be an
array or expression on the right side (see below). As noted in the
introduction, the array assignment is an O(1) operation because it only
copies the header and increases the reference counter. The
Mat.clone()
method can be used to get a full (deep) copy of the array when you need it.
- Construct a header for a part of another array. It can be a single
row, single column, several rows, several columns, rectangular region in the
array (called a *minor* in algebra) or a diagonal. Such operations are also
O(1) because the new header references the same data. You can actually modify
a part of the array using this feature, for example:
// C++ code:
// 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);
Due to the additional datastart
and dataend
members, it is possible to compute a relative sub-array position in the main
*container* array using locateROI()
:
// C++ code:
Mat A = Mat.eye(10, 10, CV_32S);
// extracts A columns, 1 (inclusive) to 3 (exclusive).
Mat B = A(Range.all(), Range(1, 3));
// extracts B rows, 5 (inclusive) to 9 (exclusive).
// that is, C ~ A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range.all());
Size size; Point ofs;
C.locateROI(size, ofs);
// size will be (width=10,height=10) and the ofs will be (x=1, y=5)
As in case of whole matrices, if you need a deep copy, use the
clone()
method of the extracted sub-matrices.
- Make a header for user-allocated data. It can be useful to do the
following:
- Process "foreign" data using OpenCV (for example, when you implement a
DirectShow* filter or a processing module for
gstreamer
, and so
on). For example:
// C++ code:
void process_video_frame(const unsigned char* pixels,
int width, int height, int step)
Mat img(height, width, CV_8UC3, pixels, step);
GaussianBlur(img, img, Size(7,7), 1.5, 1.5);
- Quickly initialize small matrices and/or get a super-fast element
access.
// C++ code:
double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
Mat M = Mat(3, 3, CV_64F, m).inv();
Partial yet very common cases of this *user-allocated data* case are
conversions from CvMat
and IplImage
to
Mat
. For this purpose, there are special constructors taking
pointers to CvMat
or IplImage
and the optional flag
indicating whether to copy the data or not.
Backward conversion from Mat
to CvMat
or
IplImage
is provided via cast operators Mat.operator
CvMat() const
and Mat.operator IplImage()
. The operators
do NOT copy the data.
// C++ code:
IplImage* img = cvLoadImage("greatwave.jpg", 1);
Mat mtx(img); // convert IplImage* -> Mat
CvMat oldmat = mtx; // convert Mat -> CvMat
CV_Assert(oldmat.cols == img->width && oldmat.rows == img->height &&
oldmat.data.ptr == (uchar*)img->imageData && oldmat.step == img->widthStep);
- Use MATLAB-style array initializers,
zeros(), ones(),
eye()
, for example:
// C++ code:
// create a double-precision identity martix and add it to M.
M += Mat.eye(M.rows, M.cols, CV_64F);
- Use a comma-separated initializer:
// C++ code:
// create a 3x3 double-precision identity matrix
Mat M = (Mat_(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
With this approach, you first call a constructor of the "Mat_" class with the
proper parameters, and then you just put <<
operator followed by
comma-separated values that can be constants, variables, expressions, and so
on. Also, note the extra parentheses required to avoid compilation errors.
Once the array is created, it is automatically managed via a
reference-counting mechanism. If the array header is built on top of
user-allocated data, you should handle the data by yourself.
The array data is deallocated when no one points to it. If you want to
release the data pointed by a array header before the array destructor is
called, use Mat.release()
.
The next important thing to learn about the array class is element access.
This manual already described how to compute an address of each array
element. Normally, you are not required to use the formula directly in the
code. If you know the array element type (which can be retrieved using the
method Mat.type()
), you can access the elementM_(ij)
of a 2-dimensional array as:
// C++ code:
M.at(i,j) += 1.f;
assuming that M is a double-precision floating-point array. There are several
variants of the method at
for a different number of dimensions.
If you need to process a whole row of a 2D array, the most efficient way is
to get the pointer to the row first, and then just use the plain C operator
[]
:
// C++ code:
// compute sum of positive matrix elements
// (assuming that M isa double-precision matrix)
double sum=0;
for(int i = 0; i < M.rows; i++)
const double* Mi = M.ptr(i);
for(int j = 0; j < M.cols; j++)
sum += std.max(Mi[j], 0.);
Some operations, like the one above, do not actually depend on the array
shape. They just process elements of an array one by one (or elements from
multiple arrays that have the same coordinates, for example, array addition).
Such operations are called *element-wise*. It makes sense to check whether
all the input/output arrays are continuous, namely, have no gaps at the end
of each row. If yes, process them as a long single row:
// compute the sum of positive matrix elements, optimized variant
double sum=0;
int cols = M.cols, rows = M.rows;
if(M.isContinuous())
cols *= rows;
rows = 1;
for(int i = 0; i < rows; i++)
const double* Mi = M.ptr(i);
for(int j = 0; j < cols; j++)
sum += std.max(Mi[j], 0.);
In case of the continuous matrix, the outer loop body is executed just once.
So, the overhead is smaller, which is especially noticeable in case of small
matrices.
Finally, there are STL-style iterators that are smart enough to skip gaps
between successive rows:
// C++ code:
// compute sum of positive matrix elements, iterator-based variant
double sum=0;
MatConstIterator_ it = M.begin(), it_end = M.end();
for(; it != it_end; ++it)
sum += std.max(*it, 0.);
The matrix iterators are random-access iterators, so they can be passed to
any STL algorithm, including std.sort()
.
Note:
- An example demonstrating the serial out capabilities of cv.Mat can be
found at opencv_source_code/samples/cpp/cout_mat.cpp