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>


 

 

 

 

 

 



0 0
原创粉丝点击