2_opencv2计算机视觉学习_操作像素
来源:互联网 发布:池州学院大数据学院 编辑:程序博客网 时间:2024/06/04 19:05
本次学习主要醋和操作图像的基本元素,如何遍历一张图像且处理其像素,并且在编程过程中,要考虑程序执行效率的问题!
像素是由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>
- 2_opencv2计算机视觉学习_操作像素
- 2_opencv2计算机视觉学习_操作像素
- 1_opencv2计算机视觉学习_图像载入显示操作
- 3_opencv2计算机视觉学习_基于类的图像处理
- 学习《OpenCV 2 计算机视觉编程手册》第二章存取像素值案例--椒盐噪声
- Python学习03_图片像素操作
- 深度学习与计算机视觉系列(2)_图像分类与KNN
- 深度学习与计算机视觉系列(2)_图像分类与KNN
- 深度学习与计算机视觉系列(2)_图像分类与KNN
- 深度学习与计算机视觉系列(2)_图像分类与KNN
- 深度学习与计算机视觉系列(2)_图像分类与KNN
- 深度学习与计算机视觉系列(2)_图像分类与KNN
- 深度学习与计算机视觉系列(1)_基础介绍
- 深度学习与计算机视觉系列(1)_基础介绍
- 深度学习与计算机视觉系列(1)_基础介绍
- 深度学习与计算机视觉系列(1)_基础介绍
- 深度学习与计算机视觉系列(1)_基础介绍
- 深度学习与计算机视觉系列(1)_基础介绍
- JAVA --- 23 kinds of design patterns
- Memcache 文件系统MemcacheFS
- PHY常见问题说明
- 20 Best Code Review Tools for Developers
- Android学习之路——Activity(2)
- 2_opencv2计算机视觉学习_操作像素
- [theano] Install Theano on Windows 7 x64 using Cuda 7.0
- 电子邮件验证 正则表达式
- 2-4-2 学生类
- android与服务器通过socket通信
- 文件的上传
- Knights in Chessboard
- 使用eclipse开发STM32
- 设计模式---策略模式