OpenCV 双矩阵除以标量会产生不正确的结果

OpenCV double matrix division by scalar produces incorrect result

本文关键字:不正确 结果 标量 OpenCV      更新时间:2023-10-16

我很好奇是否有人在将矩阵(双值)与标量(双精度)除以时能够获得正确的结果。我注意到当我试图追踪 MATLAB 中的算法与C++中复制的算法之间的一些不一致的来源时,OpenCV 没有给出正确的(嗯,"精确")的结果。这是我看到的问题的最小示例:

cv::Mat some_matrix(1, 1, CV_64FC1, cv::Scalar::all(95));
cv::Mat some_matrix_div = some_matrix / 235.0;
printf(
        "Expected: %.53gn"
        "OpenCV  : %.53gn",
        some_matrix.at<double>(0,0) / 235.0,
        some_matrix_div.at<double>(0,0) );

运行后我看到

Expected: 0.40425531914893614304773450385255273431539535522460938
OpenCV  : 0.404255319148936198558885735110379755496978759765625

第一个是值应该是什么(以及如果你在C++或 MATLAB 中执行 95/235 的双精度除法,你会得到什么),但第二个是 OpenCV 在使用除法运算符时产生的结果。我尝试在 OpenCV 源代码中追踪问题,但矩阵操作有点复杂,目前我没有很多时间来研究它,所以我想知道是否有其他人遇到过这个问题并知道解决方法?

编辑

我将补充一些说明。

首先,我知道双精度不是精确的数字表示。我所说的"精确"(为什么用引号引起来)的意思是直接执行的双除法(例如打印 95.0/235.0 的结果)与 OpenCV 将矩阵除以标量时所做的并不完全相同,尽管矩阵中的值确实存储为双精度,并且标量确实也被视为双精度。人们会期望这两个结果应该是相同的;也就是说,如果我将一个双精度除以另一个双精度,则结果应与 OpenCV 双精度矩阵除以双精度标量相同。

我也已经尝试在代码中将所有数字常量显式转换为双精度,但没有成功。

虽然在这种情况下差异相对较小(e^-16),但我不确定随着时间的推移,这可能会如何复合成越来越大的错误。这是一个问题。另一个更像是一个小烦恼,误解为什么OpenCV没有做人们直觉期望它做的事情。最后,它可能不会引起任何问题,但如果可以避免奇怪的行为,我显然更喜欢这样,特别是因为它使得计算与 MATLAB 结果的预期结果不匹配时不清楚,因为计算奇怪或因为实际的算法实现问题(这就是我假设的)。

希望这更清楚。

浮点数学本质上是不精确的。 在 x86 平台上,可以使用 FPU(80 位扩展精度)或 SSE/AVX 矢量单元(64 位双精度)计算双精度数字。 此计算的执行位置取决于编译器的选择以及传递给编译器的各种选项。 更糟糕的是,如果编译器用完 80 位寄存器,它会将结果作为 64 位结果"溢出"到内存中。 事实证明,即使对于标量,向量单元对于大多数浮点运算也更快,因此在允许的情况下,编译器通常更喜欢这一点。

如果软件被明确编写为使用 SSE 或 AVX 以获得最大速度,那么它肯定会使用 64 位版本。 OpenCV可能就是这种情况。 OpenCV甚至可以通过首先计算倒数(1.0/235.0),然后将结果乘以每个像素来近似计算,因为这会快得多。

可以尝试的一些方法:

some_matrix.at<double>(0,0) * (1.0 / 235.0)

还可以尝试更改编译器标志以包含-mfpmath=sse -msse2以确保编译器知道您有一个 SSE 单元,并将其用于双精度。

阅读此处,了解这些效果的更详细描述:https://gcc.gnu.org/wiki/x87note

正如@Borgleader在评论中提到的,问题是在计算库中使用 -fast-math 编译器选项进行库编译,而不是我的应用程序。在这种情况下,它不是OpenCV,而是我的发行版中的另一个库,但正是这种差异导致了差异。此问题已通过重建没有标志的库来修复,以获得一致的结果。