Goal
在本教程中,您将学习如何使用 OpenCV 函数应用各种线性滤波器来平滑图像,例如:
Theory
笔记
下面的解释属于 Richard Szeliski 的 Computer Vision: Algorithms and Applications 一书和 LearningOpenCV
- 平滑,也称为模糊,是一种简单且经常使用的图像处理操作。
- 平滑的原因有很多。 在本教程中,我们将专注于平滑以减少噪声(其他用途将在以下教程中看到)。
- 为了执行平滑操作,我们将对图像应用过滤器。 最常见的滤波器类型是线性的,其中输出像素的值(即 g(i,j))被确定为输入像素值的加权和(即 f(i+k,j+l)):
h(k,l) 称为核,无非就是滤波器的系数。
它有助于将滤波器可视化为在图像上滑动的系数窗口。
- 过滤器有很多种,这里我们将提到最常用的:
Normalized Box Filter
归一化箱式过滤器
这个过滤器是最简单的! 每个输出像素是其内核邻居的平均值(它们都具有相同的权重)
内核如下:
Gaussian Filter
可能是最有用的过滤器(虽然不是最快的)。 高斯滤波是通过将输入数组中的每个点与高斯核进行卷积,然后将它们全部相加以产生输出数组来完成的。
只是为了让图片更清晰,还记得一维高斯核的样子吗?
假设图像是一维的,您会注意到位于中间的像素的权重最大。 其邻居的权重随着它们与中心像素之间的空间距离的增加而减小。
笔记
请记住,二维高斯可以表示为:
其中 μ 是平均值(峰值),
表示方差(每个变量 x 和 y)
Median Filter
中值滤波器遍历信号的每个元素(在本例中为图像),并将每个像素替换为其相邻像素的中值(位于被评估像素周围的正方形邻域中)。
Bilateral Filter
双边过滤器
到目前为止,我们已经解释了一些主要目标是平滑输入图像的过滤器。 但是,有时过滤器不仅可以消除噪声,还可以平滑边缘。 为了避免这种情况(至少在一定程度上),我们可以使用双边滤波器。
与高斯滤波器类似,双边滤波器也考虑相邻像素,并为每个像素分配权重。 这些权重有两个分量,第一个分量与高斯滤波器使用的权重相同。 第二个组件考虑了相邻像素和评估像素之间的强度差异。
有关更详细的说明,您可以查看此链接Bilateral Filtering (ed.ac.uk) 灰度和彩色图像的双边过滤
|
| |
(a) | (b) | (c) |
Figure 1 |
Code
这个程序有什么作用?
加载图像
应用 4 种不同类型的过滤器(在理论中解释)并按顺序显示过滤后的图像
可下载代码:点击这里raw.githubusercontent.com
代码一览:
/**
* file Smoothing.cpp
* brief Sample code for simple filters
* author OpenCV team
*/
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace std;
using namespace cv;
/// 全局变量
int DELAY_CAPTION = 1500;//延迟 标题
int DELAY_BLUR = 100;//模糊延迟
int MAX_KERNEL_LENGTH = 31;//最大内核长度
Mat src; Mat dst;
char window_name[] = "Smoothing Demo";
/// Function headers
int display_caption( const char* caption );//显示标题
int display_dst( int delay );//显示目标图像
/**
* function main
*/
int main( int argc, char ** argv )
{ //定义窗口
namedWindow( window_name, WINDOW_AUTOSIZE );
/// 加载源图像
const char* filename = argc >=2 ? argv[1] : "lena.jpg";//文件名
src = imread( samples::findFile( filename ), IMREAD_COLOR );//读取彩色图像
if (src.empty())
{
printf(" 打开图片时出错\n");
printf(" Usage:\n %s [image_name-- default lena.jpg] \n", argv[0]);
return EXIT_FAILURE;
}
//显示文本
if( display_caption( "Original Image" ) != 0 )//显示文本
{
return 0;//delay时间内按下按键 ,结束程序
}
dst = src.clone();
if( display_dst( DELAY_CAPTION ) != 0 )//显示目标图像
{
return 0;
}
/// Applying Homogeneous blur 应用均匀模糊
if( display_caption( "Homogeneous Blur" ) != 0 )
{
return 0;
}
//![blur]滤波
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
blur( src, dst, Size( i, i ), Point(-1,-1) );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
//![blur]
/// Applying Gaussian blur 应用高斯模糊 高斯滤波
if( display_caption( "Gaussian Blur" ) != 0 )
{
return 0;
}
//![gaussianblur]高斯滤波
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
GaussianBlur( src, dst, Size( i, i ), 0, 0 );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
//![gaussianblur]
/// Applying Median blur 均值滤波
if( display_caption( "Median Blur" ) != 0 )
{
return 0;
}
//![medianblur]
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
medianBlur ( src, dst, i );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
//![medianblur]
/// Applying Bilateral Filter 双边滤波
if( display_caption( "Bilateral Blur" ) != 0 )
{
return 0;
}
//![bilateralfilter]
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
bilateralFilter ( src, dst, i, i*2, i/2 );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
//![bilateralfilter]
/// Done
display_caption( "Done!" );
return 0;
}
/**
* @function display_caption
*/
int display_caption( const char* caption )
{
dst = Mat::zeros( src.size(), src.type() );//目标图像
putText( dst, caption,
Point( src.cols/4, src.rows/2),
FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );//显示文本
return display_dst(DELAY_CAPTION);
}
/**
* @function display_dst
*/
int display_dst( int delay )//显示目标图像
{
imshow( window_name, dst );
int c = waitKey ( delay );
if( c >= 0 ) { return -1; }//delay时间内按下按键,返回-1。
return 0;
}
Explanation
让我们检查一下只涉及平滑过程的 OpenCV 函数,因为其余的现在已经知道了。
Normalized Block Filter: 归一化块过滤器:
OpenCV 提供了 blur() 函数来使用这个过滤器进行平滑处理。 我们指定了 4 个参数(更多详细信息,请查看参考资料):
src:源图像
dst:目标图像
Size(w, h):定义要使用的内核的大小(宽度为 w 像素,高度为 h 像素)
Point(-1, -1):指示锚点(评估的像素)相对于邻域的位置。 如果有负值,则内核的中心被认为是锚点。
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
blur( src, dst, Size( i, i ), Point(-1,-1) );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
Gaussian Filter:
它由函数 GaussianBlur() 执行:这里我们使用 4 个参数(更多详细信息,请查看 OpenCV 参考):
src:源图像
dst:目标图像
Size(w, h):要使用的内核的大小(要考虑的邻居)。 w 和 h 必须是奇数和正数,否则将使用 σx 和 σy 参数计算大小。
σx:x 的标准差。 写入 0 意味着 σx 是使用内核大小计算的。
σy:y 的标准差。 写入 0 意味着 σy 是使用内核大小计算的。
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
GaussianBlur( src, dst, Size( i, i ), 0, 0 );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
Median Filter:
中值滤波器:
该过滤器由 medianBlur() 函数提供:我们使用三个参数:
src:源图像
dst:目标图片,必须和src同类型
i:内核的大小(只有一个,因为我们使用方形窗口)。 一定很奇数。
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
medianBlur ( src, dst, i );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
Bilateral Filter
由 OpenCV 函数提供的bilateralFilter() 我们使用 5 个参数:
src:源图像
dst:目标图像
d:每个像素邻域的直径。
σColor:颜色空间的标准偏差。
σSpace:坐标空间中的标准偏差(以像素为单位)
- Provided by OpenCV function bilateralFilter() We use 5 arguments:
- src: Source image
- dst: Destination image
- d: The diameter of each pixel neighborhood.
- σColor: Standard deviation in the color space.
- σSpace: Standard deviation in the coordinate space (in pixel terms)
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
{
bilateralFilter ( src, dst, i, i*2, i/2 );
if( display_dst( DELAY_BLUR ) != 0 )
{
return 0;
}
}
Results
代码打开一个图像(在本例中为 lena.jpg)并在解释的 4 个过滤器的效果下显示它。
这是使用 medianBlur (中值滤波)平滑的图像快照:
waitKey()函数详解
1--waitKey()--这个函数是在一个给定的时间内(单位ms)等待用户按键触发;如果用户没有按下 键,则继续等待(循环)
2--如下所示: while(1){ if(waitKey(100)==27)break; } 在这个程序中,我们告诉OpenCv等待用户触发事件,等待时间为100ms,如果在这个时间段内, 用户按下ESC(ASCII码为27),则跳出循环,否则,则继续循环
3--如果设置waitKey(0),则表示程序会无限制的等待用户的按键事件
函数waitKey在delay≤0时无限等待一个按键事件,或者为正时等待delay毫秒。 由于操作系统在切换线程之间有最短时间,因此该函数不会完全等待delay毫秒,它会等待至少delay毫秒,具体取决于当时您计算机上正在运行的其他内容。 如果在指定的时间过去之前没有按下任何键,则返回被按下键的代码或 -1。
此函数是 HighGUI 中唯一可以获取和处理事件的方法,因此需要定期调用它以进行正常的事件处理,除非在处理事件处理的环境中使用 HighGUI。