我想做三国志

努力学习做游戏的小白

双边滤波 铜牌收录

事先声明,这代码改编自

https://github.com/Jermmy/BilateralFilter/tree/master/bilateral_filter/bilateral_filter

不过原来的代码是用 OpenCV 写的,OpenCV 有点难配置,我把它翻译成 easyx。

简单说明

原图

这里不贴公式。

找到一个点,规定一个区域,让这个点的颜色变成这个区域的颜色的平均值,效果会是这样(区域半径为 5 像素):

平均值滤波

我把这称为平均值滤波。如果比较的区域变大了,用平均值滤波的话图像就会越模糊,就像这样(区域半径为 10 像素):

平均值滤波

为了平衡距离对结果的影响,需要让越靠近中心的点对结果影响越大,也就是权重越大,这里用正态分布的概率密度函数

y=e^(-x^2/(2*σ^2)) 来分配权重,x 为距离,y 为权重,σ 为正态分布的标准差,这里用于调整分配策略,σ 越小,越靠近中心的点分配的权重越大,越远离中心的点分配的权重越小,σ 越大就反过来,同时 σ 接近无穷大时变为平均值滤波。

分配一个 2radius * 2radius 的二维数组来存权重,因为我们比较的点只有 2radius * 2radius 这个区域内的点,所以只需要这个区域内的点的权重,每个点的值乘以每个点的权重,加起来除以总权重。这个其实跟求数学期望很像,这块区域距离中心的不同距离上出现不同的颜色,该颜色在该距离上出现的次数服从正态分布,求覆盖这块区域的颜色的数学期望(我是这么理解的)。这就是高斯滤波了。

区域半径为 5:

高斯滤波,区域半径为 5

区域半径为 10:

高斯滤波,区域半径为 10

比平均值滤波略微好了一点是吧,这还是没有调正态分布的标准差的时候,以上的图片是在标准差为 5 的时候生成的,如果我把正态分布标准差调小一点就会是

区域半径为 10,标准差为 2:

高斯滤波

反之,标准差调大一点就会是

区域半径为 10,标准差为 100:

高斯滤波

但是标准差调太小了就跟原图一样了,就无法去除噪点,同时对于色差特别大的区域,高斯滤波无法明确分辨这是个边界,于是会让这个棱角分明的像素点跟它周围的环境同化。

为了分辨边界,需要另一个权重,这个权重专门为了色差而设置,当区域内的点和中心点的色差特别大时,它分配到的权重就变小,当区域内的点和中心点的色差特别小时,它分配到的权重就大,这个权重的分配方法和高斯滤波的一样,y=e^(-x^2/(2*σ^2)),y 是权重,x 是色差,σ 是色差标准差,这里用于调整分配策略。

这是相似点滤波。

相似点滤波与距离无关,能很好的保存边界,同高斯滤波,当标准差无穷大时相似点滤波变为平均值滤波

区域半径为 10,标准差为 30:

相似点滤波

区域半径为 10,标准差为 100:

相似点滤波

由于对距离的把控不准,中心点颜色可能变为远处与它相差无几的颜色,虽然能较好地保存边界,但是不能与周围的颜色渐变,所以需要跟高斯滤波合作。

合作的具体做法就是权重直接相乘,然后权重总和是这些相乘的权重的总和,颜色乘以权重后累加起来除以权重总和。

最终的效果如下

半径为 5,高斯滤波标准差为 5,相似点滤波标准差为 30:

双边滤波

代码

#include<graphics.h>
#include<iostream>
#include<math.h>
using namespace std;

// 颜色类型,用 int 会占 4 字节,干脆自己写一个占 3 字节的颜色类型
typedef struct ColorType
{
	unsigned char Red;
	unsigned char Green;
	unsigned char Blue;
}ColorType;

// 将 RGB 转化为 ColorType
ColorType RGBTOCOL(unsigned int rgb)
{
	ColorType result;
	unsigned char temp = rgb % (1 << 8);
	result.Red = temp;
	rgb = rgb >> 8;
	temp = rgb % (1 << 8);
	result.Green = temp;
	rgb = rgb >> 8;
	temp = rgb % (1 << 8);
	result.Blue = temp;
	return result;
}

// 将 ColorType 转化为 RGB
unsigned int COLTORGB(ColorType col)
{
	return RGB(col.Red, col.Green, col.Blue);
}

// 创建颜色数组
ColorType** CreateColorArr(unsigned int height, unsigned int width)
{
	ColorType** temp = new ColorType * [height];
	for (int i = 0; i < height; i++)
		temp[i] = new ColorType[width];
	return temp;
}

// 清除颜色数组
void ClearColorArr(ColorType** arr, unsigned int height)
{
	for (int i = 0; i < height; i++)
		delete[] arr[i];
	delete[] arr;
}

