開始之前
在上一篇我們實作了讀取噪聲影像, 然后 進行三種形式的均值濾波得到結果, 由于我們自己寫的均值濾波未作邊緣處理, 所以效果有一定的下降, 但是總體來說, 我們得到的結果能夠說明我們的演算法執行之后得到的影像噪聲更低, 影像更清晰. 但是也會造成影像的模糊, 導致部分細節丟失. 在這一章中,我們介紹一下中值濾波及其實作
摘要
首先介紹了中值濾波的原理, 給出其實作思路,并根據思路實作了 C++ 的代碼, 然后 同樣測驗 opencv 自帶的中值濾波, 同樣的測驗影像, 得到對比結果, 分析代碼的實作程序, .
正文
中值濾波原理
中值濾波(Media Filter)就是對于影像的每一個點計算其鄰域視窗的像素序列中值, 可以表示為:
\[g(x,y) = media_{(i,j) \in S}f(i,j) \]
核心就是將相應視窗內的像素值進行排列, 我們之前也說過, 我們選擇的視窗為奇數尺寸, 所以我們能夠保證視窗內的像素個數也是奇數個, 這樣我們可以保證取得唯一的中值, 相應的設定為該點的目標值就行了.
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 自定義函式的方法, 這邊采用的比較函式的方式, 還有別的方式實作兩個元素的比較, 可以參考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 中值濾波
這里還是之前的方法, 一樣的介面, 實作起來很簡單, 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 以上的圖的顯示, 所以有需要的去看這個就好.
最近一直用的圖拼接使用的 做好圖 在線拼接圖片 主要是懶得自己寫了, 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 的實作, 然后我們根據之前的程式上加入了中值濾波的實作效果, 最后在中值濾波的基礎上進行優化, 做了自適應中值濾波的實作,測驗發現結果還要更好, 最后我稍微提了一下中值濾波的優化加速, 這一塊做的很多, 可以去參考里面去找, 算是完成了中值濾波的章節, 如果這里搞懂了我再來完善這一章節..
參考
- 《繪制函式呼叫圖(call graph)(4):doxygen + graphviz_運維_許振坪的專欄-CSDN博客》. 見于 2020年5月2日. https://blog.csdn.net/benkaoya/article/details/79763668.
- 《任意半徑中值濾波(擴展至百分比濾波器)O(1)時間復雜度演算法的原理、實作及效果, - Imageshop - 博客園》. 見于 2020年5月3日. https://www.cnblogs.com/Imageshop/archive/2013/04/26/3045672.html.
- 《數字影像處理------中值濾波 - ranjiewen - 博客園》. 見于 2020年5月2日. https://www.cnblogs.com/ranjiewen/p/5699395.html.
- 《【演算法隨記三】小半徑中值模糊的急速實作(16MB圖7.5ms實作) + Photoshop中蒙塵和劃痕演算法解讀, - Imageshop - 博客園》. 見于 2020年5月3日. https://www.cnblogs.com/Imageshop/p/11087804.html.
- 《影像處理基礎(2):自適應中值濾波器(基于OpenCV實作) - Brook_icv - 博客園》. 見于 2020年5月3日. https://www.cnblogs.com/wangguchangqing/p/6379646.html.
- 《影像處理之原理 - 中值濾波 - tanfy - 博客園》. 見于 2020年5月2日. https://www.cnblogs.com/tanfy/p/median_filter.html.
- 《影像處理之中值濾波介紹及C實作 - 淇淇寶貝 - 博客園》. 見于 2020年5月2日. https://www.cnblogs.com/qiqibaby/p/5281743.html.
- 《中值濾波的快速演算法_網路_LinJM-機器視覺-CSDN博客》. 見于 2020年5月3日. https://blog.csdn.net/linj_m/article/details/35780163.
- 《中值濾波器》. 收入 維基百科,自由的百科全書, 2017年9月8日. https://zh.wikipedia.org/w/index.php?title=中值濾波器&oldid=46098815.
- 《中值濾波器(Median filter)特性及其實作_人工智能_Ivan 的專欄-CSDN博客》. 見于 2020年5月2日. https://blog.csdn.net/liyuanbhu/article/details/48502005.
- 《自適應中值濾波及實作_人工智能_hongbin_xu的博客-CSDN博客》. 見于 2020年5月3日. https://blog.csdn.net/hongbin_xu/article/details/79780967.
- GitHub. 《ARM-Software/ComputeLibrary》. 見于 2020年5月3日. https://github.com/ARM-software/ComputeLibrary.
- 《c++中vector自定義排序的問題_C/C++_Stone_Sky-CSDN博客》. 見于 2020年5月2日. https://blog.csdn.net/aastoneaa/article/details/8471722.
- 《OpenCV 彩色影像的自適應中值濾波 C++_人工智能_cyf15238622067的博客-CSDN博客》. 見于 2020年5月3日. https://blog.csdn.net/cyf15238622067/article/details/88718615.
- 《?opencv: ?Image Filtering》. 見于 2020年5月3日. http://schen.xyz:89/opencv/d4/d86/group__imgproc__filter.html#gad7c87bbc46b97e7eafa71357916ab568.
- 知乎專欄. 《OpenCV影像處理專欄九 | 基于直方圖的快速中值濾波演算法》. 見于 2020年5月3日. https://zhuanlan.zhihu.com/p/98092747.
- 簡書. 《OpenCV原始碼分析(四):中值濾波》. 見于 2020年5月2日. https://www.jianshu.com/p/eb0b856286f2.
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/30651.html
標籤:C++
上一篇:二叉樹
下一篇:運算式·運算式樹·運算式求值
