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...