跳到主内容
快讯直播
AI智模界
教程

AI 生成儿童绘本:文字+插图一条龙

这套流程的目标很具体:输入一个主题(比如"小狐狸第一次坐火车"),产出一个可以直接打印或上传的绘本文件——12 页左右,每页一张插图配一到两句中文,主角从头到尾长得一样,文字量适合 3~6 岁亲子阅读。

整条链路分成四段:定结构 → 写文字 → 做插图 → 排版成 PDF。四段之间用文件传递,任何一段不满意都可以单独重跑,不必从头再来。下面按顺序走一遍。

前置条件清单

开始前确认这几样东西:

  • 一个能调用文本模型的 API 账号(用于写故事、拆页、生成提示词)
  • 一个能调用图像生成模型的 API 账号(用于出插图)
  • Python 3.9 以上环境,能执行 pip install requests playwright
  • 一台能装中文字体的机器(排版用)
  • 一个空文件夹作为项目目录,后面所有文件都放里面

关于接口地址、模型名、计费方式,各家差异不小,以官方文档当前版本为准。下面代码里用环境变量占位,换成自己的即可。

```bash

mkdir picturebook && cd picturebook

python -m venv .venv

source .venv/bin/activate # Windows 用 .venv\Scripts\activate

pip install requests playwright

python -m playwright install chromium

```

第一步:先定结构,别急着写故事

很多人一上来就让模型"写一个绘本故事",结果拿到 800 字散文,没法分页。正确做法是先定"页数 × 每页字数"这个骨架。

给一个 12 页的骨架:第 1 页是引入,第 2~10 页是情节推进,第 11 页冲突解决,第 12 页收尾。每页文字控制在 15~35 个汉字,这是 3~6 岁孩子一页的注意力容量。

同时要定一份"角色设定卡",这是后面插图一致性的命根子。写成固定字符串,每次调用图像模型都带上它,不改一个字:

```json

{

"character": "一只圆脸小狐狸,橙色毛发,白色肚皮,脖子上系一条蓝色针织围巾,尾巴尖是白色",

"style": "儿童绘本插画风格,柔和水彩质感,暖色调,圆润线条,背景简洁不杂乱",

"negative": "不要出现任何文字、水印、签名、恐怖元素、尖锐牙齿、写实照片风格、知名卡通角色"

}

```

把这段存成 setting.json,后面脚本读它。

第二步:让模型按 JSON 输出分页文案

关键点是强制结构化输出。不要让它自由发挥格式,否则解析一次崩一次。

```python

make_story.py

import json, os, requests

API_BASE = os.environ["LLM_API_BASE"] # 形如 https://<服务商域名>/v1

API_KEY = os.environ["LLM_API_KEY"]

MODEL = os.environ["LLM_MODEL"] # 具体模型名以官方文档当前版本为准

SYSTEM_PROMPT = """你是一位儿童绘本作者,为 3-6 岁孩子写作。

要求:

1. 每页文字 15-35 个汉字,一到两句话,口语化,可朗读。

2. 不出现暴力、惊吓、危险动作示范、品牌名和真实人名。

3. 输出严格的 JSON,不要额外解释。

JSON 结构:

{"title":"书名","pages":[{"page":1,"text":"本页文字","scene":"本页画面描述(中文,40字内)"}]}

"""

def make_story(idea: str, pages: int = 12) -> dict:

payload = {

"model": MODEL,

"messages": [

{"role": "system", "content": SYSTEM_PROMPT},

{"role": "user", "content": f"主题:{idea}\n总页数:{pages}"},

],

"temperature": 0.85,

"response_format": {"type": "json_object"}, # 字段名以官方文档为准

}

resp = requests.post(

f"{API_BASE}/chat/completions",

headers={"Authorization": f"Bearer {API_KEY}",

"Content-Type": "application/json"},

json=payload, timeout=180,

)

resp.raise_for_status()

content = resp.json()["choices"][0]["message"]["content"]

return json.loads(content)

if __name__ == "__main__":

story = make_story("小狐狸第一次坐火车去海边")

with open("story.json", "w", encoding="utf-8") as f:

json.dump(story, f, ensure_ascii=False, indent=2)

print("已生成", len(story["pages"]), "页")

```

