AI漫剧制 | 第 37 天:漫画翻译与本地化——AI辅助多语言发布

第 37/60 天

引言

当你的 AI 漫画在国内平台积累了第一批读者后,下一个增长曲线往往在海外。Webtoon、Manga Plus、Tapas 等平台上有大量华语创作者用 AI 工具批量产出的漫画,通过多语言发布触达全球读者。但横在面前的第一道坎就是语言:漫画是图文混合媒介,翻译不只是「把台词翻成外语」,还要处理画面里的文字、气泡布局、字体风格,以及跨文化的语境适配。

传统漫画本地化需要译者、嵌字师、修图师三个工种协作,单话成本高、周期长。而 AI 漫画本身以「批量、快速」为核心优势,如果本地化环节成为瓶颈,就与整个工作流背道而驰。好消息是:OCR 文字识别 + 大模型翻译 + 图像区域重绘 + 自动嵌字,这套「AI 本地化流水线」已经足够成熟,个人创作者完全可以在几小时内完成一话的多语言版本。

本文将带你从零搭建一套 AI 漫画翻译与本地化工作流,覆盖文字提取、机器翻译、去字重绘、嵌字排版、发布适配五个环节,并提供可直接复用的脚本与工具选型。

核心概念

什么是漫画本地化(Localization)

与「直译(Translation)」不同,本地化要考虑:
长度适配:日语/中文台词通常短,英语/德语普遍长 20-40%,气泡可能放不下
文化适配:谐音梗、方言、特定文化梗需要意译甚至改写
阅读习惯:日语/中文竖排或从左到右,阿拉伯语从右到左,可能需要镜像翻页
格式习惯:拟声词(SFX)在英文漫画中常用斜体手写体

三层处理模型

漫画画面中的文字可以分成三类,处理方式完全不同:

文字类型 例子 处理方式
台词(气泡内) 对话、内心独白 OCR → 翻译 → 去字 → 重嵌
画面内文字 招牌、报纸、屏幕 UI OCR → 翻译 → 区域内重绘(inpainting)
拟声词(SFX) 「轰!」「咚咚」 通常保留或音译,风格化处理

AI 本地化流水线的四步骨架

OCR 提取台词 → LLM 批量翻译 → 原图去字重绘 → 翻译文本嵌字排版

每一环都有对应的主流工具,下面实战步骤会给出完整可跑的方案。

实战步骤

第一步:环境准备(Shell)

先安装 OCR 引擎与 Python 依赖。漫画文字通常为印刷体,PaddleOCR 在中文、日文、英文混合场景表现优秀;tesseract 更轻量但多语言混合稍弱。

# 系统依赖(Ubuntu/Debian)
sudo apt-get update
sudo apt-get install -y tesseract-ocr 
  tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-eng 
  python3-pip

# Python 依赖
pip install paddleocr paddlepaddle 
  opencv-python-headless pillow 
  deep-translator requests

# 验证 OCR
python3 -c "from paddleocr import PaddleOCR; print('PaddleOCR OK')"

第二步:OCR 提取台词(Python)

对单页漫画做 OCR,输出带坐标的文本块 JSON。坐标是后面「去字重绘」和「重新嵌字」的关键输入。

# extract_text.py
import json
from paddleocr import PaddleOCR

ocr = PaddleOCR(lang="ch", use_angle_cls=True)  # 中文为主,可切 "japan" 等

def extract_page(image_path, out_json):
    result = ocr.ocr(image_path, cls=True)
    boxes, text_blocks = [], []
    for line in result[0]:
        if not line:
            continue
        box, (text, conf) = line[0], line[1]
        if conf < 0.6:          # 过滤低置信度
            continue
        boxes.append(box)
        text_blocks.append({
            "text": text,
            "conf": round(float(conf), 3),
            "box": box,          # 四个角坐标 [左上,右上,右下,左下]
        })
    with open(out_json, "w", encoding="utf-8") as f:
        json.dump({"image": image_path, "blocks": text_blocks},
                  f, ensure_ascii=False, indent=2)
    print(f"提取 {len(text_blocks)} 个文本块 -> {out_json}")

