AI漫剧制 | 第 19 天:漫画分镜自动化——AI辅助页面布局

第 19/60 天

引言

一篇漫画是否”好看”,很大程度上取决于分镜(Storyboard)与页面布局(Panel Layout)。同样的台词和情节,格子怎么切、镜头怎么给、留白怎么安排,直接决定了读者的阅读节奏与情绪张力。然而,对于单打独斗的 AI 漫剧创作者来说,最耗时的往往不是”生成画面”,而是”把画面组织成一页书”:一格一格手动排版、反复调整比例、拼接出血线……几个小时就过去了。

好消息是,分镜恰恰是 AI 最容易”结构化”的环节。漫画分镜本质上是一套可量化的规则系统:页数、格子数量、格子比例、阅读顺序(Z 字形)、重点格子(Splash Panel)的放大规则——这些都能用脚本和模型自动化。本文将从”分镜的底层逻辑”讲起,手把手带你搭建一套 AI 辅助的分镜自动化流水线:用 LLM 生成分镜脚本 JSON → 用 Python 切割与排版画布 → 用 Stable Diffusion / ComfyUI 逐格生成 → 自动拼合为成品页面。

核心概念

1. 漫画页面布局的基本规则

漫画阅读顺序在东亚地区通常为从右到左、从上到下(日漫标准),欧美为从左到右。无论哪种,页面布局都遵循几条”铁律”:

规则 说明 违反的后果
格子数量控制 常规页 4~8 格,动作页可少、对话页可多 太密难读,太疏浪费篇幅
重点格放大 每 1~2 页安排一个重点格(Splash / 大格) 失去视觉锚点,节奏平淡
视线引导 相邻格子按阅读顺序自然衔接,避免”跳格” 读者走神、读错顺序
留白与出血 页边出血位(Blend)至少 3~5mm,内部留白均匀 印刷裁切后内容被切掉
网格对齐 格子边线尽量落在统一的网格线上 页面显得杂乱、廉价

AI 辅助布局的价值,就是把上面这些”经验值”转换成参数与约束,交给脚本强制执行,从而把人类的精力解放给创意本身。

2. 分镜自动化的三层架构

一个完整的 AI 分镜自动化流水线通常分为三层:

┌─────────────────────────────────────────────────┐
│  第 1 层:结构与脚本层(LLM)                     │
│  → 剧本/文戏 → 分镜脚本 JSON(格数、景别、对白)  │
├─────────────────────────────────────────────────┤
│  第 2 层:布局与画布层(Python/规则引擎)          │
│  → JSON → 页面网格坐标(绝对定位 + 阅读顺序)      │
├─────────────────────────────────────────────────┤
│  第 3 层:生成与拼合层(SD/ComfyUI + 后处理)      │
│  → 逐格出图 → 按蒙版/区域生成 → 拼图 + 气泡位     │
└─────────────────────────────────────────────────┘

第 1 层负责”想”,第 2 层负责”排”,第 3 层负责”画”。每一层都可以独立升级——比如你把第 3 层的模型从 SD 1.5 换成 SDXL,流水线骨架完全不用动。

3. 关键术语速查

  • Panel(格子):漫画页面上的一个画面单元。
  • Gutter(格沟):格子之间的空白间隔,通常 2~4mm。
  • Splash Panel(跨页/整页大格):占据整页或大半页的重点画面。
  • Bleed(出血):印刷时超出裁切线的部分,AI 生成时建议扩 5%~8%。
  • Camera/Shot(景别):远景(LS)、中景(MS)、特写(CU)等,是分镜脚本的核心字段。
  • Reading Order(阅读顺序):格子编号,决定读者视线路径,也是拼图时重排的依据。

实战步骤

第一步:环境准备

分镜自动化需要 Python 3.10+、Pillow 和 OpenAI 兼容的 LLM 接口(本地 Ollama 或云端 API 均可):

# 创建独立虚拟环境(避免污染系统 Python)
python3 -m venv ~/manga-pipeline
source ~/manga-pipeline/bin/activate

# 核心依赖:图像处理 + HTTP 请求 + 提示词渲染
pip install pillow requests jinja2 pyyaml

# 可选:调用本地 LLM 完成"文戏 → 分镜脚本"转换
# (只需 OpenAI 兼容接口,Ollama 亦可用)
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:14b   # 中文能力较强的本地模型

