☰
40只鸟如何优雅排进一张纸:Fugleramme的Spiral与Voids布局算法深度解析
2026/9/28 21:21:43 网站建设 项目流程

40只鸟如何优雅排进一张纸:Fugleramme的Spiral与Voids布局算法深度解析

【免费下载链接】fuglerammeBird frame for Raspberry Pi - real-time bird detection by audio, fully local AI, rendered as real, hand-cut 1800s bird illustrations. On an e-ink panel, a TV, or any screen.项目地址: https://gitcode.com/gh_mirrors/fu/fugleramme

Fugleramme 是一个运行在树莓派上的鸟鸣检测相框:它用本地 AI 识别麦克风里听到的鸟,再把每种鸟匹配到一张 19 世纪的手绘博物插画,最后把最多 40 只鸟优雅地排进一张纸。本文深度解析它背后的两套布局算法——Spiral(螺旋)与 Voids(空洞填充),以及"放不下就先缩小、再二分找回"的自动缩放机制。即使你不写代码,也能看懂这两套算法为何能把整页花园鸟排得疏密有致、互不打架。

问题从哪来:一张纸,最多40只鸟

默认配置下,Collage(拼贴)模式会展示观察窗口内听到的所有鸟,上限 40 只(settings.species_limit)。这些鸟有大有小:一只绿头鸭可能比一只柳莺重 100 倍。目标很苛刻:

  • 不重叠:任意两只鸟的"实体"像素绝不重叠;
  • 不出界:每只鸟(连同它的名字标签)必须完整落在纸上;
  • 像样:大鸟居中、小鸟填缝,留白自然,而不是挤成一片。

忙起来的一天,花园里可能真的出现三四十种鸟。效果大致是这样:

两套算法,两种提问方式

Fugleramme 的全部打包逻辑集中在 src/fugleramme/render/packing.py,官方文档 docs/display.md 里对两种布局只有一句话的区别:

布局一句话原理
Spiral从页面中心往外一圈圈找空位,大鸟在中间,小鸟绕外圈
Voids一次性算出整页所有合法位置,专挑"最空"的纸面落子

少鸟时两种布局的差异最直观——Spiral 长成中间密、四角空的"圆团",Voids 则把鸟撒向四个角落:

Spiral 布局:一圈一圈向外生长

Spiral 实现见 spiral(),思路朴素到几乎可以手绘:

  1. 按体重排序,最大的先落子。鸟的显示大小来自真实体重数据 assets/bird_sizes.csv(AVONET 数据集),经 sizes.py 压缩后,最重的鸟也只比最轻的大 2.5 倍左右——大鸟有排面,但小鸟不会消失。
  2. 沿同心圆环试探。从页面中心出发,每隔 6 像素画一个候选圆环,环上均匀取点,逐个检查"这里放得下吗"。放得下就立刻停手,绝不贪心比较。
  3. 先查三行,再查全图。检查一只完整插画是否碰撞要扫几千个像素,于是它先挑鸟影上的 3 条"探针行"快速试错——99% 的碰撞位置在这一步就被淘汰,成本只有整盒碰撞检查的百分之一。

最大的鸟(比如一只绿头鸭)总是最先拿到中心位置:

代价也很诚实:Spiral 是贪心的,先来的鸟抢了好位置,后来的小鸟只能在边角挤,整页会形成一个"中间饱满、四角空荡"的圆形团块。它也因此比 Voids 慢约 3 倍。

Voids 布局:一次 FFT 算出所有合法位置

Voids 把问题反过来问:不是一只鸟一个位置地试,而是一次性回答"这只鸟可以放在纸上的哪些格子"。

它的核心工具是互相关——一次 FFT(快速傅里叶变换)就能对纸上每一个偏移位置同时算出"这只鸟放这里会踩到已占用的像素吗"。见 Board.against():

  • 碰撞检测:鸟的剪影与已占用页面互相关,结果小于 0.5 的偏移就是合法位置;
  • 空度评分:先把"空白纸面"做成一个高斯模糊的场(某格周围越空旷值越高),再与鸟的剪影互相关——"这只鸟盖住多大面积的空纸"一步算完;
  • 贴邻偏好:halo场标记紧贴实体边缘的空格,让小鸟愿意"窝"在邻居身侧或墙角,而不是飘在中央。

于是每只鸟的成本函数是:-空度 - 贴邻系数 × 接触量 + 轻微居中拉力,取最低分的位置落子。大鸟自然铺满整张纸,小鸟钻进剩下的缝隙——包括四个角落:

工程上还有一个聪明的安全网:碰撞判断在 6×6 像素的粗格网格上做最大池化,这种粗化只会多报碰撞,绝不会漏报——细像素上的重叠必然落在双方都占用的格子内。性能大幅提升,正确性零损失。

放不下怎么办:先缩小,再二分找回来

40 只鸟加 40 个名字标签,第一版布局大概率放不下。collage.py 的处理分两步:

  1. 每轮缩 10%:整体尺寸乘以0.9,最多试 20 轮,直到整页塞得下;
  2. 二分找回:缩小必然"过缩",于是在"刚能放下"和"刚放不下"之间做二分,把浪费的空白尽量吃回来。Spiral 慢,找到首次能放下就直接停;Voids 快,允许再花 3 步二分把版面再撑大一点。

名字标签也有自己的生存保障:标签不是画完鸟再补上去,而是和鸟绑成一个整体一起参与打包(见 _with_label()),这样每个名字在几何上就"有座",永远不会压到邻居或出界。如果连鸟带名字实在塞不下,系统会退掉名字保住鸟——毕竟"鸟比空白重要"。

如何切换布局

布局由settings.layout控制,默认是spiral。在管理页(:8080/admin)的 Layout 选项里切换即可,可选值定义在 LAYOUTS:

  • Spiral— grows out from the middle,适合喜欢"大鸟居中"传统图鉴感的用户;
  • Voids— fills the emptiest spot first,鸟铺满整张纸,适合大屏电视展示。

两种布局打包的"鸟座"会被缓存(见 collage.py 缓存逻辑),所以切换后重算也只发生在鸟群变化时。

小结

机制一句话总结
按体重排序大鸟先选座,中心永远属于大鸟
Spiral同心圆环贪心找位,简单、快 0 倍开销,页面呈圆团
Voids一次 FFT 算全页合法位置与空度评分,角落都不浪费
探针行用百分之一的成本淘汰 99% 的碰撞位置
6×6 粗格池化碰撞检查只多报、不漏报
缩 10% + 二分先保证放得下,再吃回浪费的纸面

这就是 Fugleramme 让 40 只鸟"优雅排进一张纸"的完整答案:Spiral 用一圈圈的耐心,Voids 用一次傅里叶变换的全局视野,最后再由"缩小—二分"兜底,保证无论花园里来多少访客,这一页纸总是既完整又好看。

【免费下载链接】fuglerammeBird frame for Raspberry Pi - real-time bird detection by audio, fully local AI, rendered as real, hand-cut 1800s bird illustrations. On an e-ink panel, a TV, or any screen.项目地址: https://gitcode.com/gh_mirrors/fu/fugleramme

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询