雙邊濾波

0 序

       均值濾波是基於模板內像素值取均值的一種濾波方式,顯然沒有考慮距離的因素,所以效果不好,邊緣不突出。因此,高斯濾波是基於此以距離爲權重作爲模板值的參考,所以邊緣得到了改善,但也不是特別明顯。另外,高斯濾波作爲線性濾波,對噪聲也是比較敏感的。所以,又在高斯基礎上,做了進一步優化,疊加了像素值的考慮,因此也就引出了雙邊濾波,一種非線性濾波,一種對保留邊緣更有效的濾波。

1 原理

       按論文《bilateral filter for gray and color images》中所述,首先引入高斯濾波的模型函數。如下圖所示。


       h(x) 表示的是高斯濾波後中心像素值,ξ表示的是模板所覆蓋的座標,f(ξ)表示的是濾波前該位置的像素值。而c(ξx)則是以高斯分佈的值(當然可以不是高斯分佈,例如xxx分佈,那麼就產生另一種基於距離的xxx濾波)。而kd(x)則表示的是歸一化參數。


       d(ξx)表示的是ξ與中心像素點的歐氏距離,而delta則是作爲一個可調整的參數。

    類似地,我們以像素差值取代歐氏距離,套用高斯分佈函數,即得到一個基於像素值考慮的高斯分佈函數。如下圖所示。


       同樣delta_r表示的是該高斯分佈的標準差,也是一個可調的參數。

       基於此,不難得到一個以像素差考慮的高斯分佈,並將其作爲卷積模板的公式,如下圖所示。


       類似地,h(x)表示的是卷積之後的中心像素值,而kr(x)表示的是歸一化參數。f(ξ)表示的是卷積前的像素值。

       而雙邊濾波是基於上述兩個公式的合集所產生的一種卷積模板。如下圖所示。


       另外標準差作爲可調整參數,需要知道它的意義。

2 代碼及效果

#include <opencv2/opencv.hpp>

#include <iostream>

#include <fstream>

#include <string>

#include <vector>

#include "tools.h"

 

/*

  雙邊濾波,(主要針對高斯濾波的優化

*/

#define KERNEL_WIDTH  5

#define KERNEL_HEIGHT 5

#define M_PI 3.1415926

static int dist_gausemask[KERNEL_HEIGHT][KERNEL_WIDTH];

static int value_gausemask[256];

static int sum_gause_mask;

// 以距離爲參考的標準差

static double dist_delta = 1.0;

 

static void generate_dist_gausemask(void) {

  int center_x = KERNEL_WIDTH / 2 ;

  int center_y = KERNEL_HEIGHT / 2;

  double delta2 = dist_delta * dist_delta;

  double param = 1.0 / (2 * M_PI * delta2);

  sum_gause_mask = 0;

  for (int i = 0; i < KERNEL_HEIGHT; i++) {

    for (int j = 0; j < KERNEL_WIDTH; j++) {

      int distance = (j - center_x)*(j - center_x) + (i - center_y)*(i - center_y);

      dist_gausemask[i][j] = 10000*param*exp(-(double)distance / (delta2 * 2));

      sum_gause_mask += dist_gausemask[i][j];

    }

  }

}

 

static void generate_value_gausemask(double value_delta) {

  double delta2 = value_delta*value_delta;

  for (int i = 0; i < 256; i++) {

    value_gausemask[i] = exp(-(i * i) / (2 * delta2)) * 1000;

  }

}

 

void GauseFilter(cv::Mat& src_img, cv::Mat& dst_img) {

  src_img.copyTo(dst_img);

  for (int i = 0; i < src_img.rows; ++i) {

    for (int j = 0; j < src_img.cols; ++j) {

      int sum_value = 0;

      for (int m = -KERNEL_HEIGHT / 2; m <= KERNEL_HEIGHT / 2; m++) {

        int row_offset = m + i;

        row_offset = row_offset < 0 ? 0 : (row_offset >= src_img.rows ? src_img.rows : row_offset);

        for (int n = -KERNEL_WIDTH / 2; n <= KERNEL_WIDTH / 2; n++) {

          int col_offset = n + j;

          col_offset = col_offset < 0 ? 0 : (col_offset >= src_img.cols ? src_img.cols : col_offset);

          sum_value += src_img.at<uchar>(row_offset, col_offset)*dist_gausemask[m + KERNEL_HEIGHT / 2][n + KERNEL_WIDTH / 2];

        }

      }

      dst_img.at<uchar>(i, j) = cv::saturate_cast<uchar>(sum_value / sum_gause_mask);

    }

  }

}

 

void BilaterFilter(cv::Mat& src_img, cv::Mat& dst_img, double delta) {

  generate_value_gausemask(delta);

  src_img.copyTo(dst_img);

  int norm_ratio;

  for (int i = 0; i < src_img.rows; ++i) {

    for (int j = 0; j < src_img.cols; ++j) {

      int sum_value = 0;

      norm_ratio = 0;

      uchar center_value = src_img.at<uchar>(i, j);

      for (int m = -KERNEL_HEIGHT / 2; m <= KERNEL_HEIGHT / 2; m++) {

        int row_offset = m + i;

        row_offset = row_offset < 0 ? 0 : (row_offset >= src_img.rows ? src_img.rows : row_offset);

        for (int n = -KERNEL_WIDTH / 2; n <= KERNEL_WIDTH / 2; n++) {

          int col_offset = n + j;

          col_offset = col_offset < 0 ? 0 : (col_offset >= src_img.cols ? src_img.cols : col_offset);

          uchar value_diff = src_img.at<uchar>(row_offset, col_offset) - center_value;

          int kernel = dist_gausemask[m + KERNEL_HEIGHT / 2][n + KERNEL_WIDTH / 2] * value_gausemask[value_diff];

          sum_value += src_img.at<uchar>(row_offset, col_offset)*kernel;

          norm_ratio += kernel;

        }

      }

      dst_img.at<uchar>(i, j) = cv::saturate_cast<uchar>(sum_value / norm_ratio);

    }

  }

}

 

int main(int argc, char* argv[]) {

  generate_dist_gausemask();

  cv::Mat src_img;

  cv::Mat dst_img;

  cv::Mat dst_img10;

  cv::Mat dst_img20;

  cv::Mat dst_img50;

  cv::Mat dst_img80;

  std::vector<cv::Mat> imgs;

  src_img = cv::imread("D:\\LyzWorkspace\\opencv\\image\\lena256.bmp", cv::IMREAD_UNCHANGED);

  assert(src_img.data != NULL);

 

  GauseFilter(src_img, dst_img);

  BilaterFilter(src_img, dst_img10, 10);

  BilaterFilter(src_img, dst_img20, 20);

  BilaterFilter(src_img, dst_img50, 50);

  BilaterFilter(src_img, dst_img80, 80);

  imgs.push_back(src_img);

  imgs.push_back(dst_img);

  imgs.push_back(dst_img10);

  imgs.push_back(dst_img20);

  imgs.push_back(dst_img50);

  imgs.push_back(dst_img80);

  imshow_many("GauseBlur", imgs, CV_8UC1);

  cv::waitKey(0);

  return 0;

}

       效果如下圖所示。從上到下,自左至右,分別爲原圖,高斯模糊圖,delta=10、20、50、80的雙邊濾波之後的圖。可見delta越低效果越好。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章