双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保留边缘去除噪声的目的。
双边滤波器之所以能够做到在平滑去噪的同时还能够很好的保存边缘(Edge Preserve),是由于其滤波器的核由两个函数生成:
void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )
int d: 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
. double sigmaColor: 颜色空间过滤器的sigma值,这个参数的值越大,表明该像素邻域内有越宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
. double sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色。当d>0时,d指定了邻域大小且与sigmaSpace无关,否则d正比于sigmaSpace.
. int borderType=BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT.
在滤波之前,首先将灰度值模板系数计算出来
double color_coeff = -0.5 / (color_sigma * color_sigma);
vector _color_weight(channels * 255); // 存放差值的平方
double *color_weight = &_color_weight[0];
for (int i = 0; i < channels * 256; i++)
color_weight[i] = exp(i * i * color_coeff);
灰度值的模板系数计算公式参见上面的公式,是两个灰度值的差值的平方。在使用的时候,首先取出模板中心的灰度值val0,然后依次取出模板其他位置的灰度值val,使用abs(val - val0)
的差值从color_weight
查表得到灰度值模板的系数。
距离的模板是二维的,这里使用的方法就i比较巧妙,将其化为了一维。
vector _space_weight(ksize * ksize); // 空间模板系数
vector _space_ofs(ksize * ksize); // 模板窗口的坐标
// 生成空间模板
int maxk = 0;
for (int i = -radius; i <= radius; i++)
{
for (int j = -radius; j <= radius; j++)
{
double r = sqrt(i*i + j * j);
if (r > radius)
continue;
space_weight[maxk] = exp(r * r * space_coeff); // 存放模板系数
space_ofs[maxk++] = i * temp.step + j * channels; // 存放模板的位置,和模板系数相对应
}
}
使用一维数组存放空间模板系数,同时使用另一个一维数组存放模板位置,和系数相对应。
整个代码的实现如下:
void myBilateralFilter(const Mat &src, Mat &dst, int ksize, double space_sigma, double color_sigma)
{
int channels = src.channels();
CV_Assert(channels == 1 || channels == 3);
double space_coeff = -0.5 / (space_sigma * space_sigma);
double color_coeff = -0.5 / (color_sigma * color_sigma);
int radius = ksize / 2;
Mat temp;
copyMakeBorder(src, temp, radius, radius, radius, radius, BorderTypes::BORDER_REFLECT);
vector _color_weight(channels * 256); // 存放差值的平方
vector _space_weight(ksize * ksize); // 空间模板系数
vector _space_ofs(ksize * ksize); // 模板窗口的坐标
double *color_weight = &_color_weight[0];
double *space_weight = &_space_weight[0];
int *space_ofs = &_space_ofs[0];
for (int i = 0; i < channels * 256; i++)
color_weight[i] = exp(i * i * color_coeff);
// 生成空间模板
int maxk = 0;
for (int i = -radius; i <= radius; i++)
{
for (int j = -radius; j <= radius; j++)
{
double r = sqrt(i*i + j * j);
if (r > radius)
continue;
space_weight[maxk] = exp(r * r * space_coeff); // 存放模板系数
space_ofs[maxk++] = i * temp.step + j * channels; // 存放模板的位置,和模板系数相对应
}
}
// 滤波过程
for (int i = 0; i < src.rows; i++)
{
const uchar *sptr = temp.data + (i + radius) * temp.step + radius * channels;
uchar *dptr = dst.data + i * dst.step;
if (channels == 1)
{
for (int j = 0; j < src.cols; j++)
{
double sum = 0, wsum = 0;
int val0 = sptr[j]; // 模板中心位置的像素
for (int k = 0; k < maxk; k++)
{
int val = sptr[j + space_ofs[k]];
double w = space_weight[k] * color_weight[abs(val - val0)]; // 模板系数 = 空间系数 * 灰度值系数
sum += val * w;
wsum += w;
}
dptr[j] = (uchar)cvRound(sum / wsum);
}
}
else if (channels == 3)
{
for (int j = 0; j < src.cols * 3; j+=3)
{
double sum_b = 0, sum_g = 0, sum_r = 0, wsum = 0;
int b0 = sptr[j];
int g0 = sptr[j + 1];
int r0 = sptr[j + 2];
for (int k = 0; k < maxk; k++)
{
const uchar *sptr_k = sptr + j + space_ofs[k];
int b = sptr_k[0];
int g = sptr_k[1];
int r = sptr_k[2];
double w = space_weight[k] * color_weight[abs(b - b0) + abs(g - g0) + abs(r - r0)];
sum_b += b * w;
sum_g += g * w;
sum_r += r * w;
wsum += w;
}
wsum = 1.0f / wsum;
b0 = cvRound(sum_b * wsum);
g0 = cvRound(sum_g * wsum);
r0 = cvRound(sum_r * wsum);
dptr[j] = (uchar)b0;
dptr[j + 1] = (uchar)g0;
dptr[j + 2] = (uchar)r0;
}
}
}
}
需要注意图像像素值的获取,首先获取到每行的坐标指针
const uchar *sptr = temp.data + (i + radius) * temp.step + radius * channels;
uchar *dptr = dst.data + i * dst.step;
在滤波循环中,从space_ofs
中取出每个模板位置偏移地址
int val = sptr[j + space_ofs[k]];
这种实现方法,大大的降低滤波的时间复杂度。
//获取色彩模板(值域模板)
///
void getColorMask(std::vector& colorMask, double colorSigma) {
for (int i = 0; i < 256; ++i) {
double colordiff = exp(-(i * i) / (2 * colorSigma * colorSigma));
colorMask.push_back(colordiff);
}
}
//获取高斯模板(空间模板)
void getGausssianMask(cv::Mat& Mask, cv::Size wsize, double spaceSigma) {
Mask.create(wsize, CV_64F);
int h = wsize.height;
int w = wsize.width;
int center_h = (h - 1) / 2;
int center_w = (w - 1) / 2;
double sum = 0.0;
double x, y;
for (int i = 0; i < h; ++i) {
y = pow(i - center_h, 2);
double* Maskdate = Mask.ptr(i);
for (int j = 0; j < w; ++j) {
x = pow(j - center_w, 2);
double g = exp(-(x + y) / (2 * spaceSigma * spaceSigma));
Maskdate[j] = g;
sum += g;
}
}
}
//双边滤波
void bilateralfiter(cv::Mat& src, cv::Mat& dst, cv::Size wsize, double spaceSigma, double colorSigma) {
cv::Mat spaceMask;
std::vector colorMask;
cv::Mat Mask0 = cv::Mat::zeros(wsize, CV_64F);
cv::Mat Mask1 = cv::Mat::zeros(wsize, CV_64F);
cv::Mat Mask2 = cv::Mat::zeros(wsize, CV_64F);
getGausssianMask(spaceMask, wsize, spaceSigma);//空间模板
getColorMask(colorMask, colorSigma);//值域模板
int hh = (wsize.height - 1) / 2;
int ww = (wsize.width - 1) / 2;
dst.create(src.size(), src.type());
//边界填充
cv::Mat Newsrc;
cv::copyMakeBorder(src, Newsrc, hh, hh, ww, ww, cv::BORDER_REPLICATE);//边界复制;
for (int i = hh; i < src.rows + hh; ++i) {
for (int j = ww; j < src.cols + ww; ++j) {
double sum[3] = { 0 };
int graydiff[3] = { 0 };
double space_color_sum[3] = { 0.0 };
for (int r = -hh; r <= hh; ++r) {
for (int c = -ww; c <= ww; ++c) {
if (src.channels() == 1) {
int centerPix = Newsrc.at(i, j);
int pix = Newsrc.at(i + r, j + c);
graydiff[0] = abs(pix - centerPix);
double colorWeight = colorMask[graydiff[0]];
Mask0.at(r + hh, c + ww) = colorWeight * spaceMask.at(r + hh, c + ww);//滤波模板
space_color_sum[0] = space_color_sum[0] + Mask0.at(r + hh, c + ww);
}
else if (src.channels() == 3) {
cv::Vec3b centerPix = Newsrc.at(i, j);
cv::Vec3b bgr = Newsrc.at(i + r, j + c);
graydiff[0] = abs(bgr[0] - centerPix[0]); graydiff[1] = abs(bgr[1] - centerPix[1]); graydiff[2] = abs(bgr[2] - centerPix[2]);
double colorWeight0 = colorMask[graydiff[0]];
double colorWeight1 = colorMask[graydiff[1]];
double colorWeight2 = colorMask[graydiff[2]];
Mask0.at(r + hh, c + ww) = colorWeight0 * spaceMask.at(r + hh, c + ww);//滤波模板
Mask1.at(r + hh, c + ww) = colorWeight1 * spaceMask.at(r + hh, c + ww);
Mask2.at(r + hh, c + ww) = colorWeight2 * spaceMask.at(r + hh, c + ww);
space_color_sum[0] = space_color_sum[0] + Mask0.at(r + hh, c + ww);
space_color_sum[1] = space_color_sum[1] + Mask1.at(r + hh, c + ww);
space_color_sum[2] = space_color_sum[2] + Mask2.at(r + hh, c + ww);
}
}
}
//滤波模板归一化
if (src.channels() == 1)
Mask0 = Mask0 / space_color_sum[0];
else {
Mask0 = Mask0 / space_color_sum[0];
Mask1 = Mask1 / space_color_sum[1];
Mask2 = Mask2 / space_color_sum[2];
}
for (int r = -hh; r <= hh; ++r) {
for (int c = -ww; c <= ww; ++c) {
if (src.channels() == 1) {
sum[0] = sum[0] + Newsrc.at(i + r, j + c) * Mask0.at(r + hh, c + ww); //滤波
}
else if (src.channels() == 3) {
cv::Vec3b bgr = Newsrc.at(i + r, j + c); //滤波
sum[0] = sum[0] + bgr[0] * Mask0.at(r + hh, c + ww);//B
sum[1] = sum[1] + bgr[1] * Mask1.at(r + hh, c + ww);//G
sum[2] = sum[2] + bgr[2] * Mask2.at(r + hh, c + ww);//R
}
}
}
for (int k = 0; k < src.channels(); ++k) {
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (src.channels() == 1)
{
dst.at(i - hh, j - ww) = static_cast(sum[0]);
}
else if (src.channels() == 3)
{
cv::Vec3b bgr = { static_cast(sum[0]), static_cast(sum[1]), static_cast(sum[2]) };
dst.at(i - hh, j - ww) = bgr;
}
}
}
}