使用 OpenCV 进行奇怪的 uint8_t 转换

Strange uint8_t conversion with OpenCV

我在 OpenCV 的 Matrix class 中遇到了关于将浮点数转换为 uint8_t 的奇怪行为。 似乎带有 Matrix class 的 OpenCV 通过执行 ceil 而不是仅仅截断小数将 float 转换为 uint8_t。

#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;
}

输出:

108
107

您知道为什么要追加以及如何纠正这种行为吗?

谢谢,

奇怪的行为是 cv::MatExpr and Lasy evaluation usage as described 的结果。

实际结果等于:

round(round(121*0.5 + 105*0.25) + 82*0.25) = 108
  • 因为元素类型是UINT8(整数类型)所以使用了四舍五入。
  • 计算顺序是“Lasy evaluation”策略的结果。

使用调试器跟踪计算过程具有挑战性,因为 OpenCV 实现包括运算符重载、模板、宏和函数指针...

实际计算在static void scalar_loop函数中执行

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

例如:src1[x] = 121src2[x] = 105scalar = 0.5

它执行一个内联函数:

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); }

实际四舍五入在saturate_cast:

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

cvRound 使用 SIMD 内部 return _mm_cvtss_si32(t)
相当于:return (int)(value + (value >= 0 ? 0.5f : -0.5f));


Lasy 评估阶段使用 alphabeta 标量构建 MatExpr

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

表达式是递归构建的(很难理解)。

跟随“运算符+”函数(使用调试器):

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

您可以尝试使用调试器跟踪计算过程。
它需要从源代码、在调试配置中构建 OpenCV,并且需要大量耐心...