general.py
|---train.py
|---detect.py
|---evaluation.py
|---yolo_error_omit_analysis.py
|---polt_file_error_omit.py
|---test_mutil_eval.py
|----utils
|----general.py
|----merits.py
|----plots.py
一、general.py
1、def set_logging(rank=-1) 设置日志格式等级
def set_logging(rank=-1): logging.basicConfig(format="%(message)s", level=logging.INFO if rank in [-1, 0] else logging.WARN)
2、def init_seeds(seed=0) 设置random、np.random、init_torch_seed三个库的seed固定,确保网络每次初始化相同 方便重现结果
3、def get_latest_run(search_dir='./runs') 在search_dir下搜索时间最新的last*.pt
def get_latest_run(search_dir='./runs'): last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) return max(last_list, key=os.path.getctime) if last_list else ''
4、def check_img_size(img_size, s=32) 使输入image_size是s=32的整数倍
def check_img_size(img_size, s=32): new_size = math.ceil(img_size / int(s)) * int(s) return new_size
5、def check_file(file)
if os.path.isfile(file) or file == '':
return file
files = glob.glob('./**/' + file, recursive=True)
return files[0]
6、def labels_to_class_weights(labels, nc=80) 返回整个数据集中每个box类别的频数(的倒数)
def labels_to_class_weights(labels, nc=80): labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO classes = labels[:, 0].astype(np.int) # labels = [class xywh] classes.shape=(866643) weights = np.bincount(classes, minlength=nc) # weights.shape=(80)
weights /= 1
7、def labels_to_image_weights(labels, nc=80, class_weights=np.noes(80)): 返回每张图片的分量
此处label.shpae=(num_Img, num_box, 5) 取label[...,0].shape=(num_img, num_box) -> class_counts.shape=(num_img, 80) -> image_weights.shape=(num_img)
def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)): class_weights=labels_to_class_weights(labels,80) class_counts = np.array([np.bincount(labels[i][:, 0].astype(np.int), minlength=nc) for i in range(len(labels)]) # labels.shape=(num_img, num_box, 5), class_counts.shape=(num_img,80) -> 每一个图片加权平均 image_weights.shape=(num_img) image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1) return image_weights
8、def scale_labels(img1_shape, labels, img0_shape, ratio_pad=None):
img1_shape现在缩小到img0_shape, 前者是数据集中实际初始的待检测图片大小,后者是预处理后模型的统一识别大小。
函数缩小的不是数据集待训练image,而是其中的label(shape=(num,5) xylsa),标注法由长边->opencv->poly 处理后 再转回去。
img1_shape(height, width), img0_shape(height, width), label.shape((num ,[ x y longside shortside Θ])) Θ:flaot[0-179]
poly = [(x1,y1),(x2,y2),(x3,y3),(x4,y4)], opencv/rect=[(x_c,y_c),(w,h),Θ] Θ:flaot[-90, 0)
def scale_labels(img1_shape, labels, img0_shape, ratio_pad=None): gain = ratio_pad[0][0] pad = ratio_pad[1] scaled_labels = [] for i, label in enumerate(labels): rect = longsideformat2cvminAreaRect(label[0], label[1], label[2], label[3], (label[4] - 179.9)) # longside -> opencv poly = cv2.boxPoints(rect) # opencv -> poly, normalized poly[:, 0] -= pad[0] # x padding --- process poly[:, 1] -= pad[1] poly[:, :] /= gain clip_poly(poly, img0_shape) rect_scale = cv2.minAreaRect(np.float32(poly)) # poly -> opencv label = np.array(cvminAreaRect2longsideformat(rect_scale[0][0],rect_scale[0][1], rect_scale[1][0], rect_scale[1][1], rect_scale[-1])) # opencv -> longside label[-1] = int(label[-1] + 180.5) # range int[0,180] 四舍五入 if label[-1] == 180: # range int[0,179] label[-1] = 179 scaled_labels.append(label) return torch.from_numpy(np.array(scaled_labels))
9、def clip_poly(poly, img_shape): 限定值域范围np.clip
def clip_poly(poly, img_shape): poly[:, 0].clip(0, img_shape[1]) poly[:, 1].clip(0, img_shape[0])
10、def strip_optimizer(f='weights/best.pt', s=''):
将模型load(f)中的optimizer、training_results、epoch、model删掉,另存为save(s)
11、def increment_dir(dir, comment=''):日志存放路径'./runs/train1024'
在train.py中使用 log_dir = increment_dir(Path(opt.logdir) / 'exp', opt.name)
def increment_dir(dir, comment=''): n = 0 # number dir = str(Path(dir)) # os-agnostic d = sorted(glob.glob(dir + '*')) # directories if len(d): n = max([int(x[len(dir):x.rfind('_') if '_' in Path(x).name else None]) for x in d]) + 1 # increment return dir + str(n) + ('_' + comment if comment else '')
12、--resume使用,返回./runs下最新的last.pt作为opt.weights继续训练
parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training') # python train.py --resume返回True
opt.weights = ckpt = opt.resume if isinstance(opt.resume, str) else get_latest_run()
log_dir = Path(ckpt).parent.parent
with open(log_dir / 'opt.yaml') as f:
opt = argparse.Namespace(**yaml.load(f, Loader=yaml.FullLoader))
def get_latest_run(search_dir='./runs'): last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) return max(last_list, key=os.path.getctime) if last_list else ''
二、metrics.py
1、def fitness(x): 求4个数的加权平均
def fitness(x): w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95] return (x[:, :4] * w).sum(1)
2、def compute_ap(recall, precision):
def compute_ap(recall, precision): x = np.linspace(0, 1, 101) ap = np.trapz(np.interp(x, recall, precision), x) return ap
3、def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, fname='precision-recall_curve.png'):
计算一个类分别在各个iou-thres下的precision和recall
tp.shape=(num_boxes, num_iou-thres),value_type is bool,计算每一个box与gtbox的iou是否大于iou-thres
conf.shape=(num_box),value_type is float
pred_cls.shape(num_box),value_type is int(class_id)
def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, fname='precision-recall_curve.png'): i = np.argsort(-conf) # Sort by conf descend tp, conf, pred_cls = tp[i], conf[i], pred_cls[i] unique_classes = np.unique(target_cls) px, py = np.linspace(0, 1, 1000), [] s = [unique_classes.shape[0], tp.shape[1]] # [num_class, num_iou-thres] ap, p, r = np.zeros(s), np.zeros(s), np.zeros(s) for ci, c in enumerate(unique_classes): i = pred_cls == c n_gt = (target_cls == c).sum() # Number of ground truth objects
tpc = tp[i].cumsum(0)
fpc = (1 - tp[i]).cumsum(0) recall = tpc / (n_gt + 1e-16) # recall curve precision = tpc / (tpc + fpc) # precision curve py.append(np.interp(px, recall[:, 0], precision[:, 0])) # Ap from recall-precision curve for j in range(tp.shape[1]): ap[ci, j] = compute_ap(recall[:, j], precision[:, j])if plot: py = np.stack(py, axis=1) fig, ax = plt.subplots(1, 1, figsize=(5, 5)) ax.plot(px, py, linewidth=0.5, color='grey') # plot(recall, precision) ax.plot(px, py.mean(1), linewidth=2, color='blue', label='all classes') ax.set_xlabel('Recall') ax.set_ylabel('Precision') ax.set_xlim(0, 1) ax.set_ylim(0, 1) plt.legend() fig.tight_layout() fig.savefig(fname, dpi=200) return p, r, ap, f1, unique_classes.astype('int32')
关于准确率acc、精确率precision、召回率recall:
True Positive(真正,TP):将正类预测为正类数
True Negative(真负,TN):将负类预测为负类数
False Positive(假正,FP):将负类预测为正类数 - 误报
False Negative(假负,FN):将正类预测为负类数 - 漏报
准确率
精确率
召回率
关于x.cumsum(0) 方便计算ap的操作

