Skip to content

Commit

Permalink
Reduce column length
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiqwang committed Sep 20, 2021
1 parent 9a652d2 commit 82f0d09
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 66 deletions.
16 changes: 10 additions & 6 deletions yolort/v5/models/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ def forward(self, imgs, size=640, augment=False, profile=False):
g = (size / max(s)) # gain
shape1.append([y * g for y in s])
imgs[i] = im if im.data.contiguous else np.ascontiguousarray(im) # update
shape1 = [make_divisible(x, int(self.stride.max())) for x in np.stack(shape1, 0).max(0)] # inference shape
# inference shape
shape1 = [make_divisible(x, int(self.stride.max())) for x in np.stack(shape1, 0).max(0)]
x = [letterbox(im, new_shape=shape1, auto=False)[0] for im in imgs] # pad
x = np.stack(x, 0) if n > 1 else x[0][None] # stack
x = np.ascontiguousarray(x.transpose((0, 3, 1, 2))) # BHWC to BCHW
Expand All @@ -441,7 +442,8 @@ class Detections:
def __init__(self, imgs, pred, files, times=None, names=None, shape=None):
super().__init__()
d = pred[0].device # device
gn = [torch.tensor([*[im.shape[i] for i in [1, 0, 1, 0]], 1., 1.], device=d) for im in imgs] # normalizations
# normalizations
gn = [torch.tensor([*[im.shape[i] for i in [1, 0, 1, 0]], 1., 1.], device=d) for im in imgs]
self.imgs = imgs # list of images as numpy arrays
self.pred = pred # list of tensors pred[0] = (xyxy, conf, cls)
self.names = names # class names
Expand Down Expand Up @@ -496,14 +498,15 @@ def display(self, pprint=False, show=False, save=False, crop=False, render=False

def print(self):
self.display(pprint=True) # print results
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {tuple(self.s)}' %
self.t)
LOGGER.info(f'Speed: {self.t[0]:.1f}ms pre-process, {self.t[1]:.1f}ms inference, '
f'{self.t[2]:.1f}ms NMS per image at shape {tuple(self.s)}')

def show(self):
self.display(show=True) # show results

def save(self, save_dir='runs/detect/exp'):
save_dir = increment_path(save_dir, exist_ok=save_dir != 'runs/detect/exp', mkdir=True) # increment save_dir
# increment save_dir
save_dir = increment_path(save_dir, exist_ok=save_dir != 'runs/detect/exp', mkdir=True)
self.display(save=True, save_dir=save_dir) # save results

def crop(self, save=True, save_dir='runs/detect/exp'):
Expand All @@ -520,7 +523,8 @@ def pandas(self):
ca = 'xmin', 'ymin', 'xmax', 'ymax', 'confidence', 'class', 'name' # xyxy columns
cb = 'xcenter', 'ycenter', 'width', 'height', 'confidence', 'class', 'name' # xywh columns
for k, c in zip(['xyxy', 'xyxyn', 'xywh', 'xywhn'], [ca, ca, cb, cb]):
a = [[x[:5] + [int(x[5]), self.names[int(x[5])]] for x in x.tolist()] for x in getattr(self, k)] # update
# update
a = [[x[:5] + [int(x[5]), self.names[int(x[5])]] for x in x.tolist()] for x in getattr(self, k)]
setattr(new, k, [pd.DataFrame(x, columns=c) for x in a])
return new

Expand Down
54 changes: 28 additions & 26 deletions yolort/v5/utils/autoanchor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@


def check_anchor_order(m):
# Check anchor order against stride order for YOLOv5 Detect() module m, and correct if necessary
# Check anchor order against stride order for
# YOLOv5 Detect() module m, and correct if necessary
a = m.anchor_grid.prod(-1).view(-1) # anchor area
da = a[-1] - a[0] # delta a
ds = m.stride[-1] - m.stride[0] # delta s
Expand All @@ -31,7 +32,8 @@ def check_anchors(dataset, model, thr=4.0, imgsz=640):
m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
wh = torch.tensor(
np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float()

def metric(k): # compute metric
r = wh[:, None] / k[None]
Expand All @@ -55,22 +57,29 @@ def metric(k): # compute metric
if new_bpr > bpr: # replace anchors
anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
m.anchor_grid[:] = anchors.clone().view_as(m.anchor_grid) # for inference
m.anchors[:] = anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1) # loss
# loss
m.anchors[:] = anchors.clone().view_as(m.anchors) / m.stride.to(m.anchors.device).view(-1, 1, 1)
check_anchor_order(m)
print(f'{prefix}New anchors saved to model. Update model *.yaml to use these anchors in the future.')
print(f'{prefix}New anchors saved to model. Update model *.yaml '
'to use these anchors in the future.')
else:
print(f'{prefix}Original anchors better than new anchors. Proceeding with original anchors.')
print(f'{prefix}Original anchors better than new anchors. '
'Proceeding with original anchors.')
print('') # newline


