一套数字人代码做成三个形象:fork 部署的 6 个必改点与素材陷阱

2026-10-23 · AI智工坊 · 数字人交付实录

同一套代码,跑出了三个能对话的数字人:一个门店接待、一个客服、一个展厅讲解。它们不是三份工程,而是同一份 fullbody_dh 代码的四个副本,各自换形象、换人设、换知识库、换端口。

这件事听起来像"复制粘贴改个名",实际 fork 过的人都知道:改完能启动不代表能用,能对话不代表像本人,像本人不代表链接能发出去。下面是我们把这套流程做顺之后沉淀的判据与坑,按"改代码 → 换素材 → 对人设 → 通公网"四段写。

一、先看清它为什么不烧显卡

很多人以为数字人必须占一张显卡。这套 kiosk 的实测是:跑着接待时 GPU 占用率 0。原因是它根本不做实时推理,而是"预渲染视频循环 + 独立音频":

组件作用技术要求
idle 视频没人说话时的常驻画面1080×1920、首尾无缝、无音轨
talk 视频说话时的口型画面1080×1920、必须 pingpong(正放+倒放拼接)、无音轨
音频真正的声音TTS 独立输出,与视频解耦

前端靠 talk 视频时长的一半作为折返点,一句话说到尾就倒着播回来,这样循环永远接得上。所以 talk 如果不是 pingpong 素材,你会看到"说到一半突然跳回开头"的抽搐感——这是 fork 时最常见的低级错误,一眼能看出来。

欢迎词不需要单独的欢迎视频:它走 /api/speak 出声,画面仍然是 idle。少一段素材,少一个出错点。

二、素材:idle 与 talk 必须用两种方式生产

这是本文最有价值的一条,也是我们返工最久的一条。

talk 要真口型,走 MuseTalk 之类的口型引擎离线渲染,没问题。

idle 千万不要走引擎。 即使你喂给引擎的是一段纯静音,它也会用 UNet 在嘴部区域合成一层"微微开合"的嘴贴上去。我们实测过:

素材嘴部区域帧间差(MAD)用户观感
引擎输出的"静音待机"4.20嘴巴一直在轻轻动,像在无声说话
源片(纯头像帧循环)0.53静止自然

4.20 对 0.53,差了近 8 倍。这个数字不是画质问题,是"这人是活的还是抽风的"问题。结论很干脆:idle 直接用头像帧做循环短片,不要过引擎。

三、构图:画面里不该出现的东西,用构图解决

这一条来自一次真实投诉。客户看到的是"视频变小了、下面没到底、两腿中间有个黑三角"。

量一下源图就明白了:

kiosk 是竖屏 1080×1920,CSS 用 object-fit: cover 铺满。这意味着源图里多留的每一像素黑边,都会原样放大成用户眼里的"视频没铺满"。

我们第一次的修法是逐行取腿内侧像素颜色去补缝。结果是留下横向条纹和暗带,被直接否掉,原话是"这个解决方式不对"。

正解是把画面裁到裙摆为止:人物放大到占宽约 83%、占高约 96.5%、底部留黑归零。腿和黑三角直接不在画面里了,一步到位、零痕。

裁切点怎么定?不用肉眼估:扫描画面,找到"某一行在两条腿之间出现 ≥15 个近黑像素"的第一行,那一行就是裙摆下沿。

反过来的教训:我写的"腿间暗缝检测器"在已经裁好的图上仍然报 0.43。原因是裙摆的深棕色本身亮度就低于阈值,被误判成缝。单一数值不能当验收判据,必须目视确认。我们后来把验收改成两条:数值在阈值内 且 截图人工过一眼。

四、改人设:共 8 处,漏一处就"人格分裂"

fork 最容易漏的不是代码逻辑,是散落各处的身份文本。我们实测过最尴尬的一次:用户说"你好",数字人回了问候语,但自称的还是原来那个名字——因为问候语是快捷返回,根本没经过大模型,所以提示词改得再干净也没用。

必须逐处改完的清单:

