Skip to content

Commit 990a8f9

Browse files
author
Beat Buesser
committed
Add support for video to AdversarialPatch
Signed-off-by: Beat Buesser <[email protected]>
1 parent 1128fb5 commit 990a8f9

File tree

3 files changed

+95
-34
lines changed

3 files changed

+95
-34
lines changed

art/attacks/evasion/adversarial_patch/adversarial_patch_numpy.py

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,31 @@ def __init__(
103103
self.clip_patch = clip_patch
104104
self._check_params()
105105

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.")
106+
if len(self.estimator.input_shape) not in [3, 4]:
107+
raise ValueError(
108+
"Unexpected input_shape in estimator detected. AdversarialPatch is expecting images or videos as input."
109+
)
108110

109111
self.image_shape = self.estimator.input_shape
110112

111-
if self.estimator.channels_first:
112-
self.i_h = 1
113-
self.i_w = 2
114-
else:
115-
self.i_h = 0
116-
self.i_w = 1
113+
self.i_h_patch = 0
114+
self.i_w_patch = 1
115+
116+
self.nb_dims = len(self.image_shape)
117+
if self.nb_dims == 3:
118+
if self.estimator.channels_first:
119+
self.i_h = 1
120+
self.i_w = 2
121+
else:
122+
self.i_h = 0
123+
self.i_w = 1
124+
elif self.nb_dims == 4:
125+
if self.estimator.channels_first:
126+
self.i_h = 2
127+
self.i_w = 3
128+
else:
129+
self.i_h = 1
130+
self.i_w = 2
117131

118132
if self.estimator.channels_first:
119133
smallest_image_edge = np.minimum(self.image_shape[1], self.image_shape[2])
@@ -246,9 +260,15 @@ def _get_circular_patch_mask(self, sharpness: int = 40) -> np.ndarray:
246260
pad_w_after = int(self.image_shape[self.i_w] - pad_w_before - mask.shape[self.i_w])
247261

248262
if self.estimator.channels_first:
249-
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after))
263+
if self.nb_dims == 3:
264+
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after))
265+
elif self.nb_dims == 4:
266+
pad_width = ((0, 0), (0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after))
250267
else:
251-
pad_width = ((pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0))
268+
if self.nb_dims == 3:
269+
pad_width = ((pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0))
270+
elif self.nb_dims == 4:
271+
pad_width = ((0, 0), (pad_h_before, pad_h_after), (pad_w_before, pad_w_after), (0, 0))
252272

253273
mask = np.pad(mask, pad_width=pad_width, mode="constant", constant_values=(0, 0),)
254274

@@ -291,11 +311,19 @@ def _scale(self, x, scale):
291311
height = None
292312
width = None
293313
if self.estimator.channels_first:
294-
zooms = (1.0, scale, scale)
295-
height, width = self.patch_shape[1:3]
314+
if self.nb_dims == 3:
315+
zooms = (1.0, scale, scale)
316+
height, width = self.patch_shape[1:3]
317+
elif self.nb_dims == 4:
318+
zooms = (1.0, 1.0, scale, scale)
319+
height, width = self.patch_shape[2:4]
296320
elif not self.estimator.channels_first:
297-
zooms = (scale, scale, 1.0)
298-
height, width = self.patch_shape[0:2]
321+
if self.nb_dims == 3:
322+
zooms = (scale, scale, 1.0)
323+
height, width = self.patch_shape[0:2]
324+
elif self.nb_dims == 4:
325+
zooms = (1.0, scale, scale, 1.0)
326+
height, width = self.patch_shape[1:3]
299327

300328
if scale < 1.0:
301329
scale_h = int(np.round(height * scale))
@@ -306,9 +334,15 @@ def _scale(self, x, scale):
306334
x_out = np.zeros_like(x)
307335

308336
if self.estimator.channels_first:
309-
x_out[:, top : top + scale_h, left : left + scale_w] = zoom(x, zoom=zooms, order=1)
337+
if self.nb_dims == 3:
338+
x_out[:, top : top + scale_h, left : left + scale_w] = zoom(x, zoom=zooms, order=1)
339+
elif self.nb_dims == 4:
340+
x_out[:, :, top : top + scale_h, left : left + scale_w] = zoom(x, zoom=zooms, order=1)
310341
else:
311-
x_out[top : top + scale_h, left : left + scale_w, :] = zoom(x, zoom=zooms, order=1)
342+
if self.nb_dims == 3:
343+
x_out[top : top + scale_h, left : left + scale_w, :] = zoom(x, zoom=zooms, order=1)
344+
elif self.nb_dims == 4:
345+
x_out[:, top : top + scale_h, left : left + scale_w, :] = zoom(x, zoom=zooms, order=1)
312346

313347
elif scale > 1.0:
314348
scale_h = int(np.round(height / scale)) + 1
@@ -317,17 +351,29 @@ def _scale(self, x, scale):
317351
left = (width - scale_w) // 2
318352

319353
if self.estimator.channels_first:
320-
x_out = zoom(x[:, top : top + scale_h, left : left + scale_w], zoom=zooms, order=1)
354+
if self.nb_dims == 3:
355+
x_out = zoom(x[:, top : top + scale_h, left : left + scale_w], zoom=zooms, order=1)
356+
elif self.nb_dims == 4:
357+
x_out = zoom(x[:, :, top : top + scale_h, left : left + scale_w], zoom=zooms, order=1)
321358
else:
322-
x_out = zoom(x[top : top + scale_h, left : left + scale_w, :], zoom=zooms, order=1)
359+
if self.nb_dims == 3:
360+
x_out = zoom(x[top : top + scale_h, left : left + scale_w, :], zoom=zooms, order=1)
361+
elif self.nb_dims == 4:
362+
x_out = zoom(x[:, top : top + scale_h, left : left + scale_w, :], zoom=zooms, order=1)
323363

