双边过滤算法的hlsl实现

来源:互联网 发布:乐视网络电视app 编辑:程序博客网 时间:2024/06/14 13:29

我是基于这篇文章来实现的。


先说说最核心的公式:


i, j 代表当前的纹理坐标

g(i, j) 代表该纹理坐标上实际的输出的颜色值

k,l 代表(i, j) 周围的点的纹理坐标,即需要采样融合到(i, j)上的点的纹理坐标

代表k, l在一定范围内取值后,通过这个公式计算出来的值的累加和。(关于累加和的解释可以看这里)

这里的这个f(k, l) 表示的是(k, l)坐标上的点的颜色值。

w(i, j, k, l)表示权重系数。


权重系数为

定义域核:

和值域核:


的乘积:



定义域核中的,就是纹理坐标i 减去纹理坐标k ,再算平方。

只是一个固定参数,自己根据实际情况给他赋值即可,在我的项目里,这个参数取到20000,效果不错。

值域核中 f(i, j) 还有f(k, l)和上面核心的公式中的不同,这里的这个f(k, l) 表示的是一个算法f。在(k, l)纹理坐标上的点的rgb值相加之和。比如,对(k, l)纹理取样后的值保存在一个float4 AA中,则算法f 就等于 AA.r + AA.g + AA.b,f(i, j)也是一样。


公式解释完了之后,实现就容易了:

uniform Texture2D diffuseTexture;uniform float radius = 10; // 过滤半径,即k, l的取值范围uniform float2 tex_size; // 传入的纹理的尺寸SamplerState textureSampler{    AddressU  = Clamp;    AddressV  = Clamp;    Filter    = Linear;};struct VertData{    float4 pos      : SV_Position;    float2 texCoord : TexCoord0;};float4 sample2rgba(float2 uv){float4 out_c = diffuseTexture.Sample(textureSampler, uv);// 因为采样取出来的值的范围是0~1de,而算法中是需要0~255的,因此这里做一下转换out_c.r = out_c.r * 255;out_c.g = out_c.g * 255;out_c.b = out_c.b * 255;// out_c.a = out_c.a * 255; // alpha不需要参与计算return out_c;}float4 main(VertData input) : SV_Target{float4 center = sample2rgba(input.texCoord);// (k, l)坐标下的采样颜色值float4 curPix;// 由于纹理坐标的取值范围是0~1的,相邻点的纹理坐标就无法计算了,因此这里把纹理坐标转成实际像素位置,这样偏移值直接+1,-1之类就行了float2 tex_coord = float2(input.texCoord.x * offset_x, input.texCoord.y * offset_y);float2 new_coord;float weight; // 权重float weight_sum = 0.0; // 权重累加和float4 c_cum; // 颜色值的累加和float temp1;float temp2;int k = 0, l = 0;for (k = -radius; k <= radius; ++k){for (l = -radius; l <= radius; ++l){// 计算出(k, l)的纹理坐标new_coord = float2(tex_coord.x + k, tex_coord.y + l);// 采样,因为new_coord为实际像素的位置,需要转换成0~1范围的坐标才能取样curPix = sample2rgba(float2(new_coord.x / offset_x, new_coord.y / offset_y));// 计算定义域核temp1 = -pow(tex_coord.x - new_coord.x, 2);temp1 += pow(tex_coord.y - new_coord.y, 2);temp1 /= sigma_space;// 计算值域核temp2 = -pow(center.r + center.g + center.b - curPix.r - curPix.g - curPix.b, 2);temp2 /= sigma_color;// 计算当前的权重temp1 = temp1 + temp2;weight = exp(temp1);// 颜色值的累加c_cum += curPix * weight;// 权重的累加weight_sum += weight;}}// 累加颜色除以累加权重,就是需要的颜色值了c_cum = c_cum / weight_sum;c_cum = c_cum / 255.0; // 算法是用0~255范围的来算的,所以这里需要再转回0~1的范围c_cum.a = 1.0; // 固定alpha值return c_cum;}


实际运行下来,这个算法效率很低,再回顾下这个算法,for嵌套for,就是把当前纹理坐标点周围的点全都取出来计算了。我之前在网上某一篇文章(具体哪篇忘了)中有看到一种做法,只是取样当前纹理点横向的点和纵向的点,也能达到效果,而取样的点也能少很多。

升级过的算法:

