opencv-11-中值濾波及自適應中值濾波

開始之前

在上一篇我們實現了讀取雜訊影像, 然後 進行三種形式的均值濾波得到結果, 由於我們自己寫的均值濾波未作邊緣處理, 所以效果有一定的下降, 但是總體來說, 我們得到的結果能夠說明我們的演算法執行之後得到的影像雜訊更低, 影像更清晰. 但是也會造成影像的模糊, 導致部分細節丟失. 在這一章中,我們介紹一下中值濾波及其實現

摘要

首先介紹了中值濾波的原理, 給出其實現思路,並根據思路實現了 C++ 的程式碼, 然後 同樣測試 opencv 自帶的中值濾波, 同樣的測試影像, 得到對比結果, 分析程式碼的實現過程, .

正文

中值濾波原理

中值濾波(Media Filter)就是對於影像的每一個點計算其鄰域窗口的像素序列中值, 可以表示為:

\[g(x,y) = meida_{(i,j) \in S}f(i,j)
\]

核心就是將相應窗口內的像素值進行排列, 我們之前也說過, 我們選擇的窗口為奇數尺寸, 所以我們能夠保證窗口內的像素個數也是奇數個, 這樣我們可以保證取得唯一的中值, 相應的設置為該點的目標值就行了.

C++ 實現中值濾波

我們來實現一下, 這方面還是能夠找到不少結果的, 感覺這個部落客寫的還是很不錯的,有興趣的可以看下數字影像處理——中值濾波,還有影像處理之中值濾波介紹及C實現, 或者 中值濾波器(Median filter)特性及其實現, 這裡我就不再造輪子了, 我們來看下 C++的實現
, 主要參考 第一篇文章, 可以看下效果

這裡有一點點需要討論的, 對於彩色影像的三個通道怎麼處理, 自己的思路就是分成三個通道進行處理, 然後分別得到三個圖之後進行合併三個通道, 得到結果影像. 查了下 目測大家都是這麼做的, 可以看OpenCV 彩色影像的自適應中值濾波 C++彩色影像空間濾波(MATLAB) 這兩篇文章, 思路都是一樣的, 我們來實現一下.

//中值濾波:C++ 程式碼實現 // 處理單通道影像 // 參考 //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

我們看一下結果影像, 原始影像可以看 //gitee.com/schen00/BlogImage/raw/master/image/1588468343599.png 這裡,

gitee 限制了 1M 以上的圖的顯示, 所以有需要的去看這個就好.

最近一直用的圖拼接使用的 做好圖 在線拼接圖片 主要是懶得自己寫了, //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 的實現, 然後我們根據之前的程式上加入了中值濾波的實現效果, 最後在中值濾波的基礎上進行優化, 做了自適應中值濾波的實現,測試發現結果還要更好, 最後我稍微提了一下中值濾波的優化加速, 這一塊做的很多, 可以去參考裡面去找, 算是完成了中值濾波的章節, 如果這裡搞懂了我再來完善這一章節..

參考

  1. 《繪製函數調用圖(call graph)(4):doxygen + graphviz_運維_許振坪的專欄-CSDN部落格》. 見於 2020年5月2日. //blog.csdn.net/benkaoya/article/details/79763668.
  2. 《任意半徑中值濾波(擴展至百分比濾波器)O(1)時間複雜度演算法的原理、實現及效果。 – Imageshop – 部落格園》. 見於 2020年5月3日. //www.cnblogs.com/Imageshop/archive/2013/04/26/3045672.html.
  3. 《數字影像處理——中值濾波 – ranjiewen – 部落格園》. 見於 2020年5月2日. //www.cnblogs.com/ranjiewen/p/5699395.html.
  4. 《【演算法隨記三】小半徑中值模糊的急速實現(16MB圖7.5ms實現) + Photoshop中蒙塵和劃痕演算法解讀。 – Imageshop – 部落格園》. 見於 2020年5月3日. //www.cnblogs.com/Imageshop/p/11087804.html.
  5. 《影像處理基礎(2):自適應中值濾波器(基於OpenCV實現) – Brook_icv – 部落格園》. 見於 2020年5月3日. //www.cnblogs.com/wangguchangqing/p/6379646.html.
  6. 《影像處理之原理 – 中值濾波 – tanfy – 部落格園》. 見於 2020年5月2日. //www.cnblogs.com/tanfy/p/median_filter.html.
  7. 《影像處理之中值濾波介紹及C實現 – 淇淇寶貝 – 部落格園》. 見於 2020年5月2日. //www.cnblogs.com/qiqibaby/p/5281743.html.
  8. 《中值濾波的快速演算法_網路_LinJM-機器視覺-CSDN部落格》. 見於 2020年5月3日. //blog.csdn.net/linj_m/article/details/35780163.
  9. 《中值濾波器》. 收入 維基百科,自由的百科全書, 2017年9月8日. //zh.wikipedia.org/w/index.php?title=中值濾波器&oldid=46098815.
  10. 《中值濾波器(Median filter)特性及其實現_人工智慧_Ivan 的專欄-CSDN部落格》. 見於 2020年5月2日. //blog.csdn.net/liyuanbhu/article/details/48502005.
  11. 《自適應中值濾波及實現_人工智慧_hongbin_xu的部落格-CSDN部落格》. 見於 2020年5月3日. //blog.csdn.net/hongbin_xu/article/details/79780967.
  12. GitHub. 《ARM-Software/ComputeLibrary》. 見於 2020年5月3日. //github.com/ARM-software/ComputeLibrary.
  13. 《c++中vector自定義排序的問題_C/C++_Stone_Sky-CSDN部落格》. 見於 2020年5月2日. //blog.csdn.net/aastoneaa/article/details/8471722.
  14. 《OpenCV 彩色影像的自適應中值濾波 C++_人工智慧_cyf15238622067的部落格-CSDN部落格》. 見於 2020年5月3日. //blog.csdn.net/cyf15238622067/article/details/88718615.
  15. 《‪opencv: ‪Image Filtering》. 見於 2020年5月3日. //schen.xyz:89/opencv/d4/d86/group__imgproc__filter.html#gad7c87bbc46b97e7eafa71357916ab568.
  16. 知乎專欄. 《OpenCV影像處理專欄九 | 基於直方圖的快速中值濾波演算法》. 見於 2020年5月3日. //zhuanlan.zhihu.com/p/98092747.
  17. 簡書. 《OpenCV源碼分析(四):中值濾波》. 見於 2020年5月2日. //www.jianshu.com/p/eb0b856286f2.

本文由部落格一文多發平台 OpenWrite 發布!