# 验证
python -c "from PIL import Image; print('Pillow OK')"

第二步:用 LLM 生成分镜脚本 JSON

把剧本片段喂给大模型,让它输出结构化分镜 JSON。关键是给出明确的字段约束(scene、shot、panel 数量、文本框内容),这样后续脚本才能直接消费。

{
  "page": 3,
  "title": "雨夜追凶",
  "layout": "6-panel",
  "reading_order": "rtl",
  "panels": [
    {
      "id": 1,
      "shot": "LS",
      "subject": "主角站在天桥,雨幕中望向远方的霓虹",
      "text": "",
      "grid": {"row": 1, "col": 1, "rowspan": 1, "colspan": 2}
    },
    {
      "id": 2,
      "shot": "CU",
      "subject": "主角紧握的拳头,雨水顺指缝滑落",
      "text": "……果然还是追过来了。",
      "grid": {"row": 1, "col": 3, "rowspan": 2, "colspan": 1}
    },
    {
      "id": 3,
      "shot": "MS",
      "subject": "反派从巷口现身,风衣被风掀起",
      "text": "你逃不掉的。",
      "grid": {"row": 2, "col": 1, "rowspan": 1, "colspan": 2}
    }
  ]
}

实际调用时,把上面的 JSON Schema 作为 response_format 或 few-shot 示例放进 prompt:

import json
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")

SCHEMA = """输出 JSON,字段:
page:int, title:str, layout:str,
panels: [{id:int, shot:LS|MS|CU|ECU, subject:str, text:str,
          grid:{row, col, rowspan, colspan}}]"""

script = "第三话开篇:雨夜,主角在天桥被反派截住……"
resp = client.chat.completions.create(
    model="qwen2.5:14b",
    messages=[
        {"role": "system", "content": "你是资深漫画分镜师,只输出合法 JSON。"},
        {"role": "user", "content": f"{SCHEMA}nn请为以下文戏设计一页 6 格分镜:n{script}"},
    ],
    temperature=0.7,
)
storyboard = json.loads(resp.choices[0].message.content)
print(f"生成 {len(storyboard['panels'])} 个分镜格,布局:{storyboard['layout']}")

第三步:Python 规则引擎——把 JSON 变成画布坐标

分镜 JSON 里的 grid 只是”相对网格”,要真正出图,必须换算成像素级的绝对坐标。下面这个脚本会把 6 格布局渲染成带编号的布局预览,并输出每格的裁切区域(给后续逐格生成使用):

from PIL import Image, ImageDraw, ImageFont

def grid_to_boxes(page_w, page_h, panels, gutter=40):
    """根据 grid 网格信息计算每格 (x1, y1, x2, y2) 区域,返回按阅读顺序排列的列表"""
    # 网格列:从布局中推导最大行列
    cols = max(p["grid"]["col"] + p["grid"]["colspan"] - 1 for p in panels)
    rows = max(p["grid"]["row"] + p["grid"]["rowspan"] - 1 for p in panels)
    col_w = (page_w - gutter * (cols + 1)) / cols
    row_h = (page_h - gutter * (rows + 1)) / rows

    boxes = []
    for p in sorted(panels, key=lambda x: x["id"]):
        g = p["grid"]
        x1 = gutter + (g["col"] - 1) * (col_w + gutter)
        y1 = gutter + (g["row"] - 1) * (row_h + gutter)
        x2 = x1 + col_w * g["colspan"] + gutter * (g["colspan"] - 1)
        y2 = y1 + row_h * g["rowspan"] + gutter * (g["rowspan"] - 1)
        boxes.append({"id": p["id"], "box": (x1, y1, x2, y2), "meta": p})
    return boxes

# 预览 800x1200 的 A4 比例页面
canvas = Image.new("RGB", (800, 1200), "white")
draw = ImageDraw.Draw(canvas)
for b in boxes_for_page:
    x1, y1, x2, y2 = b["box"]
    draw.rectangle([x1, y1, x2, y2], outline="black", width=4)
    draw.text((x1 + 10, y1 + 10), str(b["id"]), fill="red")
canvas.save("/tmp/layout_preview.png")
print("布局预览已保存:/tmp/layout_preview.png")

第四步:ComfyUI 节点式逐格生成

