Skip to content

Commit 1128fb5

Browse files
author
Beat Buesser
committed
Support rectangular images
Signed-off-by: Beat Buesser <[email protected]>
1 parent 8171abe commit 1128fb5

File tree

3 files changed

+121
-81
lines changed

3 files changed

+121
-81
lines changed

art/attacks/evasion/adversarial_patch/adversarial_patch_numpy.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from __future__ import absolute_import, division, print_function, unicode_literals
2525

2626
import logging
27+
import math
2728
from typing import Optional, Union
2829

2930
import random
@@ -102,6 +103,9 @@ def __init__(
102103
self.clip_patch = clip_patch
103104
self._check_params()
104105

106+
if len(self.estimator.input_shape) not in [3]:
107+
raise ValueError("Wrong input_shape in estimator detected. AdversarialPatch is expecting images as input.")
108+
105109
self.image_shape = self.estimator.input_shape
106110

107111
if self.estimator.channels_first:
@@ -148,7 +152,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
148152
for _ in trange(self.max_iter, desc="Adversarial Patch Numpy"):
149153
patched_images, patch_mask_transformed, transforms = self._augment_images_with_random_patch(x, self.patch)
150154

151-
num_batches = int(x.shape[0] / self.batch_size)
155+
num_batches = int(math.ceil(x.shape[0] / self.batch_size))
152156
patch_gradients = np.zeros_like(self.patch)
153157

154158
for i_batch in range(num_batches):
@@ -159,7 +163,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
159163
patched_images[i_batch_start:i_batch_end], y_target[i_batch_start:i_batch_end],
160164
)
161165

162-
for i_image in range(self.batch_size):
166+
for i_image in range(gradients.shape[0]):
163167
patch_gradients_i = self._reverse_transformation(
164168
gradients[i_image, :, :, :], patch_mask_transformed[i_image, :, :, :], transforms[i_image],
165169
)
@@ -230,23 +234,24 @@ def _get_circular_patch_mask(self, sharpness: int = 40) -> np.ndarray:
230234

231235
mask = 1 - np.clip(z_grid, -1, 1)
232236

237+
channel_index = 1 if self.estimator.channels_first else 3
238+
axis = channel_index - 1
239+
mask = np.expand_dims(mask, axis=axis)
240+
mask = np.broadcast_to(mask, self.patch_shape).astype(np.float32)
241+
233242
pad_h_before = int((self.image_shape[self.i_h] - mask.shape[self.i_h]) / 2)
234243
pad_h_after = int(self.image_shape[self.i_h] - pad_h_before - mask.shape[self.i_h])
235244

236245
pad_w_before = int((self.image_shape[self.i_w] - mask.shape[self.i_w]) / 2)
237246
pad_w_after = int(self.image_shape[self.i_w] - pad_w_before - mask.shape[self.i_w])
238247

239-
mask = np.pad(
240-
mask,
241-
pad_width=((pad_h_before, pad_h_after), (pad_w_before, pad_w_after)),
242-
mode="constant",
243-
constant_values=(0, 0),
244-
)
248+
if self.estimator.channels_first:
249+
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after))
250+
else:
251+
pad_width = ((pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0))
252+
253+
mask = np.pad(mask, pad_width=pad_width, mode="constant", constant_values=(0, 0),)
245254

246-
channel_index = 1 if self.estimator.channels_first else 3
247-
axis = channel_index - 1
248-
mask = np.expand_dims(mask, axis=axis)
249-
mask = np.broadcast_to(mask, self.patch_shape).astype(np.float32)
250255
return mask
251256

252257
def _augment_images_with_random_patch(self, images, patch, scale=None):
@@ -299,20 +304,22 @@ def _scale(self, x, scale):
299304
left = (width - scale_w) // 2
300305

301306
x_out = np.zeros_like(x)
302-
x_out[top : top + scale_h, left : left + scale_w] = zoom(x, zoom=zooms, order=1)
303307

304308
if self.estimator.channels_first:
305309
x_out[:, top : top + scale_h, left : left + scale_w] = zoom(x, zoom=zooms, order=1)
306310
else:
307311
x_out[top : top + scale_h, left : left + scale_w, :] = zoom(x, zoom=zooms, order=1)
308312

309313
elif scale > 1.0:
310-
scale_h = int(np.round(height / scale))
311-
scale_w = int(np.round(width / scale))
314+
scale_h = int(np.round(height / scale)) + 1
315+
scale_w = int(np.round(width / scale)) + 1
312316
top = (height - scale_h) // 2
313317
left = (width - scale_w) // 2
314318

315-
x_out = zoom(x[top : top + scale_h, left : left + scale_w], zoom=zooms, order=1)
319+
if self.estimator.channels_first:
320+
x_out = zoom(x[:, top : top + scale_h, left : left + scale_w], zoom=zooms, order=1)
321+
else:
322+
x_out = zoom(x[top : top + scale_h, left : left + scale_w, :], zoom=zooms, order=1)
316323

317324
cut_top = (x_out.shape[self.i_h] - height) // 2
318325
cut_left = (x_out.shape[self.i_w] - width) // 2
@@ -325,16 +332,16 @@ def _scale(self, x, scale):
325332
else:
326333
x_out = x
327334

335+
assert x.shape == x_out.shape
336+
328337
return x_out
329338

330339
def _shift(self, x, shift_h, shift_w):
331340
if self.estimator.channels_first:
332341
shift_hw = (0, shift_h, shift_w)
333342
else:
334343
shift_hw = (shift_h, shift_w, 0)
335-
336-
x = shift(x, shift=shift_hw, order=1)
337-
return x, shift_h, shift_w
344+
return shift(x, shift=shift_hw, order=1)
338345

339346
def _random_transformation(self, patch, scale):
340347
patch_mask = self._get_circular_patch_mask()
@@ -359,12 +366,13 @@ def _random_transformation(self, patch, scale):
359366
if shift_max_h > 0 and shift_max_w > 0:
360367
shift_h = random.uniform(-shift_max_h, shift_max_h)
361368
shift_w = random.uniform(-shift_max_w, shift_max_w)
362-
patch, _, _ = self._shift(patch, shift_h, shift_w)
363-
patch_mask, shift_1, shift_2 = self._shift(patch_mask, shift_h, shift_w)
364-
transformation["shift_1"] = shift_1
365-
transformation["shift_2"] = shift_2
369+
patch = self._shift(patch, shift_h, shift_w)
370+
patch_mask = self._shift(patch_mask, shift_h, shift_w)
371+
transformation["shift_h"] = shift_h
372+
transformation["shift_w"] = shift_w
366373
else:
367-
transformation["shift"] = (0, 0, 0)
374+
transformation["shift_h"] = 0
375+
transformation["shift_w"] = 0
368376

369377
return patch, patch_mask, transformation
370378

