openCV2 第三讲 遍历图像1

遍历图像 

            首先,对于遍历图像,我们主要可以采用两种方式,第一种是通过指针的方式进行遍历图像,第二种主要是通过迭代器的方式来遍历图像。但是在遍历图像之前,我们需要考虑一个问题,这个问题就是:对于一个图像来说,他的颜色数目太过于多,特别是对于彩色图像来说,如果每个通道都是用一个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();

好了,本讲就讲到这,下次在讲述用迭代器进行图像遍历。













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值