本次学习主要醋和操作图像的基本元素,如何遍历一张图像且处理其像素,并且在编程过程中,要考虑程序执行效率的问题!
像素是由8位无符号数来表示,其中0表示黑色,255代表白色。对于彩色图来说,每个像素需要三个这样的8位无符号数来表示三个颜色通道(红绿蓝),所以矩阵的元素是一个三元数。保存不同像素类型有整形(CV_8U)浮点型(CV_32F)
1、存取像素值
为了存取矩阵元素,需要访问矩阵的行和列。如果图像是单通道,返回值是单个值;如果图像是多通道,返回值是一组向量(Vector)。此例在图像中加入校验噪点,椒盐噪点就是随机的把部分像素设为白色或者黑色。在传输过程中,如果部分像素丢失,就会出现噪点。我们随机在图片中随机挑出若干像素,将其设置为白色。
首先我们创建一个salt函数,它的形参为一张图image和白噪点的个数n。我们用随机函数rand()结合图像的宽高随机选取一个点设置为噪点,用at访问该点,设置为白色。我们要设置n个噪点,那可以使用循环循环n次。另外,由于图像不知道是灰度图还是彩色图,我们要对其判断,程序如下:
- <span style="font-size:10px;"><span style="font-size:10px;">#include <iostream>
- #include<opencv2/core/core.hpp>
- #include<opencv2/highgui/highgui.hpp>
- using namespace cv;
- using namespace std;
- int main()
- {
- void salt(Mat &image,int);//函数声明
- Mat image=imread("bridge.jpg");
- if(!image.data){
- cout<<"imread error";
- return 0;
- }
- namedWindow("bridge");
- //调用函数
- salt(image,3000);
- imshow("bridge",image);
- waitKey(0);
- return 1;
- }
- void salt(Mat &image,int n){//n为白噪点个数
- for(int k=0;k<n;k++){
- int j=rand()%image.rows;//行
- int i=rand()%image.cols;//列
- if(image.channels()==1){
- image.at<uchar>(j,i)=255;//at可以存取图像元素
- }else if(image.channels()==3){
- //设置三通道噪点颜色
- image.at<Vec3b>(j,i)[0]=255;
- image.at<Vec3b>(j,i)[1]=255;
- image.at<Vec3b>(j,i)[2]=255;
- }
- }
- }
- </span></span>
注:
1、intj=rand()%image.rows;inti=rand()%image.cols;
由于随机数生成的数范围不确定,我们使用上述两条语句(取余)可以使得j,i不会超出image.rows;image.cols范围内
2、单通道元素访问image.at<uchar>(j,i)=255
多通道元素访问image.at<Vec3b>(j,i)[channel]=value;
3、cv::Mat_的使用重载操作符()
cv::Mat_<uchar>im2=image;//im2指向image
im2(50,100)=0;//存取50行100列元素
2、指针遍历图像
在图像中,我们经常要遍历图像进行操作,考虑像素有可能非常多,所以高效的遍历图像是很必要的。首先使用的是指针算术。
例子:减少图像中的颜色数目
算法:对图像中像素每一个通道,将其值除以N(整数出发,舍去余数),在乘以N,得到不大于原始图像的N的最大倍数。对每个8位通道的值进行上述操作,可得到共计256/N*256/N*256/N个颜色值。程序如下:
方法一:指针运算
- void colorReduce1(Mat&image, int div=64){
- int nl=image.rows;//行数
- int nc=image.cols*image.channels();//列数:彩色图channel为3
- for(int j=0;j<nl;j++){
- //ptr访问j行地址
- uchar *data=image.ptr<uchar>(j);
- for(int i=0;i<nc;i++){
- data[i]=data[i]/div*div+div/2;//算法
- //*data++=*data/div*div+div/2;
- //data[i]=data[i]-data[i]%div+div/2 计算速度变慢存取每个像素两次
- }
- }
- }
方法二:位运算
- void colorReduce2(Mat&image, int div=64){
- int nl=image.rows;
- int nc=image.cols*image.channels();
- //位运算
- intn=static_cast<int>(log(static_cast<double>(div))/log(2.0));
- for(int j=0;j<nl;j++){
- uchar *data=image.ptr<uchar>(j);
- for(int i=0;i<nc;i++){
- //用来对像素取整的掩膜
- uchar mask=0xFF<<n;
- data[i]=(data[i]&mask)+div/2;
- }
- }
- }
方法三:高效遍历连续图像
图像在行尾不进行填补时,图像可认为是一个长为W*H的一位数组,我们通过cv::Mat成员函数isContinue来判断,如果图像连续,整个处理使用一次循环完成。程序如下
- void colorReduce3(Mat&image, int div=64){
- int nl=image.rows;
- int nc=image.cols*image.channels();
- //判断图像是否连续
- if(image.isContinuous()){
- nc=nc*nl;
- nl=1;//一维数组
- }
- for(int j=0;j<nl;j++){
- uchar *data=image.ptr<uchar>(j);
- for(int i=0;i<nc;i++){
- //处理每个像素
- data[i]=data[i]/div*div+div/2;
- }
- }
- }
方法四:底层指针运算 (不建议容易出错)
- void colorReduce4(Mat&image, int div=64){
- int nl=image.rows;
- int nc=image.cols*image.channels();
- //判断图像是否连续
- if(image.isContinuous()){
- nc=nc*nl;
- nl=1;//一维数组
- }
- for(int j=0;j<nl;j++){
- uchar *data=image.data;
- for(int i=0;i<nc;i++){
- data=image.data+i*image.step+i*image.elemSize();
- //data+=image.step;
- }
- }
- }
方法五:使用迭代器遍历图像
- void colorReduce5(Mat&image,int div=64){
- //迭代器的初始化 常量迭代器cv::Mat_<cv::Vec3b>::const_iterator it
- Mat_<Vec3b>::iteratorit=image.begin<Vec3b>();
- Mat_<Vec3b>::iteratoritend=image.end<Vec3b>();
- 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;
- }
- }
方法六:at访问
- void colorReduce6(cv::Mat&image, int div=64) {
- int nl= image.rows;// number of lines
- int nc= image.cols;// number of columns
- for (int j=0; j<nl; j++) {
- for (int i=0; i<nc; i++) {
- // process each pixel---------------------
- image.at<cv::Vec3b>(j,i)[0]=image.at<cv::Vec3b>(j,i)[0]/div*div+ div/2;
- image.at<cv::Vec3b>(j,i)[1]=image.at<cv::Vec3b>(j,i)[1]/div*div+ div/2;
- image.at<cv::Vec3b>(j,i)[2]=image.at<cv::Vec3b>(j,i)[2]/div*div + div/2;
- }
- }
- }
方法七
- void colorReduce7(constcv::Mat &image, cv::Mat &result, int div=64) {
- int nl= image.rows;// number of lines
- int nc= image.cols ;// number of columns
- // allocate outputimage if necessary
- result.create(image.rows,image.cols,image.type());
- // created imageshave no padded pixels
- nc= nc*nl;
- nl= 1; // it is now a 1D array
- int n=static_cast<int>(log(static_cast<double>(div))/log(2.0));
- // mask used to roundthe pixel value
- uchar mask=0xFF<<n; // e.g. for div=16, mask= 0xF0
- for (int j=0; j<nl; j++) {
- uchar* data= result.ptr<uchar>(j);
- constuchar* idata= image.ptr<uchar>(j);
- for (int i=0; i<nc; i++) {
- // process each pixel--------------------
- *data++= (*idata++)&mask + div/2;
- *data++= (*idata++)&mask +div/2;
- *data++= (*idata++)&mask +div/2;
- }
- }
- }
注:
迭代器是一种特殊的类,他专门用来遍历集合中的各个元素,同时隐藏了在给定集合上元素迭代的具体实现方式。
一个迭代器的声明如下:
cv::MatIterator_<cv::Vec3b> it;
另外一种使用定义在Mat_内部的迭代器类型:
cv::Mat_<cv::Vec3b>::Iterator it;
这样就可以用常规的begin和end这两个迭代器方法遍历所以图像。如果你想从图像的第二行开始,可以用image.begin<cv::Vec3b>()+image.rows来初始化迭代器。
常量迭代器的声明:
cv::MatConstIterator_<cv::Vec3b>it;
cv::Mat_<cv::Vec3b>::const_iteratorit;
3、高效遍历循环
cv::getTickCount()
测量一段代码的运行时间,这个函数返回从上次开始算起的时钟周期!我们测量的是代码运行的毫秒数,还需要另一个函数cv::getTickFrequency(),此函数返回每秒钟内的时钟周期数。统计代码运行时间如下:
- double duration;
- duration=static_cast<double>(getTickCount());
- colorReduce1(imageclone);
- duration=(static_cast<double>(getTickCount())duration/getTickFrequency();
前三小结总的程序如下
- #include<opencv2/core/core.hpp>
- #include<iostream>
- #include<opencv2/highgui/highgui.hpp>
- usingnamespacecv;
- usingnamespacestd;
- intmain()
- {
- //函数声明
- voidcolorReduce1(Mat&image,intdiv=64);
- voidcolorReduce2(Mat&image,intdiv=64);
- voidcolorReduce3(Mat&image,intdiv=64);
- voidcolorReduce4(Mat&image,intdiv=64);
- voidcolorReduce5(Mat&image,intdiv=64);
- voidcolorReduce6(Mat&image,intdiv=64);
- voidcolorReduce7(Mat&image,intdiv=64);
- Matimage=imread("bridge.jpg");
- //判断是否读入图片成功
- if(!image.data){
- cout<<"dataerror!";
- }
- Matimageclone=image.clone();
- //定义一个数组存放记录不同程序运行时间
- doubleduration[8]={0};
- //6种方式运行时间程序
- duration[1]=static_cast<double>(getTickCount());
- colorReduce1(imageclone);
- duration[1]=(static_cast<double>(getTickCount())-duration[1])/getTickFrequency();
- duration[2]=static_cast<double>(getTickCount());
- colorReduce2(imageclone);
- duration[2]=(static_cast<double>(getTickCount())-duration[2])/getTickFrequency();
- duration[3]=static_cast<double>(getTickCount());
- colorReduce3(imageclone);
- duration[3]=(static_cast<double>(getTickCount())-duration[3])/getTickFrequency();
- duration[4]=static_cast<double>(getTickCount());
- colorReduce4(imageclone);
- duration[4]=(static_cast<double>(getTickCount())-duration[4])/getTickFrequency();
- duration[5]=static_cast<double>(getTickCount());
- colorReduce5(imageclone);
- duration[5]=(static_cast<double>(getTickCount())-duration[5])/getTickFrequency();
- duration[6]=static_cast<double>(getTickCount());
- colorReduce6(imageclone);
- duration[6]=(static_cast<double>(getTickCount())-duration[6])/getTickFrequency();
- duration[7]=static_cast<double>(getTickCount());
- colorReduce6(imageclone);
- duration[7]=(static_cast<double>(getTickCount())-duration[7])/getTickFrequency();
- //循环输出数组
- for(inti=1;i<=7;i++){
- cout<<"colorReduce"<<i<<"timeis:"<<duration[i]<<"(s)"<<endl;
- }
- namedWindow("Image");
- imshow("Image",image);
- namedWindow("result");
- imshow("result",imageclone);
- waitKey(0);
- return0;
- }
- //******************************************
- voidcolorReduce1(Mat&image,intdiv=64){
- intnl=image.rows;//行数
- intnc=image.cols*image.channels();//列数:彩色图channel为3
- for(intj=0;j<nl;j++){
- //ptr访问j行地址
- uchar*data=image.ptr<uchar>(j);
- for(inti=0;i<nc;i++){
- data[i]=data[i]/div*div+div/2;//算法
- //*data++=*data/div*div+div/2;
- //data[i]=data[i]-data[i]%div+div/2计算速度变慢存取每个像素两次
- }
- }
- }
- //*****************************************
- voidcolorReduce2(Mat&image,intdiv=64){
- intnl=image.rows;
- intnc=image.cols*image.channels();
- //位运算
- intn=static_cast<int>(log(static_cast<double>(div))/log(2.0));
- for(intj=0;j<nl;j++){
- uchar*data=image.ptr<uchar>(j);
- for(inti=0;i<nc;i++){
- //用来对像素取整的掩膜
- ucharmask=0xFF<<n;
- data[i]=(data[i]&mask)+div/2;
- }
- }
- }
- //***********************************
- voidcolorReduce3(Mat&image,intdiv=64){
- intnl=image.rows;
- intnc=image.cols*image.channels();
- //判断图像是否连续
- if(image.isContinuous()){
- nc=nc*nl;
- nl=1;//一维数组
- }
- for(intj=0;j<nl;j++){
- uchar*data=image.ptr<uchar>(j);
- for(inti=0;i<nc;i++){
- //处理每个像素
- data[i]=data[i]/div*div+div/2;
- }
- }
- }
- //********************************************
- voidcolorReduce4(Mat&image,intdiv=64){
- intnl=image.rows;
- intnc=image.cols*image.channels();
- //判断图像是否连续
- if(image.isContinuous()){
- nc=nc*nl;
- nl=1;//一维数组
- }
- for(intj=0;j<nl;j++){
- uchar*data=image.data;
- for(inti=0;i<nc;i++){
- data=image.data+i*image.step+i*image.elemSize();
- //data+=image.step;
- }
- }
- }
- //********************************************
- voidcolorReduce5(Mat&image,intdiv=64){
- //迭代器的初始化 常量迭代器cv::Mat_<cv::Vec3b>::const_iteratorit
- Mat_<Vec3b>::iteratorit=image.begin<Vec3b>();
- Mat_<Vec3b>::iteratoritend=image.end<Vec3b>();
- 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;
- }
- }
- //********************************************
- voidcolorReduce6(cv::Mat&image,intdiv=64){
- intnl=image.rows;//numberoflines
- intnc=image.cols;//numberofcolumns
- for(intj=0;j<nl;j++){
- for(inti=0;i<nc;i++){
- //processeachpixel---------------------
- image.at<cv::Vec3b>(j,i)[0]=image.at<cv::Vec3b>(j,i)[0]/div*div+div/2;
- image.at<cv::Vec3b>(j,i)[1]=image.at<cv::Vec3b>(j,i)[1]/div*div+div/2;
- image.at<cv::Vec3b>(j,i)[2]=image.at<cv::Vec3b>(j,i)[2]/div*div+div/2;
- }
- }
- }
- //********************************************
- voidcolorReduce7(constcv::Mat&image,cv::Mat&result,intdiv=64){
- intnl=image.rows;//numberoflines
- intnc=image.cols;//numberofcolumns
- //allocateoutputimageifnecessary
- if(image.isContinuous()){
- //createdimageshavenopaddedpixels
- nc=nc*nl;
- nl=1; //itisnowa1Darray
- }
- intn=static_cast<int>(log(static_cast<double>(div))/log(2.0));
- //maskusedtoroundthepixelvalue
- ucharmask=0xFF<<n;//e.g.fordiv=16,mask=0xF0
- for(intj=0;j<nl;j++){
- uchar*data=result.ptr<uchar>(j);
- constuchar*idata=image.ptr<uchar>(j);
- for(inti=0;i<nc;i++){
- //processeachpixel--------------------
- *data++=(*idata++)&mask+div/2;
- *data++=(*idata++)&mask+div/2;
- *data++=(*idata++)&mask+div/2;
- }
- }
- }
4、遍历图像和邻域操作
图像处理中,当邻域包含图像的前几行和下几行时,需要同时扫描图像的若干行!
例子:对图像进行锐化
它基于拉普拉斯算子,将一幅图像减去他经过拉普拉斯滤波后的图像,这幅图的边缘部分将得到放大,即细节更加锐利。图像遍历使用三个指针:一个指向当前行,一个指向上一行,一个下一行。由于每个像素值的计算都需要它的上下左右四个邻居像素,所以不可能对图像的第一行、最后一行、第一列、最后一列进行计算。锐化算子的计算方式如下:
sharpened_pixel=5*current-left-right-up-dowm;
程序如下:
- <span style="font-weight: normal;">#include<iostream>
- #include<opencv2/core/core.hpp>
- #include<opencv2/highgui/highgui.hpp>
- #include<opencv2/imgproc/imgproc.hpp>
- voidsharpen(constcv::Mat&image,cv::Mat&result){
- result.create(image.size(),image.type());//分配大小
- for(intj=1;j<image.rows-1;j++){//除了第一行和最后一行进行遍历
- constuchar*previous=image.ptr<constuchar>(j-1);//上一行
- constuchar*current=image.ptr<constuchar>(j); //当前行
- constuchar*next=image.ptr<constuchar>(j+1); //下一行
- uchar*output=result.ptr<uchar>(j);
- for(inti=1;i<image.cols-1;i++){
- *output++=cv::saturate_cast<uchar>(5*current[i]-current[i-1]-current[i+1]-previous[i]-next[i]);
- // output[i]=cv::saturate_cast<uchar>(5*current[i]-current[i-1]-current[i+1]-previous[i]-next[i]);
- }
- }
- //未处理像素设置为0
- result.row(0).setTo(cv::Scalar(0));
- result.row(result.rows-1).setTo(cv::Scalar(0));
- result.col(0).setTo(cv::Scalar(0));
- result.col(result.cols-1).setTo(cv::Scalar(0));
- }
- voidsharpen2(constcv::Mat&image,cv::Mat&result){
- result.create(image.size(),image.type());//allocateifnecessary
- intstep=image.step1();
- constuchar*previous=image.data;
- constuchar*current= image.data+step;
- constuchar*next=image.data+2*step;
- uchar*output=result.data+step;
- for(intj=1;j<image.rows-1;j++){//foreachrow(exceptfirstandlast)
- for(inti=1;i<image.cols-1;i++){//foreachcolumn(exceptfirstandlast)
- output[i]=cv::saturate_cast<uchar>(5*current[i]-current[i-1]-current[i+1]-previous[i]-next[i]);
- }
- previous+=step;
- current+=step;
- next+=step;
- output+=step;
- }
- result.row(0).setTo(cv::Scalar(0));
- result.row(result.rows-1).setTo(cv::Scalar(0));
- result.col(0).setTo(cv::Scalar(0));
- result.col(result.cols-1).setTo(cv::Scalar(0));
- }
- voidsharpen3(constcv::Mat&image,cv::Mat&result){
- cv::Mat_<uchar>::const_iteratorit=image.begin<uchar>()+image.step;
- cv::Mat_<uchar>::const_iteratoritend=image.end<uchar>()-image.step;
- cv::Mat_<uchar>::const_iteratoritup=image.begin<uchar>();
- cv::Mat_<uchar>::const_iteratoritdown=image.begin<uchar>()+2*image.step;
- result.create(image.size(),image.type());
- cv::Mat_<uchar>::iteratoritout=result.begin<uchar>()+result.step;
- for(;it!=itend;++it,++itup,++itdown){
- *itout=cv::saturate_cast<uchar>(*it*5-*(it-1)-*(it+1)-*itup-*itdown);
- }
- }
- intmain()
- {
- cv::Matimage=cv::imread("bridge.jpg",0);
- if(!image.data)
- return0;
- cv::Matresult;
- result.create(image.size(),image.type());
- doubletime=static_cast<double>(cv::getTickCount());
- sharpen(image,result);
- time=(static_cast<double>(cv::getTickCount())-time)/cv::getTickFrequency();
- std::cout<<"time="<<time<<std::endl;
- cv::namedWindow("Image");
- cv::imshow("Image",result);
- image=cv::imread("bridge.jpg",0);
- time=static_cast<double>(cv::getTickCount());
- sharpen3(image,result);
- time=(static_cast<double>(cv::getTickCount())-time)/cv::getTickFrequency();
- std::cout<<"time3="<<time<<std::endl;
- cv::namedWindow("Image3");
- cv::imshow("Image3",result);
- cv::waitKey();
- return0;
- }</span>
5、简单图像算术
图像就是一些矩阵,我们对矩阵进行运算从而就改变了图像的性质。在此次学习中,我们对两幅图进行相加,可以分为两种情况:
①相加的的两张图为类型和大小相同的
②相加的的两张图不一样
类型和大小相同的两张图如下:
调用函数
cv::addWeighted()用法如下:(具体含义见Reference Manual)
void addWeighted(InputArray src1, double alpha,InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1)
可以使用dst = src1*alpha + src2*beta + gamma;
例如c[i]=a[i]+b[i]等价于cv::add(imageA,imageB,resultC)
c[i]=a[i]+k等价于cv::add(imageA,cv::Scalar(k),resultC)
c[i]=k1*a[i]+k2*b[i]+k3
等价于cv::addWeighted(imageA,k1,imageB,k2,k3,imageC)
我们也可以重载操作符:
Result=0.7*imageA+0.9*imageB+0.8;
此次程序为:
- #include<iostream>
- #include<opencv2/core/core.hpp>
- #include<opencv2/highgui/highgui.hpp>
- usingnamespacecv;
- usingnamespacestd;
- intmain(){
- //读取相加的两张图片
- cv::Matimage1=imread("bridge.jpg");;
- cv::Matimage2=imread("sun.jpg");;
- //判断是否读取成功
- if(!image1.data)
- return0;
- if(!image2.data)
- return0;
- //显示两张图
- cv::namedWindow("Image1");
- cv::imshow("Image1",image1);
- cv::namedWindow("Image2");
- cv::imshow("Image2",image2);
- //方法一
- cv::Matresult;
- //addweighted两张图需要大小类型相同
- cv::addWeighted(image1,0.7,image2,0.9,0.,result);
- //显示相加的结果
- cv::namedWindow("result");
- cv::imshow("result",result);
- //方法二
- //重载运算符“+”,“*”
- result=0.7*image1+0.9*image2;
- //显示
- cv::namedWindow("resultwithoperators");
- cv::imshow("resultwithoperators",result);
- image2=cv::imread("rain.jpg",0);
- cv::waitKey();
- return0;
- }
叠加结果为:
类型不同的两张图
Logo.bmp bridge.jpg
我们要把大小和类型不同两张图加到一起,由于cv::add要求输出图像要相同的尺寸,所以不能用。我们首先要定义感兴趣区域(ROI),只要感兴趣区域和logo大小相同,cv::add就能工作了!
//定义感兴趣区域
cv::MatimageROI;
imageROI=image(cv::Rect(300,300,logo.cols,logo.rows));
//插入logo
cv::addWeighted(imageROI,1.0,logo,0.5,0.,imageROI);
直接相加得到图像
得到的图像不是很令人满意,可以将插入处的像素设置为logo图像的像素效果会更好,可以通过一个图像掩码完成:
imageROI= image(cv::Rect(300,300,logo.cols,logo.rows));
// 加载掩膜(必须为灰度图)
cv::Matmask= cv::imread("logo.bmp",0);
// 拷贝拷贝ROI
logo.copyTo(imageROI,mask);
得到
程序为
- <span style="font-size:10px;">#include<iostream>
- #include<opencv2/core/core.hpp>
- #include<opencv2/highgui/highgui.hpp>
- usingnamespacecv;
- usingnamespacestd;
- intmain(){
- //感兴趣区域
- cv::Matimage=cv::imread("bridge.jpg");
- cv::Matlogo=cv::imread("logo.bmp");
- //定义感兴趣区域
- cv::MatimageROI;
- imageROI=image(cv::Rect(300,300,logo.cols,logo.rows));
- //插入logo
- cv::addWeighted(imageROI,1.0,logo,0.5,0.,imageROI);
- cv::namedWindow("withlogo");
- cv::imshow("withlogo",image);
- //用掩膜
- logo=cv::imread("logo.bmp");
- imageROI=image(cv::Rect(300,300,logo.cols,logo.rows));
- //加载掩膜(必须为灰度图)
- cv::Matmask=cv::imread("logo.bmp",0);
- //拷贝拷贝ROI
- logo.copyTo(imageROI,mask);
- cv::namedWindow("withlogo2");
- cv::imshow("withlogo2",image);
- logo=cv::imread("logo.bmp",0);
- image=cv::imread("bridge.jpg");
- cv::waitKey();
- return0;
- }
- </span>