遍历图像
首先,对于遍历图像,我们主要可以采用两种方式,第一种是通过指针的方式进行遍历图像,第二种主要是通过迭代器的方式来遍历图像。但是在遍历图像之前,我们需要考虑一个问题,这个问题就是:对于一个图像来说,他的颜色数目太过于多,特别是对于彩色图像来说,如果每个通道都是用一个8位的unsignal char来表示的,那么所有可能的颜色数目就为256X256X256.是一个很庞大的数目,所以我们在遍历图像之前,为了降低分析的复杂度,适当的降低颜色的数目是有用的。
现在来介绍一下我将要采用的方法。假如图像中第i行,第j列的像素的像素值为k[i][j。那么我们现在将像素值这样变换k[i][j]=k[i][j]/div*div+div/2;(其中div表示缩小的倍数)。例如div=10,那么像素值在0~9的像素此时的值都变成5,像素值在10~19的像素值此时都等于15,如此类推,那么这样颜色的数目就大大降低了。
至此,我开始讲如何遍历图像,这里我将讲述如何用指针来遍历图像(结合缩减颜色数目)。先来看看下面的实现程序。
<pre name="code" class="cpp"><span style="font-size:18px;">#include<opencv2/highgui/highgui.h>
#include<opencv2/core/core.hpp>
void colorReduce(cv::Mat &image,int div=64){
int n1=image.rows;
int nc=image.cols*image.channels();
for(int j=0;j<n1;j++){
uchar* data=image.ptr<uchar>(j);
for(int i=0;i<nc;i++){
data[i]=data[i]/div*div+div/2;
}
}
}
int main(){
cv::Mat image=cv::imread("qie.jpg");
cv::namedWindow("原图");
cv::namedWindow("缩减图");
cv::imshow("原图",image);
colorReduce(image);
cv::imshow("缩减图",image);
cv::waitKey(0);
return 0;
}</span>
上述图片分别是缩减图和原图。我们主要是通过两个for循环语句来遍历整个图像的像素的。具体每一步的功能是什么,我现在来一一解释。
首先,我从 void colorReduce(cv::Mat &image, int div=64)函数说起,这个函数就是来实现图像遍历和颜色缩减的功能。其中第二个参数div=64表示默认的缩减因子为64.且大家注意到没,该函数的返回值为void,但函数参数中应用了cv::Mat &,那么表示在此函数中对图像进行修改后,那么输入进来的实参图像也就被修改了,所以下面main函数中调用colorReduce函数后image就发生改变了。
好了,我们接着往下看,int n1=image.rows;表示将输入图像的行数赋给了n1;对于int nc=image.cols*image.
channels(),这一句为什么是这样写呢?因为对于一个多通道图像来说他每行的的像素是这样来排列的。首先是第一个像素的B通道,接着是G通道,再是R通道,接着是第二个像素的B通道,G通道,R通道,如此循环下去。所以nc等于图像的列数乘以通道数。
再往下面看,第一个for循环,是用来遍历每一行的,然后将每一行的首地址赋给指针data,于是就有了 uchar* data=image.ptr<uchar>(j)。为了简化运算,cv::Mat提供了ptr函数可以得到图像任意行的首地址。ptr是一个模板函数,他返回第j行的首地址。
然后下面的for循环是用来遍历每一行中的每一个像素,是一个一个通道的来遍历。data[i]表示的就是第i个通道,然后就是对他就行缩减,缩减函数的原理在上面已经讲过,这里我就不在啰嗦了。这样colorReduce函数就介绍完了,main函数的功能已经在前面几章讲过。这里也不再啰嗦了,唯一要提一下的是,在colorReduce(mage)函数调用时,我们并没有写入第二个参数,因为有默认的值64,所以不需要写,如果此时传入一个值K的话,那么K将代替默认值64,(如果此处不懂得话,其查看C++相关内容)还有只有在调用了colorReduce 函数后image才会发生改变。所以colorReduce前后的两次显示image图像已经不一样了。
知识拓展:
1.step表示以字节为单位的图像的宽度。
2.elemSize表示像素的大小,列如对于一个三通道的short型矩阵(CV_16SC3),他的elemSize返回值为6,total函数返回的是矩阵的像素个数。
3.除了上述的颜色缩减公式以外,还有其他的缩减公式这里我在举几个缩减公式。
(1). data[i]=data[i]-data[i]%div+div/2;(这种也可以实现这种功能,但是运算速度比较慢,耗时比较长)。
(2). uchar mask=0xFF<<n;//例如div=16;,mask=0xF0;
data[i]=(data[i]&mask)+div/2;(&是与运算)。
4.拷贝:深拷贝与浅拷贝.
cv::Mat image1,image2; image2=image1;这种直接复制表示的是浅拷贝,浅拷贝的含义是两个对象指向同一个内存单元,其中一个对象发生改变,那么另外一个也会发生改变。 然而对于copyTo和clone()。这两种表示的都是深拷贝。拷贝后的两个图像占有不同的内存单元。result。copyTo(image3);表示将result里面的内容拷贝到image3中,
image4=image3.clone();表示将image3的内容拷贝到image4中,这两种拷贝都是深拷贝,拷贝后的两个图像占不同的内存单元。
5.create()函数的运用。
我们用create()函数创造出一个与输入图像尺寸和类型相同的矩阵。方法:result.create(image.rows,image.cols,
images.type());首先create()函数先要判断result与image尺寸和类型是否一样,如果一样,就不执行,不一样就把result变成和image一样。
6.高效遍历连续图像。
有时,为了考虑到效率问题,我们会在行尾扩大若干个像素。但是如果我们没有对行进行补充的话,图像可以视为一个长为WXH的一维数组。这样我们就可以通过成员函数isContinuous来判断这幅图像是否进行了行补充,如果isContinous方法返回值为真的话,则该图像没有进行行补充,这种情况下我们可以采用一种高效的遍历方法。
缩减函数可以重写成这样:
void colorReduce(cv::Mat &image,int div=64){
int n1=image.rows;
int nc=image.cols*image.channels();
if(image.isContinuous()){
nc=nc*n1;
n1=1;
}
for(int j=0;j<n1;j++){
uchar *data=image.ptr<uchar>(j);
for(int i=0;i<nc;i++){
data[i]=data[i]/div*div+div/2;
}
}
}
上述程序的主要意思就是如果isContinuous()函数的结果为真的话,就将原来的矩阵变成一维向量,然后再进行遍历,这样从而消除了外层循环。7.底层指针。
uchar *data=image.data;是将矩阵第一行的的首地址赋给了data。
如果想从当前行到下一行可以通过对指针加上行宽完成:data+=image.step;
要是想获得第j行,第i列的像素的地址可以采用这样的方法:data=image.data+j*step+i*image.slemSize();
好了,本讲就讲到这,下次在讲述用迭代器进行图像遍历。