Convert Mat to Array/Vector in OpenCV Convert Mat to Array/Vector in OpenCV arrays arrays

Convert Mat to Array/Vector in OpenCV


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.


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:


Here is another possible solution assuming matrix have one column( you can reshape original Mat to one column Mat via reshape):

Mat matrix= Mat::zeros(20, 1, CV_32FC1);vector<float> vec;matrix.col(0).copyTo(vec);