目标检测加权框融合 WBF(Weighted Boxes Fusion)

1. WBF简介

        WBF是一种目标检测模型集成方法,最初是由 Ensemble of Exemplar-SVMs (EES) 的作者在其论文中提出的。WBF 可以通过对多个已有目标检测模型的结果进行融合,来提高检测精度。

2. WBF的具体实现如下

(1)假设有训练好的N个模型对同一幅图像进行预测,每个模型的预测框被添加到一个单独的集合B,并将列表中的元素按照置信度降 C {\mathbf{C}} C序排序。
(2)建立两个空列表:列表L表示每个目标检测框的cluster(用于存放属于同一个目标的所有框),列表F表示每个目标的fusion即融合框(用cluster 中所有的框融合的新框)。
(3)遍历列表B中的预测框,在F中找到相应的匹配框(IoU大于阈值的框)。
        如果没有找到匹配项,则将列表B中的框作为新框添加到列表L和F的最后,然后继续匹配列表B中的下一个框。
        如果找到匹配项,将此框添加到列表L中与列表F中匹配框对应的位置。
(4)使用以下的融合公式,对cluster中每个位置的所有所有 T {\mathbf{T}} T个框,重新计算新的坐标和置信度。然后设置fusion中每个目标框的置信度为该目标所有框的平均置信度。每个框的坐标是该目标所有框坐标的加权和,权重就是每个框的置信度,因此置信度较高的框对最终融合坐标的贡献越大。
C = ∑ i = 1 T C i T X 1 , 2 = ∑ i = 1 T C i ∗ X 1 , 2 i ∑ i = 1 T C i Y 1 , 2 = ∑ i = 1 T C i ∗ Y 1 , 2 i ∑ i = 1 T C i \begin{gathered} \mathbf{C}=\frac{\sum_{i=1}^{\mathbf{T}}\mathbf{C}_i}{T} \\ \mathbf{X}\mathbf{1},\mathbf{2} =\frac{\sum_{i=1}^\mathbf{T}\mathbf{C}_i*\mathbf{X}\mathbf{1},\mathbf{2}_i}{\sum_{i=1}^\mathbf{T}\mathbf{C}_i} \\ \mathbf{Y1,2} =\frac{\sum_{i=1}^\mathbf{T}\mathbf{C}_i*\mathbf{Y}\mathbf{1},\mathbf{2}_i}{\sum_{i=1}^\mathbf{T}\mathbf{C}_i} \end{gathered} C=Ti=1TCiX1,2=i=1TCii=1TCiX1,2iY1,2=i=1TCii=1TCiY1,2i
其中 X 1 , 2 {\mathbf{X}\mathbf{1},\mathbf{2} } X1,2预测框的横坐标 x 1 , x 2 {x_1,x_2} x1,x2 Y 1 , 2 {\mathbf{Y}\mathbf{1},\mathbf{2} } Y1,2预测框的纵坐标 y 1 , y 2 {y_1,y_2} y1,y2
(5)处理完B中的所有框以后,重新算出F中的置信度得分,具体来说:先乘框的总数,再除以模型的数量 N {N} N。如果cluster中框的个数很少,也就意味着只有少数模型预测到目标,这时可以用以下两种方式降低这种情况的置信度分数:
C = C ∗ m i n ( T , N ) N , C = C ∗ T N , \begin{aligned}\mathbf{C}&=\mathbf{C}*\frac{min(T,N)}N,\\\\\mathbf{C}&=\mathbf{C}*\frac TN,\end{aligned} CC=CNmin(T,N),=CNT,

3. 代码详解


import warnings
import numpy as np

