在图像处理中,经常需要处理一个当前点这个点的值可能是基于附近几个临近像素点而得出的.当临近像素点包含上一行或者下一行数据的时候,你需要同时扫描图像的多行.这节会告诉你怎么做.
Getting ready
本节,我们会用一个锐化图像的例子举例.它是基于拉普拉斯操作的(在第6章会讨论).众所周知,如果你对一幅图像使用拉普拉斯算法,这个图像的边缘会增强,可以获得一个锐化图像.这个锐化操作如下:
sharpened_pixel= 5*current-left-right-up-down;
这里left是当前像素的左侧,up是当前像素上一行相同列数的点.以此类推.
How to do it ...
这一次,图像处理不用使用in-place方法完成了,用户需要提供一个输出图像.使用三个像素指针遍历图像,一个是当前行,一个是上一行还有一个是下一行.因为,每一个像素计算都需要访问临近像素点,只使用一行(或一列)图像的所有像素是不能实现这个操作的.这个循环如下:
void sharpen(const cv::Mat &image, cv::Mat &result) {
// allocate if necessary
result.create(image.size(), image.type());
for (int j= 1; j<image.rows-1; j++) { // for all rows
// (except first and last)
const uchar* previous=
image.ptr<const uchar>(j-1); // previous row
const uchar* current=
image.ptr<const uchar>(j); // current row
const uchar* next=
image.ptr<const uchar>(j+1); // next row
uchar* output= result.ptr<uchar>(j); // output row
for (int i=1; i<image.cols-1; i++) {
*output++= cv::saturate_cast<uchar>(
5*current[i]-current[i-1]
-current[i+1]-previous[i]-next[i]);
}
}
// 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));
}
如果我们使用一个灰度图像,我们会获得如下图像:
How It works ...
为了去访问上一行或者下一行的临近像素.我们必须定义额外的指针.然后在内循环中访问这些像素值.
在输出像素的处理中,使用了模版函数cv::staurate_case.这是因为在使用多个像素处理后的结果可能超过像素值的界限(0~255).解决的方法是把超出的值,变成在范围内的.这个操作使得小于0的值变为0,超出255的值变为255.这就是cv::sturate_cast<unchar>函数所做的事情.需要注意,如果如果输入参数是一个浮点数,这个结果会返回一个最近的整数.显然,你也可以对其他数据类型使用这个函数,结果会返这种类型定义的范围.
图像框的像素不会被处理,因为它们的临近值不完全存在,需要分开处理.这里我们简单的把他们设置为0.在其他的例子中,它们可能会被执行一些特殊的运算,但是在大部分的情况下,没有必要花费时间处理这部分很少的像素.在我的的函数中,使用了两种特殊的方法把边缘的像素值置为0.第一个就是row和col.他们当指定一个参数时,会返回一个cv::Mat对象一个单行(或单列).这里不会产生复制.因为如果一维矩阵的元素被修改,在原始图像中也会修改.这也是我们使用setTo()方法做的事情.这个方法需要指定一个像素的所有元素.如下:
result.row(0).setTo(cv::Scalar(0));
对result的第一行所有元素值都置为0.在3通道图像,需要使用能够cv::Scalar(a,b,c)去指定每个通道的值.
There's more...
当做临近像素值运算的时候,通常使用核心矩阵.这个核心描述了如何与临近像素进行运算的.在本节的锐化中,这内核是:
0 -1 0
-1 5 -1
0 -1 0
除非另有规定,当前像素位于核心的中间.核心的每个因子都是可以增加的.返回的结果是,像素分别乘以相应位置的总和.内核的大小对应于临近像素的大小(这里是3×3).从表面上看,正像锐化所要求的,水平和垂直的四个邻近点被乘以-1,当前像素点乘以5.使用内核更方便表示,这是基于信号处理的卷积表示.内核定义了一个应用于图像的滤波器.
因为在图像处理中滤波是一个常用的操作,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方法更有利,这种情况下,效率更高.
See also
在第六章我们会了解更多的图像滤波.