图片预处理
大家好,我是阿赵。
这一篇主要讲一下2种比较常见的模糊算法。
1、 均值模糊
均值模糊,意思很简单,就是把图片某个像素点周围一定范围内的所有像素点的颜色加起来,然后除以像素的总数量,求出一个平均值。
比如半径是1,那么就是会采样9个格子,每个格子取九分之一的色值,再加起来。
比如求中心点的一个均值模糊,如果模糊半径是1,那么就会找中心点一圈的点的色值进行模糊。
如果半径是2,那么就会找中心点周围2圈的点的色值进行模糊,那就是取25个格子,然后每个格子的色值取二十五分之一,再加起来。
由于是求的平均值,所以中心点的色值并没有什么优势,很容易会被边缘的值平均,而由于范围是一个一个的正方形,所以均值模糊之后,比较容易出现方块感。
public static Texture2D AverageBlur(Texture2D tex,int radius = 1)
{
if(radius <= 0)
{
return tex;
}
int width = tex.width;
int height = tex.height;
Texture2D newTex = new Texture2D(width, height);
float[,] colRList = new float[width, height];
float[,] colGList = new float[width, height];
float[,] colBList = new float[width, height];
float[,] colAList = new float[width, height];
Color[] cols = tex.GetPixels(0, 0, width, height);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
int index = j * width + i;
Color targetCol = cols[index];
colRList[i, j] = targetCol.r;
colGList[i, j] = targetCol.g;
colBList[i, j] = targetCol.b;
colAList[i, j] = targetCol.a;
}
}
float count = 0;
for(int i = -radius;i<radius+1;i++)
{
count++;
}
count = count * count;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
float sumR = 0;
float sumG = 0;
float sumB = 0;
for (int k = -radius; k < radius+1; k++)
{
for (int l = -radius; l < radius +1; l++)
{
int x = i + k;
int y = j + l;
if (x >= 0 && x < width && y >= 0 && y < height)
{
sumR += colRList[x, y] ;
sumG += colGList[x, y] ;
sumB += colBList[x, y] ;
}
}
}
Color col = new Color(Saturate(sumR/count), Saturate(sumG/count), Saturate(sumB/count), colAList[i,j]);
newTex.SetPixel(i, j, col);
}
}
newTex.Apply();
return newTex;
}
2、 高斯模糊
高斯模糊其实也是取像素点周围的色值取平均,但和均值模糊的区别是,均值模糊每个格子取的比例都一样,但高斯模糊是不一样的,中心的格子取色值的权重会高一些,然后越往边缘,色值的权重越低。所以的色值加起来的权重是1。
这个权重的框框,我们一般称为卷积核。如果只是简单的实现中间高四周低,那么情况会是这样:
但实际上高斯模糊的卷积核,越往外的越低,所以四个角的权重应该比邻边要低,会变成这样:
而控制这个卷积核,一般应该有2个参数,第一个是半径,第二个是sigma值。半径控制的是卷积核的大小,sigma值控制的是中间点和边缘点的差异。
比如同样半径是1,我调整sigma值,可以生成另外一组卷积核:
这组卷积核,中间的色值的权重会更高,四角的色值权重会更低,这样做出来的模糊效果四周色值对中间色值的影响会更小。
再比如,我修改半径,可以得到更大的卷积核:
卷积核可以通过公式计算
float wd = (-1 * x * x - y * y) / (2 * spaceSigma * spaceSigma);
wd = Mathf.Exp(wd);
其中x和y是离中心像素的坐标距离。然后最后通过一个函数exp(x)代表自然指数函数,把计算的取值变成0-1之间,当离中间点像素距离越短,值就越大,离得越远,得到的值就越小。而sigma参数,是调节曲线的弯曲度,值越大,曲线越平缓,那么中心像素和边缘像素的差别就越小。如果sigma参数越小,曲线越陡,那么中心像素和边缘像素的差别就越大。
如果写一个方法来得到卷积核的数组,大概可以这样:
public static List<float> CreateGaussianFactor(int radius,float spaceSigma = 1)
{
float count = 0;
List<float> factorList = new List<float>();
for (int i = -radius; i < radius + 1; i++)
{
for (int j = -radius; j < radius + 1; j++)
{
float wd = (-1 * i * i - j * j) / (2 * spaceSigma * spaceSigma);
wd = Mathf.Exp(wd);
factorList.Add(wd);
count += wd;
}
}
for (int i = 0; i < factorList.Count; i++)
{
factorList[i] = factorList[i] / count;
}
return factorList;
}
上图左边是均值模糊,右边是高斯模糊。大概可以看出,高斯模糊的方块感会没那么明显,然后由于中心色值受旁边色值的权重没那么高,所以原像素点的色值会保持得好一些。然后,高斯模糊可以通过sigma值控制中央像素点的色值权重比例,可调性稍微的大一点。
public static Texture2D GaussianBlur(Texture2D tex, int radius = 1,float spaceSigma = 1)
{
if (radius <= 0)
{
return tex;
}
int width = tex.width;
int height = tex.height;
Texture2D newTex = new Texture2D(width, height);
float[,] colRList = new float[width, height];
float[,] colGList = new float[width, height];
float[,] colBList = new float[width, height];
float[,] colAList = new float[width, height];
Color[] cols = tex.GetPixels(0, 0, width, height);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
int index = j * width + i;
Color targetCol = cols[index];
colRList[i, j] = targetCol.r;
colGList[i, j] = targetCol.g;
colBList[i, j] = targetCol.b;
colAList[i, j] = targetCol.a;
}
}
List<float> factorList = CreateGaussianFactor(radius, spaceSigma);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
float sumR = 0;
float sumG = 0;
float sumB = 0;
int factorIndex = 0;
for (int k = -radius; k < radius + 1; k++)
{
for (int l = -radius; l < radius + 1; l++)
{
int x = i + k;
int y = j + l;
if (x >= 0 && x < width && y >= 0 && y < height)
{
float factor = factorList[factorIndex];// radius + 1 - ind;
factorIndex++;
sumR += colRList[x, y] * factor;
sumG += colGList[x, y] * factor;
sumB += colBList[x, y] * factor;
}
}
}
Color col = new Color(Saturate(sumR), Saturate(sumG), Saturate(sumB), colAList[i,j]);
newTex.SetPixel(i, j, col);
}
}
newTex.Apply();
return newTex;
}