Convert Mat to Array/Vector in Opencv

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.

OpenCV convert Mat Object to array in C++

This will copy the image pixels into a vector. If you need a C like array just use &pixels[0].

std::vector<cv::Vec3b> pixels;

cv::MatIterator_<Vec3b> it, end;
for(it = imgHSV .begin<Vec3b>(), end = imgHSV.end<Vec3b>(); it != end; ++it)
{
pixels.push_back(*it);
}

This is a more efficient way to fill the pixels vector:

std::vector<cv::Vec3b> pixels(imgHSV.rows * imgHSV.cols);
cv::Mat m(imgHSV.rows, imgHSV.cols, CV_8UC3, &pixels[0]);
imgHSV.copyTo(m);

How to convert a OpenCV 2D matrix into a 1D array in C++?

Try looping through the matrix using pointer arithmetic. First, we create a random BGR matrix of size 9, to test the procedure. The data type stored in the mat are BGR pixels represented as cv::Scalars:

//Create random test mat:
cv::Mat3b randomMat(3,3);
cv::randu( randomMat, cv::Scalar(0,0,0), cv::Scalar(256,256,256) );
cv::Mat testMat = randomMat;

//Get mat total elements:
int matElements = testMat.cols * testMat.rows;

//Prepare output array:
cv::Scalar matArray[ matElements ];

This is the test matrix:

Sample Image

The raw values will be stored in the matArray container. Loop through the matrix using pointer arithmetic and store each pixel in the array:

//Loop trhough the mat, insert into array:
cv::MatIterator_<cv::Vec3b> it, end;
int i = 0;

for ( it = testMat.begin<cv::Vec3b>(), end = testMat.end<cv::Vec3b>(); it != end; ++it ) {

//get current bgr pixels:
uchar &r = (*it)[2];
uchar &g = (*it)[1];
uchar &b = (*it)[0];

//Store them into array, as a cv::Scalar:
matArray[i] = cv::Scalar(b,g,r);
i++;

}

We can check the data stored in the array like this:

for ( int i = 0; i < matElements; i++ ){
std::cout<<"i: "<<i<<" - "<<matArray[i]<<std::endl;
}

This yields:

i: 0 - [246, 156, 192, 0]
i: 1 - [7, 165, 166, 0]
i: 2 - [2, 179, 231, 0]
i: 3 - [212, 171, 230, 0]
i: 4 - [93, 138, 123, 0]
i: 5 - [80, 105, 242, 0]
i: 6 - [231, 239, 174, 0]
i: 7 - [174, 176, 191, 0]
i: 8 - [134, 25, 124, 0]

Converting a row of cv::Mat to std::vector

Data in OpenCV matrices is laid out in row-major order, so that each row is guaranteed to be contiguous. That means that you can interpret the data in a row as a plain C array. The following example comes directly from the documentation:

// compute sum of positive matrix elements
// (assuming that M is double-precision matrix)
double sum=0;
for(int i = 0; i < M.rows; i++)
{
const double* Mi = M.ptr<double>(i);
for(int j = 0; j < M.cols; j++)
sum += std::max(Mi[j], 0.);
}

Therefore the most efficient way is to pass the plain pointer to std::vector:

// Pointer to the i-th row
const double* p = mat.ptr<double>(i);

// Copy data to a vector. Note that (p + mat.cols) points to the
// end of the row.
std::vector<double> vec(p, p + mat.cols);

This is certainly faster than using the iterators returned by begin() and end(), since those involve extra computation to support gaps between rows.

incorrect size of vector when converting Mat to Vector

You have 4 times the expected number of elements in your vector because your matrix is of type CV_32FC1. If you look at the type of m.datastart and m.dataend you will see that they are uchar* and not float* as you expect.

To correct this, change v.assign(m.datastart, m.dataend); to v.assign((float*)m.datastart, (float*)m.dataend);. And you will need to pass a vector of float instead of a vector<uchar>.

Of course, your conversion function will only work for float type matrices. you could add some tests to detect the type of the matrix inside the function.

To answer your follow up question, it appears that you have the inverse problem. You are passing a CV_8U type matrix to a function that expects a CV_32F type one. Use your old conversion function for 8-bit matrices and use the fix I suggested for 32-bit floating-values matrices. You can also add a test inside the conversion function to automatically choose the right conversion. I also advise you to read a bit on OpenCV Mat class to understand better what type of data is in your matrices.



Related Topics



Leave a reply



Submit