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

Posted

技术标签:

【中文标题】标量的OpenCV双矩阵除法产生不正确的结果【英文标题】:OpenCV double matrix division by scalar produces incorrect result 【发布时间】:2015-01-05 21:03:30 【问题描述】:

我很好奇在用标量(双精度)除矩阵(双值)时是否有人能够得到正确的结果。当我试图追踪 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: %.53g\n"
        "OpenCV  : %.53g\n",
        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 结果的预期结果不匹配时变得不清楚,因为计算奇怪或因为一个实际的算法实现问题(这是我假设的)。

希望这更清楚。

【问题讨论】:

您的结果不“准确”,因为double 不准确。 en.cppreference.com/w/cpp/types/numeric_limits/is_exact 您是否使用 -fast-math 或类似的东西进行编译? 两个结果重合为16位,为双精度。差异真的相关吗? – DrewDormann,PaulMcKenzie,是的,他们并不准确。问题是,为什么它们不完全一样? @Borgleader 有趣的是,如果我使用 --fast-math 编译,OpenCV 结果现在确实匹配!虽然我不太明白为什么 OpenCV 结果发生了变化,而不是我执行的直接计算。它必须影响 OpenCV 中的内联操作。你能写一个答案让我接受吗?这似乎确实解决了问题。 啊,我搞混了。确实不是 OpenCV 结果发生了变化。这更有意义。所以我的 OpenCV 库必须用 --fast-math 编译。 【参考方案1】:

浮点数学本质上是不精确的。在 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

【讨论】:

我更新了我的评论以澄清一点。浮点不精确不是问题。即,对于应该是两个完全相同的计算(双/双),它没有得到相同的结果。硬件矢量单元是一个有趣的想法,但我没有想到这一点。包括标志并没有帮助。 OpenCV 库可能被告知以不同的方式执行它们的计算,但我不确定如何确定它们是如何完成的。也许我只需要自己重建它,所以我肯定知道...... 当然,OpenCV 库是用它们自己的一组编译器标志构建的,你会希望事情匹配。试试-ffast-math【参考方案2】:

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

【讨论】:

以上是关于标量的OpenCV双矩阵除法产生不正确的结果的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV中的MAT类矩阵的各种基本运算及示例代码(加减乘点乘点除乘方累加转置等)

Python的Numpy库中各种矩阵基本运算的示例代码(加减乘点乘点除乘方转置等)

OpenCV矩阵元素除法给出全零结果

有没有办法防止opencv矩阵除法中的舍入

Opencv:用imwrite保存一个双矩阵

matlab的运算法则?