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

AI 生成表情包:从文字到可下载

这套流程能做出什么

输入一句主题,比如"一只戴圆框眼镜的橘猫,职场日常",跑完几个脚本之后,你会得到:

  • 一套 16 张风格统一的表情包,正方形、透明背景的 PNG;
  • 每张图带一句短配字,比如"收到""在改了""先撤了";
  • 一个 preview.html 预览页,双击就能在浏览器里看整组效果,想换哪张直接换;
  • 一个 dist/stickers.zip,可以直接发给同事,或者按平台要求提交审核。

整套流程分六步:出创意 → 批量出图 → 抠背景统一尺寸 → 叠中文配字 → 预览筛选 → 打包下载。中间任何一步断掉都能续跑,不用从头再来。

前置条件清单

开始之前确认这几件事:

1. Python 环境。装 Python 3.10 或更高(以官方文档当前版本为准),并会用 pip install。需要的库:requestspillownumpyopenai

2. 一个文本模型接口。用来把主题拆成结构化创意表。可以是云端 API,也可以是本地跑的模型服务,只要兼容 OpenAI 的 /chat/completions 格式即可。

3. 一个文生图接口。同样支持 /images/generations 的兼容接口,或者本地 Stable Diffusion / ComfyUI。模型名、调用方式以你所用服务的官方文档为准。

4. 中文字体文件。给表情叠字用,比如系统自带的黑体。注意商用场景要确认字体授权。

5. 目录约定。先建一个空目录,后面所有文件都放进去:

```bash

mkdir -p sticker-pack/{raw,out,dist}

cd sticker-pack

```

时间上,第一次跑通大概一两个小时,其中大半时间在等出图和调整提示词。

第一步:把一句话主题变成结构化创意表

直接让模型"生成 16 个表情包创意",结果往往很散。更好的做法是让它输出 JSON,字段固定,后面脚本才好处理。

generate_ideas.py

```python

import json

import os

from openai import OpenAI

client = OpenAI(

api_key=os.environ["LLM_API_KEY"],

base_url=os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),

)

SYSTEM = """你是表情包策划。根据用户给的主题,输出一组表情创意。

严格输出 JSON 对象,格式为 {"ideas": [...]},每个元素包含:

  • id: 两位数字字符串,从 "01" 递增
  • name: 动作名,4 字以内
  • scene: 适用场景,一句话
  • caption: 图上配字,不超过 6 个字,口语化
  • visual: 中文画面描述,写清主体动作和表情
  • prompt: 英文文生图提示词,只描述主体与动作,不要写风格词

不要输出任何解释文字。"""

def make_ideas(theme, n=16):

resp = client.chat.completions.create(

model=os.environ.get("LLM_MODEL", ""),

messages=[

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

{"role": "user", "content": f"主题:{theme}\n数量:{n}"},

],

response_format={"type": "json_object"},

temperature=0.8,

)

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

ideas = data.get("ideas", data) if isinstance(data, dict) else data

return ideas

if __name__ == "__main__":

ideas = make_ideas("一只戴圆框眼镜的橘猫,职场日常", 16)

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

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

print(f"生成 {len(ideas)} 条创意,已写入 ideas.json")

```

跑完后打开 ideas.json 通读一遍。重点看 caption 读起来像不像人话——"收到""在改了""这个我不太确定"这类才有传播力,书面语直接改掉。

第二步:固定风格锚点,批量出图

风格漂移是这类任务里最常见的翻车点:16 张图跑出 16 种画风。解决办法是把风格写成一段固定后缀,拼在每条 prompt 后面,全程不加一个字、不减一个字。

generate_images.py

```python

import base64

import json

import os

import time

from pathlib import Path

import requests

API_BASE = os.environ.get("IMAGE_BASE_URL", "https://api.openai.com/v1")

API_KEY = os.environ["IMAGE_API_KEY"]

MODEL = os.environ["IMAGE_MODEL"] # 填你所用服务文档里的模型名

STYLE = os.environ.get(

"STYLE_SUFFIX",

"flat vector sticker, thick clean outline, solid flat colors, "

"single centered subject, plain pure white background, "

"no text, no watermark, no shadow",

)

def gen_one(prompt, out_path, retries=3):

payload = {

"model": MODEL,

"prompt": f"{prompt}, {STYLE}",

"size": "1024x1024",

"n": 1,

}

for i in range(retries):

try:

r = requests.post(

f"{API_BASE}/images/generations",

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

json=payload,

timeout=300,

)

r.raise_for_status()

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

if item.get("b64_json"):

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

else:

blob = requests.get(item["url"], timeout=300).content

out_path.write_bytes(blob)

return True

except Exception as e:

print(f" 第 {i+1} 次失败:{e}")

time.sleep(3 * (i + 1))

return False

if __name__ == "__main__":

ideas = json.loads(Path("ideas.json").read_text(encoding="utf-8"))

for it in ideas:

out = Path("raw") / f"{it['id']}.png"

if out.exists():

print(f"跳过 {out}")

continue

ok = gen_one(it["prompt"], out)

print(f"{it['id']} {'完成' if ok else '失败'}")

time.sleep(1)

```

两个细节值得注意:一是已经存在的文件直接跳过,这样中途断网重跑不会重复花钱;二是失败重试带退避,接口偶发超时很常见。

第三步:抠背景、统一尺寸

拿到的图背景是白的,尺寸也可能不完全是正方形。用 Pillow 统一处理。

postprocess.py

