171 lines
7.8 KiB
Python
171 lines
7.8 KiB
Python
|
import argparse
|
||
|
import glob
|
||
|
import time
|
||
|
from pathlib import Path
|
||
|
|
||
|
import os
|
||
|
import cv2
|
||
|
import torch
|
||
|
import torch.backends.cudnn as cudnn
|
||
|
from numpy import random
|
||
|
import numpy as np
|
||
|
from models.experimental import attempt_load
|
||
|
from utils.datasets import letterbox
|
||
|
from utils.general import check_img_size, check_requirements, non_max_suppression_face, apply_classifier, \
|
||
|
scale_coords, xyxy2xywh, strip_optimizer, set_logging, increment_path
|
||
|
from utils.plots import plot_one_box
|
||
|
from utils.torch_utils import select_device, load_classifier, time_synchronized
|
||
|
from tqdm import tqdm
|
||
|
|
||
|
def dynamic_resize(shape, stride=64):
|
||
|
max_size = max(shape[0], shape[1])
|
||
|
if max_size % stride != 0:
|
||
|
max_size = (int(max_size / stride) + 1) * stride
|
||
|
return max_size
|
||
|
|
||
|
def scale_coords_landmarks(img1_shape, coords, img0_shape, ratio_pad=None):
|
||
|
# Rescale coords (xyxy) from img1_shape to img0_shape
|
||
|
if ratio_pad is None: # calculate from img0_shape
|
||
|
gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
|
||
|
pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
|
||
|
else:
|
||
|
gain = ratio_pad[0][0]
|
||
|
pad = ratio_pad[1]
|
||
|
|
||
|
coords[:, [0, 2, 4, 6, 8]] -= pad[0] # x padding
|
||
|
coords[:, [1, 3, 5, 7, 9]] -= pad[1] # y padding
|
||
|
coords[:, :10] /= gain
|
||
|
#clip_coords(coords, img0_shape)
|
||
|
coords[:, 0].clamp_(0, img0_shape[1]) # x1
|
||
|
coords[:, 1].clamp_(0, img0_shape[0]) # y1
|
||
|
coords[:, 2].clamp_(0, img0_shape[1]) # x2
|
||
|
coords[:, 3].clamp_(0, img0_shape[0]) # y2
|
||
|
coords[:, 4].clamp_(0, img0_shape[1]) # x3
|
||
|
coords[:, 5].clamp_(0, img0_shape[0]) # y3
|
||
|
coords[:, 6].clamp_(0, img0_shape[1]) # x4
|
||
|
coords[:, 7].clamp_(0, img0_shape[0]) # y4
|
||
|
coords[:, 8].clamp_(0, img0_shape[1]) # x5
|
||
|
coords[:, 9].clamp_(0, img0_shape[0]) # y5
|
||
|
return coords
|
||
|
|
||
|
def show_results(img, xywh, conf, landmarks, class_num):
|
||
|
h,w,c = img.shape
|
||
|
tl = 1 or round(0.002 * (h + w) / 2) + 1 # line/font thickness
|
||
|
x1 = int(xywh[0] * w - 0.5 * xywh[2] * w)
|
||
|
y1 = int(xywh[1] * h - 0.5 * xywh[3] * h)
|
||
|
x2 = int(xywh[0] * w + 0.5 * xywh[2] * w)
|
||
|
y2 = int(xywh[1] * h + 0.5 * xywh[3] * h)
|
||
|
cv2.rectangle(img, (x1,y1), (x2, y2), (0,255,0), thickness=tl, lineType=cv2.LINE_AA)
|
||
|
|
||
|
clors = [(255,0,0),(0,255,0),(0,0,255),(255,255,0),(0,255,255)]
|
||
|
|
||
|
for i in range(5):
|
||
|
point_x = int(landmarks[2 * i] * w)
|
||
|
point_y = int(landmarks[2 * i + 1] * h)
|
||
|
cv2.circle(img, (point_x, point_y), tl+1, clors[i], -1)
|
||
|
|
||
|
tf = max(tl - 1, 1) # font thickness
|
||
|
label = str(int(class_num)) + ': ' + str(conf)[:5]
|
||
|
cv2.putText(img, label, (x1, y1 - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
|
||
|
return img
|
||
|
|
||
|
def detect(model, img0):
|
||
|
stride = int(model.stride.max()) # model stride
|
||
|
imgsz = opt.img_size
|
||
|
if imgsz <= 0: # original size
|
||
|
imgsz = dynamic_resize(img0.shape)
|
||
|
imgsz = check_img_size(imgsz, s=64) # check img_size
|
||
|
img = letterbox(img0, imgsz)[0]
|
||
|
# Convert
|
||
|
img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
|
||
|
img = np.ascontiguousarray(img)
|
||
|
img = torch.from_numpy(img).to(device)
|
||
|
img = img.float() # uint8 to fp16/32
|
||
|
img /= 255.0 # 0 - 255 to 0.0 - 1.0
|
||
|
if img.ndimension() == 3:
|
||
|
img = img.unsqueeze(0)
|
||
|
|
||
|
# Inference
|
||
|
pred = model(img, augment=opt.augment)[0]
|
||
|
# Apply NMS
|
||
|
pred = non_max_suppression_face(pred, opt.conf_thres, opt.iou_thres)[0]
|
||
|
gn = torch.tensor(img0.shape)[[1, 0, 1, 0]].to(device) # normalization gain whwh
|
||
|
gn_lks = torch.tensor(img0.shape)[[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]].to(device) # normalization gain landmarks
|
||
|
boxes = []
|
||
|
h, w, c = img0.shape
|
||
|
if pred is not None:
|
||
|
pred[:, :4] = scale_coords(img.shape[2:], pred[:, :4], img0.shape).round()
|
||
|
pred[:, 5:15] = scale_coords_landmarks(img.shape[2:], pred[:, 5:15], img0.shape).round()
|
||
|
for j in range(pred.size()[0]):
|
||
|
xywh = (xyxy2xywh(pred[j, :4].view(1, 4)) / gn).view(-1)
|
||
|
xywh = xywh.data.cpu().numpy()
|
||
|
conf = pred[j, 4].cpu().numpy()
|
||
|
landmarks = (pred[j, 5:15].view(1, 10) / gn_lks).view(-1).tolist()
|
||
|
class_num = pred[j, 15].cpu().numpy()
|
||
|
x1 = int(xywh[0] * w - 0.5 * xywh[2] * w)
|
||
|
y1 = int(xywh[1] * h - 0.5 * xywh[3] * h)
|
||
|
x2 = int(xywh[0] * w + 0.5 * xywh[2] * w)
|
||
|
y2 = int(xywh[1] * h + 0.5 * xywh[3] * h)
|
||
|
boxes.append([x1, y1, x2-x1, y2-y1, conf])
|
||
|
return boxes
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument('--weights', nargs='+', type=str, default='runs/train/exp5/weights/last.pt', help='model.pt path(s)')
|
||
|
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
|
||
|
parser.add_argument('--conf-thres', type=float, default=0.02, help='object confidence threshold')
|
||
|
parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS')
|
||
|
parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
|
||
|
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
|
||
|
parser.add_argument('--augment', action='store_true', help='augmented inference')
|
||
|
parser.add_argument('--update', action='store_true', help='update all models')
|
||
|
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
|
||
|
parser.add_argument('--project', default='runs/detect', help='save results to project/name')
|
||
|
parser.add_argument('--name', default='exp', help='save results to project/name')
|
||
|
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
|
||
|
parser.add_argument('--save_folder', default='./widerface_evaluate/widerface_txt/', type=str, help='Dir to save txt results')
|
||
|
parser.add_argument('--dataset_folder', default='../WiderFace/val/images/', type=str, help='dataset path')
|
||
|
parser.add_argument('--folder_pict', default='/yolov5-face/data/widerface/val/wider_val.txt', type=str, help='folder_pict')
|
||
|
opt = parser.parse_args()
|
||
|
print(opt)
|
||
|
|
||
|
# changhy : read folder_pict
|
||
|
pict_folder = {}
|
||
|
with open(opt.folder_pict, 'r') as f:
|
||
|
lines = f.readlines()
|
||
|
for line in lines:
|
||
|
line = line.strip().split('/')
|
||
|
pict_folder[line[-1]] = line[-2]
|
||
|
|
||
|
# Load model
|
||
|
device = select_device(opt.device)
|
||
|
model = attempt_load(opt.weights, map_location=device) # load FP32 model
|
||
|
with torch.no_grad():
|
||
|
# testing dataset
|
||
|
testset_folder = opt.dataset_folder
|
||
|
|
||
|
for image_path in tqdm(glob.glob(os.path.join(testset_folder, '*'))):
|
||
|
if image_path.endswith('.txt'):
|
||
|
continue
|
||
|
img0 = cv2.imread(image_path) # BGR
|
||
|
if img0 is None:
|
||
|
print(f'ignore : {image_path}')
|
||
|
continue
|
||
|
boxes = detect(model, img0)
|
||
|
# --------------------------------------------------------------------
|
||
|
image_name = os.path.basename(image_path)
|
||
|
txt_name = os.path.splitext(image_name)[0] + ".txt"
|
||
|
save_name = os.path.join(opt.save_folder, pict_folder[image_name], txt_name)
|
||
|
dirname = os.path.dirname(save_name)
|
||
|
if not os.path.isdir(dirname):
|
||
|
os.makedirs(dirname)
|
||
|
with open(save_name, "w") as fd:
|
||
|
file_name = os.path.basename(save_name)[:-4] + "\n"
|
||
|
bboxs_num = str(len(boxes)) + "\n"
|
||
|
fd.write(file_name)
|
||
|
fd.write(bboxs_num)
|
||
|
for box in boxes:
|
||
|
fd.write('%d %d %d %d %.03f' % (box[0], box[1], box[2], box[3], box[4] if box[4] <= 1 else 1) + '\n')
|
||
|
print('done.')
|