I have 3D Mat and would like to convert it to Vector. I tried opencv's reshape() function but it seems to not work with matrices that have dimensions more than 2. How can I convert to 3D Mat to Vector ? I can do it by accessing all elements in the Mat. Is there any efficient way?
问题:
回答1:
If we have the following 3D matrix:
const int ROWS=2, COLS=3, PLANES=4;
int dims[3] = {ROWS, COLS, PLANES};
cv::Mat m = cv::Mat(3, dims, CV_32SC1); // works with other types (e.g. float, double,...)
This only works for continuous mat objects (i.e. m.isContinuous() == true)
To get a vector of the same type containing the same elements:
Using the overloaded STL vector constructor:
Declaring and initializing a vector of same type as the matrix and copying all mat elements:
const int* p3 = m3.ptr<int>(0); std::vector<int> flat0(p3, p3+m3.total());
Using OpenCV's reshape:
This differs from the first solution in how the elements are laid out in the end. Originally from this post: How to divide 3D matrix into bunches of 2D matrix with opencv C++ I second his suggestion on defining your dimensions as channels if possible. OpenCV handles channels better than dimensions. Of course, that might not be applicable if you intended to have more than 4 dimensions.
cv::Mat m2(ROWS, COLS*PLANES, CV_32SC1, m3.data); // no copying happening here cv::Mat m2xPlanes = m2.reshape(PLANES); // not sure if this involves a copy std::vector<Mat> planes; cv::split(m2xPlanes, planes); // usually used for splitting multi-channel matrices std::vector<int> flat; for(size_t i=0; i<planes.size(); i++) { cv::Mat plane_i = planes[i]; const int* plane_i_ptr = plane_i.ptr<int>(0); flat.insert(flat.end(), plane_i_ptr, plane_i_ptr+plane_i.total()); }
With both solutions all elements are accounted for except that they're ordered differently and thus accessed differently. In the first you access elements at row, col, plane via
int index = row * COLS * PLANES + col * PLANES + p
In the second you're elements are ordered by plane.
Picking which solution probably depends on how you'll be indexing the vector.
回答2:
Another variant but one that, as per the real problem, starts with 2D arrays and then uses the 3d-given-already-allocated-data form of the Mat constructor to affect a reshape by giving a 3d slice view of the 2d matrices to satisfy copyTo (as both source and arguments will have the same dimensions 3x4x1 (as given by aslicesizes below)
int nRows = 3;
int nCols = 4;
int nPlanes = 2;
int aslicesizes[3] = {nRows,nCols,1};
int a3DMsizes[3] = {nRows,nCols,nPlanes};
Mat OneM = Mat::ones(nRows,nCols,CV_8UC1);
Mat MAs3DPlane; // will be generic slice that will hava a 3d slice (plane) view of the 2d matrices (OneM, TwoM) before they are copied into OneTwo3D
// Mat OneM = Mat::ones(3,aslicesizes,CV_8UC1);
Mat TwoM = Mat::ones(nRows,nCols,CV_8UC1)+1;
// Mat TwoM = Mat::ones(3,aslicesizes,CV_8UC1)+1;
Mat OneTwo3D = Mat::zeros(3,a3DMsizes,CV_8UC1); // target 3d array
Mat OneTwo3DPlaneM; // slice of the 3d target array
Range OneTwo3DRanges[] = {Range::all(),Range::all(),Range(0,1)}; // first slice range
OneTwo3DPlaneM = OneTwo3D(OneTwo3DRanges); // first target slice of OneTwo3D
MAs3DPlane = Mat::Mat(3,aslicesizes,CV_8UC1,OneM.data); // source slice of OneM.
MAs3DPlane.copyTo(OneTwo3DPlaneM); // copying OneM slice to first slice
OneTwo3DRanges[2] = Range(1,2); // reset ranges appropriate OneTwo3D's second slice (plane)
OneTwo3DPlaneM = OneTwo3D(OneTwo3DRanges); // set to second slice of OneTwo3d
MAs3DPlane = Mat::Mat(3,aslicesizes,CV_8UC1,TwoM.data);// source slice of TwoM.
MAs3DPlane.copyTo(OneTwo3DPlaneM);// copying TwoM slice to first slice
回答3:
Reshape should work. A sample below.
//make sample 3d mat
Mat img = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 9);
//reshape - creates a new mat header of 1 row without copying data
Mat result = img.reshape ( 0, 1 );
// declare vector and alloc size
std::vector<double> outVector;
outVector.reserve( result.cols );
//copy data
result.copyTo( outVector );