Converting a Row of Cv::Mat to Std::Vector

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.

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 2.3.1. cv::Mat to std::vector cast

It seems like you are trying to convert a two-dimensional 3x3 matrix into a one-dimensional vector. Not sure what result you're expecting from that, but you probably want to convert a row of the matrix into a vector. You can use this by giving the vector constructor a pointer to the row data:

int *p = eye.ptr<int>(0); // pointer to row 0
std::vector<int> vec(p, p+eye.cols); // construct a vector using pointer

Converting a cv::Mat into a vectorint

cv::Mat has a conversion operator to std::vector, provided the vector has the proper data type.

cv::Mat m = cv::Mat::eye(3, 3, CV_8UC1);
std::vector<uchar> v = m.reshape(0, 1);

cv::Mat_ to std::vector conversion

No, there are new constructors from rvalues in C++0x, but nothing like the one used here.

If isContinuous() means that all vales are the same, you could possibly use vector<_Tp>((size_t)(rows + cols - 1), *(_Tp*)data) to make copies of the first value.

Your iterator version seems correct otherwise.

Need a faster way to convert a cv::Mat into 1 dimensional vector form

assuming that the data in your Mat is continuous, Mat::reshape() for the win.

and it's almost for free. only rows/cols get adjusted, no memory moved. i.e, mat = mat.reshape(1,1) would make a 1d float array of it.

From Mat to vectorVec3f and the other way around without corrupting the image

Let's use a small random image for demonstration:

// Generate random input image
cv::Mat image(5, 5, CV_8UC3);
cv::randu(image, 0, 256);

Option 1

Since the input is CV_8UC3 (i.e. each element is a cv::Vec3b) and we want the elements as cv::Vec3f, we first need to use convertTo, to convert the Mat to CV_32FC3. We store the result in a temporary matrix, and for convenience (since we know the element type) we can explicitly use cv::Mat3f.

// First convert to 32bit floats
cv::Mat3f temp;
image.convertTo(temp, CV_32FC3);

Now we can just use Mat iterators to initialize the vector.

// Use Mat iterators to construct the vector.
std::vector<cv::Vec3f> v1(temp.begin(), temp.end());

Option 2

The previous option ends up allocating a temporary array. With a little creativity, we can avoid this.

As it turns out, it is possible to create a cv:Mat header wrapping a vector, sharing the underlying data storage.

We begin by crating an adequately sized vector:

std::vector<cv::Vec3f> v2(image.total());

The Mat created from such vector will have 1 column, and as many rows as there are elements. Therefore, we'll reshape our input matrix to identical shape, and then use convertTo, to write directly to the vector.

image.reshape(3, static_cast<int>(image.total())).convertTo(v2, CV_32FC3);

Whole program:

#include <opencv2/opencv.hpp>

#include <vector>

template<typename T>
void dump(std::string const& label, T const& data)
{
std::cout << label << ":\n";
for (auto const& v : data) {
std::cout << v << " ";
}
std::cout << "\n";

}

int main()
{
// Generate random input image
cv::Mat image(5, 5, CV_8UC3);
cv::randu(image, 0, 256);

// Option 1
// ========

// First convert to 32bit floats
cv::Mat3f temp;
image.convertTo(temp, CV_32FC3);

// Use Mat iterators to construct the vector.
std::vector<cv::Vec3f> v1(temp.begin(), temp.end());

// Option 2
// ========

std::vector<cv::Vec3f> v2(image.total());
image.reshape(3, static_cast<int>(image.total())).convertTo(v2, CV_32FC3);

// Output
// ======

dump("Input", cv::Mat3b(image));
dump("Vector 1", v1);
dump("Vector 2", v2);

return 0;
}

Sample output:

Input:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]
Vector 1:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]
Vector 2:
[246, 156, 192] [7, 165, 166] [2, 179, 231] [212, 171, 230] [93, 138, 123] [80, 105, 242] [231, 239, 174] [174, 176, 191] [134, 54, 234] [69, 25, 147] [24, 67, 124] [158, 203, 206] [89, 144, 210] [51, 31, 132] [123, 250, 234] [246, 204, 74] [111, 208, 249] [149, 234, 37] [55, 147, 143] [29, 214, 169] [215, 84, 190] [204, 110, 239] [216, 103, 137] [248, 173, 53] [221, 251, 29]

Issues with your Code

  1. In src->assign(in->datastart, in->dataend);

    Elements of src are Vec3f, however datastart and dataend are pointers to uchar.

    This will have several consequences. First of all, since in is CV_8UC3, there will be 3x as many elements. Also, each of the Vec3f instances will only have the first entry set, the other 2 will be 0.

  2. In src->insert(src->end(), in->ptr<Vec3f>(i), in->ptr<Vec3f>(i)+cols);

    Recall that you have already initialized src as vector<Vec3f>(rows * cols); -- i.e. the vector already has as many elements as there are pixels in the source image. However, in the loop you keep adding further elements at the end. This means that the resulting vector will have twice as many elements, with the first half of them being zeros.

    Furthermore, in is CV_8UC3, but you interpret the data as cv::Vec3f. This means you take the byte values of 4 consecutive pixels and intepret this as a sequence of 3 32bit floating point numbers. The result can't be anything else than garbage.

    It also means that you end up accessing data outside the valid area, potentially past the end of the buffer.

  3. In cv::Mat(rows, cols, CV_8U, src, cv::Mat::AUTO_STEP)...

    First of all, src holds Vec3f elements, but you're creating the Mat as CV_8U (which is also an issue, since you need to provide channel count here as well, so it's actually interpreted asCV_8UC1). So not only would you have the wrong number of channels, they would contain garbage due to type mismatch.

    Even bigger issue is that you pass src as the 4th parameter. Now, this is a pointer to the std::vector instance, not to the actual data it holds. (It compiles, since the 4th parameter is void*). That means you're actually interpreting the metadata of the vector, along with a lot of other unknown data. Result is garbage at best (Or as you found out, SEGFAULTs, or potentially nasty security bugs).


Back to Mat

Note that it is possible to imshow a floating point Mat, assuming the values are normalized in range [0,1].

We can take advantage of the Mat constructor that takes a vector, and just reshape the resulting matrix back to the original shape.

cv::Mat result(cv::Mat(v2).reshape(3, image.rows));

Note that in this case, the underlying data storage is shared with the source vector, hence you need to assure it remains in scope as long the the Mat does. If you do not wish to share the data, simply pass true as a second parameter to the constructor.

cv::Mat result(cv::Mat(v2, true).reshape(3, image.rows));

Of course, if you want to go back to CV_8UC3, that's as simple as adding a convertTo. In this case there's no need to copy the vector data, since the data type changes and new storage array will allocated automatically.

cv::Mat result;
cv::Mat(v2).reshape(3, image.rows).convertTo(result, CV_8UC3);


Related Topics



Leave a reply



Submit