在上一篇Blog中,介绍了使用基础图像容器Mat创建图像矩阵的六种方式
,当我们使用创建的Mat矩阵进行图像处理时,需要对Mat矩阵中像素点进行遍历操作,图像像素点的遍历是任何图像处理算法必不可少的执行步骤。在OpenCV中提供了3种图像遍历的方法:
这三种方式在访问速度上有所差异,其中.at方法遍历速度最慢,但是代码可读性好;ptr指针遍历更快,但是指针操作没有类型检查和访问越界检查,即使程序在编译过程中没问题在执行过程中可能报错;iterator迭代器遍历方式最快而且安全不会出现访问越界情况,但是源码编辑稍显复杂。在编程过程中应该根据实践需要采取不同的遍历方式,以达到最高效的访问、读写和编辑。
1、.at方法遍历
Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,通过image.at
uchar pix_value = srcImage.at<uchar>(i,j); //读取srcImage第i行j列元素
at方法遍历图像所有像素的一般格式为(srcImage表示原图):
//单通道灰度图
for(int i=0;i<srcImage.rows;i++){
for(int j=0;j<srcImage.cols;j++){
srcImage.at<uchar>(i,j)=...
...
}
}
//3通道RGB彩色图
for(int i=0;i<srcImage.rows;i++){
for(int j=0;j<srcImage.cols;j++){
srcImage.at<Vec3b>(i,j)[2]=... //R通道
srcImage.at<Vec3b>(i,j)[1]=... //G通道
srcImage.at<Vec3b>(i,j)[0]=... //B通道
...
}
}
【注】at方法只适合访问灰度值为8位的图像,即单通道图像,如灰度图、RGB分离单通道图等。
2、指针访问
指针访问图像像素的方式是利用C语言中的 “[]” 操作符,此时图像从上到下、从左到右可以看成是一个“一维数组”(不一定连续存储),通过Mat类成员变量cols和成员函数channels()获取图像宽和通道数,那么Mat矩阵第i行第j列元素可表示为: i ∗ s r c I m a g e . c o l s ∗ s r c I m a g e . c h a n n e l s ( ) + j i*srcImage.cols*srcImage.channels()+j i∗srcImage.cols∗srcImage.channels()+j为了简化指针运算,Mat类提供了ptr函数可以得到图像任意行首地址,它返回第i行的首地址:
uchar* data = dstImage.ptr<uchar>(i); //data为第i行像素首地址
那么第i行第j列像素的标识方式简化为:data[i]+j
ptr函数配合指针遍历图像所有像素的一般格式为(srcImage表示原图):
//单通道灰度图
for(int i=0;i<srcImage.rows;i++){
uchar* p=srcImage.ptr<uchar>(i);
for(int j=0;j<srcImage.cols;j++){
p[j]=...
...
}
}
//3通道RGB彩色图
for(int i=0;i<srcImage.rows;i++){
Vec3b* p=srcImage.ptr<Vec3b>(i);
for(int j=0;j<srcImage.cols;j++){
p[j][2]=... //R通道
p[j][1]=... //G通道
p[j][0]=... //B通道
...
}
}
3、iterator迭代器遍历
这种方式类似STL,在访问图像矩阵时,仅仅需要获得图像矩阵的begin和end元素,从begin遍历到end过程中,通过 ~ * ~ 操作符获取指针所指数据,即可访问Mat图像内容。
iterator迭代器遍历图像所有像素的一般格式为(srcImage表示原图):
//单通道灰度图
MatIterator_<uchar> it_beg=srcImage.begin<uchar>();
MatIterator_<uchar> it_end=srcImage.end<uchar>();
for( , it_beg != it_end; it_beg++){
*it_beg=...
...
}
//3通道RGB彩色图
MatIterator_<Vec3b> it_beg=srcImage.begin<uchar>();
MatIterator_<Vec3b> it_end=srcImage.end<uchar>();
for( , it_beg != it_end; it_beg++){
(*it_beg)[2]=... //R通道
(*it_beg)[1]=... //G通道
(*it_beg)[0]=... //B通道
...
}
关于uchar与Vec3b类型一些问题:
涉及到OpenCV中对图像像素的存储方式,一般情况下,单通道图像像素类型为uchar(unsigned char表示0-255区间的无符号整形),Vec是OpenCV一个模板类,表示一个向量,可表示多通道的图像像素类型,OpenCV预定义的一些类型有:
typedef Vec<uchar, n> Vecnb;
typedef Vec<short, n> Vecns;
typedef Vec<int, n> Vecni;
typedef Vec<float, n> Vecnf;
typedef Vec<double, n> Vecnd;
【注】n表示维度,可取2、3、4,分别表示2维向量、三维向量、四维向量,Vec3b就表示3维向量,每一个维度类型都是uchar型。
下面通过一个demo程序,也是最通用的像素遍历示例程序来演示以上三种方法在遍历图像矩阵中的开销和优缺点。
Test: 颜色空间缩减
加载一幅RGB彩色图像,使其颜色种类从256中变成64种。在颜色空间缩减方法中讲过这种方式,即每个像素值除以4向下取整然后再乘以4即可将其颜色种类缩减到64种。
首先,为直观对比三种方式时间开销的差异,需要用到OpenCV提供的计时函数:
这两个函数配合使用即可计算出程序耗时,类似于C++中的clock()函数,具体用法如下:
double time0 = static_cast<double>(getTickCount()); //记录起始时间
//...
//一系列操作
//...
time0 = ((double)getTickCount()-time0)/getTickFrequency();
std::cout<<"..操作 运行时间为:"<<time0<<"s"<<std::endl; //输出运行时间
Code:
#include
#include
using namespace cv;
void colorReduceAt(Mat& srcImage, Mat& dstImageAt, int div);
void colorReduceIterator(Mat& srcImage, Mat& dstImageIterator, int div);
void colorReducePtr(Mat& srcImage, Mat& dstImagePtr, int div);
int main()
{
//加载图像
Mat srcImage = imread("");
if(srcImage.empty())
{
cout << "Load failture...." << endl << endl;
exit(-1);
}
//声明处理后图像变量
Mat dstImageAt, dstImageIterator, dstImagePtr;
dstImageAt = srcImage.clone();
dstImageIterator = srcImage.clone();
dstImagePtr = srcImage.clone();
int div = 4;
//声明时间变量
double timeAt, timeIterator, timePtr;
timeAt = static_cast<double>(getTickCount());
colorReduceAt(srcImage, dstImageAt, div);
timeAt = ((double)getTickCount() - timeAt) / getTickFrequency();
imshow("dstImageAt",dstImageAt);
cout << "使用at()动态地址计算耗时:" << timeAt << endl << endl;
timeIterator = static_cast<double>(getTickCount());
colorReduceIterator(srcImage, dstImageIterator, div);
timeIterator = ((double)getTickCount() - timeIterator) / getTickFrequency();
imshow("dstImageIterator",dstImageIterator);
cout << "使用iterator迭代器耗时:" << timeIterator << endl << endl;
timePtr = static_cast<double>(getTickCount());
colorReducePtr(srcImage, dstImagePtr, div);
timePtr = ((double)getTickCount() - timePtr) / getTickFrequency();
imshow("dstImagePtr",dstImagePtr);
cout << "使用ptr指针耗时:" << timePtr << endl;
waitKey(0);
return 0;
}
//使用at动态地址计算方式
void colorReduceAt(Mat& srcImage, Mat& dstImageAt, int div)
{
int rowNumber = dstImageAt.rows; //获取图像行数
int colNumber = dstImageAt.cols; //获取图像列数
//对每个像素进行处理
for(int i = 0; i < rowNumber; i++)
{
for(int j = 0; j < colNumber; j++)
{
dstImageAt.at<Vec3b>(i,j)[0] = dstImageAt.at<Vec3b>(i,j)[0]/div*div; //Blue
dstImageAt.at<Vec3b>(i,j)[1] = dstImageAt.at<Vec3b>(i,j)[1]/div*div; //Green
dstImageAt.at<Vec3b>(i,j)[2] = dstImageAt.at<Vec3b>(i,j)[2]/div*div; //Red
}
}
}
//使用iterator迭代器方式
void colorReduceIterator(Mat& srcImage, Mat& dstImageIterator, int div)
{
MatIterator_<Vec3b> imageIt = dstImageIterator.begin<Vec3b>(); //获取迭代器初始位置
MatIterator_<Vec3b> imageEnd = dstImageIterator.end<Vec3b>(); //获取迭代器结束位置
//对每个像素进行处理
for(;imageIt != imageEnd; imageIt++)
{
(*imageIt)[0] = (*imageIt)[0]/div*div; //Blue
(*imageIt)[1] = (*imageIt)[1]/div*div; //Green
(*imageIt)[2] = (*imageIt)[2]/div*div; //Red
}
}
//使用ptr指针
void colorReducePtr(Mat& srcImage, Mat& dstImagePtr, int div)
{
int rowNumber = dstImagePtr.rows; //获取图像矩阵行数
int colNumber = dstImagePtr.cols*dstImagePtr.channels(); //三通道图像每行元素个数为列数x通道数
for(int i = 0; i < rowNumber; i++)
{
uchar* pixelPtr = dstImagePtr.ptr<uchar>(i); //获取矩阵每行首地址指针
for(int j = 0; j < colNumber; j++)
pixelPtr[j] = pixelPtr[j] / div * div;
}
}
续更…
参考博客:OpenCV成长之路(2):图像的遍历