您的位置:首页 > 运维架构

OpenCV学习笔记(1)——操作像素

2014-06-14 21:06 405 查看
一、存取像素

Mat 数据结构里矩阵的每隔元素代表一个像素,如果图像是单通道的,返回值是单个数值,用8位无符号数来表示(uchar);如果图像是多通道的,返回值用3个8位无符号数来表示,表示三个通道,这时矩阵的元素是一个三元素,OpenCV中将此类定义为 cv::Vec3b。

下面用一个例子来熟悉一下,很简单,给一副图像加一些椒盐噪点,我这里加了3000个噪点。

直接贴代码:

void salt(Mat &image, int n) //产生 白色的椒盐噪声
{
int i,j;
for (int k=0; k<n; k++)
{
// rand() is the MFC random number generator
i= rand()%image.cols;
j= rand()%image.rows;

if (image.channels() == 1) //如果是单通道 灰度图像
{ // gray-level image
image.at<uchar>(j,i)= 255;
}
else if (image.channels() == 3) //如果是三通道 彩色图像,返回的是一组向量
{ // color image
image.at<Vec3b>(j,i)[0]= 255;
image.at<Vec3b>(j,i)[1]= 255;
image.at<Vec3b>(j,i)[2]= 255;
}
}
}
结果如下:





二、使用指针遍历图像

Mat 提供了 ptr 函数可以得到图像任意行的首地址,ptr 函数是一个模板函数。还是举个例子吧,在例子里面说,空着说不太会。

简单点了,减少图像中颜色的数目,主要是为了降低分析的复杂度。彩色图像3通道,全部颜色数目 256*256*256 ,大于1600万个,比较多。

算法:如果N是颜色缩小比例,对于图像中每个像素的每一个通道,将其值除以N(整除,舍余数),然后再乘以N,得到不大于原始像素值的N的最大倍值。

简单解释:将RGB空间划分为同等大小的格子,如果颜色降低维数为 div ,那么总颜色数为 256/div *256/div *256/div ,原始图像中的每隔颜色都替换为它所在的格子的中心对应的颜色。把[0,div)区间的色素变成div/2的,[div,2*div)的就会变成3*div/2,把周围附近的色调简化为一个色调.这样对颜色进行了简化.

好了,回归到遍历图像的问题上。

1、采用数组的方式

void colorReduce0(Mat &image, int div=64)
{

int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line

for (int j=0; j<nl; j++)
{
uchar* data= image.ptr<uchar>(j);//得到第j行的首地址

for (int i=0; i<nc; i++) //处理每一行像素
{

data[i]= data[i]/div*div + div/2;//数组方式

}
}
}
需要存取每个像素两次。

2、指针的方式

替换原来的代码为

*data++= *data/div*div + div/2;//指针方式
3、取模运算

for (int j=0; j<nl; j++)
{

uchar* data= image.ptr<uchar>(j);

for (int i=0; i<nc; i++)
{
int v= *data;
*data++= v - v%div + div/2;//模运算
}
}
4、指针和位运算

// using .ptr and * ++ and bitwise
void colorReduce3(Mat &image, int div=64)
{

int nl= image.rows; // number of lines
int nc= image.cols * image.channels(); // total number of elements per line
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));

// mask used to round the pixel value
uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0

for (int j=0; j<nl; j++)
{

uchar* data= image.ptr<uchar>(j);

for (int i=0; i<nc; i++)
{
*data++= *data&mask + div/2;//位运算

}
}
}
5、直接指针掩码运算

*(data+i)= *data&mask + div/2;


运行时间和图像减少数目后的结果如下:





可以看出,位运算的速度最快,其次是直接指针掩码。

三、遍历图像和领域操作

通过图像锐化的例子来说明

1、锐化算子的计算方式:sharpened_pixel = 5 * current - left - right -up - down

//遍历图像和邻域操作——锐化
void sharpen(const Mat &image, Mat &result)
{
result.create(image.size(), image.type()); // allocate if necessary

for (int j= 1; j<image.rows-1; j++) //处理除第一行和最后一行外所有的行
{
const uchar* previous= image.ptr<const uchar>(j-1); // 上一行
const uchar* current= image.ptr<const uchar>(j);	// 当前行
const uchar* next= image.ptr<const uchar>(j+1);		// 下一行

uchar* output= result.ptr<uchar>(j);	// 输出行

for (int i=1; i<image.cols-1; i++) {

*output++= saturate_cast<uchar>(5*current[i]-current[i-1]-current[i+1]-previous[i]-next[i]);
//			output[i]= saturate_cast<uchar>(5*current[i]-current[i-1]-current[i+1]-previous[i]-next[i]);
}
}

// 将未处理的边缘像素设为0
result.row(0).setTo(Scalar(0));
result.row(result.rows-1).setTo(Scalar(0));
result.col(0).setTo(Scalar(0));
result.col(result.cols-1).setTo(Scalar(0));
}
2、用迭代器来遍历

void sharpen3(const Mat &image, Mat &result) {

Mat_<uchar>::const_iterator it= image.begin<uchar>()+image.step;
Mat_<uchar>::const_iterator itend= image.end<uchar>()-image.step;
Mat_<uchar>::const_iterator itup= image.begin<uchar>();
Mat_<uchar>::const_iterator itdown= image.begin<uchar>()+2*image.step;

result.create(image.size(), image.type()); // allocate if necessary
Mat_<uchar>::iterator itout= result.begin<uchar>()+result.step;

for ( ; it!= itend; ++it, ++itup, ++itdown) {

*itout= saturate_cast<uchar>(*it *5 - *(it-1)- *(it+1)- *itup - *itdown);
}
}
3、采用OpenCV的滤波函数来处理:filter2D

定义一个 3 * 3 的核函数矩阵,可以达到和上面相同的效果

0 -1 0

-1 5 -1

0 -1 0

void sharpen2D(const Mat &image, Mat &result) {

// Construct kernel (all entries initialized to 0)
Mat kernel(3,3,CV_32F,Scalar(0));
// assigns kernel values
kernel.at<float>(1,1)= 5.0;
kernel.at<float>(0,1)= -1.0;
kernel.at<float>(2,1)= -1.0;
kernel.at<float>(1,0)= -1.0;
kernel.at<float>(1,2)= -1.0;

//filter the image
filter2D(image,result,image.depth(),kernel);
}
三种方法所需时间对比:

,可以看出,用核函数来做速度最快,迭代器最慢!

锐化结果图:



四、定义感兴趣区域(ROI)

这次的测试实例是合并两个大小不同的图像,任务是把logo加到图像的感兴趣区域去,所以要先定义感兴趣区域。

介绍两个函数:add,addWeighted

addWeighted(image1,0.7,image2,0.9,0.,result)

0.7,0.9是权重,后面的0是微调参数

注意的是:add,addweighted函数要求输入图像具有相同的尺寸

// define image ROI
Mat imageROI;
imageROI= image(Rect(385,270,logo.cols,logo.rows));

// add logo to image
addWeighted(imageROI,1.0,logo,0.3,0.,imageROI);



logo图像直接和原始图像相加,肯能伴随着像素饱和,视觉效果不是很令人满意。所以将插入处的像素设置为logo图像的像素值。

// load the mask (must be gray-level)
Mat mask= imread("logo.bmp",0);

// copy to ROI with mask
logo.copyTo(imageROI,mask);

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: