2987 字
15 分钟
我没买这个游戏,但我教会了梯度下降怎么在里面躲猫猫

起因是我在 B 站看《美术生不准玩躲猫猫!!》的录播。画面里一个纯白的小人站在《拿破仑翻越阿尔卑斯山》的油画前面,还没来得及给自己上色。这个游戏叫 MECCHA CHAMELEON,规则一句话能讲完:你顶着一具纯白的身体,要在猎人到场之前把自己涂成背景色,然后一动不动地装作不存在。PCGamesN 说它 7 月 5 日之前卖了 1500 万份,峰值同时在线 34 万人Kotaku 的评价是”像 Prop Hunt,但是好玩”

看着看着我意识到一件事:从猎人那个机位看过去,理想的涂装是有唯一解的。它就是被你身体挡住的那块背景。换句话说,一个玩家涂得像的程度,等价于他徒手做 inpainting 的水平。而 inpainting 恰好是机器很擅长的事。

我甚至没买游戏。手头只有一张截图,连第二个参考机位都没有。那就做一个完全离线的 POC:输入截图,输出一份人类可以照着涂的笔触清单,顺便做个视频看看效果。丑话说在前面,把笔触自动打回游戏里那一步碰 TOS,我没做,也不会做。这个管线到 JSON 文件为止。

计划是这样的,然后第一步就翻车了#

链路本身不复杂,纯二维屏幕空间,不搞 UV 反投影也不搞三维重建:

截图 → 抠人形 mask → LaMa 补全 → 调色板量化 → 笔触拟合 → JSON + 视频

动手前的计划里写着:纯白人形在 HSV 里就是个高明度低饱和的连通域,阈值一卡就出来,连 SAM 都不用。

实测第一帧就翻车了。我把阈值设好一跑,抠出来一个 192 像素的小碎块,定睛一看是玩家名牌上的文字。那个”纯白”人形呢?直播观战视角把画面压得很暗,它在像素上是 V 中位数只有 72 的中灰,比我阈值的下限还暗一截。我盯着取色器看了半天才反应过来,真正把它和背景分开的根本不在亮度上:暖色灯光把墙纸、岩石、马腹全都染到了饱和度 80 以上,唯独这具身体的躯干 S 中位数是 9。全场只有它是灰的。

所以判据从”找白的”改成了”找不带颜色的”,用滞回阈值先抓亮的躯干核心,再顺着连通域把偏暗的四肢捞回来。头顶悬浮的名牌和点赞图标离身体太近,会污染补全的上下文,我把它们单独检出来,一起扔进要抹掉的区域里。

mask 提取结果:红色是人形本体,绿色是连名牌一起抹掉的扩展区

抹掉之后 LaMa 补出来的背景长这样,左边原图,右边补全:

原始帧与 LaMa 补全的并排对比

先证明这条路能走,再写笔触#

有个绕不过去的问题:被身体挡住的那块背景,永远没有任何办法拍到。补全出来的东西没有真值可对。如果补全本身质量不行,后面笔触写得再花哨也是白搭,所以我把评估挪到了笔触前面。

办法是自己造真值。把人形轮廓的 mask 原样平移到画面里背景可见的地方,挖掉,让 LaMa 补,再跟被挖掉的真实像素对答案。轮廓必须原样用,一换成矩形分数就虚高,因为补全难度和 mask 形状强相关。落点按周边结构复杂度铺开,再单挑三个最难的当上界。跑出来 8 个常规落点的 L1 在 1.1 到 9.3 之间(255 制),最难的落点也没崩。行,这条路能走。

评估落点分布图:绿框是复杂度分位落点,红框是高难度上界,蓝框是玩家本体

另一个指标不需要真值:看 mask 外面那些砖缝、画框、棱线的方向,外推进 mask 里,检查补全图有没有把线接上。单独看这个数字没有意义,所以我把同样的检查打在真实背景上做基线。结果是真实背景自己能到 0.81,LaMa 的补全只有 0.39。也就是说 LaMa 补出来的东西偏糊,高频细节丢了不少。这不冤枉它,直播录像的编码块效应本来就把高频纹理污染了,它能参考的东西有限。

这个 0.39 对 0.81 我原样写进了成品文案。遮挡区的补全只能被验证为”合理”,永远没法被验证为”正确”,这句话必须说在前面。

笔触:让梯度下降学画画#

补全出来的目标图要变成一笔一笔可执行的动作。我走的是 Stylized Neural Painting 那一路:每笔是一个胶囊形状,参数是位置、长宽、朝向、颜色和不透明度,用 SDF 加 sigmoid 软边缘搭一个可微渲染器,三百笔按顺序 alpha 叠起来,直接对目标图做梯度下降。颜色不能随便调,要从调色板里选,就用 softmax 从软到硬退火。损失函数在 mask 边界往里 15 像素的环带上乘了 8 倍权重,因为轮廓上的接缝一断,整个伪装就穿帮了,身体中间差一点颜色反而没人看得出来。

16 色 300 笔,最后 masked L1 是 1.17。从导出的 JSON 逐笔回放一遍是 1.35,差的那零点二来自坐标取整。视频里放的就是 JSON 回放,所见即人能照着做的东西:

首帧四宫格演示:原始帧、补全目标、笔触延时累积、合成回原帧

