Futhark的蓝色噪音

2021-04-13 01:08:53

久前,我读了苏克马的抖动的优秀文章。立即,它引起了我对奥德拉·丁尼恩的游戏回归的兴趣,因为我从非常享受。我也认为尝试实施他在Futhark描述的一些抖动算法很有趣,但我从未真正过到处。到现在!

我主要将专注于蓝色噪声滤波器,因为这似乎是最有趣的滤波器,但我也可以在拜耳过滤器中实施拜耳滤波器,也许是其他人。我将跳过大部分关于抖动工作以及目的的详细信息,而是将您指向上面链接的文章。本篇文章的目的是说明我们如何实现使用Futhark描述的算法。

顺便说一下,这个博客帖子是使用Futhark incorate编写的,所以您可以在闲暇时检查整个源代码。

我们将使用与Surma的博客文章中的相同图像:

将图像加载到Futhark界面中以[] [] U32的形式返回ARGB格式的像素。我们只对灰度图像感兴趣,因此让我们写一些函数来将argb图像转换为灰度。我们将使用0到1之间的F32值来表示灰度,0是黑色和1个白色。我们也立即执行一些伽玛修正,因此我们可以从现在开始使用灰度图像。

让unpack_rgb(像素:u32):( u8,u8,u8)=(u8.u32像素,u8.u32(像素>> 8),U8.u32(像素>> 16))让亮度(像素:U32):F32 =设(r,g,b)= unpack_rgb pixel - 我们可以只使用其中一个频道,但这应该给我们带来相同的结果 - 当输入图像已经灰度时,结果也是如此。在(F32.U8 R + F32.U8 G + F32.U8 B)/(255.0 * 3)

让_linear(b:f32):f32 =如果b< = 0.04045则b / 12.92 else((b + 0.055)/ 1.055)** 2.4灰度[n] [m] [m] [m] [n](img:[n] [m] U32):[n] [m] f32 = map(map(to_linear<亮度))img

现在我们可以尝试应用单个量化方法检查每个像素是否低于或高于0.5,以便确定它是否应该是黑色或白色:

请注意,我使用Booleans表示纯黑白像素:True是白色,假是黑色。

如原始文章所述,这种方法非常不满意。我们几乎无法看到所描绘的内容。

Import" lib / github.com / diku-dk / cpprandom / comantum"模块d = siment_real_distibution f32 minstd_randlet量化_random [n] [m](种子:i32)(img:[n] [m] f32) :[n] [m] bool = - 每像素创建RNG,让RNGs = minstd_rand.rng_from_seed [seed] |> minstd_rand.split_rng n |>地图(minstd_rand.split_rng m) - 对于每个像素,在map2中应用随机性因子并量化(map2(\ rng pixel - > let(_,x)= d.rand(0,1)rng在pixel> x ))RNGS IMG

我们在量化时真正做的是,将原始图像中的每个像素进行比较到掩码。我们已经看到了两个案例,我们将其与掩码进行比较,其中所有值为0.5,以及随机生成掩码的一个掩码,但存在许多其他掩模。我们还可以想象掩码可能没有必要具有与输入图像相同的大小,如果我们只能用掩模图像铺设原始图像。

此广义过程称为抖动,因此让我们写一个函数来向图像应用抖动掩码:

让抖动[n] [n] [n'] [m'](max:[n] [m] f32)(掩模:[n'] [m'] f32):[ n] [m] bool =让辅助ij pixel = pixel> map2中[i%n']在map2(\ i - > map2(辅助i)(iota m))(iota n)img

现在让我们看看一些面具。第一个是拜耳掩模,它使用拜耳矩阵。

首先,我们需要一个辅助函数:Concat_m采用四个相等大小的矩阵并将它们排列在方矩阵中。

假设concat_m [n]' t(xss1:[n] t)(xss2:[n] [n] t)(xss3:[n] [n] t)(xss4:[n] [ n] t):[] t =拍摄n2 = n * 2在concat(转置(转置xss1)(翻转xss2)))(转置(concate_to n2(翻转xss3)(翻转xss3)))

让拜耳(n:i64):[] [] i32 =让舵机i = map(map(\ x - > 4 * x + i))让拜耳= [[0,2],[3,1]]如果n == 0那么拜耳别跳别队_在1 ... n do concat_m(助手0拜耳)(Helper 2拜耳)(Helper 3 Bayer)(Helper 1 Bayer)

注意,我们应该使用比特算术方法,或者至少确定哪一个更快:https://en.wikipedia.org/wiki/Ordered_dithing

我们还需要能够正常化拜耳滤波器(以及后来的Bluenoise过滤器)。为此,我们将介绍正常化_I32:

让rangalize_i32 [n] [m](xss:[n] [m] i32):[n] [m] f32 =在地图中置于map(map(\ x - &gt)的最大= i32.maximum ; f32.i32 x / f32.i32最大值))xss

让我们看看一些结果。首先,我们创建了前四个拜耳矩阵,看看较大矩阵对抖动结果的影响:

让Bayer0 = Normalize_I32(拜耳0)让Bayer1 = Normalize_I32(拜耳1)让Bayer2 = Normalize_I32(拜耳2)让Bayer3 = Normalize_I32(拜耳3)

让我们继续前进到蓝色噪声过滤器,这是一种为抖动产生掩模的另一种方式。它基于最初由Robert Ulichney描述的空白和群集方法。

该算法采用随机二进制图案作为输入,进入初始二进制模式,然后使用该初始二进制模式来通过三相生成最终掩模。

首先,我们需要能够生成输入模式,这只是一个随机生成的二进制模式,其中不到一半的值是白色:

module dist = siment_int_distribution i64 minstd_randlet rand_binary_pattern(seed:i32)(n:i64)(m:i64):[n] [m] bool = let rng = minstd_rand.rng_from_seed [seed] - 仅使用n * m矩阵 - 生成n * m矩阵`false`值让xss = replice n(repliced m false) - 生成少数逻辑数目,并将它们设置为`true`。让RNGS = minstd_rand.split_rng(n * m / 4)rng(idxs,vals)= map(\ rng - > let(rng,y)= dist.rand(0,n)rng vet(_,x) = dist.rand(0,m)rng In((y,x),true))rngs |>散射x2d xss andxs vals中的解压缩

该算法取决于能够找到最紧密的集群和给定图像的最大void。要找到最密封的集群,我们将高斯模糊应用于图像,并在结果中找到最亮的像素。要找到最大的void,我们这样做但尝试找到结果的最黑暗的像素。

因此,我们需要能够计算我们可以用来模糊的高斯内核。我们使用Surma也使用的高斯函数,这是Ulichney建议的略微修改的版本。

让Gaussian_kernel(n:i64):[n] [n] f32 =让sigma:f32 = 1.5让因素= 1 /(2 * f32.pi * sigma ** 2)让高斯xy = factor * f32.e ** ( - (f32.i64(x - n / 2)** 2 + f32.i64(y - n / 2)** 2)/(2 * sigma ** 2))在Tabular_2d nn gaussian

让Blur_naive [n](内核:[n] [n] f32)(Inp:[n] bool)= Let Halfn = N / 2 Let blur_pixel(py:i64)(px:i64):f32 = map2 (\ qy - > map2(\ qx g - > let x =(px + qx - halfn)%n在f32.bool inp [y,x] * g中let y =(py + qy - halfn)%n )(iota n))(iota n)内核|>扁平|> f32.sum在tabulate_2d n b blur_pixel中

实现了我们的模糊功能,我们现在可以实现CILICEST_CLUSTER和RESTAL_VOID函数。真的,他们是非常相似的,我们肯定可以将它们抽出一个函数,但保持它们分开使其更清楚他们的所作所为。

