Linear Algebra in OpenCV: Assignment, Cloning and Accessing Sub-parts

In the last post, we saw the basic methods to load and display matrices. Let us see some more aspects of an OpenCV matrix.

Matrix Assignment and Cloning

In opencv, the assignment (“=”) operator does not copy a matrix. It only attaches a new label or alias to a matrix. In c/c++ when we write A = B, we expect A and B to be two different variables with same value. However, in OpenCV, such an assignment operation will actually say that A and B are invariably the same matrix. This will be illustrated in the following opencv snippet.

Program Code Program Output
[code lang=”cpp”]
cv::Mat MyMat = cv::Mat::eye(3,3,CV_64FC1);
cv::Mat AnotherMat = MyMat;
AnotherMat.at(1,2) = 5.5;
std::cout<<MyMat;
[/code]
begin{array}{ccc} [1, & 0, & 0;\ 0, & 1, & 5.5;\ 0, & 0, & 1;] end{array}
Notice that although we have changed “AnotherMat”, “MyMat” has been changed. This is because, both names actually point to the same matrix. If we want to create a new matrix, we need to use

the clone() method. For example, if we change the 2nd line of this code by the following line, then “MyMat” will not be changed anymore when we’ll change the “AnotherMat” matrix.

[code lang=”cpp”]cv::Mat AnotherMat = MyMat.clone();[/code]

When we use the clone method, a deep copy of all the elements take place. So, for big matrices, the clone operation may take considerably longer time than the assignment operation. According to the language of OpenCV documentation, the matrix data is copied in clone operation whereas only the matrix header is copied in simple assignments. Header actually means some basic information about a matrix such as, the number of rows, columns, number of channels, data type etc. Sometimes, we might need to change the header. To do that, we can use the create() method. The following code will clarify its use.

[code lang=”cpp”]
cv::Mat MyMat = cv::Mat::eye(3,3,CV_64FC1);
MyMat.create(2,2,CV_64FC1);
MyMat.setTo(5);
std::cout<<MyMat;
[/code]

The MyMat was a 3×3 double type single channel matrix. However, the create() method in line 2 has changed the header into a 2×2 double type single channel matrix. The setTo() method in line 3 is used to set all the elements of the matrix equal to to 5.0.

Accessing Sub-parts of Matrices

While working with matrices in OpenCV, we often need to access into a matrix partially. For instance, we sometimes need to access the rows, columns, or diagonal elements of a matrix. We often also need to concatenate two matrices (or vectors) horizontally or vertically. There is a set of functions in OpenCV (row, col, diag, operator(), rowrange, colrange, hconcat, vconcat, copyto) to support these operations. The following code illustrates the use of these functions.

Program Code Program Output
[code lang=”cpp”]
cv::Mat MyMat = cv::Mat::eye(3,3,CV_64FC1);
std::cout<<MyMat<<std::endl;
cv::Mat oneRow = MyMat.row(1);
std::cout<<oneRow<<std::endl;
// Also play with col

oneRow.setTo(5.0);
std::cout<<oneRow<<std::endl;
std::cout<<MyMat<<std::endl;
cv::Mat twoRows = MyMat.rowRange(1,3).clone();
// Notice that it is not (1,2)
std::cout<<twoRows<<std::endl;
twoRows.copyTo(MyMat.rowRange(0,2));
std::cout<<MyMat<<std::endl;
std::cout<<oneRow<<std::endl;
// Also play with colRange

MyMat(cv::Range(1,3),cv::Range(0,2)).setTo(1.5);
std::cout<<MyMat<<std::endl;
std::cout<<MyMat.diag()<<std::endl;
MyMat.diag().setTo(3.33);
std::cout<<MyMat<<std::endl;
[/code]

[code lang=”cpp”]
[1,0,0;
0,1,0;
0,0,1]
[0,1,0]
[5,5,5]
[1,0,0;
5,5,5;
0,0,1]
[5,5,5;
0,0,1]
[5,5,5;
0,0,1;
0,0,1]
[0,0,1]
[5,5,5;
1.5,1.5,1;
1.5,1.5,1]
[5;1.5;1]
[3.33,5,5;
1.5,3.33,1;
1.5,1.5,3.33]
[/code]

We need to analyze this code very carefully. In lines 1 and 2, a 3×3 identity matrix has been created and displayed. In line 3, the matrix “oneRow” is defined for pointing to the second row of “MyMat”. Unlike MATLAB, the row method uses a 0-based indexing. In fact, 0-based indexing is consistently used in all the functions of OpenCV. Notice in line 3 that, the assignment operator is used without any cloning operation. As a result, if we change oneRow, this will be reflected in MyMat. This is shown in line 7-9 of the code. When we use the setTo command to set all the elements of oneRow to become 5.0, it also changes the 2nd row of MyMat (look at lines 6-8 of program output). This kind of operation should be handled very carefully because this behavior often cause confusions while working with OpenCV. Similar operations can also be done with col method, where the columns will be manipulated.

In line 10 of the program code, rows second and third rows are copied in twoRows matrix. Notice the indexing convension. It is a zero-based index, so the index of first, second and third rows are zero, one and two. However, in the rowRange function, it takes rows from the first argument (1) to one less than the second argument. Since we want the rows one and two to be copied; so we’ve given the values (1,3). We have copied this two rows in the first two rows MyMat by the copyTo method using the range argument (0,2). We need to remember one point here. copyTo function CAN NOT refer to itself. That is, you can not combine line 10 and 13 as follows.

[code lang=”cpp”]
MyMat.rowRange(1,3).clone().copyTo(MyMat.rowRange(0,2));
// Does Not Work!!
[/code]

In Line 18, a block of the matrix is selected and set to 1.5. Output of this code is in line 15 of the program output. The program also shows the use of the diag method. It gives the diagonal entries of a matrix as a column vector. diag can take a integer as its argument. If its value is zero, it refers to the main diagonal. On the other hand, positive integers indicate the diagonals upper than the main one, and negatives indicate lower diagonals.

Concatenation of Vectors into Matrix

Sometimes it is needed to concatenate multiple vectors into a matrix. It can easily be done in OpenCV by using std::vector.

[code lang=”cpp”]
std::vector<cv::Vec2f> vectors;
cv::Vec2f vec1;
cv::Vec2f vec2;
// … assign values in vec1, vec2 here …
vectors.push_back(vec1);
vectors.push_back(vec2);
cv::Mat concatMat = cv::Mat(vectors).reshape(1).t();
[/code]