// 将 source 颜色数组用复制的方法复制到 dest 数组里,注意 dest 数组的尺寸比 source 数组的尺寸宽高各大 radius
void CopyImg(ColorType** Dest, ColorType** source,
	const unsigned int height, const unsigned int width, const unsigned int radius)
{
	for (int i = 0; i < height; i++)
		for (int j = 0; j < width; j++)
			Dest[i + radius][j + radius] = source[i][j];
	for (int i = 0; i < radius; i++)
	{
		for (int j = 0; j < radius; j++)
		{
			Dest[i][j] = source[0][0];
			Dest[i + radius + height][j] = source[height - 1][0];
			Dest[i][j + radius + width] = source[0][width - 1];
			Dest[i + radius + height][j + radius + width] = source[height - 1][width - 1];
		}
	}
	for (int i = 0; i < radius; i++)
	{
		for (int j = 0; j < width; j++)
		{
			Dest[i][j + radius] = source[0][j];
			Dest[(i + radius + height)][j + radius] = source[height - 1][j];
		}
	}
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < radius; j++)
		{
			Dest[i + radius][j] = source[i][0];
			Dest[i + radius][j + radius + width] = source[i][width - 1];
		}
	}
}

// 创建距离表,weightsum 是权重总数,进行高斯模糊时可以直接用
double** BuildDistanceTable(int radius, double SigmaDis, double* weightsum)
{
	*weightsum = 0;
	double** result = new double*[2 * radius];
	for (int i = 0; i < 2 * radius; i++)
		result[i] = new double[2 * radius];
	for (int i = -radius; i < radius; i++)
	{
		for (int j = -radius; j < radius; j++)
		{
			// i * i + j * j 是距离的平方,SigmaDis 是距离标准差
			double value= exp(-(0.5 * (i * i + j * j) / (SigmaDis * SigmaDis)));
			result[i + radius][j + radius] = value;
			*weightsum += value;
		}
	}
	return result;
}

// 清除距离表
void ClearDistanceTable(double** distable, unsigned int radius)
{
	for (int i = 0; i < 2 * radius; i++)
		delete[] distable[i];
	delete[] distable;
}

// 创建颜色表,SigmaCol 是颜色标准差
double* BuildColorTable(double SigmaCol)
{
	double* result = new double[256];
	for (int i = 0; i < 256; i++)
		// i * i 是色差的平方
		result[i] = exp(-(0.5 * (i * i) / (SigmaCol * SigmaCol)));
	return result;
}

// 清除颜色表
void ClearColorTable(double* colortable)
{
	delete[]colortable;
}

// 平均值滤波
void AverageFilter(ColorType** img, const unsigned int width, const unsigned int height, const int radius)
{
	ColorType** temp = CreateColorArr(height + radius * 2, width + radius * 2);
	CopyImg(temp, img, height, width, radius);
	double sum = 4 * radius * radius;
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			double red_all = 0, green_all = 0, blue_all = 0;
			for (int area_row = -radius; area_row < radius; area_row++)
			{
				for (int area_col = -radius; area_col < radius; area_col++)
				{
					red_all += temp[row + area_row + radius][col + area_col + radius].Red;
					green_all += temp[row + area_row + radius][col + area_col + radius].Green;
					blue_all += temp[row + area_row + radius][col + area_col + radius].Blue;
				}
			}
			putpixel(col, row,
				RGB((unsigned int)(red_all / sum),
					(unsigned int)(green_all / sum),
					(unsigned int)(blue_all / sum)));
		}
	}
	ClearColorArr(temp, height + radius * 2);
}

// 高斯滤波
void GaussianFilter(ColorType** img, const unsigned int width, const unsigned int height,
	const int radius, const double SigmaDis)
{
	ColorType** temp = CreateColorArr(height + radius * 2, width + radius * 2);
	double WeightSum;
	double** distanceTable = BuildDistanceTable(radius, SigmaDis, &WeightSum);
	CopyImg(temp, img, height, width, radius);
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			double red_all = 0, green_all = 0, blue_all = 0;
			for (int area_row = -radius; area_row < radius; area_row++)
			{
				for (int area_col = -radius; area_col < radius; area_col++)
				{
					red_all += temp[row + area_row + radius][col + area_col + radius].Red *
						distanceTable[area_row + radius][area_col + radius];;
					green_all += temp[row + area_row + radius][col + area_col + radius].Green *
						distanceTable[area_row + radius][area_col + radius];;
					blue_all += temp[row + area_row + radius][col + area_col + radius].Blue *
						distanceTable[area_row + radius][area_col + radius];;
				}
			}
			putpixel(col, row,
				RGB((unsigned int)(red_all / WeightSum),
					(unsigned int)(green_all / WeightSum),
					(unsigned int)(blue_all / WeightSum)));
		}
	}
	ClearColorArr(temp, height + radius * 2);
	ClearDistanceTable(distanceTable, radius);
}

