为什么SRT白板动画srt-whiteboard-animation画复杂线稿不跳笔?墨流聚类与密度梯度游走详解
【免费下载链接】srt-whiteboard-animation将 SRT 字幕做成暖米黄纸张底的流式笔迹白板手绘动画 skill:mask 分区遮罩编排 + stream 连续笔迹(ink→color)。项目地址: https://gitcode.com/gh_mirrors/sr/srt-whiteboard-animation
srt-whiteboard-animation 是一个把 SRT 字幕变成暖米黄纸张底白板手绘动画的开源 Skill:它按字幕叙事顺序分区揭示画面,并用流式笔迹让笔尖沿线稿连续滑行落墨,再逐步添彩导出 MP4。很多人问:画面里线稿又多又碎,笔尖为什么从不"瞬移"?答案藏在渲染器的两层算法里——墨流聚类(ink stream clustering)与密度梯度游走(gradient walk),本文带你逐层看懂它。
先认识这张图:渲染器要解决的问题
动画的输入就是一张手绘线稿图加一份同名标注文件(例如 examples/scene-01-monkey-mountain-banana.annotation.json),渲染核心在 scripts/stream_render.py 与 scripts/render_stream_whiteboard.py。
"跳笔"的本质是一个排序问题:图上有成百上千个含墨像素格,如果按"从上到下、从左到右"机械遍历,笔尖就会在对象之间来回穿梭,观感支离破碎。srt-whiteboard-animation 的做法是把"先画哪、后画哪"交给算法:先聚类成语义完整的"墨流",再在每条墨流内部生成一条几乎不折返的连续路径。
第一步:把线稿量化成"墨迹格"
在 scripts/stream_render.py 中,渲染器先做三件事:
- 自适应阈值:用
adaptiveThreshold把灰度图二值化,灰度低于 10 的像素判定为"墨迹",不受纸张底色影响; - 网格量化:整张图切成 10×10 像素的格子(
grid_edge=10),只要格内有墨迹就算"活跃格"; - 背景染色:采样原图四角颜色,把接近背景的像素替换成画布底色,保证起笔与添彩阶段背景一致。
量化之后,"画哪里"从像素问题变成了格的游走问题,这是后面所有算法的输入。
墨流聚类:主体、文字、轮廓分开画
核心入口是cluster_ink_streams(scripts/stream_render.py),它把全部活跃格聚成若干条语义完整的"墨流",流水线如下:
- 连通域标记:对墨迹格做 8 连通域分析(
_label_components),每个连通域就是一块"笔画团"; - 细桥断开:地面横线、箭头这类细长笔画会把本不相干的物体连成一大块。
_split_bridge_connected_component(scripts/stream_render.py)通过垂直投影找"墨迹稀疏的峡谷",在最优走廊处把弱连接切开,避免笔尖在几个物体间反复横跳; - 碎片归并:1~2 格的噪点碎片会被并入质心最近的大连通域(
_merge_small_components),防止"画一块没画完就跳去补一个点"; - 语义分类:
classify_stroke_groups(scripts/stream_render.py)把每块团判定为三类——主体(最大连通域)、文字(宽高比 ≥2.2 且填充密度 ≥0.5)、轮廓(其余细线)。绘制顺序固定为主体 → 文字 → 轮廓; - 视觉区域合并:边框、字和箭头哪怕分属不同连通域,只要包围盒相邻就归入同一区域(
_group_adjacent_stroke_groups),保证"一个视觉单元画完再离开"。
区域之间怎么接?算法记住每条墨流的出口,下一轮总是挑"入口离出口最近"的墨流继续画;若发现从墨流终点进入更近,还会把整条路径整体反向。这一步把跨区域的跳笔压到了最短。
密度梯度游走:笔尖从墨最浓处下笔
单条墨流内部的路径由_gradient_walk生成(scripts/stream_render.py),思路非常像真人写字:
- 起笔点:
_density_seed统计每格周围 5×5 邻域内的墨迹格数量,选邻居最密的格子下笔——模拟"从墨最浓处落笔"; - 走法:每步在 8 个未访问邻居中,挑局部密度最高、且与来向转角最小的那格,形成"贴着墨迹走、少折返"的连续轨迹;
- 断笔兜底:若四周已无可走邻居,才跳到全局最近的未访问格继续,跳的距离天然很短,肉眼几乎不可察。
对文字区域,算法换成更贴近书写的横向按段扫描(_text_scan_order,scripts/stream_render.py):把字格按列切成若干段,段内沿墨迹最近邻游走,段间起点尽量靠近上一段的出口——观感就像一笔一划写字,而不是栅格式刷扫。
骨架模式:复杂线稿的像素级追踪
网格模式默认稳定,但对线条更精细的插画,可以用--ink-path skeleton切到骨架追踪:
- Zhang-Suen 细化:把墨迹细化到 1 像素宽的中轴线(scripts/stream_render.py);
- 8 邻接最直边追踪:
trace_8connected(scripts/stream_render.py)从端点出发,在 T 型/十字交叉处沿"最直的未访问边"继续走,用无向边集合记账,避免交叉点碎成三角形小笔; - 重采样 + Chaikin 平滑:去锯齿、去毛刺后再按弧长等距取点。
骨架追踪失败时会自动回退到网格路径(scripts/render_stream_whiteboard.py),保证任何线稿都能出片。
从路径到成片:插值、抬笔与遮罩双保险
拿到有序格路径后,scripts/render_stream_whiteboard.py 的_grid_plan把相邻格中心按 2 像素间距插值成连续的笔尖坐标;遇到非相邻格切换就标记一个"抬笔点",跳过插值——视觉上只是短暂提笔,绝非瞬移。每帧再把内置手部素材 assets/drawing-hand.png 的笔尖锚点精确对齐到当前落墨点,强化"笔在画"的错觉。
编排层还有一道保险:每个区域的允许掩码= 自身矩形区域 − 所有后续区域 −protectedRegions(scripts/render_stream_whiteboard.py),落墨被严格限制在掩码内,未开始的元素不会提前露线。这样"分区遮罩编排 + 流式笔迹"各司其职:遮罩管何时能画,聚类与游走管按什么顺序画。
快速上手:3 条命令验证效果
python scripts/prepare_env.py --check # 输出 ENV_PY=<解释器路径> python scripts/parse_srt.py <字幕.srt> --target-sec 30 <ENV_PY> scripts/render_stream_whiteboard.py <图片> <标注.json> <输出.mp4> assets/drawing-hand.png --ink-path grid线稿清晰的场景把--ink-path换成skeleton即可。分镜、区域与字幕关联可以无服务器地在浏览器预览台 assets/preview.html 中拖拽调整;完整工作流见 SKILL.md,多幕成片用 scripts/merge_scenes.py 合并。
总结:不跳笔 = 好的排序 + 短兜底
| 环节 | 做什么 | 对应实现 |
|---|---|---|
| 墨迹量化 | 自适应阈值 + 10px 网格 | _active_mask |
| 墨流聚类 | 连通域分类 + 细桥断开 + 碎片归并 | classify_stroke_groups |
| 流间串联 | 出口→入口最近邻,可整条反向 | cluster_ink_streams |
| 流内路径 | 密度种子 + 梯度游走(文字按段扫) | _gradient_walk/_text_scan_order |
| 精细模式 | 骨架细化 + 最直边追踪 + 平滑 | trace_8connected |
| 编排保险 | 允许掩码限制落墨范围 | _allowed_mask |
正是这套"先聚类成语义墨流、再在流内沿密度梯度连续游走"的设计,让 srt-whiteboard-animation 面对复杂线稿时,笔尖始终贴着画面走,跳笔只剩提笔级别的短停顿。
【免费下载链接】srt-whiteboard-animation将 SRT 字幕做成暖米黄纸张底的流式笔迹白板手绘动画 skill:mask 分区遮罩编排 + stream 连续笔迹(ink→color)。项目地址: https://gitcode.com/gh_mirrors/sr/srt-whiteboard-animation
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考