OpenCV平滑(模糊)图像
- 一、学习目标
- 二、平滑理论介绍
- 三、学习四种不同的滤波器
- 四、完整使用实例
一、学习目标
- 了解什么是图像的平滑(模糊)
- 学会使用均值模糊、高斯模糊、双边模糊、中值模糊等处理图像
- 动手练习平滑实例
二、平滑理论介绍
平滑,也叫模糊,是一种简单而常用的图像处理操作。平滑通常可以用来减少噪声(其他用途将在下面的教程中看到)。
为了执行平滑操作,我们将对我们的图像应用一个滤波器。最常见的滤波器类型是线性的,输出像素的值(即g(i,j))为输入像素值(即 f(i+k,j+l))的加权和:
h(k,l) 称为核,有的地方也称为算子,它是滤波的系数。可以将滤波器看作是一个固定尺寸的系数窗口,在图像上滑动,图像处理的很多操作都是基于滑动窗口来完成的。
三、学习四种不同的滤波器
1、均值滤波器
这个滤波器是所有过滤器中最简单的。每个输出像素都是其中心像素及其相邻像素的均值(它们的权重都相等)。内核如下:
Kwidth 和 Kheight 分别表示内核的宽和高。
OpenCV使用 cv::blur函数实现均值滤波,其函数原型为:
void cv::blur( InputArray src,
OutputArray dst,
Size ksize,
Point anchor = Point(-1,-1),
int borderType = BORDER_DEFAULT )
- 参数 src: 待处理的图像,它可以有任意数量的通道,这些通道是独立处理的,但是图像深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F
- 参数 dst: 与src相同大小和类型的输出图像
- 参数 ksize: 均值模糊的内核大小
- 参数 anchor: 锚点,默认值 Point(-1,-1) 表示锚位于内核中心
- 参数 borderType: 用于指定图像边界处理时的填充像素方式,枚举自BorderTypes。不支持BORDER_WRAP
边界类型的取值见下表:
参数取值 | 取值说明 |
BORDER_CONSTANT | iiiiiii(abcdefgh)iiiiiii,使用指定的常量 i 填充 |
BORDER_REPLICATE | aaaaaa(abcdefgh)hhhhhhh,填充的所有像素都使用对应的边界像素 |
BORDER_REFLECT | fedcba(abcdefgh)hgfedcb,填充几个边界像素就从对应边界处取几个像素 |
BORDER_WRAP | cdefgh(abcdefgh)abcdefg |
BORDER_REFLECT_101 | gfedcb(abcdefgh)gfedcba |
BORDER_TRANSPARENT | uvwxyz(abcdefgh)ijklmno |
BORDER_REFLECT101 | 与BORDER_REFLECT_101一样 |
BORDER_DEFAULT | 与BORDER_REFLECT_101一样 |
BORDER_ISOLATED | 不管ROI之外的像素 |
还可以使用函数cv::boxFilter完成均值滤波,其函数原型为:
void cv::boxFilter( InputArray src,
OutputArray dst,
int ddepth,
Size ksize,
Point anchor = Point(-1,-1),
bool normalize = true,
int borderType = BORDER_DEFAULT )
- 参数 src: 输入图像
- 参数 dst: 与src相同大小和类型的输出图像
- 参数 ddepth: 输出图像的深度(-1使用src.depth())
- 参数 ksize: 均值模糊的内核大小
- 参数 anchor: 锚点,默认值 Point(-1,-1) 表示锚位于内核中心
- 参数 normalize: 标志,指定内核是否按其面积进行归一化,即为true时表示求均值
- 参数 borderType: 用于指定图像边界处理时的填充像素方式,枚举自BorderTypes。不支持BORDER_WRAP
非归一化盒滤波器用于计算每个像素邻域上的各种积分特征,如图像导数的协方差矩阵(用于密集光流算法等)。其遵循如下公式:
其中:
如下两种调用效果相同:
blur(src, dst, ksize, anchor, borderType)
和
boxFilter(src, dst, src.type(), ksize, anchor, true, borderType).
2、高斯滤波器
高斯滤波器可能是最有用的过滤器(虽然不是最快的)。高斯滤波是通过将输入数组中的每个点与高斯核进行卷积,然后将它们全部加起来产生输出数组来完成的。
一维高斯的公式为:
其分布图如下:
假设图像是一维的,可以注意到位于中间的像素拥有最大的权值。相邻像素的权重随着它们与中心像素空间距离的增大而减小。二维高斯的公式为:
其分布图如下:
其中μ是平均值(峰值),σ2 代表方差(变量x和y各自的方差)
OpenCV使用cv::GaussianBlur函数完成高斯滤波,其函数原型为:
void cv::GaussianBlur( InputArray src,
OutputArray dst,
Size ksize,
double sigmaX,
double sigmaY = 0,
int borderType = BORDER_DEFAULT )
- 参数 src: 输入图像
- 参数 dst: 与src相同大小和类型的输出图像
- 参数 ksize: 高斯核的大小。ksize.width和ksize.height可以不同,但它们都必须是正奇数。它们可以是0,这时就需要根据sigma来计算
- 参数 sigmaX: X方向的高斯核标准差
- 参数 sigmaY: Y方向的高斯核标准差。如果sigmaY为零,则设置为sigmaX,如果两个sigma都为零,则根据ksize计算。(详见getGaussianKernel函数)。建议指定所有的ksize、sigmaX和sigmaY
- 参数 borderType: 用于指定图像边界处理时的填充像素方式,枚举自BorderTypes。不支持BORDER_WRAP
3、中值滤波器
中值滤波器遍历内核窗口中的每一个像素,并用这些相邻像素的中值替换每个中心像素(位于评估像素周围的正方形区域)。
OpenCV使用cv::medianBlur函数完成中值滤波,其函数原型为:
void cv::medianBlur( InputArray src,
OutputArray dst,
int ksize )
- 参数 src: 输入1,3,或4通道图像。当ksize为3或5时,图像深度应为CV_8U, CV_16U,或CV_32F。对于较大的内核尺寸,只能为CV_8U
- 参数 dst: 与输入图像尺寸和类型一致的输出图像
- 参数 ksize: 内核尺寸;必须为奇数且大于1,例如:3,5,7…
4、双边滤波器
到目前为止,我们已经解释了一些主要应用于平滑输入图像的过滤器。然而,有时滤波器不仅可以消除噪声,还可能会平滑边缘。为了避免这种情况(至少在一定程度上),我们可以使用双边滤波器。
与高斯滤波器类似,双边滤波器也考虑给每个相邻像素赋不同的权值。这些权重有两个分量,第一个是与高斯滤波器使用的相同的权重。第二个分量考虑相邻像素和核心像素之间的强度差异。
OpenCV使用cv::bilateralFilter函数完成双边滤波,其函数原型为:
void cv::bilateralFilter( InputArray src,
OutputArray dst,
int d,
double sigmaColor,
double sigmaSpace,
int borderType = BORDER_DEFAULT )
- 参数 src: 8位或浮点,1通道或3通道输入图像
- 参数 dst: 与src相同大小和类型的目标图像
- 参数 d: 滤波过程中使用的每个像素邻域的直径。如果它是负数,则根据sigmaSpace计算
- 参数 sigmaColor: 在颜色空间中过滤的sigma。该参数的值越大,意味着像素邻域内更远的颜色(参见sigmaSpace)将混合在一起,从而产生更大的相等颜色区域
- 参数 sigmaSpace: 在坐标空间中过滤的sigma。参数值越大,越远的像素将相互影响,只要它们的颜色足够接近(请参阅sigmaColor)。当d>0时,它指定邻域大小,而不考虑sigmspace。否则,d与sigmaSpace成比例
- 参数 borderType: 用于指定图像边界处理时的填充像素方式,枚举自BorderTypes
四、完整使用实例
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int DELAY_CAPTION = 1500;
int DELAY_BLUR = 500;
int MAX_KERNEL_LENGTH = 31;
Mat src; Mat dst;
char window_name[] = "Smoothing Demo";
int display_caption(const char* caption);
int display_dst(int delay);
int main(int argc, char** argv)
{
namedWindow(window_name, WINDOW_AUTOSIZE);
string fileName = samples::findFile("lena.jpg");
src = imread(fileName, IMREAD_COLOR);
if (src.empty())
{
fprintf(stderr, "Failed to load image: %s.\n", fileName);
system("pause");
return EXIT_FAILURE;
}
if (display_caption("Original Image") != 0)
{
return 0;
}
dst = src.clone();
if (display_dst(DELAY_CAPTION) != 0)
{
return 0;
}
if (display_caption("Homogeneous Blur") != 0)
{
return 0;
}
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;
}
}
if (display_caption("Gaussian Blur") != 0)
{
return 0;
}
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;
}
}
if (display_caption("Median Blur") != 0)
{
return 0;
}
for (int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2)
{
medianBlur(src, dst, i);
if (display_dst(DELAY_BLUR) != 0)
{
return 0;
}
}
if (display_caption("Bilateral Blur") != 0)
{
return 0;
}
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;
}
}
display_caption("Done!");
return EXIT_SUCCESS;
}
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);
}
int display_dst(int delay)
{
imshow(window_name, dst);
int c = waitKey(delay);
if (c >= 0) { return -1; }
return 0;
}