// 相似点滤波
void SimilarityFilter(ColorType** img, const unsigned int width, const unsigned int height,
	const int radius, const double SigmaCol)
{
	ColorType** temp = CreateColorArr(height + radius * 2, width + radius * 2);
	double* colorTable = BuildColorTable(SigmaCol);
	CopyImg(temp, img, height, width, radius);
	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			ColorType currentPoint = temp[row + radius][col + radius];
			double red_all = 0, green_all = 0, blue_all = 0;
			double Weight_Red = 0, Weight_Green = 0, Weight_Blue = 0;
			for (int area_row = -radius; area_row < radius; area_row++)
			{
				for (int area_col = -radius; area_col < radius; area_col++)
				{
					double middle = colorTable[abs(temp[row + area_row + radius][col + area_col + radius].Red - currentPoint.Red)];
					red_all += temp[row + area_row + radius][col + area_col + radius].Red * middle;;
					Weight_Red += middle;
					middle= colorTable[abs(temp[row + area_row + radius][col + area_col + radius].Green - currentPoint.Green)];
					green_all += temp[row + area_row + radius][col + area_col + radius].Green * middle;
					Weight_Green += middle;
					middle= colorTable[abs(temp[row + area_row + radius][col + area_col + radius].Blue - currentPoint.Blue)];
					blue_all += temp[row + area_row + radius][col + area_col + radius].Blue * middle;
					Weight_Blue += middle;
				}
			}
			putpixel(col, row,
				RGB((unsigned int)(red_all / Weight_Red),
					(unsigned int)(green_all / Weight_Green),
					(unsigned int)(blue_all / Weight_Blue)));
		}
	}
	ClearColorArr(temp, height + radius * 2);
	ClearColorTable(colorTable);
}

// 双边滤波
void BilateralFilter(ColorType** img, const unsigned int width, const unsigned int height,
	const int radius, const double SigmaDis, const double SigmaCol)
{
	ColorType** temp = CreateColorArr(height + radius * 2, width + radius * 2);
	double WeightSum;
	double** distanceTable = BuildDistanceTable(radius, SigmaDis, &WeightSum);
	double* colorTable = BuildColorTable(SigmaCol);
	CopyImg(temp, img, height, width, radius);

	for (int row = 0; row < height; row++)
	{
		for (int col = 0; col < width; col++)
		{
			ColorType currentPoint = temp[row + radius][col + radius];
			double red_all = 0, green_all = 0, blue_all = 0;
			double Weight_Red = 0, Weight_Green = 0, Weight_Blue = 0;
			for (int area_row = -radius; area_row < radius; area_row++)
			{
				for (int area_col = -radius; area_col < radius; area_col++)
				{
					double middle = distanceTable[area_row + radius][area_col + radius] *
						colorTable[abs(temp[row + area_row + radius][col + area_col + radius].Red - currentPoint.Red)];
					red_all += temp[row + area_row + radius][col + area_col + radius].Red * middle;;
					Weight_Red += middle;
					middle = distanceTable[area_row + radius][area_col + radius] *
						colorTable[abs(temp[row + area_row + radius][col + area_col + radius].Green - currentPoint.Green)];
					green_all += temp[row + area_row + radius][col + area_col + radius].Green * middle;
					Weight_Green += middle;
					middle = distanceTable[area_row + radius][area_col + radius] *
						colorTable[abs(temp[row + area_row + radius][col + area_col + radius].Blue - currentPoint.Blue)];
					blue_all += temp[row + area_row + radius][col + area_col + radius].Blue * middle;
					Weight_Blue += middle;
				}
			}
			putpixel(col, row,
				RGB((unsigned int)(red_all / Weight_Red),
					(unsigned int)(green_all / Weight_Green),
					(unsigned int)(blue_all / Weight_Blue)));
		}
	}
	ClearColorArr(temp, height + radius * 2);
	ClearColorTable(colorTable);
	ClearDistanceTable(distanceTable, radius);
}

int main()
{
	IMAGE img;
	loadimage(&img, "project2.png");	// 在这里输入要处理的图像名字
	initgraph(img.getwidth(), img.getheight());
	putimage(0, 0, &img);
	getchar();
	ColorType **arr = CreateColorArr(img.getheight(), img.getwidth());
	for (int i = 0; i < img.getheight(); i++)// 将整张图复制下来
		for (int j = 0; j < img.getwidth(); j++)
			arr[i][j] = RGBTOCOL(getpixel(j, i));
	GaussianFilter(arr, img.getwidth(), img.getheight(), 5, 5);	// 高斯滤波
	getchar();
	SimilarityFilter(arr, img.getwidth(), img.getheight(), 5, 100);	// 相似点滤波
	getchar();
	BilateralFilter(arr, img.getwidth(), img.getheight(), 5, 5, 100);	// 双边滤波
	ClearColorArr(arr, img.getheight());
	getchar();
	closegraph();
	return 0;
}

添加评论