中值滤波介绍
中值滤波介绍
中值滤波定义:将𝑛(𝑛为奇数)个数据按其值𝑑𝑖进行从大到小或者从小到大排列后得到一个有序序列:𝑑0,𝑑1, … ,𝑑𝑛−1,则𝑑(𝑛/2)称为中值。例如:有序序列10,11,12,13,14,15,16,17,18的𝑛 = 9,有 [9/2] = 4,则中值为𝑑4,即14。
根据以上可知,中值滤波就是以当前像素为中心取一个邻域,用该区域的所有像素灰度值的中值作为该像素滤波后的灰度值。
中值滤波作用
中值滤波的作用很大程度上是基于均值滤波来讲的,我们知道均值滤波是以某一像素为中心,将其对应的某一领域中所有像素值的均值作为这一中心的滤波后的值,均值滤波总会导致图像的模糊,而采用中值滤波可以很好的解决这个问题,尤其是在面对椒盐噪声这种像素点噪声时,中值滤波总是会有非常好的效果。
opencv自带的中值滤波函数
中值滤波函数
medianBlur(OriImage1, ResImage, size); OriImage为原始图像,ResImage为滤波后的图像,size为滤波器的大小
/*
* 实现中值滤波(算法来源:A Fast Two-Dimensional Median Filtering Algorithm)
* 环境:opencv4.4 debug x64
* 博客链接:
* 仅供学习
*/
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2\imgproc\types_c.h>
using namespace std;
using namespace cv;
int main()
{
Mat image1 = imread("salt_pepper_Image.jpg");
if (image1.empty())
{
cout << "读取图片失败。" << endl;
}
imshow("Origin Image", image1);
//中值滤波
Mat result;
medianBlur(image1, result, 3);
imshow("Result Image(after median fluter)", result);
waitKey(0);
return 0;
}
结果如下:
简单算法实现中值滤波
下面我们来详细叙述一下中值滤波的实现算法,首先是简单中值滤波,采用一般思维,排序取中值;
伪代码:
Input: image X of size m*n, kernel radius r. 图X,像素大小为m*n ,中值滤波器的半径r
output: image Y as X. 输出图Y
for i = r to m - r do 边界范围为r——(m-r)
for j = r to n - r do 边界为r——n-r
initialize list A[] 初始化表A
for a = i-r to i+r
for b = j-r to j+r
add X(a, b) to A[] 将滤波器对应位置的像素值放入表A中
end
end
sort A[] then Y(i ,j) = A[A.size/2] 排序取中值
end
end
实现的话应该很简单,有兴趣的可以根据上述伪代码取尝试实现,重要的是下面将要叙述的快速算法实现中值滤波。
快速算法实现中值滤波
算法思想来源于A Fast Two-Dimensional Median Filtering Algorithm这篇论文,有兴趣的同学可以去看看,当然我不是很推荐,几十年前写的论文,算法流程写的一点都不简洁,看的老费劲了。找了半天终于找到中文版的介绍,下面图片摘自https://blog.51cto.com/qianqing13579/1590217这篇博客,但是于我而言习惯于用opencv的数据格式(哈哈通俗点说,离开opencv图片都读不了),大佬的代码没法直接用,当然也有脱裤子放屁的嫌疑,你看你又离不开opencv又不他内置的函数,没办法谁叫老师就喜欢这种重复造轮子的作业呢。[无奈~~]
/*
* 实现中值滤波(算法来源:A Fast Two-Dimensional Median Filtering Algorithm)
* 环境:opencv4.4 debug x64
* 博客链接:
* 仅供学习
*/
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2\imgproc\types_c.h>
using namespace std;
using namespace cv;
void QuickMedianFluter(Mat OriImage, Mat& ResImage, int size);
int FindMedian(int H[], int n,int size,int &mNum);
int main()
{
Mat image1 = imread("salt_pepper_Image.jpg", IMREAD_GRAYSCALE);
if (image1.empty())
{
cout << "读取图片失败。" << endl;
}
imshow("Origin Image", image1);
//中值滤波,调用opencv自带的函数
Mat result1;
medianBlur(image1, result1, 3);
imshow("Result1 Image(after median fluter,opencv自带)", result1);
//快速中值滤波,利用自己编写的函数
Mat result2;
result2 = image1.clone();
//ResImage = OriImage.clone();
QuickMedianFluter(image1, result2, 3);
imshow("Result2 Image(自我编写函数)", result2);
waitKey(0);
return 0;
}
/***********************************************************
* 函数功能:中值滤波(利用直方图法快速实现)
* 参数介绍:OriInage:原图 ResImage:滤波后的图
* size:滤波器(或者领域)边长,大小为size x size(如3x3)
***********************************************************/
void QuickMedianFluter(Mat OriImage, Mat& ResImage, int size)
{
int m = OriImage.rows; //m是行,对应下面的x
int n = OriImage.cols; //n是列,对应下面的n
int *Histogram = new int[256];
memset(Histogram, 0, 256 * sizeof(int));
int radius = size / 2; //半径
int th = size * size / 2 + 1; //算法中的th
int median = 0; //保存直方图中的中值对应的像素
int mNum = 0; //保存小于中值像素median的总个数
int left = 0; //最左列将要移除的
int right = 0; //最右边将要加入的
if (OriImage.channels() != 1)
{
cout << "图片不是灰度图像,请转化为灰度图像后再尝试。" << endl;
}
//处理边界,边界没法进行中值滤波,因此将边界所有像素赋值给ResImage
//直接clone过去即可,中间部分会随着滤波过程的进行随之改变
for (int i = radius; i < m - radius; i++) //m是行,对应x从上至下
{
//初始化Histogram
memset(Histogram, 0, 256 * sizeof(int));
for (int k = i-radius; k <= i+radius; k++)
{
for (int h = 0; h < size; h++)
{
Histogram[OriImage.at<uchar>(k, h)]++;
}
}
median= FindMedian(Histogram, 256, size,mNum);
ResImage.at<uchar>(i,radius) = median;
for (int j = radius; j < n - radius; j++) //n是列,对应y从左至右
{
//j在radius处,直方图已求,跳过
if (j == radius) continue;
//根据算法,中心向右移动一位,将最左列的数移除直方图中
for (int k = i-radius; k <= i + radius; k++)
{
left = OriImage.at<uchar>(k, j - 1 - radius);
Histogram[left]--;
if (left <= median) mNum = mNum - 1;
}
//根据算法,中心向右移动一位,将最右侧的数移入直方图中
for (int k = i-radius; k <= i + radius; k++)
{
right = OriImage.at<uchar>(k, j + radius);
Histogram[right]++;
if (right <= median) mNum = mNum + 1;
}
if (mNum == th)
{
ResImage.at<uchar>(i, j) = median;
continue;
}
//利用前一中值和所有小于等于中值median的数mNum,求当前中值
if (mNum < th)
{
//小于th(第th个数为中值),在当前median右边,往右找(像素加一寻找)
while (mNum < th) {
median = median + 1;
mNum = mNum + Histogram[median];
}
}
else {
//大于th(第th个数为中值),在当前median左边,往右找(像素减一寻找)
while (mNum > th)
{
mNum = mNum - Histogram[median];
median = median - 1;
}
}
ResImage.at<uchar>(i, j) = median;
}
}
delete[] Histogram;
}
/**************************************************************
* 函数功能:通过直方图返回中值
* 参数介绍:H[]:直方图数组 n:直方图维数
* size:滤波器的大小
****************************************************************/
int FindMedian(int H[], int n,int size,int &mNum)
{
int median = 0; //初始化中值为0
int sum_cout = 0; //求和
int median_flag = size * size / 2; //中值标志
for (int i = 0; i < 256; i++)
{
sum_cout += H[i];
if (sum_cout > median_flag)
{
median = i;
break;
}
}
mNum = sum_cout;
return median;
}
结果:
另外提一句,使用imread()函数读取图片时,最好加上IMREAD_GREYSCALE(表示读取为灰度图片)等类似限定图片类型的限定参数,不然可能会出现很多意想不到的意外,我就是因为这个问题,改了两天的bug。。。泪目。。。
稍微说下吧,当我用
Mat image1 = imread("salt_pepper_Image.jpg");
直接读取时,最终结果是这样:
很奇怪吧,只有左边一部分进行了 中值滤波,我查过总共是 150列(利用image.cols),但是实际上远不是150列。
当我加上IMREAD_GREYSCALE时,即:
Mat image1 = imread("salt_pepper_Image.jpg",IMREAD_GREYSCALE);
后,输出的图片是这样的:
所以,不能 偷懒,该写上的 东西就别忘!!!