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

OpenCV 2 学习笔记(8): 利用邻域处理图像与简单的算术图像处理算法:图像滤波与加权和

2013-08-21 16:07 846 查看
关于邻域的概念,请查阅有关数字图像处理的书籍,在这里就不再赘述。在图像处理中,利用像素邻域的值来计算本邻域的值是非常常见的事情。例如滤波,边缘检测等。要同时访问多行中像素中的值。
        我们选取锐化图像来讲述本节。拉普拉斯算子,关于这个后面的章节会提到。他是一个计算梯度的算子,通常用在边缘检测中。根据拉普拉斯算子模板卷积之后的算式为:sharpened_pixel=5*current-left-right-up-down;left就是本行左边的像素,上就是本列的上一个像素,以此类推。

下面就提出了一个问题,我们的图像还能不能in-place处理。答案当然是否定的。因为我们同时需要三个指针,指向当前行,上一行和下一行。这三个指针指向的像素在处理时都是不可以变化的。于是锐化处理的算法可以这样写:

#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>

void sharpen(cv::Mat &image, cv::Mat &result)
{
result.create(image.size(),image.type());
//the first ,last row and colum do not process , bucause they do not have up or left neighbour
for(int i = 1; i<image.rows-1; i++)
{
const uchar* previous = image.ptr<const uchar>(i-1);
const uchar* current = image.ptr<const uchar>(i);
const uchar* next = image.ptr<const uchar>(i+1);

uchar* output = result.ptr<uchar>(i);

for (int j=1; j<image.cols-1 ;j++ )
*output++ = cv::saturate_cast<uchar>(5*current[j]-current[j-1]-current[j+1]-previous[j]-next[j]);
}
//Set the unprocess pixels to 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));
}

int _tmain(int argc, _TCHAR* argv[])
{
/*
imread the second parameter:
CV_LOAD_IMAGE_UNCHANGED (<0) loads the image as is (including the alpha channel if present)
CV_LOAD_IMAGE_GRAYSCALE ( 0) loads the image as an intensity one
CV_LOAD_IMAGE_COLOR (>0) loads the image in the RGB forma(default)
*/
cv::Mat image = cv::imread("boldt.jpg",0);
if(!image.data)
return 0;
cv::Mat result;
result.create(image.size(),image.type());
sharpen(image,result);
cv::namedWindow("result");
cv::imshow("result",result);
cv::waitKey(0);
return 0;
}


处理后的图像如图所示:



下面我们来分析一下代码。为了访问像素的上一行和下一行,我们必须同时定义两个同时递增的指针。在计算输出像素值时,要用到模板方法cv::saturate_cast。这是因为算术表达式在计算时结果有可能超出我们的结果类型的范围,例如超出uchar的0-255,使用这个方法就是把只控制在范围之内。当值为负值时置为0,当超过255时置为255。如果是一个浮点数值。那么就置为最靠近的整数值。这个模板方法使用起来很方便,给他一个数据类型,他就保证不超出范围。
边缘的像素处理不到,是因为它们的邻域不完全存在。只能另行处理,这里我们把他置为0.在其他的情况下,可能不是简单地置为0,但是毕竟是少量的边缘像素,通常情况下忽略。我们使用row和line方法访问边缘像素,它们返回一个cv::Mat类型的值,并且只含有一行或者一列。使用setTo方法是为了给所有的矩阵值赋予一个相同的值。在3-channel彩色图像里,需要这样写cv::Scalar(a,b,c)指定每一个频道里的值。
我们上面提到的拉普拉斯模板,模板描述了一个滤波器。模板写出了对应位置上像素的权值,也就是怎样参与运算,最后的结果再赋值给中间的像素。大家可以参照代码中运算部分理解理解,另外再理解理解卷积这个词,在本例中,模板就是:

