Zimm3r Zimm3r - 2 months ago 27
Java Question

OpenCV Mat divide answers wrong

I have the following example code that calculates the percentage of red pixels,

import org.opencv.core.*;


public class ImageProcessing {
static
{
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
}

public static void main(String[] args)
{
Mat red = Mat.zeros(5, 1, CvType.CV_8UC1);
Mat sum = Mat.zeros(5, 1, CvType.CV_8UC1); // sum of red, green, and blue

double[] red_data = {1, 1, 1, 1,255};
double[] sum_data = {1,11,12,13,255};
red.put(0, 0, red_data);

sum.put(0, 0, sum_data);

Mat percentage = Mat.zeros(5, 1, CvType.CV_64FC1);

//red.convertTo(red, CvType.CV_64F);
//sum.convertTo(sum, CvType.CV_64F);
Core.divide(red, sum, percentage, CvType.CV_64FC1);



System.out.println("Results");
for (int i = 0; i < 5; i++)
{
System.out.printf("%d: %10f / %10f = %10f (%10f) \n", i+1, red.get(i, 0)[0],
sum.get(i, 0)[0],
percentage.get(i, 0)[0],
red.get(i, 0)[0]/sum.get(i, 0)[0] );

}

}
}


Running it I expect the results in ()s however I obviously get strange results. I thought at first it had to do with integer division ( see: divide two matrices in opencv ). However converting them to 64bit floats (the commented out lines). Only gives other strange results.

Without float conversion

Results
1: 1.000000 / 1.000000 = 6.000000 ( 1.000000)
2: 1.000000 / 11.000000 = 1.000000 ( 0.090909)
3: 1.000000 / 12.000000 = 0.000000 ( 0.083333)
4: 1.000000 / 13.000000 = 0.000000 ( 0.076923)
5: 255.000000 / 255.000000 = 6.000000 ( 1.000000)

Process finished with exit code 0


With conversion to 64 bit floats

Results
1: 1.000000 / 1.000000 = 6.000000 ( 1.000000)
2: 1.000000 / 11.000000 = 0.545455 ( 0.090909)
3: 1.000000 / 12.000000 = 0.500000 ( 0.083333)
4: 1.000000 / 13.000000 = 0.461538 ( 0.076923)
5: 255.000000 / 255.000000 = 6.000000 ( 1.000000)


I do notice there are consistent results where there is a clear mapping (1.0 maps to 6.0 for both 1.0 / 1.0 and 255 / 255 but I can't find a pattern that would cause this).

Answer

It appears that you are using this overload of the divide function:

public static void divide(Mat src1, Mat src2, Mat dst, double scale)

Parameters:

  • src1 - First source array.
  • src2 - Second source array of the same size and type as src1.
  • dst - Destination array of the same size and type as src2.
  • scale - Scalar factor.

Which performs the operation dst(I) = saturate(src1(I) * scale / src2(I)).

NB: Note the actual meaning of the 4th parameter, where you pass Core.CV_64FC1.

With a little bit of research, you can find out that the value of the constant CV_64FC1 is 6.

This means that in reality, the calculations being performed are:

1 * 6 / 1
1 * 6 / 11
1 * 6 / 12
1 * 6 / 13
255 * 6 / 255

The rest is straightforward.

Note the requirement in documentation that the output array should be "of the same size and type as src2." In the first case, when the two input arrays are CV_8UC1, the second condition is not met, hence a new array is allocated.

Since in the first case the output is integer array, you see the rounded results. In the second case it's floating point, so no rounding is done.