让紧身_cluster [n](模糊:[n] [n] bool - > [n] [n] f32)(Inp:[n] [n] bool):( i64,i64)= - 模糊输入图像Blur Inp - 还返回每个像素的指数及其布尔值。 |> MAP3 ZIP3(表格_2D N N(\ I j - >(i,j)))INP - 压平矩阵,因此我们正在使用一条单维数组。 - 查找最高值的像素,仅考虑仅为“真实”的像素 - 原始输入。 |>扁平|> reame_comm(\(idx,x,v)(idx',x',v') - >如果v> v' ||!x'然后(idx,x, v)否则(Idx',x',v'))((-1,-1),false,f32.lowest)|> (.0)让Rest_void [n](模糊:[n] [n] bool - > [n] [n] f32)(Inp:[n] bool):( I64,I64)=模糊INP |> MAP3 ZIP3(表格_2D N N(\ I j - >(i,j)))INP |>扁平|> reame_comm(\(idx,x,v)(Idx',x',v') - >如果v< v' || x'然后(idx,x,v )否则(IDX',x',V'))((-1,-1),真实,f32.highest)|> (.0)

使用这些构建块,让我们实现Initial_Binary_Pattern。 IP是输入模式,结果是初始二进制模式。

让initial_binary_pattern [n](模糊:[n] [n] bool - > [n] [n] f32)(ip:* [n] [n] bool):* [n] [n] bool = let( _,_,res)= - 用无效但不同的值循环初始化两个索引((i,j),(i',j'),IP)=((-2,-2), (-1,-1),IP) - 虽然这两个指数不同(i,j)!=(i',j')do - 计算最紧密的集群的位置i,j)= cixtest_cluster blur ip - 将该位置设置为false,让let ip [i,j] = false - 计算最大的void的位置让(i',j')= tally_void blur ip - - 将该位置设置为true,让ip [i',j'] = true - 重复((i,j),(i',j'),IP),IP)

最后,为了可视化Smallish模式,让我们写一些函数来扩展它们,以便达到任意像素大小:

让规模[n] [m]' t(n2:i64)(m2:i64)(img:[n] [m] t):* [n2] [m2] t = let y_scale = f32.i64 n2 / f32.i64 n设x_scale = f32.i64m2 / f32.i64 m在表格_2d n2 m2中(\ ij - > img [i64.f32< | f32.i64i / y_scale,i64.f32< | f32 .i64 j / x_scale])Let Scale_F32:(I64 - > I64 - > [] [] f32 - > * [] f32)=缩放量表_bool:(i64 - > i64 - > [] [ ] BOOL - > * [] [] bool)=比例

随着所有的,让我们看看生成的初始二进制模式可以看起来像:

看起来很好,我想!所以现在,让我们走进蓝色噪音模式。 Bluenoise函数是ulichneys原始纸上所述的算法非常直接的实现:

