Mat 读取数据 及效率对比

初始化

参考手册中的构造函数如下所示:

(1) Mat::Mat()

(2) Mat::Mat(int rows, int cols, int type)

(3) Mat::Mat(Size size, int type)

(4) Mat::Mat(int rows, int cols, int type, constScalar& s)

(5) Mat::Mat(Size size, int type, constScalar& s)

(6) Mat::Mat(const Mat& m)

(7) Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)  //data 为数组,使用 数组初始化 Mat ,注意 如果使用的是二维数组,记得使用 data[0]作为传入的参数,如果 data是一维,直接传入 data

(8) Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)

(9) Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)

(10) Mat::Mat(const Mat& m, const Rect& roi)

(11) Mat::Mat(const CvMat* m, bool copyData=false)

(12) Mat::Mat(const IplImage* img, bool copyData=false)

(13) template<typename T, int n> explicit Mat::Mat(const Vec<T, n>& vec, bool copyData=true)

(14) template<typename T, int m, int n> explicit Mat::Mat(const Matx<T, m, n>& vec, bool copyData=true)

(15) template<typename T> explicit Mat::Mat(const vector<T>& vec, bool copyData=false)

(16) Mat::Mat(const MatExpr& expr)

(17) Mat::Mat(int ndims, const int* sizes, int type)

(18) Mat::Mat(int ndims, const int* sizes, int type, constScalar& s)

(19) Mat::Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0)

(20) Mat::Mat(const Mat& m, const Range* ranges)

OpenCV 里面的很多东西都会涉及到对Mat的操作,从基本的单个元素存取到整个Mat 数据的扫描。一般情况下用Mat.at(Point pt)就可以了,但是当有反复存取整个Mat数据的时候,整个扫描的效率就值得考虑了。有些同学对三通道或多通道的Mat数据的读取或许还有些疑问,同时,在debug和release的不同情况下,数据的读取速度也是不同的。所以这里再讨论一下几种数据存取的方法。

1 Mat.at 

at 操作可以说是Opencv提供的官方操作, 具体执行如:

for (int i = 0; i<img.rows; i++)
    {
        for (int j = 0; j < img.cols; j++)
        {
            Vec3b& color = img.at<Vec3b>(i,j);//i是行,j是列,Point(x,y),x是列,y是行,注意
            color[0] = 0;
            color[1] = 255;
            color[2] = 255;
        }
    }

用at来扫描整个数据确实显得比较臃肿。估计是因为里面涉及到很多Opencv设定的指针之类的操作,我猜想是有很多需要动态链接的东西, 所以整个扫描过程比较慢,在debug模式下,会消耗很多时间。但是在release模式下,at操作将会快很多,仅仅比直接用指针的操作慢一点点,所以在真正运用的时候,at的速度并不慢。

2 指针 
Mat的数据结构是分为两部分的,一个是头,一个是数据。头就相当于很多文件前面的说明性内容,表明记录的是什么,怎么记录的,有什么特点,数据位置在哪之类。而数据就是单纯的数据,这个数据占用一串连续的存储空间。 数据和头在内存中的位置并不是必须连在一起的,但是头中肯定记录了数据的位置,这些数据的位置包括整个数据的起始和终点,每一行数据的起始位置等等。数据的存储顺序是按行进行的,从左到右,依次记录每个位置的所有通道的数据。所以说,只要我们知道了数据的起始位置和数据类型,那么利用指针就可以很方便快捷的扫描整个数据了。 

利用指针读取数据的方法有三种,第一种是获取数据的开头后直接读取所有数据,数据的读取就像数组一样。这种方法要求头里面指定的整个数据必须连续。 像前面说的,一般情况下数据都是连续的,但是有时头里面指定的是某几个数据段,这时数据有可能就不连续。可以利用Mat.isContinuous()来查看头里面指定的数据是否是连续的。具体执行如:

if (img.isContinuous())//判断数据是否连续
{
    int data_length= img.rows*img.cols*img.channels();
    uchar* data= img.ptr<uchar>(0);//查找第一个数据的位置,这里指针的类型可以是各种数据结构,图像一般用CV_8U,所以这里利用unchar来作为指针类型。
    int channels = img.channels();
    for (int i = 0; i< data_length; i = i + channels)
    {
        data[i] = 255;
        data[i+1] = 0;
        data[i+2] = 0;//这里我们认为图像是彩色的三通道,所以对每个像素的BGR通道分别进行单独操作
    }
}

 

第二种方法是获取每行数据的起始位置然后利用同样的原理进行数据的读取。因为一行内的数据肯定是连续的,所以不用检测数据是否连续。具体如下:

 

int channels = img.channels();
int j_end = img.cols*channel;
    for (int i = 0; i<img.rows; i++)
    {
        uchar* data = img.ptr<uchar>(i);
        for (int j = 0; j<j_end; j = j + channels)
        {
            data[j] = 255;
            data[j+1] = 0;
            data[j+2] = 255;
        }
    }//在实际应用中应注意数据的类型

 

在上面链接的那篇博客里,还介绍了一种指针的扫描方式,是利用Mat.step来返回每行数据的长度,然后据此来找到所有数据的位置。因为指针的位置是根据数据的开始位置和行数及列数来计算的,所以该方法也要求所有的数据都是连续的。具体如下:

 

int step = img.step;
int channels= img.channels();
uchar* data;
for (int i = 0; i<img.rows; i)
{
    for (int j = 0; j<img.cols; j++)
    {
        data = img.data + i*step + j * channels;
        data[0] = 0;
        data[1] = 255;
        data[2] = 100;
    }
}//在实际应用中应注意数据的类型

 

利用指针来读取数据是最快的扫描方式,无论在debug还是release模式下。 一般情况下,循环中的操作越少速度会越快,所以第一种指针读取的速度是最快的,第二种涉及到读取每行的数据位置,速度稍慢一点,最后一种涉及到两个乘法,所以更慢一些。但是经过多次测试,这三种指针的读取速度都比Mat.at的速度要快一点。

 

总结:对Mat类型的数据的读取在一般情况下用at操作就可以了,但当涉及反复读取整个数据即需要多次扫描数据的时候,指针是比较高效的方法。好的编程风格确实可以极大提升程序的准确率和速度,节约程序员的时间。 在循环中尽量少涉及复杂的计算,特别是一些可以避免的重复计算。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NineDays66

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值