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