def weighted_boxes_fusion(
        boxes_list, # 每个模型的预测框列表,每个框是4个数字。shape为(models_number, model_preds, 4)
        scores_list, # 每个模型预测框分数列表。shape为(models_number, model_preds)
        labels_list, # 每个模型预测框类别列表。shape为(models_number, model_preds)
        weights=None, # 每个模型的权重,默认:None,每个模型权重为 1
        iou_thr=0.55, # 与当前框的IoU值大于 iou_thr 的预测框都会被舍弃
        skip_box_thr=0.0, # 排除得分低于此变量的预测框
        conf_type='avg', # 如何计算加权框的置信度,
        allows_overflow=False # 如果我们希望置信度不超过1.0,则为False
):
    '''
    'avg': 平均值,
    'max': 最大值,
    'box_and_model_avg': 预测框和模型混合加权平均,
    'absent_model_aware_avg': 考虑了缺席(不在集群中)模型的加权平均。
    '''
    # 判断每个模型的权重是否为None,如果为None,权重都设为 1
    if weights is None:
        weights = np.ones(len(boxes_list))
    # 每个模型权重的个数必须和模型个数相等
    if len(weights) != len(boxes_list):
        print('Warning: incorrect number of weights {}. Must be: {}. Set weights equal to 1.'.format(len(weights), len(boxes_list)))
        weights = np.ones(len(boxes_list))
    weights = np.array(weights)
    # 确定计算加权框的置信度的方法是否合理
    if conf_type not in ['avg', 'max', 'box_and_model_avg', 'absent_model_aware_avg']:
        print('Unknown conf_type: {}. Must be "avg", "max" or "box_and_model_avg", or "absent_model_aware_avg"'.format(conf_type))
        exit()
    # 根据 skip_box_thr 初步过滤预测框,并进行简单的修正,输出字典 {label:[[label, score*weight, weight, model index, x1, y1, x2, y2],...]}
    filtered_boxes = prefilter_boxes(boxes_list, scores_list, labels_list, weights, skip_box_thr)
    # 初步筛选后的预测框为0个,直接返回0if len(filtered_boxes) == 0:
        return np.zeros((0, 4)), np.zeros((0,)), np.zeros((0,))

    overall_boxes = []
    for label in filtered_boxes:
        # 取出字典中键为label的值
        boxes = filtered_boxes[label]
        new_boxes = []
        # np.empty()根据给定的维度和数值类型返回一个新的数组,其元素不进行初始化。
        # 初始化一个融合框,后面会选出IoU与之相近的预测框进行融合,更新这个融合框。
        weighted_boxes = np.empty((0, 8))

        # Clusterize boxes
        for j in range(0, len(boxes)):
            index, best_iou = find_matching_box_fast(weighted_boxes, boxes[j], iou_thr)

            if index != -1:
                # new_boxes里面存放着相应index的融合框由哪些预测框融合而成。
                new_boxes[index].append(boxes[j])
                # 预测框融合
                weighted_boxes[index] = get_weighted_box(new_boxes[index], conf_type)
            else:
                new_boxes.append([boxes[j].copy()])
                weighted_boxes = np.vstack((weighted_boxes, boxes[j].copy()))

        # 根据模型和框的数量重新调整置信度
        for i in range(len(new_boxes)):
            # new_boxes里面存放着相应index的融合框由哪些预测框融合而成。这些预测框称之为集群框。
            clustered_boxes = new_boxes[i]
            if conf_type == 'box_and_model_avg':
                clustered_boxes = np.array(clustered_boxes)
                # 边框数加权平均
                weighted_boxes[i, 1] = weighted_boxes[i, 1] * len(clustered_boxes) / weighted_boxes[i, 2]
                # identify unique model index by model index column
                # np.unique
                # 去除其中重复的元素 ,并按元素 由小到大 返回一个新的无元素重复的元组或者列表
                # return_index=True 返回新列表元素在旧列表中的位置(下标),并以列表形式存储。
                _, idx = np.unique(clustered_boxes[:, 3], return_index=True)
                # 根据唯一的模型权重重新缩放
                weighted_boxes[i, 1] = weighted_boxes[i, 1] *  clustered_boxes[idx, 2].sum() / weights.sum()
            elif conf_type == 'absent_model_aware_avg':
                clustered_boxes = np.array(clustered_boxes)
                # 获取集群中唯一的模型索引
                # np.unique
                # 去除其中重复的元素 ,并按元素 由小到大 返回一个新的无元素重复的元组或者列表
                # 返回新列表元素在旧列表中的位置(下标),并以列表形式存储。
                models = np.unique(clustered_boxes[:, 3]).astype(int)
                # 创建一个蒙版来获取未使用的模型权重
                mask = np.ones(len(weights), dtype=bool)
                # 将已经在边框融合中使用的模型权重的mask设为False
                mask[models] = False
                # absent model aware weighted average
                # 使用全部模型的权重来调整置信度
                weighted_boxes[i, 1] = weighted_boxes[i, 1] * len(clustered_boxes) / (weighted_boxes[i, 2] + weights[mask].sum())
            elif conf_type == 'max':
                weighted_boxes[i, 1] = weighted_boxes[i, 1] / weights.max()
            elif not allows_overflow:
                weighted_boxes[i, 1] = weighted_boxes[i, 1] * min(len(weights), len(clustered_boxes)) / weights.sum()
            else:
                weighted_boxes[i, 1] = weighted_boxes[i, 1] * len(clustered_boxes) / weights.sum()
        overall_boxes.append(weighted_boxes)
    overall_boxes = np.concatenate(overall_boxes, axis=0)
    overall_boxes = overall_boxes[overall_boxes[:, 1].argsort()[::-1]]
    boxes = overall_boxes[:, 4:]
    scores = overall_boxes[:, 1]
    labels = overall_boxes[:, 0]
    return boxes, scores, labels



