OpenCV 里面的很多东西都会涉及到对Mat的操作,从基本的单个元素存取到整个Mat 数据的扫描。一般情况下用Mat.at(Point pt)就可以了,但是当有反复存取整个Mat数据的时候,整个扫描的效率就值得考虑了。CSDN上已经有大神对Mat的数据结构和存取做过比较详细的介绍了,可以参考这里:http://blog.csdn.net/yang_xian521/article/details/7182185 。这边是对那篇博文的一些补充,有些同学对三通道或多通道的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 number_of_uchar = img.elemSize();//Mat.elemSize()返回的是每个位置的数据占多个Byte,单通道的8U型数据就是返回1,三通道的就返回3。
for (int i = 0; i< data_length; i = i + number_of_uchar)
{
data[i] = 255;
data[i+1] = 0;
data[i+2] = 0;//这里我们认为图像是彩色的三通道,所以对每个像素的BGR通道分别进行单独操作
}
}
第二种方法是获取每行数据的起始位置然后利用同样的原理进行数据的读取。因为一行内的数据肯定是连续的,所以不用检测数据是否连续。具体如下:
int channel = img.channels();
int elemet = img.elemSize();
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 + elemet)
{
data[j] = 255;
data[j+1] = 0;
data[j+2] = 255;
}
}//在实际应用中应注意数据的类型
在上面链接的那篇博客里,还介绍了一种指针的扫描方式,是利用Mat.step来返回每行数据的长度,然后据此来找到所有数据的位置。因为指针的位置是根据数据的开始位置和行数及列数来计算的,所以该方法也要求所有的数据都是连续的。具体如下:
int step = img.step;
int elemet = img.elemSize();
uchar* data;
for (int i = 0; i<img.rows; i)
{
for (int j = 0; j<img.cols; j++)
{
data = img.data + i*step + j * elemets;
data[0] = 0;
data[1] = 255;
data[2] = 100;
}
}//在实际应用中应注意数据的类型
利用指针来读取数据是最快的扫描方式,无论在debug还是release模式下。 一般情况下,循环中的操作越少速度会越快,所以第一种指针读取的速度是最快的,第二种涉及到读取每行的数据位置,速度稍慢一点,最后一种涉及到两个乘法,所以更慢一些。但是经过多次测试,这三种指针的读取速度都比Mat.at的速度要快一点。
3 在上面链接的博客中还提到了一种利用迭代器进行数据的读取。因为使用迭代器的方法无论在debug还是release情况下都是最慢的,所以这里就不介绍了,感兴趣的同学可以参看那篇博文。
总结:对Mat类型的数据的读取在一般情况下用at操作就可以了,但当涉及反复读取整个数据即需要多次扫描数据的时候,指针是比较高效的方法。好的编程风格确实可以极大提升程序的准确率和速度,节约程序员的时间。 在循环中尽量少涉及复杂的计算,特别是一些可以避免的重复计算。