双边滤波

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越低效果越好。

双边滤波