Multi-Dimensional Data in a Matrix in Opencv with C++

Multi-Dimensional data in a Matrix in OpenCV with C++

Here is a short example from the NAryMatIterator documentation; it shows how to create, populate, and process a multi-dimensional matrix in OpenCV:

void computeNormalizedColorHist(const Mat& image, Mat& hist, int N, double minProb)
{
const int histSize[] = {N, N, N};

// make sure that the histogram has a proper size and type
hist.create(3, histSize, CV_32F);

// and clear it
hist = Scalar(0);

// the loop below assumes that the image
// is a 8-bit 3-channel. check it.
CV_Assert(image.type() == CV_8UC3);
MatConstIterator_<Vec3b> it = image.begin<Vec3b>(),
it_end = image.end<Vec3b>();
for( ; it != it_end; ++it )
{
const Vec3b& pix = *it;
hist.at<float>(pix[0]*N/256, pix[1]*N/256, pix[2]*N/256) += 1.f;
}

minProb *= image.rows*image.cols;
Mat plane;
NAryMatIterator it(&hist, &plane, 1);
double s = 0;
// iterate through the matrix. on each iteration
// it.planes[*] (of type Mat) will be set to the current plane.
for(int p = 0; p < it.nplanes; p++, ++it)
{
threshold(it.planes[0], it.planes[0], minProb, 0, THRESH_TOZERO);
s += sum(it.planes[0])[0];
}

s = 1./s;
it = NAryMatIterator(&hist, &plane, 1);
for(int p = 0; p < it.nplanes; p++, ++it)
it.planes[0] *= s;
}

Also, check out the cv::compareHist function for another usage example of the NAryMatIterator here.

Print multidimensional Mat in OpenCV (C++)

To print n-dim matrix you could use Matrix slice. Since 2d matrices are stored row by row, 3d matrices plane by plane and so on, you could use code:

cv::Mat sliceMat(cv::Mat L,int dim,std::vector<int> _sz)
{
cv::Mat M(L.dims - 1, std::vector<int>(_sz.begin() + 1, _sz.end()).data(), CV_8UC1, L.data + L.step[0] * 0);
return M;
}

To perform mat slice.For more dimensions you should make more slices. Example shows 3 and 4 dimension matrices:

std::cout << "3 dimensions" << std::endl;

std::vector<int> sz = { 3,3,3 };

cv::Mat L;
L.create(3, sz.data(), CV_8UC1);
L = cv::Scalar(255);

std::cout<< sliceMat(L, 1, sz);

std::cout << std::endl;
std::cout <<"4 dimensions"<< std::endl;
sz = { 5,4,3,5 };
L.create(4, sz.data(), CV_8UC1);
L = cv::Scalar(255);
std::cout << sliceMat(sliceMat(L, 1, sz),2, std::vector<int>(sz.begin() + 1, sz.end()));

end result screen

Sample Image

Read multi-dimensional array from caffe in opencv (C++)

In first step we need to get a pointer to OpenCV Mat object, you can do this by the below command.(I assume that data that represent your data is primarily float and consider probability Mat is prob which we get this Mat from caffe)

float* p = (float*)(prob.data);

This pointer will points to the where data is reside in memory. So for example if we want to get access to the element in (1,3,7,7) location we can do this operation like this:

int S= sizeof(float);    
float val = p[(
7*p.step[3]/S + //forth dimension
7*p.step[2]/S + //third dimension
3*p.step[1]/S //second dimension
)]
//first dimension is not needed, because it is decoded in address of p
//and if you have any higher number than 1 in first dimension you need to add it to the above command

So for traversing in the probability matrix you can do that like the below:

auto S=sizeof(float);
for (int d2 = 0; d2 < 129; ++d2) {
for (int d3 = 0; d3 < 129; ++d3) {
for (int d4 = 0; d4 < 10; ++d4) {
float val = p[(d2*prob.step[3]/S + d3*prob.step[2]/S + d4* prob.step[1]/S)];
}
}
}

Saving multi-dimensional matrix in opencv c++

Your Mat seems to be what I think of as a 2D Mat with N channels. just as a typical image is a 2D Mat 3 channels (RGB). THe following code will store all of channels 0, followed by all of channel 1 etc, up to channel N-1:

