2

Say I need to find a specific element in a cv::Mat which can be a row vector in my case (though for more general case Mat can be more than one dimension). The data type of the target can be as simple as char, int, double etc.

There is an existing post: How to find if an item is present in a std::vector? which explained how to find the element in a std::vector. Therefore, one way to do this can be: 1) converting the cv::Mat to std::vector; 2) used the method in the post to find the element. However, I need to do this searching operation hundreds of times per row. When I have hundreds of rows need to be processed, the performance can be an issue.

I am wondering how's the performance of above method (convert + search) and is there any more efficient way to do this (maybe search element directly using cv::Mat without conversion)?

p.s: Here is a post for Converting a row of cv::Mat to std::vector

Community
  • 1
  • 1
linzhang.robot
  • 359
  • 1
  • 8
  • 23
  • what exactly do you mean by 'find element' ? do you need a binary answer ( like does it contain '17' ? ) ? a list of indices ? x,y positions ? an occurrence count ('where i==17) ? – berak Sep 14 '14 at 18:39
  • I wanted the x,y position (in my case only 1 dimensional position) of the target in the mat. @marol gave a binary version of answer which can be modified to get the index easily. – linzhang.robot Sep 14 '14 at 18:58

1 Answers1

4

Combining those two answers and depending on the mat type (here CV_64F) you get:

bool findValue(const cv::Mat &mat, double value) {
    for(int i = 0;i < mat.rows;i++) {
        const double* row = mat.ptr<double>(i);
        if(std::find(row, row + mat.cols, value) != row + mat.cols)
            return true;
    }
    return false;
}

(see find docs for more information). Of course first converting mat row to a vector and then using std::find on that vector is slower than using find directly on pointer to a row array.

EDIT: After some more research, it is not quite hard to develop a generic version:

template <class T>
bool findValue(const cv::Mat &mat, T value) {
    for(int i = 0;i < mat.rows;i++) {
        const T* row = mat.ptr<T>(i);
        if(std::find(row, row + mat.cols, value) != row + mat.cols)
            return true;
    }
    return false;
}

I tested it on more complex data types:

cv::Mat matDouble = cv::Mat::zeros(10, 10, CV_64F);
cv::Mat matRGB = cv::Mat(10, 10, CV_8UC3, cv::Scalar(255, 255, 255));

std::cout << findValue(matDouble, 0.0) << std::endl;
std::cout << findValue(matDouble,1.0) << std::endl;
std::cout << findValue(matRGB, cv::Scalar(255, 255, 255)) << std::endl;
std::cout << findValue(matRGB, cv::Scalar(255, 255, 254)) << std::endl;

And what surprised me the output is:

1
0
0 // should be 1, right?
0

The problem is with the cv::Scalar size structure. No matter of the version of the constructor we're using (ie oone, two, three or four arguments) the size is... constant. This is no so surprising cause this is still the same structure, on my machine the size is 32 bytes (by default cv::Scalar is type of double so on my machine double is 8 bytes and 4 * 8 = 32). So the find goes strictly wrong, cause it assumes size of the element in the array as 32 bytes and it should be 3 bytes.

So don't use std::find with cv::Scalar! However it works with the primitive data types remarkable well and efficient.

EDIT2 (after berak's comment):

Yes, you can use cv::Vec3b with find and it seems working well although it have not done more testing than simply correct test:

cv::Mat matRGB = cv::Mat(10, 10, CV_8UC3, cv::Scalar(255, 255, 255));

std::cout << findValue(matRGB, cv::Vec3b(255, 255, 255)) << std::endl;
std::cout << findValue(matRGB, cv::Vec3b(255, 255, 254)) << std::endl;

(still you have to use Scalar in the Mat constructor, but it does not matter and the Mat is properly initialized). Now the output is as expected:

1
0
marol
  • 4,034
  • 3
  • 22
  • 31
  • did you try: `findValue(matRGB, Vec3b(255, 255, 255))` ? Scalar is the wrong data type for the ptr<> template – berak Sep 14 '14 at 19:11