4、def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-9):
box1 box2:xyxy or xywh型;type is list, box1.shape=(4),返回一个标量
5、def box_iou(box1, box2):
效果同上,box1 box2:xyxy型,box1.shape=(num_box, 4), 返回一个向量shape=(num_box)
6、def wh_iou(wh1, wh2)
计算anchor-wh1.shape=(N,2)和gtbox-wh2.shape=(M,2)的iou;不需要xywh,只需要wh。
def wh_iou(wh1, wh2): wh1 = wh1[:, None] # [N,1,2] wh2 = wh2[None] # [1,M,2] inter = torch.min(wh1, wh2).prod(2) # [N,M] return inter / (wh1.prod(2) + wh2.prod(2) - inter)
wh1和wh2的最后一个shape相同即可做torch.min(), prod(2)指定某个通道内的数全部相乘,消除了一个维度。

三、plot.py
1、def plot_one_rotated_box(rbox, img, color=None, label=None, line_thickness=None, pi_format=True):
该函数只将一个方框和label绘入img中,pi_format指角度格式。 绘制方框robx-longside -> opencv -> longside
longside-opencv:[(x_c,y_c),(w,h),Θ] Θ:flaot[0-179] -> (-180,0), poly:[(x1,y1),(x2,y2),(x3,y3),(x4,y4)]
def plot_one_rotated_box(rbox, img, color=None, label=None, line_thickness=None, pi_format=True): ''' @param rbox:[tensor(x),tensor(y),tensor(l),tensor(s),tensor(θ)], shape=(1,) @param img: img.shape=(size1,size2,3) @param color: size(3) eg:[25, 184, 176] @param label: 字符串 @param line_thickness: 框的厚度 @param pi_format: θ是否为pi且 θ ∈ [-pi/2,pi/2) False说明 θ∈[0,179] ''' if isinstance(rbox, torch.Tensor): rbox = rbox.cpu().float().numpy() tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1 # line/font thickness color = color or [random.randint(0, 255) for _ in range(3)] if pi_format: rbox[-1] = (rbox[-1] * 180 / np.pi) + 90 # θ∈[0,179] rect = longsideformat2cvminAreaRect(rbox[0], rbox[1], rbox[2], rbox[3], (rbox[4] - 179.9)) # longside -> opencv poly = np.float32(cv2.boxPoints(rect)) # opencv -> poly poly = np.int0(poly) cv2.drawContours(image=img, contours=[poly], contourIdx=-1, color=color, thickness=2 * tl)if label:
c1 = (int(rbox[0], int(rbox[1])) tf = max(tl - 1, 1) # font thickness t_size = cv2.getTextSize(label, 0, fontScale=tl / 4, thickness=tf)[0] c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3 cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 4, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
2、def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''): 绘制学习率曲线
def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''): optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals y = [] for _ in range(epochs): scheduler.step() y.append(optimizer.param_groups[0]['lr'])
3、def plot_results_overlay(start=0, stop=0)
def plot_results(start=0, stop=0, bucket='', id=(), labels=(), save_dir='')
对result.txt下的box、Objectness、classification、Angle、Total_Loss、Precision、recall、mAP各项指标可视化。

浙公网安备 33010602011771号