在上一篇咱们实现了读取噪声图像, 而后 进行三种形式的均值滤波获得结果, 因为咱们本身写的均值滤波未做边缘处理, 因此效果有必定的降低, 可是整体来讲, 咱们获得的结果可以说明咱们的算法执行以后获得的图像噪声更低, 图像更清晰. 可是也会形成图像的模糊, 致使部分细节丢失. 在这一章中,咱们介绍一下中值滤波及其实现php
首先介绍了中值滤波的原理, 给出其实现思路,并根据思路实现了 C++ 的代码, 而后 一样测试 opencv 自带的中值滤波, 一样的测试图像, 获得对比结果, 分析代码的实现过程, .html
中值滤波(Media Filter)就是对于图像的每个点计算其邻域窗口的像素序列中值, 能够表示为:c++
核心就是将相应窗口内的像素值进行排列, 咱们以前也说过, 咱们选择的窗口为奇数尺寸, 因此咱们可以保证窗口内的像素个数也是奇数个, 这样咱们能够保证取得惟一的中值, 相应的设置为该点的目标值就好了.git
咱们来实现一下, 这方面仍是可以找到很多结果的, 感受这个博主写的仍是很不错的,有兴趣的能够看下数字图像处理------中值滤波,还有图像处理之中值滤波介绍及C实现, 或者 中值滤波器(Median filter)特性及其实现, 这里我就再也不造轮子了, 咱们来看下 C++的实现
, 主要参考 第一篇文章, 能够看下效果github
这里有一点点须要讨论的, 对于彩色图像的三个通道怎么处理, 本身的思路就是分红三个通道进行处理, 而后分别获得三个图以后进行合并三个通道, 获得结果图像. 查了下 目测你们都是这么作的, 能够看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 自定义函数的方法, 这边采用的比较函数的方式, 还有别的方式实现两个元素的比较, 能够参考c++中vector自定义排序的问题数组
// 自定义两个像素的比较函数, // 使用和值 排序 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 提供的 函数仍是很丰富的, 很厉害bash
// 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]); }
没什么难度, 就是用来拼接一个字符串, 用来显示在界面上, 或者 输出输出来,app
这样的咱们就能很容易的去写测试的函数了, 三种方法依次去实现, 比较麻烦的是第一种, 须要将彩色图像分红三个通道的灰度图像, 而后分别进行中值滤波, 最后合并结果,获得结果图像.
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 以上的图的显示, 因此有须要的去看这个就好.
最近一直用的图拼接使用的 作好图 在线拼接图片 主要是懒得本身写了, http://www.zuohaotu.com/image-merge.aspx 连接在这里了 有须要自取
这里的第一副图是噪声图像, 第二副是咱们拆分通道处理后拼接起来了的, 没有处理边缘的细节问题, 第三章图就是咱们进行自定义中值排序获得的图, 部分点处理不掉 甚至还复制了出来, 不过总体效果仍是不错的, 第四章图就是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 的实现, 而后咱们根据以前的程序上加入了中值滤波的实现效果, 最后在中值滤波的基础上进行优化, 作了自适应中值滤波的实现,测试发现结果还要更好, 最后我稍微提了一下中值滤波的优化加速, 这一块作的不少, 能够去参考里面去找, 算是完成了中值滤波的章节, 若是这里搞懂了我再来完善这一章节..
本文由博客一文多发平台 OpenWrite 发布!