OpenCV: Basic Structures

Jan 14, 2016

Contents

Mat like a Smart Pointer

The mostly used basic structure in OpenCV C++ is Mat. And the first thing to remember about Mat is to treat it like a smart pointer (like shared_ptr) with the addition of some meta data (like cols rows etc), instead of an ordinary struct or container (like std::vector). Hence, when using = operator to pass Mat like:

using namespace cv;
Mat m1;
Mat m2 = m1;

You must know that apart from some meta data, m2 and m1 share one memory part for their internal matrix data, and any change you make to the matrix data of m1 or m2 will happen to another one. The reason why OpenCV implements Mat like this is to release users from mannual memory management. Since in computer vision applications, image data is usually stored in Mat, and it would be huge cost for the computer if image data was frequently copied and reallocated in memory.

Internally, such mechanism is achieved by the internal cv::Mat::data pointer, which is managed by OpenCV and ordinary users do not need to care about its details.

Of course, as Mat is quite a good tool for matrix operation, in many cases we use it to store small matrices (like a 3 by 3 rotation matrix). For such operations, making an individual copy of Mat is necessary and would cost little computer resources. To create a new clone independent from m1, we can use clone() function:

Mat m2 = m1.clone();

Or if we already have m2, use copyTo():

m1.copyTo(m2);

Some Functions For Mat

copyTo

There are a bit more need to mention about copyTo() function. Here the tricky thing is, with m1.copyTo(m2), what exactly happens to m2 depends on whether m2 has the same size as m1. We use a simple example here:

Mat m1 = Mat::ones(3,3,CV_32FC1);
Mat m2 = m1;
Mat m3 = Mat::zeros(3,3,CV_32FC1);
m3.copyTo(m1);
cout << "m1 = " << endl << m1 << endl;
cout << "m2 = " << endl << m2 << endl;

This will give us:

m1 = 
[0, 0, 0;
0, 0, 0;
0, 0, 0]
m2 = 
[0, 0, 0;
0, 0, 0;
0, 0, 0]

The result is reasonable. Since m2 and m1 share the same memory location, as we copy the data of m3 to m1, the same change will happen to m2. However, if we initialize m3 to be

Mat m3 = Mat::zeros(3,2,CV_32FC1);

The output will be

m1 = 
[0, 0;
0, 0;
0, 0]
m2 = 
[1, 1, 1;
1, 1, 1;
1, 1, 1]

m2 does not change like m1! This is because m1 and m3 have different size. In such situation, when copying data from m3 to m1, OpenCV will allocate a new memory location for m1, hence m1 and m2 no longer share the same memory location, and are independent from each other ever since.

reshape

reshape function is to rearrange the elements in a Mat structure. Its format is like

Mat Mat::reshape(int cn, int rows=0) const

Parameters:

  • cn – New number of channels. If the parameter is 0, the number of channels remains the same.

  • rows – New number of rows. If the parameter is 0, the number of rows remains the same.

The key of using this function is to remember:

  1. the number of elements in (rows * cols * numChannels) must be the same before and after reshaping

  2. the elements keep an order of ‘left to right, up to down’

Suppose we have

mat = [a b c d]

Then mat.reshape(0,2) gives us

[a b; c d]

push_back

Just like what we do with std::vector, we can also push a Mat (its rows) to the back of another Mat. This is sometimes very convenient. For example, to stitch two images, just do:

img1.push_back(img2);

It will make a new image with img2 under the original img1.

Mat_ as Template Container

Apart from Mat, OpenCV also provide a template container Mat_, which is derived from Mat, for more convenient matrix operation. For example, with Mat_, we can fill a matrix like:

float theta;
Mat m = (Mat_<float>(3,3) <<
        cos(theta), -sin(theta), 0,
        sin(theta),  cos(theta), 0,
        0,           0,          1);

Very elegant, right? And a bit like MATLAB. And the use of << operator is very C++. Eigen library also support similar operation style on matrix.

BTW, the above opperation will also allocate a new memory location for m. Therefore, with the code below:

Mat m1 = Mat::ones(3,3,CV_32FC1);
Mat m2 = m1;
Mat m3 = Mat::zeros(3,3,CV_32FC1);
m1 = (Mat_<float>(3,3) << 1,0,0,0,1,0,0,0,1);
cout << "m1 = " << endl << m1 << endl;
cout << "m2 = " << endl << m2 << endl;

The output is

m1 = 
[1, 0, 0;
0, 1, 0;
0, 0, 1]
m2 = 
[1, 1, 1;
1, 1, 1;
1, 1, 1]

m1 no longer points to the same location as m2.

For element access, Mat_ also provides more convenient interfaces than Mat. One example:

void Frame::computeBoundUn(const Mat& K, const Mat& D){
    float x = (float)img.cols;
    float y = (float)img.rows;
    if(D.at<float>(0) == 0.){
        minXUn = 0.f;
        minYUn = 0.f;
        maxXUn = x;
        maxYUn = y;
        return;
    }
    Mat_<Point2f> mat(1,4);
    mat << Point2f(0,0), Point2f(x,0), Point2f(0,y), Point2f(x,y);
    undistortPoints(mat, mat, K, D, Mat(), K);
    minXUn = std::min(mat(0).x, mat(2).x);
    minYUn = std::min(mat(0).y, mat(1).y);
    maxXUn = std::max(mat(1).x, mat(3).x);
    maxYUn = std::max(mat(2).y, mat(3).y);
}

See the last four lines. With (i) (or (i, j)), element access becomes much convenient than using .at<TypeName>(i, j) function.

Matrices with More than Two Dimensions

We usually use Mat for 2D matrices. What if we want multi-dimension matrices? One method is like:

int dims[] = {3,4,5};
Mat mat(3, dims, CV_32FC1, Scalar(0));
cout << mat.at<float>(0,0,0);

Initialize a multi-dimension matrix by passing in an int for how many dimensions to assign and an array for the depth of every dimension. For element access, use at<TypeName>() function, as we do for 2-D Mat.

Using a 2D Mat with more than one channel is also common. For example, for an RGB image, we can use Mat(m, n, CV_8UC3). Every element of the matrix has three channels, each channel representing the value of Red/Green/Blue. To access the element in a specific channel of a given location (i, j), use cv::Vec3b:

uchar newval[] = {255, 255, 255};
image.at<cv::Vec3b>(i, j)[0] = newval[0];
image.at<cv::Vec3b>(i, j)[1] = newval[1];
image.at<cv::Vec3b>(i, j)[2] = newval[2];

Although these can be used for multi-dimension matrices, strictly speaking, they are still 2-D matrices, and are different from those initialized directly with dimention number and dimension depths.


Tags: OpenCV

Comment by Github Issues

Chat in Gitter/fan-farm

License (CC) BY-NC-SA | Subscribe RSS | Email hi@fzheng.me