美文网首页
【OPENCV】cv::Mat像素遍历方法比较

【OPENCV】cv::Mat像素遍历方法比较

作者: ItchyHiker | 来源:发表于2018-10-24 20:07 被阅读0次

    像素级别遍历是我们在图像任务中经常遇到的问题,在实时的图像处理中,能够高效的访问像素数据是很重要的。OpenCV中的数据容器是cv::Mat,cv::Mat提供了三种数据访问的方式分别是下标寻址,指针访问,迭代器访问。下面我们对比下这几种不同方式的访问速度。

    #include <iostream>
    #include <assert.h>
    #include "opencv2/core/core.hpp"
    using namespace std;
    void method1(cv::Mat);
    void method2(cv::Mat);
    void method3(cv::Mat);
    void method4(cv::Mat);
    void method5(cv::Mat);
    void method6(cv::Mat);
    void method7(cv::Mat);
    
    int main(int argc, char* argv[])
    {
        cv::Size imgSize(6400,4800);
        cv::Mat image = cv::Mat(imgSize, CV_8UC3, cv::Scalar(1,1,1));
        method1(image);
        method2(image);
        method3(image);
        method4(image);
        method5(image);
        method6(image);
        method7(image);
    }
    
    void method1(cv::Mat img){
        // at access with Vec3b Vector
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum = 0;
        for(int row=0; row < height; row++){
            for(int col=0; col < width; col++){
                cv::Vec3b uc_pixel = img.at<cv::Vec3b>(row, col);
                int a = uc_pixel[0];
                int b = uc_pixel[1];
                int c = uc_pixel[2];
                sum += a + b + c;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method1: " << time << std::endl;
    }
    
    void method2(cv::Mat img){
        // direct at access 
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum = 0;
        for(int row=0; row < height; row++){
            for(int col=0; col < width; col++){
                int a = img.at<cv::Vec3b>(row, col)[0];
                int b = img.at<cv::Vec3b>(row, col)[1];
                int c = img.at<cv::Vec3b>(row, col)[2];
                sum += a + b + c;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method2: " << time << std::endl;
    }
    
    void method3(cv::Mat img){
        // pointer + Vec3b vector
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum = 0;
        for(int row=0; row < height; row++){
            cv::Vec3b *ptr = img.ptr<cv::Vec3b>(row);
            for(int col=0; col < width; col++){
                cv::Vec3b pixel = ptr[col];
                int a = pixel[0];
                int b = pixel[1];
                int c = pixel[2];
                sum += a + b + c;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method3: " << time << std::endl;
    }
    void method4(cv::Mat img){
        // pointer
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum = 0;
        for(int row=0; row < height; row++){
            cv::Vec3b *ptr = img.ptr<cv::Vec3b>(row);
            for(int col=0; col < width; col++){
                int a = ptr[col][0];
                int b = ptr[col][1];
                int c = ptr[col][2];
                sum += a + b + c;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method4: " << time << std::endl;
    }
    
    void method5(cv::Mat img){
        // raw pointer 
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum=0;
        for(int row=0; row < height; row++){
            const uchar *ptr = img.ptr(row);
            for(int col=0; col < width; col++){
                const uchar *uc_pixel = ptr;
                int a = uc_pixel[0];
                int b = uc_pixel[1];
                int c = uc_pixel[2];
                // int a = ptr[0];
                // int b = ptr[1];
                // int c = ptr[2];
                sum += a + b + c;
                ptr += 3;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method5: " << time << std::endl;
    }
    
    void method6(cv::Mat img){
        // raw pointer + raw step
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum = 0;
        const uchar *uc_pixel = img.data;
        for(int row=0; row < height; row++){
            uc_pixel = img.data + row*img.step;
            for(int col=0; col < width; col++){
                int a = uc_pixel[0];
                int b = uc_pixel[1];
                int c = uc_pixel[2];
                sum += a + b + c;
                uc_pixel += 3;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method6: " << time << std::endl;
    }
    
    void method7(cv::Mat image){
        double t0 = (double) cv::getTickCount();
        int height = image.rows;
        int width = image.cols;
        cv::MatConstIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>(), it_end = image.end<cv::Vec3b>();
        int sum = 0;
        for(; it != it_end; ++it){
            int a = (*it)[0];
            int b = (*it)[1];
            int c = (*it)[2];           
            sum += a + b + c;
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method7: " << time << std::endl;
    }
    
    Time for method1: 0.586296
    Time for method2: 0.414636
    Time for method3: 0.504703
    Time for method4: 0.207575
    Time for method5: 0.111554
    Time for method6: 0.0940078
    Time for method7: 0.522204
    

    对比这几种方式我们可以发现,最为高效的还是直接使用指针计算地址偏移量, 然而这种方式必须保证Mat在内存的存储是连续的,可以通过cv::Mat::isContinous()函数检测,如果是连续的则可以处理为单行向量,使用最为高效的方式访问。如果不想这么麻烦,其实method5是一种较为可取的方式,通过从cv::Mat::ptr()得到每一行的首地址,这样就不需要保证连续存储,速度和纯粹使用指针也差不了多少。

    实际上对于method5,不使用中间指针进行改写的话:

    void method5(cv::Mat img){
        // raw pointer 
        double t0 = (double) cv::getTickCount();
        int height = img.rows;
        int width = img.cols;
        int sum=0;
        for(int row=0; row < height; row++){
            const uchar *ptr = img.ptr(row);
            for(int col=0; col < width; col++){
                // const uchar *uc_pixel = ptr;
                // int a = uc_pixel[0];
                // int b = uc_pixel[1];
                // int c = uc_pixel[2];
                // 不使用中间指针
                int a = ptr[0];
                int b = ptr[1];
                int c = ptr[2];
                sum += a + b + c;
                ptr += 3;
            }
        }
        assert(sum==3*height*width);
        double time = ((double) cv::getTickCount() - t0) / cv::getTickFrequency();
        std::cout << "Time for method5: " << time << std::endl;
    }
    

    重新测试下:

    Time for method1: 0.58601
    Time for method2: 0.416404
    Time for method3: 0.507943
    Time for method4: 0.208068
    Time for method5: 0.0918915
    Time for method6: 0.0917811
    Time for method7: 0.523099
    

    时间上已经十分接近method6,实际操作的时候直接使用method5,不使用中间指针即可。

    Reference

    1. http://longstryder.com/2014/07/which-way-of-accessing-pixels-in-opencv-is-the-fastest/
    2. https://blog.csdn.net/xiaowei_cqu/article/details/19839019
    3. https://blog.csdn.net/ajianyingxiaoqinghan/article/details/72391523

    相关文章

      网友评论

          本文标题:【OPENCV】cv::Mat像素遍历方法比较

          本文链接:https://www.haomeiwen.com/subject/dcrnzftx.html