简述
我们在图像处理时经常会用到遍历图像像素点的方式,同样是遍历图像像素点,共有很多中方法可以做到;在这些方法中,有相对高效的,也有低效的;不是说低效的方法就不好,不同场景使用不同方法。
方法
下面将一一介绍这些遍历图像像素点的方法:
方法一:数组遍历法1
图像Mat中每个像素点,其实就是一个值(int、float、double、uchar等类型),而Mat是一个二维数组。
1、单通道图像(CV_8UC1);backImg是单通道灰色图。
int height = backImg.rows;
int width = backImg.cols;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int index = i * width + j;
//像素值
int data = (int)backImg.data[index];
}
}
2、三通道图像(CV_8UC3);backImg、colorImg均为三通道彩色图,且大小相同。
int height = backImg.rows;
int width = backImg.cols;
//差值图
//三个通道中只要有一个差值大于阈值,就进行提取
Mat subImg(height, width, CV_8UC1);
subImg.setTo(0);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int index = i * width + j;
//背景
int b1 = (int)backImg.data[3 * index + 0];
int g1 = (int)backImg.data[3 * index + 1];
int r1 = (int)backImg.data[3 * index + 2];
//当前图
int b2 = (int)colorImg.data[3 * index + 0];
int g2 = (int)colorImg.data[3 * index + 1];
int r2 = (int)colorImg.data[3 * index + 2];
//th为阈值
if (g1 - g2 > th || b1 - b2 > th || r1 - r2 > th)
{
subImg.data[index] = 255;
}
}
}
方法二:数组遍历法2 -- at<typename>(i,j)
Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。
void colorReduce(Mat& image)
{
for(int i=0;i<image.rows;i++)
{
for(int j=0;j<image.cols;j++)
{
if(image.channels() == 1)
{
//取出灰度图像中i行j列的点
image.at<uchar>(i,j) = image.at<uchar>(i,j) / 2;
}
else if(image.channels() == 3)
{
//取出彩色图像中i行j列第k(0/1/2)通道的颜色点
image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0] / 2;
image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1] / 2;
image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2] / 2;
}
}
}
}
方法三:指针遍历法
1、这种图像遍历方法相比“数组遍历法”更高效。
void colorReduce(Mat& srcImg,Mat& dstImg)
{
dstImg = srcImg.clone();
dstImg.setTo(0);
int height = srcImg.rows;
// 将3通道转换为1通道
int width = srcImg.cols * srcImg.channels();
for(int k = 0;k < height; k++)
{
// 获取第k行的首地址
const uchar* inData = srcImg.ptr<uchar>(k);
uchar* outData = dstImg.ptr<uchar>(k);
//处理每个像素
for(int i = 0; i < width; i++)
{
outData[i] = inData[i] / 2;
}
}
}
程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。
但是这种用法不能用在行与行之间,因为图像在opencv里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。
2、但是我们可以申明一个连续的空间来存储图像,这样效率就会更高。图像可以是连续的,也可以是不连续的,Mat提供了一个检测图像是否连续的函数isContinuous(),当图像连通时,我们就可以把图像完全展开,看成是一行。
void colorReduce(Mat& srcImg,Mat& dstImg)
{
int height = srcImg.rows;
int width = srcImg.cols;
dstImg = srcImg.clone();
dstImg.setTo(0);
//判断图像是否连续
if(srcImg.isContinuous() && dstImg.isContinuous())
{
height = 1;
width = width * srcImg.rows * srcImg.channels();
}
for(int i = 0; i< height; ++i)
{
const uchar* inData = srcImg.ptr<uchar>(i);
uchar* outData = dstImg.ptr<uchar>(i);
for(int j = 0; j< width; ++j)
{
outData[j] = inData[j] / 2;
}
}
}
方法四:迭代器遍历法
1、迭代器Matlterator_
Matlterator_是Mat数据操作的迭代器,:begin()表示指向Mat数据的起始迭代器,:end()表示指向Mat数据的终止迭代器。迭代器方法是一种更安全的用来遍历图像的方式,首先获取到数据图像的矩阵起始,再通过递增迭代实现移动数据指针。
//三通道彩色图
void colorReduce(Mat srcImage)
{
Mat tempImage = srcImage.clone();
// 初始化原图像迭代器
MatConstIterator_<Vec3b> srcIterStart = srcImage.begin<Vec3b>();
MatConstIterator_<Vec3b> srcIterEnd = srcImage.end<Vec3b>();
// 初始化输出图像迭代器
MatIterator_<Vec3b> resIterStart = tempImage.begin<Vec3b>();
MatIterator_<Vec3b> resIterEnd = tempImage.end<Vec3b>();
while (srcIterStart != srcIterEnd)
{
(*resIterStart)[0] = 255 - (*srcIterStart)[0];
(*resIterStart)[1] = 255 - (*srcIterStart)[1];
(*resIterStart)[2] = 255 - (*srcIterStart)[2];
srcIterStart++;
resIterStart++;
}
}
//单通道灰色图
void colorReduce(Mat srcImage)
{
Mat tempImage = srcImage.clone();
// 初始化原图像迭代器
MatConstIterator_<uchar> srcIterStart = srcImage.begin<uchar>();
MatConstIterator_<uchar> srcIterEnd = srcImage.end<uchar>();
// 初始化输出图像迭代器
MatIterator_<uchar> resIterStart = tempImage.begin<uchar>();
MatIterator_<uchar> resIterEnd = tempImage.end<uchar>();
while (srcIterStart != srcIterEnd)
{
*resIterStart = 255 - *srcIterStart;
srcIterStart++;
resIterStart++;
}
}
2、迭代器Mat_
OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。
void colorReduce(Mat &img)
{
uchar t;
//单通道图像
if(img.channels() == 1)
{
Mat_<uchar>::iterator it = img.begin<uchar>();
Mat_<uchar>::iterator itend = img.end<uchar>();
while(it != itend)
{
//相关操作
*it = 0;
it++;
}
}
//三通道图像
else if(img.channels() == 3)
{
Mat_<Vec3b>::iterator it = img.begin<Vec3b>();
Mat_<Vec3b>::iterator itend = img.end<Vec3b>();
while(it != itend)
{
//相关操作
(*it)[0] = 0;
(*it)[1] = 1;
(*it)[2] = 2;
it++;
}
}
}
//单通道图像(CV_8UC1)src是单通道灰色图
//作用:统计图像中白色像素个数
int BaseFun::whiteSums(Mat src)
{
int counter = 0;
//迭代器访问像素点
Mat_<uchar>::iterator it = src.begin<uchar>();
Mat_<uchar>::iterator itend = src.end<uchar>();
for (; it!=itend; ++it)
{
if((*it)>0) counter+=1;//二值化后,像素点是0或者255
}
return counter;
}
方法五:Mat_数组遍历法(非迭代器法,Mat_迭代器法已在方法四中介绍)
Mat_也可以通过下标的方式来遍历图像,但是相比Mat::at()方法,无疑Mat_通过下标遍历图像是慢的,不推荐这种方式,如果要使用Mat_,最好只是使用迭代器Mat_<>::iterator。
//单通道图像
void colorReduce(Mat srcImg)
{
int height = srcImg.rows;
int width = srcImg.cols;
//转化为Mat_<uchar>
Mat_<uchar> tempImg = (Mat_<uchar>&)srcImg;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
tempImg(i,j) = tempImg(i,j) * 2;
printf("%d ",tempImg(i,j));
}
printf("\n");
}
}
//三通道图像
void colorReduce(Mat srcImg)
{
Mat_<Vec3b> tempImg = (Mat_<Vec3b>&)srcImg;
int height = srcImg.rows;
int width = srcImg.cols;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
printf("%d ",tempImg(i,j)[0]);
printf("%d ",tempImg(i,j)[1]);
printf("%d ",tempImg(i,j)[2]);
}
printf("\n");
}
}
Mat_<uchar>对应的是CV_8U,Mat_<char>对应的是CV_8S,Mat_<int>对应的是CV_32S,Mat_<float>对应的是CV_32F,Mat_<double>对应的是CV_64F,对应的数据深度如下:
• CV_8U - 8-bit unsigned integers ( 0..255 )
• CV_8S - 8-bit signed integers ( -128..127 )
• CV_16U - 16-bit unsigned integers ( 0..65535 )
• CV_16S - 16-bit signed integers ( -32768..32767 )
• CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
• CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
• CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
附一张opencv官方给出的关于Mat_解释的图片:
总结
目前找到了这些方式来遍历图像像素,全部亲测可用。如有朋友发现新的遍历方法,可一起交流。
本文部分内容参考博客:http://www.cnblogs.com/ronny/p/opencv_road_2.html,感谢这位博主。