@@ -374,7 +382,7 @@ def _reverse_transformation(self, gradients: np.ndarray, patch_mask_transformed,
374382
# shift
375383
shift_h = transformation["shift_h"]
376384
shift_w = transformation["shift_w"]
377-
gradients, _, _ = self._shift(gradients, -shift_h, -shift_w)
385+
gradients = self._shift(gradients, -shift_h, -shift_w)
378386

379387
# scale
380388
scale = transformation["scale"]
@@ -383,4 +391,5 @@ def _reverse_transformation(self, gradients: np.ndarray, patch_mask_transformed,
383391
# rotate
384392
angle = transformation["rotate"]
385393
gradients = self._rotate(gradients, -angle)
394+
386395
return gradients

art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,20 @@ def __init__(
105105
self.image_shape = classifier.input_shape
106106
self._check_params()
107107

108-
if self.image_shape[2] not in [1, 3]:
109-
raise ValueError("Color channel need to be in last dimension.")
108+
if self.estimator.channels_first:
109+
raise ValueError("Color channel needs to be in last dimension.")
110+
111+
self.nb_dims = len(self.image_shape)
112+
if self.nb_dims == 3:
113+
self.i_h = 0
114+
self.i_w = 1
115+
elif self.nb_dims == 4:
116+
self.i_h = 1
117+
self.i_w = 2
110118

111119
if self.patch_shape is None:
112120
self.patch_shape = self.estimator.input_shape
113121

114-
if self.patch_shape[2] not in [1, 3]:
115-
raise ValueError("Color channel need to be in last dimension.")
116-
117122
if self.patch_shape[0] != self.patch_shape[1]:
118123
raise ValueError("Patch height and width need to be the same.")
119124

@@ -186,13 +191,13 @@ def _loss(self, images: "tf.Tensor", target: "tf.Tensor") -> "tf.Tensor":
186191

187192
return loss
188193

189-
def _get_circular_patch_mask(self, nb_images: int, sharpness: int = 40) -> "tf.Tensor":
194+
def _get_circular_patch_mask(self, nb_samples: int, sharpness: int = 40) -> "tf.Tensor":
190195
"""
191196
Return a circular patch mask.
192197
"""
193198
import tensorflow as tf # lgtm [py/repeated-import]
194199

195-
diameter = self.patch_shape[0]
200+
diameter = np.minimum(self.patch_shape[self.i_h], self.patch_shape[self.i_w])
196201

197202
x = np.linspace(-1, 1, diameter)
198203
y = np.linspace(-1, 1, diameter)
@@ -202,19 +207,19 @@ def _get_circular_patch_mask(self, nb_images: int, sharpness: int = 40) -> "tf.T
202207
image_mask = 1 - np.clip(z_grid, -1, 1)
203208
image_mask = np.expand_dims(image_mask, axis=2)
204209
image_mask = np.broadcast_to(image_mask, self.patch_shape)
205-
image_mask = tf.stack([image_mask] * nb_images)
210+
image_mask = tf.stack([image_mask] * nb_samples)
206211
return image_mask
207212

208213
def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional[float] = None) -> "tf.Tensor":
209214
import tensorflow as tf # lgtm [py/repeated-import]
210215
import tensorflow_addons as tfa
211216

212-
nb_images = images.shape[0]
217+
nb_samples = images.shape[0]
213218

214-
image_mask = self._get_circular_patch_mask(nb_images=nb_images)
219+
image_mask = self._get_circular_patch_mask(nb_samples=nb_samples)
215220
image_mask = tf.cast(image_mask, images.dtype)
216221

217-
smallest_image_edge = np.minimum(self.image_shape[0], self.image_shape[1])
222+
smallest_image_edge = np.minimum(self.image_shape[self.i_h], self.image_shape[self.i_w])
218223

219224
image_mask = tf.image.resize(
220225
image_mask,
@@ -225,9 +230,15 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
225230
name=None,
226231
)
227232

233+
pad_h_before = int((self.image_shape[self.i_h] - image_mask.shape[self.i_h + 1]) / 2)
234+
pad_h_after = int(self.image_shape[self.i_h] - pad_h_before - image_mask.shape[self.i_h + 1])
235+
236+
pad_w_before = int((self.image_shape[self.i_w] - image_mask.shape[self.i_w + 1]) / 2)
237+
pad_w_after = int(self.image_shape[self.i_w] - pad_w_before - image_mask.shape[self.i_w + 1])
238+
228239
image_mask = tf.pad(
229240
image_mask,
230-
paddings=tf.constant([[0, 0,], [0, 0,], [100, 100], [0, 0]]),
241+
paddings=tf.constant([[0, 0], [pad_h_before, pad_h_after], [pad_w_before, pad_w_after], [0, 0]]),
231242
mode="CONSTANT",
232243
constant_values=0,
233244
name=None,
@@ -236,7 +247,7 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
236247
image_mask = tf.cast(image_mask, images.dtype)
237248

238249
patch = tf.cast(patch, images.dtype)
239-
padded_patch = tf.stack([patch] * nb_images)
250+
padded_patch = tf.stack([patch] * nb_samples)
240251

241252
padded_patch = tf.image.resize(
242253
padded_patch,
@@ -247,15 +258,9 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
247258
name=None,
248259
)
249260

250-
pad_w_0 = int((self.image_shape[0] - smallest_image_edge) / 2)
251-
pad_w_1 = self.image_shape[0] - smallest_image_edge - pad_w_0
252-
253-
pad_h_0 = int((self.image_shape[1] - smallest_image_edge) / 2)
254-
pad_h_1 = self.image_shape[1] - smallest_image_edge - pad_h_0
255-
256261
padded_patch = tf.pad(
257262
padded_patch,
258-
paddings=tf.constant([[0, 0], [pad_h_0, pad_h_1], [pad_w_0, pad_w_1], [0, 0]]),
263+
paddings=tf.constant([[0, 0], [pad_h_before, pad_h_after], [pad_w_before, pad_w_after], [0, 0]]),
259264
mode="CONSTANT",
260265
constant_values=0,
261266
name=None,
@@ -265,14 +270,14 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
265270

266271
transform_vectors = list()
267272

268-
for i in range(nb_images):
273+
for i in range(nb_samples):
269274
if scale is None:
270275
im_scale = np.random.uniform(low=self.scale_min, high=self.scale_max)
271276
else:
272277
im_scale = scale
273278

274-
padding_after_scaling_h = self.image_shape[0] - im_scale * padded_patch.shape[0]
275-
padding_after_scaling_w = self.image_shape[1] - im_scale * padded_patch.shape[1]
279+
padding_after_scaling_h = self.image_shape[self.i_h] - im_scale * padded_patch.shape[self.i_h]
280+
padding_after_scaling_w = self.image_shape[self.i_w] - im_scale * padded_patch.shape[self.i_w]
276281

277282
x_shift = np.random.uniform(-padding_after_scaling_w, padding_after_scaling_w)
278283
y_shift = np.random.uniform(-padding_after_scaling_h, padding_after_scaling_h)
@@ -336,7 +341,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
336341

337342
return (
338343
self._patch.numpy(),
339-
self._get_circular_patch_mask(nb_images=1).numpy()[0],
344+
self._get_circular_patch_mask(nb_samples=1).numpy()[0],
340345
)
341346

342347
def apply_patch(self, x: np.ndarray, scale: float, patch_external: Optional[np.ndarray] = None) -> np.ndarray:

0 commit comments

Comments
 (0)