Yolov5中统计iou值的分布

val.py中对应位置添加以下代码:

for循环前增添一行代码

    # iou计数初始化,添加下面这行代码
    allcount = torch.zeros(1).view(-1).to('cuda:0')
    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
        t1 = time_sync()
        if pt or jit or engine:
            im = im.to(device, non_blocking=True)
            targets = targets.to(device)
        im = im.half() if half else im.float()  # uint8 to fp16/32
        im /= 255  # 0 - 255 to 0.0 - 1.0
        nb, _, height, width = im.shape  # batch size, channels, height, width
        t2 = time_sync()
        dt[0] += t2 - t1

        # Inference
        out, train_out = model(im) if training else model(im, augment=augment, val=True)  # inference, loss outputs
        dt[1] += time_sync() - t2

接着,在循环内部添加两行代码

# Evaluate
            if nl:
                tbox = xywh2xyxy(labels[:, 1:5])  # target boxes
                scale_coords(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
                labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels
                correct = process_batch(predn, labelsn, iouv)
                # 添加以下两行代码
                oncecount = countsofiou(predn, labelsn, iouv).view(-1).to(iouv.device)
                allcount = torch.cat((allcount, oncecount), 0)
                if plots:
                    confusion_matrix.process_batch(predn, labelsn)
            else:
                correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool)
            stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))  # (correct, conf, pcls, tcls)

然后在循环结束后添加以下代码

    # 计算iou counts, 注意与for循环起始位置对齐
    allcount = torch.round(allcount * 100) / 100
    unique_elements, counts = torch.unique(allcount, return_counts=True)
    # 将唯一元素和出现次数保存到 CSV 文件中
    with open(save_dir/'unique_elements.csv', mode='w') as csv_file:
        writer = csv.writer(csv_file)
        writer.writerow(['Unique Elements', 'Frequency'])
        for i in range(len(unique_elements)):
            writer.writerow([unique_elements[i], counts[i]])
    # 创建一个图形窗口
    fig, ax = plt.subplots()
    # 绘制柱状图
    ax.bar(unique_elements.cpu().numpy(), counts.cpu().numpy(), width=0.01, edgecolor='black')
    # 设置 x 和 y 轴标签以及标题
    ax.set_xlabel('IOU')
    ax.set_ylabel('Frequency')
    ax.set_title('Frequency of IOU')
    # 设置 x 轴范围为输入的范围
    ax.set_xlim(0.5, 1)
    # 显示图形
    plt.show()
    # fig改成plt的话保存的就是空白图片
    fig.savefig(save_dir / 'histogram.png')
    # 打印2位有效数字
    print("唯一元素:", unique_elements)
    print("出现次数:", counts)


    # 下方代码为原始自带代码,复制上方代码到指定位置即可
    # Compute metrics
    stats = [np.concatenate(x, 0) for x in zip(*stats)]  # to numpy
    if len(stats) and stats[0].any():
        tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
        ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95
        mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
        nt = np.bincount(stats[3].astype(np.int64), minlength=nc)  # number of targets per class
    else:
        nt = torch.zeros(1)

最后在全局区添加以下函数:

