一、存取像素
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);