ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

【Bug已解决】`transformers.image_transforms.resize` doesnot work for negative values 解决方案

【Bug已解决】`transformers.image_transforms.resize` doesnot work for negative values 解决方案 【Bug已解决】transformers.image_transforms.resizedoesnot work for negative values 解决方案一、现象长什么样transformers.image_transforms.resize是图像预处理里把 PIL 图 resize 到目标尺寸的工具。当你传入负值作为目标尺寸时行为异常# 现象 A负值被当成绝对像素喂给 PIL直接报错 ValueError: width and height must be 0 # 传入 size(-1, 64) 或 size(64, -1)PIL.Image.resize 不接受负尺寸 # 现象 B负值的按比例语义没实现结果错 # 用户期望 size(256, -1) 表示宽 256、高按比例自动但代码直接把 -1 当尺寸 # resize 失败或产出错误形状 # 现象 C负坐标/负 crop 参数被静默接受图像被裁错 # resize 内部先 crop 再 resizecrop 框含负坐标PIL 行为未定义 - 图像错位 # 典型触发 from transformers.image_transforms import resize img resize(pil_image, size(-1, 224)) # 期望高 224、宽按比例实际报错最典型的指纹用户用负值表达某一维自动/按比例但resize没有这套语义把负值直接传给 PIL 导致报错或错误结果。二、背景图像 resize 在很多视觉 pipeline 里需要只固定一维、另一维按比例。常见约定如torchvision.transforms.Resizesize是单个 int → 短边缩放到该值长边按比例size是(h, w)元组且允许其中一个为-1表示该维自动按比例。但transformers.image_transforms.resize早期实现只支持两维都是正 int的简单情况没有处理一维为-1按比例负值应当报错或解释为相对/自动resize 前的 crop 步骤里出现的负坐标。于是负值要么被 PIL 拒绝现象 A要么被误当绝对尺寸现象 B要么在内部 crop 里产生未定义行为现象 C。三、根因根因有三类负值被直接传给 PIL.Image.resize。 PIL 的resize((w, h))要求w,h 0。image_transforms.resize没在调用 PIL 前把-1解析成按比例直接把(-1, 224)传进去 →ValueError: width and height must be 0。缺少一维 -1 自动的语义解析。 正确做法若size(256, -1)先算出当前图宽高比把-1替换成round(256 * 原高/原宽)。原实现没做这步 → 现象 B。内部 crop 的负坐标未裁剪/校验。 resize 流程有时先crop再 resize如先居中裁剪再缩。crop 框若含负坐标如(box[-10, 0, 100, 100])PIL 行为依赖于版本可能裁出错位图。应在 crop 前把负坐标 clamp 到[0, 边界]。四、最小可运行复现下面用纯 Python 模拟负值直接传给 PIL 报错而解析 -1 为按比例后正常from dataclasses import dataclass from typing import Tuple dataclass class FakePIL: w: int h: int def pil_resize(img: FakePIL, size: Tuple[int, int]): w, h size if w 0 or h 0: raise ValueError(width and height must be 0) return FakePIL(w, h) def resize_buggy(img: FakePIL, size: Tuple[int, int]): 有 bug直接把 size 传给 PIL。 return pil_resize(img, size) def resolve_neg(size: Tuple[int, int], img: FakePIL) - Tuple[int, int]: 修正把 -1 解析成按比例。 h, w size if h -1 and w ! -1: h round(img.h * w / img.w) elif w -1 and h ! -1: w round(img.w * h / img.h) return h, w def resize_fixed(img: FakePIL, size: Tuple[int, int]): h, w resolve_neg(size, img) return pil_resize(img, (h, w)) # 复现size(-1, 224) 直接传 PIL - 报错 img FakePIL(400, 300) try: resize_buggy(img, (-1, 224)) print(复现失败) except ValueError as e: print(复现成功(根因):, e) # 修正解析 -1 为按比例 out resize_fixed(img, (-1, 224)) print(按比例结果:, out.w, x, out.h) # 宽 224高应为 224 * 300/400 168 assert out.w 224 and out.h 168运行后buggy 版因-1直接传给 PIL 而ValueErrorfixed 版把-1解析成按比例224×300/400168复现并修复了根因 1/2。五、解决方案第一层最小直接修复最快的止血在调用PIL.Image.resize前先把-1解析成按比例的正尺寸并对 crop 负坐标做 clampfrom PIL import Image def safe_resize(image: Image.Image, size): 第一层修复支持 (256, -1) / (-1, 256) 按比例拒绝/修正负值。 # size 兼容 int 与 (h, w) 元组 if isinstance(size, int): size (size, size) h, w size oh, ow image.height, image.width # 解析 -1 - 按比例 if h -1 and w ! -1: h round(oh * w / ow) elif w -1 and h ! -1: w round(ow * h / oh) elif h -1 and w -1: raise ValueError(size 两维不能同时为 -1) # 防御其他负值直接报错非 -1 的负值无明确定义 if h 0 or w 0: raise ValueError(f解析后尺寸必须为正得到 ({h}, {w})) return image.resize((w, h)) # 使用 img Image.open(cat.jpg) resized safe_resize(img, (-1, 224)) # 宽 224高按比例第一层让用户立刻能用(256, -1)这类一维自动的写法负值不再崩。六、解决方案第二层结构性改进用ImageResizeNormalizer把负尺寸解析 crop 负坐标 clamp 异常类型统一收口from dataclasses import dataclass from typing import Tuple dataclass class ImageResizeNormalizer: 统一的图像 resize 参数归一化负号语义、crop 坐标校验。 def normalize_size(self, size, img_hw: Tuple[int, int]) - Tuple[int, int]: if isinstance(size, int): size (size, size) h, w size oh, ow img_hw if h -1 and w ! -1: h max(1, round(oh * w / ow)) elif w -1 and h ! -1: w max(1, round(ow * h / oh)) if h 0 or w 0: raise ValueError(fresize 目标尺寸必须为正得到 {(h, w)}) return h, w def clamp_crop_box(self, box, img_hw: Tuple[int, int]) - Tuple[int, int, int, int]: oh, ow img_hw left, top, right, bottom box # 负坐标 clamp 到 [0, 边界]避免 PIL 未定义行为 left max(0, left); top max(0, top) right min(ow, right); bottom min(oh, bottom) if right left or bottom top: raise ValueError(fcrop 框非法: {(left, top, right, bottom)}) return (left, top, right, bottom) # 使用 norm ImageResizeNormalizer() h, w norm.normalize_size((-1, 224), (300, 400)) box norm.clamp_crop_box((-10, 0, 100, 100), (300, 400))ImageResizeNormalizer把负尺寸语义 crop 负坐标收口resize 调用方不再各自处理避免遗漏。七、解决方案第三层断言 / CI 守护用 pytest 固化-1 按比例、非 -1 负值报错、crop 负坐标被 clampimport pytest def test_neg_one_means_proportional(): from img_resize_norm import ImageResizeNormalizer n ImageResizeNormalizer() h, w n.normalize_size((-1, 224), (300, 400)) assert w 224 and h 168 def test_both_neg_raises(): from img_resize_norm import ImageResizeNormalizer n ImageResizeNormalizer() with pytest.raises(ValueError): n.normalize_size((-1, -1), (300, 400)) def test_non_neg_one_negative_raises(): from img_resize_norm import ImageResizeNormalizer n ImageResizeNormalizer() with pytest.raises(ValueError): n.normalize_size((-5, 224), (300, 400)) # -5 不是 -1无定义 def test_crop_negative_clamped(): from img_resize_norm import ImageResizeNormalizer n ImageResizeNormalizer() box n.clamp_crop_box((-10, 0, 100, 100), (300, 400)) assert box[0] 0, 负 crop 坐标应被 clamp 到 0CI 跑pytest tests/test_image_resize.py以后只要有人又把负值裸传给 PIL或忘了按比例解析测试立刻红灯。八、排查清单当image_transforms.resize遇负值异常按顺序查width and height must be 0→ 负值被直接传给 PIL用safe_resize先解析-1为按比例。想只固定一维 → 用(256, -1)/(-1, 256)确保有解析逻辑。非-1的负值如-5→ 无明确定义应当报错而非静默。crop 后 resize 图像错位 → crop 框含负坐标用clamp_crop_box钳到[0, 边界]。长期方案用ImageResizeNormalizer统一负尺寸语义 crop 校验。九、小结transformers.image_transforms.resizedoesnot work for negative values 的根因是resize 没有实现一维-1表示按比例的语义把负值直接传给PIL.Image.resize要求 0导致报错同时内部 crop 的负坐标未被校验/clamp产生未定义行为。第一层调用 PIL 前把-1解析成按比例正尺寸并对 crop 负坐标 clamp立刻可用(256, -1)。第二层用ImageResizeNormalizer统一负尺寸语义 crop 坐标校验避免遗漏。第三层pytest 断言-1 按比例、非 -1 负值报错、crop 负坐标 clamp防止回归。记住图像 resize 的负值应只用于一维自动-1 按比例这一约定且必须在调用 PIL 前解析成正数其它负值无定义应明确报错而非静默传给底层库。
返回列表