#include <fstream>
#include <vector>

void writeNoise(string filename, Mat noise)
{
int chans = noise.channels();
int sz = noise.rows; // should check that noise.cols is == noise.rows

std::ofstream f(filename, ios::out | ios::binary);

if (!f.is_open())
throw std::ofstream::failure("out directory is not found\n");

std::vector<float> buffer(sz);

for(int c = 0; c < chans; c++)
{
for(int i = 0; i < sz; ++i)
{
float* p = noise.ptr<float>(i) + c;

for(int j = 0; j < sz; ++j)
{
buffer[j] = *p;
p += chans;
}
f.write((const char *) &buffer[0], sizeof(float) * buffer.size());
}
}

f.close();
}

if you want to access channel 15 at (row = 15, col = 15), given that you don't know the number of channels at compile time (so you can't use at()), you can do this;

float get(cv::Mat m, int row, int col, int chan)
{
float* p = m.ptr<float>(row);
return p[col * m.channels() + chan];
}

/* ... */

cout << get(noise, 15, 15, 15) << endl;

Note that it would be inappropriate to use a get() like that for sequential access to pixels (because it would be very slow compared to accessing the pixels like I did in writeNoise())

How to modify part of the multi-dimensional matrix in openCV?

OpenCV is optimized for 2D matrices. Multidimensional matrix will work, but are rather inefficient and difficult to access.

This example code will show you how to write and read values from an 3D matrix:

#include <opencv2\opencv.hpp>
using namespace cv;

int main()
{
int sizes[] = { 5, 5, 25 };
Mat data(3, sizes, CV_32F);

Mat1f some_matrix(sizes[0], sizes[1]);
randu(some_matrix, 0.f, 100.f); // some random values

// Init data with each plane a constant increasing value
for (int z = 0; z < data.size[2]; ++z)
{
// Set each z-plane to some scalar value
Range ranges[] = { Range::all(), Range::all(), Range(z, z + 1) };
data(ranges) = data.size[2] - z;
}

// Set the n-th z-plane to some_matrix
int z = 0;
for (int r = 0; r < sizes[0]; ++r)
{
for (int c = 0; c < sizes[1]; ++c)
{
data.at<float>(r, c, z) = some_matrix(r, c);
}
}

// Access all slices along z dimension
for (int z = 0; z < data.size[2]; ++z)
{
Range ranges[] = { Range::all(), Range::all(), Range(z, z + 1) };
Mat slice3d(data(ranges).clone()); // with clone slice is continuous, but still 3d
Mat slice(2, &data.size[0], data.type(), slice3d.data);
}

return 0;
}

However, it's far easier and practical to store your 5x5x25 3D matrix as a std::vector<Mat>, where the vector has length 25, and each matrix is a 2D 5x5.

See the code:

#include <opencv2\opencv.hpp>
using namespace cv;

int main()
{
int sizes[] = { 5, 5, 25 };

vector<Mat> data(sizes[2]);
// Init data with each plane a constant increasing value
for (int z = 0; z < sizes[2]; ++z)
{
data[z] = Mat(sizes[0], sizes[1], CV_32F, float(sizes[2] - z));
}

Mat1f some_matrix(sizes[0], sizes[1]);
randu(some_matrix, 0.f, 100.f); // some random values

// Set the n-th z-plane to some_matrix
int z = 0;
data[z] = some_matrix;

return 0;
}

Minimum along a dimension in a Multi Dimensional arrays C++

You can slice the the 3D mat along the z dimension, and use cv::min to compare the slices.

Code:

#include <opencv2\opencv.hpp>
using namespace cv;

