opencv-11-中值濾波及自適應中值濾波
阿新 • • 發佈:2020-05-03
## 開始之前
在上一篇我們實現了讀取噪聲影象, 然後 進行三種形式的均值濾波得到結果, 由於我們自己寫的均值濾波未作邊緣處理, 所以效果有一定的下降, 但是總體來說, 我們得到的結果能夠說明我們的演算法執行之後得到的影象噪聲更低, 影象更清晰. 但是也會造成影象的模糊, 導致部分細節丟失. 在這一章中,我們介紹一下中值濾波及其實現
## 摘要
首先介紹了中值濾波的原理, 給出其實現思路,並根據思路實現了 C++ 的程式碼, 然後 同樣測試 opencv 自帶的中值濾波, 同樣的測試影象, 得到對比結果, 分析程式碼的實現過程, .
## 正文
### 中值濾波原理
中值濾波(Media Filter)就是對於影象的每一個點計算其鄰域視窗的畫素序列中值, 可以表示為:
$$
g(x,y) = meida_{(i,j) \in S}f(i,j)
$$
核心就是將相應視窗內的畫素值進行排列, 我們之前也說過, 我們選擇的視窗為奇數尺寸, 所以我們能夠保證視窗內的畫素個數也是奇數個, 這樣我們可以保證取得唯一的中值, 相應的設定為該點的目標值就行了.
### C++ 實現中值濾波
我們來實現一下, 這方面還是能夠找到不少結果的, 感覺這個博主寫的還是很不錯的,有興趣的可以看下[數字影象處理------中值濾波](https://www.cnblogs.com/ranjiewen/p/5699395.html),還有[影象處理之中值濾波介紹及C實現](https://www.cnblogs.com/qiqibaby/p/5281743.html), 或者 [中值濾波器(Median filter)特性及其實現](https://blog.csdn.net/liyuanbhu/article/details/48502005), 這裡我就不再造輪子了, 我們來看下 C++的實現
, 主要參考 第一篇文章, 可以看下效果
這裡有一點點需要討論的, 對於彩色影象的三個通道怎麼處理, 自己的思路就是分成三個通道進行處理, 然後分別得到三個圖之後進行合併三個通道, 得到結果影象. 查了下 目測大家都是這麼做的, 可以看[OpenCV 彩色影象的自適應中值濾波 C++](https://blog.csdn.net/cyf15238622067/article/details/88718615) 和 [彩色影象空間濾波(MATLAB)](https://blog.csdn.net/liyuqian199695/article/details/70050852) 這兩篇文章, 思路都是一樣的, 我們來實現一下.
```cpp
//中值濾波: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 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(i+delta[k][0], j+ delta[k][1]);
}
//1.2 排序 // 使用自帶的庫及排序即可
std::sort(pixel.begin(), pixel.end());
//1.3 獲取該中心點的值
dst.at(i, j) = pixel[num / 2];
}
}
return dst;
}
```
思路還是那個思路, 不過在寫的過程中, 我在想, 能不能直接處理彩色的影象呢, 對於彩色影象最麻煩的地方就是排序了, 我們沒辦法考慮顏色的高低值, 所以 那我們自定義一個比較函式應該就行了吧. 我們使用三個顏色的和值 做比較
這裡使用了C++ 的sort 自定義函式的方法, 這邊採用的比較函式的方式, 還有別的方式實現兩個元素的比較, 可以參考[c++中vector自定義排序的問題](https://blog.csdn.net/aastoneaa/article/details/8471722)
```cpp
// 自定義兩個畫素的比較函式, // 使用和值 排序
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 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(i + delta[k][0], j + delta[k][1]);
}
//1.2 排序 // 使用自定義的排序函式排序彩色影象
std::sort(pixel.begin(),pixel.end(),comp);
//1.3 獲取該中心點的值
dst.at(i, j) = pixel[num / 2];
}
}
return dst;
}
```
### opencv 中值濾波
這裡還是之前的方法, 一樣的介面, 實現起來很簡單, opencv 提供的 函式還是很豐富的, 很厲害
```cpp
// opencv 中值濾波
cv::Mat mediaFilterDefault(const cv::Mat &src, int ksize = 3)
{
cv::Mat dst;
cv::medianBlur(src, dst, ksize);
return dst;
}
```
### 中值濾波演算法對比
我們這裡就跟之前均值演算法的計算很相似了, 我們已經寫了三種演算法的實現, 然後測試就好了, 趁著功夫, 將上一章一直重複的兩個圖比較並輸出引數的部分寫成了一個函式
```cpp
// 對比兩個影象 然後輸出 引數資訊
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]);
}
```
沒什麼難度, 就是用來拼接一個字串, 用來顯示在介面上, 或者 輸出輸出來,
這樣的我們就能很容易的去寫測試的函數了, 三種方法依次去實現, 比較麻煩的是第一種, 需要將彩色影象分成三個通道的灰度影象, 然後分別進行中值濾波, 最後合併結果,得到結果影象.
```cpp
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 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]);
}
}
```
我們仍然選擇高椒鹽噪聲影象用於測試, 先看下結果, 分別對應噪聲圖的引數, 以及三種方法進行的引數結果.
第三行的結果就是我們進行自定義排序的影象處理,
```bash
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) 主要是懶得自己寫了, http://www.zuohaotu.com/image-merge.aspx 連結在這裡了 有需要自取
![中值濾波處理結果影象預覽圖](https://img2020.cnblogs.com/other/841950/202005/841950-20200503120215811-1291224.png)
這裡的第一副圖是噪聲影象, 第二副是我們拆分通道處理後拼接起來了的, 沒有處理邊緣的細節問題, 第三章圖就是我們進行自定義中值排序得到的圖, 部分點處理不掉 甚至還複製了出來, 不過整體效果還是不錯的, 第四章圖就是opencv 自帶的中值濾波的處理.
### 中值濾波演算法優化
類似均值濾波, 處理的時候考慮變化了的邊界就好了, 那中值濾波怎麼優化呢, 感覺這一塊做的人還挺多, 中值濾波的優化主要是使用自適應中值濾波, 和在中值濾波的方法上進行加速運算,
#### 自適應中值濾波
可以參考[自適應中值濾波及實現](https://blog.csdn.net/hongbin_xu/article/details/79780967), 我感覺介紹的還是比較詳細的, 主要的思路就是如果噪聲比較嚴重時, 視窗獲取到的中值可能是噪聲值, 這時候增大視窗, 然後重新進行中值濾波,直到找到比較符合的中值.
引用他給出的部分敘述
> 在自適應中值濾波演算法中,A步驟裡面會先判斷是否滿足 $Zmin pixels;
for (int a = -kernelSize / 2; a <= kernelSize / 2; a++)
for (int b = -kernelSize / 2; b <= kernelSize / 2; b++)
{
pixels.push_back(im.at(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(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(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 的引數上我們能看到影象質量的提升, 我們給出影象結果, 肉眼上能看出稍微一點的區別, 對比之前的已經完全不存在白點了, 影象已經比較接近真實影象了..
```cpp
// 拆分三個通道 計算自適應中值濾波
cv::split(test_img, bgr);
for (int i = 0; i < 3; i++)
bgr[i] = adaptiveMediaFilter(bgr[i]);
cv::merge(bgr, dst[3]);
```
```bash
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
```
![自適應中值濾波影象結果](https://img2020.cnblogs.com/other/841950/202005/841950-20200503120217740-643588106.png)
#### 中值濾波計算加速
由於中值濾波無論多大的視窗都是用來將視窗內的畫素進行排序, 這裡的優化有兩個方向 一個是視窗的優化, 一個計算的加速,
我真的 imageshop 的這篇文章 [任意半徑中值濾波(擴充套件至百分比濾波器)O(1)時間複雜度演算法的原理、實現及效果。](https://www.cnblogs.com/Imageshop/archive/2013/04/26/3045672.html)
已經寫的比較完全了, 我都不想在寫了,
![影象視窗滑動](https://img2020.cnblogs.com/other/841950/202005/841950-20200503120218947-2106789502.png)
再從[中值濾波的快速演算法](https://blog.csdn.net/linj_m/article/details/35780163) 偷一張圖,
![中值濾波演算法流程](https://img2020.cnblogs.com/other/841950/202005/841950-20200503120221043-69453287.png)
感興趣的可以看一下的連結
[OpenCV原始碼分析(四):中值濾波](https://www.jianshu.com/p/eb0b856286f2) 這裡詳細介紹了 opencv 中怎麼實現的 中值濾波
![中值濾波函式呼叫圖](https://img2020.cnblogs.com/other/841950/202005/841950-20200503120222173-391224216.png)
## 總結
算是從中值濾波的基礎上做了一個開始, 介紹了一下中值濾波的原理, 然後根據原理使用C++ 進行了實現, 之後再進行 opencv 的實現, 然後我們根據之前的程式上加入了中值濾波的實現效果, 最後在中值濾波的基礎上進行優化, 做了自適應中值濾波的實現,測試發現結果還要更好, 最後我稍微提了一下中值濾波的優化加速, 這一塊做的很多, 可以去參考裡面去找, 算是完成了中值濾波的章節, 如果這裡搞懂了我再來完善這一章節..
## 參考
1. 《繪製函式呼叫圖(call graph)(4):doxygen + graphviz_運維_許振坪的專欄-CSDN部落格》. 見於 2020年5月2日. https://blog.csdn.net/benkaoya/article/details/79763668.
2. 《任意半徑中值濾波(擴充套件至百分比濾波器)O(1)時間複雜度演算法的原理、實現及效果。 - Imageshop - 部落格園》. 見於 2020年5月3日. https://www.cnblogs.com/Imageshop/archive/2013/04/26/3045672.html.
3. 《數字影象處理------中值濾波 - ranjiewen - 部落格園》. 見於 2020年5月2日. https://www.cnblogs.com/ranjiewen/p/5699395.html.
4. 《【演算法隨記三】小半徑中值模糊的急速實現(16MB圖7.5ms實現) + Photoshop中蒙塵和劃痕演算法解讀。 - Imageshop - 部落格園》. 見於 2020年5月3日. https://www.cnblogs.com/Imageshop/p/11087804.html.
5. 《影象處理基礎(2):自適應中值濾波器(基於OpenCV實現) - Brook_icv - 部落格園》. 見於 2020年5月3日. https://www.cnblogs.com/wangguchangqing/p/6379646.html.
6. 《影象處理之原理 - 中值濾波 - tanfy - 部落格園》. 見於 2020年5月2日. https://www.cnblogs.com/tanfy/p/median_filter.html.
7. 《影象處理之中值濾波介紹及C實現 - 淇淇寶貝 - 部落格園》. 見於 2020年5月2日. https://www.cnblogs.com/qiqibaby/p/5281743.html.
8. 《中值濾波的快速演算法_網路_LinJM-機器視覺-CSDN部落格》. 見於 2020年5月3日. https://blog.csdn.net/linj_m/article/details/35780163.
9. 《中值濾波器》. 收入 維基百科,自由的百科全書, 2017年9月8日. https://zh.wikipedia.org/w/index.php?title=%E4%B8%AD%E5%80%BC%E6%BB%A4%E6%B3%A2%E5%99%A8&oldid=46098815.
10. 《中值濾波器(Median filter)特性及其實現_人工智慧_Ivan 的專欄-CSDN部落格》. 見於 2020年5月2日. https://blog.csdn.net/liyuanbhu/article/details/48502005.
11. 《自適應中值濾波及實現_人工智慧_hongbin_xu的部落格-CSDN部落格》. 見於 2020年5月3日. https://blog.csdn.net/hongbin_xu/article/details/79780967.
12. GitHub. 《ARM-Software/ComputeLibrary》. 見於 2020年5月3日. https://github.com/ARM-software/ComputeLibrary.
13. 《c++中vector自定義排序的問題_C/C++_Stone_Sky-CSDN部落格》. 見於 2020年5月2日. https://blog.csdn.net/aastoneaa/article/details/8471722.
14. 《OpenCV 彩色影象的自適應中值濾波 C++_人工智慧_cyf15238622067的部落格-CSDN部落格》. 見於 2020年5月3日. https://blog.csdn.net/cyf15238622067/article/details/88718615.
15. 《opencv: Image Filtering》. 見於 2020年5月3日. http://schen.xyz:89/opencv/d4/d86/group__imgproc__filter.html#gad7c87bbc46b97e7eafa71357916ab568.
16. 知乎專欄. 《OpenCV影象處理專欄九 | 基於直方圖的快速中值濾波演算法》. 見於 2020年5月3日. https://zhuanlan.zhihu.com/p/98092747.
17. 簡書. 《OpenCV原始碼分析(四):中值濾波》. 見於 2020年5月2日. https://www.jianshu.com/p/eb0b856286f2.
> 本文由部落格一文多發平臺 [OpenWrite](https://openwrite.cn?from=article_bottom)