0-10
-15-1
0-10
因为滤波运算在图像处理中经常用到,所以OpenCV定义了处理滤波的函数,OpenCV就是强大。cv::filter2D函数。在使用它时只需要定义模板,输入图像,就会返回滤波后的图像。模板可以是用矩阵方法。上述的代码可以更改为:
void sharpen2D(const cv::Mat &image, cv::Mat &result) {
// Construct kernel (all entries initialized to 0)
cv::Mat kernel(3,3,CV_32F,cv::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
cv::filter2D(image,result,image.depth(),kernel);
}

在效率问题上和上述的代码基本相同,结果相同。但是在模板很大时,建议使用filter2D,它更有效率。
下面我们就执行一些简单的图像处理算法。
图像可以被任意形式的组合,因为他们就是规则的矩阵。可以相加减乘除,OpenCV提供了许多算术算子。
首先让我们看一下两张图片是怎样结合在一起的,下面是一张下雨的图片,



我们准备把它加在别的图片上造成下雨的感觉。这在我们想得到一些特殊效果或者在图像上添加信息(例如水印)时非常有用。如果让我们自己写时,肯定想到的就是两个矩阵相加,不过OpenCV已经给出了函数,cv::add函数,但是更多情况下使用cv::addWeighted函数,因为我们想获得加权和。也就是:
cv::addWeighted(image1,0.7,image2,0.9,0.,result);

下面是效果:



让我们来解析一下这个代码。所有的二进制函数代码都以同样的方式工作。前面两个是输入,后面两个是输出。在一些情况下,权重可以随意指定为标量的乘数。cv::add是最长见的一种形式。
// 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[1]+k2*b[i]+k3;
cv::addWeighted(imageA,k1,imageB,k2,k3,resultC);
// c[i]= k*a[1]+b[i];
cv::scaleAdd(imageA,k,imageB,resultC);

对于其它的函数,也可以指定掩模值(mask)
// if (mask[i]) c[i]= a[i]+b[i];
cv::add(imageA,imageB,resultC,mask);

也就是说只有当像素的掩模值不是空的时候(mask必须是1-channel)。不仅有cv::subtract, cv::absdiff, cv::multiply ,和cv::divide算术算子。而且还有cv::bitwise_and
, cv::bitwise_or, cv::bitwise_xor ,和cv::bitwise_not。cv::min和 cv::max找到最大的和最小的像素值也非常的有用。做算术处理的两个图像必须有相同的大小和类型,如果输出图像和输入图像大小不一样还要从新分配。因为处理是一个像素一个像素进行的,所以其中的一个输入图像也可以作为输出。另外还有许多其他但操作数的算术函数,cv::sqrt
, cv::pow,
cv::abs,cv::cuberoot, cv::exp, andcv::log。实际上只要你想应用在图像中的任何操作基本都可以在OpenCV中找到相应函数。
         我们同样可以使用C++算术操作来处理cv::Mat或者某个频道,也就是说可以重载。例如:cv::addWeighted也可以写成result= 0.7*image1+0.9*image2;这样写起来更容易理解。效果相同,但是操作之后都需要cv::saturate_cast操作。C++大部分的操作符在OpenCV中都被重载了。包括这些为操作符&,|, 
^, ~,,min,max,abs函数。比较操作符<,  <=,  ==,!=, >,  >=;m1*m2是常见的事,这里m1,m2都是cv::Mat类型。矩阵求逆m1.inv(),旋转矩阵m1.t(),矩阵行列式m1.determinant(),向量范数v1.norm(),向量积v1.cross(v2),点积v1.dot(v2),等等。还可以使用+=符号。
在我们前一节如果效率的扫描图像中,我们展示了一个减少图像颜色程序使用了一些算术操作。在学习了这一节后我们可以把算术操作重新写成:
image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);

使用Scalar是因为要操作的是彩色图像,如果使用这个语句的话需要执行89ms。这是因为在循环里每一次都要执行按位与操作。
下面这个方法想必很多人都希望知道。那就是分离图像通道。也许有时候你想单独处理图像的某个通道。当然,你可以通过循环来实现,但是你也可以使用cv::split函数把彩色图像的三个通道分别复制到三个cv::Mat中去。mixChannels也可以实现相同的功能,但是参数太多,容易出错,我觉得最好使用用split

假如我们只想把雨加到蓝色通道中区,那么就应该这样做:
// create vector of 3 images
std::vector<cv::Mat> planes;
// split 1 3-channel image into 3 1-channel images
cv::split(image1,planes);
// add to blue channel
planes[0]+= image2;
// merge the 3 1-channel images into 1 3-channel image
cv::merge(planes,result);

效果如下:



cv::merge把单通道图像合并成彩色图像。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  OpenCV VC++
相关文章推荐