def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True):
""" Creates kmeans-evolved anchors from training dataset
def kmean_anchors(dataset='./data/coco128.yaml', n=9,
img_size=640, thr=4.0, gen=1000,
verbose=True):
"""
Creates kmeans-evolved anchors from training dataset
Arguments:
Args:
dataset: path to data.yaml, or a loaded dataset
n: number of anchors
img_size: image size used for training
thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0
thr: anchor-label wh ratio threshold hyperparameter
hyp['anchor_t'] used for training, default=4.0
gen: generations to evolve anchors using genetic algorithm
verbose: print all results
Expand Down Expand Up @@ -98,12 +107,15 @@ def anchor_fitness(k): # mutation fitness
def print_results(k):
k = k[np.argsort(k.prod(1))] # sort small to large
x, best = metric(k, wh0)
bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr
# best possible recall, anch > thr
bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n
print(f'{prefix}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr')
print(f'{prefix}n={n}, img_size={img_size}, metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, '
print(f'{prefix}n={n}, img_size={img_size}, '
f'metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, '
f'past_thr={x[x > thr].mean():.3f}-mean: ', end='')
for i, x in enumerate(k):
print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n') # use in *.cfg
# use in *.cfg
print('%i,%i' % (round(x[0]), round(x[1])), end=', ' if i < len(k) - 1 else '\n')
return k

if isinstance(dataset, str): # *.yaml file
Expand All @@ -119,32 +131,22 @@ def print_results(k):
# Filter
i = (wh0 < 3.0).any(1).sum()
if i:
print(f'{prefix}WARNING: Extremely small objects found. {i} of {len(wh0)} labels are < 3 pixels in size.')
print(f'{prefix}WARNING: Extremely small objects found. '
f'{i} of {len(wh0)} labels are < 3 pixels in size.')
wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels
# wh = wh * (np.random.rand(wh.shape[0], 1) * 0.9 + 0.1) # multiply by random scale 0-1

# Kmeans calculation
print(f'{prefix}Running kmeans for {n} anchors on {len(wh)} points...')
s = wh.std(0) # sigmas for whitening
k, dist = kmeans(wh / s, n, iter=30) # points, mean distance
assert len(k) == n, print(f'{prefix}ERROR: scipy.cluster.vq.kmeans requested {n} points but returned only {len(k)}')
assert len(k) == n, (f'{prefix}ERROR: scipy.cluster.vq.kmeans requested {n} points '
f'but returned only {len(k)}')
k *= s
wh = torch.tensor(wh, dtype=torch.float32) # filtered
wh0 = torch.tensor(wh0, dtype=torch.float32) # unfiltered
k = print_results(k)

# Plot
# k, d = [None] * 20, [None] * 20
# for i in tqdm(range(1, 21)):
# k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance
# fig, ax = plt.subplots(1, 2, figsize=(14, 7), tight_layout=True)
# ax = ax.ravel()
# ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.')
# fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh
# ax[0].hist(wh[wh[:, 0]<100, 0],400)
# ax[1].hist(wh[wh[:, 1]<100, 1],400)
# fig.savefig('wh.png', dpi=200)

# Evolve
npr = np.random
f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma
Expand Down
38 changes: 27 additions & 11 deletions yolort/v5/utils/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,28 @@ def get_latest_run(search_dir='.'):


def user_config_dir(dir='Ultralytics', env_var='YOLOV5_CONFIG_DIR'):
# Return path of user configuration directory. Prefer environment variable if exists. Make dir if required.
# Return path of user configuration directory. Prefer environment
# variable if exists. Make dir if required.
env = os.getenv(env_var)
if env:
path = Path(env) # use environment variable
else:
cfg = {'Windows': 'AppData/Roaming', 'Linux': '.config', 'Darwin': 'Library/Application Support'} # 3 OS dirs
# 3 OS dirs
cfg = {
'Windows': 'AppData/Roaming',
'Linux': '.config',
'Darwin': 'Library/Application Support',
}
path = Path.home() / cfg.get(platform.system(), '') # OS-specific config dir
path = (path if is_writeable(path) else Path('/tmp')) / dir # GCP and AWS lambda fix, only /tmp is writeable
# GCP and AWS lambda fix, only /tmp is writeable
path = (path if is_writeable(path) else Path('/tmp')) / dir
path.mkdir(exist_ok=True) # make if required
return path


def is_writeable(dir, test=False):
# Return True if directory has write permissions, test opening a file with write permissions if test=True
# Return True if directory has write permissions, test opening
# a file with write permissions if test=True
if test: # method 1
file = Path(dir) / 'tmp.txt'
try:
Expand Down Expand Up @@ -243,7 +251,8 @@ def check_requirements(requirements='requirements.txt', exclude=(), install=True
if n: # if packages updated
source = file.resolve() if 'file' in locals() else requirements
s = (f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n"
f"{prefix} WARNING {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n")
f"{prefix} WARNING "
f"{colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n")
print(emojis(s))


Expand All @@ -254,7 +263,8 @@ def check_img_size(imgsz, s=32, floor=0):
else: # list i.e. img_size=[640, 480]
new_size = [max(make_divisible(x, int(s)), floor) for x in imgsz]
if new_size != imgsz:
print(f'WARNING: --img-size {imgsz} must be multiple of max stride {s}, updating to {new_size}')
print(f'WARNING: --img-size {imgsz} must be multiple of '
f'max stride {s}, updating to {new_size}')
return new_size


Expand Down Expand Up @@ -285,7 +295,8 @@ def check_file(file):
file = Path(urllib.parse.unquote(file)).name.split('?')[0]
print(f'Downloading {url} to {file}...')
torch.hub.download_url_to_file(url, file)
assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}'
assert Path(file).exists() and Path(file).stat().st_size > 0, (
f'File download failed: {url}')
return file
else: # search
files = glob.glob('./**/' + file, recursive=True) # find file
Expand Down Expand Up @@ -461,7 +472,8 @@ def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
# a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')
# b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
# x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
# x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
# # coco to darknet
# x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)]
x = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34, 35,
Expand Down Expand Up @@ -600,9 +612,13 @@ def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=Non
xc = prediction[..., 4] > conf_thres # candidates

