Skip to content

[Bug]: 图片送 VLM 前不做 EXIF 方向归一化,横躺的扫描件(Orientation=6)整页 OCR 错位 #3911

Description

@linus-liu-web

internal/application/service/image_multimodal.go:781 的 readImageBytes 把知识库图片的原样字节取出,vlm.VLM.Predict 直接 base64 成 data: URI 发给模型(internal/models/vlm/remote_api.go:103)。全链路没有任何 EXIF 方向处理:git grep -ni "exif\|orientation" -- '*.go' 除文档解析链路的 internal/infrastructure/docparser/paddleocr_vl_converter.go:98(useDocOrientationClassify: false)外 0 命中,非测试代码里也没有任何图片解码/缩放/重编码。

后果

相机与扫描仪按传感器姿态存像素,把"该怎么转"写在 EXIF tag 0x0112(Orientation)里:

  • 浏览器与图片查看器读这个 tag —— 所以 /preview 返回原始字节,人眼看 UI 一切正常;
  • 视觉模型读像素矩阵、普遍不读 tag —— tag=3/6/8 的图到达模型时是倒着的、或躺着的。模型既要认旋转后的字形,又要在旋转过的坐标系里做表格二维推理,合并单元格锚点、姓名、电话就会成片错,而且每次错的位置都不一样(模型在猜归属,这比稳定错更难发现)。

实物取证

拿一份真实扫描件(JPEG,919,752 字节)解析其头部:

项 值
SOF0 尺寸 7016 × 4961(横躺;A4@600DPI 的纵向尺寸正是 4961 × 7016)
EXIF IFD0 Orientation(0x0112) = 6

同一批图片里带旋转 tag 的是少数(几十张,且都是"原件 + 导出副本"成对出现):这是"少数派路径、但一旦命中就影响整份文档"的形态。

A/B 实测(同一模型、同一 prompt):原样字节两次跑出不同的错误位置(表格分界线漂移、姓名与手机号成片错),按 EXIF 摆正后两次都正确。错误位置每次都变,说明模型在猜布局归属——比稳定出错更难被发现。

涉及链路

Predict 在生产代码里共 8 个调用点,全部直接把原始字节交给 VLM,没有任何一处做方向处理:

internal/application/service/agent_service.go:269
internal/application/service/image_action_loop.go:85,109
internal/application/service/image_multimodal.go:512
internal/application/service/temporary_document.go:600,624
internal/handler/model.go:613
internal/handler/session/image_upload.go:86

建议修法

修在装配点而不是调用点:internal/models/vlm/vlm.go:93 NewVLM 里给实例套一个方向归一化装饰器,一次覆盖全部调用点与三个实现(remote API / ollama / weknoracloud):

  • 只走 JPEG 段标记读 APP1 → IFD0 → tag 0x0112(支持 II/MM 字节序),不触碰图像数据;
  • 按 EXIF 定义实现 2/4(镜像)、3(180°)、6(顺时针 90°)、8(逆时针 90°)、5/7(对角镜像)八种变换,重新编码后 tag 不再残留(否则浏览器会再转一次);
  • 无 tag、tag=1、非 JPEG、解码失败一律原字节按引用透传,绝大多数图片零开销;
  • 超大树画布(解码要占 ≈4 字节/像素)保持原样并打 warning,避免拖垮 worker。

不建议在入库时归一化:那会改写用户存储的原始文件,并改变 ImageInfo.SHA256(现契约是"原始文件的哈希")与去重语义,还要同时改 6 个存储后端。


我们按上面的口径实现并提了 PR:见下条评论。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions