Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
SamSamhuns committed Feb 25, 2024
2 parents ff1af68 + 6996f9c commit b90fc4d
Show file tree
Hide file tree
Showing 25 changed files with 771 additions and 573 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ Physical Adversarial Patch Defense

https://github.com/SamSamhuns/yolov5_adversarial/assets/13418507/b389449f-98a4-4167-9208-771cb98ce3ce

VisDrone Dataset Patch | Custom Dataset Patch
:-------------------------:|:-------------------------:
<img src="adv_patch_gen/media/visdrone_p.png" width="256" /> | <img src="adv_patch_gen/media/custom_p.png" width="256" />
| VisDrone Dataset Patch | Custom Dataset Patch |
| :----------------------------------------------------------: | :--------------------------------------------------------: |
| <img src="adv_patch_gen/media/visdrone_p.png" width="256" /> | <img src="adv_patch_gen/media/custom_p.png" width="256" /> |

Note: Install all required dependencies as mentioned in the main YOLOv5 repository and install additional yolov5 adversarial dependency as follows:

Expand All @@ -32,6 +32,7 @@ Detailed instructions for setup and docker use at [adv_patch_gen/README.md](adv_
```shell
python train_patch.py --cfg adv_patch_gen/configs/base.json
```

Instructions in creating the config json file present at [adv_patch_gen/configs/README.md](adv_patch_gen/configs/README.md).

## Test the performance of the adversarial patch
Expand All @@ -41,11 +42,11 @@ python test_patch.py --cfg CONFIG_JSON_FILE -w YOLOV5_TARGET_MODEL_WEIGHTS_PATH
python test_patch.py -h # to get a list of all testing options
```

## Attack Sccess Rate of patches tested against the VisDrone-2019 dataset
## Attack Success Rate of patches tested against the VisDrone-2019 dataset

ASR against coco-pretrained & scratch-trained | ASR against increasing detection confidence threshold
:-------------------------:|:-------------------------:
<img src="adv_patch_gen/media/asr_s_coco_2_s_coco_s_scratch.png" /> | <img src="adv_patch_gen/media/plot_all_avg_0_76.png" />
| ASR against coco-pretrained & scratch-trained | ASR against increasing detection confidence threshold |
| :-----------------------------------------------------------------: | :-----------------------------------------------------: |
| <img src="adv_patch_gen/media/asr_s_coco_2_s_coco_s_scratch.png" /> | <img src="adv_patch_gen/media/plot_all_avg_0_76.png" /> |

<div align="center">
<p>
Expand Down
2 changes: 1 addition & 1 deletion adv_patch_gen/conv_visdrone_2_yolo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ python disp_yolo.py -a ANNOTS_DIR -i IMAGES_DIR

Note: The classes to consider and any additional re-assingment of classes must be done with variables `CLASS_2_CONSIDER` and `CLASS_ID_REMAP` inside `conv_visdrone_2_yolo_fmt.py`.

Can use optional params `low_dim_cutoff` and `low_area_cutoff` to cutoff bunding boxes that do not satisfy a minimum box dimenison or area percentage cutoff.
Can use optional params `low_dim_cutoff` and `low_area_cutoff` to cutoff bunding boxes that do not satisfy a minimum box dimension or area percentage cutoff.

```shell
# example conversion of VisDrone train, val and test set annotations to YOLO format. Use -h for all options
Expand Down
83 changes: 61 additions & 22 deletions adv_patch_gen/conv_visdrone_2_yolo/conv_visdrone_2_yolo_fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,58 @@


def get_parsed_args():
parser = argparse.ArgumentParser(
description="VisDrone to YOLO annot format")
parser.add_argument('--sad', '--source_annot_dir', type=str, dest="source_annot_dir", required=True,
help='VisDrone annotation source dir. Should contain annot txt files (default: %(default)s)')
parser.add_argument('--sid', '--source_image_dir', type=str, dest="source_image_dir", required=True,
help='VisDrone images source dir. Should contain image files (default: %(default)s)')
parser.add_argument('--td', '--target_annot_dir', type=str, dest="target_annot_dir", required=True,
help='YOLO annotation target dir. YOLO by default uses dirname labels (default: %(default)s)')
parser.add_argument('--dc', '--low_dim_cutoff', type=int, dest="low_dim_cutoff", default=None,
help='All bboxes with dims(w/h) < cutoff pixel are ignored i.e 400 (default: %(default)s)')
parser.add_argument('--ac', '--low_area_cutoff', type=float, dest="low_area_cutoff", default=None,
help='All bboxes with area perc < cutoff area perc are ignored i.e. 0.01 (default: %(default)s)')
parser = argparse.ArgumentParser(description="VisDrone to YOLO annot format")
parser.add_argument(
"--sad",
"--source_annot_dir",
type=str,
dest="source_annot_dir",
required=True,
help="VisDrone annotation source dir. Should contain annot txt files (default: %(default)s)",
)
parser.add_argument(
"--sid",
"--source_image_dir",
type=str,
dest="source_image_dir",
required=True,
help="VisDrone images source dir. Should contain image files (default: %(default)s)",
)
parser.add_argument(
"--td",
"--target_annot_dir",
type=str,
dest="target_annot_dir",
required=True,
help="YOLO annotation target dir. YOLO by default uses dirname labels (default: %(default)s)",
)
parser.add_argument(
"--dc",
"--low_dim_cutoff",
type=int,
dest="low_dim_cutoff",
default=None,
help="All bboxes with dims(w/h) < cutoff pixel are ignored i.e 400 (default: %(default)s)",
)
parser.add_argument(
"--ac",
"--low_area_cutoff",
type=float,
dest="low_area_cutoff",
default=None,
help="All bboxes with area perc < cutoff area perc are ignored i.e. 0.01 (default: %(default)s)",
)
args = parser.parse_args()
return args


def conv_visdrone_2_yolo(source_annot_dir: str, source_image_dir: str, target_annot_dir: str, low_dim_cutoff: Optional[int], low_area_cutoff: Optional[float]):
def conv_visdrone_2_yolo(
source_annot_dir: str,
source_image_dir: str,
target_annot_dir: str,
low_dim_cutoff: Optional[int],
low_area_cutoff: Optional[float],
):
"""
low_dim_cutoff: int, lower cutoff for bounding boxes width/height dims in pixels
low_area_cutoff: float, lower area perc cutoff for bounding box areas in perc
Expand All @@ -64,19 +99,19 @@ def conv_visdrone_2_yolo(source_annot_dir: str, source_image_dir: str, target_an
assert len(src_image_paths) == len(src_annot_paths)

os.makedirs(target_annot_dir, exist_ok=True)
low_dim_cutoff = float('-inf') if not low_dim_cutoff else low_dim_cutoff
low_area_cutoff = float('-inf') if not low_area_cutoff else low_area_cutoff
target_img_list_fpath = osp.join(osp.dirname(target_annot_dir), source_annot_dir.split('/')[-2].lower()+".txt")
low_dim_cutoff = float("-inf") if not low_dim_cutoff else low_dim_cutoff
low_area_cutoff = float("-inf") if not low_area_cutoff else low_area_cutoff
target_img_list_fpath = osp.join(osp.dirname(target_annot_dir), source_annot_dir.split("/")[-2].lower() + ".txt")

with tqdm.tqdm(total=len(src_image_paths)) as pbar, open(target_img_list_fpath, "w") as imgw:
orig_box_count = new_box_count = 0
for src_annot_file, src_image_file in zip(src_annot_paths, src_image_paths):
try:
iw, ih = imagesize.get(src_image_file)
target_annot_file = osp.join(target_annot_dir, src_annot_file.split('/')[-1])
target_annot_file = osp.join(target_annot_dir, src_annot_file.split("/")[-1])
with open(src_annot_file, "r") as fr, open(target_annot_file, "w") as fw:
for coords in fr:
annots = list(map(int, coords.strip().strip(',').split(',')))
annots = list(map(int, coords.strip().strip(",").split(",")))
x, y = annots[0], annots[1]
w, h = annots[2], annots[3]
score, class_id, occu = annots[4], annots[5], annots[7]
Expand All @@ -95,8 +130,11 @@ def conv_visdrone_2_yolo(source_annot_dir: str, source_image_dir: str, target_an
class_id = CLASS_ID_REMAP[class_id] if CLASS_ID_REMAP else class_id
fw.write(f"{class_id} {xc/iw} {yc/ih} {w/iw} {h/ih}\n")
new_box_count += 1
target_image_path = osp.join(osp.dirname(osp.dirname(target_annot_file)), "images",
osp.basename(target_annot_file).split('.')[0] + osp.splitext(src_image_file)[1])
target_image_path = osp.join(
osp.dirname(osp.dirname(target_annot_file)),
"images",
osp.basename(target_annot_file).split(".")[0] + osp.splitext(src_image_file)[1],
)
imgw.write(f"{osp.abspath(target_image_path)}\n")
except Exception as excep:
print(f"{excep}: Error reading img {src_image_file}")
Expand All @@ -108,8 +146,9 @@ def conv_visdrone_2_yolo(source_annot_dir: str, source_image_dir: str, target_an
def main():
args = get_parsed_args()
print(args)
conv_visdrone_2_yolo(args.source_annot_dir, args.source_image_dir, args.target_annot_dir,
args.low_dim_cutoff, args.low_area_cutoff)
conv_visdrone_2_yolo(
args.source_annot_dir, args.source_image_dir, args.target_annot_dir, args.low_dim_cutoff, args.low_area_cutoff
)


if __name__ == "__main__":
Expand Down
27 changes: 19 additions & 8 deletions adv_patch_gen/conv_visdrone_2_yolo/disp_visdrone.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
import sys

sys.path.append(".")

import cv2
Expand All @@ -17,20 +18,30 @@


def get_parsed_args():
parser = argparse.ArgumentParser(
description="Disp VisDrone annotated images")
parser.add_argument('-a', '--source_annot_dir', type=str, dest="source_annot_dir", required=True,
help='VisDrone annotation source dir. Should contain annot txt files (default: %(default)s)')
parser.add_argument('-i', '--source_image_dir', type=str, dest="source_image_dir", required=True,
help='VisDrone images source dir. Should contain image files (default: %(default)s)')
parser = argparse.ArgumentParser(description="Disp VisDrone annotated images")
parser.add_argument(
"-a",
"--source_annot_dir",
type=str,
dest="source_annot_dir",
required=True,
help="VisDrone annotation source dir. Should contain annot txt files (default: %(default)s)",
)
parser.add_argument(
"-i",
"--source_image_dir",
type=str,
dest="source_image_dir",
required=True,
help="VisDrone images source dir. Should contain image files (default: %(default)s)",
)
args = parser.parse_args()
return args


def main():
args = get_parsed_args()
annot_paths, image_paths = get_annot_img_paths(
args.source_annot_dir, args.source_image_dir, ANNOT_EXT, IMG_EXT)
annot_paths, image_paths = get_annot_img_paths(args.source_annot_dir, args.source_image_dir, ANNOT_EXT, IMG_EXT)

for ant, img in zip(annot_paths, image_paths):
image = cv2.imread(img)
Expand Down
27 changes: 19 additions & 8 deletions adv_patch_gen/conv_visdrone_2_yolo/disp_yolo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
import sys

sys.path.append(".")

import cv2
Expand All @@ -20,20 +21,30 @@


def get_parsed_args():
parser = argparse.ArgumentParser(
description="Disp YOLO annotated images")
parser.add_argument('-a', '--source_annot_dir', type=str, dest="source_annot_dir", required=True,
help='YOLO annotation source dir. Should contain annot txt files (default: %(default)s)')
parser.add_argument('-i', '--source_image_dir', type=str, dest="source_image_dir", required=True,
help='YOLO images source dir. Should contain image files (default: %(default)s)')
parser = argparse.ArgumentParser(description="Disp YOLO annotated images")
parser.add_argument(
"-a",
"--source_annot_dir",
type=str,
dest="source_annot_dir",
required=True,
help="YOLO annotation source dir. Should contain annot txt files (default: %(default)s)",
)
parser.add_argument(
"-i",
"--source_image_dir",
type=str,
dest="source_image_dir",
required=True,
help="YOLO images source dir. Should contain image files (default: %(default)s)",
)
args = parser.parse_args()
return args


def main():
args = get_parsed_args()
annot_paths, image_paths = get_annot_img_paths(
args.source_annot_dir, args.source_image_dir, ANNOT_EXT, IMG_EXT)
annot_paths, image_paths = get_annot_img_paths(args.source_annot_dir, args.source_image_dir, ANNOT_EXT, IMG_EXT)

for ant, img in zip(annot_paths, image_paths):
image = cv2.imread(img)
Expand Down
6 changes: 4 additions & 2 deletions adv_patch_gen/conv_visdrone_2_yolo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import numpy as np


def get_annot_img_paths(annot_dir: str, image_dir: str, annot_ext: Set[str], img_ext: Set[str]) -> Tuple[List[str], List[str]]:
def get_annot_img_paths(
annot_dir: str, image_dir: str, annot_ext: Set[str], img_ext: Set[str]
) -> Tuple[List[str], List[str]]:
annots_path = os.path.join(annot_dir, "*")
images_path = os.path.join(image_dir, "*")
annot_paths = [p for p in sorted(glob.glob(annots_path)) if os.path.splitext(p)[-1] in annot_ext]
Expand All @@ -19,7 +21,7 @@ def load_visdrone_annots_as_np(annot_file: str) -> np.ndarray:
annot_list = []
with open(annot_file, "r") as f:
for values in f:
annots = list(map(int, values.strip().strip(',').split(',')))
annots = list(map(int, values.strip().strip(",").split(",")))
x1, y1 = annots[0], annots[1]
x2, y2 = x1 + annots[2], y1 + annots[3]
score, class_id, occu = annots[4], annots[5], annots[7]
Expand Down
10 changes: 5 additions & 5 deletions adv_patch_gen/test_notebooks/Loss Function Test.ipynb

Large diffs are not rendered by default.

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions adv_patch_gen/test_notebooks/visdrone_data_analysis.ipynb

Large diffs are not rendered by default.

27 changes: 14 additions & 13 deletions adv_patch_gen/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@ class BColors:
Sample Use:
print(f"{BColors.WARNING}Warning: Information.{BColors.ENDC}"
"""
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'

HEADER = "\033[95m"
OKBLUE = "\033[94m"
OKCYAN = "\033[96m"
OKGREEN = "\033[92m"
WARNING = "\033[93m"
FAIL = "\033[91m"
ENDC = "\033[0m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"


def is_port_in_use(port: int) -> bool:
"""
Checks if a port is free for use
"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as stream:
return stream.connect_ex(('localhost', int(port))) == 0
return stream.connect_ex(("localhost", int(port))) == 0


def pad_to_square(img: Image, pad_rgb: Tuple[int, int, int] = (127, 127, 127)) -> Image:
Expand All @@ -46,17 +47,17 @@ def pad_to_square(img: Image, pad_rgb: Tuple[int, int, int] = (127, 127, 127)) -
else:
if w < h:
padding = (h - w) / 2
padded_img = Image.new('RGB', (h, h), color=pad_rgb)
padded_img = Image.new("RGB", (h, h), color=pad_rgb)
padded_img.paste(img, (int(padding), 0))
else:
padding = (w - h) / 2
padded_img = Image.new('RGB', (w, w), color=pad_rgb)
padded_img = Image.new("RGB", (w, w), color=pad_rgb)
padded_img.paste(img, (0, int(padding)))
return padded_img


def calc_mean_and_std_err(arr: Union[list, np.ndarray]) -> Tuple[float, float]:
""""
""" "
Calculate mean and standard error
"""
mean = np.mean(arr)
Expand Down
18 changes: 11 additions & 7 deletions adv_patch_gen/utils/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@ def load_config_object(cfg_path: str) -> edict:
"""
Loads a config json and returns a edict object
"""
with open(cfg_path, 'r', encoding="utf-8") as json_file:
with open(cfg_path, "r", encoding="utf-8") as json_file:
cfg_dict = json.load(json_file)

return edict(cfg_dict)


def get_argparser(desc="Config file load for training adv patches") -> argparse.ArgumentParser:
"""
Get parser with the default config argument
"""
parser = argparse.ArgumentParser(
description=desc)
parser.add_argument('--cfg', '--config', type=str, dest="config", required=True,
help='Path to JSON config file to use for adv patch generation (default: %(default)s)')
parser = argparse.ArgumentParser(description=desc)
parser.add_argument(
"--cfg",
"--config",
type=str,
dest="config",
required=True,
help="Path to JSON config file to use for adv patch generation (default: %(default)s)",
)
return parser

Loading

0 comments on commit b90fc4d

Please sign in to comment.