background
I think the reason opencv
does not supply a functionality like that is the way cv::Mat
is organized. The memory address of the beginning of each dimension is equidistantly spaced (see cv::Mat.step
attribute).
To cite from the docs cv::Mat Class Reference
The class Mat represents an n-dimensional dense numerical
single-channel or multi-channel array. It can be used to store real or
complex-valued vectors and matrices, grayscale or color images, voxel
volumes, vector fields, point clouds, tensors, histograms (though,
very high-dimensional histograms may be better stored in a SparseMat
). The data layout of the array M is defined by the array M.step[], so
that the address of element (i0,...,iM.dims−1), where 0≤ik<M.size[k],
is computed as:
addr(Mi0,...,iM.dims−1)=M.data+M.step[0]∗i0+M.step1∗i1+...+M.step[M.dims−1]∗iM.dims−1
A cv::Mat
returned from a boolean indexing operation, could not be represented in this layout anymore - hence copying is necessary.
self implemented solution
The following solution supports booolean indexing
(select by boolean values along a dimension) and list indexing
(select specific indices along a dimension) and works for cv::Mat
with an arbitrary number of dimensions
. It is not as flexible as numpy
, as it only works along one axis/dimension
.
code
#include <iostream>
#include <opencv2/core/core.hpp>
/**
* Reduce cv::Mat to certain elements in one dimension, which listed by index in listInds.
*
* @param[in] src source mat
* @param[in] dim dimension index, along which to apply boolInds
* @param[in] listInds index of the elements to be selected
* @returns mat reduced to selected elements along dimension dim
*/
cv::Mat mat_list_indexing(cv::Mat &src, const int dim, const std::vector<int> &listInds)
{
int *ns = new int[src.dims]; // ns: new size
std::vector<int> size;
for (int ii=0; ii< src.dims; ++ii)
{
ns[ii] = src.size[ii];
}
ns[dim] = listInds.size();
cv::Mat dst(src.dims, ns, src.type());
// loop over all indices of dst
int dd;
std::vector<int> index (src.dims, 0);
while (true)
{
int srcOffset = 0;
int dstOffset = 0;
for (int ii=0; ii<src.dims; ++ii)
{
dstOffset += dst.step[ii] * index[ii];
if (ii != dim)
{
srcOffset += src.step[ii] * index[ii];
}
else
{
srcOffset += src.step[ii] * listInds[index[ii]];
}
}
memcpy(dst.data + dstOffset, src.data + srcOffset, src.elemSize());
// update index
dd = src.dims - 1;
while (index[dd] == ns[dd] - 1)
{
--dd;
if (dd < 0)
{
// break;
delete [] ns;
return dst;
}
}
index[dd] += 1;
for (int ii=dd+1; ii<src.dims; ++ii)
{
index[ii] = 0;
}
}
}
/**
* Reduce cv::Mat to certain elements in one dimension, which are marked by a boolean single row cv::Mat.
* https://stackoverflow.com/questions/21749348/accessing-matrix-elements-in-opencv-c-in-the-style-of-numpy-python
*
* @param[in] src source mat
* @param[in] dim dimension index, along which to apply boolInds
* @param[in] boolInds boolean indices to select elements along dimension dim; single row or single col mat
* @returns mat reduced to selected elements along dimension dim
*/
cv::Mat mat_boolean_indexing(cv::Mat &src, int dim, cv::Mat1b boolInds)
{
boolInds = boolInds.reshape(0, 1);
std::vector<int> listInds;
for (size_t ii=0; ii < boolInds.cols; ++ii)
{
if (boolInds(0, ii) > 0)
{
listInds.push_back(ii);
}
}
return mat_list_indexing(src, dim, listInds);
}
void test_boolean_indexing_2d()
{
std::cout << "\n\n***************** test_boolean_indexing_2d() *****************\n\n\n";
// init
cv::Mat1f src = (cv::Mat1f(3, 4) <<
0, 1, 2.2, NAN,
-.4, .5, .6, 7,
-70, NAN, 8.8, 9
);
cv::Mat1b boolInds {true, false, true, true};
boolInds = boolInds.reshape(0, 1);
// test indexing
cv::Mat1f dst = mat_boolean_indexing(src, 1, boolInds);
// cout
std::cout << "src:\n" << src << "\n";
std::cout << "boolInds: " << boolInds << " along dim 1\n";
std::cout << "dst:\n" << dst << "\n";
}
void test_boolean_indexing_3d()
{
std::cout << "\n\n***************** test_boolean_indexing_3d() *****************\n\n\n";
// init
const int sz[] = {2, 4, 3};
cv::Mat1f src(3, sz);
for (int i0=0; i0<sz[0]; ++i0)
{
for (int i1=0; i1<sz[1]; ++i1)
{
for (int i2=0; i2<sz[2]; ++i2)
{
src.at<float>(i0, i1, i2) = 100*i0 + 10*i1 + i2;
}
}
}
cv::Mat1b boolInds {true, false, false, true};
boolInds = boolInds.reshape(0, 1);
// test indexing
cv::Mat1f dst = mat_boolean_indexing(src, 1, boolInds);
// cout
std::cout << "\nsrc:\n";
for (int i0=0; i0<sz[0]; ++i0)
{
for (int i1=0; i1<sz[1]; ++i1)
{
for (int i2=0; i2<sz[2]; ++i2)
{
std::cout << "src(" << i0 <<", " << i1 << ", " << i2 << ")=" << src(i0, i1, i2) << "\n";
}
}
}
std::cout << "boolInds: " << boolInds << " along dim 1\n";
std::cout << "dst:\n";
for (int i0=0; i0<dst.size[0]; ++i0)
{
for (int i1=0; i1<dst.size[1]; ++i1)
{
for (int i2=0; i2<dst.size[2]; ++i2)
{
std::cout << "dst(" << i0 <<", " << i1 << ", " << i2 << ")=" << dst(i0, i1, i2) << "\n";
}
}
}
}
void test_list_indexing_2d()
{
std::cout << "\n\n***************** test_list_indexing_2d() *****************\n\n\n";
// init
cv::Mat1f src = (cv::Mat1f(4, 2) <<
0, 1,
-.4, .5,
-70, NAN,
10, 100
);
std::vector<int> listInds {3, 2};
// test indexing
cv::Mat1f dst = mat_list_indexing(src, 0, listInds);
// cout
std::cout << "src:\n" << src << "\n";
std::cout << "listInds: {";
for (size_t ii=0; ii<listInds.size(); ++ii)
{
std::cout << listInds[ii] << "; ";
}
std::cout << "} along dim 0\n";
std::cout << "dst:\n" << dst << "\n";
}
int main()
{
test_boolean_indexing_2d();
test_boolean_indexing_3d();
test_list_indexing_2d();
}
output
***************** test_boolean_indexing_2d() *****************
src:
[0, 1, 2.2, nan;
-0.40000001, 0.5, 0.60000002, 7;
-70, nan, 8.8000002, 9]
boolInds: [ 1, 0, 1, 1] along dim 1
dst:
[0, 2.2, nan;
-0.40000001, 0.60000002, 7;
-70, 8.8000002, 9]
***************** test_boolean_indexing_3d() *****************
src:
src(0, 0, 0)=0
src(0, 0, 1)=1
src(0, 0, 2)=2
src(0, 1, 0)=10
src(0, 1, 1)=11
src(0, 1, 2)=12
src(0, 2, 0)=20
src(0, 2, 1)=21
src(0, 2, 2)=22
src(0, 3, 0)=30
src(0, 3, 1)=31
src(0, 3, 2)=32
src(1, 0, 0)=100
src(1, 0, 1)=101
src(1, 0, 2)=102
src(1, 1, 0)=110
src(1, 1, 1)=111
src(1, 1, 2)=112
src(1, 2, 0)=120
src(1, 2, 1)=121
src(1, 2, 2)=122
src(1, 3, 0)=130
src(1, 3, 1)=131
src(1, 3, 2)=132
boolInds: [ 1, 0, 0, 1] along dim 1
dst:
dst(0, 0, 0)=0
dst(0, 0, 1)=1
dst(0, 0, 2)=2
dst(0, 1, 0)=30
dst(0, 1, 1)=31
dst(0, 1, 2)=32
dst(1, 0, 0)=100
dst(1, 0, 1)=101
dst(1, 0, 2)=102
dst(1, 1, 0)=130
dst(1, 1, 1)=131
dst(1, 1, 2)=132
***************** test_list_indexing_2d() *****************
src:
[0, 1;
-0.40000001, 0.5;
-70, nan;
10, 100]
listInds: {3; 2; } along dim 0
dst:
[10, 100;
-70, nan]
bugs
I just developed this and it is only tested on these 3 examples. Some things are not yet tested, for instance multi channel cv::Mats
. I will keep this answer updated and document changes in this section. Please report bugs in the comments.