在上一篇咱們實現了讀取噪聲圖像, 而後 進行三種形式的均值濾波獲得結果, 因爲咱們本身寫的均值濾波未做邊緣處理, 因此效果有必定的降低, 可是整體來講, 咱們獲得的結果可以說明咱們的算法執行以後獲得的圖像噪聲更低, 圖像更清晰. 可是也會形成圖像的模糊, 致使部分細節丟失. 在這一章中,咱們介紹一下中值濾波及其實現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 發佈!