打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

像素级别遍历是我们在图像任务中经常遇到的问题,在实时的图像处理中,能够高效的访问像素数据是很重要的。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.586296Time for method2: 0.414636Time for method3: 0.504703Time for method4: 0.207575Time for method5: 0.111554Time for method6: 0.0940078Time 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.58601Time for method2: 0.416404Time for method3: 0.507943Time for method4: 0.208068Time for method5: 0.0918915Time for method6: 0.0917811Time 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
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
图像锐化(拉普拉斯算子):
OpenCV-图像色温
OpenCV读取图片像素值
opencv中mat,cvmat,Iplimage构造体定义以及格式互相转换
【从零学习OpenCV 4】这4种读取Mat类元素的的方法你都知道么?
OpenCV成长之路(2):图像的遍历 - 博客 - 伯乐在线
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服