def countsofiou(detections, labels, iouv):
    """
    Return correct predictions matrix. Both sets of boxes are in (x1, y1, x2, y2) format.
    Arguments:
        detections (Array[N, 6]), x1, y1, x2, y2, conf, class
        labels (Array[M, 5]), class, x1, y1, x2, y2
    Returns:
        correct (Array[N, 10]), for 10 IoU levels
    """
    iou = box_iou(labels[:, 1:], detections[:, :4])
    x = torch.where(((iou >= iouv[0]) & (labels[:, 0:1] == detections[:, 5])))  # IoU above threshold and classes match
    # 返回的是condition中非0元素的索引,条件为真时所在元素位置索引。(labels[:, 0:1] == detections[:, 5])返回的是(N, M)的矩阵
    if x[0].shape[0]:
        matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()  # [N, 索引:label + detection + iou]
        # torch.stack(x, 1)在第二个维度拼接x中的两个一维张量(N, 2),最后再拼接上iou(N, 1)为(N, 3)
        if x[0].shape[0] > 1:
            # 对iou排降序。argsort()升序,[::-1]从最后一个元素开始不断-1向前遍历,即反向降序
            matches = matches[matches[:, 2].argsort()[::-1]]
            # 对detection去重,返回2数组,第一个为唯一值,第二个为该值第一次出现的索引,如果pre的框重复出现2次,那也是记录与label的iou最高的一次,保留索引对应的值
            matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
            # matches = matches[matches[:, 2].argsort()[::-1]]  重新反向排序,但被注释掉了,应该是不需要重新排序
            # 对label去重,返回2数组,第一个为唯一值,第二个为该值第一次出现的索引,如果一个label对应多个pre的框,也是保留与pre的iou最高的一次。
            matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
            matches = torch.tensor(matches).to(iouv.device)
            # 取出匹配成功的iou值,
            iou = matches[:, 2].view(-1)
            # 为每个元素保留2位小数
            iou = torch.round(iou * 100) / 100

    return iou

 运行结果:

改进:不难发现,在val.py中添加了以上代码,检测结果得到的FPS值会下降,统计iou操作所耗费的时间被算入了推理时间内,如果进行正常的val操作就需要注释掉上述代码,较为麻烦,于是可以在val.py中添加一个bool值的超参数来给该功能做个开关。

在parse_opt()函数的超参数设置的最后一行加上

parser.add_argument('--iou-count', default=True, help='use iou counts')

接着在run()函数的形参的最后一个加上 iou_count=False

def run(data,
        weights=None,  # model.pt path(s)
        batch_size=32,  # batch size
        imgsz=640,  # inference size (pixels)
        conf_thres=0.001,  # confidence threshold
        iou_thres=0.6,  # NMS IoU threshold
        task='val',  # train, val, test, speed or study
        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        workers=8,  # max dataloader workers (per RANK in DDP mode)
        single_cls=False,  # treat as single-class dataset
        augment=False,  # augmented inference
        verbose=False,  # verbose output
        save_txt=False,  # save results to *.txt
        save_hybrid=False,  # save label+prediction hybrid results to *.txt
        save_conf=False,  # save confidences in --save-txt labels
        save_json=False,  # save a COCO-JSON results file
        project=ROOT / 'runs/val',  # save to project/name
        name='1',  # save to project/name
        exist_ok=False,  # existing project/name ok, do not increment
        half=True,  # use FP16 half-precision inference
        dnn=False,  # use OpenCV DNN for ONNX inference
        model=None,
        dataloader=None,
        save_dir=Path(''),
        plots=True,
        callbacks=Callbacks(),
        compute_loss=None,
        iou_count=False
        ):

接下来与前文类似,在对应的位置上加上if判断语句。

    # iou计数初始化
    if iou_count:
        allcount = torch.zeros(1).view(-1).to('cuda:0')
    for batch_i, (im, targets, paths, shapes) in enumerate(pbar):
        t1 = time_sync()
        if pt or jit or engine:
            im = im.to(device, non_blocking=True)
            targets = targets.to(device)
        im = im.half() if half else im.float()  # uint8 to fp16/32
        im /= 255  # 0 - 255 to 0.0 - 1.0
        nb, _, height, width = im.shape  # batch size, channels, height, width
        t2 = time_sync()
        dt[0] += t2 - t1
