OpenCV对像素的操作

来源:互联网 发布:知阅小说网 vip账号 编辑:程序博客网 时间:2024/05/10 20:20

一、访问像素值

利用Mat的at函数可以访问元素。因为Mat可以接受任何类型的元素,所以at函数被实现成一个模板函数,在调用时必须指定图像元素的类型:

image.at<uchar>(j,i)=0;//或,对于彩色图像image.at<cv::Vec3b>(j,i)[channel]=0;

channel索引用来指明三个通道的一个。因为彩色图像有3个通道,所以访问彩色图像的像素会返回一个向量。OpenCV定义这个短向量为cv::Vec3b,代表3个8位的数值。此外还有针对其它元素类型的向量,例如,浮点数类型cv::Vec3f。对于整型,最后的字母替换成i;对于短整型,替换成s;对于双精度,替换成d。

为了避免at函数冗长的感觉,可以使用cv::Mat的模板子类cv::Mat_,它可以直接通过operator()访问元素:

cv::Mat_<uchar> im(image);im(1,1) = 0;//或,对于彩色图像cv::Mat_<cv::Vec3b> im(image);im(1,1) [channel]= 0;

值得一提的是,at方法适合对像素点进行随机访问,当涉及像素的遍历时,考虑处理的性能,应该使用更高效的方法。

二、用指针遍历像素

int nl = image.rows;int nc = image.cols*image.channels();for (int j = 0; j < nl;j++) {    uchar* pt = image.ptr<uchar>(j);    for (int i = 0; i < nc; i++) {        //对像素进行某些操作        pt[i]=0;    } }

如果弄清楚彩色图像中像素值数据的排列顺序,那么上述代码很容易理解。图像数据缓冲区的前3个字节表示左上角像素的三色通道,接下来的3个字节为第一行的第二个像素,依次类推。

对于连续图像,因为图像整个像素数据存储的地址是连续的,上述代码可改写为单个for循环:

uchar* pt = image.ptr<uchar>(0);for (int i = 0; i < image.rows*image.cols*image.channels(); i++) {        //对像素进行某些操作        pt[i]=0;}

这里要澄清一个概念,什么是连续图像?一个宽W高H的图像所需内存为W×H×3 uchar。然而出于性能的考虑,我们会用几个额外的像素来填补一行的长度,因为有些芯片处理图像时,若行的长度是4或8的整数倍,处理的性能就会更高。所以OpenCV Mat数据结构会有一个属性:step。直译就是步长,应该叫做有效宽度。当图像是连续图像时,即没有无效的数据填补行时,有效宽度等于实际的图像宽度。

检查图像的连续性,可以这样做:

//检查行的长度(字节数)与“列的个数×单个像素的字节数”是否相等if(image.step==image.cols*image.elemSize()) {}

三、用迭代器遍历像素

cv::Mat_<uchar>::iterator it=image.begin<uchar>();cv::Mat_<uchar>::iterator itend=image.end<uchar>();for(;it!=itend;it++) {    //对像素进行某些操作    (*it)=0;}

迭代器也可以这样定义:cv:MatIterator_<uchar> it;

使用迭代器的主要目的是简化代码,降低出错的可能性,测试显示,在时间效率上与指针操作的方式相比差一些。

四、邻域操作或块操作下的像素遍历

这里假设需要同时访问当前像素以及上下左右相邻的像素。

int nchannels=image.channels();//处理除了首行和末行以外的所有行for(int j=1;j<image.rows-1;j++) {    uchar* previous=image.ptr<uchar>(j-1);//上一行    uchar* current=image.ptr<uchar>(j);//当前行    uchar* next=image.ptr<uchar>(j+1);//下一行    //除去第一列和最后一列不处理    for(int i=nchannels;i<(image.cols-1)*nchannels;i++) {        current[i]=0;//当前像素        current[i-nchannels]=0;//相邻左像素        current[i+nchannels]=0;//相邻右像素        previous[i]=0;//相邻上像素        next[i]=0;//相邻下像素    }}

对像素邻域进行计算时,通常用一个核算子来表示。核算子的大小就是邻域大小(比如3×3),核算子每个单元格表示相关像素的乘法系数,像素应用核算子得到的结果,就是这些乘积的累加。核算子定义的运算叫做核运算(和卷积相似),这种操作叫做滤波。OpenCV里定义了相关的函数,即cv::filter2D。

五、操作像素值(滤波,混合等)

这里把两幅图像加权混合,可以使用cv::addWeighted函数:cv::addWeighted(image1,0.4,image2,0.7,0.,result);

下面就是两幅输入图像:
图像1

图像2

混合的结果为:
这里写图片描述

在OpenCV2中,大多数运算函数有对应的重载运算符。因此加权混合也可写为:result=0.4*image1+0.9*image2;

注意做加法的结果并不会使输出像素值超出255,因为函数调用了cv::saturate_cast函数。在对像素运算的场合都要使用这个函数,以确保结果在预定的像素范围之内。使用方法类似C++里的类型转换,比如results.at<uchar>(j,i)=cv::saturate_cast<uchar>(0.9*image1.at<uchar>(j,i)+0.9*image2.at<uchar>(j,i));

五、操作像素位置(重映射、变形等)

这类操作不会修改像素值,而是把每个像素的位置重新映射到新的位置。需要使用到OpenCV的remap函数。

//映射参数cv::Mat srcX(image.rows,image.cols,CV_32F);cv::Mat srcY(image.rows, image.cols, CV_32F);//创建映射参数for (int j = 0; j < image.rows;j++) {    for (int i = 0; i < image.cols; i++) {        srcX.at<float>(j, i) = i;//保持在同列        srcY.at<float>(j, i) = j+7*sin(i/10.0);//保持在同列    }}cv::Mat result;cv::remap(image,result,srcX,srcY,cv::INTER_LINEAR);

为了构建新图像,需要知道新图像的每个像素在源图像中的原始位置。因此需要这样的映射函数,它能根据像素的新位置得到像素的原始位置,这个过程叫做反向映射。可以用两个映射参数来描述,在这里是srcX,srcY,一个针对x坐标,一个针对y坐标。值得注意的是参数包含的值为浮点数,即说明反向映射后的坐标可能并不为整数,这就需要使用像素插值技术,remap函数的最后的参数指明了使用的插值方法。

下面是一副输入图像:
这里写图片描述

运行程序的结果图像为:
这里写图片描述

0 0
原创粉丝点击