@@ -59,12 +59,13 @@ class RobustDPatch(EvasionAttack):
5959 "learning_rate" ,
6060 "max_iter" ,
6161 "batch_size" ,
62- "verbose" ,
6362 "patch_location" ,
6463 "crop_range" ,
6564 "brightness_range" ,
6665 "rotation_weights" ,
6766 "sample_size" ,
67+ "targeted" ,
68+ "verbose" ,
6869 ]
6970
7071 _estimator_requirements = (BaseEstimator , LossGradientsMixin , ObjectDetectorMixin )
@@ -81,6 +82,7 @@ def __init__(
8182 learning_rate : float = 5.0 ,
8283 max_iter : int = 500 ,
8384 batch_size : int = 16 ,
85+ targeted : bool = False ,
8486 verbose : bool = True ,
8587 ):
8688 """
@@ -96,6 +98,7 @@ def __init__(
9698 :param learning_rate: The learning rate of the optimization.
9799 :param max_iter: The number of optimization steps.
98100 :param batch_size: The size of the training batch.
101+ :param targeted: Indicates whether the attack is targeted (True) or untargeted (False).
99102 :param verbose: Show progress bars.
100103 """
101104
@@ -120,9 +123,10 @@ def __init__(
120123 self .brightness_range = brightness_range
121124 self .rotation_weights = rotation_weights
122125 self .sample_size = sample_size
126+ self ._targeted = targeted
123127 self ._check_params ()
124128
125- def generate (self , x : np .ndarray , y : Optional [np .ndarray ] = None , ** kwargs ) -> np .ndarray :
129+ def generate (self , x : np .ndarray , y : Optional [List [ Dict [ str , np .ndarray ]] ] = None , ** kwargs ) -> np .ndarray :
126130 """
127131 Generate RobustDPatch.
128132
@@ -133,7 +137,9 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
133137 channel_index = 1 if self .estimator .channels_first else x .ndim - 1
134138 if x .shape [channel_index ] != self .patch_shape [channel_index - 1 ]:
135139 raise ValueError ("The color channel index of the images and the patch have to be identical." )
136- if y is not None :
140+ if y is None and self .targeted :
141+ raise ValueError ("The targeted version of RobustDPatch attack requires target labels provided to `y`." )
142+ if y is not None and not self .targeted :
137143 raise ValueError ("The RobustDPatch attack does not use target labels." )
138144 if x .ndim != 4 :
139145 raise ValueError ("The adversarial patch can only be applied to images." )
@@ -144,6 +150,24 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
144150 else :
145151 image_height , image_width = x .shape [1 :3 ]
146152
153+ if not self .estimator .native_label_is_pytorch_format and y is not None :
154+ from art .estimators .object_detection .utils import convert_tf_to_pt
155+
156+ y = convert_tf_to_pt (y = y , height = x .shape [1 ], width = x .shape [2 ])
157+
158+ if y is not None :
159+ for i_image in range (x .shape [0 ]):
160+ y_i = y [i_image ]["boxes" ]
161+ for i_box in range (y_i .shape [0 ]):
162+ x_1 , y_1 , x_2 , y_2 = y_i [i_box ]
163+ if (
164+ x_1 < self .crop_range [1 ]
165+ or y_1 < self .crop_range [0 ]
166+ or x_2 > image_width - self .crop_range [1 ] + 1
167+ or y_2 > image_height - self .crop_range [0 ] + 1
168+ ):
169+ raise ValueError ("Cropping is intersecting with at least one box, reduce `crop_range`." )
170+
147171 if (
148172 self .patch_location [0 ] + self .patch_shape [0 ] > image_height - self .crop_range [0 ]
149173 or self .patch_location [1 ] + self .patch_shape [1 ] > image_width - self .crop_range [1 ]
@@ -165,14 +189,20 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
165189 i_batch_start = i_batch * self .batch_size
166190 i_batch_end = min ((i_batch + 1 ) * self .batch_size , x .shape [0 ])
167191
192+ if y is None :
193+ y_batch = y
194+ else :
195+ y_batch = y [i_batch_start :i_batch_end ]
196+
168197 # Sample and apply the random transformations:
169198 patched_images , patch_target , transforms = self ._augment_images_with_patch (
170- x [i_batch_start :i_batch_end ], self ._patch , channels_first = self .estimator .channels_first
199+ x [i_batch_start :i_batch_end ], y_batch , self ._patch , channels_first = self .estimator .channels_first
171200 )
172201
173202 gradients = self .estimator .loss_gradient (
174203 x = patched_images ,
175204 y = patch_target ,
205+ standardise_output = True ,
176206 )
177207
178208 gradients = self ._untransform_gradients (
@@ -187,7 +217,7 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
187217
188218 patch_gradients_old = patch_gradients
189219
190- self ._patch = self ._patch + np .sign (patch_gradients ) * self .learning_rate
220+ self ._patch = self ._patch + np .sign (patch_gradients ) * ( 1 - 2 * int ( self . targeted )) * self .learning_rate
191221
192222 if self .estimator .clip_values is not None :
193223 self ._patch = np .clip (
@@ -199,12 +229,13 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
199229 return self ._patch
200230
201231 def _augment_images_with_patch (
202- self , x : np .ndarray , patch : np .ndarray , channels_first : bool
232+ self , x : np .ndarray , y : Optional [ List [ Dict [ str , np . ndarray ]]], patch : np .ndarray , channels_first : bool
203233 ) -> Tuple [np .ndarray , List [Dict [str , np .ndarray ]], Dict [str , Union [int , float ]]]:
204234 """
205235 Augment images with patch.
206236
207237 :param x: Sample images.
238+ :param y: Target labels.
208239 :param patch: The patch to be applied.
209240 :param channels_first: Set channels first or last.
210241 """
@@ -242,17 +273,73 @@ def _augment_images_with_patch(
242273
243274 transformations .update ({"rot90" : rot90 })
244275
276+ if y is not None :
277+
278+ y_copy : List [Dict [str , np .ndarray ]] = list ()
279+
280+ for i_image in range (x_copy .shape [0 ]):
281+ y_b = y [i_image ]["boxes" ].copy ()
282+ image_width = x .shape [2 ]
283+ image_height = x .shape [1 ]
284+ x_1_arr = y_b [:, 0 ]
285+ y_1_arr = y_b [:, 1 ]
286+ x_2_arr = y_b [:, 2 ]
287+ y_2_arr = y_b [:, 3 ]
288+ box_width = x_2_arr - x_1_arr
289+ box_height = y_2_arr - y_1_arr
290+
291+ if rot90 == 0 :
292+ x_1_new = x_1_arr
293+ y_1_new = y_1_arr
294+ x_2_new = x_2_arr
295+ y_2_new = y_2_arr
296+
297+ if rot90 == 1 :
298+ x_1_new = y_1_arr
299+ y_1_new = image_width - x_1_arr - box_width
300+ x_2_new = y_1_arr + box_height
301+ y_2_new = image_width - x_1_arr
302+
303+ if rot90 == 2 :
304+ x_1_new = image_width - x_2_arr
305+ y_1_new = image_height - y_2_arr
306+ x_2_new = x_1_new + box_width
307+ y_2_new = y_1_new + box_height
308+
309+ if rot90 == 3 :
310+ x_1_new = image_height - y_1_arr - box_height
311+ y_1_new = x_1_arr
312+ x_2_new = image_height - y_1_arr
313+ y_2_new = x_1_arr + box_width
314+
315+ y_i = dict ()
316+ y_i ["boxes" ] = np .zeros_like (y [i_image ]["boxes" ])
317+ y_i ["boxes" ][:, 0 ] = x_1_new
318+ y_i ["boxes" ][:, 1 ] = y_1_new
319+ y_i ["boxes" ][:, 2 ] = x_2_new
320+ y_i ["boxes" ][:, 3 ] = y_2_new
321+
322+ y_i ["labels" ] = y [i_image ]["labels" ]
323+ y_i ["scores" ] = y [i_image ]["scores" ]
324+
325+ y_copy .append (y_i )
326+
245327 # 3) adjust brightness:
246328 brightness = random .uniform (* self .brightness_range )
247- x_copy = np .round (brightness * x_copy )
248- x_patch = np .round (brightness * x_patch )
329+ x_copy = np .round (brightness * x_copy / self . learning_rate ) * self . learning_rate
330+ x_patch = np .round (brightness * x_patch / self . learning_rate ) * self . learning_rate
249331
250332 transformations .update ({"brightness" : brightness })
251333
252334 logger .debug ("Transformations: %s" , str (transformations ))
253335
254336 patch_target : List [Dict [str , np .ndarray ]] = list ()
255- predictions = self .estimator .predict (x = x_copy )
337+
338+ if self .targeted :
339+ predictions = y_copy
340+ else :
341+ predictions = self .estimator .predict (x = x_copy , standardise_output = True )
342+
256343 for i_image in range (x_copy .shape [0 ]):
257344 target_dict = dict ()
258345 target_dict ["boxes" ] = predictions [i_image ]["boxes" ]
@@ -385,8 +472,8 @@ def _check_params(self) -> None:
385472 if len (self .brightness_range ) != 2 :
386473 raise ValueError ("The length of brightness range must be 2." )
387474
388- if self .brightness_range [0 ] < 0.0 or self . brightness_range [ 1 ] > 1.0 :
389- raise ValueError ("The brightness range must be between 0.0 and 1 .0." )
475+ if self .brightness_range [0 ] < 0.0 :
476+ raise ValueError ("The brightness range must be >= 0 .0." )
390477
391478 if self .brightness_range [0 ] > self .brightness_range [1 ]:
392479 raise ValueError ("The first element of the brightness range must be less or equal to the second one." )
@@ -408,3 +495,6 @@ def _check_params(self) -> None:
408495 raise ValueError ("The EOT sample size must be of type int." )
409496 if self .sample_size <= 0 :
410497 raise ValueError ("The EOT sample size must be greater than 0." )
498+
499+ if not isinstance (self .targeted , bool ):
500+ raise ValueError ("The argument `targeted` has to be of type bool." )
0 commit comments