def prefilter_boxes(boxes, scores, labels, weights, thr):
    # Create dict with boxes stored by its label
    new_boxes = dict()
    # 遍历每一个模型
    for t in range(len(boxes)):
        # 预测框的个数必须和预测框得分个数相等
        if len(boxes[t]) != len(scores[t]):
            print('Error. Length of boxes arrays not equal to length of scores array: {} != {}'.format(len(boxes[t]), len(scores[t])))
            exit()
        # 预测框的个数必须和预测框类别个数相等
        if len(boxes[t]) != len(labels[t]):
            print('Error. Length of boxes arrays not equal to length of labels array: {} != {}'.format(len(boxes[t]), len(labels[t])))
            exit()
        # 遍历每个预测框
        for j in range(len(boxes[t])):
            # 预测框得分
            score = scores[t][j]
            # 如果预测框得分小于最小分则被舍弃
            if score < thr:
                continue
            label = int(labels[t][j])
            box_part = boxes[t][j]
            x1 = float(box_part[0])
            y1 = float(box_part[1])
            x2 = float(box_part[2])
            y2 = float(box_part[3])

            # 进行一些操作使预测框更加合理,如果预测框没有归一化,需要把关于归一化的处理注释掉
            if x2 < x1:
                warnings.warn('X2 < X1 value in box. Swap them.')
                x1, x2 = x2, x1
            if y2 < y1:
                warnings.warn('Y2 < Y1 value in box. Swap them.')
                y1, y2 = y2, y1
            if x1 < 0:
                warnings.warn('X1 < 0 in box. Set it to 0.')
                x1 = 0
            if x1 > 1:
                warnings.warn('X1 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.')
                x1 = 1
            if x2 < 0:
                warnings.warn('X2 < 0 in box. Set it to 0.')
                x2 = 0
            if x2 > 1:
                warnings.warn('X2 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.')
                x2 = 1
            if y1 < 0:
                warnings.warn('Y1 < 0 in box. Set it to 0.')
                y1 = 0
            if y1 > 1:
                warnings.warn('Y1 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.')
                y1 = 1
            if y2 < 0:
                warnings.warn('Y2 < 0 in box. Set it to 0.')
                y2 = 0
            if y2 > 1:
                warnings.warn('Y2 > 1 in box. Set it to 1. Check that you normalize boxes in [0, 1] range.')
                y2 = 1
            if (x2 - x1) * (y2 - y1) == 0.0:
                warnings.warn("Zero area box skipped: {}.".format(box_part))
                continue
            # 将标签,得分,模型权重,模型的索引,坐标作为 值 存入字典,键 为标签
            # [label, score, weight, model index, x1, y1, x2, y2]
            b = [int(label), float(score) * weights[t], weights[t], t, x1, y1, x2, y2]
            if label not in new_boxes:
                new_boxes[label] = []
            new_boxes[label].append(b)

    # 按分数排序字典中的每个列表,并将其转换为numpy数组
    for k in new_boxes:
        current_boxes = np.array(new_boxes[k])
        new_boxes[k] = current_boxes[current_boxes[:, 1].argsort()[::-1]]

    return new_boxes