让bluenoise [n](模糊:[n] [n] bool - > [n] [n] f32)(Ibp:[n] [n] bool):[n] [n] i32 = - 加载具有初始二进制模式的二进制模式让BP = Copy IBP - Oner是二进制模式中的“True”值的数量,让One = Flatten IBP |≫地图i32.bool |> I32.Sum让Rank = Oner - 1 - DIT`是结果抖动阵列WE' LL输入值。让DIT =复制n(复制n 0i32) - 阶段1让(DIT,_,_)=循环(DIT,BP,等级)而等级> = 0做,让(i,j)= cirtent_cluster blur bp让bp [i,j] = false让dit [i,j] =排名(dit,bp,rank-1)让bp = copy Ibp让Rank = One - 阶段2让(DIT,BP,RANK)=循环( dit,bp,等级)等级< i32.i64(n * n / 2)让(i,j)= gest_void blur bp让bp [i,j] = true let dit [i,j] =排名(dit,bp,等级+ 1) - - 倒置二进制模式,使得`FALSE`现在是少数像素让BP =地图(MAP(!))BP - 阶段3让(DIT,_,_)=循环(DIT,BP,等级)而等级< i32.i64(n * n)让(i,j)= cixtest_cluster blur bp让bp [i,j] = false令dit [i,j] = dit中的(dit,bp,rank + 1)排名

对我看起来很随意。让我们试着将它应用于我们的图像:

即使在Futhark,朴素的蓝色噪音实现也很慢。让我们试着看看我们是否可以通过在频率空间中应用高斯来加速它。

让Center_FFT [N]' T(IMG:[n] [n] t):[n] [n] t = map(旋转(n / 2))img |>旋转(N / 2)

要模糊,我们将内核和输入图像转换为频率空间并将它们乘以在一起

让模糊[n](内核:[n] [n] f32)(inp:[n] bool):[n] [n] f32 =让核' =内核|> fft32.fft2_re |> Center_fft让InP' =地图(MAP F32.BOOL)INP |> fft32.fft2_re |> Map2中的Center_FFT(Map2(C32。*))内核' INP' |> Center_FFT |> FFT32.IFFT2 |> Center_FFT |>地图(地图C32.mag)Let Bluenoise_Mask_FFT = Normalize_I32(Bluenoise(Blur_fft Ker_64)IBP)

- == - 输入:blur_naive_bench--编译随机输入{[32] [32] bool} - 编译随机输入{[64] [64] bool} - 编译随机输入{[128] [128] bool }

- == - 输入:blur_fft_bench--编译随机输入{[32] [32] bool} - 编译随机输入{[64] [64] bool} - 编译随机输入{[128] [128] bool } - 编译随机输入{[256] [256] bool}

- == - 条目:bluenoise_test_naive - 编译随机输入{[16] [16] bool} - 编译随机输入{[32] [32] bool} - 编译随机输入{[64] [64] bool }

条目bluenoise_test_naive [n](inp:* [n] [n] bool):* [n] [n] i32 = let kernel = gaussian_kernel n让ibp = initial_binary_pattern(blur_naive内核)在bluenoise(blur_naive内核)IBP中的IBP

- == - 输入:bluenoise_test_fft - 编译随机输入{[16] [16] bool} - 编译随机输入{[32] [32] bool} - 编译随机输入{[64] [64] bool } - 编译随机输入{[128] [128] bool}

条目bluenoise_test_fft [n](inp:* [n] [n] bool):* [n] [n] i32 = let kernel = gaussian_kernel n让ibp = initial_binary_pattern(blur_naive内核)在bluenoise(blur_fft kernel)IBP中的IBP

$ futhark bechch - backend = depencl bluenoise.futcompiling bluenoise.fut ...报告每个DataSet.bluenoise.fut:blur_naive_bench(使用bluenoise.fut.tuning):data / [32] [32] bool 。 )BOOL.in:4424μs(RSD:0.005; min:-1%; max:+ 1%)数据/ [256] bool.in:38266μs(RSD:0.006; min :-1%; max:+ 1%)bluenoise.fut:blur_fft_bool(使用bluenoise.fut.tuning):data / [32] [32] bool.in:159μs(RSD:0.165; min:-31%;最大:+ 25%)数据/ [64] [64] BOOL.IN:175μs(RSD:0.107; min:-14%;最大:+ 20%)数据/ [128] bool.in:179μs(RSD :0.180; min:-35%; max:+ 26%)数据/ [256] bool.in:133μs(rsd:0.160; min:-18%; max:+ 29%)bluenoise.fut:bluenoise_test_naive (使用Bluenoise.Fut.Tuning):[16] [16] BOOL:23879μs(RSD:0.003; min:-1%;最大:+0%)[32] [32] BOOL:99598μs(RSD:0.012; min :-1%;最大值:+ 2%)[64] [64] bool:5203831μs(RSD:0.005; min:-1%; max:+ 1%)bluenoise.fut:bluenoise_te st_fft(使用bluenoise.fut.tuning):[16] [16] BOOL:40842μs(RSD:0.076; Min:-8%; MAX:+ 8%)[32] BOOL:131507μs(RSD:0.003; MIN:-0%;最大:+0%)[64] BOOL:522510μs(RSD:0.002; min:-0 %; max:+ 0%)[128] bool:2110416μs(RSD:0.009; min:-1%;最大:+ 1%)

Surma提到它需要他大约半分钟,以在2018年MacBook Pro上产生64x64的蓝色噪声纹理。相比之下,我们在大约一秒钟内完成它。