Bilateral Filtering for Gray and Color Images
雙邊濾波器:保留邊界的平滑濾波器。 在局部上,就是在灰度值差異不大的區域平滑,在灰度值差異比較大的邊界地區保留邊界。所以雙邊濾波器作用於每個像素的同時,必然會受到領域像素點的距離、灰度值差的權重影響。
已知低通濾波可以表示為:
range filter可以表示為:(range filter 試選定一個數值范圍,再做濾波的一個操作)
所以,雙邊濾波器的定義是:
其中,k(x)是歸一化(normalize)函數,
( f 表示原圖像,h 表示處理后的圖像 x 表示 h 中某個像素點位置,ξ 表示 f 中x位置像素點的鄰域像素,f(ξ)表示該像素點的灰度值,c表示低通濾波, s表示range filter)
其中,
//Filters.h #ifndef FILTERS_H #define FILTERS_H #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include "opencv2/core.hpp" #include <iostream> #include <cmath> //Bilateral Filtering //sigmaD == sigmaSpace, sigmaR == sigmaColor cv::Mat BilateralFilter(cv::Mat inputImg, int filterSize, double sigmaD, double sigmaR); cv::Mat fastBilateralFilter(cv::Mat inputImg, int filterSize, double sigmaD, double sigmaR); #endif // ! FILTERS_H
//Filters.cpp #include "Filters.h" double SpaceFactor(int x1, int y1, int x2, int y2, double sigmaD) { double absX = pow(abs(x1 - x2), 2); double absY = pow(abs(y1 - y2), 2); return exp(-(absX + absY) / (2 * pow(sigmaD, 2))); } double ColorFactor(int x, int y, double sigmaR) { double distance = abs(x - y) / sigmaR; return exp(-0.5 * pow(distance, 2)); } cv::Mat BilateralFilter(cv::Mat inputImg, int filterSize, double sigmaD, double sigmaR) { int len; //must be odd number cv::Mat gray; // must be 1-channel image cv::Mat LabImage; // if channels == 3 if (filterSize % 2 != 1 || filterSize <= 0) { std::cerr << "Filter Size must be a positive odd number!" << std::endl; return inputImg; } len = filterSize / 2; if (inputImg.channels() >= 3) { cv::cvtColor(inputImg, LabImage, cv::COLOR_BGR2Lab); gray = cv::Mat::zeros(LabImage.size(), CV_8UC1); for (int i = 0; i < LabImage.rows; i++) { for (int j = 0; j < LabImage.cols; j++) { gray.ptr<uchar>(i)[j] = LabImage.ptr<uchar>(i, j)[0]; } } } else if(inputImg.channels() == 1){ inputImg.copyTo(gray); } else { std::cerr << "the count of input image's channel can not be 2!" << std::endl; return inputImg; } cv::Mat resultGrayImg = cv::Mat::zeros(gray.size(), CV_8UC1); for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { double k = 0; double f = 0; for (int r = i - len; r <= i + len; r++) { for (int c = j - len; c <= j + len; c++) { if (r < 0 || c < 0 || r >= gray.rows || c >= gray.cols) continue; f = f + gray.ptr<uchar>(r)[c] * SpaceFactor(i, j, r, c, sigmaD) * ColorFactor(gray.ptr<uchar>(i)[j], gray.ptr<uchar>(r)[c], sigmaD); k += SpaceFactor(i, j, r, c, sigmaD) * ColorFactor(gray.ptr<uchar>(i)[j], gray.ptr<uchar>(r)[c], sigmaD); } } int value = f / k; if (value < 0) value = 0; else if (value > 255) value = 255; resultGrayImg.ptr<uchar>(i)[j] = (uchar)value; } } cv::Mat resultImg; if (inputImg.channels() >= 3) { for (int i = 0; i < LabImage.rows; i++) { for (int j = 0; j < LabImage.cols; j++) { LabImage.ptr<uchar>(i, j)[0] = resultGrayImg.ptr<uchar>(i)[j]; } } cv::cvtColor(LabImage, resultImg, cv::COLOR_Lab2BGR); } else { resultGrayImg.copyTo(resultImg); } return resultImg; } cv::Mat fastBilateralFilter(cv::Mat inputImg, int filterSize, double sigmaD, double sigmaR) { int len; //must be odd number cv::Mat gray; // must be 1-channel image cv::Mat LabImage; // if channels == 3 if (filterSize % 2 != 1 || filterSize <= 0) { std::cerr << "Filter Size must be a positive odd number!" << std::endl; return inputImg; } len = filterSize / 2; if (inputImg.channels() >= 3) { cv::cvtColor(inputImg, LabImage, cv::COLOR_BGR2Lab); gray = cv::Mat::zeros(LabImage.size(), CV_8UC1); for (int i = 0; i < LabImage.rows; i++) { for (int j = 0; j < LabImage.cols; j++) { gray.ptr<uchar>(i)[j] = LabImage.ptr<uchar>(i, j)[0]; } } } else if (inputImg.channels() == 1) { inputImg.copyTo(gray); } else { std::cerr << "the count of input image's channel can not be 2!" << std::endl; return inputImg; } cv::Mat resultGrayImg = cv::Mat::zeros(gray.size(), CV_8UC1); for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { double k = 0; double f = 0; double sum = 0; for (int r = i - len; r <= i + len; r++) { if (r < 0 || r >= gray.rows) continue; f = f + gray.ptr<uchar>(r)[j] * SpaceFactor(i, j, r, j, sigmaD) * ColorFactor(gray.ptr<uchar>(i)[j], gray.ptr<uchar>(r)[j], sigmaD); k += SpaceFactor(i, j, r, j, sigmaD) * ColorFactor(gray.ptr<uchar>(i)[j], gray.ptr<uchar>(r)[j], sigmaD); } sum = f / k; f = k = 0.0; for (int c = j - len; c <= j + len; c++) { if (c < 0 || c >= gray.cols) continue; f = f + gray.ptr<uchar>(i)[c] * SpaceFactor(i, j, i, c, sigmaD) * ColorFactor(gray.ptr<uchar>(i)[j], gray.ptr<uchar>(i)[c], sigmaD); k += SpaceFactor(i, j, i, c, sigmaD) * ColorFactor(gray.ptr<uchar>(i)[j], gray.ptr<uchar>(i)[c], sigmaD); } int value = (sum + f / k) / 2; if (value < 0) value = 0; else if (value > 255) value = 255; resultGrayImg.ptr<uchar>(i)[j] = (uchar)value; } } cv::Mat resultImg; if (inputImg.channels() >= 3) { for (int i = 0; i < LabImage.rows; i++) { for (int j = 0; j < LabImage.cols; j++) { LabImage.ptr<uchar>(i, j)[0] = resultGrayImg.ptr<uchar>(i)[j]; } } cv::cvtColor(LabImage, resultImg, cv::COLOR_Lab2BGR); } else { resultGrayImg.copyTo(resultImg); } return resultImg; }
//main.cpp #include <iostream> #include <time.h> #include "Filters.h" using namespace std; int main() { cv::Mat img = cv::imread("Capture.jpg", cv::IMREAD_UNCHANGED); clock_t begin_time = clock(); cv::Mat result = BilateralFilter(img, 15, 12.5, 50); std::cout << float(clock() - begin_time) / CLOCKS_PER_SEC << std:: endl; cv::imshow("original", result); cv::waitKey(0); cv::imwrite("original.jpg", result); begin_time = clock(); result = fastBilateralFilter(img, 15, 12.5, 50); std::cout << float(clock() - begin_time) / CLOCKS_PER_SEC << std::endl; cv::imshow("fast", result); cv::waitKey(0); cv::imwrite("fast.jpg", result); begin_time = clock(); cv::bilateralFilter(img, result, 15, 50, 12.5); std::cout << float(clock() - begin_time) / CLOCKS_PER_SEC << std::endl; cv::imshow("opencv", result); cv::waitKey(0); cv::imwrite("opencv.jpg", result); system("pause"); return 0; }
運行結果:
46.889s 5.694s 0.202s
二維算子降成兩個一維算子之后,速度加快了一些,但是還是不如opencv的快,效果也比它差一些(No more reinventing the wheel...)
從左至右:“小雀斑”帥氣原圖、BilateralFilter處理結果、fastBilateralFilter處理結果、opencv接口處理結果