有了每格的坐标和 subject,下一步就是逐格出图。推荐用 ComfyUI 搭一个”分镜批处理”工作流:KSampler → VAE Decode → 按区域保存。关键节点配置用 YAML 表达(对应 ComfyUI 的 API 格式——每个节点是一个 dict):

# storyboard_workflow.yaml — ComfyUI API 格式(节选)
3:                                  # KSampler 节点
  class_type: "KSampler"
  inputs:
    seed: 42
    steps: 28
    cfg: 7.0
    sampler_name: "dpmpp_2m"
    scheduler: "karras"
    denoise: 0.85                 # 配合 img2img 保持角色一致
    model: ["4", 0]
    positive: ["6", 0]
    negative: ["7", 0]
    latent_image: ["10", 0]
6:
  class_type: "CLIPTextEncode"
  inputs:
    text: ""          # 每格动态注入:shot + subject 渲染的提示词(见第五步)
    clip: ["4", 1]

组合一套负向提示词模板,避免漫画中最常见的翻车:

negative: "blurry, lowres, bad anatomy, bad hands,
extra fingers, watermark, text, ugly, deformed,
photorealistic, 3d render, jpeg artifacts"

第五步:提示词模板渲染 + 批量生成循环

用 Jinja2 把 shot + subject 渲染成稳定的正向提示词,然后循环调用 ComfyUI 的 /prompt API。分镜自动化的核心技巧:每格共享同一段”风格前缀”,只替换画面主体——这是保证整页风格统一的关键:

import requests, json
from jinja2 import Template

STYLE = "black and white manga page, high contrast, screentone, clean lineart"
TPL = Template("""{{ style }}, {{ shot_desc }}, {{ subject }}, {{ extra | default('') }}""")

def build_prompt(panel, shot_map=None):
    shot_map = shot_map or {"LS": "wide shot", "MS": "medium shot",
                            "CU": "close-up", "ECU": "extreme close-up"}
    return TPL.render(style=STYLE,
                      shot_desc=shot_map[panel["shot"]],
                      subject=panel["subject"])

# 循环逐格提交 ComfyUI
wf = json.load(open("/tmp/storyboard_workflow.json"))
for panel in storyboard["panels"]:
    panel_id = panel["id"]
    positive, negative = build_prompt(panel), "blurry,bad anatomy,bad hands,watermark"
    # 将提示词注入工作流 JSON 的对应节点
    wf["6"]["inputs"]["text"] = positive
    wf["7"]["inputs"]["text"] = negative
    resp = requests.post("http://127.0.0.1:8188/prompt",
                         json={"prompt": wf, "client_id": f"panel-{panel_id}"})
    print(f"Panel {panel_id}: {resp.status_code} -> {resp.json().get('prompt_id')}")
    time.sleep(3)   # 节流,避免队列过载

第六步:拼版与出稿

所有格子生成完毕后,按第三步的坐标把图片裁剪并粘贴回画布,自动加上格沟(Gutter)与文本框占位(后续再接第 18 天讲的文字处理流程):

# 一键执行完整流水线(从分镜 JSON 到成品页面)
python manga_layout.py --script storyboard.json 
    --pages-dir ./generated_panels/ 
    --output ./final_pages/page_003.png 
    --gutter 40 --bleed 5%
# manga_layout.py 核心:拼版函数
from PIL import Image

def assemble(boxes, panel_images, canvas_size, gutter=40):
    canvas = Image.new("RGB", canvas_size, "white")
    for b, img in zip(boxes, panel_images):
        x1, y1, x2, y2 = b["box"]
        canvas.paste(img.resize((int(x2 - x1), int(y2 - y1))), (int(x1), int(y1)))
    return canvas

final = assemble(boxes_for_page, rendered_panels, (1600, 2400))
final.save("/tmp/page_003_final.png")

常见问题

Q1:逐格生成的风格总是不统一怎么办?
把”风格前缀”作为每格提示词的固定常量(如 black and white manga, screentone, clean lineart),并让所有格子共享同一个 LoRA(如日漫风格 LoRA)。进阶做法是用 img2img 以”上一页最后一个格子”为底图做低 denoise 重绘(0.5~0.6),画面会像”接力”一样连贯。

Q2:角色在不同格子之间长相差很多,如何处理?
分镜自动化只解决”布局”,角色一致性需要 IP-Adapter / InstantID / 角色 LoRA 配合(这正是本系列第 5、13、14 天讲的内容)。流水线中应该为每个分镜格传入”角色参考图”作为 ControlNet/Image 输入节点,而不是只靠文字描述。