324364
cut_top = (x_out.shape[self.i_h] - height) // 2
325365
cut_left = (x_out.shape[self.i_w] - width) // 2
326366

327367
if self.estimator.channels_first:
328-
x_out = x_out[:, cut_top : cut_top + height, cut_left : cut_left + width]
368+
if self.nb_dims == 3:
369+
x_out = x_out[:, cut_top : cut_top + height, cut_left : cut_left + width]
370+
elif self.nb_dims == 4:
371+
x_out = x_out[:, :, cut_top : cut_top + height, cut_left : cut_left + width]
329372
else:
330-
x_out = x_out[cut_top : cut_top + height, cut_left : cut_left + width, :]
373+
if self.nb_dims == 3:
374+
x_out = x_out[cut_top : cut_top + height, cut_left : cut_left + width, :]
375+
elif self.nb_dims == 4:
376+
x_out = x_out[:, cut_top : cut_top + height, cut_left : cut_left + width, :]
331377

332378
else:
333379
x_out = x
@@ -338,9 +384,15 @@ def _scale(self, x, scale):
338384

339385
def _shift(self, x, shift_h, shift_w):
340386
if self.estimator.channels_first:
341-
shift_hw = (0, shift_h, shift_w)
387+
if self.nb_dims == 3:
388+
shift_hw = (0, shift_h, shift_w)
389+
elif self.nb_dims == 4:
390+
shift_hw = (0, 0, shift_h, shift_w)
342391
else:
343-
shift_hw = (shift_h, shift_w, 0)
392+
if self.nb_dims == 3:
393+
shift_hw = (shift_h, shift_w, 0)
394+
elif self.nb_dims == 4:
395+
shift_hw = (0, shift_h, shift_w, 0)
344396
return shift(x, shift=shift_hw, order=1)
345397

346398
def _random_transformation(self, patch, scale):

art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ def __init__(
108108
if self.estimator.channels_first:
109109
raise ValueError("Color channel needs to be in last dimension.")
110110

111+
self.i_h_patch = 0
112+
self.i_w_patch = 1
113+
111114
self.nb_dims = len(self.image_shape)
112115
if self.nb_dims == 3:
113116
self.i_h = 0
@@ -197,7 +200,7 @@ def _get_circular_patch_mask(self, nb_samples: int, sharpness: int = 40) -> "tf.
197200
"""
198201
import tensorflow as tf # lgtm [py/repeated-import]
199202

200-
diameter = np.minimum(self.patch_shape[self.i_h], self.patch_shape[self.i_w])
203+
diameter = np.minimum(self.patch_shape[self.i_h_patch], self.patch_shape[self.i_w_patch])
201204

202205
x = np.linspace(-1, 1, diameter)
203206
y = np.linspace(-1, 1, diameter)
@@ -230,11 +233,11 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
230233
name=None,
231234
)
232235

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])
236+
pad_h_before = int((self.image_shape[self.i_h] - image_mask.shape[self.i_h_patch + 1]) / 2)
237+
pad_h_after = int(self.image_shape[self.i_h] - pad_h_before - image_mask.shape[self.i_h_patch + 1])
235238

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])
239+
pad_w_before = int((self.image_shape[self.i_w] - image_mask.shape[self.i_w_patch + 1]) / 2)
240+
pad_w_after = int(self.image_shape[self.i_w] - pad_w_before - image_mask.shape[self.i_w_patch + 1])
238241

239242
image_mask = tf.pad(
240243
image_mask,
@@ -294,8 +297,8 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
294297
a0, a1 = xform_matrix[0]
295298
b0, b1 = xform_matrix[1]
296299

297-
x_origin = float(self.image_shape[1]) / 2
298-
y_origin = float(self.image_shape[0]) / 2
300+
x_origin = float(self.image_shape[self.i_w]) / 2
301+
y_origin = float(self.image_shape[self.i_h]) / 2
299302

300303
x_origin_shifted, y_origin_shifted = np.matmul(xform_matrix, np.array([x_origin, y_origin]))
301304

@@ -307,10 +310,16 @@ def _random_overlay(self, images: np.ndarray, patch: np.ndarray, scale: Optional
307310

308311
transform_vectors.append(np.array([a0, a1, a2, b0, b1, b2, 0, 0]).astype(np.float32))
309312

310-
image_mask = tfa.image.transform(image_mask, transform_vectors, "BILINEAR", output_shape=self.image_shape[:2])
311-
padded_patch = tfa.image.transform(
312-
padded_patch, transform_vectors, "BILINEAR", output_shape=self.image_shape[:2]
313-
)
313+
image_mask = tfa.image.transform(image_mask, transform_vectors, "BILINEAR",)
314+
padded_patch = tfa.image.transform(padded_patch, transform_vectors, "BILINEAR",)
315+
316+
if self.nb_dims == 4:
317+
image_mask = tf.stack([image_mask] * 15, axis=1)
318+
image_mask = tf.cast(image_mask, images.dtype)
319+
320+
padded_patch = tf.stack([padded_patch] * 15, axis=1)
321+
padded_patch = tf.cast(padded_patch, images.dtype)
322+
314323
inverted_mask = 1 - image_mask
315324

316325
return images * inverted_mask + padded_patch * image_mask

tests/attacks/test_adversarial_patch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def test_failure_feature_vectors(self):
170170
_ = AdversarialPatch(classifier=classifier)
171171

172172
self.assertIn(
173-
"Wrong input_shape in estimator detected. AdversarialPatch is expecting images as input.",
173+
"Unexpected input_shape in estimator detected. AdversarialPatch is expecting images or videos as input.",
174174
str(context.exception),
175175
)
176176

0 commit comments

Comments
 (0)