├── COCO2YOLO ├── general_json2yolo.py └── utils.py ├── CrowHuman2YOLO └── data │ ├── crowdhuman-template.data │ ├── crowdhuman.names │ ├── gen_txts.py │ ├── prepare_data.sh │ ├── raw │ └── 这里放置下载的原始文件 │ └── verify_txts.py ├── README.md ├── YOLOv5 └── crowdhuman.yaml ├── check_yolov5_label_format.py ├── confusion_matrix_test ├── confusion_matrix_test.py └── general.py ├── look_up_anchor.py ├── voc2yolo.py └── widerface2yolo.py /COCO2YOLO/general_json2yolo.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import cv2 4 | import pandas as pd 5 | from PIL import Image 6 | 7 | from utils import * 8 | 9 | 10 | # Convert INFOLKS JSON file into YOLO-format labels ---------------------------- 11 | def convert_infolks_json(name, files, img_path): 12 | # Create folders 13 | path = make_dirs() 14 | 15 | # Import json 16 | data = [] 17 | for file in glob.glob(files): 18 | with open(file) as f: 19 | jdata = json.load(f) 20 | jdata['json_file'] = file 21 | data.append(jdata) 22 | 23 | # Write images and shapes 24 | name = path + os.sep + name 25 | file_id, file_name, wh, cat = [], [], [], [] 26 | for x in tqdm(data, desc='Files and Shapes'): 27 | f = glob.glob(img_path + Path(x['json_file']).stem + '.*')[0] 28 | file_name.append(f) 29 | wh.append(exif_size(Image.open(f))) # (width, height) 30 | cat.extend(a['classTitle'].lower() for a in x['output']['objects']) # categories 31 | 32 | # filename 33 | with open(name + '.txt', 'a') as file: 34 | file.write('%s\n' % f) 35 | 36 | # Write *.names file 37 | names = sorted(np.unique(cat)) 38 | # names.pop(names.index('Missing product')) # remove 39 | with open(name + '.names', 'a') as file: 40 | [file.write('%s\n' % a) for a in names] 41 | 42 | # Write labels file 43 | for i, x in enumerate(tqdm(data, desc='Annotations')): 44 | label_name = Path(file_name[i]).stem + '.txt' 45 | 46 | with open(path + '/labels/' + label_name, 'a') as file: 47 | for a in x['output']['objects']: 48 | # if a['classTitle'] == 'Missing product': 49 | # continue # skip 50 | 51 | category_id = names.index(a['classTitle'].lower()) 52 | 53 | # The INFOLKS bounding box format is [x-min, y-min, x-max, y-max] 54 | box = np.array(a['points']['exterior'], dtype=np.float32).ravel() 55 | box[[0, 2]] /= wh[i][0] # normalize x by width 56 | box[[1, 3]] /= wh[i][1] # normalize y by height 57 | box = [box[[0, 2]].mean(), box[[1, 3]].mean(), box[2] - box[0], box[3] - box[1]] # xywh 58 | if (box[2] > 0.) and (box[3] > 0.): # if w > 0 and h > 0 59 | file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box)) 60 | 61 | # Split data into train, test, and validate files 62 | split_files(name, file_name) 63 | write_data_data(name + '.data', nc=len(names)) 64 | print('Done. Output saved to %s' % (os.getcwd() + os.sep + path)) 65 | 66 | 67 | # Convert vott JSON file into YOLO-format labels ------------------------------- 68 | def convert_vott_json(name, files, img_path): 69 | # Create folders 70 | path = make_dirs() 71 | name = path + os.sep + name 72 | 73 | # Import json 74 | data = [] 75 | for file in glob.glob(files): 76 | with open(file) as f: 77 | jdata = json.load(f) 78 | jdata['json_file'] = file 79 | data.append(jdata) 80 | 81 | # Get all categories 82 | file_name, wh, cat = [], [], [] 83 | for i, x in enumerate(tqdm(data, desc='Files and Shapes')): 84 | try: 85 | cat.extend(a['tags'][0] for a in x['regions']) # categories 86 | except: 87 | pass 88 | 89 | # Write *.names file 90 | names = sorted(pd.unique(cat)) 91 | with open(name + '.names', 'a') as file: 92 | [file.write('%s\n' % a) for a in names] 93 | 94 | # Write labels file 95 | n1, n2 = 0, 0 96 | missing_images = [] 97 | for i, x in enumerate(tqdm(data, desc='Annotations')): 98 | 99 | f = glob.glob(img_path + x['asset']['name'] + '.jpg') 100 | if len(f): 101 | f = f[0] 102 | file_name.append(f) 103 | wh = exif_size(Image.open(f)) # (width, height) 104 | 105 | n1 += 1 106 | if (len(f) > 0) and (wh[0] > 0) and (wh[1] > 0): 107 | n2 += 1 108 | 109 | # append filename to list 110 | with open(name + '.txt', 'a') as file: 111 | file.write('%s\n' % f) 112 | 113 | # write labelsfile 114 | label_name = Path(f).stem + '.txt' 115 | with open(path + '/labels/' + label_name, 'a') as file: 116 | for a in x['regions']: 117 | category_id = names.index(a['tags'][0]) 118 | 119 | # The INFOLKS bounding box format is [x-min, y-min, x-max, y-max] 120 | box = a['boundingBox'] 121 | box = np.array([box['left'], box['top'], box['width'], box['height']]).ravel() 122 | box[[0, 2]] /= wh[0] # normalize x by width 123 | box[[1, 3]] /= wh[1] # normalize y by height 124 | box = [box[0] + box[2] / 2, box[1] + box[3] / 2, box[2], box[3]] # xywh 125 | 126 | if (box[2] > 0.) and (box[3] > 0.): # if w > 0 and h > 0 127 | file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box)) 128 | else: 129 | missing_images.append(x['asset']['name']) 130 | 131 | print('Attempted %g json imports, found %g images, imported %g annotations successfully' % (i, n1, n2)) 132 | if len(missing_images): 133 | print('WARNING, missing images:', missing_images) 134 | 135 | # Split data into train, test, and validate files 136 | split_files(name, file_name) 137 | print('Done. Output saved to %s' % (os.getcwd() + os.sep + path)) 138 | 139 | 140 | # Convert ath JSON file into YOLO-format labels -------------------------------- 141 | def convert_ath_json(json_dir): # dir contains json annotations and images 142 | # Create folders 143 | dir = make_dirs() # output directory 144 | 145 | jsons = [] 146 | for dirpath, dirnames, filenames in os.walk(json_dir): 147 | for filename in [f for f in filenames if f.lower().endswith('.json')]: 148 | jsons.append(os.path.join(dirpath, filename)) 149 | 150 | # Import json 151 | n1, n2, n3 = 0, 0, 0 152 | missing_images, file_name = [], [] 153 | for json_file in sorted(jsons): 154 | with open(json_file) as f: 155 | data = json.load(f) 156 | 157 | # # Get classes 158 | # try: 159 | # classes = list(data['_via_attributes']['region']['class']['options'].values()) # classes 160 | # except: 161 | # classes = list(data['_via_attributes']['region']['Class']['options'].values()) # classes 162 | 163 | # # Write *.names file 164 | # names = pd.unique(classes) # preserves sort order 165 | # with open(dir + 'data.names', 'w') as f: 166 | # [f.write('%s\n' % a) for a in names] 167 | 168 | # Write labels file 169 | for i, x in enumerate(tqdm(data['_via_img_metadata'].values(), desc='Processing %s' % json_file)): 170 | 171 | image_file = str(Path(json_file).parent / x['filename']) 172 | f = glob.glob(image_file) # image file 173 | if len(f): 174 | f = f[0] 175 | file_name.append(f) 176 | wh = exif_size(Image.open(f)) # (width, height) 177 | 178 | n1 += 1 # all images 179 | if len(f) > 0 and wh[0] > 0 and wh[1] > 0: 180 | label_file = dir + 'labels/' + Path(f).stem + '.txt' 181 | 182 | nlabels = 0 183 | try: 184 | with open(label_file, 'a') as file: # write labelsfile 185 | for a in x['regions']: 186 | # try: 187 | # category_id = int(a['region_attributes']['class']) 188 | # except: 189 | # category_id = int(a['region_attributes']['Class']) 190 | category_id = 0 # single-class 191 | 192 | # bounding box format is [x-min, y-min, x-max, y-max] 193 | box = a['shape_attributes'] 194 | box = np.array([box['x'], box['y'], box['width'], box['height']], 195 | dtype=np.float32).ravel() 196 | box[[0, 2]] /= wh[0] # normalize x by width 197 | box[[1, 3]] /= wh[1] # normalize y by height 198 | box = [box[0] + box[2] / 2, box[1] + box[3] / 2, box[2], 199 | box[3]] # xywh (left-top to center x-y) 200 | 201 | if box[2] > 0. and box[3] > 0.: # if w > 0 and h > 0 202 | file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box)) 203 | n3 += 1 204 | nlabels += 1 205 | 206 | if nlabels == 0: # remove non-labelled images from dataset 207 | os.system('rm %s' % label_file) 208 | # print('no labels for %s' % f) 209 | continue # next file 210 | 211 | # write image 212 | img_size = 4096 # resize to maximum 213 | img = cv2.imread(f) # BGR 214 | assert img is not None, 'Image Not Found ' + f 215 | r = img_size / max(img.shape) # size ratio 216 | if r < 1: # downsize if necessary 217 | h, w, _ = img.shape 218 | img = cv2.resize(img, (int(w * r), int(h * r)), interpolation=cv2.INTER_AREA) 219 | 220 | ifile = dir + 'images/' + Path(f).name 221 | if cv2.imwrite(ifile, img): # if success append image to list 222 | with open(dir + 'data.txt', 'a') as file: 223 | file.write('%s\n' % ifile) 224 | n2 += 1 # correct images 225 | 226 | except: 227 | os.system('rm %s' % label_file) 228 | print('problem with %s' % f) 229 | 230 | else: 231 | missing_images.append(image_file) 232 | 233 | nm = len(missing_images) # number missing 234 | print('\nFound %g JSONs with %g labels over %g images. Found %g images, labelled %g images successfully' % 235 | (len(jsons), n3, n1, n1 - nm, n2)) 236 | if len(missing_images): 237 | print('WARNING, missing images:', missing_images) 238 | 239 | # Write *.names file 240 | names = ['knife'] # preserves sort order 241 | with open(dir + 'data.names', 'w') as f: 242 | [f.write('%s\n' % a) for a in names] 243 | 244 | # Split data into train, test, and validate files 245 | split_rows_simple(dir + 'data.txt') 246 | write_data_data(dir + 'data.data', nc=1) 247 | print('Done. Output saved to %s' % Path(dir).absolute()) 248 | 249 | 250 | def convert_coco_json(json_dir='../coco/annotations/', use_segments=False): 251 | save_dir = make_dirs() # output directory 252 | jsons = glob.glob(json_dir + '*.json') 253 | coco80 = coco91_to_coco80_class() 254 | 255 | # Import json 256 | for json_file in sorted(jsons): 257 | fn = Path(save_dir) / 'labels' / Path(json_file).stem.replace('instances_', '') # folder name 258 | fn.mkdir() 259 | with open(json_file) as f: 260 | data = json.load(f) 261 | 262 | # Create image dict 263 | images = {'%g' % x['id']: x for x in data['images']} 264 | 265 | # Write labels file 266 | for x in tqdm(data['annotations'], desc='Annotations %s' % json_file): 267 | if x['iscrowd']: 268 | continue 269 | 270 | img = images['%g' % x['image_id']] 271 | h, w, f = img['height'], img['width'], img['file_name'] 272 | 273 | # The COCO box format is [top left x, top left y, width, height] 274 | box = np.array(x['bbox'], dtype=np.float64) 275 | box[:2] += box[2:] / 2 # xy top-left corner to center 276 | box[[0, 2]] /= w # normalize x 277 | box[[1, 3]] /= h # normalize y 278 | 279 | # Segments 280 | segments = [j for i in x['segmentation'] for j in i] # all segments concatenated 281 | s = (np.array(segments).reshape(-1, 2) / np.array([w, h])).reshape(-1).tolist() 282 | 283 | # Write 284 | if box[2] > 0 and box[3] > 0: # if w > 0 and h > 0 285 | line = coco80[x['category_id'] - 1], *(s if use_segments else box) # cls, box or segments 286 | with open((fn / f).with_suffix('.txt'), 'a') as file: 287 | file.write(('%g ' * len(line)).rstrip() % line + '\n') 288 | 289 | 290 | if __name__ == '__main__': 291 | source = 'coco' 292 | 293 | if source == 'coco': 294 | convert_coco_json('../../Downloads/coco/annotations/') 295 | 296 | 297 | 298 | # zip results 299 | # os.system('zip -r ../coco.zip ../coco') 300 | -------------------------------------------------------------------------------- /COCO2YOLO/utils.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import shutil 4 | from pathlib import Path 5 | 6 | import numpy as np 7 | from PIL import ExifTags 8 | from tqdm import tqdm 9 | 10 | # Parameters 11 | img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng'] # acceptable image suffixes 12 | vid_formats = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv'] # acceptable video suffixes 13 | 14 | # Get orientation exif tag 15 | for orientation in ExifTags.TAGS.keys(): 16 | if ExifTags.TAGS[orientation] == 'Orientation': 17 | break 18 | 19 | 20 | def exif_size(img): 21 | # Returns exif-corrected PIL size 22 | s = img.size # (width, height) 23 | try: 24 | rotation = dict(img._getexif().items())[orientation] 25 | if rotation == 6: # rotation 270 26 | s = (s[1], s[0]) 27 | elif rotation == 8: # rotation 90 28 | s = (s[1], s[0]) 29 | except: 30 | pass 31 | 32 | return s 33 | 34 | 35 | def split_rows_simple(file='../data/sm4/out.txt'): # from utils import *; split_rows_simple() 36 | # splits one textfile into 3 smaller ones based upon train, test, val ratios 37 | with open(file) as f: 38 | lines = f.readlines() 39 | 40 | s = Path(file).suffix 41 | lines = sorted(list(filter(lambda x: len(x) > 0, lines))) 42 | i, j, k = split_indices(lines, train=0.9, test=0.1, validate=0.0) 43 | for k, v in {'train': i, 'test': j, 'val': k}.items(): # key, value pairs 44 | if v.any(): 45 | new_file = file.replace(s, '_' + k + s) 46 | with open(new_file, 'w') as f: 47 | f.writelines([lines[i] for i in v]) 48 | 49 | 50 | def split_files(out_path, file_name, prefix_path=''): # split training data 51 | file_name = list(filter(lambda x: len(x) > 0, file_name)) 52 | file_name = sorted(file_name) 53 | i, j, k = split_indices(file_name, train=0.9, test=0.1, validate=0.0) 54 | datasets = {'train': i, 'test': j, 'val': k} 55 | for key, item in datasets.items(): 56 | if item.any(): 57 | with open(out_path + '_' + key + '.txt', 'a') as file: 58 | for i in item: 59 | file.write('%s%s\n' % (prefix_path, file_name[i])) 60 | 61 | 62 | def split_indices(x, train=0.9, test=0.1, validate=0.0, shuffle=True): # split training data 63 | n = len(x) 64 | v = np.arange(n) 65 | if shuffle: 66 | np.random.shuffle(v) 67 | 68 | i = round(n * train) # train 69 | j = round(n * test) + i # test 70 | k = round(n * validate) + j # validate 71 | return v[:i], v[i:j], v[j:k] # return indices 72 | 73 | 74 | def make_dirs(dir='new_dir/'): 75 | # Create folders 76 | dir = Path(dir) 77 | if dir.exists(): 78 | shutil.rmtree(dir) # delete dir 79 | for p in dir, dir / 'labels', dir / 'images': 80 | p.mkdir(parents=True, exist_ok=True) # make dir 81 | return dir 82 | 83 | 84 | def write_data_data(fname='data.data', nc=80): 85 | # write darknet *.data file 86 | lines = ['classes = %g\n' % nc, 87 | 'train =../out/data_train.txt\n', 88 | 'valid =../out/data_test.txt\n', 89 | 'names =../out/data.names\n', 90 | 'backup = backup/\n', 91 | 'eval = coco\n'] 92 | 93 | with open(fname, 'a') as f: 94 | f.writelines(lines) 95 | 96 | 97 | def image_folder2file(folder='images/'): # from utils import *; image_folder2file() 98 | # write a txt file listing all imaged in folder 99 | s = glob.glob(folder + '*.*') 100 | with open(folder[:-1] + '.txt', 'w') as file: 101 | for l in s: 102 | file.write(l + '\n') # write image list 103 | 104 | 105 | def add_coco_background(path='../data/sm4/', n=1000): # from utils import *; add_coco_background() 106 | # add coco background to sm4 in outb.txt 107 | p = path + 'background' 108 | if os.path.exists(p): 109 | shutil.rmtree(p) # delete output folder 110 | os.makedirs(p) # make new output folder 111 | 112 | # copy images 113 | for image in glob.glob('../coco/images/train2014/*.*')[:n]: 114 | os.system('cp %s %s' % (image, p)) 115 | 116 | # add to outb.txt and make train, test.txt files 117 | f = path + 'out.txt' 118 | fb = path + 'outb.txt' 119 | os.system('cp %s %s' % (f, fb)) 120 | with open(fb, 'a') as file: 121 | file.writelines(i + '\n' for i in glob.glob(p + '/*.*')) 122 | split_rows_simple(file=fb) 123 | 124 | 125 | def create_single_class_dataset(path='../data/sm3'): # from utils import *; create_single_class_dataset('../data/sm3/') 126 | # creates a single-class version of an existing dataset 127 | os.system('mkdir %s_1cls' % path) 128 | 129 | 130 | def flatten_recursive_folders(path='../../Downloads/data/sm4/'): # from utils import *; flatten_recursive_folders() 131 | # flattens nested folders in path/images and path/JSON into single folders 132 | idir, jdir = path + 'images/', path + 'json/' 133 | nidir, njdir = Path(path + 'images_flat/'), Path(path + 'json_flat/') 134 | n = 0 135 | 136 | # Create output folders 137 | for p in [nidir, njdir]: 138 | if os.path.exists(p): 139 | shutil.rmtree(p) # delete output folder 140 | os.makedirs(p) # make new output folder 141 | 142 | for parent, dirs, files in os.walk(idir): 143 | for f in tqdm(files, desc=parent): 144 | f = Path(f) 145 | stem, suffix = f.stem, f.suffix 146 | if suffix.lower()[1:] in img_formats: 147 | n += 1 148 | stem_new = '%g_' % n + stem 149 | image_new = nidir / (stem_new + suffix) # converts all formats to *.jpg 150 | json_new = njdir / (stem_new + '.json') 151 | 152 | image = parent / f 153 | json = Path(parent.replace('images', 'json')) / str(f).replace(suffix, '.json') 154 | 155 | os.system("cp '%s' '%s'" % (json, json_new)) 156 | os.system("cp '%s' '%s'" % (image, image_new)) 157 | # cv2.imwrite(str(image_new), cv2.imread(str(image))) 158 | 159 | print('Flattening complete: %g jsons and images' % n) 160 | 161 | 162 | def coco91_to_coco80_class(): # converts 80-index (val2014) to 91-index (paper) 163 | # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/ 164 | # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n') 165 | # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n') 166 | # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco 167 | # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet 168 | x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, None, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, None, 24, 25, None, 169 | None, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, None, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 170 | 51, 52, 53, 54, 55, 56, 57, 58, 59, None, 60, None, None, 61, None, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 171 | None, 73, 74, 75, 76, 77, 78, 79, None] 172 | return x 173 | -------------------------------------------------------------------------------- /CrowHuman2YOLO/data/crowdhuman-template.data: -------------------------------------------------------------------------------- 1 | classes = 2 2 | train = data/crowdhuman-{width}x{height}/train.txt 3 | valid = data/crowdhuman-{width}x{height}/test.txt 4 | names = data/crowdhuman.names 5 | backup = backup/ 6 | -------------------------------------------------------------------------------- /CrowHuman2YOLO/data/crowdhuman.names: -------------------------------------------------------------------------------- 1 | head 2 | person 3 | -------------------------------------------------------------------------------- /CrowHuman2YOLO/data/gen_txts.py: -------------------------------------------------------------------------------- 1 | """gen_txts.py 2 | 3 | To generate YOLO txt files from the original CrowdHuman annotations. 4 | Please also refer to README.md in this directory. 5 | 6 | Inputs: 7 | * raw/annotation_train.odgt 8 | * raw/annotation_val.odgt 9 | * crowdhuman-{width}x{height}/[IDs].jpg 10 | 11 | Outputs: 12 | * crowdhuman-{width}x{height}train.txt 13 | * crowdhuman-{width}x{height}/test.txt 14 | * crowdhuman-{width}x{height}/[IDs].txt (one annotation for each image in the training or test set) 15 | """ 16 | 17 | 18 | import json 19 | from pathlib import Path 20 | from argparse import ArgumentParser 21 | 22 | import numpy as np 23 | import cv2 24 | 25 | 26 | # input image width/height of the yolov4 model, set by command-line argument 27 | INPUT_WIDTH = 0 28 | INPUT_HEIGHT = 0 29 | 30 | # Minimum width/height of objects for detection (don't learn from 31 | # objects smaller than these 32 | MIN_W = 5 33 | MIN_H = 5 34 | 35 | # Do K-Means clustering in order to determine "anchor" sizes 36 | DO_KMEANS = True 37 | KMEANS_CLUSTERS = 9 38 | BBOX_WHS = [] # keep track of bbox width/height with respect to 608x608 39 | 40 | 41 | def image_shape(ID, image_dir): 42 | assert image_dir is not None 43 | jpg_path = image_dir / ('%s.jpg' % ID) 44 | img = cv2.imread(jpg_path.as_posix()) 45 | return img.shape 46 | 47 | 48 | def txt_line(cls, bbox, img_w, img_h): 49 | """Generate 1 line in the txt file.""" 50 | assert INPUT_WIDTH > 0 and INPUT_HEIGHT > 0 51 | x, y, w, h = bbox 52 | x = max(int(x), 0) 53 | y = max(int(y), 0) 54 | w = min(int(w), img_w - x) 55 | h = min(int(h), img_h - y) 56 | w_rescaled = float(w) * INPUT_WIDTH / img_w 57 | h_rescaled = float(h) * INPUT_HEIGHT / img_h 58 | if w_rescaled < MIN_W or h_rescaled < MIN_H: 59 | return '' 60 | else: 61 | if DO_KMEANS: 62 | global BBOX_WHS 63 | BBOX_WHS.append((w_rescaled, h_rescaled)) 64 | cx = (x + w / 2.) / img_w 65 | cy = (y + h / 2.) / img_h 66 | nw = float(w) / img_w 67 | nh = float(h) / img_h 68 | return '%d %.6f %.6f %.6f %.6f\n' % (cls, cx, cy, nw, nh) 69 | 70 | 71 | def process(set_='test', annotation_filename='raw/annotation_val.odgt', 72 | output_dir=None): 73 | """Process either 'train' or 'test' set.""" 74 | assert output_dir is not None 75 | output_dir.mkdir(exist_ok=True) 76 | jpgs = [] 77 | with open(annotation_filename, 'r') as fanno: 78 | for raw_anno in fanno.readlines(): 79 | anno = json.loads(raw_anno) 80 | ID = anno['ID'] # e.g. '273271,c9db000d5146c15' 81 | print('Processing ID: %s' % ID) 82 | img_h, img_w, img_c = image_shape(ID, output_dir) 83 | assert img_c == 3 # should be a BGR image 84 | txt_path = output_dir / ('%s.txt' % ID) 85 | # write a txt for each image 86 | with open(txt_path.as_posix(), 'w') as ftxt: 87 | for obj in anno['gtboxes']: 88 | if obj['tag'] == 'mask': 89 | continue # ignore non-human 90 | assert obj['tag'] == 'person' 91 | if 'hbox' in obj.keys(): # head 92 | line = txt_line(0, obj['hbox'], img_w, img_h) 93 | if line: 94 | ftxt.write(line) 95 | if 'fbox' in obj.keys(): # full body 96 | line = txt_line(1, obj['fbox'], img_w, img_h) 97 | if line: 98 | ftxt.write(line) 99 | jpgs.append('data/%s/%s.jpg' % (output_dir, ID)) 100 | # write the 'data/crowdhuman/train.txt' or 'data/crowdhuman/test.txt' 101 | set_path = output_dir / ('%s.txt' % set_) 102 | with open(set_path.as_posix(), 'w') as fset: 103 | for jpg in jpgs: 104 | fset.write('%s\n' % jpg) 105 | 106 | 107 | def rm_txts(output_dir): 108 | """Remove txt files in output_dir.""" 109 | for txt in output_dir.glob('*.txt'): 110 | if txt.is_file(): 111 | txt.unlink() 112 | 113 | 114 | def main(): 115 | global INPUT_WIDTH, INPUT_HEIGHT 116 | 117 | parser = ArgumentParser() 118 | parser.add_argument('dim', help='input width and height, e.g. 608x608') 119 | args = parser.parse_args() 120 | 121 | dim_split = args.dim.split('x') 122 | if len(dim_split) != 2: 123 | raise SystemExit('ERROR: bad spec of input dim (%s)' % args.dim) 124 | INPUT_WIDTH, INPUT_HEIGHT = int(dim_split[0]), int(dim_split[1]) 125 | if INPUT_WIDTH % 32 != 0 or INPUT_HEIGHT % 32 != 0: 126 | raise SystemExit('ERROR: bad spec of input dim (%s)' % args.dim) 127 | 128 | output_dir = Path('crowdhuman-%s' % args.dim) 129 | if not output_dir.is_dir(): 130 | raise SystemExit('ERROR: %s does not exist.' % output_dir.as_posix()) 131 | 132 | rm_txts(output_dir) 133 | process('test', 'raw/annotation_val.odgt', output_dir) 134 | process('train', 'raw/annotation_train.odgt', output_dir) 135 | 136 | with open('crowdhuman-%s.data' % args.dim, 'w') as f: 137 | f.write("""classes = 2 138 | train = data/crowdhuman-%s/train.txt 139 | valid = data/crowdhuman-%s/test.txt 140 | names = data/crowdhuman.names 141 | backup = backup/\n""" % (args.dim, args.dim)) 142 | 143 | if DO_KMEANS: 144 | try: 145 | from sklearn.cluster import KMeans 146 | except ModuleNotFoundError: 147 | print('WARNING: no sklearn, skipping anchor clustering...') 148 | else: 149 | X = np.array(BBOX_WHS) 150 | kmeans = KMeans(n_clusters=KMEANS_CLUSTERS, random_state=0).fit(X) 151 | centers = kmeans.cluster_centers_ 152 | centers = centers[centers[:, 0].argsort()] # sort by bbox w 153 | print('\n** for yolov5-%dx%d, ' % (INPUT_WIDTH, INPUT_HEIGHT), end='') 154 | print('resized bbox width/height clusters are: ', end='') 155 | print(' '.join(['(%.2f, %.2f)' % (c[0], c[1]) for c in centers])) 156 | print('\nanchors = ', end='') 157 | print(', '.join(['%d,%d' % (int(c[0]), int(c[1])) for c in centers])) 158 | 159 | 160 | if __name__ == '__main__': 161 | main() 162 | -------------------------------------------------------------------------------- /CrowHuman2YOLO/data/prepare_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # check argument 6 | if [[ -z $1 || ! $1 =~ [[:digit:]]x[[:digit:]] ]]; then 7 | echo "ERROR: This script requires 1 argument, \"input dimension\" of the YOLO model." 8 | echo "The input dimension should be {width}x{height} such as 608x608 or 416x256.". 9 | exit 1 10 | fi 11 | 12 | if which python3 > /dev/null; then 13 | PYTHON=python3 14 | else 15 | PYTHON=python 16 | fi 17 | 18 | 19 | pushd $(dirname $0)/raw > /dev/null 20 | 21 | get_file() 22 | { 23 | # do download only if the file does not exist 24 | if [[ -f $2 ]]; then 25 | echo Skipping $2 26 | else 27 | echo Downloading $2... 28 | python3 -m gdown.cli $1 29 | fi 30 | } 31 | 32 | echo "** Download dataset files" 33 | 34 | 35 | # unzip image files (ignore CrowdHuman_test.zip for now) 36 | echo "** Unzip dataset files" 37 | for f in CrowdHuman_train01.zip CrowdHuman_train02.zip CrowdHuman_train03.zip CrowdHuman_val.zip ; do 38 | unzip -n ${f} 39 | done 40 | 41 | echo "** Create the crowdhuman-$1/ subdirectory" 42 | rm -rf ../crowdhuman-$1/ 43 | mkdir ../crowdhuman-$1/ 44 | ln Images/*.jpg ../crowdhuman-$1/ 45 | 46 | # the crowdhuman/ subdirectory now contains all train/val jpg images 47 | 48 | echo "** Generate yolo txt files" 49 | cd .. 50 | ${PYTHON} gen_txts.py $1 51 | 52 | popd > /dev/null 53 | 54 | echo "** Done." 55 | -------------------------------------------------------------------------------- /CrowHuman2YOLO/data/raw/这里放置下载的原始文件: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CrowHuman2YOLO/data/verify_txts.py: -------------------------------------------------------------------------------- 1 | """verify_txts.py 2 | 3 | For verifying correctness of the generated YOLO txt annotations. 4 | """ 5 | 6 | 7 | import random 8 | from pathlib import Path 9 | from argparse import ArgumentParser 10 | 11 | import cv2 12 | 13 | 14 | WINDOW_NAME = "verify_txts" 15 | 16 | parser = ArgumentParser() 17 | parser.add_argument('dim', help='input width and height, e.g. 608x608') 18 | args = parser.parse_args() 19 | 20 | if random.random() < 0.5: 21 | print('Verifying test.txt') 22 | jpgs_path = Path('crowdhuman-%s/test.txt' % args.dim) 23 | else: 24 | print('Verifying train.txt') 25 | jpgs_path = Path('crowdhuman-%s/train.txt' % args.dim) 26 | 27 | with open(jpgs_path.as_posix(), 'r') as f: 28 | jpg_names = [l.strip()[5:] for l in f.readlines()] 29 | 30 | random.shuffle(jpg_names) 31 | for jpg_name in jpg_names: 32 | img = cv2.imread(jpg_name) 33 | img_h, img_w, _ = img.shape 34 | txt_name = jpg_name.replace('.jpg', '.txt') 35 | with open(txt_name, 'r') as f: 36 | obj_lines = [l.strip() for l in f.readlines()] 37 | for obj_line in obj_lines: 38 | cls, cx, cy, nw, nh = [float(item) for item in obj_line.split(' ')] 39 | color = (0, 0, 255) if cls == 0.0 else (0, 255, 0) 40 | x_min = int((cx - (nw / 2.0)) * img_w) 41 | y_min = int((cy - (nh / 2.0)) * img_h) 42 | x_max = int((cx + (nw / 2.0)) * img_w) 43 | y_max = int((cy + (nh / 2.0)) * img_h) 44 | cv2.rectangle(img, (x_min, y_min), (x_max, y_max), color, 2) 45 | cv2.imshow(WINDOW_NAME, img) 46 | if cv2.waitKey(0) == 27: 47 | break 48 | 49 | cv2.destroyAllWindows() 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YOLOv5-Tools 2 | 3 | ## general_json2yolo.py 4 | COCO格式转YOLOv5格式 5 | 6 | ## check_yolov5_label_format.py 7 | 在各种格式转到YOLOv5格式之后,防止转换错误,最后检查一下,可视化一下标注结果。 8 | 9 | ## look_up_anchor.py 10 | 查看anchor的数值是多少 11 | 12 | ## voc2yolo.py 13 | VOC格式转YOLOv5格式 14 | 15 | ## 混淆矩阵 16 | 根据检测框和GT boxes输出混淆矩阵(TP,FN,FP,TN)据此可以计算模型指标 17 | 实现文件general.py 18 | 使用方法confusion_matrix_test.py 19 | 20 | 21 | ## widerface2yolo.py 22 | widerface人脸数据集转yolov5格式 23 | -------------------------------------------------------------------------------- /YOLOv5/crowdhuman.yaml: -------------------------------------------------------------------------------- 1 | train: ../crowdhuman/images/train2017/ 2 | val: ../crowdhuman/images/train2017/ 3 | #test: test.txt 4 | 5 | #number of classes 6 | nc: 2 7 | 8 | # class names 9 | names: ['person', 'head'] 10 | -------------------------------------------------------------------------------- /check_yolov5_label_format.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | import torch 4 | 5 | label_path = './coco128/labels/train2017/000000000094.txt' 6 | image_path = './coco128/images/train2017/000000000094.jpg' 7 | 8 | #坐标转换,原始存储的是YOLOv5格式 9 | # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right 10 | def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0): 11 | 12 | y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) 13 | y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x 14 | y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh # top left y 15 | y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw # bottom right x 16 | y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh # bottom right y 17 | return y 18 | 19 | #读取labels 20 | with open(label_path, 'r') as f: 21 | lb = np.array([x.split() for x in f.read().strip().splitlines()], dtype=np.float32) # labels 22 | print(lb) 23 | 24 | # 读取图像文件 25 | img = cv2.imread(str(image_path)) 26 | h, w = img.shape[:2] 27 | lb[:, 1:] = xywhn2xyxy(lb[:, 1:], w, h, 0, 0)#反归一化 28 | print(lb) 29 | 30 | #绘图 31 | for _, x in enumerate(lb): 32 | class_label = int(x[0]) # class 33 | 34 | cv2.rectangle(img,(x[1],x[2]),(x[3],x[4]),(0, 255, 0) ) 35 | cv2.putText(img,str(class_label), (int(x[1]), int(x[2] - 2)),fontFace = cv2.FONT_HERSHEY_SIMPLEX,fontScale=1,color=(0, 0, 255),thickness=2) 36 | cv2.imshow('show', img) 37 | cv2.waitKey(0)#按键结束 38 | cv2.destroyAllWindows() 39 | 40 | 41 | -------------------------------------------------------------------------------- /confusion_matrix_test/confusion_matrix_test.py: -------------------------------------------------------------------------------- 1 | from general import ap_per_class, ConfusionMatrix 2 | confusion_matrix = ConfusionMatrix(nc=1) 3 | 4 | import numpy as np 5 | 6 | def xywh2xyxy(x): #坐标转化 7 | # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right 8 | y = np.copy(x) 9 | y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x 10 | y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y 11 | y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x 12 | y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y 13 | return y 14 | #[N, 6] x1, y1, x2, y2, confidence, class 15 | preds=np.array([[3.20333e+02, 1.08333e+00, 4.08333e+02, 2.37667e+02, 8.51074e-01, 0.00000e+00], 16 | [8.02667e+02, 4.18000e+02, 1.06133e+03, 7.14667e+02, 8.42773e-01, 0.00000e+00], 17 | [1.18067e+03, 5.42500e+01, 1.27667e+03, 3.76667e+02, 7.18262e-01, 0.00000e+00], 18 | [5.24667e+02, 5.51667e+02, 7.94000e+02, 7.10000e+02, 6.36230e-01, 0.00000e+00], 19 | [9.87333e+02, 0.00000e+00, 1.05400e+03, 6.83333e+01, 6.34766e-01, 0.00000e+00], 20 | [1.06800e+03, 0.00000e+00, 1.14133e+03, 6.29167e+01, 4.94629e-01, 0.00000e+00], 21 | [6.27000e+02, 5.50667e+02, 8.03333e+02, 7.13333e+02, 3.66455e-01, 0.00000e+00]]) 22 | 23 | #[M, 5] class,x1, y1, x2, y2, 24 | gt_boxes=np.array([[0.00000e+00, 3.21000e+02, 2.99988e+00, 4.02000e+02, 2.40000e+02], 25 | [0.00000e+00, 7.69001e+02, 4.23000e+02, 1.05900e+03, 7.16000e+02], 26 | [0.00000e+00, 4.29000e+02, 5.49000e+02, 7.85999e+02, 7.16000e+02], 27 | [0.00000e+00, 1.17000e+03, 1.87000e+02, 1.28000e+03, 3.95000e+02], 28 | [0.00000e+00, 1.08200e+03, 9.99969e-01, 1.14900e+03, 6.30000e+01], 29 | [0.00000e+00, 1.19800e+03, 3.10000e+01, 1.28000e+03, 2.28000e+02], 30 | [0.00000e+00, 9.85999e+02, 9.99969e-01, 1.05300e+03, 6.30000e+01]]) 31 | 32 | 33 | confusion_matrix.process_batch(preds, gt_boxes) 34 | 35 | print("result:",confusion_matrix.matrix) -------------------------------------------------------------------------------- /confusion_matrix_test/general.py: -------------------------------------------------------------------------------- 1 | # Model validation metrics 2 | 3 | from pathlib import Path 4 | 5 | import matplotlib.pyplot as plt 6 | import numpy as np 7 | 8 | def fitness(x): 9 | # Model fitness as a weighted combination of metrics 10 | w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95] 11 | return (x[:, :4] * w).sum(1) 12 | 13 | 14 | def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=()): 15 | """ Compute the average precision, given the recall and precision curves. 16 | Source: https://github.com/rafaelpadilla/Object-Detection-Metrics. 17 | # Arguments 18 | tp: True positives (nparray, nx1 or nx10). 19 | conf: Objectness value from 0-1 (nparray). 20 | pred_cls: Predicted object classes (nparray). 21 | target_cls: True object classes (nparray). 22 | plot: Plot precision-recall curve at mAP@0.5 23 | save_dir: Plot save directory 24 | # Returns 25 | The average precision as computed in py-faster-rcnn. 26 | """ 27 | 28 | # Sort by objectness 29 | i = np.argsort(-conf) 30 | tp, conf, pred_cls = tp[i], conf[i], pred_cls[i] 31 | 32 | # Find unique classes 33 | unique_classes = np.unique(target_cls) 34 | nc = unique_classes.shape[0] # number of classes, number of detections 35 | 36 | # Create Precision-Recall curve and compute AP for each class 37 | px, py = np.linspace(0, 1, 1000), [] # for plotting 38 | ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000)) 39 | for ci, c in enumerate(unique_classes): 40 | i = pred_cls == c 41 | n_l = (target_cls == c).sum() # number of labels 42 | n_p = i.sum() # number of predictions 43 | 44 | if n_p == 0 or n_l == 0: 45 | continue 46 | else: 47 | # Accumulate FPs and TPs 48 | fpc = (1 - tp[i]).cumsum(0) 49 | tpc = tp[i].cumsum(0) 50 | 51 | # Recall 52 | recall = tpc / (n_l + 1e-16) # recall curve 53 | r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases 54 | 55 | # Precision 56 | precision = tpc / (tpc + fpc) # precision curve 57 | p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) # p at pr_score 58 | 59 | # AP from recall-precision curve 60 | for j in range(tp.shape[1]): 61 | ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j]) 62 | if plot and j == 0: 63 | py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5 64 | 65 | # Compute F1 (harmonic mean of precision and recall) 66 | f1 = 2 * p * r / (p + r + 1e-16) 67 | if plot: 68 | plot_pr_curve(px, py, ap, Path(save_dir) / 'PR_curve.png', names) 69 | plot_mc_curve(px, f1, Path(save_dir) / 'F1_curve.png', names, ylabel='F1') 70 | plot_mc_curve(px, p, Path(save_dir) / 'P_curve.png', names, ylabel='Precision') 71 | plot_mc_curve(px, r, Path(save_dir) / 'R_curve.png', names, ylabel='Recall') 72 | 73 | i = f1.mean(0).argmax() # max F1 index 74 | return p[:, i], r[:, i], ap, f1[:, i], unique_classes.astype('int32') 75 | 76 | 77 | def compute_ap(recall, precision): 78 | """ Compute the average precision, given the recall and precision curves 79 | # Arguments 80 | recall: The recall curve (list) 81 | precision: The precision curve (list) 82 | # Returns 83 | Average precision, precision curve, recall curve 84 | """ 85 | 86 | # Append sentinel values to beginning and end 87 | mrec = np.concatenate(([0.], recall, [recall[-1] + 0.01])) 88 | mpre = np.concatenate(([1.], precision, [0.])) 89 | 90 | # Compute the precision envelope 91 | mpre = np.flip(np.maximum.accumulate(np.flip(mpre))) 92 | 93 | # Integrate area under curve 94 | method = 'interp' # methods: 'continuous', 'interp' 95 | if method == 'interp': 96 | x = np.linspace(0, 1, 101) # 101-point interp (COCO) 97 | ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate 98 | else: # 'continuous' 99 | i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes 100 | ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve 101 | 102 | return ap, mpre, mrec 103 | 104 | 105 | 106 | 107 | def box_iou_calc(boxes1, boxes2): 108 | """ 109 | Return intersection-over-union (Jaccard index) of boxes. 110 | Both sets of boxes are expected to be in (x1, y1, x2, y2) format. 111 | Arguments: 112 | boxes1 (Array[N, 4]) 113 | boxes2 (Array[M, 4]) 114 | Returns: 115 | iou (Array[N, M]): the NxM matrix containing the pairwise 116 | IoU values for every element in boxes1 and boxes2 117 | This implementation is taken from the above link and changed so that it only uses numpy.. 118 | """ 119 | 120 | def box_area(box): 121 | # box = 4xn 122 | return (box[2] - box[0]) * (box[3] - box[1]) 123 | 124 | area1 = box_area(boxes1.T) 125 | area2 = box_area(boxes2.T) 126 | 127 | lt = np.maximum(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2] 128 | rb = np.minimum(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2] 129 | 130 | inter = np.prod(np.clip(rb - lt, a_min=0, a_max=None), 2) 131 | return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter) 132 | 133 | 134 | class ConfusionMatrix: 135 | def __init__(self, nc, conf=0.25, iou_thres=0.45): 136 | self.matrix = np.zeros((nc + 1, nc + 1)) 137 | self.nc = nc # number of classes 138 | self.conf = conf 139 | self.iou_thres = iou_thres 140 | 141 | def process_batch(self, detections, labels): 142 | """ 143 | Return intersection-over-union (Jaccard index) of boxes. 144 | Both sets of boxes are expected to be in (x1, y1, x2, y2) format. 145 | Arguments: 146 | detections (Array[N, 6]), x1, y1, x2, y2, conf, class 147 | labels (Array[M, 5]), class, x1, y1, x2, y2 148 | Returns: 149 | None, updates confusion matrix accordingly 150 | """ 151 | detections = detections[detections[:, 4] > self.conf] 152 | gt_classes = labels[:, 0].astype(int) 153 | detection_classes = detections[:, 5].astype(int) 154 | iou = box_iou_calc(labels[:, 1:], detections[:, :4]) 155 | 156 | x = np.where(iou > self.iou_thres) 157 | # print("x:", x) 158 | # print("x[0].shape:", x[0].shape) 159 | if x[0].shape[0]: 160 | matches = np.concatenate((np.stack(x, 1), iou[x[0], x[1]][:, None]), 1) 161 | if x[0].shape[0] > 1: 162 | matches = matches[matches[:, 2].argsort()[::-1]] 163 | matches = matches[np.unique(matches[:, 1], return_index=True)[1]] 164 | matches = matches[matches[:, 2].argsort()[::-1]] 165 | matches = matches[np.unique(matches[:, 0], return_index=True)[1]] 166 | else: 167 | matches = np.zeros((0, 3)) 168 | 169 | # print("matches.shape:", matches.shape) 170 | # print("matches:", matches) 171 | 172 | n = matches.shape[0] > 0 173 | m0, m1, _ = matches.transpose().astype(np.int16) 174 | for i, gc in enumerate(gt_classes): 175 | j = m0 == i 176 | if n and sum(j) == 1: 177 | self.matrix[gc, detection_classes[m1[j]]] += 1 # correct 178 | else: 179 | self.matrix[self.nc, gc] += 1 # background FP 180 | 181 | if n: 182 | for i, dc in enumerate(detection_classes): 183 | if not any(m1 == i): 184 | # print(i,dc) 185 | # print(self.nc) 186 | self.matrix[dc, self.nc] += 1 # background FN 187 | 188 | 189 | def matrix(self): 190 | return self.matrix 191 | 192 | def plot(self, save_dir='', names=()): 193 | try: 194 | import seaborn as sn 195 | 196 | array = self.matrix / (self.matrix.sum(0).reshape(1, self.nc + 1) + 1E-6) # normalize 197 | array[array < 0.005] = np.nan # don't annotate (would appear as 0.00) 198 | 199 | fig = plt.figure(figsize=(12, 9), tight_layout=True) 200 | sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size 201 | labels = (0 < len(names) < 99) and len(names) == self.nc # apply names to ticklabels 202 | sn.heatmap(array, annot=self.nc < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True, 203 | xticklabels=names + ['background FP'] if labels else "auto", 204 | yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1)) 205 | fig.axes[0].set_xlabel('True') 206 | fig.axes[0].set_ylabel('Predicted') 207 | fig.savefig(Path(save_dir) / 'confusion_matrix.png', dpi=250) 208 | except Exception as e: 209 | pass 210 | 211 | def print(self): 212 | for i in range(self.nc + 1): 213 | print(' '.join(map(str, self.matrix[i]))) 214 | 215 | 216 | # Plots ---------------------------------------------------------------------------------------------------------------- 217 | 218 | def plot_pr_curve(px, py, ap, save_dir='pr_curve.png', names=()): 219 | # Precision-recall curve 220 | fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True) 221 | py = np.stack(py, axis=1) 222 | 223 | if 0 < len(names) < 21: # display per-class legend if < 21 classes 224 | for i, y in enumerate(py.T): 225 | ax.plot(px, y, linewidth=1, label=f'{names[i]} {ap[i, 0]:.3f}') # plot(recall, precision) 226 | else: 227 | ax.plot(px, py, linewidth=1, color='grey') # plot(recall, precision) 228 | 229 | ax.plot(px, py.mean(1), linewidth=3, color='blue', label='all classes %.3f mAP@0.5' % ap[:, 0].mean()) 230 | ax.set_xlabel('Recall') 231 | ax.set_ylabel('Precision') 232 | ax.set_xlim(0, 1) 233 | ax.set_ylim(0, 1) 234 | plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left") 235 | fig.savefig(Path(save_dir), dpi=250) 236 | 237 | 238 | def plot_mc_curve(px, py, save_dir='mc_curve.png', names=(), xlabel='Confidence', ylabel='Metric'): 239 | # Metric-confidence curve 240 | fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True) 241 | 242 | if 0 < len(names) < 21: # display per-class legend if < 21 classes 243 | for i, y in enumerate(py): 244 | ax.plot(px, y, linewidth=1, label=f'{names[i]}') # plot(confidence, metric) 245 | else: 246 | ax.plot(px, py.T, linewidth=1, color='grey') # plot(confidence, metric) 247 | 248 | y = py.mean(0) 249 | ax.plot(px, y, linewidth=3, color='blue', label=f'all classes {y.max():.2f} at {px[y.argmax()]:.3f}') 250 | ax.set_xlabel(xlabel) 251 | ax.set_ylabel(ylabel) 252 | ax.set_xlim(0, 1) 253 | ax.set_ylim(0, 1) 254 | plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left") 255 | fig.savefig(Path(save_dir), dpi=250) 256 | -------------------------------------------------------------------------------- /look_up_anchor.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from models.experimental import attempt_load 3 | 4 | model = attempt_load('./weights/best.pt', map_location=torch.device('cpu')) 5 | m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] 6 | print(m.anchor_grid) -------------------------------------------------------------------------------- /voc2yolo.py: -------------------------------------------------------------------------------- 1 | 2 | import xml.etree.ElementTree as ET 3 | import os 4 | 5 | classes = ['person'] 6 | def convert(size, box): 7 | dw = 1./(size[0]) #width 8 | dh = 1./(size[1]) #height 9 | x = (box[0] + box[1])/2.0 - 1 10 | y = (box[2] + box[3])/2.0 - 1 11 | w = box[1] - box[0] 12 | h = box[3] - box[2] 13 | x = x * dw 14 | w = w * dw 15 | y = y * dh 16 | h = h * dh 17 | if w>=1: 18 | w=0.99 19 | if h>=1: 20 | h=0.99 21 | return (x,y,w,h) 22 | 23 | def convert_annotation(rootpath,xmlname): 24 | xmlpath = rootpath 25 | xmlfile = os.path.join(xmlpath,xmlname) 26 | with open(xmlfile, "r", encoding='UTF-8') as in_file: 27 | txtname = xmlname[:-4]+'.txt' 28 | print(txtname) 29 | txtpath = rootpath + '/acutal_txt' 30 | if not os.path.exists(txtpath): 31 | os.makedirs(txtpath) 32 | txtfile = os.path.join(txtpath,txtname) 33 | with open(txtfile, "w+" ,encoding='UTF-8') as out_file: 34 | tree=ET.parse(in_file) 35 | root = tree.getroot() 36 | size = root.find('size') 37 | w = int(size.find('width').text) 38 | h = int(size.find('height').text) 39 | out_file.truncate() 40 | for obj in root.iter('object'): 41 | cls = obj.find('name').text 42 | if cls not in classes: 43 | continue 44 | cls_id = classes.index(cls) 45 | xmlbox = obj.find('bndbox') 46 | b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text)) 47 | bb = convert((w,h), b) 48 | out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') 49 | 50 | 51 | if __name__ == "__main__": 52 | rootpath='../voc/' # 只要是包含xml文件即可 53 | xmlpath=rootpath 54 | list=os.listdir(xmlpath) 55 | for i in range(0,len(list)) : 56 | path = os.path.join(xmlpath,list[i]) 57 | print(path) 58 | if ('.xml' in path)or('.XML' in path): 59 | convert_annotation(rootpath,list[i]) 60 | print('done', i) 61 | else: 62 | print('not xml file',i) 63 | -------------------------------------------------------------------------------- /widerface2yolo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | import sys 4 | import torch 5 | import torch.utils.data as data 6 | import cv2 7 | import numpy as np 8 | 9 | class WiderFaceDetection(data.Dataset): 10 | def __init__(self, txt_path, preproc=None): 11 | self.preproc = preproc 12 | self.imgs_path = [] 13 | self.words = [] 14 | f = open(txt_path,'r') 15 | lines = f.readlines() 16 | isFirst = True 17 | labels = [] 18 | for line in lines: 19 | line = line.rstrip() 20 | if line.startswith('#'): 21 | if isFirst is True: 22 | isFirst = False 23 | else: 24 | labels_copy = labels.copy() 25 | self.words.append(labels_copy) 26 | labels.clear() 27 | path = line[2:] 28 | path = txt_path.replace('label.txt','images/') + path 29 | self.imgs_path.append(path) 30 | else: 31 | line = line.split(' ') 32 | label = [float(x) for x in line] 33 | labels.append(label) 34 | 35 | self.words.append(labels) 36 | 37 | def __len__(self): 38 | return len(self.imgs_path) 39 | 40 | def __getitem__(self, index): 41 | img = cv2.imread(self.imgs_path[index]) 42 | height, width, _ = img.shape 43 | 44 | labels = self.words[index] 45 | annotations = np.zeros((0, 15)) 46 | if len(labels) == 0: 47 | return annotations 48 | for idx, label in enumerate(labels): 49 | annotation = np.zeros((1, 15)) 50 | # bbox 51 | annotation[0, 0] = label[0] # x1 52 | annotation[0, 1] = label[1] # y1 53 | annotation[0, 2] = label[0] + label[2] # x2 54 | annotation[0, 3] = label[1] + label[3] # y2 55 | 56 | # landmarks 57 | annotation[0, 4] = label[4] # l0_x 58 | annotation[0, 5] = label[5] # l0_y 59 | annotation[0, 6] = label[7] # l1_x 60 | annotation[0, 7] = label[8] # l1_y 61 | annotation[0, 8] = label[10] # l2_x 62 | annotation[0, 9] = label[11] # l2_y 63 | annotation[0, 10] = label[13] # l3_x 64 | annotation[0, 11] = label[14] # l3_y 65 | annotation[0, 12] = label[16] # l4_x 66 | annotation[0, 13] = label[17] # l4_y 67 | if (annotation[0, 4]<0): 68 | annotation[0, 14] = -1 69 | else: 70 | annotation[0, 14] = 1 71 | 72 | annotations = np.append(annotations, annotation, axis=0) 73 | target = np.array(annotations) 74 | if self.preproc is not None: 75 | img, target = self.preproc(img, target) 76 | 77 | return torch.from_numpy(img), target 78 | 79 | def detection_collate(batch): 80 | """Custom collate fn for dealing with batches of images that have a different 81 | number of associated object annotations (bounding boxes). 82 | 83 | Arguments: 84 | batch: (tuple) A tuple of tensor images and lists of annotations 85 | 86 | Return: 87 | A tuple containing: 88 | 1) (tensor) batch of images stacked on their 0 dim 89 | 2) (list of tensors) annotations for a given image are stacked on 0 dim 90 | """ 91 | targets = [] 92 | imgs = [] 93 | for _, sample in enumerate(batch): 94 | for _, tup in enumerate(sample): 95 | if torch.is_tensor(tup): 96 | imgs.append(tup) 97 | elif isinstance(tup, type(np.empty(0))): 98 | annos = torch.from_numpy(tup).float() 99 | targets.append(annos) 100 | 101 | return (torch.stack(imgs, 0), targets) 102 | 103 | 104 | def landmark_normalization(x,scale): 105 | if x < 0: 106 | x=0 107 | if x >= scale: 108 | x=x-1 109 | x = x / scale 110 | return x 111 | 112 | if __name__ == "__main__": 113 | 114 | #图片和文件分别独立的文件夹 115 | save_path = '/media/ubuntu/data/pytorch1.7/widerface/' 116 | wfd=WiderFaceDetection("/media/ubuntu/data/dataset/original-widerface/WIDER_train/label.txt") 117 | 118 | #按照yolov5的方式创建文件夹结构 119 | phase = 'train' 120 | images_path=(save_path + "/images/" + phase) 121 | if not os.path.exists(images_path): 122 | os.makedirs(images_path) 123 | 124 | labels_path=(save_path + "/labels/" + phase) 125 | if not os.path.exists(labels_path): 126 | os.makedirs(labels_path) 127 | 128 | for i in range(len(wfd.imgs_path)): 129 | print(i, wfd.imgs_path[i]) 130 | img = cv2.imread(wfd.imgs_path[i]) 131 | base_img = os.path.basename(wfd.imgs_path[i])#文件名和扩展名 132 | base_txt = os.path.basename(wfd.imgs_path[i])[:-4] +".txt" 133 | save_img_path = os.path.join(images_path, base_img) 134 | save_txt_path = os.path.join(labels_path, base_txt) 135 | with open(save_txt_path, "w") as f: 136 | height, width, _ = img.shape 137 | labels = wfd.words[i] 138 | annotations = np.zeros((0, 14)) 139 | if len(labels) == 0: 140 | continue 141 | for idx, label in enumerate(labels): 142 | annotation = np.zeros((1, 14)) 143 | # bbox 防止边框超出图片大小 144 | label[0] = max(0, label[0]) 145 | label[1] = max(0, label[1]) 146 | label[2] = min(width - 1, label[2]) 147 | label[3] = min(height - 1, label[3]) 148 | #widerface box 转yolov5 box归一化存储 149 | annotation[0, 0] = (label[0] + label[2] / 2) / width # cx 150 | annotation[0, 1] = (label[1] + label[3] / 2) / height # cy 151 | annotation[0, 2] = label[2] / width # w 152 | annotation[0, 3] = label[3] / height # h 153 | 154 | # landmarks 也跟着归一化, 防止超出图片外 ,原始-1表示没有关键点,这里用10个数都是0,表示没有关键点。 155 | annotation[0, 4] = landmark_normalization(label[4],width) # 0_x 156 | annotation[0, 5] = landmark_normalization(label[5], height) # 0_y 157 | annotation[0, 6] = landmark_normalization(label[7] , width ) # 1_x 158 | annotation[0, 7] = landmark_normalization(label[8] , height) # 1_y 159 | annotation[0, 8] = landmark_normalization(label[10] , width ) # 2_x 160 | annotation[0, 9] = landmark_normalization(label[11] , height )# 2_y 161 | annotation[0, 10] = landmark_normalization(label[13] , width ) # 3_x 162 | annotation[0, 11] = landmark_normalization(label[14] , height) # 3_y 163 | annotation[0, 12] = landmark_normalization(label[16] , width ) # 4_x 164 | annotation[0, 13] = landmark_normalization(label[17] , height ) # 4_y 165 | 166 | 167 | str_cls="0 " 168 | 169 | for i in range(len(annotation[0])): 170 | str_cls =str_cls+" "+str(annotation[0][i]) 171 | 172 | str_cls = str_cls + '\n' 173 | f.write(str_cls) 174 | cv2.imwrite(save_img_path, img) 175 | 176 | --------------------------------------------------------------------------------