Koushik S
Koushik S

Reputation: 33

How to implement an efficient im2col function in C++ using OpenCV?

I've been trying to implement im2col function present in MATLAB and GNU Octave. I found it hard to understand the implementation present in Octave's source code, so I ran the function on few matrices to understand the logic behind it. Using that, I've implemented the same in C++ using OpenCV, and although the result seems to be the same, it's awfully slow.

#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;
}

Is there any way to improve on it? I'm sure I might be doing a lot of things very inefficiently here.

Upvotes: 3

Views: 3289

Answers (1)

Micka
Micka

Reputation: 20160

This one is much faster for me:

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...
}

I added it to your code, to test the equality (it would be better to write a function for each one and encapsulate, though ;) )

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;
}

Upvotes: 5

Related Questions