同一套代码,跑出了三个能对话的数字人:一个门店接待、一个客服、一个展厅讲解。它们不是三份工程,而是同一份 fullbody_dh 代码的四个副本,各自换形象、换人设、换知识库、换端口。
这件事听起来像"复制粘贴改个名",实际 fork 过的人都知道:改完能启动不代表能用,能对话不代表像本人,像本人不代表链接能发出去。下面是我们把这套流程做顺之后沉淀的判据与坑,按"改代码 → 换素材 → 对人设 → 通公网"四段写。
很多人以为数字人必须占一张显卡。这套 kiosk 的实测是:跑着接待时 GPU 占用率 0。原因是它根本不做实时推理,而是"预渲染视频循环 + 独立音频":
| 组件 | 作用 | 技术要求 |
|---|---|---|
idle 视频 | 没人说话时的常驻画面 | 1080×1920、首尾无缝、无音轨 |
talk 视频 | 说话时的口型画面 | 1080×1920、必须 pingpong(正放+倒放拼接)、无音轨 |
| 音频 | 真正的声音 | TTS 独立输出,与视频解耦 |
前端靠 talk 视频时长的一半作为折返点,一句话说到尾就倒着播回来,这样循环永远接得上。所以 talk 如果不是 pingpong 素材,你会看到"说到一半突然跳回开头"的抽搐感——这是 fork 时最常见的低级错误,一眼能看出来。
欢迎词不需要单独的欢迎视频:它走 /api/speak 出声,画面仍然是 idle。少一段素材,少一个出错点。
这是本文最有价值的一条,也是我们返工最久的一条。
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 个近黑像素"的第一行,那一行就是裙摆下沿。
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 没识别"还是"大脑没回答"。
两个配套的坑:
>> 会 Permission denied,必须带 sudo;StandardOutput 和大脑 tail 的路径必须是同一个文件。fork 时只改了一处,就会出现"ASR 明明听到了、大脑毫无反应"的灵异现象。临时隧道(quick tunnel)的域名是随机的,进程一重启就换一个。把这种地址发给客户,等于埋一颗定时炸弹:某天隧道崩了自动重启,客户收藏的链接就 404 了,而你还不知道。
我们的做法是加一层固定前门:
Restart=always + 开机自启),崩了自动拉起并换新地址;/* <新隧道>/:splat 302 的重定向;https://<项目>.pages.dev。这样隧道换址不需要改任何代码,也不需要重新发链接给客户。成本几乎为零,收益是"链接不会死"。
服务起来了不等于链路通了。判断标准只有一个:
curl -sk https://127.0.0.1:<端口>/healthz
# {"ok":true,"mode":"real",...} ← 真链路
# {"ok":true,"mode":"mock",...} ← 假链路,助手没加载,回答全是模板
mode 是 mock 的时候,一切"看起来正常":页面能开、能说话、有回复。但回复是写死的模板。不看这个字段就上线,等于交了一个演示壳。
把上面的清单固化之后,复制一个新形象的耗时分布大致是:
| 环节 | 主要成本 | 可复用度 |
|---|---|---|
| 代码副本 + 路径 rebase | 低,纯字符串替换 | 高,脚本化 |
| 人设 8 处改写 | 中,靠清单不漏 | 中,每次都要写文案 |
| 知识库撰写与调优 | 高,决定答得准不准 | 低,与业务强相关 |
| idle / talk 素材 | 高,渲染+构图+循环校验 | 中,工具链可复用 |
| 端口/服务/证书/隧道 | 低,照抄上一套 | 高,脚本化 |
结论:代码复用率能到 90%,真正花时间的是素材和知识库。所以评估一个数字人项目报价时,按"形象资产 + 知识库"算,而不是按"部署几套代码"算——这两件事的工作量不在一个量级。
它适合固定场景、话术相对收敛、要长期开机的接待场景:门店迎宾、展厅讲解、政务大厅引导、售后答疑。它的优势是不吃显卡、单机可跑多实例、离线可用;代价是它不是通用聊天机器人,知识库边界之外的问题仍会绕回兜底话术。
如果你的场景是"用户会问各种开放问题",那要解决的不是 kiosk,是知识库与兜底策略——那是另一件事,别混在一起评估。