您的位置:首页 > 理论基础

2_opencv2计算机视觉学习_操作像素

2015-06-07 23:04 369 查看


本次学习主要醋和操作图像的基本元素,如何遍历一张图像且处理其像素,并且在编程过程中,要考虑程序执行效率的问题!

像素是由8位无符号数来表示,其中0表示黑色,255代表白色。对于彩色图来说,每个像素需要三个这样的8位无符号数来表示三个颜色通道(红绿蓝),所以矩阵的元素是一个三元数。保存不同像素类型有整形(CV_8U)浮点型(CV_32F)


1、存取像素值

为了存取矩阵元素,需要访问矩阵的行和列。如果图像是单通道,返回值是单个值;如果图像是多通道,返回值是一组向量(Vector)。此例在图像中加入校验噪点,椒盐噪点就是随机的把部分像素设为白色或者黑色。在传输过程中,如果部分像素丢失,就会出现噪点。我们随机在图片中随机挑出若干像素,将其设置为白色。

首先我们创建一个salt函数,它的形参为一张图image和白噪点的个数n。我们用随机函数rand()结合图像的宽高随机选取一个点设置为噪点,用at访问该点,设置为白色。我们要设置n个噪点,那可以使用循环循环n次。另外,由于图像不知道是灰度图还是彩色图,我们要对其判断,程序如下:

[cpp] view
plaincopy

<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个颜色值。程序如下:


方法一:指针运算

[cpp] view
plaincopy

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 计算速度变慢存取每个像素两次

}

}

}


方法二:位运算

[cpp] view
plaincopy

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来判断,如果图像连续,整个处理使用一次循环完成。程序如下

[cpp] view
plaincopy

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;

}

}

}


方法四:底层指针运算 (不建议容易出错)

[cpp] view
plaincopy

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;

}

}

}


方法五:使用迭代器遍历图像

[cpp] view
plaincopy

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访问

[cpp] view
plaincopy

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;

}

}

}


方法七

[cpp] view
plaincopy

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(),此函数返回每秒钟内的时钟周期数。统计代码运行时间如下:

[cpp] view
plaincopy

double duration;

duration=static_cast<double>(getTickCount());

colorReduce1(imageclone);

duration=(static_cast<double>(getTickCount())duration/getTickFrequency();

前三小结总的程序如下

[cpp] view
plaincopy

#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;

程序如下:

[cpp] view
plaincopy

<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;

此次程序为:

[cpp] view
plaincopy

#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);

得到



程序为

[cpp] view
plaincopy

<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>

网址:http://blog.csdn.net/yale524/article/details/44837465
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: