2

Error:

what(): OpenCV(4.2.0) ../modules/core/src/arithm.cpp:691: error: (-5:Bad argument) When the input arrays in add/subtract/multiply/divide functions have different types, the output array type must be explicitly specified in function 'arithm_op'

Code:

// cx is a cv::Mat containing a 1280x720x1 matrix of floats

// what i want is an output of 0.0 where cx <= 0.5 and (cx-0.5)*2 otherwise

auto c0 = (cx > 0.5).mul((cx - 0.5) * 2);

I tried:

auto c0 = (cx > 0.5).mul((cx - 0.5) * 2, CV_32F);

Which didn't work.

AbdelAziz AbdelLatef
  • 3,650
  • 6
  • 24
  • 52
wuxiekeji
  • 1,772
  • 2
  • 15
  • 22
  • How do you want to subtract a float value from a matrix? – AbdelAziz AbdelLatef May 22 '21 at 20:26
  • @AbdelAzizAbdelLatef That should broadcast across the matrix. I don't think that's the issue, the issue is the mask (cx > 0.5) is an int value and I want to multiple the int 0 or 1 by the float on the right side. It says "the output array type must be explicitly specified" but I'm not sure how to explicitly specify it. – wuxiekeji May 24 '21 at 04:13

1 Answers1

3

The main issue is that the type of (cx > 0.5) is CV_8U and not CV_32F.

Take a look at the following code sample:

//Initialize 3x3 matrix for example
cv::Mat cx = (cv::Mat_<float>(3, 3) << 5.0f, 0, 0, 0, 5.0f, 0, 0, 0, 5.0f);

//The type of "logical matrix" result of (cx > 0.5f) is UINT8 (255 where true).
cv::Mat cx_above05 = (cx > 0.5f);

I am using Image Watch and Visual Studio 2017 for convenience.

Here is the result of cv::Mat cx_above05 = (cx > 0.5f):

enter image description here

As you can see, the type is UINT8 and the value is 255 where cx>0.5 and 0 where not.


You can use convertTo for converting the type to CV_32F and dividing by 255:

cx_above05.convertTo(cx_above05 , CV_32F, 1.0/255.0);  //Convert type to float (1.0f where true).

Result:

enter image description here

As you can see, the type is FLOAT32, and 255 values goes to 1.0f


Complete code sample:

cv::Mat cx = (cv::Mat_<float>(3, 3) << 5.0f, 0, 0, 0, 5.0f, 0, 0, 0, 5.0f);  //Initialize 3x3 matrix for example      
cv::Mat cx_above05 = (cx > 0.5f); //The type of "logical matrix" result of (cx > 0.5f) is UINT8 (255 where true).
cx_above05.convertTo(cx_above05 , CV_32F, 1.0/255.0); //Convert type to float (1.0f where true).
cv::Mat c0 = cx_above05.mul((cx - 0.5f) * 2.0f);

Result:
enter image description here


Instead of multiplying by the mask we can use the trick described in OpenCV pow documentation:

cv::Mat cx = (cv::Mat_<float>(3, 3) << 5.0f, 0, 0, 0, 5.0f, 0, 0, 0, 5.0f);  //Initialize 3x3 matrix for example      
cv::Mat mask = (cx <= 0.5f); //The type of "logical matrix" result of (cx <= 0.5f) is UINT8 (255 where true).
cv::Mat c0 = (cx - 0.5f) * 2.0f;
cv::subtract(c0, c0, c0, mask);

I found a more elegant solution here (using setTo):

cv::Mat c0 = (cx - 0.5f) * 2.0f;
c0.setTo(0, cx <= 0.5f);

One line solution:

The type of the expression ((cx - 0.5f) * 2.0f) is cv::MatExpr, and we can cast it to cv::Mat:

cv::Mat c0 = ((cv::Mat)((cx - 0.5f) * 2.0f)).setTo(0, cx <= 0.5f);
Rotem
  • 30,366
  • 4
  • 32
  • 65
  • Thanks! I'm wondering at this point whether iterating over all values in the matrix would be more efficient since if the mask is 0 I can avoid the floating point computation in the first place ... I'm not sure if OpenCV does any vectorization magic though (?) – wuxiekeji May 24 '21 at 04:15
  • A simple loop is probably going to be faster, because the code is simple enough for the complier to vectorize. You can also try using [Eigen](https://eigen.tuxfamily.org/index.php?title=Main_Page) library, but I don't know if there is an elegant solution for implementing the condition `(cx > 0.5f)`. OpenCV can be built with Eigen, and there is a change that OpenCV mathematical expression does vectorization magics. But `setTo(0, cx <= 0.5f)` most likely reduces the performance. – Rotem May 24 '21 at 05:44