Filter OpenCV Mat for NAN values
Asked Answered
U

4

8

Is there a direct way to create a mask where values in a cv::Mat_<double> are compared against NAN?

cv::Mat_<real> mat = ...
cv::Mat_<uchar> mask = (mat == NAN);

does not work because f == NAN is always false, even if f was assigned NAN. And there seems to be no overload of isnan() for matrices.

Ultraconservative answered 20/1, 2017 at 8:36 Comment(0)
C
14

As noted by user pSoLT, if you want to determine which values are NaN, simply compare the matrix with itself. For those elements that are not equal, those would be considered as NaN by the standard definition. You can create a new mask using that logic:

cv::Mat mask = cv::Mat(mat != mat);

mat here would be a matrix that contains NaN values and mask will be a CV_8UC1 (i.e. uchar) type matrix with each element being 0xFF if a value is NaN and 0x00 otherwise.

This post on OpenCV forums may also help: http://answers.opencv.org/question/2221/create-a-mask-for-nan-cells/


Edit (as of April 23, 2020)

As mentioned in the comments as well as one of the answers in this post, the above Boolean expression is buggy and can result in inconsistent behaviour. This is due to certain optimisation decisions that OpenCV makes. Please see this Github issue: https://github.com/opencv/opencv/issues/16465

The solution to this is to use cv::patchNaNs() to address this problem, which converts values that are NaN to a specific number.

To replicate creating the mask in the question, note that patchNaNs performs an in-place replacement of the values, so you would have to make two copies of the image, use the patchNaNs to set the NaN values to a distinct value per image then check to see the occurrence of both values at the same locations at the same time. In other words:

cv::Mat mat1 = mat.clone();
cv::Mat mat2 = mat.clone();
cv::patchNaNs(mat1, 128);
cv::patchNaNs(mat2, 200);
cv::Mat mask = mat1 == 128 & mat2 == 200;

mask will give you the results as expected in the original version of this answer.

Counterstatement answered 20/1, 2017 at 8:47 Comment(2)
There is a bug in OpenCV which causes this operation to result in inconsistent behaviour. See github.com/opencv/opencv/issues/16465Chargeable
Definitely aware of this @AndrewMarshall. patchNANs was introduced for this very reason. There's an answer already here for that but I'll update mine for closure. I just haven't had the time.... Given what's currently going on right now!Counterstatement
U
3

I am posting this answer to be a reference for those who may be searching for how to filter out the nan's from a matrix.

Although it may not be the precise answer to your question, if you want to filter out those nan indices, there is a quick and easy way to do it.

In my case, I had a zero by zero division which ended up nans in my float matrix. To set those nan's to another value (in my case 0), the following method can be used:

cv::patchNaNs(mat, 0.0);

This method finds and replaces the nan indices with the given value.

Upholster answered 19/3, 2020 at 3:34 Comment(0)
A
1

If you want to test value for NaN simply compare it with itself. According to standard NaN is not equal to any other number, including itself (fValue != fValue) should be true for NaN.

Antaeus answered 20/1, 2017 at 8:42 Comment(0)
E
1

According to the github issue, a workaround is to use 255 - (mat == mat) instead of mat != mat (this should work in general, but is a bug currently). rayryeng's double-patch approach is more robust, but brings a significant performance drawback.

Here a minimal working example:

#include <iostream>
#include <string>


#include <opencv2/opencv.hpp>

cv::Mat1b is_nan(cv::Mat mat)
{
    return 255 - (mat == mat);
}


int main()
{
    cv::Mat mat(5, 7, CV_32FC1);
    cv::randu(mat, cv::Scalar(-1), cv::Scalar(1));
    mat.at<float>(0,0) = std::nan("");
    mat.at<float>(4,5) = std::nan("");
    
    std::cout << mat << std::endl;
    std::cout << is_nan(mat) << std::endl;
    return 0;
}

output

[nan, -0.60148162, -0.19788112, 0.62877017, -0.12573405, -0.5024206, 0.54621011;
 0.52418745, -0.38441104, 0.40486339, -0.043105587, 0.58438003, -0.82831377, -0.8498795;
 -0.67315322, -0.40044156, 0.81130785, 0.41937166, -0.70057499, 0.53087986, -0.75143719;
 -0.9925428, 0.10302717, 0.99639863, -0.68201572, -0.6776439, -0.92362136, -0.14827734;
 0.69225526, 0.77520895, -0.47057521, -0.4584339, 0.9053328, nan, 0.62043273]
[255,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0,   0,   0;
   0,   0,   0,   0,   0, 255,   0]
Empower answered 9/7, 2020 at 12:21 Comment(3)
I would expect so, but did not check. Is there any specific reason you want to go a special way in a buggy environment?Empower
~ meant just for legibility. And no, I am not happy about this inconsistent "optimization", OpenCV should use -ffast-math or some preprocessor macro to enable it explicitly.Tound
I completely agree. At least the missing NAN support of opencv should be mentioned more explicitly (I read it once, but don't remember where). There are a bunsh of other functions which do not work with NANs either. For Instance: GaussianBlur or minMaxLocEmpower

© 2022 - 2024 — McMap. All rights reserved.