def get_weighted_box(boxes, conf_type='avg'):
    """
    Create weighted box for set of boxes
    :param boxes: set of boxes to fuse
    :param conf_type: type of confidence one of 'avg' or 'max'
    :return: weighted box (label, score, weight, model index, x1, y1, x2, y2)
    """

    box = np.zeros(8, dtype=np.float32)
    conf = 0
    conf_list = []
    w = 0
    # 遍历每个预测框
    for b in boxes:
        # 预测框坐标乘以得分并相加
        box[4:] += (b[1] * b[4:])
        # 得分相加
        conf += b[1]
        # 得分列表
        conf_list.append(b[1])
        # 模型权重相加
        w += b[2]
    # 类别
    box[0] = boxes[0][0]
    if conf_type in ('avg', 'box_and_model_avg', 'absent_model_aware_avg'):
        # 融合框的分数等于预测框平均分数
        box[1] = conf / len(boxes)
    elif conf_type == 'max':
        # 融合框的分数等于预测框最大分数
        box[1] = np.array(conf_list).max()
    # 融合框的模型权重等于预测框模型权重的总和
    box[2] = w
    # 模型索引(无用)设为-1
    box[3] = -1 # model index field is retained for consistency but is not used.
    # 融合框的坐标等于预测框坐标和除以得分和
    box[4:] /= conf
    return box


def find_matching_box_fast(boxes_list, new_box, match_iou):
    """
        Reimplementation of find_matching_box with numpy instead of loops. Gives significant speed up for larger arrays
        (~100x). This was previously the bottleneck since the function is called for every entry in the array.
    """

    def bb_iou_array(boxes, new_box):
        # bb interesection over union
        xA = np.maximum(boxes[:, 0], new_box[0])
        yA = np.maximum(boxes[:, 1], new_box[1])
        xB = np.minimum(boxes[:, 2], new_box[2])
        yB = np.minimum(boxes[:, 3], new_box[3])

        interArea = np.maximum(xB - xA, 0) * np.maximum(yB - yA, 0)

        # compute the area of both the prediction and ground-truth rectangles
        boxAArea = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
        boxBArea = (new_box[2] - new_box[0]) * (new_box[3] - new_box[1])

        iou = interArea / (boxAArea + boxBArea - interArea)

        return iou
    # 如果融合框个数为0,不进行融合,因为初始化融合框个数为0,后面会选出得分最高的框添加融合框列表
    if boxes_list.shape[0] == 0:
        return -1, match_iou

    # boxes = np.array(boxes_list)
    boxes = boxes_list
    # 计算融合框和预测框IoU值
    ious = bb_iou_array(boxes[:, 4:], new_box[4:])
    # 将类别不同的预测框IoU值设为 -1
    ious[boxes[:, 0] != new_box[0]] = -1

    # 筛选最大的IoU
    best_idx = np.argmax(ious)
    best_iou = ious[best_idx]
    # 如果最大的IoU小于等于设定的阈值,则将最大IoU值设为阈值,预测框的索引设为-1,下一步将此框添加到融合框列表
    if best_iou <= match_iou:
        best_iou = match_iou
        best_idx = -1
    # 返回此预测框可以和融合框列表哪个框融合,和相应的IoU值
    return best_idx, best_iou