有意思的是扫 N 色乘 M 笔的曲线,扫出来几乎是平的,8 色 60 笔就有 1.33。直到把调色板打印出来才明白是怎么回事:这一帧的遮挡区 16 个颜色的 RGB 全部落在 0 到 29 之间。那块地方黑得根本没几个比特的信息量,加色加笔都是白给。这是素材的性质,测不出方法的上限。

然后来了五张新图,假设死了第二遍#

写完这些之后,又塞进来另一位主播的五张截图:路牌柱、蒙娜丽莎、洗衣房、Minecraft 风树屋、涂鸦砖墙。全是亮场景。

“找不带颜色的”这条判据当场阵亡。路牌的白面、STOP 的字母、指示牌上的图标,全是低饱和高亮度。更狠的是树屋那帧,暖光把白身体直接染到了 S 中位数 90 左右,那具身体连”不带颜色”都不剩了。

新方案变成四组阈值预设撒网加打分选人。打分因子试了一圈,真正顶用的是内部拉普拉斯平滑度:游戏渲染出来的身体是抗锯齿的平滑曲面,而大理石有纹、金属有噪点、Minecraft 有像素块,全是高频。另一个救命的操作是沿图像边缘切断连通域,玩家贴在白牌子上的时候和牌面连成一整块,但身体轮廓总有一圈环境光遮蔽的暗边可以下刀。

五帧里三帧全自动选对。剩下两帧值得单独讲。

洗衣房那帧,我看着抠出来的 mask 的第一反应是选错了:红色标出来的分明是绿色指示牌上印的白色小人图标。放大看了半天才发现事情不对,那就是玩家本体。他吸附在牌面上,摆出了和图标一模一样的姿势,破绽只有头顶的名牌和身上那点 3D 阴影。这个玩家的伪装骗过了我的提取器,严格来说是提取器对了,我错了。

洗衣房帧的 mask:红色盖住的是玩家本体,他正模仿指示牌上的白色小人图标

洗衣房帧的四宫格演示:模仿图标的玩家被抹掉后融进纯绿牌面,再一笔笔涂回去

路牌柱那帧是真正的地狱难度。人形横跨四块牌子还伸手扶着牌边,边缘切割会顺着牌子轮廓把身体切成碎片,开运算和碎片捡回的各种组合我挨个试过去,要么丢一条手臂,要么把牌边捡回来。中途还有一次我看着结果里那条红色的”手臂”觉得哪里不对劲,跟原图对了半天才发现 mask 整体偏了半个身位,那条东西根本是路牌上字形的横杠。

路牌柱调试现场:左边是原图里的玩家,右边是偏了半个身位的 mask,那条"手臂"其实是字形横杠

最后我认了,用一个手写的身体坐标点去锚定完整连通块,mask 里混进一段牌边就混进去,LaMa 重建一条白边不费什么事。

五帧的成绩单(16 色 300 笔,连续率是前面说的无参考指标):

连续率笔触 L1备注
洗衣房0.721.78被完全挡住的图标无法凭空重建,涂完反而融进纯绿牌面
树屋0.631.63暖光染色帧
蒙娜丽莎0.682.67头部被 LaMa 猜成一坨蓝灰
涂鸦墙0.533.02横躺人形,砖缝接得很好
路牌柱0.687.76最难帧,高对比彩色印刷背景

亮场景的连续率在 0.53 到 0.72 之间,全面高于第一帧暗场景的 0.39。背景里可辨认的结构越多,LaMa 的发挥就越稳,这个模型当年的卖点就是用傅里叶卷积延续大面积缺失里的周期结构,正好对上路牌和砖墙这种场景。

涂鸦墙帧的四宫格演示:横躺的人形被抹掉后砖缝和涂鸦被延续,再一笔笔涂回去

附赠一个包管理的坑#

simple-lama-inpainting 把 pillow 钉死在 10 以下,装出来的 9.5 旧轮子在 Windows 加 Python 3.12 上一画 truetype 文字就 access violation,崩在 FreeType 的 getmask2 里,堆栈都不给全。我一开始怀疑是微软雅黑那个 .ttc 字体的问题,换成黑体照崩。强制升到 pillow 10.4 之后一切正常,LaMa 对新版毫无怨言。这个版本钉死保护的是一个不存在的兼容性,制造的是一个真实存在的崩溃。

回头看#

要说这一趟学到什么,大概是三件事。

评估要放在生成前面。自造真值那步花了不到一小时,却决定了后面每一步的底气。反过来的话,先写三天笔触优化器再发现补全不行,沉没成本会替我做决定。

跨场景的”简单规则”基本活不过第二批数据。低饱和判据死于亮场景,消色差假设死于暖光,最后活下来的是拉普拉斯平滑度那个因子。事后想想不奇怪,前两条描述的是某一帧的光照,最后这条描述的是渲染管线本身。

还有就是知道什么时候停。路牌柱那帧我放弃了全自动,一个写在配置里的种子坐标换来人形零遗漏,比再调两个小时打分权重划算。POC 的任务是回答”这条路通不通”,任何一帧都没有义务被优雅地解决。

代码和全部产物在 GitHub 仓库里,素材出处和两位主播的名字标在每个视频的页脚。至于游戏本体,写完这一整套管线,我还是没买。

我没买这个游戏,但我教会了梯度下降怎么在里面躲猫猫
https://blog.lishuyu.app/posts/2026-07-21-meccha-chameleon-inpaint-poc/
作者
猫猫魔女
发布于
2026-07-21
许可协议
CC BY-NC-SA 4.0