opencv3访问图像像素的五种方法
在做图像处理中,很多时候并不是针对整个图像进行操作,往往需要遍历图像的所有的点或者部分的点。比如一张1920X1080的图像,它的像素很多,所以在遍历图像像素的算法就需要考虑操作方式的优劣性。所以总结一下,常用的方法。
会有以下五种方式。下面的例子以对图像进行反色操作为例,分析五种方法的时间复杂度。
一:下标M.at<float>(i,j)
Mat inverseColor1(Mat srcImg)
{
Mat temp = srcImg.clone();
int row = temp.rows;
int col = temp.cols;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col;j++)
{
temp.at<cv::Vec3b>(i, j)[0] = 255 - temp.at<cv::Vec3b>(i, j)[0];
temp.at<cv::Vec3b>(i, j)[1] = 255 - temp.at<cv::Vec3b>(i, j)[1];
temp.at<cv::Vec3b>(i, j)[2] = 255 - temp.at<cv::Vec3b>(i, j)[2];
}
}
return temp;
}
temp.at<cv::Vec3b>(i, j)[n]表示彩色3通道图像中i行j列第k个通道的颜色像素值,其中<cv::Vec3b>是opencv里面的像素值类型。其函数模板为typedef Vec<uchar,3>Vec3b,表示3通道uchar。
at速度是五种方式里面较慢的,也是最简单的使用方式。
二:指针遍历Mat::ptr<type>
Mat inverseColor2(Mat srcImg)
{
Mat temp = srcImg.clone();
int row = temp.rows;
int col = temp.cols;
int nStep = temp.cols * temp.channels();
for (int i = 0; i < row; i++)
{
uchar *pSrcData = srcImg.ptr<uchar>(i);
uchar *pResuiltData = temp.ptr<uchar>(i);
for (int j = 0; j < nStep;j++)
{
pResuiltData[j] = saturate_cast<uchar>(255 - pSrcData[j]);
}
}
return temp;
}
Opencv提供了ptr的方法,用于表示遍历图像的每一个字节,默认返回值为uchar* 或者const uchar* 。来自模板temp<typename _Tp>_Tp*Mat::ptr(int i0=0),i0代表是以零行为基准的索引,函数返回指向特定矩阵的uchar* 或指针。
ptr速度优于at和MatIterator_,是最常用的一种方式。相比第一种而言,操作方式复杂,运用到指针操作。相比第三种,操作简单。
三:迭代器MatIterator_
Mat inverseColor3(Mat srcImg)
{
Mat temp = srcImg.clone();
MatConstIterator_<Vec3b> srcIterStart = srcImg.begin<Vec3b>();
MatConstIterator_<Vec3b> srcIterEnd = srcImg.end<Vec3b>();
MatIterator_<Vec3b> resIterStart = temp.begin<Vec3b>();
MatIterator_<Vec3b> resIterEnd = temp.end<Vec3b>();
while (srcIterStart != srcIterEnd)
{
(*resIterStart)[0] = 255 - (*srcIterStart)[0];
(*resIterStart)[1] = 255 - (*srcIterStart)[1];
(*resIterStart)[2] = 255 - (*srcIterStart)[2];
srcIterStart++;
resIterStart++;
}
return temp;
}
MatConstIterator_和MatIterator_是两个迭代器,MatConstIterator_<Vec3b> srcIterStart = srcImg.begin<Vec3b>();表示指向迭代器的起始位置。MatConstIterator_<Vec3b> srcIterEnd = srcImg.end<Vec3b>();表示指向迭代器的终止位置,通过起始起始和终止位置之间,移动指针完成对图像的像素遍历。
MatConstIterator_和MatIterator_
两者关系如下图:存在继承关系。
MatConstiterator 将迭代器设置为构造函数。
MatConstIterator_和MatIterator_ 两者是将迭代器设置为矩阵指定元素的构造函数。
迭代器MatIterator_速度是五种方式里面的最慢的,操作方式比较复杂。不推荐使用。
四:isContinouous
Mat inverseColor4(Mat src)
{
int row = src.rows;
int col = src.cols;
cv::Mat temp = src.clone();
// 判断是否是连续图像,即是否有像素填充
if (src.isContinuous() && temp.isContinuous())
{
row = 1;
// 按照行展开
col = col * src.rows * src.channels();
}
// 遍历图像的每个像素
for (int i = 0; i < row; i++)
{
// 设定图像数据源指针及输出图像数据指针
const uchar* pSrcData = src.ptr<uchar>(i);
uchar* pResultData = temp.ptr<uchar>(i);
for (int j = 0; j < col; j++)
{
*pResultData++ = 255 - *pSrcData++;
}
}
return temp;
}
图像行与行之间的存储可能是不连续的,上述三种方法,进行像素值遍历,很大程度上造成数据指针移动的浪费。Mat提供了一个检测是否连续的函数isContinuous(),1xN的图像矩阵是连续的。当图像元素连续时,可以看成一行,按行展开,利用指针来获取起始行的位置,进行遍历,节省了寻址的时间。
例:一张M x N的图像按行展开后,成为了1 x( N x M) 列的连续像素点。
isContinuous()的方法优于前面三种方法,操作难度居中,推荐使用(备注:图像必须是连续的才能使用)。
图像不是连续的,可看下面这篇博客,改成连续的图像。
参考博客:https://blog.csdn.net/guyuealian/article/details/78614662
五:LTU查表法
Mat inverseColor5(Mat src)
{
int row = src.rows;
int col = src.cols;
Mat temp = src.clone();
// 建立LUT 反色table
uchar LutTable[256 * 3];
for (int i = 0; i < 256; ++i)
{
LutTable[i * 3] = 255 - i;
LutTable[i * 3+1] = 255 - i;
LutTable[i * 3+2] = 255 - i;
}
Mat lookUpTable(1, 256, CV_8UC3, LutTable);
// 应用索引表进行查找
LUT(src, lookUpTable, temp);
return temp;
}
/*LTU查表反色处理(单通道)*/
Mat inverseColor5_1(Mat src)
{
int row = src.rows;
int col = src.cols;
Mat temp = src.clone();
// 建立LUT 反色table
uchar LutTable[256];
for (int i = 0; i < 256; ++i)
{
LutTable[i] = 255 - i;
}
Mat lookUpTable(1, 256, CV_8U);
uchar* pData = lookUpTable.data;
// 建立映射表
for (int i = 0; i < 256; ++i)
{
pData[i] = LutTable[i];
}
// 应用索引表进行查找
LUT(src, lookUpTable, temp);
return temp;
}
LUT函数介绍(来自opencv官网文档):数组的查找表转换。
函数LUT以来自查找表的值填充输出数组。每个像素的索引是从输入数组中获取的。也就是说,这个函数处理src的每个元素如下:
dst(I)←lut(src(I) + d)
参数:
src输入阵列的8位元素。256个元素的lut查找表;
在多通道输入阵列的情况下,该表应该有一个单独的通道(在本例中为所有通道使用相同的表)或与输入阵列相同的通道数量。与src相同大小和通道数量的dst输出阵列,和lut的深度相同。
总之,单通道使用单个映射表;多通道输入时,可以多个通道使用同一个映射表,也可以使用多个映射表(看个人需求),上例三通道图像使用同一个表,输出图像的通道和映射表的深度有关。
LUT方法通过映射关系表大大减少相关操作的时间复杂度,是这五种方法中最快的处理方式,常用于(替换、反转、赋值、阈值、二值化、灰度变换等)图像操作。
参考博客:https://blog.csdn.net/y363703390/article/details/79431450
总结:
本人选取一张1920x1080测试时间结果如下(取10次的平均值):
一:0.965547秒 、
二:0.11931秒 、
三:1.04746秒 、
四:0.0185972秒、
五: 0.0176833秒、
很明显后两种速度优于前三种,对要求速度快的朋友,推荐使用后两种。本人曾尝试选取过2560x1600的图片测试,LTU比isContinuous()的方法还是快了许多。
代码文件地址:https://download.csdn.net/download/stack_moon/10549645
运行环境为WINDOWS10 VS2015,opencv3.3.0。
如有错误欢迎指出,本人邮箱:972839018@qq.com。