forked from ultralytics/yolov5
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1fd419c
commit 94be9ce
Showing
13 changed files
with
4,278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Adversarial Patches against UAE Object Detection | ||
|
||
## Setup | ||
|
||
Tested with python 3.8 | ||
|
||
```shell | ||
python -m venv venv | ||
source venv/bin/activate | ||
pip install tqdm==4.65.0 | ||
pip install imagesize==1.4.1 | ||
pip install opencv-python==4.7.0.72 | ||
``` | ||
|
||
## VisDrone Dataset Format | ||
|
||
Dataset can be downloaded from <https://github.com/VisDrone/VisDrone-Dataset>. | ||
|
||
Annotations for the detections are follows: | ||
|
||
`<bbox_left>, <bbox_top>, <bbox_width>, <bbox_height>, <score>, <object_category>, <truncation>, <occlusion>` | ||
|
||
- <bbox_left> The x coordinate of the top-left corner of the predicted bounding box | ||
- <bbox_top> The y coordinate of the top-left corner of the predicted object bounding box | ||
- <bbox_width> The width in pixels of the predicted object bounding box | ||
- <bbox_height> The height in pixels of the predicted object bounding box | ||
- <score> The score in the DETECTION file indicates the confidence of the predicted bounding box enclosing an object instance. The score in GROUNDTRUTH file is set to 1 or 0. 1 indicates the bounding box is considered in evaluation, while 0 indicates the bounding box will be ignored. | ||
- <object_category> The object category indicates the type of annotated object, (i.e., ignored regions(0), pedestrian(1), people(2), bicycle(3), car(4), van(5), truck(6), tricycle(7), awning-tricycle(8), bus(9), motor(10), others(11)) | ||
- <truncation> The score in the DETECTION result file should be set to the constant -1.The score in the GROUNDTRUTH file indicates the degree of object parts appears outside a frame (i.e., no truncation = 0 (truncation ratio 0%), and partial truncation = 1 (truncation ratio 1% ~ 50%)). | ||
- <occlusion> The score in the DETECTION file should be set to the constant -1. The score in the GROUNDTRUTH file indicates the fraction of objects being occluded (i.e., no occlusion = 0 (occlusion ratio 0%), partial occlusion = 1 (occlusion ratio 1% ~ 50%), and heavy occlusion = 2 (occlusion ratio 50% ~ 100%)). | ||
|
||
## Download VisDrone Dataset | ||
|
||
Download Dataset from <https://github.com/VisDrone/VisDrone-Dataset> and unzip and place under top level directory `data`. | ||
|
||
Alternatively, use gdown to download the zip files from the command line. | ||
|
||
```shell | ||
mkdir data | ||
cd data | ||
pip install gdown | ||
# object detection train subset | ||
gdown 1a2oHjcEcwXP8oUF95qiwrqzACb2YlUhn | ||
# object detection val subset | ||
gdown 1bxK5zgLn0_L8x276eKkuYA_FzwCIjb59 | ||
# object detection test subset | ||
gdown 1PFdW_VFSCfZ_sTSZAGjQdifF_Xd5mf0V | ||
|
||
# unzip all data | ||
unzip VisDrone2019-DET-test-dev.zip | ||
unzip VisDrone2019-DET-train.zip | ||
unzip VisDrone2019-DET-val.zip | ||
|
||
# remove all zip files | ||
rm *.zip | ||
``` | ||
|
||
## Visualize Images | ||
|
||
Running annotation visualizations for VisDrone or YOLO format. | ||
|
||
```shell | ||
python disp_visdrone.py -a ANNOTS_DIR -i IMAGES_DIR | ||
python disp_yolo.py -a ANNOTS_DIR -i IMAGES_DIR | ||
``` | ||
|
||
## Convert VisDrone to YOLO annotation format | ||
|
||
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. | ||
|
||
```shell | ||
# example conversion of VisDrone train, val and test set annotations to YOLO format. Use -h for all options | ||
python conv_visdrone_2_yolo_fmt.py --sad data/VisDrone2019-DET-train/annotations --sid data/VisDrone2019-DET-train/images --td data/VisDrone2019-DET-train/labels | ||
python conv_visdrone_2_yolo_fmt.py --sad data/VisDrone2019-DET-val/annotations --sid data/VisDrone2019-DET-val/images --td data/VisDrone2019-DET-val/labels | ||
python conv_visdrone_2_yolo_fmt.py --sad data/VisDrone2019-DET-test-dev/annotations --sid data/VisDrone2019-DET-test-dev/images --td data/VisDrone2019-DET-test-dev/labels | ||
``` | ||
|
||
Note: To convert to COCO annotation format refer to <https://github.com/SamSamhuns/ml_data_processing/tree/master/annotation_format_conv> | ||
|
||
## References | ||
|
||
- [VisDrone](https://github.com/VisDrone/VisDrone-Dataset) |
116 changes: 116 additions & 0 deletions
116
adv_patch_gen/conv_visdrone_2_yolo/conv_visdrone_2_yolo_fmt.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
""" | ||
Convert VisDrone annotation format to YOLO labels format | ||
Works for training of YOLOv5 and YOLOv7. | ||
YOLOv7 requires an additional txt file (Same name as the first parent directory) with paths to the images for the train, val & test splits | ||
""" | ||
import os | ||
import os.path as osp | ||
import glob | ||
import argparse | ||
from typing import Optional | ||
|
||
import tqdm | ||
import imagesize | ||
|
||
# VisDrone annot fmt | ||
# <bbox_left>, <bbox_top>, <bbox_width>, <bbox_height>, <score>, <object_category>, <truncation>, <occlusion> | ||
# | ||
# classes: | ||
# ignore(0), pedestrian(1), people(2), bicycle(3), | ||
# car(4), van(5), truck(6), tricycle(7), | ||
# awning-tricycle(8), bus(9), motor(10), others(11) | ||
|
||
# YOLO annot fmt | ||
# One row per object | ||
# Each row is class x_center y_center width height format. | ||
# Box coordinates must be in normalized xywh format (from 0 - 1). | ||
# If boxes are in pixels, divide x_center and width by image width, and y_center and height by image height. | ||
# Class numbers are zero-indexed (start from 0). | ||
|
||
|
||
CLASS_2_CONSIDER = {1, 2, 4, 5, 6, 9} # only get pedestrian, people, car, van, truck, bus classes from VisDrone | ||
CLASS_ID_REMAP = {4: 0, 5: 0, 6: 1, 9: 2, 1: 3, 2: 3} # optionally remap class ids, can be set to None | ||
IMG_EXT = {".jpg", ".png"} | ||
|
||
|
||
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)') | ||
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]): | ||
""" | ||
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 | ||
""" | ||
if not all([osp.isdir(source_annot_dir), osp.isdir(source_image_dir)]): | ||
raise ValueError(f"source_annot_dir and source_image_dir must be directories") | ||
src_annot_path = osp.join(source_annot_dir, "*") | ||
src_image_path = osp.join(source_image_dir, "*") | ||
src_annot_paths = sorted(glob.glob(src_annot_path)) | ||
src_image_paths = [p for p in sorted(glob.glob(src_image_path)) if osp.splitext(p)[-1] in IMG_EXT] | ||
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") | ||
|
||
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]) | ||
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(','))) | ||
x, y = annots[0], annots[1] | ||
w, h = annots[2], annots[3] | ||
score, class_id, occu = annots[4], annots[5], annots[7] | ||
if class_id not in CLASS_2_CONSIDER: # only keep classes to consider | ||
continue | ||
orig_box_count += 1 | ||
if w < low_dim_cutoff or h < low_dim_cutoff: # cutoff value for dims to remove outliers | ||
continue | ||
area_perc = 100 * (w * h) / (iw * ih) | ||
if area_perc < low_area_cutoff: # cutoff value for area perc to remove outliers | ||
continue | ||
|
||
xc, yc = x + (w / 2), y + (h / 2) | ||
# only use objects used for eval along and all levels of occlusion (0,1,2) | ||
if score and occu <= 2: | ||
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]) | ||
imgw.write(f"{osp.abspath(target_image_path)}\n") | ||
except Exception as excep: | ||
print(f"{excep}: Error reading img {src_image_file}") | ||
pbar.update(1) | ||
print(f"Original Box Count: {orig_box_count}. Converted Box Count {new_box_count}") | ||
print(f"{100 * (new_box_count) / orig_box_count:.2f}% of total boxes kept") | ||
|
||
|
||
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) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import argparse | ||
import sys | ||
sys.path.append(".") | ||
|
||
import cv2 | ||
from adv_patch_gen.conv_visdrone_2_yolo.utils import get_annot_img_paths, load_visdrone_annots_as_np | ||
|
||
|
||
# visdrone dataset classes | ||
# ignore(0), pedestrian(1), people(2), bicycle(3), | ||
# car(4), van(5), truck(6), tricycle(7), awning-tricycle(8), | ||
# bus(9), motor(10), others(11) | ||
|
||
ANNOT_EXT = {".txt"} | ||
IMG_EXT = {".jpg", ".png"} | ||
CLASS_IDS_2_CONSIDER = {4, 5, 6, 9} | ||
|
||
|
||
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)') | ||
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) | ||
|
||
for ant, img in zip(annot_paths, image_paths): | ||
image = cv2.imread(img) | ||
annots = load_visdrone_annots_as_np(ant) | ||
for annot in annots: | ||
score, class_id = annot[4], annot[5] | ||
if class_id not in CLASS_IDS_2_CONSIDER: # car, van ,truck, bus | ||
continue | ||
color = (0, 0, 255) if score == 0 else (0, 255, 0) | ||
x1, y1, x2, y2 = annot[:4] | ||
cv2.rectangle(image, (x1, y1), (x2, y2), color, 1) | ||
|
||
cv2.imshow("VisDrone annot visualized", image) | ||
key = cv2.waitKey() | ||
if key == ord("q"): | ||
cv2.destroyAllWindows() | ||
exit() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import argparse | ||
import sys | ||
sys.path.append(".") | ||
|
||
import cv2 | ||
from adv_patch_gen.conv_visdrone_2_yolo.utils import get_annot_img_paths, load_yolo_annots_as_np | ||
|
||
|
||
# visdrone dataset classes | ||
# ignore(0), pedestrian(1), people(2), bicycle(3), | ||
# car(4), van(5), truck(6), tricycle(7), awning-tricycle(8), | ||
# bus(9), motor(10), others(11) | ||
# CLASS_IDS_2_CONSIDER = {4, 5, 6, 9} | ||
|
||
ANNOT_EXT = {".txt"} | ||
IMG_EXT = {".jpg", ".png"} | ||
# custom yolo classes | ||
# car(0), van(1), truck(2), bus(3) | ||
CLASS_IDS_2_CONSIDER = {0, 1, 2, 3} | ||
|
||
|
||
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)') | ||
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) | ||
|
||
for ant, img in zip(annot_paths, image_paths): | ||
image = cv2.imread(img) | ||
annots = load_yolo_annots_as_np(ant) | ||
for annot in annots: | ||
class_id = annot[0] | ||
if class_id not in CLASS_IDS_2_CONSIDER: # car, van ,truck, bus | ||
continue | ||
ih, iw, _ = image.shape | ||
xc, yc, w, h = annot[1] * iw, annot[2] * ih, annot[3] * iw, annot[4] * ih | ||
x1, y1, x2, y2 = xc - w / 2, yc - h / 2, xc + w / 2, yc + h / 2 | ||
x1, y1, x2, y2 = map(int, (x1, y1, x2, y2)) | ||
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 0, 255), 1) | ||
|
||
cv2.imshow("YOLO annot visualized", image) | ||
key = cv2.waitKey() | ||
if key == ord("q"): | ||
cv2.destroyAllWindows() | ||
exit() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import os | ||
import glob | ||
from typing import List, Tuple, Set | ||
|
||
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]]: | ||
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] | ||
image_paths = [p for p in sorted(glob.glob(images_path)) if os.path.splitext(p)[-1] in img_ext] | ||
|
||
assert len(annot_paths) == len(image_paths) | ||
return annot_paths, image_paths | ||
|
||
|
||
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(','))) | ||
x1, y1 = annots[0], annots[1] | ||
x2, y2 = x1 + annots[2], y1 + annots[3] | ||
score, class_id, occu = annots[4], annots[5], annots[7] | ||
annot_list.append([x1, y1, x2, y2, score, class_id, occu]) | ||
return np.asarray(annot_list) | ||
|
||
|
||
def load_yolo_annots_as_np(annot_file: str) -> np.ndarray: | ||
annot_list = [] | ||
with open(annot_file, "r") as f: | ||
for values in f: | ||
annots = list(map(float, values.strip().split())) | ||
class_id = annots[0] | ||
xc, yc = annots[1], annots[2] | ||
w, h = annots[3], annots[4] | ||
annot_list.append([class_id, xc, yc, w, h]) | ||
return np.asarray(annot_list) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
0.10588,0.054902,0.1098 | ||
0.48235,0.094118,0.16863 | ||
0.50196,0.52549,0.17647 | ||
0.082353,0.31765,0.18431 | ||
0.47843,0.61176,0.51765 | ||
0.07451,0.3098,0.45882 | ||
0.67843,0.14902,0.18039 | ||
0.086275,0.14118,0.26275 | ||
0.26667,0.36863,0.47843 | ||
0.76078,0.54118,0.5451 | ||
0.73333,0.49412,0.27451 | ||
0.25882,0.35294,0.18039 | ||
0.47843,0.22353,0.36471 | ||
0.27059,0.086275,0.11765 | ||
0.7098,0.32157,0.2 | ||
0.27451,0.13725,0.29412 | ||
0.75294,0.75686,0.63137 | ||
0.28627,0.54902,0.41176 | ||
0.47451,0.2902,0.15294 | ||
0.74902,0.70196,0.28627 | ||
0.098039,0.42745,0.44314 | ||
0.50588,0.65098,0.65882 | ||
0.12549,0.42745,0.23529 | ||
0.4902,0.58431,0.33725 | ||
0.26275,0.49412,0.26275 | ||
0.07451,0.14902,0.12549 | ||
0.090196,0.20392,0.36078 | ||
0.68627,0.15686,0.30196 | ||
0.30196,0.5451,0.57647 | ||
0.71765,0.32941,0.40784 |
Oops, something went wrong.