Home > Enterprise >  Strange uint8_t conversion with OpenCV
Strange uint8_t conversion with OpenCV

Time:10-28

I have encountered a strange behavior from the Matrix class in OpenCV regarding the conversion float to uint8_t. It seems that OpenCV with the Matrix class converts float to uint8_t by doing a ceil instead of just truncating the decimal.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>

int main() {

  cv::Mat m1(1, 1, CV_8UC1);
  cv::Mat m2(1, 1, CV_8UC1);
  cv::Mat m3(1, 1, CV_8UC1);
  m1.at<uint8_t>(0, 0) = 121;
  m2.at<uint8_t>(0, 0) = 105;
  m3.at<uint8_t>(0, 0) =  82;
  
  cv::Mat x = m1 * 0.5   m2 * 0.25   m3 * 0.25;
  printf("%d \n", x.at<uint8_t>(0, 0));
  
  uint8_t r = 121 * 0.5   105 * 0.25   82 * 0.25;
  printf("%d \n\n", r);

  return 0;
}

Output:

108
107

Do you know why this append and how to correct this behavior ?

Thank you,

CodePudding user response:

The strange behavior is a result of cv::MatExpr and Lasy evaluation usage as described here.

The actual result equals:

round(round(121*0.5   105*0.25)   82*0.25) = 108
  • The rounding is used because the element type is UINT8 (integer type).
  • The computation order is a result of the "Lasy evaluation" strategy.

Following the computation process using the debugger is challenging, because OpenCV implementation includes operator overloading, templates, macros and pointer to functions...

The actual computation is performed in static void scalar_loop function in

dst[x] = op::r(src1[x], src2[x], scalar);

When for example: src1[x] = 121, src2[x] = 105 and scalar = 0.5.

It executes an inline function:

inline uchar c_add<uchar, float>(uchar a, uchar b, float alpha, float beta, float gamma)
{ return saturate_cast<uchar>(CV_8TO32F(a) * alpha   CV_8TO32F(b) * beta   gamma); }

The actual rounding is in saturate_cast:

template<> inline uchar saturate_cast<uchar>(float v)        { int iv = cvRound(v); return saturate_cast<uchar>(iv); }

cvRound uses an SIMD intrinsic return _mm_cvtss_si32(t)
It's equivalent to: return (int)(value (value >= 0 ? 0.5f : -0.5f));


The Lasy evaluation stages builds MatExpr with alpha and beta scalars.

cv::Mat x = m1 * 0.5   m2 * 0.25   m3 * 0.25;  //m1 = 121, m2 = 105, m3 = 82

The expression is built recursively (hard to follow).

Following the "operator " function (using the debugger):

MatExpr operator   (const MatExpr& e1, const MatExpr& e2)
{
    MatExpr en;
    e1.op->add(e1, e2, en);
    return en;
}

State 1:
e1.a data = 121 (UINT8)
e1.b (NULL)
e1.alpha = 0.5
e1.beta  = 0

e2.a data = 105 (UINT8)
e1.b (NULL)
e1.alpha = 0.25
e1.beta  = 0

Result:
en.a data = 121 (UINT8)
en.b data = 105 (UINT8)
en.alpha = 0.5
en.beta = 0.25



State 2:
e1.a data = 121 (UINT8)
e1.b data = 105 (UINT8)
e1.alpha = 0.5
e1.beta = 0.25


e2.a data = 82 (UINT8)
e1.b (NULL)
e1.alpha = 0.25
e1.beta  = 0

en.a data = 87 (UINT8)   <--- 121*0.5   105*0.25 = 86.7500 rounded to 87
en.b data = 82 (UINT8)
en.alpha = 1
en.beta = 0.25



Stage 3: (in MatExpr::operator Mat() const):
m data = 108 (UINT8)   <--- 87*1   82*0.25 = 87   20.5 = 107.5 rounded to 108

You may try to follow the computation process using the debugger.
It requires building OpenCV from sources, in Debug configuration, and a lot of patient...

  • Related