#位置漏改后的症状
1大脑的人设常量(整段)回答口吻是旧人设
2问候语快捷返回说"你好"时自称旧名
3自我介绍快捷返回问"你是谁"时答旧名
4兜底回复模板识别失败时冒出旧名
5意图分类提示词分类跑偏,答非所问
6领域关键词表专业问题被判成闲聊
7后端系统提示词 + mock 兜底调试模式下露出旧人设
8唤醒词表 + 页面标题叫新名字没反应

收尾必做一步:grep -n '旧人名' backend/*.py,输出必须为空。这一步比任何测试都快。

还有一个隐蔽项:知识库文件名也要跟着改。只把目录路径 rebase 过去、KB 文件名还留着旧名,启动不报错、对话也通,但知识永远加载不上——日志里只会给你一句 Knowledge not found,很容易被当成"知识库内容不够"。

五、没麦克风也能测:往日志里追一行

这套架构的对话链路是:

浏览器麦克风 → 前端 WebSocket → ASR → 往日志写"[听] 文字" → 对话进程 tail 这一行 → 生成回复 → 调 TTS → 前端播音频并切 talk 视频。

注意中间那步:大脑是靠 tail 日志工作的。所以没有麦克风、没有浏览器、甚至没有声卡,都能跑通一轮完整问答——直接往日志追加一行就行:

echo '[听] 你们公司是做什么的' >> ~/<项目>/<日志文件>

然后看日志里的 TTS reply。这一招在无显示器的节点上排障极快,能立刻区分"是 ASR 没识别"还是"大脑没回答"。

两个配套的坑:

六、公网链接:不要把会变的地址发出去

临时隧道(quick tunnel)的域名是随机的,进程一重启就换一个。把这种地址发给客户,等于埋一颗定时炸弹:某天隧道崩了自动重启,客户收藏的链接就 404 了,而你还不知道。

我们的做法是加一层固定前门:

  1. 隧道本身上 systemd(Restart=always + 开机自启),崩了自动拉起并换新地址;
  2. 一个 5 分钟跑一次的同步器,从隧道日志里取最新域名,向 Pages 项目上传一条 /* <新隧道>/:splat 302 的重定向;
  3. 域名没变就不重发(幂等),对外永远是一个固定的 https://<项目>.pages.dev。

这样隧道换址不需要改任何代码,也不需要重新发链接给客户。成本几乎为零,收益是"链接不会死"。

七、验收:一句 healthz 分辨真假链路

服务起来了不等于链路通了。判断标准只有一个:

curl -sk https://127.0.0.1:<端口>/healthz
# {"ok":true,"mode":"real",...}  ← 真链路
# {"ok":true,"mode":"mock",...}  ← 假链路,助手没加载,回答全是模板

mode 是 mock 的时候,一切"看起来正常":页面能开、能说话、有回复。但回复是写死的模板。不看这个字段就上线,等于交了一个演示壳。

八、一次 fork 的真实工作量

把上面的清单固化之后,复制一个新形象的耗时分布大致是:

环节主要成本可复用度
代码副本 + 路径 rebase低,纯字符串替换高,脚本化
人设 8 处改写中,靠清单不漏中,每次都要写文案
知识库撰写与调优高,决定答得准不准低,与业务强相关
idle / talk 素材高,渲染+构图+循环校验中,工具链可复用
端口/服务/证书/隧道低,照抄上一套高,脚本化

结论:代码复用率能到 90%,真正花时间的是素材和知识库。所以评估一个数字人项目报价时,按"形象资产 + 知识库"算,而不是按"部署几套代码"算——这两件事的工作量不在一个量级。

九、这套东西适合谁

它适合固定场景、话术相对收敛、要长期开机的接待场景:门店迎宾、展厅讲解、政务大厅引导、售后答疑。它的优势是不吃显卡、单机可跑多实例、离线可用;代价是它不是通用聊天机器人,知识库边界之外的问题仍会绕回兜底话术。

如果你的场景是"用户会问各种开放问题",那要解决的不是 kiosk,是知识库与兜底策略——那是另一件事,别混在一起评估。

需要现成的清单?我们把上面所有验收项做成一页可打印的检查表,免费下载:数字人 kiosk fork 验收清单(12 项)。需要按你的形象和人设落地一套,走 服务页 的"数字人多形象部署"档。