This commit is contained in:
Glenn Jocher 2019-03-21 22:41:12 +02:00
parent 8ebb4da5cc
commit 6aef4e6a78
4 changed files with 109 additions and 140 deletions

21
test.py
View File

@ -3,6 +3,8 @@ import json
import time import time
from pathlib import Path from pathlib import Path
from torch.utils.data import DataLoader
from models import * from models import *
from utils.datasets import * from utils.datasets import *
from utils.utils import * from utils.utils import *
@ -39,16 +41,21 @@ def test(
model.to(device).eval() model.to(device).eval()
# Get dataloader # Dataloader
# dataloader = torch.utils.data.DataLoader(LoadImagesAndLabels(test_path), batch_size=batch_size) dataset = LoadImagesAndLabels(test_path, img_size=img_size)
dataloader = LoadImagesAndLabels(test_path, batch_size=batch_size, img_size=img_size) dataloader = DataLoader(dataset, batch_size=batch_size, num_workers=0)
mean_mAP, mean_R, mean_P, seen = 0.0, 0.0, 0.0, 0 mean_mAP, mean_R, mean_P, seen = 0.0, 0.0, 0.0, 0
print('%11s' * 5 % ('Image', 'Total', 'P', 'R', 'mAP')) print('%11s' * 5 % ('Image', 'Total', 'P', 'R', 'mAP'))
mP, mR, mAPs, TP, jdict = [], [], [], [], [] mP, mR, mAPs, TP, jdict = [], [], [], [], []
AP_accum, AP_accum_count = np.zeros(nC), np.zeros(nC) AP_accum, AP_accum_count = np.zeros(nC), np.zeros(nC)
coco91class = coco80_to_coco91_class() coco91class = coco80_to_coco91_class()
for (imgs, targets, paths, shapes) in dataloader: for imgs, targets, paths, shapes in dataloader:
# Unpad and collate targets
for j, t in enumerate(targets):
t[:, 0] = j
targets = torch.cat([t[t[:, 5].nonzero()] for t in targets], 0).squeeze(1)
targets = targets.to(device) targets = targets.to(device)
t = time.time() t = time.time()
output = model(imgs.to(device)) output = model(imgs.to(device))
@ -71,7 +78,7 @@ def test(
if save_json: if save_json:
# [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ... # [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ...
box = detections[:, :4].clone() # xyxy box = detections[:, :4].clone() # xyxy
scale_coords(img_size, box, shapes[si]) # to original shape scale_coords(img_size, box, (shapes[0][si], shapes[1][si])) # to original shape
box = xyxy2xywh(box) # xywh box = xyxy2xywh(box) # xywh
box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
@ -129,7 +136,7 @@ def test(
# Print image mAP and running mean mAP # Print image mAP and running mean mAP
print(('%11s%11s' + '%11.3g' * 4 + 's') % print(('%11s%11s' + '%11.3g' * 4 + 's') %
(seen, dataloader.nF, mean_P, mean_R, mean_mAP, time.time() - t)) (seen, len(dataset), mean_P, mean_R, mean_mAP, time.time() - t))
# Print mAP per class # Print mAP per class
print('\nmAP Per Class:') print('\nmAP Per Class:')
@ -139,7 +146,7 @@ def test(
# Save JSON # Save JSON
if save_json: if save_json:
imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataloader.img_files] imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataset.img_files]
with open('results.json', 'w') as file: with open('results.json', 'w') as file:
json.dump(jdict, file) json.dump(jdict, file)

View File

@ -42,10 +42,8 @@ def train(
optimizer = torch.optim.SGD(model.parameters(), lr=lr0, momentum=.9) optimizer = torch.optim.SGD(model.parameters(), lr=lr0, momentum=.9)
# Dataloader # Dataloader
if num_workers > 0:
cv2.setNumThreads(0) # to prevent OpenCV from multithreading
dataset = LoadImagesAndLabels(train_path, img_size=img_size, augment=True) dataset = LoadImagesAndLabels(train_path, img_size=img_size, augment=True)
dataloader = DataLoader(dataset, batch_size=batch_size, num_workers=num_workers) dataloader = DataLoader(dataset, batch_size=batch_size, num_workers=4)
cutoff = -1 # backbone reaches to cutoff layer cutoff = -1 # backbone reaches to cutoff layer
start_epoch = 0 start_epoch = 0
@ -103,17 +101,28 @@ def train(
if int(name.split('.')[1]) < cutoff: # if layer < 75 if int(name.split('.')[1]) < cutoff: # if layer < 75
p.requires_grad = False if (epoch == 0) else True p.requires_grad = False if (epoch == 0) else True
ui = -1
rloss = defaultdict(float) rloss = defaultdict(float)
for i, (imgs, targets, _, _) in enumerate(dataloader): for i, (imgs, targets, _, _) in enumerate(dataloader):
if targets.shape[1] == 100: # multithreaded 100-size block # Unpad and collate targets
targets = targets.view((-1, 6)) for j, t in enumerate(targets):
targets = targets[targets[:, 5].nonzero().squeeze()] t[:, 0] = j
targets = torch.cat([t[t[:, 5].nonzero()] for t in targets], 0).squeeze(1)
nT = targets.shape[0] nT = len(targets)
if nT == 0: # if no targets continue if nT == 0: # if no targets continue
continue continue
# Plot images with bounding boxes
plot_images = False
if plot_images:
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
for ip in range(batch_size):
labels = xywh2xyxy(targets[targets[:, 0] == ip, 2:6]).numpy() * img_size
plt.subplot(3, 3, ip + 1).imshow(imgs[ip].numpy().transpose(1, 2, 0))
plt.plot(labels[:, [0, 2, 2, 0, 0]].T, labels[:, [1, 1, 3, 3, 1]].T, '.-')
plt.axis('off')
# SGD burn-in # SGD burn-in
if (epoch == 0) and (i <= n_burnin): if (epoch == 0) and (i <= n_burnin):
lr = lr0 * (i / n_burnin) ** 4 lr = lr0 * (i / n_burnin) ** 4
@ -138,9 +147,8 @@ def train(
optimizer.zero_grad() optimizer.zero_grad()
# Running epoch-means of tracked metrics # Running epoch-means of tracked metrics
ui += 1
for key, val in loss_dict.items(): for key, val in loss_dict.items():
rloss[key] = (rloss[key] * ui + val) / (ui + 1) rloss[key] = (rloss[key] * i + val) / (i + 1)
s = ('%8s%12s' + '%10.3g' * 7) % ( s = ('%8s%12s' + '%10.3g' * 7) % (
'%g/%g' % (epoch, epochs - 1), '%g/%g' % (epoch, epochs - 1),
@ -197,7 +205,7 @@ if __name__ == '__main__':
parser.add_argument('--multi-scale', action='store_true', help='random image sizes per batch 320 - 608') parser.add_argument('--multi-scale', action='store_true', help='random image sizes per batch 320 - 608')
parser.add_argument('--img-size', type=int, default=32 * 13, help='pixels') parser.add_argument('--img-size', type=int, default=32 * 13, help='pixels')
parser.add_argument('--resume', action='store_true', help='resume training flag') parser.add_argument('--resume', action='store_true', help='resume training flag')
parser.add_argument('--num_workers', type=int, default=4, help='number of Pytorch DataLoader workers') parser.add_argument('--num_workers', type=int, default=0, help='number of Pytorch DataLoader workers')
opt = parser.parse_args() opt = parser.parse_args()
print(opt, end='\n\n') print(opt, end='\n\n')

View File

@ -6,6 +6,7 @@ import random
import cv2 import cv2
import numpy as np import numpy as np
import torch import torch
from torch.utils.data import Dataset
from utils.utils import xyxy2xywh from utils.utils import xyxy2xywh
@ -88,152 +89,102 @@ class LoadWebcam: # for inference
return 0 return 0
class LoadImagesAndLabels: # for training class LoadImagesAndLabels(Dataset): # for training/testing
def __init__(self, path, batch_size=1, img_size=608, augment=False): def __init__(self, path, img_size=416, augment=False):
with open(path, 'r') as file: with open(path, 'r') as file:
self.img_files = file.read().splitlines() self.img_files = file.read().splitlines()
self.img_files = list(filter(lambda x: len(x) > 0, self.img_files)) self.img_files = list(filter(lambda x: len(x) > 0, self.img_files))
assert len(self.img_files) > 0, 'No images found in %s' % path
self.nF = len(self.img_files) # number of image files self.img_size = img_size
self.nB = math.ceil(self.nF / batch_size) # number of batches self.augment = augment
assert self.nF > 0, 'No images found in %s' % path
self.label_files = [x.replace('images', 'labels').replace('.png', '.txt').replace('.jpg', '.txt') self.label_files = [x.replace('images', 'labels').replace('.png', '.txt').replace('.jpg', '.txt')
for x in self.img_files] for x in self.img_files]
self.batch_size = batch_size def __len__(self):
self.img_size = img_size return len(self.img_files)
self.augment = augment
def __iter__(self):
self.count = -1
#self.shuffled_vector = np.random.permutation(self.nF) if self.augment else np.arange(self.nF)
return self
def __getitem__(self, index): def __getitem__(self, index):
imgs, labels0, img_paths, img_shapes = self.load_images(index, index + 1) img_path = self.img_files[index]
label_path = self.label_files[index]
labels0[:, 0] = index % self.batch_size img = cv2.imread(img_path) # BGR
labels = torch.zeros(100, 6) assert img is not None, 'File Not Found ' + img_path
labels[:min(len(labels0), 100)] = labels0 # max 100 labels per image
return imgs.squeeze(0), labels, img_paths, img_shapes augment_hsv = True
if self.augment and augment_hsv:
# SV augmentation by 50%
fraction = 0.50
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
S = img_hsv[:, :, 1].astype(np.float32)
V = img_hsv[:, :, 2].astype(np.float32)
def __next__(self): a = (random.random() * 2 - 1) * fraction + 1
self.count += 1 # batches S *= a
if self.count >= self.nB: if a > 1:
raise StopIteration np.clip(S, a_min=0, a_max=255, out=S)
ia = self.count * self.batch_size # start index a = (random.random() * 2 - 1) * fraction + 1
ib = min(ia + self.batch_size, self.nF) # end index V *= a
if a > 1:
np.clip(V, a_min=0, a_max=255, out=V)
return self.load_images(ia, ib) img_hsv[:, :, 1] = S.astype(np.uint8)
img_hsv[:, :, 2] = V.astype(np.uint8)
cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img)
def load_images(self, ia, ib): h, w, _ = img.shape
img_all, labels_all, img_paths, img_shapes = [], [], [], [] img, ratio, padw, padh = letterbox(img, height=self.img_size)
for index, files_index in enumerate(range(ia, ib)):
img_path = self.img_files[files_index]
label_path = self.label_files[files_index]
img = cv2.imread(img_path) # BGR # Load labels
assert img is not None, 'File Not Found ' + img_path if os.path.isfile(label_path):
# labels0 = np.loadtxt(label_path, dtype=np.float32).reshape(-1, 5) # SLOWER
with open(label_path, 'r') as file:
lines = file.read().splitlines()
labels0 = np.array([x.split() for x in lines], dtype=np.float32)
augment_hsv = True # Normalized xywh to pixel xyxy format
if self.augment and augment_hsv: labels = labels0.copy()
# SV augmentation by 50% labels[:, 1] = ratio * w * (labels0[:, 1] - labels0[:, 3] / 2) + padw
fraction = 0.50 labels[:, 2] = ratio * h * (labels0[:, 2] - labels0[:, 4] / 2) + padh
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) labels[:, 3] = ratio * w * (labels0[:, 1] + labels0[:, 3] / 2) + padw
S = img_hsv[:, :, 1].astype(np.float32) labels[:, 4] = ratio * h * (labels0[:, 2] + labels0[:, 4] / 2) + padh
V = img_hsv[:, :, 2].astype(np.float32) else:
labels = np.array([])
a = (random.random() * 2 - 1) * fraction + 1 # Augment image and labels
S *= a if self.augment:
if a > 1: img, labels, M = random_affine(img, labels, degrees=(-5, 5), translate=(0.10, 0.10), scale=(0.90, 1.10))
np.clip(S, a_min=0, a_max=255, out=S)
a = (random.random() * 2 - 1) * fraction + 1 nL = len(labels)
V *= a if nL > 0:
if a > 1: # convert xyxy to xywh
np.clip(V, a_min=0, a_max=255, out=V) labels[:, 1:5] = xyxy2xywh(labels[:, 1:5].copy()) / self.img_size
img_hsv[:, :, 1] = S.astype(np.uint8) if self.augment:
img_hsv[:, :, 2] = V.astype(np.uint8) # random left-right flip
cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) lr_flip = True
if lr_flip & (random.random() > 0.5):
img = np.fliplr(img)
if nL > 0:
labels[:, 1] = 1 - labels[:, 1]
h, w, _ = img.shape # random up-down flip
img, ratio, padw, padh = letterbox(img, height=self.img_size) ud_flip = False
if ud_flip & (random.random() > 0.5):
img = np.flipud(img)
if nL > 0:
labels[:, 2] = 1 - labels[:, 2]
# Load labels labels_out = np.zeros((100, 6), dtype=np.float32)
if os.path.isfile(label_path): if nL > 0:
# labels0 = np.loadtxt(label_path, dtype=np.float32).reshape(-1, 5) # SLOWER labels_out[:nL, 1:] = labels # max 100 labels per image
with open(label_path, 'r') as file:
lines = file.read().splitlines()
labels0 = np.array([x.split() for x in lines], dtype=np.float32)
# Normalized xywh to pixel xyxy format
labels = labels0.copy()
labels[:, 1] = ratio * w * (labels0[:, 1] - labels0[:, 3] / 2) + padw
labels[:, 2] = ratio * h * (labels0[:, 2] - labels0[:, 4] / 2) + padh
labels[:, 3] = ratio * w * (labels0[:, 1] + labels0[:, 3] / 2) + padw
labels[:, 4] = ratio * h * (labels0[:, 2] + labels0[:, 4] / 2) + padh
else:
labels = np.array([])
# Augment image and labels
if self.augment:
img, labels, M = random_affine(img, labels, degrees=(-5, 5), translate=(0.10, 0.10), scale=(0.90, 1.10))
plotFlag = False
if plotFlag:
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10)) if index == 0 else None
plt.subplot(4, 4, index + 1).imshow(img[:, :, ::-1])
plt.plot(labels[:, [1, 3, 3, 1, 1]].T, labels[:, [2, 2, 4, 4, 2]].T, '.-')
plt.axis('off')
nL = len(labels)
if nL > 0:
# convert xyxy to xywh
labels[:, 1:5] = xyxy2xywh(labels[:, 1:5].copy()) / self.img_size
if self.augment:
# random left-right flip
lr_flip = True
if lr_flip & (random.random() > 0.5):
img = np.fliplr(img)
if nL > 0:
labels[:, 1] = 1 - labels[:, 1]
# random up-down flip
ud_flip = False
if ud_flip & (random.random() > 0.5):
img = np.flipud(img)
if nL > 0:
labels[:, 2] = 1 - labels[:, 2]
if nL > 0:
labels = np.concatenate((np.zeros((nL, 1), dtype='float32') + index, labels), 1)
labels_all.append(labels)
img_all.append(img)
img_paths.append(img_path)
img_shapes.append((h, w))
# Normalize # Normalize
img_all = np.stack(img_all)[:, :, :, ::-1].transpose(0, 3, 1, 2) # list to np.array and BGR to RGB img = img[:, :, ::-1].transpose(2, 0, 1) # list to np.array and BGR to RGB
img_all = np.ascontiguousarray(img_all, dtype=np.float32) # uint8 to float32 img = np.ascontiguousarray(img, dtype=np.float32) # uint8 to float32
img_all /= 255.0 # 0 - 255 to 0.0 - 1.0 img /= 255.0 # 0 - 255 to 0.0 - 1.0
if len(labels_all) > 0: return torch.from_numpy(img), torch.from_numpy(labels_out), img_path, (h, w)
labels_all = np.concatenate(labels_all, 0)
else:
labels_all = np.zeros((1, 6), dtype='float32')
labels_all = torch.from_numpy(labels_all)
return torch.from_numpy(img_all), labels_all, img_paths, img_shapes
def __len__(self):
return self.nB # number of batches
def letterbox(img, height=416, color=(127.5, 127.5, 127.5)): # resize a rectangular image to a padded square def letterbox(img, height=416, color=(127.5, 127.5, 127.5)): # resize a rectangular image to a padded square

View File

@ -15,6 +15,9 @@ from utils import torch_utils
torch.set_printoptions(linewidth=1320, precision=5, profile='long') torch.set_printoptions(linewidth=1320, precision=5, profile='long')
np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5 np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5
# Prevent OpenCV from multithreading (to use PyTorch DataLoader)
cv2.setNumThreads(0)
def float3(x): # format floats to 3 decimals def float3(x): # format floats to 3 decimals
return float(format(x, '.3f')) return float(format(x, '.3f'))