PixelUMM 结果不稳定时 / 先把图片预处理差异排除掉

文章导读
PixelUMM 输出时好时坏时,先把“模型能力不行”这个假设往后放。同一张原图从不同入口上传,缩放库、压缩质量、EXIF 方向处理、透明通道合成只要有一处不同,送进模型的就已经不是同一张图。建议的判断顺序是:先把上传前的处理链路完整写下来,再用固定样本、每次只改一个变量的方式跑对比,最后才决定问题该归到模型还是归到链路。
📋 目录
  1. 壹 把图片上传前的每一步处理写下来
  2. 贰 写一个每次只改一个变量的对比脚本骨架
  3. 叁 固定输入只改预处理,跑两轮看输出差异
  4. 肆 把差异归类到尺寸、格式、方向还是压缩
  5. 伍 固化一套下发前的预处理规范
A A

PixelUMM 输出时好时坏时,先把“模型能力不行”这个假设往后放。同一张原图从不同入口上传,缩放库、压缩质量、EXIF 方向处理、透明通道合成只要有一处不同,送进模型的就已经不是同一张图。建议的判断顺序是:先把上传前的处理链路完整写下来,再用固定样本、每次只改一个变量的方式跑对比,最后才决定问题该归到模型还是归到链路。

如果同一批图片在不同入口、不同上传路径下表现差异明显,先按预处理链路排查:记录从原图到送模的每一步操作,固定样本后每次只改一个变量跑对比,观察差异是否随某个变量成组出现。如果同一输入、同一预处理参数多次调用仍然不一致,再把怀疑转向模型或调用侧——预处理规范再严格也解释不了这种情况。这套方法只用于定位差异来源,不能用来判断模型能力上限,也不要据此给出稳定性指标。

把图片上传前的每一步处理写下来

先不要动模型,把下面这些步骤按实际执行顺序列成清单,写清每一步用的库和参数。写不出取值的那一步,通常就是还没被控制住的变量。

  • 读取与解码:PIL / ImageIO / OpenCV / ffmpeg / 前端 canvas,具体用哪个解码器;多帧图是否只取第一帧。
  • 方向纠正:是否读取 EXIF Orientation 并旋转,旋转发生在缩放前还是缩放后,纠正后是否清除方向标记。
  • 色彩与通道:是否统一转 RGB;RGBA 是合成到白底还是黑底,还是一起送进去;是否有色彩空间转换。
  • 缩放:短边对齐还是长边对齐,具体目标像素值;用哪个缩放库和插值算法(PIL LANCZOS、cv2 INTER_AREA、canvas drawImage);是否分两步缩放,是否做等比 padding。
  • 编码与压缩:输出 PNG / JPEG / WebP,quality 取值多少;是否存在二次压缩;是否有字节上限触发的重压。
  • 传输:base64 编码、表单上传还是本地路径直读,编码环节是否改变字节。

记录时一行一个变量,避免“大概 1024 左右”这类描述。下面这张表可以直接抄成团队内的记录模板:

步骤实际取值由谁决定本轮是否固定
方向纠正读 EXIF / 不读服务端脚本是
缩放算法例如 LANCZOS 或 INTER_AREA服务端脚本是
目标尺寸短边 768 / 长边 1024前端或服务端是
输出格式PNG / JPEG / WebP服务端脚本是
压缩质量quality 取值服务端脚本是

写一个每次只改一个变量的对比脚本骨架

多个变量同时变化时,两轮结果不同也说明不了什么。把每个可能影响结果的处理做成开关,一次只打开一个。下面是一份通用骨架,具体实现替换成你实际使用的缩放库和编码器即可。

# preprocess_variants.py —— 通用骨架,按自己的技术栈替换实现细节
import json, pathlib

# 每个变体只在一个字段上与 base 不同
VARIANTS = {
    "base":      {"long_side": 1024, "fmt": "PNG",  "quality": None, "exif": "keep",  "bg": "none"},
    "v_resize":  {"long_side": 768,  "fmt": "PNG",  "quality": None, "exif": "keep",  "bg": "none"},
    "v_fmt":     {"long_side": 1024, "fmt": "JPEG", "quality": 90,   "exif": "keep",  "bg": "none"},
    "v_quality": {"long_side": 1024, "fmt": "JPEG", "quality": 70,   "exif": "keep",  "bg": "none"},
    "v_exif":    {"long_side": 1024, "fmt": "PNG",  "quality": None, "exif": "apply", "bg": "none"},
    "v_bg":      {"long_side": 1024, "fmt": "PNG",  "quality": None, "exif": "keep",  "bg": "white"},
}

