general.py

YOLOv5_DOTA_OBB-master

|---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各项指标可视化。

 

posted @ 2021-11-17 13:43  shines87  阅读(366)  评论(0)    收藏  举报