新建 Qt控制台应用项目,以椒盐噪声为内容,展示对灰度图像和三通道彩色图像中像素的引用,main.cpp 代码段,如下:
#include <QCoreApplication> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; void salt(Mat &source, int n) { for( int k = 0; k < n; k ++) { int i = rand()%source.cols; int j = rand()%source.rows; if ( source.channels() == 1 ) { source.at<uchar>(j, i) = 255;//灰度图像 } else if ( source.channels() == 3 ) { source.at<Vec3b>(j, i)[0] = 255;//彩色图像的三个通道 source.at<Vec3b>(j, i)[1] = 255; source.at<Vec3b>(j, i)[2] = 255; } } } int main() { Mat src = imread("lena.jpg",1); salt(src, 3000); namedWindow("src",0); imshow("src",src); waitKey(0); return 0; }
1、rand() 函数用来取随机数,函数之后的求余符号用来保证所求的的随机数大小不超过图片行和列的范围。
2、source.at<uchar>(j,i) 灰度图像只有一个通道,所以在类型上为 uchar,并且没有数组下标。
3、source.at<Vec3b>(j,i)[ 0~2 ] 三通道彩色图像有三个通道,类型 Vec3b 是由三个 uchar 组成的向量,于是有数组下标。
4、salt( &src, n ) 函数中的 n 是产生随机数的次数。
关于简洁引用图片像素的方法:
对于已经知道 Mat 类的图片数据类型的情况下,OpenCV 提供一个模板类 cv::Mat_ ,这样使得引用像素更加简洁:
Mat_<uchar> img = src;//img指向src img(50,100) = 0;//第50行,100列
但是,上面这种引用方法只对于较小的图片,可以起到在引用的时候比较方便、简洁,但是在实际的图像处理过程中,图片都是比较大的,并且很多情况下要求遍历整张图片上的所有像素,这样上述的方法就会非常低效,下面介绍指针遍历图像像素的方法,以处理图片每个像素为内容,main.cpp 代码段如下:
#include <QCoreApplication> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; void colorReduce(Mat &source, int div = 64) { int row = source.rows;//行数 int rowN = source.cols*source.channels();//每行元素的个数 for ( int j = 0; j < row; j ++ ) { uchar* data = source.ptr<uchar>(j);//得到第j行的首地址 for ( int i = 0; i < rowN; i ++ ) { data[i] = data[i]/div*div+div/2;//对每个像素进行处理 } } } int main() { Mat src = imread("lena.jpg",1); colorReduce(src); namedWindow("src",0); imshow("src",src); waitKey(0); return 0; }
1、colorReduce() 函数第一个参数为输入的原图像,第二个参数为处理像素值的除法系数。
2、source.ptr<uchar>(j) 是 Mat 对象中的一个方法,要用尖括号指定数据类型,用圆括号指定指针所指向的某行的首地址。
3、在获得首地址之后,元素的列个数就可以用数组指针来确定,即 data[i] 。
4、在做数据运算的时候,位运算效率最高,因此大家可以自行考虑位运算方法。
指针索引方法之所以在处理速度上较快是因为,对于遍历某一行的所有像素,只需要做一次行地址索引,而第一种方法则要做列数次的行索引,所以效率一比就知道了。
更高效的遍历连续图像
在 Mat 类中,有个方法 isContinuous() 用来判断内存是否对图像进行了行填补,返回值为真则没有进行填补,说明图像是连续的,修改后的colorReduce如下:
void colorReduce(Mat &source, int div = 64) { int row = source.rows;//行数 int rowN = source.cols*source.channels();//每行元素的个数 if ( source.isContinuous() ) { rowN = rowN*row;//如果图像是连续的,则将整个图像的像素以一维数组排列计数 row = 1; } for ( int j = 0; j < row; j ++ )//对于连续图像本循环只执行一次 { uchar* data = source.ptr<uchar>(j); for ( int i = 0; i < rowN; i ++ ) { data[i] = data[i]/div*div+div/2;//对每个像素进行处理 } } }
void colorReduce(Mat &source, int div = 64) { if ( source.isContinuous() ) { source.reshape(1,source.cols*source.rows); } int row = source.rows;//行数 int rowN = source.cols*source.channels();//每行元素的个数 for ( int j = 0; j < row; j ++ )//对于连续图像本循环只执行一次 { uchar* data = source.ptr<uchar>(j); for ( int i = 0; i < rowN; i ++ ) { data[i] = data[i]/div*div+div/2;//对每个像素进行处理,也可以这样 *data++ = *data/div*div + div/2 } } }
使用迭代器遍历图像
一个 Mat 类的迭代器可以通过 MatIterator_ 类来得到,这个类类似于 Mat_ 类,因此这两个类都是OpenCV的模板类。同样要求预先知道类对象的数据类型,使用迭代器模板类修改后的代码如下:
void colorReduce(Mat &source, int div = 64) { Mat_<Vec3b>::iterator it = source.begin<Vec3b>();//初始位置的迭代器 Mat_<Vec3b>::iterator itEnd = source.end<Vec3b>();//结束位置的迭代器 //也可以这样定义: MatIterator_<Vec3b> it; for ( ; it != itEnd; ++it )//一次循环遍历所有元素 { (*it)[0] = (*it)[0]/div*div+div/2; (*it)[1] = (*it)[1]/div*div+div/2; (*it)[2] = (*it)[2]/div*div+div/2; } }
Mat_<Vec3b>::const_iterator it; MatConstIterator_<Vec3b> it;