Q3:LLM 输出的分镜 JSON 格式不稳定,经常报错?
三重防御:① 在 prompt 里给 1~2 个 few-shot 完整 JSON 示例;② 用 response_format={"type": "json_object"}(OpenAI 系接口支持);③ 解析失败时降级——用正则提取 "panels": [...] 片段再 json.loads,仍然失败就让脚本按默认 6 格模板兜底,绝不让流水线中断。

Q4:格子数量怎么定才合理?
对话戏一页 6~8 格,动作戏 4~5 格,情绪大场面直接 1 个大 Splash。经验法则:”每页至少 1 个重点格,任何一格放不下对白就砍格数”。可以把这条规则写进第 2 层的校验器——生成布局后自动检查 Splash 缺失/格子过密 并报警告。

Q5:拼出来的页面总感觉”格子边缘对不齐”?
所有格子必须在同一套网格坐标系下计算(本文第三步),不要各自独立缩放。拼版时统一按”格沟 40px + 计算后尺寸”粘贴;若结果仍有 1~2px 误差,把 gutter 设成 4 的整数倍(打印分辨率 300dpi 下 4px ≈ 0.34mm),并在最终一步做全画布轻微整数缩放。

Q6:分镜脚本自动生成会吞掉我的创意吗?
不会,它吞掉的是”机械化劳动”。景别、构图、翻页节奏这类创意决定仍然由你把控——你只需要把 LLM 的分镜当成”第一稿”,在 JSON 上改格子布局比在白纸上重画快一个数量级。

总结

  1. 分镜是可编程的:漫画页面布局本质是一套网格 + 编号 + 比例规则,完全可以交给 LLM 出 JSON、Python 排坐标、SD 逐格出图的自动化流水线。
  2. 三层架构各司其职:脚本层(想)→ 布局层(排)→ 生成层(画),任何一层都可以独立替换升级,这是 AI 漫剧制作效率的关键。
  3. 风格统一靠”常量 + 共享参考”:整页共享风格前缀、同一 LoRA、img2img 接力,比逐格手调提示词可靠得多。
  4. 校验规则要内置:Splash 缺失、格子过密、坐标越界这类低级错误交给脚本自动拦截,让你的精力始终留在讲故事上。
  5. 下一站是文字层:页面拼好了,接下来就是气泡、拟声词与排版的战场——把这套流水线接到文字处理模块,你就拥有了接近”全自动”的 AI 漫画生产线。

下期预告:第 20 天《AI漫画风格迁移:从日漫到美漫》,我们将对比风格 LoRA、模型合并与 CFG 调整三种风格迁移路线,并给出跨风格批量迁移的实战配方。


📚 AI漫剧制 60 天系列目录(每日更新):

  • 第 1 天:AI漫剧制是什么?从概念到行业全景
  • 第 2 天:AI漫画生成工具全景对比
  • 第 3 天:从小说到漫画:AI漫剧制完整工作流
  • 第 4 天:提示词工程:AI漫画分镜的基石
  • 第 5 天:角色一致性:AI漫画最难攻克的技术
  • 第 6 天:画面构图:用AI控制镜头语言
  • 第 7 天:AI漫画的版权问题与法律边界
  • 第 8 天:漫剧制 vs 传统漫画:效率与艺术的平衡
  • 第 9 天:漫画脚本自动生成:AI编剧入门
  • 第 10 天:Stable Diffusion WebUI 漫画工作流搭建
  • 第 11 天:ComfyUI 节点式漫画工作流入门
  • 第 12 天:ControlNet 在漫画中的应用:姿态控制与构图
  • 第 13 天:LoRA 训练:打造专属漫画角色
  • 第 14 天:IP-Adapter 实现角色一致性
  • 第 15 天:AI漫画上色工作流:从线稿到彩色
  • 第 16 天:AI漫画文字处理:气泡、拟声词与排版
  • 第 17 天:AI漫画背景生成:场景与特效
  • 第 18 天:AI漫画的叙事结构:如何让AI讲好故事
  • 第 19 天:漫画分镜自动化:AI辅助页面布局(🟢 今日)
  • 第 20 天:AI漫画风格迁移:从日漫到美漫
  • ……(共 60 天,持续更新中)
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容