# Checks
assert 0 <= conf_thres <= 1, (f'Invalid Confidence threshold {conf_thres}, '
'valid values are between 0.0 and 1.0')
assert 0 <= iou_thres <= 1, f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0'
assert 0 <= conf_thres <= 1, (
f'Invalid Confidence threshold {conf_thres}, '
'valid values are between 0.0 and 1.0'
)
assert 0 <= iou_thres <= 1, (
f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0'
)

# Settings
min_wh, max_wh = 2, 4096 # (pixels) minimum and maximum box width and height
Expand Down
14 changes: 9 additions & 5 deletions yolort/v5/utils/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def fitness(x):


def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=()):
""" Compute the average precision, given the recall and precision curves.
"""
Compute the average precision, given the recall and precision curves.
Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
# Arguments
tp: True positives (nparray, nx1 or nx10).
Expand Down Expand Up @@ -285,7 +286,9 @@ def box_area(box):
area2 = box_area(box2.T)

# inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
inter = (
torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])
).clamp(0).prod(2)
return inter / (area1[:, None] + area2 - inter) # iou = inter / (area1 + area2 - inter)


Expand All @@ -304,8 +307,8 @@ def bbox_ioa(box1, box2, eps=1E-7):
b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]

# Intersection area
inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \
(np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0)
inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * (
np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0)

# box2 area
box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + eps
Expand Down Expand Up @@ -357,7 +360,8 @@ def plot_pr_curve(px, py, ap, save_dir='pr_curve.png', names=()):
plt.close()


def plot_mc_curve(px, py, save_dir='mc_curve.png', names=(), xlabel='Confidence', ylabel='Metric'):
def plot_mc_curve(px, py, save_dir='mc_curve.png', names=(),
xlabel='Confidence', ylabel='Metric'):
# Metric-confidence curve
fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
# display per-class legend if < 21 classes
Expand Down
Loading

0 comments on commit 82f0d09

Please sign in to comment.