在opencv中索引矩阵的最佳方法

YW *_*won 6 c++ matlab opencv

让我们说,A并且B是相同大小的矩阵.在Matlab,我可以使用如下的简单索引.

idx = A>0;
B(idx) = 0
Run Code Online (Sandbox Code Playgroud)

我怎么能这样做OpenCV?我应该使用

for (i=0; ... rows)
    for(j=0; ... cols)
        if (A.at<double>(i,j)>0) B.at<double>(i,j) = 0;
Run Code Online (Sandbox Code Playgroud)

这样的事情?是否有更好(更快,更有效)的方式?

而且,在OpenCV我尝试的时候

Mat idx = A>0;
Run Code Online (Sandbox Code Playgroud)

变量idx似乎是一个CV_8U矩阵(不是布尔而是整数).

Mik*_*iki 6

您可以轻松转换此MATLAB代码:

idx = A > 0;
B(idx) = 0;

// same as 

B(A>0) = 0;
Run Code Online (Sandbox Code Playgroud)

到OpenCV:

Mat1d A(...)
Mat1d B(...)

Mat1b idx = A > 0;
B.setTo(0, idx) = 0;

// or

B.setTo(0, A > 0);
Run Code Online (Sandbox Code Playgroud)

关于性能,在C++中,它通常更快(取决于启用的优化)来处理原始指针(但不太可读):

for (int r = 0; r < B.rows; ++r)
{
    double* pA = A.ptr<double>(r);
    double* pB = B.ptr<double>(r);
    for (int c = 0; c < B.cols; ++c)
    {
        if (pA[c] > 0.0) pB[c] = 0.0;
    }
}
Run Code Online (Sandbox Code Playgroud)

还要注意,在OpenCV中没有任何布尔矩阵,但它是一个CV_8UC1矩阵(也就是单个通道矩阵unsigned char),其中0是均值false,任何值>0都是真的(通常255).

评估

请注意,这可能会因 OpenCV启用的优化而有所不同.您可以在PC上测试以下代码以获得准确的结果.

以毫秒计的时间:

          my results           my results      @AdrienDescamps
          (OpenCV 3.0 No IPP)  (OpenCV 2.4.9)

Matlab  : 13.473     
C++ Mask: 640.824              5.81815         ~5
C++ Loop: 5.24414              4.95127         ~4
Run Code Online (Sandbox Code Playgroud)

注意:我不完全确定OpenCV 3.0的性能下降,所以我只想说:在PC上测试下面的代码以获得准确的结果.

正如@AdrienDescamps在评论中所述:

看起来OpenCV 3.0的性能下降与OpenCL选项有关,现在在比较运算符中启用了该选项.

C++代码

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
    // Random initialize A with values in [-100, 100]
    Mat1d A(1000, 1000);
    randu(A, Scalar(-100), Scalar(100));

    // B initialized with some constant (5) value
    Mat1d B(A.rows, A.cols, 5.0); 

    // Operation: B(A>0) = 0;

    {
        // Using mask

        double tic = double(getTickCount());
        B.setTo(0, A > 0);
        double toc = (double(getTickCount()) - tic) * 1000 / getTickFrequency();

        cout << "Mask: " << toc << endl;
    }
    {
        // Using for loop

        double tic = double(getTickCount());
        for (int r = 0; r < B.rows; ++r)
        {
            double* pA = A.ptr<double>(r);
            double* pB = B.ptr<double>(r);
            for (int c = 0; c < B.cols; ++c)
            {
                if (pA[c] > 0.0) pB[c] = 0.0;
            }
        }
        double toc = (double(getTickCount()) - tic) * 1000 / getTickFrequency();

        cout << "Loop: " << toc << endl;
    }


    getchar();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Matlab代码

% Random initialize A with values in [-100, 100]
A = (rand(1000) * 200) - 100;
% B initialized with some constant (5) value
B = ones(1000) * 5; 

tic
B(A>0) = 0; 
toc
Run Code Online (Sandbox Code Playgroud)

UPDATE

OpenCV 3.0在函数中使用IPP优化setTo.如果你启用了(你可以查看cv::getBuildInformation()),你将有一个更快的计算.


Adr*_*mps 5

Miki的回答非常好,但我只是想补充一些关于性能问题的说明,以避免任何混乱。

确实,使用 OpenCV 实现图像过滤器(或任何算法)的最佳方法是使用原始指针,如 Miki 的第二个 C++ 示例(C++ 循环)所示。使用 at 函数也是正确的,但速度明显慢。

然而,大多数时候,您不需要担心这一点,您可以简单地使用 OpenCV 的高级功能(Miki 的第一个示例,C++ Mask)。它们经过了很好的优化,通常几乎与指针上的低级循环一样快,甚至更快。

当然,也有例外(我们刚刚发现了一个),您应该始终针对您的具体问题进行测试。

现在,关于这个具体问题:

这里的示例中,高级函数比低级循环慢得多(慢 100 倍),这不是正常情况,因为 OpenCV 其他版本/配置的时间要低得多,这表明了这一点。问题似乎是,当使用 OpenCL 编译 OpenCV3.0 时,第一次调用使用 OpenCL 的函数时会产生巨大的开销。如果您使用 OpenCV3.0,最简单的解决方案是在编译时禁用 OpenCL(如果您感兴趣,另请参阅此处了解其他可能的解决方案)。