# Evaluate
            if nl:
                tbox = xywh2xyxy(labels[:, 1:5])  # target boxes
                scale_coords(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
                labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels
                correct = process_batch(predn, labelsn, iouv)
                if iou_count:
                    oncecount = countsofiou(predn, labelsn, iouv).view(-1).to(iouv.device)
                    allcount = torch.cat((allcount, oncecount), 0)
                if plots:
                    confusion_matrix.process_batch(predn, labelsn)
            else:
                correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool)
            stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))  # (correct, conf, pcls, tcls)
    if iou_count:
        # 计算iou counts
        allcount = torch.round(allcount * 100) / 100
        unique_elements, counts = torch.unique(allcount, return_counts=True)
        # 将唯一元素和出现次数保存到 CSV 文件中
        with open(save_dir/'unique_elements.csv', mode='w') as csv_file:
            writer = csv.writer(csv_file)
            writer.writerow(['Unique Elements', 'Frequency'])
            for i in range(len(unique_elements)):
                writer.writerow([unique_elements[i], counts[i]])
        # 创建一个图形窗口
        fig, ax = plt.subplots()
        # 绘制柱状图
        ax.bar(unique_elements.cpu().numpy(), counts.cpu().numpy(), width=0.01, edgecolor='black')
        # 设置 x 和 y 轴标签以及标题
        ax.set_xlabel('IOU')
        ax.set_ylabel('Frequency')
        ax.set_title('Frequency of IOU')
        # 设置 x 轴范围为输入的范围
        ax.set_xlim(0.5, 1)
        # 显示图形
        plt.show()
        # fig改成plt的话保存的就是空白图片
        fig.savefig(save_dir / 'histogram.png')
        # 打印2位有效数字
        print("唯一元素:", unique_elements)
        print("出现次数:", counts)

以上代码保存了csv文件,绘制了柱状图并展示了柱状图,同时打印了iou统计数据。读者可以根据自己的需求删掉多余的代码,例如删掉plt.show(),将打印删掉等。

在plot.py中添加以下函数

