Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert Mat to Array/Vector in OpenCV

I am novice in OpenCV. Recently, I have troubles finding OpenCV functions to convert from Mat to Array. I researched with .ptr and .at methods available in OpenCV APIs, but I could not get proper data. I would like to have direct conversion from Mat to Array(if available, if not to Vector). I need OpenCV functions because the code has to be undergo high level synthesis in Vivado HLS. Please help.

like image 744
Main Avatar asked Oct 31 '14 19:10

Main


People also ask

How to convert Mat to vector in c++?

Mat to vectorcv::Mat flat = image. reshape(1, image. total()*image. channels()); std::vector<uchar> vec = image.

What is a CV :: mat?

In OpenCV the main matrix class is called Mat and is contained in the OpenCV-namespace cv. This matrix is not templated but nevertheless can contain different data types. These are indicated by a certain type-number. Additionally, OpenCV provides a templated class called Mat_, which is derived from Mat.

What is use of mat class in OpenCV?

The Mat class of OpenCV library is used to store the values of an image. It represents an n-dimensional array and is used to store image data of grayscale or color images, voxel volumes, vector fields, point clouds, tensors, histograms, etc.

What is CV_32FC1?

CV_32F defines the depth of each element of the matrix, while. CV_32FC1 defines both the depth of each element and the number of channels.


2 Answers

If the memory of the Mat mat is continuous (all its data is continuous), you can directly get its data to a 1D array:

std::vector<uchar> array(mat.rows*mat.cols*mat.channels()); if (mat.isContinuous())     array = mat.data; 

Otherwise, you have to get its data row by row, e.g. to a 2D array:

uchar **array = new uchar*[mat.rows]; for (int i=0; i<mat.rows; ++i)     array[i] = new uchar[mat.cols*mat.channels()];  for (int i=0; i<mat.rows; ++i)     array[i] = mat.ptr<uchar>(i); 

UPDATE: It will be easier if you're using std::vector, where you can do like this:

std::vector<uchar> array; if (mat.isContinuous()) {   // array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)   array.assign(mat.data, mat.data + mat.total()*mat.channels()); } else {   for (int i = 0; i < mat.rows; ++i) {     array.insert(array.end(), mat.ptr<uchar>(i), mat.ptr<uchar>(i)+mat.cols*mat.channels());   } } 

p.s.: For cv::Mats of other types, like CV_32F, you should do like this:

std::vector<float> array; if (mat.isContinuous()) {   // array.assign((float*)mat.datastart, (float*)mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)   array.assign((float*)mat.data, (float*)mat.data + mat.total()*mat.channels()); } else {   for (int i = 0; i < mat.rows; ++i) {     array.insert(array.end(), mat.ptr<float>(i), mat.ptr<float>(i)+mat.cols*mat.channels());   } } 

UPDATE2: For OpenCV Mat data continuity, it can be summarized as follows:

  • Matrices created by imread(), clone(), or a constructor will always be continuous.
  • The only time a matrix will not be continuous is when it borrows data (except the data borrowed is continuous in the big matrix, e.g. 1. single row; 2. multiple rows with full original width) from an existing matrix (i.e. created out of an ROI of a big mat).

Please check out this code snippet for demonstration.

like image 148
herohuyongtao Avatar answered Sep 28 '22 05:09

herohuyongtao


Can be done in two lines :)

Mat to array

uchar * arr = image.isContinuous()? image.data: image.clone().data; uint length = image.total()*image.channels(); 

Mat to vector

cv::Mat flat = image.reshape(1, image.total()*image.channels()); std::vector<uchar> vec = image.isContinuous()? flat : flat.clone(); 

Both work for any general cv::Mat.

Explanation with a working example

    cv::Mat image;     image = cv::imread(argv[1], cv::IMREAD_UNCHANGED);   // Read the file     cv::namedWindow("cvmat", cv::WINDOW_AUTOSIZE );// Create a window for display.     cv::imshow("cvmat", image );                   // Show our image inside it.      // flatten the mat.     uint totalElements = image.total()*image.channels(); // Note: image.total() == rows*cols.     cv::Mat flat = image.reshape(1, totalElements); // 1xN mat of 1 channel, O(1) operation     if(!image.isContinuous()) {         flat = flat.clone(); // O(N),     }     // flat.data is your array pointer     auto * ptr = flat.data; // usually, its uchar*     // You have your array, its length is flat.total() [rows=1, cols=totalElements]     // Converting to vector     std::vector<uchar> vec(flat.data, flat.data + flat.total());     // Testing by reconstruction of cvMat     cv::Mat restored = cv::Mat(image.rows, image.cols, image.type(), ptr); // OR vec.data() instead of ptr     cv::namedWindow("reconstructed", cv::WINDOW_AUTOSIZE);     cv::imshow("reconstructed", restored);      cv::waitKey(0);      

Extended explanation:

Mat is stored as a contiguous block of memory, if created using one of its constructors or when copied to another Mat using clone() or similar methods. To convert to an array or vector we need the address of its first block and array/vector length.

Pointer to internal memory block

Mat::data is a public uchar pointer to its memory.
But this memory may not be contiguous. As explained in other answers, we can check if mat.data is pointing to contiguous memory or not using mat.isContinous(). Unless you need extreme efficiency, you can obtain a continuous version of the mat using mat.clone() in O(N) time. (N = number of elements from all channels). However, when dealing images read by cv::imread() we will rarely ever encounter a non-continous mat.

Length of array/vector

Q: Should be row*cols*channels right?
A: Not always. It can be rows*cols*x*y*channels.
Q: Should be equal to mat.total()?
A: True for single channel mat. But not for multi-channel mat
Length of the array/vector is slightly tricky because of poor documentation of OpenCV. We have Mat::size public member which stores only the dimensions of single Mat without channels. For RGB image, Mat.size = [rows, cols] and not [rows, cols, channels]. Mat.total() returns total elements in a single channel of the mat which is equal to product of values in mat.size. For RGB image, total() = rows*cols. Thus, for any general Mat, length of continuous memory block would be mat.total()*mat.channels().

Reconstructing Mat from array/vector

Apart from array/vector we also need the original Mat's mat.size [array like] and mat.type() [int]. Then using one of the constructors that take data's pointer, we can obtain original Mat. The optional step argument is not required because our data pointer points to continuous memory. I used this method to pass Mat as Uint8Array between nodejs and C++. This avoided writing C++ bindings for cv::Mat with node-addon-api.

References:

  • Create memory continuous Mat
  • OpenCV Mat data layout
  • Mat from array
like image 33
sziraqui Avatar answered Sep 28 '22 05:09

sziraqui