如何使用 OpenCV 在 C++ 中实现高效的 im2col 函数?

Posted

技术标签:

【中文标题】如何使用 OpenCV 在 C++ 中实现高效的 im2col 函数?【英文标题】:How to implement an efficient im2col function in C++ using OpenCV? 【发布时间】:2015-01-04 15:25:19 【问题描述】:

我一直在尝试实现 MATLAB 和 GNU Octave 中的 im2col 函数。我发现很难理解 Octave 源代码中的实现,所以我在几个矩阵上运行该函数以了解其背后的逻辑。使用它,我使用 OpenCV 在 C++ 中实现了相同的功能,虽然结果似乎相同,但速度非常慢。

#include <opencv2/opencv.hpp>
#include <iostream>


using namespace std;
using namespace cv;

int main(int argc, char** argv)

    Mat input = Mat::eye(100,100,CV_32FC1);
    input.at<float>(1,2) = 2;   //Makes it easier to verify the correct solution
    int rowBlock = 7;
    int colBlock = 5;

    int m = input.rows;
    int n = input.cols;

    int x = m - rowBlock + 1;
    int y = n - colBlock + 1;

    Mat result = Mat::zeros(1,rowBlock*colBlock,CV_32FC1);

    for(int i = 0; i< y; i++)
    
        for (int j = 0; j< x; j++)
        
            Mat temp2 = input.rowRange(j,j+rowBlock).colRange(i,i+colBlock).t();
            temp2 = temp2.reshape(1,1);
            vconcat(result,temp2,result);
        
    
    result = result.rowRange(1,result.rows);
    cout << result << endl;

    return 0;

有什么方法可以改进吗?我敢肯定我在这里做很多事情效率很低。

【问题讨论】:

不知道 im2col 应该做什么,但我猜它的结果将是您示例中的 9024 x 35 矩阵?每个块中的元素是否只是写入结果中的一行?如果是,顺序是什么?结果中行的第一个元素中的块的第一行?紧随其后的第二行? 你有一个问题,行和列不直观地与 x 和 y 混合。也许您的问题是,您的外循环在列上,而您的内循环在行上,这违反了数据排序,导致缓存问题。 @Micka - im2col 是一个 MATLAB 函数,它采用已知大小的每个可能的像素邻域,将它们转换为堆叠的一维列,并创建一个矩阵,将所有这些列连接在一起。当我想实现一个在 MATLAB 中没有内置等效项的过滤器时,我一直使用它。 堆叠邻域的顺序重要吗?我会假设 col-first ordering,但是您的代码给出了 row-first ordering。如果您想要最高效率,这些事情很重要。但是,我发布的答案应该与您发布的结果相同,但应该更快。 @Micka - 这不是我的代码或我的帖子,但是是的,它以 col-first 排序。它按列抓取像素邻域,并对像素邻域进行排序,以使列首先展开。因此,如果我们有一个像素邻域为1,2,3, 4,5,6, 7,8,9;,它将变为:1,4,7,2,5,8,3,6,9; 【参考方案1】:

这个对我来说要快得多:

int main()

    cv::Mat input = cv::Mat::eye(100,100,CV_32FC1);
    input.at<float>(1,2) = 2;   //Makes it easier to verify the correct solution
    int rowBlock = 7;
    int colBlock = 5;

    int m = input.rows;
    int n = input.cols;

    // using right x = col; y = row
    int yB = m - rowBlock + 1;
    int xB = n - colBlock + 1;

    // you know the size of the result in the beginning, so allocate it all at once
    cv::Mat result2 = cv::Mat::zeros(xB*yB,rowBlock*colBlock,CV_32FC1);
    for(int i = 0; i< yB; i++)
    
        for (int j = 0; j< xB; j++)
        
            // here yours is in different order than I first thought:
            //int rowIdx = j + i*xB;    // my intuition how to index the result
            int rowIdx = i + j*yB;

            for(unsigned int yy =0; yy < rowBlock; ++yy)
                for(unsigned int xx=0; xx < colBlock; ++xx)
                
                    // here take care of the transpose in the original method
                    //int colIdx = xx + yy*colBlock; // this would be not transposed
                    int colIdx = xx*rowBlock + yy; 

                    result2.at<float>(rowIdx,colIdx) = input.at<float>(i+yy, j+xx);
                

        
    
    // check your output here...

