PIL之Image.crop的box参数超界的问题
结论
Image.crop(box=None)
Returns a rectangular region from this image. The box is a 4-tuple defining the left, upper, right, and lower pixel coordinate.
This is a lazy operation. Changes to the source image may or may not be reflected in the cropped image. To break the connection, call the load() method on the cropped copy.
Parameters: box – The crop rectangle, as a (left, upper, right, lower)-tuple.
Return type: Image
Returns: An Image object.
这里的crop
对于box
参数超出图片的时候,不会报错,对于超出的部分会用0代替。
分析
先看这样一段代码:
class RandomCrop(object):
def __init__(self, size, padding=0):
if isinstance(size, numbers.Number):
self.size = (int(size), int(size))
else:
self.size = size
self.padding = padding
def __call__(self, img, mask):
if self.padding > 0:
img = ImageOps.expand(img, border=self.padding, fill=0)
mask = ImageOps.expand(mask, border=self.padding, fill=0)
assert img.size == mask.size
w, h = img.size
th, tw = self.size
if w == tw and h == th:
return img, mask
if w < tw or h < th:
return img.resize((tw, th), Image.BILINEAR), mask.resize((tw, th), Image.NEAREST)
x1 = random.randint(0, w - tw)
y1 = random.randint(0, h - th)
return img.crop((x1, y1, x1 + tw, y1 + th)), mask.crop((x1, y1, x1 + tw, y1 + th))
这段代码主要是用来在pytorch的代码中做一个数据的预处理的。逻辑很简单,但是这里因为代码“乱改”的时候,遇到了一个出乎意料的地方,这里是我改后的代码,我已经忘了当时为什么这样改动了,但是代码就清清楚楚那样写着,我也不好推脱。
class RandomCrop(object):
def __init__(self, size, size1, padding=0):
if isinstance(size, numbers.Number):
self.size = (int(size), int(size1))
else:
self.size = size
self.padding = padding
def __call__(self, img, mask):
if self.padding > 0:
img = ImageOps.expand(img, border=self.padding, fill=0)
mask = ImageOps.expand(mask, border=self.padding, fill=0)
assert img.size == mask.size
w, h = img.size
if w >= h:
w = 640
h = int(640 * (h / w))
else:
w = int(640 * (w / h))
h = 640
th, tw = self.size
if w == tw and h == th:
return img, mask
if w < tw or h < th:
return img.resize((tw, th), Image.BILINEAR), \
mask.resize((tw, th), Image.NEAREST)
x1 = random.randint(0, w - tw)
y1 = random.randint(0, h - th)
print("大于", (x1, y1, x1 + tw, y1 + th), img.size, mask.size)
return img.crop((x1, y1, x1 + tw, y1 + th)), \
mask.crop((x1, y1, x1 + tw, y1 + th))
上面的代码中,需要关注的一个改动是那个if:
if w >= h:
w = 640
h = int(640 * (h / w))
else:
w = int(640 * (w / h))
h = 640
因为这个,连带引起了后面一个想不到的错误。加了它之后,对于我的小尺寸(tw,th)
而言,基本上都会直接使用跳过中间的两个if判断。
先来放一下测试代码:
class RandomCrop(object):
def __init__(self, size, size1, padding=0):
if isinstance(size, numbers.Number):
self.size = (int(size), int(size1))
else:
self.size = size
self.padding = padding
@pysnooper.snoop()
def __call__(self, img, mask):
if self.padding > 0:
img = ImageOps.expand(img, border=self.padding, fill=0)
mask = ImageOps.expand(mask, border=self.padding, fill=0)
assert img.size == mask.size
w, h = img.size
if w >= h:
w = 640
h = int(640 * (h / w))
else:
w = int(640 * (w / h))
h = 640
th, tw = self.size
if w == tw and h == th:
return img, mask
if w < tw or h < th:
return img.resize((tw, th), Image.BILINEAR), \
mask.resize((tw, th), Image.NEAREST)
x1 = random.randint(0, w - tw)
y1 = random.randint(0, h - th)
print("大于", (x1, y1, x1 + tw, y1 + th), img.size, mask.size)
return img.crop((x1, y1, x1 + tw, y1 + th)), \
mask.crop((x1, y1, x1 + tw, y1 + th))
if __name__ == '__main__':
a = torch.randn((3, 320, 320))
b = torch.randn((1, 320, 320))
to_pil = transforms.ToPILImage()
crop_rand = RandomCrop(320, 320)
a = to_pil(a)
b = to_pil(b)
a, b = crop_rand(a, b)
print(np.asarray(a), np.asarray(b))
输出是这样的:
Starting var:.. mask = <PIL.Image.Image image mode=L size=320x320 at 0x7F387886FC18>
Starting var:.. img = <PIL.Image.Image image mode=RGB size=320x320 at 0x7F38B7902EB8>
Starting var:.. self = <__main__.RandomCrop object at 0x7f387886f940>
16:31:46.754249 call 30 @pysnooper.snoop()
16:31:46.754382 line 32 if self.padding > 0:
16:31:46.754407 line 36 assert img.size == mask.size
16:31:46.754430 line 37 w, h = img.size
New var:....... h = 320
New var:....... w = 320
16:31:46.754462 line 38 if w >= h:
16:31:46.754482 line 39 w = 640
Modified var:.. w = 640
16:31:46.754508 line 40 h = int(640 * (h / w))
16:31:46.754529 line 45 th, tw = self.size
New var:....... tw = 320
New var:....... th = 320
16:31:46.754560 line 46 if w == tw and h == th:
16:31:46.754579 line 48 if w < tw or h < th:
16:31:46.754599 line 52 x1 = random.randint(0, w - tw)
New var:....... x1 = 224
16:31:46.754659 line 53 y1 = random.randint(0, h - th)
New var:....... y1 = 0
16:31:46.754708 line 54 print("大于", (x1, y1, x1 + tw, y1 + th), img.size, mask.size)
16:31:46.754748 line 55 return img.crop((x1, y1, x1 + tw, y1 + th)), \
16:31:46.754848 line 56 mask.crop((x1, y1, x1 + tw, y1 + th))
16:31:46.754915 return 56 mask.crop((x1, y1, x1 + tw, y1 + th))
大于 (224, 0, 544, 320) (320, 320) (320, 320)
[[[102 254 26]
[145 171 119]
[165 4 131]
...
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[ 72 83 73]
[ 19 216 110]
[ 49 84 237]
...
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[ 91 212 189]
[176 94 233]
[107 89 209]
...
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
...
[[184 39 186]
[186 79 116]
[ 15 250 253]
...
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[205 86 26]
[ 78 48 214]
[231 66 66]
...
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]
[[195 158 16]
[120 92 177]
[244 176 85]
...
[ 0 0 0]
[ 0 0 0]
[ 0 0 0]]] [[ 84 15 222 ... 0 0 0]
[175 166 167 ... 0 0 0]
[ 73 155 19 ... 0 0 0]
...
[246 37 34 ... 0 0 0]
[ 62 204 81 ... 0 0 0]
[113 228 149 ... 0 0 0]]
Process finished with exit code 0
从上面的输出可以看出,这里的crop
方法对于坐标超界,是没有提示的,而且会直接补零。
进一步有一个更简单的验证:
if __name__ == '__main__':
a = torch.randn((3, 320, 320))
b = torch.randn((1, 320, 320))
to_pil = transforms.ToPILImage()
crop_rand = RandomCrop(320, 320)
a = to_pil(a)
b = to_pil(b)
lu_x, lu_y, rb_x, rb_y = (320, 320, 544, 544)
b.crop((lu_x, lu_y, rb_x, rb_y))
print(np.asarray(b.crop((lu_x, lu_y, rb_x, rb_y))))
输出
[[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
...
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]
[0 0 0 ... 0 0 0]]