### 回答1: 加权融合Weighted Box FusionWBF)是一种目标检测算法中的后处理技术,用于将多个模型的检测结果进行融合,提高检测精度。在YOLOv5中,WBF被用于将多个检测模型的结果进行融合,从而得到更准确的目标检测结果。WBF的核心思想是对多个模型的检测结果进行加权平均,其中权重根据每个模型的置信度和重叠度进行计算。通过WBF,YOLOv5可以在保持高速度的同时,提高目标检测的准确性。 ### 回答2: 加权融合 (weighted box fusion, WBF) 是一种目标检测模型集成方法,最初是由 Ensemble of Exemplar-SVMs (EES) 的作者在其论文中提出的。WBF 可以通过对多个已有目标检测模型的结果进行融合,来提高检测精度。 相比于其他的融合方法,WBF 的优势在于能够考虑每个模型输出的盒子的置信度得分,而不仅是盒子的位置信息。这意味着在融合的过程中,WBF 会根据每个模型得出的结果的置信度,动态地为不同的盒子分配不同的权重系数,从而更准确地融合检测结果。在 YOLOv5 中,WBF 被应用于多个目标检测模型的集成中。 WBF实现上需要考虑到一些问题,比如盒子重叠和不同模型结果的一致性。具体来说,WBF 根据 NMS (non-maximum suppression) 的思想,对于同一类别的盒子,保留置信度最高的那一个,然后根据每个模型输出的盒子的置信度得分进行加权融合。 在 YOLOv5 中,WBF 开箱即用,使用起来非常简单。通过在训练集上训练多个目标检测模型,在测试时将每个模型输出的结果输入到 WBF 中进行融合,便可以提高检测精度。同时,YOLOv5 还考虑了 WBF 在 GPU 上的实现效率,通过使用 PyTorch 的并行计算技术,使得 WBF 的计算速度得到了很大提升。 总之,加权融合 (WBF) 对于提高目标检测模型的检测精度具有很大的帮助。在 YOLOv5 中,WBF 的应用方便,计算效率高,可以通过使用多个模型的集成,进一步提升模型的准确性。 ### 回答3: 加权融合Weighted Boxes FusionWBF)是一种用于多物体检测融合的方法,能够有效地提高检测的准确率和鲁棒性。WBF在YOLOv5中被广泛应用,已经成为了一个重要的特性。 WBF将多个检测器的输出结合起来,形成最终的检测。这里所谓的“加权”是指融合过程中每个检测的权重不同,这些权重会在上下文中自动学习到。这个过程可以用以下几步来描述: 步骤一:定义IOU阈值 首先,WBF需要定义一个IOU阈值。对于两个检测A和B,如果它们的IOU(交并比)超过了这个阈值,那么它们就会被视为重叠的。 步骤二:计算权重 WBF利用历史检测的信息来估算每个检测的权重。权重是通过计算检测和历史检测之间的IOU得到的。具体来说,在一段时间内,如果一个物体没有移动太远,那么在下一帧中检测到的物体可以由上一帧检测到的做出修改,这也被称为跟踪信息。因此,WBF对于每个检测计算历史检测中对其影响最大的几个的IOU。 步骤三:融合 在定义IOU阈值和计算权重之后,WBF将使用融合来代表每个物体。融合的位置和形状与所有权重大于0的检测的位置和形状相似,但具有更高的置信度得分。具体而言,融合的位置和形状会根据所有权重大于0的检测的位置和形状进行加权求和平均。 步骤四:过滤 最后,WBF会过滤掉得分低于一定阈值的融合。过滤后,剩下的融合就是最终的检测结果。 总之,WBF是一种有效的多目标检测融合方法,能够有效地提高检测的准确性和鲁棒性。在YOLOv5中,WBF已经被广泛应用,并成为一个重要的特性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值