跑完得到 story.json这一步一定要自己读一遍,改掉拗口的句子、改掉不合适的词。模型写的故事通常结构没问题,但语感偏书面,人工顺手改两句,朗读效果会好很多。

第三步:加一道自动校验

别靠眼睛一页页数。写个检查脚本,把硬性规则交给代码:

```python

check.py

import json, sys

story = json.load(open("story.json", encoding="utf-8"))

problems = []

if len(story["pages"]) < 8:

problems.append("页数太少,不适合做绘本")

for p in story["pages"]:

n = len(p["text"])

if n > 40:

problems.append(f"第{p['page']}页文字 {n} 字,偏长")

if "scene" not in p or len(p["scene"]) < 8:

problems.append(f"第{p['page']}页缺少画面描述")

banned = ["血", "打死", "可怕", "恐怖"]

for p in story["pages"]:

for w in banned:

if w in p["text"]:

problems.append(f"第{p['page']}页出现敏感词:{w}")

print("\n".join(problems) if problems else "校验通过")

sys.exit(1 if problems else 0)

```

第四步:把画面描述拼成插图提示词

这一步做的是"翻译":把中文画面描述 + 角色设定卡 + 风格词,拼成图像模型能吃的提示词。角色卡每次原样带上,只换场景部分。

```python

make_prompts.py

import json

story = json.load(open("story.json", encoding="utf-8"))

cfg = json.load(open("setting.json", encoding="utf-8"))

for p in story["pages"]:

prompt = (

f"{cfg['character']}。场景:{p['scene']}。"

f"画面风格:{cfg['style']}。"

f"禁止:{cfg['negative']}。"

f"同一本书内角色外观保持一致。"

)

p["prompt"] = prompt

json.dump(story, open("story_with_prompts.json", "w", encoding="utf-8"),

ensure_ascii=False, indent=2)

print("提示词已生成")

```

如果所用平台支持固定随机种子(seed)、参考图上传或角色一致性开关,把参数一起写进配置里。这是让 12 张图里狐狸长得像同一只狐狸的主要手段。平台不提供这些能力时,靠的就是角色卡描述足够细——颜色、材质、配饰写死,不要写"可爱的小动物"这种模糊词。

第五步:批量出图

先出 2 页样张,确认风格和角色满意,再跑全量。图片按张计费,批量重跑代价不小。

```python

make_images.py

import base64, json, os, requests, time

API_BASE = os.environ["IMAGE_API_BASE"]

API_KEY = os.environ["IMAGE_API_KEY"]

IMG_MODEL = os.environ["IMAGE_MODEL"] # 以官方文档当前版本为准

os.makedirs("images", exist_ok=True)

story = json.load(open("story_with_prompts.json", encoding="utf-8"))

def gen(prompt: str, out_path: str, size: str = "1024x1024"):

payload = {"model": IMG_MODEL, "prompt": prompt, "size": size, "n": 1}

r = requests.post(

f"{API_BASE}/images/generations",

headers={"Authorization": f"Bearer {API_KEY}",

"Content-Type": "application/json"},

json=payload, timeout=300,

)

r.raise_for_status()

item = r.json()["data"][0]

if item.get("b64_json"):

data = base64.b64decode(item["b64_json"])

open(out_path, "wb").write(data)

else:

url = item["url"] # 返回字段以官方文档为准

img = requests.get(url, timeout=300).content

open(out_path, "wb").write(img)

for p in story["pages"]:

out = f"images/page_{p['page']:02d}.png"

if os.path.exists(out):

continue

gen(p["prompt"], out)

print("完成", out)

time.sleep(1) # 温和限速,避免触发频率限制

```

脚本里做了断点续跑:已存在的文件跳过,中途报错重跑不会浪费已出的图。

第六步:排版成 PDF

用 HTML 排版再用浏览器转 PDF,中文换行和字体控制比直接画图省事。先生成 HTML:

