Mage 读图答非所问——是预处理分辨率没对上还是对话模板用错了?

文章导读
图里的内容你自己看得一清二楚,模型却答得像没看过图,这种现象通常只有两条来路:一是图在送进模型前已经被预处理改过(裁切、等比缩到很小的长边、强制拉伸),关键区域消失或糊掉;二是对话模板里承载图片的那段标记没有正确落到最终输入里,模型收到的其实只有文字。两种表现都像“没看图”,但取证方式不一样,可以先分别留证据,再决定改哪一处。
📋 目录
  1. 壹 把原图和送进模型的张量分别存成图片对照
  2. 贰 核对预处理里的归一化与通道顺序
  3. 叁 换一套对话模板重跑同一张图
  4. 肆 用单张只含一行文字的图做最小验证
A A

图里的内容你自己看得一清二楚,模型却答得像没看过图,这种现象通常只有两条来路:一是图在送进模型前已经被预处理改过(裁切、等比缩到很小的长边、强制拉伸),关键区域消失或糊掉;二是对话模板里承载图片的那段标记没有正确落到最终输入里,模型收到的其实只有文字。两种表现都像“没看图”,但取证方式不一样,可以先分别留证据,再决定改哪一处。

先存两张图:原图和由送入张量还原出来的图,肉眼比对有没有被裁掉或缩糊;再打印张量的取值范围与通道顺序,确认归一化没有把像素压成异常值;然后把同一张图换一套对话模板重跑,并打印两次拼接后的输入,看图片标记是否还在。如果连只含一行文字的图都答错,优先查模板与输入拼接;如果文字图答对、复杂图答错,再回头调分辨率与裁切。单张简单图只能做定性判断。

把原图和送进模型的张量分别存成图片对照

这一步只回答一个问题:模型实际拿到的像素,和你看到的原图是不是同一张内容。做法是在预处理返回之后、batch 拼接之前,把张量另存一份转成 PNG,此时看到的才是模型真正读到的内容。

import torch
from torchvision.utils import save_image

def dump_input_tensor(t, path):
    x = t.detach().float().cpu()
    if x.dim() == 4:
        x = x[0]                 # 取 batch 中的第一张
    mean = torch.tensor([0.5, 0.5, 0.5]).view(3, 1, 1)  # 与预处理保持一致
    std = torch.tensor([0.5, 0.5, 0.5]).view(3, 1, 1)
    x = (x * std + mean).clamp(0, 1)
    save_image(x, path)

如果通道顺序不是 RGB、或者张量布局还是 HWC,先转成 (C,H,W) 的 RGB 再存,否则还原图颜色本身就是错的,容易误判成预处理问题。

缩放与裁切配置通常集中在这几个字段,名字按你实际使用的仓库替换:

  • image_size / max_pixels / min_pixels:控制长边或总像素上限;
  • do_resize / shortest_edge:等比缩放还是强制拉伸;
  • do_center_crop / crop_size:是否居中裁切、裁多大;
  • patch_size:视觉侧切 patch 的粒度,间接决定小字还看不看得清。

验证方式是把原图和还原图并排看三件事:边缘有没有被切掉、文字或小目标有没有糊成一片、宽高比有没有被拉伸。只要出现裁切或明显缩糊,就先归到预处理侧,记下当时的配置值再调整。

核对预处理里的归一化与通道顺序

尺寸对不代表数值对。均值方差用错、少了除以 255 这一步、通道从 RGB 变成 BGR,都会让模型看到一张亮度和颜色都不对的图,常见表现是能说出大致场景,但答非所问。

归一化参数一般和图像处理器定义写在一起,字段名类似 image_mean、image_std、do_normalize、rescale_factor;通道顺序则取决于读图库——PIL 读出来是 RGB,OpenCV 默认是 BGR。找到这两处之后,打印送进模型的张量做核对:

x = batch['pixel_values']
print(x.dtype, x.shape)          # 期望 float32/float16 与 (B, C, H, W)
print('min/max/mean:', float(x.min()), float(x.max()), float(x.mean()))
print('per-channel mean:', [float(c.mean()) for c in x[0]])

取值范围可以先按这几档对照:只做除以 255 的缩放,张量大致落在 [0, 1];mean=std=0.5 的归一化,大致落在 [-1, 1];ImageNet 常用均值方差,单张图多数落在 -2 到 2 附近。打印出来是 0 到 255、成百上千的数值、或者三个通道均值明显异常时,优先怀疑归一化没生效或通道顺序反了。红蓝互换是最直观的通道顺序证据。

换一套对话模板重跑同一张图

模板问题的典型症状是:日志里确实有图片张量,回答却像在答一道纯文字题。原因通常是图片占位符在拼接时被丢掉、数量对不上,或者被放到了指令之后,模型拿到的是“一张图 + 一段没提到图的文字”。

Mage 读图答非所问——是预处理分辨率没对上还是对话模板用错了?

切换方式有两种:在构造 messages 的地方把 template 参数换成另一套;或者直接绕开 apply_chat_template,手工拼接字符串。两种方式都用同一张图、同一句提问,其余参数保持不变。

text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print(repr(text))          # 看图片占位符是否出现、位置在哪

打印拼接文本后重点看三处:占位符有没有出现、出现次数是否等于图片数量、位置是否在用户提问之前。不同模板对占位符的写法不一样,有的用固定特殊 token,有的靠 processor 按字符串替换;换模板时必须确认新模板的占位符和新 processor 的替换规则一致,否则模型收到的就是纯文本输入。

然后把两次回答并列记录,不要只看哪次更顺眼:

  • 模板 A、默认预处理:原样抄下实际输出,标注是否提到图中元素;
  • 模板 B、同一张图:原样抄下实际输出,标注是否提到图中元素。

如果两套模板的输出差别只在措辞,说明模板不是主因;如果其中一套明显开始描述图片内容,那么问题就落在图片占位符与拼接链路上。

用单张只含一行文字的图做最小验证

复杂图很难分清“没看懂”还是“没看到”。做一张白底黑字、只有一行短文字的图,答案可以被人工判定,能把范围压到最小。

  • 测试图内容:一张方形白底图,居中一行黑色大字,例如 MAGE-1234,文字不要贴边,字号要保证缩到目标分辨率后仍然可辨认;
  • 期望回答:回答中明确包含图上那串字符;
  • 实际回答:分别在默认配置、以及修正后的预处理与模板下各跑一次,把原始输出抄进记录里。

据此可以划定初步边界:

  1. 文字图答对、复杂图答错——图片链路是通的,重点回到分辨率与裁切,例如调大 max_pixels、减少激进缩放;
  2. 文字图答错,且打印出的拼接文本里没有图片占位符——先修模板与输入拼接;
  3. 文字图答错,但拼接文本和还原图都正常——回到归一化、通道顺序、模型加载方式继续查;
  4. 三种都不成立——说明还有别的环节在改动输入,需要结合具体仓库代码逐段定位。

结论边界要说清楚:单张行文字图只能证明图片有没有被正确送进模型,不能用来评价模型对复杂图表、密集文字或小目标的识别能力,也不要因为这一张图通过就认为分辨率问题已经解决。