叙光:一个可自部署的 AI 漫剧分镜生成工具,以及角色一致性的三板斧
叙光:一个可自部署的 AI 漫剧分镜生成工具,以及角色一致性的三板斧
这是我在「赵强的AI产品实践」里记录的一个真实项目——
叙光 (Xuguang)。它不是又一款"输入文字直接出视频"的黑盒玩具,而是一个让创作者可控的漫剧分镜生产工具。
项目定位:不做黑盒,做可控的生产线
市面上的 AI 视频工具(即梦、可灵、各类 OpenMontage 类)大多是"全自动视频生成":你丢一个故事进去,它吐一段视频出来,中间发生了什么你完全干预不了。这对"玩一下"很爽,对"认真做内容"很难用——你没法保证主角的脸每一帧都一样,也没法在故事中途改一句台词重新生成那一帧。
叙光的设计取舍正好相反,核心定位是"创作者可控的分镜工具":
| 对比维度 | 同类工具(即梦/可灵) | 叙光 |
|---|---|---|
| 核心定位 | 全自动视频生成 | 创作者可控的分镜工具 |
| 分镜控制 | 黑盒生成,无法干预 | 分镜可查看、可编辑、可重新生成 |
| 角色一致性 | 依赖模型,难以保证 | 角色设定卡 + 固定 Seed + 角色参考图三层锁定 |
| 叙事性 | 画面机械对应文字 | 每帧 Prompt 含镜头语言、情绪氛围、叙事节拍 |
| 输出灵活性 | 直接出视频 | 分镜图、配音、字幕可单独导出 |
| 技术门槛 | 闭源 SaaS | 开源 / 可自部署 |
一句话总结产品哲学:把"确定性可控"留给创作者,"不可控的创造力"交给模型。
技术栈与架构
- 后端:PHP 8.2 + ThinkPHP 8
- 存储:MySQL(项目/分镜数据)+ Redis(队列与缓存)
- AI 能力:统一走硅基流动 API,按场景切模型:
- 文本/分镜脚本:
Qwen/Qwen2.5-7B-Instruct - 生图:
Kwai-Kolors/Kolors(hd/fast 模式) - 一致模式指令式编辑:
Qwen/Qwen-Image-Edit-2509 - 视频生成:
Wan-AI/Wan2.1-I2V-14B-720P-Turbo - 配音 TTS:
FunAudioLLM/CosyVoice2-0.5B
- 文本/分镜脚本:
核心数据实体只有两个:projects(一个故事项目)和 scripts(该项目下的每一帧分镜)。围绕它们组织出一组服务层模块:
app/common/
├── PromptBuilder.php # 叙事化分镜 Prompt 组装
├── CharacterManager.php # 角色一致性三板斧
├── AiHelper.php # 文本/生图统一调用封装
├── AudioHelper.php # 配音 TTS
└── VideoHelper.php # 图生视频
角色一致性的三板斧
做 AI 漫剧最致命的问题就是**"主角脸在飘"**——第一帧是黑长直,第三帧变成金卷发。叙光用三层机制把角色锁死:
第一斧:角色外观卡片
提交故事后,先用大模型从文本里抽取结构化角色设定(名字/物种/外貌/服装/特征),写入 projects.character_card,然后注入到每一帧的 Prompt 中。这样模型每画一帧都"记得"角色长什么样。
第二斧:固定 Seed
同一角色派生出同一个随机种子,保证全片画面的风格稳定(在 hd/fast 模式下生效)。这是成本最低、收益最高的一招。
第三斧:角色立绘参考图(Qwen-Image-Edit)
先为角色生成一张标准立绘 projects.character_sheet_url,后续每一帧都携带这张图做指令式编辑,从图像层面(而非仅靠文字)锁定角色外观。
代码里 CharacterManager 就是这个机制的实现:extractCard() 抽设定 → deriveSeed() 定种子 → generateSheet() 出立绘 → 后续帧用立绘做 localToDataUri() 注入编辑。
叙事化 Prompt:让画面服务故事
光角色一致还不够,如果每一帧只是"把文字翻译成画",故事就会很机械。叙光的 PromptBuilder 把每帧 Prompt 严格按权重顺序组装(build() 方法里一目了然):
1. 画风硬约束(权重最高,必须放最前)
2. 角色锁定(第二权重,简洁但强硬)
3. 场景内容 / 动作(叙事主体)
4. 表情
5. 镜头语言(景别 → 构图)
6. 情绪氛围(情绪 → 光影色调)
7. 角色设定重复强调(尾部再锚定一次)
8. 叙事节拍(第 N 帧 / 故事阶段)
这里有个反直觉但很关键的点:SD 系模型对 Prompt 前面的词权重更高,所以"画风硬约束"和"角色锁定"被放在最前面,从根源上压住画风漂移和角色走形;同时在 Prompt 尾部再用一句"再次确认角色外貌完全一致"做双重锚定。
buildEditPrompt()(一致模式)和 buildChainPrompt()(链式生成)共用这套叙事段,区别是:一致模式强调"严格保持参考图角色 + 重建场景",链式生成(第 N 帧拿第 N-1 帧当参考图)强调"与上一帧连续"。
一个真实的坑:硅基流动的 img2img 不认 strength
这是项目里最值的记录的一个技术踩坑。
我最初计划用 Kolors / Z-Image 的 img2img + 低 strength(比如 0.3)来"小幅改动角色立绘",保持角色外观、只换场景。结果实测发现硅基流动平台的 img2img 根本不接收 strength 参数——同一张图、0.3 和 0.95 出来的结果完全一样。更糟的是,参考图会"锁死"角色的姿势,导致所有帧里的角色都保持立绘那个站姿,"没有动作"。
解决方案是:一致模式改用 Qwen-Image-Edit-2509 做指令式编辑(AI_IMAGE_EDIT_MODEL 可配)。这个模型会读取参考图里的角色形象,同时按文字指令重建场景、动作、表情——既锁住了脸,又放开了动作。一行配置切换,效果天差地别。
三种生图模式与工程化细节
生图接口 POST /script/batchGenerateImages 提供三种模式,本质是"质量—成本"的三角权衡:
| 模式 | 说明 | 成本 |
|---|---|---|
consistent(默认推荐) | 先生成角色立绘参考图,每帧用 Qwen-Image-Edit 指令式编辑 | 最高 |
hd | 1024px,角色卡片 + 固定 seed + 完整叙事 Prompt(无参考图) | 中 |
fast | 512px,角色卡片 + 固定 seed + 完整叙事 Prompt | 低 |
工程化上值得一提的两点:
- 零迁移数据库:
projects表新增character_card/character_sheet_url,scripts表新增character_desc/emotion/prompt,首次调用生图接口时自动补齐字段,无需手工跑迁移脚本(DB_CHARSET需为utf8mb4)。 - 完整后台 RBAC:
admin路由组下包含用户、角色、权限、项目管理、发布管理和系统配置,前后端分离式鉴权(AdminAuth中间件)。
写在最后
叙光让我验证了一件事:在大模型能力还远不完美的当下,"可控性"比"全自动"更有产品价值。 用户要的不是被 AI 惊艳一次,而是能稳定、可迭代地做出自己想要的东西。角色一致性三板斧、叙事化 Prompt 的权重设计、以及对"strength 参数失效"这类平台特性的硬核排查,都是为了让"可控"真正成立。
项目已在 Gitee 开源(kuaoo/xuguang),支持 composer install 后自部署。如果你也在做 AI 内容生成方向,欢迎交流踩坑经验。
注:文中模型名称、接口路径均来自项目实际源码与公开 README,可对照仓库验证。