模糊图像——线性滤波
模糊图像是opencv常见的操作,使用模糊操作的原因是为了给图像预处理时降低噪声影响,Smooth和Blur是opencv图像模糊的API,其背后的原理其实是数学的卷积操作,而卷积算子计算通常是线性计算,所以也可以称为线性滤波。
卷积.png 卷积是图像处理的一个操作,是kernel在图像上的每个像素上的操作。kernel本质是一个固定大小的矩阵数组,中心点称为锚点(anchor point)。
把kernel放到像素数组上,求锚点周围覆盖的像素乘积之和(包括锚点),用来替换锚点覆盖下像素点值称为卷积处理。
上面的卷积过程是一个3x3的窗口从左到右,从上到下平移过程。黄色的每个像素点之和取平均值赋值给中心的红色点作为卷积处理后的像素值。这是一种均值滤波。
opencv提供了均值滤波(模糊)的API blur(Mat src,Mat dst,Size(xradius,yradius),Point(-1,-1))
当然还有其他模糊的方式,如高斯模糊 GaussianBlur(Mat src,Mat dst,Size(x,y),sigmax,sigmay)
其中Size(x,y)的x和y必须为正基数,且一般x=y
不同卷积算子可以看做不同的滤波结果:
卷积.png
中值滤波
中值滤波的应用medianBlur(Mat src,Mat dst,ksize)
- 统计排序滤波器
- 中值滤波对椒盐噪声有很好的抑制作用
椒盐噪声一般是作为最值对信号产生影响,中值滤波对最值有很好的抑制作用。
那么是否均值滤波能做为生产环境的滤波处理,均值滤波有什么缺陷呢?
均值模糊无法克服边缘信息丢失缺陷,因为均值滤波是基于平均权重处理。而高斯模糊部分克服了改缺陷,但是无法完全避免,因为高斯模糊没有考虑像素值不同的影响。
双边滤波
双边滤波也可以称为高斯双边模糊,是边缘保留的滤波方法,避免了边缘信息的丢失,保留了图像轮廓不变。
考虑像素值的处理,如果有1和255两个像素值,那么在各自的像素值处离的比较远,可以不做处理
bilateralFilter(Mat src,Mat dst,d=15,150,3)
- 15表示计算的半径,半径之内像素全部纳入计算,如果是-1,那么根据sigma space参数取值
- 150 sigma color 决定多少差值之内的像素会被计算
- 3 sigma space 如果d的值大于0则声明无效,否则根据它来计算d值
#include "stdafx.h"
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
Mat src, dst;
src = imread("F:/cat.jpg");
if (!src.data) {
printf("无法加载图片\n");
return - 1;
}
namedWindow("input img", CV_WINDOW_AUTOSIZE);
imshow("show img", src);
dst = Mat::zeros(src.size(),src.type());
//medianBlur(src, dst, 3);
bilateralFilter(src, dst, 15, 100, 5);
namedWindow("bilateralFilter", CV_WINDOW_AUTOSIZE);
imshow("bilateralFilter result", dst);
//Mat bgblur(src.size(), src.type());
//GaussianBlur(src, bgblur, Size(15, 15), 3, 3);
//imshow("GaussianBlur result", bgblur);
//可以在双边滤波后做对比度增强
Mat resultImg(src.size(), src.type());
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(src, resultImg, -1, kernel, Point(-1, -1), 0);
imshow("result", resultImg);
waitKey(0);
return 0;
}
输出结果演示
image.png
自定义线性滤波
线性滤波常用的算子有Sobel算子,Laplance算子,当然我们可以自己定义算子然后通过opencv提供的filter2D来实现自己的滤波器。
#include "stdafx.h"
#include <opencv2/opencv.hpp>
using namespace cv;
int main(int argc,int ** argv)
{
Mat src, dst;
int ksize = 0;
src = imread("F:/cat.png");
if (!src.data) {
printf("无法加载图片\n");
return -1;
}
namedWindow("input img", CV_WINDOW_AUTOSIZE);
imshow("show img", src);
dst = Mat::zeros(src.size(), src.type());
namedWindow("bilateralFilter", CV_WINDOW_AUTOSIZE);
//Robert X方向
//Mat kernelx = (Mat_<int>(2, 2) << 1, 0, 0, -1);
//filter2D(src, dst, -1, kernelx, Point(-1, -1), 0.0);
//Robert Y方向
//Mat result = Mat::zeros(src.size(), src.type());
//Mat kernely = (Mat_<int>(2, 2) << 0, 1, -1, 0);
//filter2D(src, result, -1, kernely, Point(-1, -1), 0.0);
//Sobel X方向
Mat kernelx = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
filter2D(src, dst, -1, kernelx, Point(-1, -1), 0.0);
//Sobel Y方向
Mat result = Mat::zeros(src.size(), src.type());
Mat kernely = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
filter2D(src, result, -1, kernely, Point(-1, -1), 0.0);
//拉普拉斯算子
Mat result_ = Mat::zeros(src.size(), src.type());
Mat kernel_ = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
filter2D(src, result_, -1, kernel_, Point(-1, -1), 0.0);
imshow("Sobel X result", dst);
imshow("Sobel Y result", result);
imshow("laplance result", result_);
waitKey(0);
return 0;
}
输出结果演示
image.png
可以看出,针对不同卷积算子,得到不同的结果,其中沿X方向的算子和沿Y方向的算子得到的结果都只有部分原图像的特征,使用高斯算子得到的特征比较符合原图像的要求。