uniform Texture2D diffuseTexture;uniform float radius = 10; // 过滤半径,即k, l的取值范围uniform float2 tex_size; // 传入的纹理的尺寸SamplerState textureSampler{    AddressU  = Clamp;    AddressV  = Clamp;    Filter    = Linear;};struct VertData{    float4 pos      : SV_Position;    float2 texCoord : TexCoord0;};float4 sample2rgba(float2 uv){    float4 out_c = diffuseTexture.Sample(textureSampler, uv);    // 因为采样取出来的值的范围是0~1的,而算法中是需要0~255的,因此这里做一下转换    out_c.r = out_c.r * 255;    out_c.g = out_c.g * 255;    out_c.b = out_c.b * 255;    // out_c.a = out_c.a * 255; // alpha不需要参与计算    return out_c;}float4 main(VertData input) : SV_Target{    float4 center = sample2rgba(input.texCoord);    // (k, l)坐标下的采样颜色值    float4 curPix;// 由于纹理坐标的取值范围是0~1的,相邻点的纹理坐标就无法计算了,因此这里把纹理坐标转成实际像素位置,这样偏移值直接+1,-1之类就行了float2 tex_coord = float2(input.texCoord.x * offset_x, input.texCoord.y * offset_y);float2 new_coord;float weight; // 权重float weight_sum = 0.0; // 权重累加和float4 c_cum; // 颜色值的累加和float temp1;float temp2;int k = 0, l = 0;// 横向部分取值计算for (k = -radius; k <= radius; ++k){// 计算出(k, l)的纹理坐标new_coord = float2(tex_coord.x + k, tex_coord.y + l);// 采样,因为new_coord为实际像素的位置,需要转换成0~1范围的坐标才能取样curPix = sample2rgba(float2(new_coord.x / offset_x, new_coord.y / offset_y));// 计算定义域核temp1 = -pow(tex_coord.x - new_coord.x, 2);temp1 += pow(tex_coord.y - new_coord.y, 2);temp1 /= sigma_space;// 计算值域核temp2 = -pow(center.r + center.g + center.b - curPix.r - curPix.g - curPix.b, 2);temp2 /= sigma_color;// 计算当前的权重temp1 = temp1 + temp2;weight = exp(temp1);// 颜色值的累加c_cum += curPix * weight;// 权重的累加weight_sum += weight;}// 纵向部分取值计算k = 0;for (l = -radius; l <= radius; ++l){// 计算出(k, l)的纹理坐标new_coord = float2(tex_coord.x + k, tex_coord.y + l);// 采样,因为new_coord为实际像素的位置,需要转换成0~1范围的坐标才能取样curPix = sample2rgba(float2(new_coord.x / offset_x, new_coord.y / offset_y));// 计算定义域核temp1 = -pow(tex_coord.x - new_coord.x, 2);temp1 += pow(tex_coord.y - new_coord.y, 2);temp1 /= sigma_space;// 计算值域核temp2 = -pow(center.r + center.g + center.b - curPix.r - curPix.g - curPix.b, 2);temp2 /= sigma_color;// 计算当前的权重temp1 = temp1 + temp2;weight = exp(temp1);// 颜色值的累加c_cum += curPix * weight;// 权重的累加weight_sum += weight;}// 累加颜色除以累加权重,就是需要的颜色值了c_cum = c_cum / weight_sum;c_cum = c_cum / 255.0; // 算法是用0~255范围的来算的,所以这里需要再转回0~1的范围c_cum.a = 1.0; // 固定alpha值return c_cum;}

运行后,效率还行,虽然有些掉帧,但还是能接受的。


后记:

对于公式,相信还是有很多人跟我一样,一看就蒙圈了,但是网上的很多贴了公式的文章,都只是草草的解释了下公式,有很多对于大神而言算是基础的,或者说是常识的东西,就直接一笔带过了,这让我们这种算法小白来说,看的就比较费劲了。这里当然不是说怪大神们不好,像这种基础的,常识的东西本来就应该了然于心的,只是这一次我走了次弯路,无视算法,先去尝试直接翻译他们的实现,以至于问题产生问题,无法解决,而不懂的还是不懂。最后实在没有办法了,才去尝试看懂算法,看懂了之后,实现,调整就都顺利了。

最后再记一些过程中发现的一些东西:

opencv是个挺牛逼的开源第三方库。它里面有双边过滤的算法实现源码。这里有一篇详细讲解该代码的文章,这里

这里还有一篇用CUDA实现的,貌似是针对CUDA做过优化的算法实现,这里

0 0
原创粉丝点击