if __name__ == "__main__":
    import sys
    extract_page(sys.argv[1], sys.argv[2])
# 批量处理整个目录的页面
for img in pages/*.png; do
  out="/tmp/ocr/$(basename "${img%.*}").json"
  python3 extract_text.py "$img" "$out"
done

第三步:LLM 批量翻译(Python + JSON)

将 OCR 结果按「文本块 → 上下文段落」喂给大模型,带上气泡顺序与角色信息以保持连贯。这里以 OpenAI 兼容 API 为例,本地 Ollama 同理。

# translate.py
import json, sys, time
import requests

API_URL = "https://api.openai.com/v1/chat/completions"
API_KEY="REPLACE_ME"       # 用环境变量或密钥管理,勿硬编码真实密钥
TARGET_LANG = "English"

def translate_batch(blocks, lang=TARGET_LANG):
    # 把多个文本块打包成一个请求,减少调用次数
    lines = [f"[{i}] {b['text']}" for i, b in enumerate(blocks)]
    prompt = (
        f"你是一名专业漫画翻译。把下面标号的中文台词翻译成{lang},"
        "保持口语化和角色语气。输出 JSON:{"0": "...", "1": "..."}nn"
        + "n".join(lines)
    )
    resp = requests.post(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={
            "model": "gpt-4o-mini",
            "messages": [{"role": "user", "content": prompt}],
            "response_format": {"type": "json_object"},
            "temperature": 0.3,
        },
        timeout=60,
    )
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]

if __name__ == "__main__":
    data = json.load(open(sys.argv[1], encoding="utf-8"))
    translated = json.loads(translate_batch(data["blocks"]))
    for i, b in enumerate(data["blocks"]):
        b["translated"] = translated.get(str(i), b["text"])
    json.dump(data, open(sys.argv[2], "w", encoding="utf-8"),
              ensure_ascii=False, indent=2)
    print(f"翻译完成 -> {sys.argv[2]}")

第四步:原图去字重绘(Python + 配置)

用 Stable Diffusion 的 inpainting 功能把原台词区域「抹掉」并重绘背景。推荐 ComfyUI 或 AUTOMATIC1111 的 api/inpaint 接口,以 JSON 提交 mask(由 OCR 坐标膨胀得到)。

# inpaint_config.yaml
api_url: "http://127.0.0.1:7860/sdapi/v1/img2img"
model: "sd_xl_base"
denoising_strength: 0.65      # 去字区域重绘强度,太高会改背景
steps: 30
cfg_scale: 7
seed: -1                      # -1 随机
mask_expand_px: 8             # 坐标膨胀,覆盖描边
prompt: "clean empty background, same style, no text, no letters"
negative_prompt: "text, letters, words, watermark"
# inpaint_page.py
import base64, io, json, sys, yaml
import cv2, numpy as np
import requests

def build_mask(img, boxes, expand=8):
    mask = np.zeros(img.shape[:2], dtype=np.uint8)
    for box in boxes:
        pts = np.array([[int(x) for x in p] for p in box], np.int32)
        cv2.fillPoly(mask, [pts], 255)
    return cv2.dilate(mask, np.ones((expand, expand), np.uint8))

def inpaint(image_path, boxes, cfg, out_path):
    img = cv2.imread(image_path)
    mask = build_mask(img, boxes, cfg["mask_expand_px"])
    _, buf = cv2.imencode(".png", img)
    _, mbuf = cv2.imencode(".png", mask)
    payload = {
        "init_images": [base64.b64encode(buf).decode()],
        "mask": base64.b64encode(mbuf).decode(),
        "prompt": cfg["prompt"],
        "negative_prompt": cfg["negative_prompt"],
        "steps": cfg["steps"],
        "cfg_scale": cfg["cfg_scale"],
        "denoising_strength": cfg["denoising_strength"],
        "seed": cfg["seed"],
        "sampler_name": "Euler a",
    }
    r = requests.post(cfg["api_url"], json=payload, timeout=300).json()
    img_out = base64.b64decode(r["images"][0])
    open(out_path, "wb").write(img_out)
    print("去字完成 ->", out_path)

if __name__ == "__main__":
    cfg = yaml.safe_load(open("inpaint_config.yaml"))
    data = json.load(open(sys.argv[1]))
    inpaint(data["image"], [b["box"] for b in data["blocks"]], cfg, sys.argv[2])

第五步:自动嵌字排版(Python)

根据 OCR 坐标把翻译后的文字绘制回气泡位置,自动按宽高换行、缩放字号。这是保证「嵌字不溢出」的关键。

# embed_text.py
import json, sys
from PIL import Image, ImageDraw, ImageFont

def fit_font(draw, text, box_w, box_h, font_path, max_size=40):
    size = max_size
    while size > 10:
        font = ImageFont.truetype(font_path, size)
        w = draw.textlength(text, font=font)
        lines = wrap(draw, text, font, box_w)
        total_h = len(lines) * (size * 1.2)
        if w <= box_w and total_h <= box_h:
            return font, lines
        size -= 2
    return ImageFont.truetype(font_path, 10), wrap(draw, text, font, box_w)

def wrap(draw, text, font, max_w):
    lines, cur = [], ""
    for ch in text:
        if draw.textlength(cur + ch, font=font) > max_w:
            lines.append(cur); cur = ch
        else:
            cur += ch
    if cur: lines.append(cur)
    return lines

def embed(image_path, data, out_path):
    img = Image.open(image_path).convert("RGB")
    draw = ImageDraw.Draw(img)
    font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
    for b in data["blocks"]:
        xs = [p[0] for p in b["box"]]; ys = [p[1] for p in b["box"]]
        x0, y0, x1, y1 = min(xs), min(ys), max(xs), max(ys)
        box_w, box_h = x1 - x0, y1 - y0
        font, lines = fit_font(draw, b.get("translated", b["text"]),
                               box_w, box_h, font_path)
        y = y0
        for line in lines:
            w = draw.textlength(line, font=font)
            draw.text((x0 + (box_w - w) / 2, y), line, fill="black", font=font)
            y += int(font.size * 1.2)
    img.save(out_path)
    print("嵌字完成 ->", out_path)

if __name__ == "__main__":
    data = json.load(open(sys.argv[1]))
    embed(data["image"], data, sys.argv[2])
# 一键跑完整流水线
bash -x localize_pipeline.sh
# 内部依次执行:
# 1. python3 extract_text.py  pages/p01.png /tmp/ocr/p01.json
# 2. python3 translate.py     /tmp/ocr/p01.json /tmp/ocr/p01_tr.json
# 3. python3 inpaint_page.py  /tmp/ocr/p01_tr.json pages/p01_clean.png
# 4. python3 embed_text.py    /tmp/ocr/p01_tr.json pages/p01_clean.png out/p01_en.png

第六步:发布适配(Shell / JSON)

多语言版本通常需要独立标题、描述与标签。以 Webtoon 风格的平台 API 为例,用 JSON 提交多语言元数据。

{
  "series_id": "aiverse-001",
  "episode": 37,
  "locales": [
    {
      "lang": "zh-CN",
      "title": "AI漫剧制 | 第 37 天:漫画翻译与本地化",
      "description": "用 AI 流水线把漫画本地化为多语言。"
    },
    {
      "lang": "en-US",
      "title": "AI Manga Series | Day 37: Translation & Localization",
      "description": "Build an AI pipeline to localize comics into multiple languages."
    },
    {
      "lang": "ja-JP",
      "title": "AI漫画シリーズ | 第37日:翻訳とローカライズ",
      "description": "AIパイプラインで漫画を多言語化する方法。"
    }
  ],
  "assets": {
    "page_dir": "out/",
    "cover": "out/cover_en.png"
  }
}
# 本地校验译后文字是否溢出气泡(对比原始坐标面积)
python3 - << 'PY'
import json, sys
from PIL import Image, ImageDraw, ImageFont
data = json.load(open("/tmp/ocr/p01_tr.json"))
img = Image.open(data["image"]); d = ImageDraw.Draw(img)
f = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
overflow = 0
for b in data["blocks"]:
    xs = [p[0] for p in b["box"]]; ys = [p[1] for p in b["box"]]
    w, h = max(xs)-min(xs), max(ys)-min(ys)
    if d.textlength(b.get("translated", b["text"]), font=f) > w * 1.1:
        overflow += 1
print(f"疑似溢出文本块: {overflow}")
PY

常见问题

Q1:为什么不用 Google 翻译直接翻译全文?
A:Google 翻译缺乏漫画上下文(谁在说话、什么语气、是否有梗),容易翻出书面腔。大模型能结合整段气泡与剧情保持角色一致性;且 Google 翻译对短句口语的断句也不如 LLM 自然。

Q2:OCR 提取坐标不准怎么办?
A:三个方向:① 提高输入分辨率,漫画页建议 1500px 以上再 OCR;② 用 use_angle_cls=True 处理旋转文字;③ 提高置信度阈值到 0.7,宁可漏检再人工补,也不要错误框污染去字区域。

Q3:去字重绘会不会把背景也改坏?
A:控制 denoising_strength(0.5-0.7)与 mask_expand_px(6-10),并用负面提示词排除文字;对于大面积台词(如整页文本框),建议按气泡逐个 inpaint 而不是整块一次重绘,背景一致性更高。

Q4:英文翻译总是比原气泡长,嵌字溢出怎么办?
A:① 调用 fit_font 自动降字号;② 设置 lang 偏好缩写(如英文常用「don’t→dont」漫画体);③ 若仍然放不下,可在去字阶段把气泡适当放大一点(膨胀 mask 后重绘),给英文留出空间。

Q5:拟声词(SFX)需要翻译吗?
A:视平台而定。英文漫画通常保留原拟声词或改为英文手写体(如「咚→BOOM」),因为拟声词偏象形、改动成本高。建议用风格迁移模型(如 SD 的 ip-adapter)只替换 SFX 区域,保持手写笔触。

Q6:版权方面,翻译他人作品需要注意什么?
A:翻译本质是二次创作,若原作受版权保护,需要取得授权。对自己用 AI 生成、享有完整版权的漫画做本地化则没有障碍。同时注意:训练翻译专用模型的语料与平台条款需符合当地法律。

总结

  • AI 本地化流水线四步法:OCR 提取台词 → LLM 批量翻译 → 去字重绘 → 自动嵌字,个人创作者即可在几小时内完成一话多语言版。
  • 坐标是关键资产:OCR 输出的文本块坐标同时服务「去字 mask」和「嵌字定位」,一定要结构化保存成 JSON,贯穿整条流水线。
  • 上下文翻译优于逐句翻译:把整页/整话台词打包给 LLM 并带上角色信息,翻译质量与连贯性远高于逐句机翻。
  • 长度与文化适配是本地化核心:字号自适应、气泡扩容、文化梗意译,决定了海外读者体验是「看得懂」还是「读得爽」。
  • AI 漫画的多语言发布是天然的规模优势:内容可复制、成本接近零,善用 AI 流水线,一份创作就能吃透全球市场。

明天进入第 38 天,我们将探讨 AI 漫画的印刷与出版准备,为你的作品从屏幕走向实体铺路。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

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

    暂无评论内容