【问题标题】:Strange uint8_t conversion with OpenCV使用 OpenCV 进行奇怪的 uint8_t 转换
【发布时间】:2021-12-12 15:11:44
【问题描述】:

我在 OpenCV 中的 Matrix 类中遇到了关于将浮点数转换为 uint8_t 的奇怪行为。 似乎带有 Matrix 类的 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

您知道为什么会出现这种附加以及如何纠正这种行为吗?

谢谢,

【问题讨论】:

  • “矩阵表达式”根据 OpenCV 语义进行评估,这显然意味着整数矩阵乘以浮点标量,再次得到一个 int 矩阵,而不是浮点矩阵。 -- 乘积将是 (60.5, 26.25, 20.5) 作为浮点数,但它们可能单独四舍五入为整数,即 (61, 26, 21) 和 108。 -- 事实上你可以at&lt;uint8_t&gt;该矩阵和 not 得到垃圾意味着中间结果和最终结果是 uint8,而不是 float
  • 感谢克里斯托夫的回答。是的,矩阵保持为 int,预计 int 转换会截断小数,因此 60.5、26.25、20.5 浮点值应该是 (60, 26, 20) 给出 106。谢谢。

标签: c++ opencv floating-point integer


【解决方案1】:

奇怪的行为是cv::MatExprLasy evaluation 使用的结果,如here 所述。

实际结果等于:

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

使用调试器跟踪计算过程具有挑战性,因为 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 &gt;= 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,并且需要大量的耐心...

【讨论】:

  • 非常感谢您的深入解答!
  • 欢迎您。我认为你应该尝试调试 OpenCV 代码,而不是仅仅相信我的答案是正确的。
  • 使用cv::Mat x = (cv::Mat)(m1 * 0.5) + (cv::Mat)(m2 * 0.25) + (cv::Mat)(m3 * 0.25);时,结果为106
猜你喜欢
  • 2014-08-13
  • 2018-08-31
  • 2012-10-05
  • 2010-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-15
相关资源
最近更新 更多