opencv-11-中值滤波及自适应中值滤波
开始之前
在上一篇我们实现了读取噪声图像, 然后 进行三种形式的均值滤波得到结果, 由于我们自己写的均值滤波未作边缘处理, 所以效果有一定的下降, 但是总体来说, 我们得到的结果能够说明我们的算法执行之后得到的图像噪声更低, 图像更清晰. 但是也会造成图像的模糊, 导致部分细节丢失. 在这一章中,我们介绍一下中值滤波及其实现
摘要
首先介绍了中值滤波的原理, 给出其实现思路,并根据思路实现了 c++ 的代码, 然后 同样测试 opencv 自带的中值滤波, 同样的测试图像, 得到对比结果, 分析代码的实现过程, .
正文
中值滤波原理
中值滤波(media filter)就是对于图像的每一个点计算其邻域窗口的像素序列中值, 可以表示为:
核心就是将相应窗口内的像素值进行排列, 我们之前也说过, 我们选择的窗口为奇数尺寸, 所以我们能够保证窗口内的像素个数也是奇数个, 这样我们可以保证取得唯一的中值, 相应的设置为该点的目标值就行了.
c++ 实现中值滤波
我们来实现一下, 这方面还是能够找到不少结果的, 感觉这个博主写的还是很不错的,有兴趣的可以看下,还有图像处理之中值滤波介绍及c实现, 或者 中值滤波器(median filter)特性及其实现, 这里我就不再造*了, 我们来看下 c++的实现
, 主要参考 第一篇文章, 可以看下效果
这里有一点点需要讨论的, 对于彩色图像的三个通道怎么处理, 自己的思路就是分成三个通道进行处理, 然后分别得到三个图之后进行合并三个通道, 得到结果图像. 查了下 目测大家都是这么做的, 可以看opencv 彩色图像的自适应中值滤波 c++ 和 彩色图像空间滤波(matlab) 这两篇文章, 思路都是一样的, 我们来实现一下.
//中值滤波:c++ 代码实现 // 处理单通道图像 // 参考 https://www.cnblogs.com/ranjiewen/p/5699395.html cv::mat medianfiltergray(const cv::mat &src, int ksize = 3) { cv::mat dst = src.clone(); //0. 准备:获取图片的宽,高和像素信息, const int num = ksize * ksize; std::vector<uchar> pixel(num); //相对于中心点,3*3领域中的点需要偏移的位置 int delta[3 * 3][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, {1, 1} }; //1. 中值滤波,没有考虑边缘 for (int i = 1; i < src.rows - 1; ++i) { for (int j = 1; j < src.cols - 1; ++j) { //1.1 提取领域值 // 使用数组 这样处理 8邻域值 不适合更大窗口 for (int k = 0; k < num; ++k) { pixel[k] = src.at<uchar>(i+delta[k][0], j+ delta[k][1]); } //1.2 排序 // 使用自带的库及排序即可 std::sort(pixel.begin(), pixel.end()); //1.3 获取该中心点的值 dst.at<uchar>(i, j) = pixel[num / 2]; } } return dst; }
思路还是那个思路, 不过在写的过程中, 我在想, 能不能直接处理彩色的图像呢, 对于彩色图像最麻烦的地方就是排序了, 我们没办法考虑颜色的高低值, 所以 那我们自定义一个比较函数应该就行了吧. 我们使用三个颜色的和值 做比较
这里使用了c++ 的sort 自定义函数的方法, 这边采用的比较函数的方式, 还有别的方式实现两个元素的比较, 可以参考
// 自定义两个像素的比较函数, // 使用和值 排序 bool comp(const cv::vec3b &p1, const cv::vec3b &p2) { return (p1[0] + p1[1] + p1[2]) < (p2[0] + p2[1] + p2[2]); } // 尝试彩色图像, 中值排序使用三个通道的和排序 cv::mat medianfiltercolor(const cv::mat &src, int ksize = 3) { cv::mat dst = src.clone(); //0. 准备:获取图片的宽,高和像素信息, const int num = ksize * ksize; std::vector<cv::vec3b> pixel(num); //相对于中心点,3*3领域中的点需要偏移的位置 int delta[3 * 3][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, {1, 1} }; //1. 中值滤波,没有考虑边缘 for (int i = 1; i < src.rows - 1; ++i) { for (int j = 1; j < src.cols - 1; ++j) { //1.1 提取领域值 // 使用数组 这样处理 8邻域值 不适合更大窗口 for (int k = 0; k < num; ++k) { pixel[k] = src.at<cv::vec3b>(i + delta[k][0], j + delta[k][1]); } //1.2 排序 // 使用自定义的排序函数排序彩色图像 std::sort(pixel.begin(),pixel.end(),comp); //1.3 获取该中心点的值 dst.at<cv::vec3b>(i, j) = pixel[num / 2]; } } return dst; }
opencv 中值滤波
这里还是之前的方法, 一样的接口, 实现起来很简单, opencv 提供的 函数还是很丰富的, 很厉害
// opencv 中值滤波 cv::mat mediafilterdefault(const cv::mat &src, int ksize = 3) { cv::mat dst; cv::medianblur(src, dst, ksize); return dst; }
中值滤波算法对比
我们这里就跟之前均值算法的计算很相似了, 我们已经写了三种算法的实现, 然后测试就好了, 趁着功夫, 将上一章一直重复的两个图比较并输出参数的部分写成了一个函数
// 对比两个图像 然后输出 参数信息 qstring compareimages(const cv::mat &i1, const cv::mat &i2, const qstring str = "noise", const qstring str_temp = "image-%1: psnr:%2, mssim: b:%3 g:%4 r:%5") { double psnr_ = getpsnr(i1, i2); cv::scalar mssim_ = getmssim(i1, i2); // 根据 输出模板 生成参数信息 qstring res_str = str_temp.arg(str) .arg(psnr_) .arg(mssim_.val[0]) .arg(mssim_.val[1]) .arg(mssim_.val[2]); return res_str; // cv::imwrite(image_dir + "dst_" + std::to_string(i + 1) + ".png", dst[i]); }
没什么难度, 就是用来拼接一个字符串, 用来显示在界面上, 或者 输出输出来,
这样的我们就能很容易的去写测试的函数了, 三种方法依次去实现, 比较麻烦的是第一种, 需要将彩色图像分成三个通道的灰度图像, 然后分别进行中值滤波, 最后合并结果,得到结果图像.
void mainwindow::testfunc2(void) { // 测试 中值 滤波 三种方式的不同 const int test = 1; // 使用统一的图进行测试 暂时使用 高 椒盐噪声图像 qstring res_str; // 噪声图像的参数值 res_str = compareimages(gsrcimg, gnoiseimg[test]); ui->pt_log->appendplaintext(res_str); cv::mat test_img = gnoiseimg[test]; cv::mat dst[3]; // 测试 中值滤波 拆分三个通道进行中值滤波然后合并图像 std::vector<cv::mat> bgr(3); cv::split(test_img, bgr); bgr[0] = medianfiltergray(bgr[0]); bgr[1] = medianfiltergray(bgr[1]); bgr[2] = medianfiltergray(bgr[2]); cv::merge(bgr, dst[0]); // 第一种方式 dst[1] = medianfiltercolor(test_img); // 第二种 彩色直接 计算中值滤波 dst[2] = mediafilterdefault(test_img); // opencv 实现 中值滤波 // 分别计算三种方式得到的滤波的效果 (结果图与 原始图比较) for(int i=0;i<3;i++) { res_str = compareimages(gsrcimg, dst[i]); // 噪声的参数值 ui->pt_log->appendplaintext(res_str); cv::imwrite(image_dir + "dst_media_" + std::to_string(i+1)+".png",dst[i]); } }
我们仍然选择高椒盐噪声图像用于测试, 先看下结果, 分别对应噪声图的参数, 以及三种方法进行的参数结果.
第三行的结果就是我们进行自定义排序的图像处理,
image-noise: psnr:19.4727, mssim: b:0.353134 g:0.383638 r:0.629353 image-noise: psnr:33.3725, mssim: b:0.896859 g:0.915976 r:0.912563 image-noise: psnr:31.2668, mssim: b:0.866162 g:0.901717 r:0.879337 image-noise: psnr:34.3125, mssim: b:0.902338 g:0.921419 r:0.91531
我们看一下结果图像, 原始图像可以看 https://gitee.com/schen00/blogimage/raw/master/image/1588468343599.png 这里,
gitee 限制了 1m 以上的图的显示, 所以有需要的去看这个就好.
最近一直用的图拼接使用的 主要是懒得自己写了, 链接在这里了 有需要自取
这里的第一副图是噪声图像, 第二副是我们拆分通道处理后拼接起来了的, 没有处理边缘的细节问题, 第三章图就是我们进行自定义中值排序得到的图, 部分点处理不掉 甚至还复制了出来, 不过整体效果还是不错的, 第四章图就是opencv 自带的中值滤波的处理.
中值滤波算法优化
类似均值滤波, 处理的时候考虑变化了的边界就好了, 那中值滤波怎么优化呢, 感觉这一块做的人还挺多, 中值滤波的优化主要是使用自适应中值滤波, 和在中值滤波的方法上进行加速运算,
自适应中值滤波
可以参考, 我感觉介绍的还是比较详细的, 主要的思路就是如果噪声比较严重时, 窗口获取到的中值可能是噪声值, 这时候增大窗口, 然后重新进行中值滤波,直到找到比较符合的中值.
引用他给出的部分叙述
在自适应中值滤波算法中,a步骤里面会先判断是否满足 \(zmin<zmed<zmaxzmin<zmed<zmax\)。这一步骤实质是判断当前区域的中值点是否是噪声点,通常来说是满足 \(zmin<zmed<zmaxzmin<zmed<zmax\) 这个条件的,此时中值点不是噪声点,跳转到b;考虑一些特殊情况,如果 \(zmed=zminzmed=zmin或者zmed=zmaxzmed=zmax\) ,则认为是噪声点,应该扩大窗口尺寸,在一个更大的范围内寻找一个合适的非噪声点,随后再跳转到b,否则输出的中值点是噪声点;
接下来考虑跳转到b之后的情况:判断中心点的像素值是否是噪声点,判断条件为 \(zmin<zxy<zmaxzmin<zxy<zmax\),原理同上,因为如果\(zxy=zminzxy=zmin\)或者\(zxy=zmaxzxy=zmax\),则认为是噪声点。如果不是噪声点,我们可以保留当前像素点的灰度值;如果是噪声点,则使用中值替代原始灰度值,滤去噪声。
同样的, 图像处理基础(2):自适应中值滤波器(基于opencv实现), 这篇文章写的更好一点, 并给出了 opencv 的实现代码, 我们来看一下
// 自适应中值滤波窗口实现 // 图像 计算座标, 窗口尺寸和 最大尺寸 uchar adaptiveprocess(const mat &im, int row, int col, int kernelsize, int maxsize) { std::vector<uchar> pixels; for (int a = -kernelsize / 2; a <= kernelsize / 2; a++) for (int b = -kernelsize / 2; b <= kernelsize / 2; b++) { pixels.push_back(im.at<uchar>(row + a, col + b)); } sort(pixels.begin(), pixels.end()); auto min = pixels[0]; auto max = pixels[kernelsize * kernelsize - 1]; auto med = pixels[kernelsize * kernelsize / 2]; auto zxy = im.at<uchar>(row, col); if (med > min && med < max) { // to b if (zxy > min && zxy < max) return zxy; else return med; } else { kernelsize += 2; if (kernelsize <= maxsize) return adaptiveprocess(im, row, col, kernelsize, maxsize); // 增大窗口尺寸,继续a过程。 else return med; } } // 自适应均值滤波 cv::mat adaptivemediafilter(const cv::mat &src, int ksize = 3) { int minsize = 3; // 滤波器窗口的起始尺寸 int maxsize = 7; // 滤波器窗口的最大尺寸 cv::mat dst; // 扩展图像的边界 cv::copymakeborder(src, dst, maxsize / 2, maxsize / 2, maxsize / 2, maxsize / 2, cv::bordertypes::border_reflect); // 图像循环 for (int j = maxsize / 2; j < dst.rows - maxsize / 2; j++) { for (int i = maxsize / 2; i < dst.cols * dst.channels() - maxsize / 2; i++) { dst.at<uchar>(j, i) = adaptiveprocess(dst, j, i, minsize, maxsize); } } cv::rect r = cv::rect(cv::point(maxsize / 2, maxsize / 2), cv::point(dst.rows-maxsize / 2, dst.rows-maxsize / 2)); cv::mat res = dst(r); return res; }
我们这里还是使用的分离三个通道然后进行自适应均值滤波, 参数就使用默认的3, 最大窗口设为7, 我们测试还是跑的之前的高椒盐噪声图像, 下面给出的最后一行就是我们使用自适应中值滤波得到的结果, 至少从 psnr 的参数上我们能看到图像质量的提升, 我们给出图像结果, 肉眼上能看出稍微一点的区别, 对比之前的已经完全不存在白点了, 图像已经比较接近真实图像了..
// 拆分三个通道 计算自适应中值滤波 cv::split(test_img, bgr); for (int i = 0; i < 3; i++) bgr[i] = adaptivemediafilter(bgr[i]); cv::merge(bgr, dst[3]);
image-noise: psnr:19.4727, mssim: b:0.353134 g:0.383638 r:0.629353 image-noise: psnr:33.3725, mssim: b:0.896859 g:0.915976 r:0.912563 image-noise: psnr:31.2655, mssim: b:0.86636 g:0.901517 r:0.879384 image-noise: psnr:34.3125, mssim: b:0.902338 g:0.921419 r:0.91531 image-noise: psnr:37.4024, mssim: b:0.946158 g:0.958146 r:0.953884
中值滤波计算加速
由于中值滤波无论多大的窗口都是用来将窗口内的像素进行排序, 这里的优化有两个方向 一个是窗口的优化, 一个计算的加速,
我真的 imageshop 的这篇文章 任意半径中值滤波(扩展至百分比滤波器)o(1)时间复杂度算法的原理、实现及效果。
已经写的比较完全了, 我都不想在写了,
再从 偷一张图,
感兴趣的可以看一下的链接
opencv源码分析(四):中值滤波 这里详细介绍了 opencv 中怎么实现的 中值滤波
总结
算是从中值滤波的基础上做了一个开始, 介绍了一下中值滤波的原理, 然后根据原理使用c++ 进行了实现, 之后再进行 opencv 的实现, 然后我们根据之前的程序上加入了中值滤波的实现效果, 最后在中值滤波的基础上进行优化, 做了自适应中值滤波的实现,测试发现结果还要更好, 最后我稍微提了一下中值滤波的优化加速, 这一块做的很多, 可以去参考里面去找, 算是完成了中值滤波的章节, 如果这里搞懂了我再来完善这一章节..
参考
- 《绘制函数调用图(call graph)(4):doxygen + graphviz_运维_许振坪的专栏-csdn博客》. 见于 2020年5月2日. .
- 《任意半径中值滤波(扩展至百分比滤波器)o(1)时间复杂度算法的原理、实现及效果。 - imageshop - 博客园》. 见于 2020年5月3日. https://www.cnblogs.com/imageshop/archive/2013/04/26/3045672.html.
- 《数字图像处理------中值滤波 - ranjiewen - 博客园》. 见于 2020年5月2日. .
- 《【算法随记三】小半径中值模糊的急速实现(16mb图7.5ms实现) + photoshop中蒙尘和划痕算法解读。 - imageshop - 博客园》. 见于 2020年5月3日. https://www.cnblogs.com/imageshop/p/11087804.html.
- 《图像处理基础(2):自适应中值滤波器(基于opencv实现) - *_icv - 博客园》. 见于 2020年5月3日. .
- 《图像处理之原理 - 中值滤波 - tanfy - 博客园》. 见于 2020年5月2日. .
- 《图像处理之中值滤波介绍及c实现 - 淇淇宝贝 - 博客园》. 见于 2020年5月2日. .
- 《中值滤波的快速算法_网络_linjm-机器视觉-csdn博客》. 见于 2020年5月3日. .
- 《中值滤波器》. 收入 *,*的百科全书, 2017年9月8日. https://zh.wikipedia.org/w/index.php?title=中值滤波器&oldid=46098815.
- 《中值滤波器(median filter)特性及其实现_人工智能_ivan 的专栏-csdn博客》. 见于 2020年5月2日. .
- 《自适应中值滤波及实现_人工智能_hongbin_xu的博客-csdn博客》. 见于 2020年5月3日. .
- github. 《arm-software/computelibrary》. 见于 2020年5月3日. https://github.com/arm-software/computelibrary.
- 《c++中vector自定义排序的问题_c/c++_stone_sky-csdn博客》. 见于 2020年5月2日. .
- 《opencv 彩色图像的自适应中值滤波 c++_人工智能_cyf15238622067的博客-csdn博客》. 见于 2020年5月3日. .
- 《opencv: image filtering》. 见于 2020年5月3日. .
- 知乎专栏. 《opencv图像处理专栏九 | 基于直方图的快速中值滤波算法》. 见于 2020年5月3日. .
- 简书. 《opencv源码分析(四):中值滤波》. 见于 2020年5月2日. .
本文由博客一文多发平台 openwrite 发布!
上一篇: 常见算法总结 - 二叉树篇
下一篇: MyBatisPlus详解