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_  

两者关系如下图:存在继承关系。

opencv读取图片获取像素数据 opencv获取图像像素_opencv读取图片获取像素数据

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()的方法优于前面三种方法,操作难度居中,推荐使用(备注:图像必须是连续的才能使用)。

图像不是连续的,可看下面这篇博客,改成连续的图像。

 

参考博客:

 

 

 

五: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函数介绍:数组的查找表转换。

函数LUT以来自查找表的值填充输出数组。每个像素的索引是从输入数组中获取的。也就是说,这个函数处理src的每个元素如下:

dst(I)←lut(src(I) + d)

opencv读取图片获取像素数据 opencv获取图像像素_数组_02

参数:

src输入阵列的8位元素。256个元素的lut查找表;

在多通道输入阵列的情况下,该表应该有一个单独的通道(在本例中为所有通道使用相同的表)或与输入阵列相同的通道数量。与src相同大小和通道数量的dst输出阵列,和lut的深度相同。

 

总之,单通道使用单个映射表;多通道输入时,可以多个通道使用同一个映射表,也可以使用多个映射表(看个人需求),上例三通道图像使用同一个表,输出图像的通道和映射表的深度有关。

LUT方法通过映射关系表大大减少相关操作的时间复杂度,是这五种方法中最快的处理方式,常用于(替换、反转、赋值、阈值、二值化、灰度变换等)图像操作。

 

 


 

总结:

本人选取一张1920x1080测试时间结果如下(取10次的平均值):

一:0.965547秒 、

二:0.11931秒 、

三:1.04746秒 、

四:0.0185972秒、

五: 0.0176833秒、

 

很明显后两种速度优于前三种,对要求速度快的朋友,推荐使用后两种。本人曾尝试选取过2560x1600的图片测试,LTU比isContinuous()的方法还是快了许多。

代码文件地址:

 

运行环境为WINDOWS10 VS2015,opencv3.3.0。