Files
cs-note/hzh/Gen2D/06-精灵图处理.md

7.0 KiB
Raw Permalink Blame History

tags, create time
tags create time
image-processing
sprite-sheet
gif
computer-vision
go
algorithm
2026-06-03 10:25

06. 精灵图处理管线

概述

自动精灵图处理管线 — 背景移除 → 投影检测 → 切割 → 对齐 → GIF 预览,将一张 AI 生成的 Sprite Sheet 无缝转化为可用的逐帧动画资源。


正文

flowchart LR
    A["Input PNG"] --> B["Background Removal"]
    B --> C["Gap Detection"]
    C --> D["Tile Extract"]
    D --> E["Filter MinFill"]
    E --> F["Trim Alpha"]
    F --> G["Align padToLargest"]
    G --> H["GIF Preview"]

    style A fill:#e3f2fd,stroke:#1976d2
    style B fill:#fff3e0,stroke:#f57c00
    style C fill:#e8f5e9,stroke:#388e3c
    style D fill:#fce4ec,stroke:#c62828
    style E fill:#f3e5f5,stroke:#7b1fa2
    style F fill:#e0f7fa,stroke:#00838f
    style G fill:#fff8e1,stroke:#f9a825
    style H fill:#e8eaf6,stroke:#303f9f

处理管线总览

AI 生成的 Sprite Sheet 通常包含白色/绿色背景、不均匀间距和尺寸差异。Gen2D 的精灵图处理管线自动完成从"原始 PNG"到"可用动画帧"的全部转换工作,无需用户手动操作。

管线核心入口:splitsprite.Process(img, opts)

步骤 函数 作用
1 removeWhiteBg / removeGreenScreen 移除背景色,生成透明通道
2 projectionSplit / fixedGridSplit 检测帧边界,确定切割位置
3 MinFillRatio 过滤 丢弃空白/低填充率的无效帧
4 trimAlpha 去除每帧的透明边框,减少冗余像素
5 padToLargest 统一画布尺寸,底部居中对齐

步骤 1:背景移除

AI 生成的图片通常带有纯色背景,管线支持两种模式:

白底模式(WhiteBg)

// 阈值距离 #FFFFFF + alpha 渐变
dist := max(255-R, max(255-G, 255-B))
if dist < threshold/2 → 全透明
if dist < threshold   → alpha 线性渐变(抗锯齿)
  • WhiteThreshold 默认 40,距离纯白 40 以内的像素被处理
  • 采用渐变 alpha 实现平滑过渡,避免硬边缘

绿幕模式(GreenScreen)

// 绿色主导检测
gDominance := G - (R+B)/2
if gDominance > tolerance*255 → 透明化
  • GreenTolerance 默认 0.2,控制绿色检测灵敏度
  • 适用于绿色背景的 AI 生成图

[!tip] 设计选择 两种模式互斥,Process() 根据 opts.WhiteBg / opts.GreenScreen 自动选择。


步骤 2:切割策略

管线提供两种切割方式,根据配置自动切换:

投影检测(Projection Detection)

这是 Gen2D 的核心创新点。传统全局投影在检测列边界时,会因为武器、尾巴、翅膀等突出物被"稀释"而导致切割不准。

两阶段投影算法:

  1. 全局行投影:统计每行非透明像素占比,检测水平间隙

    • 行间隙通常是干净且全宽的,全局投影效果好
  2. 逐行列投影:在每个行段内独立计算列密度

    • 防止突出物(武器/尾巴)被其他行稀释
    • 每行段获得独立的列边界,互不干扰

[!question] 为什么不用简单的阈值分割?

精灵图中的角色往往有复杂轮廓——比如挥舞的剑可能横跨多个帧的位置。如果仅用固定阈值,剑的连续像素会让算法误判为一帧。按行段独立分析的思路是把二维问题拆解为多个一维子问题,每个子问题只关心当前行段的内容,从而避免跨行干扰。

flowchart TB
    A["输入图像"] --> B["全局行投影<br/>检测行间隙"]
    B --> C["行段 1"]
    B --> D["行段 2"]
    B --> E["行段 N"]
    C --> F["逐行列投影"]
    D --> G["逐行列投影"]
    E --> H["逐行列投影"]
    F --> I["tiles[]"]
    G --> I
    H --> I

    style B fill:#e8f5e9,stroke:#388e3c
    style F fill:#fff3e0,stroke:#f57c00
    style G fill:#fff3e0,stroke:#f57c00
    style H fill:#fff3e0,stroke:#f57c00

峰值检测算法(findCuts):

  • 对密度曲线做滑动平均平滑(kernel = minGap)
  • 以均值为参考检测峰值段(start: mean1.05, continue: mean0.95)
  • 在相邻峰之间的谷底确定切割位置
  • 回退机制:峰值不足时降级到阈值法(findCutsByGap)

固定网格(Fixed Grid)

当 GridRows > 0 && GridCols > 0 时启用,将图像等分为 Rows×Cols 个单元格。

  • GridPadding:单元格间间距(默认 2px)
  • 适用于已知行列数的标准 Sprite Sheet

步骤 3:过滤 — MinFillRatio

切割后的每个 tile 都计算填充率:

fillRatio = 非透明像素数 / 总像素数
  • MinFillRatio 默认 0.14(14%)
  • 低于阈值的 tile 被判定为空白帧,自动丢弃
  • 有效过滤因间隙检测误差产生的空白切片

步骤 4:裁剪 — trimAlpha

对每个 tile 执行透明边框裁剪:

  1. 扫描四边界,找到非透明像素的最小包围矩形
  2. 裁切到该矩形,去除四周透明区域
  3. 减少冗余像素,为后续对齐做准备

步骤 5:对齐 — padToLargest

动画播放时,如果每帧尺寸不同且内容未对齐,会导致角色"抖动"。

底部居中锚定(Bottom-Center Anchor):

canvasW = maxW * 110%    // 最大帧宽度 + 10% padding
canvasH = maxH * 110%    // 最大帧高度 + 10% padding

每帧偏移:
  ox = (canvasW - frameW) / 2   // 水平居中
  oy = canvasH - frameH          // 底部对齐(脚踏同一水平线)
  • 所有帧共享统一画布尺寸
  • 水平居中保证角色在同一屏幕位置
  • 底部对齐保证角色"脚踏实地",防止上下漂移

GIF Maker

gifmaker.Encode() 将处理后的帧序列编码为动画 GIF:

特性 实现
统一画布 所有帧归一化到 maxW × maxH
透明色 调色板索引 0 = 完全透明
防鬼影 DisposalBackground 每帧清除前一帧
调色板 采样像素构建(每 3px 取样),最多 256 色
循环 LoopCount = 0(无限循环)
anim.Disposal = append(anim.Disposal, gif.DisposalBackground)
anim.BackgroundIndex = 0  // 透明色

[!warning] DisposalBackground 的重要性 如果不设置此选项,GIF 播放器会在前一帧基础上叠加新帧,产生"残影"效果。


Options 配置速查

参数 类型 默认值 说明
WhiteBg bool true 白底移除模式
WhiteThreshold uint8 40 白色阈值(0-255)
GreenScreen bool false 绿幕移除模式
GreenTolerance float64 0.2 绿色容差(0-1)
GridRows / GridCols int 0 固定网格行列数
GapThreshold float64 0.03 间隙判定阈值
MinGapWidth int 2 最小间隙宽度(px)
MinFillRatio float64 0.14 最小填充率
Trim bool true 透明边框裁剪
CenterAlign bool true 底部居中对齐

关联文档