使用 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] = 121
、src2[x] = 105
和 scalar = 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 评估阶段使用 alpha
和 beta
标量构建 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 的 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] = 121
、src2[x] = 105
和 scalar = 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 评估阶段使用 alpha
和 beta
标量构建 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,并且需要大量耐心...