上课老师没有详细讲,自己查阅了一下然后做了点程序验证一下
简单来说就是用一个nn的框框(n为奇数)去框一个图像,框选中的nn个像素点中取中值,然后用中值取代替框框中间的那个点的像素
哎说起来简单,代码有点烧脑,直接撸上来
void MF(Mat& image_input, Mat& image_output, int diameter)
{
int radius = (diameter - 1) / 2;//向下取整
int i, j, p, q, t,sum = 0,median;
uchar val;
int Hist[256] = { 0 };
int Threshold = (diameter*diameter + 1) / 2;
for (i = 0; i < image_input.rows; i++)
{
for (j = 0; j <radius; j++)
{
image_output.at<uchar>(i, j) = image_input.at<uchar>(i, j);
image_output.at<uchar>(i, (image_input.cols - j - 1)) = image_input.at<uchar>(i, (image_input.cols - j - 1));
}
}
for (j = 0; j < image_input.cols; j++)
{
for (i = 0; i < radius; i++)
{
image_output.at<uchar>(i, j) = image_input.at<uchar>(i, j);
image_output.at<uchar>((image_input.rows - i - 1), j) = image_input.at<uchar>((image_input.rows - i - 1), j);
}
}
//处理完了边缘部分
for (i = radius; i < (image_input.rows - radius); i++)
{
for (j = radius; j < (image_input.cols - radius); j++)
{
memset(Hist, 0, sizeof(Hist)); //初始化直方图;
for (p = i - radius; p <= i + radius; p++)
{
for (q = j - radius; q <= j + radius; q++)
{
val = image_input.at<uchar>(p, q);
Hist[val]++;
}
}
for (t = 0; t < 256; t++)
{
sum = sum + Hist[t];
if (sum >= Threshold)
{
sum = 0;
median = t;
break;
}
}
//中值找到,需要替换
image_output.at<uchar>(i, j) = (uchar)median;
}
}
}
这里推荐一篇文献:A Fast Two-Dimensional Median Filtering Algorithm
这个讲的就是中值滤波是对每个可以滤波的点进行一遍中值计算,运算量还是比较大的。
但是,快速中值滤波简化了中值计算
如图所示,对于同一行的像素点,其实它是向右平移一格,其实不需要计算所有像素点的中值:只要去掉原来的最左边一列,然后加上新的最右边一列,就可以形成新的直方图
这里用直方图的方法计算中值,对于灰度图来说一共有256级,可以新建一个数组作为直方图,当出现某一级灰度,变在这个直方图数组对应的灰度数组上加1;
一:建立第一个窗口的直方图,并找到中值。然后计算这个窗口里小于中值的像素个数作为itmdn
二:移动到下一个窗口,然后删掉最左边一列,加上最右边一列,然后更新灰度直方图,更新itmdn,这时候的itmdn存储的是当前窗口中小于上一个窗口中值的像素个数。
三:比较itmdn和(窗口像素数/2),会有三种可能,
若itmdn大,中值左移;
若itmdn小,中值右移;
若一样大,中值不移动;
四:重复步骤二
上代码:
void FMF(Mat& image_input, Mat& image_output,int diameter) //用于提取边缘,保留不动
{
int radius = (diameter-1)/2;//向下取整
int i, j, p, q, s, t;
uchar val;
int sum = 0;
int Hist[256] = { 0 };
int Threshold = (diameter*diameter + 1) / 2;
int median;
for (i = 0; i < image_input.rows; i++)
{
for (j = 0; j <radius; j++)
{
image_output.at<uchar>(i,j) = image_input.at<uchar>(i, j);
image_output.at<uchar>(i,(image_input.cols-j-1)) = image_input.at<uchar>(i, (image_input.cols - j - 1));
}
}
for (j = 0; j < image_input.cols; j++)
{
for (i = 0; i < radius; i++)
{
image_output.at<uchar>(i,j) = image_input.at<uchar>(i, j);
image_output.at<uchar>((image_input.rows-i-1),j) = image_input.at<uchar>((image_input.rows - i - 1), j);
}
}
//处理完了边缘部分
for (i = radius; i < (image_input.rows - radius); i++)
{
for (j = radius; j < (image_input.cols - radius); j++)
{
if (j == radius) //表示每行第一个需要滤波的元素,需要对它进行直方图建立
{
memset(Hist, 0, sizeof(Hist)); //初始化直方图;
for ( p = i - radius; p <=i + radius; p++)
{
for ( q = j - radius; q <= j + radius; q++)
{
val = image_input.at<uchar>(p, q);
Hist[val]++;
}
}
}//判断结束表示直方图建立完毕,后面要做的是更新直方图
//如果j != radius表示直方图处于更新中
if (j != radius)
{
for (s = i - radius; s <= i + radius; s++)
{
val = image_input.at<uchar>(s, j - radius - 1);
Hist[val]--;
val = image_input.at<uchar>(s, j + radius);
Hist[val]++;
}//直方图更新完毕
}
//寻找中值
for ( t = 0; t < 256; t++)
{
sum = sum + Hist[t];
if (sum >= Threshold)
{
sum = 0;
median = t;
break;
}
}
//中值找到,需要替换
image_output.at<uchar>(i, j) = (uchar)median;
}
}
}
简单的很,一句话搞定
medianBlur(src, dst, 9);
对外输出图片的中值滤波处理时间
Mat dst = Mat::zeros(src.size(),CV_8UC1);
start = clock();
FMF(src, dst, 9);
end = clock();
start = (end - start) / CLOCKS_PER_SEC;
printf("the fast filter consume time is %f s\n", start);
namedWindow("fast filter", WINDOW_AUTOSIZE);
imshow("fast filter", dst);
dst = Mat::zeros(src.size(), CV_8UC1);
所以啊:
1、快速中值滤波有一定的加快计算速度的效果,
2、而且随着窗口的变大,中值滤波和快速中值滤波所用的时间相差的越大,
3、当图片的大小越大,中值滤波和快速中值滤波所用的时间相差的越大(快速中值滤波比中值滤波快的更明显),
4、滤波窗口越大,图像越模糊,计算量越大
5、当然,都没有API快,hhhh
最后,钢炼yyds