```python

import numpy as np

from PIL import Image

from pathlib import Path

SIZE = 512

WHITE_THR = 242 # 高于这个亮度视为背景

def white_to_alpha(img):

img = img.convert("RGBA")

arr = np.array(img)

rgb = arr[..., :3].astype(np.int16)

near_white = (rgb > WHITE_THR).all(axis=-1)

arr[..., 3] = np.where(near_white, 0, arr[..., 3])

return Image.fromarray(arr, "RGBA")

def square_fit(img, size):

alpha = img.getchannel("A")

bbox = alpha.getbbox()

if bbox:

img = img.crop(bbox)

w, h = img.size

side = max(w, h)

pad = int(side * 0.08) # 四周留白,避免贴边

canvas = Image.new("RGBA", (side + pad * 2, side + pad * 2), (0, 0, 0, 0))

canvas.paste(img, (pad + (side - w) // 2, pad + (side - h) // 2), img)

return canvas.resize((size, size), Image.LANCZOS)

if __name__ == "__main__":

Path("out").mkdir(exist_ok=True)

for p in sorted(Path("raw").glob("*.png")):

img = white_to_alpha(Image.open(p))

square_fit(img, SIZE).save(Path("out") / p.name)

print("处理完成", p.name)

```

如果主体本身颜色很浅(白猫、白衬衫),纯阈值抠图会把主体一起抠掉。这时把 WHITE_THR 调高,或者换用专门的抠图模型/库,效果通常更好,具体用法看对应项目文档。

第四步:叠中文配字

不要指望文生图模型把中文画对,字让 Pillow 来叠。这样随时能改文案,也避免了错别字。

```python

from PIL import Image, ImageDraw, ImageFont

def add_caption(img_path, text, out_path, font_path, size=52):

img = Image.open(img_path).convert("RGBA")

w, h = img.size

draw = ImageDraw.Draw(img)

font = ImageFont.truetype(font_path, size)

draw.text(

(w // 2, int(h * 0.94)),

text,

font=font,

fill="white",

stroke_width=5,

stroke_fill="black",

anchor="mb",

)

img.save(out_path)

```

caption 和文件名对应上,循环调用即可。字太多就缩小字号,六字以内观感通常更好。

第五步:生成预览页,人工筛一遍

自动跑完不等于能用。写个脚本把所有成品拼成一张 HTML,逐张看:有没有多出手指、配字和画面对不上、风格跳脱。

```python

from pathlib import Path

import json

ideas = {i["id"]: i for i in json.loads(Path("ideas.json").read_text(encoding="utf-8"))}

cards = []

for p in sorted(Path("out").glob("*.png")):

cap = ideas.get(p.stem, {}).get("caption", "")

cards.append(f'<figure><img src="out/{p.name}"><figcaption>{cap}</figcaption></figure>')

html = f"""<!doctype html><meta charset="utf-8">

<title>表情包预览</title>

<style>

body{{background:#f5f5f5;font-family:sans-serif;padding:24px}}

.grid{{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;max-width:960px}}

figure{{margin:0;background:#fff;border-radius:12px;padding:12px;text-align:center}}

img{{width:100%;image-rendering:auto}}

figcaption{{margin-top:8px;font-size:14px;color:#333}}

</style>

<div class="grid">{''.join(cards)}</div>"""

Path("preview.html").write_text(html, encoding="utf-8")

```

不满意的单张,改 ideas.json 里对应的 prompt,删掉 raw/out/ 里的那张图,重跑第二步和第三步。不用整组重来。

第六步:导出可下载的包

不同平台对尺寸、体积、背景的要求不一样,具体规格以目标平台官方页面当前的要求为准。通用做法是准备两个尺寸:一个用于聊天展示,一个用于详情页或封面,都保留透明背景。

```bash

cd dist

cp ../out/*.png .

zip -r stickers.zip *.png

cd ..

```

打包完把 dist/stickers.zip 发出去,或者在本地解压查看。preview.html 也可以一起发,方便对方先看效果再决定要不要用。

常见坑与排错

风格不统一。 检查风格后缀是否每张都完全一致;如果还是漂移,固定随机种子,或者改用"参考图 + 图生图"的方式,让每张都以第一张为基准。

背景抠不干净,边缘一圈灰白。 阈值太宽或太窄。先把 WHITE_THR 在 235~250 之间调几次;边缘仍有明显白边,可以先对 alpha 通道做一次轻微腐蚀再输出。

中文被模型画成了乱码。 不要试图让文生图模型写字。把 no text 写进风格后缀,字全部交给第四步。

接口限流或超时。 加长重试间隔,把批量任务拆成几批跑;单张失败不要影响整组,脚本里的"跳过已存在文件"就是为这个准备的。

成本失控。 先只跑 2~3 张确认风格方向,再放量到 16 张。多数服务按张计费,具体单价看官方页面。

审核风险。 不要使用真人明星形象、受保护的角色 IP、品牌 logo,也不要生成涉及暴力、色情、政治敏感的内容。平台审核不过的话,返工成本很高。

字体授权。 叠字用的字体如果在商用表情包里分发,需要确认授权范围,免费商用字体和系统自带字体的可用范围不一样。

下一步建议

跑通一版之后,可以往这几个方向走:

1. 让角色稳定下来。 用同一张参考图或训练一个角色 LoRA,让"那只戴眼镜的橘猫"在第 30 张和第 3 张长得一样,这是系列表情能否持续做下去的关键。

2. 把配字抽成配置文件。 文案单独放一个 JSON,改字不用重跑图,几十秒就能出一版新文案。

3. 加上自动质检。 用文本模型对图和配字做一次匹配打分,把明显对不上的挑出来,人工只看剩下那几张。

4. 接进流水线。 用 Makefile 或 GitHub Actions 把六个步骤串起来,换个主题改一行参数就能出一套新表情。

5. 沉淀模板库。 把好用的风格后缀、动作清单、配字语气分类存下来,下一套作品直接复用,产出速度会明显不一样。

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