Spine 模型超分
因为本人苦于这个spine太糊,想要让它变清晰,自己随便找个超分模型效果又不好,于是去寻求大肥鱼帮我超分图片得到了以下经验,在站上找好像又没有这一类的经验,于是发出来给大伙省token,具体一切操作由ai进行,本人一窍不通,有问题问ai。
超分前 自己用超分模型超分图片后直接载入spine模型,会有如上图的零件边线 ai一番操作,超分后使用的spine显示软件为Live2dviewerEX,配置json的选项里有个着色器,如果显示泛白可以选Straight Alpha着色器,一般默认是不带括号的
从「为什么糊」到「怎么修好」,包含选型逻辑、六个坑的原理、和一套能直接跑的流程。
全部结论来自实测(figure_1978326 个零件、风织香彩夏夜引光457 个零件)。
0. 先看结论
| 你可能遇到的问题 | 真正的原因 | 怎么解决 |
|---|---|---|
| 模型整体发糊、放大更糊 | atlas 声明 4096,贴图 PNG 只有 2048 | 把 PNG 补到 4096,atlas 不用改 |
| 边缘一圈黑边 | 图集是预乘 alpha(PMA),但没按 PMA 处理 | 先反预乘 → 超分 → 再预乘 |
| 发丝出现紫色杂色、皮肤有白斑 | 相邻零件互相串色 | 按零件逐个处理(有间隙的图集) |
| 零件连接处有锯齿、硬阶梯 | ① 对 alpha 做了「收紧」 ② 无缝图集被按矩形切断 | alpha 别动;无缝图集改整图处理 |
| 头发渐变变成一块一块的 | 超分模型偏好赛璐璐风格,把渐变压平 | 混入一部分插值结果(--blend 0.2) |
| 黑色纱网/条纹变粗或消失 | 1px 周期纹理被模型当噪声处理 | 该零件改用 NEAREST 放大保频率 |
一条命令跑完(大部分模型都适用):
chaofen\spine_upscale_kit
python spine_upscale.py --dir "你的模型目录" --out "输出目录"
1. 为什么你的 Spine 模型是糊的
1.1 一句话版本
Spine 只看 .atlas 里写的尺寸,不看你的图片实际多大。 尺寸对不上,图就被硬拉,越拉越糊。
1.2 具体是怎么回事
把 .atlas 文件用记事本打开,开头通常长这样:
figure_1978.png
size: 4096,4096
format: RGBA8888
filter: Linear,Linear
repeat: none
size: 4096,4096 是在告诉运行时:「这张图是 4096×4096 的,你按这个尺寸算坐标」。
然后再看你目录里的 figure_1978.png —— 右键属性一看,2048×2048。
于是运行时干的事就是:把一张 2048 的图,当成 4096 的用。等于在你每次显示的时候,先把图放大一倍,再去切零件。放大一倍靠的是显卡的双线性插值,结果就是糊。
打个比方:atlas 是地图,png 是实景照片。地图上标着「这一片区域 4096 米宽」,但你的照片只拍了 2048 米的范围,剩下的靠"想象"补 —— 当然糊。
1.3 所以修法很简单
把 PNG 补到 4096,atlas 一个字都不用改。
因为 atlas 里的坐标(每个零件的 xy、size)本来就是按 4096 空间的坐标系写的,你把图补到 4096,坐标刚好全对。
反过来,如果你去改 atlas 把 4096 改成 2048,那么所有坐标都要除以 2 —— 而且要处理旋转零件的宽高互换,非常容易出错。别动 atlas。
1.4 怎么快速确认自己的模型是不是这个情况
python spine_probe.py "你的模型目录"
输出里会直接告诉你:
atlas 声明尺寸 : 4096 x 4096
贴图实际尺寸 : 2048 x 2048
=> 需要放大倍数 : 2x (目标 4096x4096)
如果显示 1x,说明本来就没问题,糊的原因在别处(可能是渲染尺寸、缩放倍数、或者显示器)。
2. 需要准备什么
2.1 软件环境
| 组件 | 版本要求 | 说明 |
|---|---|---|
| Python | 3.10+ | 64 位 |
| torch | 2.x(CPU 版就行) | 不用装 CUDA,实测 CPU 跑 300~500 秒/模型 |
| Pillow / numpy / scipy | 任意较新版本 | scipy 只用到 distance_transform_edt |
安装:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
pip install pillow numpy scipy
为什么 CPU 就够? 一个 326 零件的图集,CPU 8 线程大约 7 分钟。装了 CUDA 能快 10 倍,但为了这几分钟去折腾驱动不值得。真正慢的是"来回试参数",不是算力。
2.2 工具包
chaofen\spine_upscale_kit\:
spine_probe.py 探测:结构 / 倍数 / PMA / 零件统计
spine_upscale.py 主程序:超分
spine_verify.py 质检:三背景合成 + 差异统计 + 特写
upscale_main.py 底层(反预乘、分块推理)
atlas_util.py atlas 解析
rrdb.py 网络结构定义
models/anime6B.pth 超分模型权重(17MB)
README.md 本教程的精简版
2.3 超分模型怎么选(很重要)
| 模型 | 能用吗 | 原因 |
|---|---|---|
| anime6B(Real-ESRGAN anime 6B) | 二次元专用,干净无幻觉,速度最快 | |
| RealESRGAN_x4plus | 会在平滑区域凭空画出文字状伪影(我实测出来的,一片皮肤上突然冒出一排"字") | |
| AnimeVideo v3 | 会把肤色洗白、发丝边缘生成紫色杂色 | |
| waifu2x | 保真好但细节提升有限,且权重格式不通用 |
为什么模型选择比参数调优重要 10 倍:参数只能微调风格,模型选错了会直接产生原图里根本不存在的东西(幻觉)。幻觉在静态图上看不出来,动起来会非常明显。
3. 第一步:探测(决定策略)
跑之前先探测,它会告诉你三件决定成败的事:
python spine_probe.py "D:\解包\sprine\风织香彩夏夜引光"
模型目录 : D:\解包\sprine\风织香彩夏夜引光
配置 json : 风织香彩夏夜引光.json
骨架 : Figure_2067.skel.asset (1901334 B)
atlas : Figure_2067.atlas.asset (50727 B)
贴图 : Figure_2067.png(2304830B)
shader_type: 0 (0=PMA着色器, 1=Straight Alpha着色器)
--- page 0: Figure_2067.png ---
atlas 声明尺寸 : 4096 x 4096
贴图实际尺寸 : 2048 x 2048
=> 需要放大倍数 : 2x (目标 4096x4096)
region 数量 : 457 (rotate=65)
覆盖率(按atlas坐标) : 57.9%
alpha 分布 : 全透明 69.5% 不透明 24.4% 半透明 6.1%
PMA 检测 : 是(预乘存储)
三个关键数字怎么读
① 需要放大倍数 — 就是 atlas 声明尺寸 ÷ 贴图实际尺寸。1x 不用处理,2x 是常态。
② PMA 检测 — 这是黑边的元凶。往下看第 5 节有详细解释。
③ alpha 分布 — 半透明占比越高,说明这个图集里"发光层"“纱”"渐变遮罩"越多,越需要小心处理 alpha 通道(不能收紧)。
4. 三种模式怎么选(核心)
这是整套流程里唯一需要你做判断的地方。
4.1 先看一张判断图
零件之间有间隙吗?(用 spine_upscale.py 的日志自动判断)
│
├─ 有间隙(零件边界是透明的)
│ └─ 用【逐零件模式 region】
│ 典型:figure_1978(只有 1.5% 的零件边界上有内容)
│
└─ 没间隙(零件紧贴,边界上就是不透明像素)
└─ 用【整图模式 whole】
典型:风织香彩夏夜引光(89.4% 的零件边界上都有内容)
--layout auto(默认)会自动算这个比例,超过 30% 就走整图模式,你不用管。
日志里会直接印出来:
无缝边界占比 89.4% -> 采用 whole 模式
4.2 为什么会有这个区别
打个比方:
- 有间隙的图集 = 一张桌子上摆了很多分开的贴纸。你把每张贴纸单独拿起来处理,互不干扰,完美。
- 无缝图集 = 一幅完整的拼贴画,画面本来是连续的,只是被 atlas 用矩形"框"成了很多块。
对第二种情况,如果你按矩形去剪,剪开的地方就是画面中间——你把画剪破了,放大后当然能看到裂口(锯齿)。
所以第二种必须整张一起处理。
4.3 两种模式各自的代价
| 逐零件 region | 整图 whole | |
|---|---|---|
| 优点 | 绝不会串色,边界干净 | 不会切断内容,画面连贯 |
| 缺点 | 无缝图集上会切断内容 | 有间隙的图集上,相邻零件颜色会渗到彼此的半透明边缘 |
| 内存 | 很小(一次一个零件) | 大一些(512 分块,块间有重叠) |
| 耗时 | 300~900 秒 | 200~300 秒 |
关键洞察:这两种模式不是"哪个更先进",而是针对两种不同的图集结构。选错了就会出现你遇到的那两种症状之一。
4.4 覆盖范围不够的情况
有些图集会有一小部分像素不属于任何零件(导出时溢出的边缘,或者 padding)。逐零件模式下这些像素会被漏掉,脚本会自动用插值补齐,日志里会写:
兜底填充未声明像素: 32446 px (0.77%)
0.77% 属于正常,如果超过 5% 就要怀疑 atlas 解析有问题了。
5. 六个坑(每个都讲清原理)
坑 1:PMA(预乘 alpha)—— 黑边的真正原因
现象:超分后所有零件边缘多了一圈黑色描边或"脏边"。
原理:
图片的 RGBA 里,RGB 是颜色,A 是透明度。存法有两种:
| 存法 | 半透明像素的 RGB 值 | 怎么合成到背景 |
|---|---|---|
| Straight(直通) | 存真实颜色(比如纯白 255) | 最终 = 颜色×alpha + 背景×(1-alpha) |
| PMA(预乘) | 存已经乘过 alpha 的颜色(纯白 53% 透明 → 存 135) | 最终 = 颜色 + 背景×(1-alpha) |
很多图集导出工具默认用 PMA 存(因为渲染更快),但 .atlas 里往往不写 pma:true。
于是超分模型看到的"透明区域"其实是 RGB=(0,0,0), A=0。模型不知道那是"没有东西",会把它当成黑色去处理,把黑色和边缘的彩色混在一起 —— 放大后就成了一圈黑边。
解决办法:
反预乘(把颜色还原成真实颜色)→ 超分 → 按需要再预乘回去
脚本自动做了这件事(探测显示 PMA 检测: True 就会走这条路)。
怎么判断自己遇到的是不是这个:看黑边是不是只有边缘一圈、而且颜色是灰黑偏冷。如果是四周一整片黑,那是别的问题(尺寸错、通道错)。
坑 2:零件串色 —— 杂色斑点的原因
现象:发丝边缘出现紫色/绿色的小杂色,皮肤上冒出白色斑块。
原理:图集是在一张大图上把几百个零件挤在一起铺,最小间隙可能只有 1~2 像素。你如果拿整张图去超分,模型在处理某个零件边缘时,会把紧挨着的隔壁零件的颜色也"参考"进来——因为对模型来说它们是连在一起的像素。
粉色的头发旁边是紫色的衣服,模型就把两地颜色一混,你的发丝边缘就紫了。
解决办法:按零件一块一块单独处理。每块裁出来后,四周用镜像的方式补一圈边(把自己边缘的像素翻出去),模型看到的全是"自己的颜色",绝不会看邻居。
np.pad(rgb, ((pad, pad), (pad, pad), (0, 0)), mode="reflect") # 镜像扩边
坑 3:无缝图集被切断 —— 锯齿的第二个原因
现象:零件连接的地方有硬阶梯,放大看像楼梯。
原理:见 4.2 的比喻。零件是拼贴画被切成了很多矩形,你在切缝处裁切就是破坏画面。
怎么自动发现:统计每个零件矩形四条边上有没有不透明像素。
# 对每个零件,看它的上/下/左/右四条边
m = max(上边最大alpha, 下边最大alpha, 左边最大alpha, 右边最大alpha)
if m > 40: # 边界上有东西 → 说明是被切断的
hit += 1
实测数据:
| 模型 | 边界有内容的零件占比 | 结论 |
|---|---|---|
| figure_1978 | 5 / 326 = 1.5% | 有间隙,用逐零件 |
| 风织香彩夏夜引光 | 406 / 454 = 89.4% | 无缝,必须用整图 |
解决办法:整图模式。整张图切成 512×512 的块去处理,块之间留 32 像素重叠,最后只取每块中间的部分拼回去 —— 这样每条块边界两侧的像素都享受了同样的上下文,拼起来没有任何接缝。
坑 4:alpha 被"收紧" —— 锯齿的头号原因(我踩过的坑)
现象:所有零件的边缘都变得像剪纸,过渡消失,放大看是一圈硬阶梯。
原理:
真实的美术图里,零件边缘不是"有/没有"两种状态,而是有一条几个像素宽的渐变带(alpha 从 255 慢慢降到 0)。正是这条渐变带让边缘看起来柔和。
早期的做法会想:既然要"锐利",那我给 alpha 套一个对比度拉伸,把渐变带压窄,边缘不就清楚了?
alpha' = clip((alpha - 0.5) × 1.72 + 0.5, 0, 1) // strength=0.6 时的效果
压缩了 42% 的过渡带。结果呢:
| alpha 收紧强度 | 半透明像素占比 | ALPHA 差异>60 | 观感 |
|---|---|---|---|
| 0.6(旧) | 1.44% | 17451 px | 边缘像剪纸,有台阶 |
| 0.35 | 1.65% | — | 还是偏硬 |
| 0.20 | 2.01% | — | 可以接受 |
| 0(正确) | 4.55% | 863 px | 过渡自然 |
结论:alpha 通道绝对不要做任何"锐化/收紧",原样放大就好。
那边缘的清晰感从哪来?从杜绝串色和不切断内容来 —— 不是从数学上压窄渐变带来。
坑 5:周期纹理 —— 纱网变粗或消失
现象:原本很细的纱网/条纹布/网点,超分后变成粗细不均的条带(像摩尔纹),或者干脆糊没了。
原理:1 像素宽的条纹,周期就是 1px。你把它放大 2 倍,它应该变成 2 像素宽 —— 但这需要精确复制。超分模型是"看着图猜细节",它看到一片高频条纹会判断成噪声,要么平滑掉(消失),要么按自己的理解重画(变粗、粗细不均)。
解决办法:这类零件不让模型处理,直接用 NEAREST(最近邻)放大:
- 1px 的条纹 → 精确变成 2px 的条纹,频率一点不变
- 整图模式下,模型会把条纹糊掉,所以要在整图处理完之后用 NEAREST 的结果覆盖回去
怎么自动识别:算零件的边缘密度和方向性。
if 面积 > 20000 and 边缘密度 > 0.40 and 水平梯度/垂直梯度 > 5:
→ 判定为周期性垂直条纹,改走 NEAREST
实测在 457 个零件里只命中 1 个(0B_Scene1_4,那条粉白竖条纹),耗时 0.03 秒。
坑 6:块化 —— 头发"细节丢失"
现象:原本细腻的渐变(发丝内部从亮到暗的过渡)变成了一块一块平涂的颜色。
原理:anime6B 是用动画帧训练的。动画的特点是赛璐璐风格——大面积平涂 + 硬边高光。模型学会这个风格之后,遇到你原图里那种细腻的写实渐变,它会"帮你去掉多余的过渡",压成色块。
缓解办法:把模型输出和纯插值放大的结果混合一下:
最终 = 模型结果 × (1 - blend) + 插值结果 × blend # blend = 0.2
插值结果虽然不清晰,但它忠实地保留了原图的低频信息(渐变走向)。混 20% 进来,既能保住清晰度,又不会把渐变压平。
实测 figure_1978 的 p99 差异从 81.8 降到 57.4(极端偏差减少)。
注意:blend 越大越软。0.2 是温和值,0.4 以上会明显感觉到"没变清晰"。
6. 完整操作流程
第 1 步:备份
# 把原始 png 复制一份
Copy-Item "模型目录\贴图.png" "模型目录\贴图_2048_原始.png"
脚本本身不会改源目录,但你会手动替换文件做对比,所以先备份。
第 2 步:探测
python spine_probe.py "模型目录"
确认三件事:需要几倍 / 是不是 PMA / 半透明占比多少。
第 3 步:超分
# 最简(auto 自动选模式)
python spine_upscale.py --dir "模型目录" --out "模型目录_4096"
# 显式指定模式(确认过结构时)
python spine_upscale.py --dir "模型目录" --out "模型目录_4096" --layout whole
# 保渐变(渐变面积大的插画风模型)
python spine_upscale.py --dir "模型目录" --out "模型目录_4096" --blend 0.2
输出目录是一个完整可用的模型目录(json、skel、atlas 都复制好了)。
第 4 步:质检
python spine_verify.py --orig "模型目录\贴图.png" ^
--res "模型目录_4096\贴图.png" ^
--atlas "模型目录\xxx.atlas" ^
--mode pma --out "_check"
看三个数字(见第 7 节)。
第 5 步:实机验证
打开 Live2DViewerEX → EX Studio → 选模型文件夹 → 点 .json。
验证技巧:新旧版本用同一个视图对比。 换图之后点一下文件列表里的任意文件,EX Studio 会重新加载纹理,视图保持不变,这样对比才公平。
7. 质检怎么看
spine_verify.py 会输出合成预览图和一组数字。
7.1 三背景合成(最直观)
生成 bg_orig_mag.png / bg_res_mag.png(品红背景)以及黑色、白色版本。
为什么要三种背景:黑边在黑背景下看不见,白边在白背景下看不见。品红最刺眼,能暴露所有问题。
7.2 三个数字
| 指标 | 含义 | 健康值 |
|---|---|---|
RGB 差异>30 占比 |
颜色改动幅度较大的像素比例 | < 2% 健康,< 1% 优秀 |
RGB p99 |
99 分位的颜色差异(极端偏差) | < 40 |
ALPHA 差异>60 |
透明度改动较大的像素数 | 越接近 0 越好,直接对应"边缘有没有被改坏" |
为什么 ALPHA 差异是关键指标:透明度的改动会直接改变零件轮廓。数字大了,说明边缘被动过,很容易就是锯齿。
7.3 前后特写
cmp_NN_零件名.png 是面积最大的几个零件的前后对比,直接看就行。
8. 实战数据
8.1 两个真实模型
| 模型 | 模式 | 零件数 | 耗时(CPU 8线程) | RGB diff>30 | RGB p99 | ALPHA diff>60 |
|---|---|---|---|---|---|---|
| figure_1978 | region / alpha 0.6(旧) | 326 | 423 s | 6.57% | 81.8 | 17451 |
| figure_1978 | region / alpha 0 + blend 0.2(新) | 326 | 932 s | 7.16% | 57.4 | 863 |
| 风织香彩夏夜引光 | region(旧,89% 边界被切断) | 454 | 294 s | 8.98% | 69.0 | 7904 |
| 风织香彩夏夜引光 | whole + 周期保护(新) | 454 | 229 s | 0.81% | 29.0 | 0 |
读法:
- 第一行到第二行:只改 alpha 策略,
ALPHA 差异从 17451 掉到 863(降 95%),这就是"锯齿消失"的量化证据 - 第三行到第四行:只改模式(region → whole),
RGB 差异从 8.98% 掉到 0.81%(降 91%),alpha 差异归零
8.2 文件体积参考
| 模型 | 原图 | 4096 结果 |
|---|---|---|
| figure_1978 | 2.6 MB / 2048² | 14.3 MB / 4096² |
| 风织香彩夏夜引光 | 2.2 MB / 2048² | 6.7 MB / 4096² |
体积涨 3~6 倍是正常的(像素数涨 4 倍,PNG 压缩率因内容而异)。
9. EX Studio 着色器必须配对
这是**唯一会让"图明明对的却出现白边"**的地方。
.json 里有个 options.shader_type:
shader_type |
着色器名称 | 期望的贴图数据 |
|---|---|---|
0 |
Skeleton |
预乘(PMA) |
1 |
Skeleton (Straight Alpha) |
直通(straight) |
用错的后果(实测:边缘纯白像素,alpha=0.53,背景深灰,正确显示值应该是 153):
| 组合 | 显示值 | 后果 |
|---|---|---|
| straight 数据 + Straight 着色器 | 153 | |
| straight 数据 + PMA 着色器 | 255 | |
| PMA 数据 + PMA 着色器 | 153 | |
| PMA 数据 + Straight 着色器 | 89 |
傻瓜判断法:
- 超分时用了
--mode pma(默认)→ 配置不用动,和原图一样 - 超分时用了
--mode straight→ 脚本会自动把shader_type改成 1
换回 2048 原图对比时记得切回去(原图集通常是 PMA 数据,要配 Skeleton),否则你会看到一圈暗边,误以为是图坏了。
10. 排查表
| 现象 | 原因 | 解决 |
|---|---|---|
| 模型整体糊、放大更糊 | 贴图尺寸小于 atlas 声明 | 跑探测,按倍数超分 |
| 边缘一圈黑 | 没反预乘 | 确认探测显示 PMA 检测: True |
| 边缘一圈白/亮线 | 着色器与数据不配对 | 见第 9 节 |
| 零件连接处锯齿 | ① alpha 被收紧 ② 无缝图集用了逐零件模式 | --alpha-strength 0;--layout whole |
| 发丝/皮肤出现杂色斑点 | 零件串色 | 有间隙的图集用 --layout region |
| 某些零件残缺/变透明 | 零件矩形重叠,后写的覆盖了先写的 | 脚本已用「alpha 大者优先」;检查是否 --limit 截断 |
| 条纹/纱网变粗或消失 | 周期纹理被模型重画 | 确认 --periodic-guard 是开的 |
| 大面积渐变被"块化" | 模型风格化 | --blend 0.2 |
| 局部冒出文字状/奇怪的形状 | 模型幻觉 | 换 anime6B,别用 x4plus |
| 超分后和原来没区别 | atlas 声明 == png 尺寸 | 本来就不用超分 |
| 结果全空白 | 输入的 png 已经是 4096,又被放大一次导致坐标错位 | 用 2048 原图重新跑 |
11. 附录:为什么"看起来更清晰"这件事本身有上限
这一段是给想深究的人看的,跳过也不影响使用。
你截的图是从 EX Studio 的渲染画面截的,而渲染画面有固定尺寸(比如 1793×1009)。模型在画面里占的区域可能只有 400×600 像素。
这时候:
- 2048 的贴图 → 显示时缩小约 1.2 倍 → 几乎 1:1 采样,细节全在
- 4096 的贴图 → 显示时缩小约 2.4 倍 → 引擎会走 mipmap,用降过采样率的层级,多出来的细节被平均掉了
所以:在"缩小显示"的场景下,超分的收益主要是"抗锯齿更好"和"放大时更清晰",而不是"同一个缩放下看得更细"。
想让超分的效果明显,两种办法:
- 把 EX Studio 的缩放调到 1.3 以上(模型变大,纹理按 1:1 或放大采样,4096 的优势立刻体现)
- 提高各向异性过滤等级:EX Studio 的
选项 → 各向异性过滤等级,默认 2,可以试 4(官方文档提到这个值太大会出现"嘴角黑边",所以别拉满)
这也是为什么经常会出现"明明超分了怎么还是差不多"的感觉——不是超分没用,是看的姿势不对。


