写在前面
中值滤波器是一种非线性滤波器,或者叫统计排序滤波器。
应用:中值滤波对脉冲噪声(如椒盐噪声)的抑制十分有用。
缺点:易造成图像的不连续性。
原理
原理很简单,如果一个信号是平缓变化的,那么某一点的输出值可以用这点的某个大小的邻域内的所有值的统计中值来代替。这个邻域在信号处理领域称之为窗(window)或者模板(Mask)。模板开的越大,输出的结果就越平滑,但也可能会把我们有用的信号特征给抹掉。所以窗的大小要根据实际的信号和噪声特性来确定。
通常我们会选择窗的大小使得窗内的数据个数为奇数个,之所以这么选是因为奇数个数据才有唯一的中间值。
排序
既然要求中值,那么就要对数据进行排序。常用的排序方法有7中如:冒泡、选择、插入、快速排序、堆、希尔排序等等,具体可以看:C排序|菜鸟教程、http://yansu.org/2015/09/07/sort-algorithms.html和常用排序算法总结(性能+代码)。下面的代码中用的是改进后的冒泡排序法。
效果
代码
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
///////////////////////////
///排序算法-冒泡排序(改进后)
///////////////////////////
void bublle_sort(std::vector<int> &arr){
bool flag=true;
for (int i = 0; i < arr.size() - 1; ++i){
while (flag){
flag = false;
for (int j = 0; j < arr.size() - 1; ++j){
if (arr[j]>arr[j + 1]){
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = true;
}
}
}
}
}
////////////////////////
//中值滤波
///////////////////////
void MedianFilter(cv::Mat& src, cv::Mat& dst, cv::Size wsize){
//图像边界扩充
if (wsize.width % 2 == 0 || wsize.height % 2 == 0){
fprintf(stderr, "Please enter odd size!");
exit(-1);
}
int hh = (wsize.height - 1) / 2;
int hw = (wsize.width - 1) / 2;
cv::Mat Newsrc;
cv::copyMakeBorder(src, Newsrc, hh, hh, hw, hw, cv::BORDER_REFLECT_101);//以边缘为轴,对称
dst = cv::Mat::zeros(src.rows, src.cols, src.type());
//中值滤波
for (int i = hh; i < src.rows + hh; ++i){
uchar* ptrdst = dst.ptr(i - hh);
for (int j = hw; j < src.cols + hw; ++j){
std::vector<int> pix;
for (int r = i - hh; r <= i + hh; ++r){
const uchar* ptrsrc = Newsrc.ptr(r);
for (int c = j - hw; c <= j + hw; ++c){
pix.push_back(ptrsrc[c]);
}
}
bublle_sort(pix);//冒泡排序
ptrdst[j - hw] = pix[(wsize.area() - 1) / 2];//将中值映射到输出图像
}
}
}
int main(){
cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\salt.tif");
if (src.empty()){
return -1;
}
if (src.channels() > 1)
cv::cvtColor(src, src, CV_RGB2GRAY);
cv::Mat dst;
cv::Mat dst1;
cv::Size wsize(5 ,5);
double t2 = (double)cv::getTickCount();
MedianFilter(src, dst, wsize); //中值滤波
t2 = (double)cv::getTickCount() - t2;
double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
std::cout << "my_process=" << time2 << " ms. " << std::endl << std::endl;
cv::namedWindow("src");
cv::imshow("src", src);
cv::namedWindow("dst");
cv::imshow("dst", dst);
//cv::imwrite("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Image Filtering\\MedianFilter\\salt.jpg",dst);
cv::waitKey(0);
}