SAMPLES = pathlib.Path("samples.txt")   # 每行一个原图路径,整轮实验不改动
def preprocess(path, cfg):
    # 按 cfg 完成方向纠正、缩放、编码,返回 (bytes, meta)
    # meta 里至少写入:原图尺寸、纠正后尺寸、最终尺寸、格式、quality、是否合成背景
    ...

def run(variant, sample, run_idx, outdir):
    cfg = VARIANTS[variant]
    data, meta = preprocess(sample, cfg)
    stem = f"{pathlib.Path(sample).stem}__{variant}__run{run_idx}"
    (outdir / f"{stem}.bin").write_bytes(data)        # 送入模型的真实字节
    (outdir / f"{stem}.json").write_text(json.dumps(meta, ensure_ascii=False, indent=2))
    return stem

for variant in VARIANTS:
    for line in SAMPLES.read_text().splitlines():
        if not line.strip():
            continue
        for i in range(1, 4):        # 每个样本每个变体跑 3 次
            run(variant, line.strip(), i, pathlib.Path("out"))

样本清单建议覆盖要验证的几类情况:大图、小图、带 EXIF 旋转的图、带透明通道的 PNG、超长截图。清单一旦确定,整轮实验不要增删,否则前后两轮没法比。输出命名用“样本id__变体名__runN”一一对应,不要用时间戳当文件名,否则回看时对不上是哪张图。

固定输入只改预处理,跑两轮看输出差异

把样本清单锁死,两轮之间只改预处理配置,不改样本,也不改调用参数。建议这样安排:

PixelUMM 结果不稳定时 / 先把图片预处理差异排除掉
  • 第一轮:只跑 base 变体,每张样本连续调用 3 次,记录 3 次输出。
  • 第二轮:只换一个变体(例如 v_resize),样本和调用次数与第一轮完全相同。
  • 每轮记录字段:变体名、完整参数、运行序号、输出文件名、人工可用性标注、差异描述。

判断时先看组内一致性。同一个变体、同一张样本的 3 次调用如果输出之间也有差异,说明除了预处理还有别的因素在动,这类情况单独标出来,不要混进预处理对比。差异描述字段建议固定成几个词,方便横向比较:裁剪或截断、方向颠倒、颜色偏移、细小文字糊、整体可用。

样本ID变体关键参数运行序号输出文件可用性差异描述
s01basePNG / 1024 / 不纠向1s01__base__run1可用无
s01v_resizePNG / 768 / 不纠向1s01__v_resize__run1不可用细小文字糊
s02v_exifPNG / 1024 / 纠向1s02__v_exif__run1不可用方向颠倒

把差异归类到尺寸、格式、方向还是压缩

跑完之后看差异是“跟着某个变量成组出现”,还是“在同一个变量内部随机出现”。只有前者才说明是预处理带来的问题。

变量类别建议样本量典型差异表现观察要点
尺寸5-10 张,大小两档都要有细节丢失、文字断行位置变化、整体内容被裁同一张图缩到两个尺寸,差异是否稳定复现
格式5-10 张,含透明通道与彩色照片边缘块状、透明区变黑底或白底、颜色发闷只换容器格式,其他参数不动
方向3-5 张明确带旋转标记的图输出描述左右颠倒或上下颠倒对比纠向前后的同一张图,不要混入其他改动
压缩5-10 张,quality 高低两档小字糊、边缘噪点被当成内容高低两档都跑,看差异是否只在低质量档出现

如果这四类变量都控制住了,同一输入多次调用仍然表现不一,那就不属于预处理链路能解释的范围了。把原图、处理后的字节、对应输出一并交给负责模型接入的人,让那边继续排查调用侧和模型侧的因素。

固化一套下发前的预处理规范

把实验里确认过的取值写成条目,作为图片下发前的固定规则:

  1. 方向:解码后立即按 EXIF 纠正,纠正后清除方向标记,后续环节不再旋转。
  2. 尺寸:统一按短边或长边策略缩放,写明目标像素值、缩放库和插值算法。
  3. 格式与压缩:统一输出格式与 quality 取值;带透明通道的图先按指定背景色合成。
  4. 单次编码:同一张图在链路里只编码一次,避免重复压缩带来的叠加损失。
  5. 版本化:预处理脚本带版本号,版本号随图片一起记录,便于回看某批次用的是哪套规则。

检查方式可以放在下发前抽检:抽取若干张图,比对脚本输出的元数据记录与规范条目是否一致,把图片和处理参数一起留档。不满足规范时的动作建议是明确的:不直接送入模型,退回上游重新生成;确实无法重新生成时,标记为“非规范输入”,与其他样本分开统计,不要混在一起比较结果。