我将它添加到您的代码中,以测试是否相等(最好为每个函数编写一个函数并进行封装;))

int main()

    cv::Mat input = cv::Mat::eye(100,100,CV_32FC1);
    input.at<float>(1,2) = 2;   //Makes it easier to verify the correct solution
    int rowBlock = 7;
    int colBlock = 5;

    int m = input.rows;
    int n = input.cols;

    // here, your naming of x and y is counter intuitive for me, since I see x being linked to cols normally (e.g. direction of x-axis)
    int x = m - rowBlock + 1;
    int y = n - colBlock + 1;

    cv::Mat result = cv::Mat::zeros(1,rowBlock*colBlock,CV_32FC1);

    for(int i = 0; i< y; i++)
    
        for (int j = 0; j< x; j++)
        
            cv::Mat temp2 = input.rowRange(j,j+rowBlock).colRange(i,i+colBlock).t();
            temp2 = temp2.reshape(1,1);
            cv::vconcat(result,temp2,result);
        
    
    result = result.rowRange(1,result.rows);

    std::cout << result.rows << " x " << result.cols << std::endl;

    char w;
    std::cin >> w;


    // using right x = col; y = row
    int yB = m - rowBlock + 1;
    int xB = n - colBlock + 1;

    // you know the size of the result in the beginning, so allocate it all at once
    cv::Mat result2 = cv::Mat::zeros(x*y,rowBlock*colBlock,CV_32FC1);
    for(int i = 0; i< yB; i++)
    
        for (int j = 0; j< xB; j++)
        
            // here yours is in different order than I first thought:
            //int rowIdx = j + i*xB;    // my intuition how to index the result
            int rowIdx = i + j*yB;

            for(unsigned int yy =0; yy < rowBlock; ++yy)
                for(unsigned int xx=0; xx < colBlock; ++xx)
                
                    // here take care of the transpose in the original method
                    //int colIdx = xx + yy*colBlock; // this would be not transposed
                    int colIdx = xx*rowBlock + yy; 

                    result2.at<float>(rowIdx,colIdx) = input.at<float>(i+yy, j+xx);
                

        
    



    std::cout << result2.rows << " x " << result2.cols << std::endl;
    std::cin >> w;

    // test whether both results are the same:
    bool allGood = true;
    for(int j=0; j<result.rows; ++j)
        for(int i=0; i<result.cols; ++i)
        
            if(result.at<float>(j,i) != result2.at<float>(j,i))
            
                    std::cout << "("<<j<<","<<i<<") = " << result.at<float>(j,i) << " != " << result2.at<float>(j,i) << std::endl;
                    allGood = false;
            
        
    if(allGood) std::cout << "matrices are equal" << std::endl;

    std::cin >> w;

    return 0;

【讨论】:

这太棒了!我只需要为我的应用程序转置结果矩阵,但非常感谢您的时间和帮助! 很高兴听到!如果您考虑在计算期间/之前已经考虑转置,代码可能会更有效,但如果它足够快,我想它不值得工作:)

以上是关于如何使用 OpenCV 在 C++ 中实现高效的 im2col 函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用背景减法在 OpenCV 中实现tripwire

在 OpenCV Java 中实现卡尔曼滤波器

如何在 C++ 中将 OpenCV 2D 矩阵转换为 1D 数组?

如何在 Opencv 中实现 Matlab 的 Imrotate?

如何在 OpenCV 中实现颜色分割和前景检测?

在 OpenCV 中实现热视觉 [关闭]