```python

build_html.py

import json, html

story = json.load(open("story_with_prompts.json", encoding="utf-8"))

pages = []

for p in story["pages"]:

pages.append(f"""

<section class="page">

<img src="images/page_{p['page']:02d}.png" alt="">

<p class="text">{html.escape(p['text'])}</p>

<span class="num">{p['page']}</span>

</section>""")

doc = f"""<!doctype html>

<html lang="zh-CN"><head><meta charset="utf-8">

<title>{html.escape(story['title'])}</title>

<style>

@page {{ size: 210mm 210mm; margin: 0; }}

html, body {{ margin: 0; padding: 0; }}

body {{ font-family: "Noto Sans SC", "Source Han Sans SC", sans-serif; }}

.page {{ width: 210mm; height: 210mm; page-break-after: always;

position: relative; overflow: hidden; }}

.page img {{ display: block; width: 210mm; height: 150mm; object-fit: cover; }}

.text {{ font-size: 22pt; line-height: 1.7; margin: 0;

padding: 10mm 14mm; text-align: center; }}

.num {{ position: absolute; bottom: 6mm; right: 10mm;

font-size: 11pt; color: #999; }}

</style></head><body>{''.join(pages)}</body></html>"""

open("book.html", "w", encoding="utf-8").write(doc)

```

然后转 PDF:

```python

to_pdf.py

from playwright.sync_api import sync_playwright

import os

url = "file://" + os.path.abspath("book.html")

with sync_playwright() as pw:

browser = pw.chromium.launch()

page = browser.new_page()

page.goto(url, wait_until="networkidle")

page.pdf(path="book.pdf", width="210mm", height="210mm",

print_background=True, prefer_css_page_size=True)

browser.close()

print("已输出 book.pdf")

```

中文能不能正常显示,取决于系统里有没有装中文字体。Linux 服务器上常见情况是没装,装一个开源中文字体(思源系列、Noto Sans CJK 都可以)再跑,字体名称要和 CSS 里写的对应。字体用于商业出版前,先确认授权条款。

常见坑与排错

每页文字一长一短,排版参差。 根因是模型自由发挥。在系统提示里把字数写死成区间,再用第三步的校验脚本兜底,超长的让它重写那一页。

12 张图里主角换了三个样子。 三种解法按优先级:开启平台的参考图或角色一致性功能;把角色卡描述写到"颜色 + 材质 + 配饰 + 体型"四要素齐全;同一本书固定 seed。都做不到时,接受轻微差异,至少围巾颜色和主色调保持一致。

插图里出现莫名其妙的文字。 图像模型经常在画面里塞字母。在负面提示里明确写"不要出现任何文字、字母、水印",并且成书文字一律由排版层叠加,不指望图里生成字。

JSON 解析失败。 多数是模型多说了几句解释。让提示词强制"只输出 JSON",使用平台提供的 JSON 模式;再不稳就加重试:捕获异常后重发一次,或在提示里附上上次报错信息让它修。

接口报 429 或超时。 批量出图时最常见。给每张图之间加 sleep,请求失败做指数退避重试,别一次并发几十个请求。

打印出来颜色发灰、边缘被裁。 家用打印机直接打 A4 缩放即可,边距留足;正式送印需要出血和 CMYK 色彩空间,具体规格找印厂确认,别自己猜。

生成内容能不能商用。 各平台条款不同,以官方页面为准。另外注意别让角色外观贴近已有知名 IP,儿童读物领域这一点被追究的概率不低。

下一步建议

跑通一册之后,把流程产品化:

一,把 setting.json 升级成角色库。同一个主角换场景写第二、第三个故事,系列感立刻出来,也省掉重新调角色一致性的力气。

二,加配音。用文本转语音把每页文字读出来,配上翻页时间轴,就能出一支绘本视频,适合放在短视频渠道做引流。

三,把人工审校做成固定环节。机器出稿、人改文字、人挑图,三步里人至少占两步。儿童内容的容错空间比成人内容小得多。

四,保留全部中间产物。story.json、提示词、图片、参数都留在项目目录里,一年后要改版或出多语言版本时,改文字重跑就能对齐原有画面。

整条链路的价值不在"一次生成多快",而在于它可复用:骨架定了,换主题就是新书。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。