def plot_image(images, targets, pre, paths=None, fname='images.jpg', names=None, max_size=1920, max_subplots=16):
    # Plot image grid with labels
    if isinstance(images, torch.Tensor):
        images = images.cpu().float().numpy()
    if isinstance(targets, torch.Tensor):
        targets = targets.cpu().numpy()
    if isinstance(pre, torch.Tensor):
        pre = pre.cpu().numpy()
    if np.max(images[0]) <= 1:
        images *= 255  # de-normalise (optional)
    bs, _, h, w = images.shape  # batch size, _, height, width
    bs = min(bs, max_subplots)  # limit plot images
    ns = np.ceil(bs ** 0.5)  # number of subplots (square)

    # Build Image
    mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8)  # init
    for i, im in enumerate(images):
        if i == max_subplots:  # if last batch has fewer images than we expect
            break
        x, y = int(w * (i // ns)), int(h * (i % ns))  # block origin
        im = im.transpose(1, 2, 0)
        mosaic[y:y + h, x:x + w, :] = im

    # Resize (optional)
    scale = max_size / ns / max(h, w)
    if scale < 1:
        h = math.ceil(scale * h)
        w = math.ceil(scale * w)
        mosaic = cv2.resize(mosaic, tuple(int(x * ns) for x in (w, h)))

    # Annotate
    fs = int((h + w) * ns * 0.01)  # font size
    annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True, example=names)
    for i in range(i + 1):
        x, y = int(w * (i // ns)), int(h * (i % ns))  # block origin
        annotator.rectangle([x, y, x + w, y + h], None, (255, 255, 255), width=2)  # borders 图像边界
        if paths:
            annotator.text((x + 5, y + 5 + h), text=Path(paths[i]).name[:40], txt_color=(220, 220, 220))  # filenames
        if len(targets) > 0:
            ti = targets[targets[:, 0] == i]  # image targets  bs的序号==i表示如果是该batch内的信息,则保存在ti中
            tj = pre[pre[:, 0] == i]
            boxesi = xywh2xyxy(ti[:, 2:6]).T
            boxesj = xywh2xyxy(tj[:, 2:6]).T
            classesi = ti[:, 1].astype('int')
            classesj = tj[:, 1].astype('int')
            labelsi = ti.shape[1] == 6  # labels if no conf column
            labelsj = tj.shape[1] == 6
            confi = None if labelsi else ti[:, 6]  # check for confidence presence (label vs pred)
            confj = None if labelsj else tj[:, 6]  # check for confidence presence (label vs pred)

            if boxesi.shape[1]:
                if boxesi.max() <= 1.01:  # if normalized with tolerance 0.01
                    boxesi[[0, 2]] *= w  # scale to pixels 框在这张图片上的原始尺寸大小的宽
                    boxesi[[1, 3]] *= h
                elif scale < 1:  # absolute coords need scale if image scales
                    boxesi *= scale
            boxesi[[0, 2]] += x  # 框在batch拼成的大图上的位置
            boxesi[[1, 3]] += y
            if boxesj.shape[1]:
                if boxesj.max() <= 1.01:  # if normalized with tolerance 0.01
                    boxesj[[0, 2]] *= w  # scale to pixels 框在这张图片上的原始尺寸大小的宽
                    boxesj[[1, 3]] *= h
                elif scale < 1:  # absolute coords need scale if image scales
                    boxesj *= scale
            boxesj[[0, 2]] += x  # 框在batch拼成的大图上的位置
            boxesj[[1, 3]] += y

            for j, box in enumerate(boxesi.T.tolist()):
                clsi = classesi[j]  # boxes与classes的索引是一一对应的
                colori = colors(clsi)
                clsi = names[clsi] if names else clsi
                if labelsi or confi[j] > 0.25:  # 0.25 conf thresh
                    labeli = f'{clsi}' if labelsi else f'{clsi} {confi[j]:.1f}'
                    annotator.box_label(box, labeli, color=colori)  # 画框
            for j, box in enumerate(boxesj.T.tolist()):
                clsj = classesj[j]
                colorj = colors(clsj)
                clsj = names[clsj] if names else clsj
                if labelsj or confj[j] > 0.25:  # 0.25 conf thresh
                    labelj = f'{clsj}' if labelsj else f'{clsj} {confj[j]:.1f}'
                    annotator.box_label(box, labelj, color=(0, 0, 255))  # 画框
    annotator.im.save(fname)  # save

再在val.py中导入库,和添加对应代码

from utils.plots import output_to_target, plot_images, plot_val_study, plot_image
        # Plot images Thread将会创建并启动两个新的线程来执行 plot_images函数,以提高程序的执行效率和响应速度
        if plots and batch_i < 3:
            f = save_dir / f'val_batch{batch_i}_labels.jpg'  # labels
            Thread(target=plot_images, args=(im, targets, paths, f, names), daemon=True).start()
            f = save_dir / f'val_batch{batch_i}_pred.jpg'  # predictions
            Thread(target=plot_images, args=(im, output_to_target(out), paths, f, names), daemon=True).start()
            f = save_dir / f'val_batch{batch_i}_labels&pred.jpg'  # predictions
            Thread(target=plot_image, args=(im, targets, output_to_target(out), paths, f, names), daemon=True).start()

 输出效果如下:val的前三个batch的label框与预测框同时展现。

接下来分析预测框与真实框之间的iou对比,比较两种算法的回归问题的准确性

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import csv
filename1 = '.../unique_elements.csv'
filename2 = '.../unique_elements.csv'
# 使用 Pandas 的 read_csv() 函数加载 CSV 文件
# 将 CSV 数据转换为张量(NumPy 数组)
baseline = pd.read_csv(filename1).values
improve = pd.read_csv(filename2).values
# 创建一个图形窗口
fig, ax = plt.subplots()
# 绘制柱状图 baseline[::2,0])选取偶数行,baseline[::2,0]选取奇数行
ax.bar(np.array(baseline[:,0]), np.array(baseline[:,1]), width=0.01, edgecolor='black', alpha=0.5)
ax.bar(np.array(improve[:,0]), np.array(improve[:,1]), width=0.01, edgecolor='black', color='red', alpha=0.5)
# 设置 x 和 y 轴标签以及标题
ax.set_xlabel('IOU')
ax.set_ylabel('Frequency')
ax.set_title('Frequency of IOU')
# 设置 x 轴范围为输入的范围
ax.set_xlim(0.5, 1)
ax.set_ylim(0, 25)
# 显示图形
plt.show()
# fig改成plt的话保存的就是空白图片
fig.savefig('bar.png')

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值