Hans
Hans

Reputation: 7

openCV: different results when matrix-multiplication with overloaded operator and without

I have a matrix in openCV like this: cv::Mat matrix = cv::Mat::zeros(5, 5, CV_32FC3);

Then, I want to do matrix multiplication in two different ways, first channel-wise and second with overloaded operator.

// VERSION 1:

for (int row_count = 0; row_count < image.rows; row_count++)
{
  for (int col_count = 0; col_count < image.cols; col_count++)
  {
    for (int channel_count = 0; channel_count < 3; channel_count++)
    {
      matrix.at<cv::Vec3b>(row_count, col_count)[channel_count] = some_image.at<cv::Vec3b>(row_count, col_count)[channel_count] * other_image.at<float>(row_count, col_count);
    }
  } 
}

// VERSION 2:

for (int row_count = 0; row_count < image.rows; row_count++)
{
  for (int col_count = 0; col_count < image.cols; col_count++)
  {
    matrix.at<cv::Vec3b>(row_count, col_count) = some_image.at<cv::Vec3b>(row_count, col_count) * other_image.at<float>(row_count, col_count);
  } 
}

Why do I get different results here?

EDIT: (here an example thx@Frank)

int main() {
  cv::Vec3b data(127, 81, 24);
  float ratio = 0.25f;

  cv::Vec3b manual;
  manual[0] = data[0] * ratio;
  manual[1] = data[1] * ratio;
  manual[2] = data[2] * ratio;

  cv::Vec3b overloaded = data * ratio;

  std::cout << manual << " vs " << overloaded << "\n";
}

The output:

[31, 20, 6] vs [32, 20, 6]

Upvotes: 0

Views: 83

Answers (1)

user4442671
user4442671

Reputation:

OpenCV uses cv::saturate_cast<> pretty much everywhere when integral types are involved. You'd think this should only change how overflow and underflow behave, but it also has an impact on how floating point values are coerced.

If you want to be binary compatible with the internal opencv code, you need to sprinkle a bunch of cv::saturate_cast<> when manually doing operations involving integer scalar.

int main() {
  cv::Vec3b data(127, 81, 24);
  float ratio = 0.25f;

  cv::Vec3b manual;
  manual[0] = cv::saturate_cast<uint8_t>(data[0] * ratio);
  manual[1] = cv::saturate_cast<uint8_t>(data[1] * ratio);
  manual[2] = cv::saturate_cast<uint8_t>(data[2] * ratio);

  cv::Vec3b overloaded = data * ratio;

  std::cout << manual << " vs " << overloaded << "\n";
}

This produces: [32, 20, 6] vs [32, 20, 6] as expected.

Details:, according to the documentation: https://docs.opencv.org/4.5.2/db/de0/group__core__utils.html#gab93126370b85fda2c8bfaf8c811faeaf

It perform an efficient and accurate conversion from one primitive type to another.

saturate_cast is a bit of a misleading name because it doesn't just saturate values, it also makes an effort to have as accurate of a cast as possible. Think of it more like a quantize_cast<> instead.

The raw C++ behavior is to round floating point values towards zero when casting them to integers, which is what you want when counting things. On the other hand, cv::saturate_cast<> rounds them to the nearest integer. This is more numerically accurate when dealing with quantization, which is how OpenCV uses integers for the most part.

Upvotes: 4

Related Questions