int main()
{
int sizes[] = {10, 7, 5};
Mat data(3, sizes, CV_32F);

// Init data with each plane a constant increasing value
for (int z = 0; z < data.size[2]; ++z)
{
Range ranges[] = { Range::all(), Range::all(), Range(z, z + 1) };
data(ranges) = data.size[2] - z;
}

// Compute minimum along 3rd dimension
Mat minmat(data.size[0], data.size[1], data.type(), Scalar(DBL_MAX));

for (int z = 0; z < data.size[2]; ++z)
{
Range ranges[] = { Range::all(), Range::all(), Range(z, z+1) };
Mat slice(data(ranges).clone()); // with clone slice is continuous, but still 3d
Mat slice2d(2, &data.size[0], data.type(), slice.data);

cv::min(slice2d, minmat, minmat);
}

// minmat is a 10x7 mat containing in (y,x) the minimum value along z

return 0;
}

Since OpenCV is better suited for 2d matrices, you should consider using a vector<Mat> instead (where each Mat in the vector is 2d).

This is the same code as above, using vector<Mat>:

#include <opencv2\opencv.hpp>
using namespace cv;

int main()
{
int size_h = 10;
int size_w = 7;
int size_z = 5;

vector<Mat> data(size_z);
// Init data with each plane a constant increasing value
for (int z = 0; z < size_z; ++z)
{
data[z] = Mat(size_h, size_w, CV_32F, Scalar(size_z - z));
}

// Compute minimum along 3rd dimension
Mat minmat(size_h, size_w, CV_32F, Scalar(DBL_MAX));

for (int z = 0; z < size_z; ++z)
{
cv::min(data[z], minmat, minmat);
}

// minmat is a 10x7 mat containing in (y,x) the minimum value along z

return 0;
}

To access each matrix you do: data[index], and to access the a pixel at a given row and col, you do: data[index].at<float>(row, col);

How to access a sub-matrix of a multi-dimensional matrix in OpenCV function?

Answer 1

If mat A is 3D 100 rows x 100 cols x 100 planes x n channels, you can use Mat Mat::operator()(const Range* ranges) const like this::

std::vector<Range> ranges;
ranges.push_back(Range(10,20));
ranges.push_back(Range(20,30));
ranges.push_back(Range(30,40));

Mat B = A(&ranges[0]);

B will be 10x10x10 x n channels


Answer 2

If however mat A is 100 rows x 100 cols x 100 channels, that is just a 100 channel 2D mat. You can do this:

Mat B = A(Range(10,20), Range(20,30));  // B will be 10x10x100

Now you need to select channels 30:40 from B, you would need to use void mixChannels(const Mat* src, size_t nsrcs, Mat* dst, size_t ndsts, const int* fromTo, size_t npairs):

Mat C(10, 10, CV_MAKETYPE(A.depth(), 10));
int from_to[] = { 10,0, 11,1, 12,2, 13,3, 14,4,
15,5, 16,6, 17,7, 18,8, 19,9};
mixChannels(&B, 1, &C, 1, fromTo, 10);

C will then be 10 rows x 10 cols x 10 channels as required. This is a bit messy but I don't know a better way.

how to convert multidimensional c++ array into openCV form?

Buddy, you have to do basic research by looking at manual or searching web before asking a question. If you don't do that, it is very difficult for us to give you an understandable answer.

Accessing elements of cv::Mat or matrix conversion is really basic stuff in OpenCV. Please refer to cheatsheet if you are that busy.

To answer your question, I don't think there is a short cut to convert multidimensional array to Mat. You should loop through elements to copy the data into Mat. That's why many people use single dimensional array to speed things up for faster image processing.

access sub-matrix of a multidimensional Mat in OpenCV

You can easily access sub-matrix of 2D cv::Mat using functions rowRange, colRange or even

cv::Mat subMat = originalMat(cv::Rect(x,y,width,height));

Also, the number of channels in a matrix, that you can define in the matrix constructor, can be used as the third dimension (but it is limited to 256 or 512 i think).

There is also the templated cv::Mat_ class that you can adapt to fit your purpose

[edit]

I have checked the constructor for >2 dimensional matrices. When you run it the rows and cols field of Mat are set to -1. The actual matrix size is store in Mat::size as an array of int.
For matrix of dimensions >2 you cannot use the submatrices constructors using a cv::Rect or rowRange/colRange.

I'm afraid you have to do a bit of work to extract submatrices for dim>2, working directly with the row data. But you can use the information stored in Mat::step which tells you the layout of the array. This is explained in the official documentation.



Related Topics



Leave a reply



Submit