PaddlePaddle深度学习项目中的非极大值抑制(NMS)技术详解
目标检测中的冗余框问题
在目标检测任务中,无论使用何种方法生成候选区域,都会面临一个共同的问题:模型可能会对同一个目标进行多次检测。这会导致每个物体周围出现多个重叠的预测框,严重影响检测结果的准确性。这种现象在基于深度学习的目标检测算法中尤为常见。
非极大值抑制(NMS)的基本原理
非极大值抑制(Non-Maximum Suppression, NMS)是一种后处理技术,用于消除冗余的预测框,保留最有可能代表真实目标的预测结果。其核心思想是:
- 对于每个检测到的物体,选择置信度(得分)最高的预测框作为候选
- 计算该候选框与其他所有预测框的交并比(IoU)
- 删除那些与候选框IoU超过预设阈值的预测框
- 对剩余的预测框重复上述过程,直到处理完所有预测框
NMS的具体实现步骤
让我们通过一个具体例子来理解NMS的工作流程:
- 输入准备:假设模型对一张图片进行预测,输出了11个预测框及其对应的置信度得分
- 得分排序:首先根据置信度得分对所有预测框进行降序排序
- 迭代处理:
- 选择得分最高的预测框作为候选
- 计算该候选框与剩余所有预测框的IoU
- 删除IoU超过阈值(通常设为0.5)的预测框
- 对剩余的预测框重复上述过程
代码实现解析
在PaddlePaddle深度学习框架中,NMS的实现通常包含以下几个关键部分:
- IoU计算:计算两个矩形框的交并比
def box_iou_xyxy(box1, box2):
# 计算两个矩形框的IoU
x1min, y1min, x1max, y1max = box1[0], box1[1], box1[2], box1[3]
x2min, y2min, x2max, y2max = box2[0], box2[1], box2[2], box2[3]
# 计算交集区域
xmin = np.maximum(x1min, x2min)
ymin = np.maximum(y1min, y2min)
xmax = np.minimum(x1max, x2max)
ymax = np.minimum(y1max, y2max)
# 计算交集面积
intersection = np.maximum(xmax - xmin, 0.) * np.maximum(ymax - ymin, 0.)
# 计算并集面积
area1 = (x1max - x1min) * (y1max - y1min)
area2 = (x2max - x2min) * (y2max - y2min)
union = area1 + area2 - intersection
# 计算IoU
iou = intersection / union
return iou
- 单类别NMS实现:
def nms(bboxes, scores, score_thresh, nms_thresh):
"""
非极大值抑制实现
:param bboxes: 预测框列表
:param scores: 对应的置信度得分
:param score_thresh: 得分阈值,低于此值的预测框将被忽略
:param nms_thresh: IoU阈值,高于此值的预测框将被抑制
:return: 保留的预测框索引
"""
inds = np.argsort(scores)
inds = inds[::-1] # 降序排列
keep_inds = []
while len(inds) > 0:
cur_ind = inds[0]
cur_score = scores[cur_ind]
# 如果当前得分低于阈值,直接跳出循环
if cur_score < score_thresh:
break
keep = True
for ind in keep_inds:
current_box = bboxes[cur_ind]
remain_box = bboxes[ind]
iou = box_iou_xyxy(current_box, remain_box)
if iou > nms_thresh:
keep = False
break
if keep:
keep_inds.append(cur_ind)
inds = inds[1:]
return np.array(keep_inds)
- 多类别NMS实现:当数据集中包含多个类别的物体时,需要对每个类别分别进行NMS处理
def multiclass_nms(bboxes, scores, score_thresh=0.01, nms_thresh=0.45,
pre_nms_topk=1000, pos_nms_topk=100):
"""
多类别非极大值抑制
:param bboxes: 预测框列表(批量)
:param scores: 对应的置信度得分(批量)
:param score_thresh: 得分阈值
:param nms_thresh: IoU阈值
:param pre_nms_topk: NMS前保留的预测框数量
:param pos_nms_topk: NMS后保留的预测框数量
:return: 保留的预测结果
"""
batch_size = bboxes.shape[0]
class_num = scores.shape[1]
rets = []
for i in range(batch_size):
bboxes_i = bboxes[i]
scores_i = scores[i]
ret = []
# 对每个类别分别进行NMS
for c in range(class_num):
scores_i_c = scores_i[c]
keep_inds = nms(bboxes_i, scores_i_c, score_thresh, nms_thresh)
if len(keep_inds) < 1:
continue
keep_bboxes = bboxes_i[keep_inds]
keep_scores = scores_i_c[keep_inds]
keep_results = np.zeros([keep_scores.shape[0], 6])
keep_results[:, 0] = c # 类别ID
keep_results[:, 1] = keep_scores[:] # 置信度得分
keep_results[:, 2:6] = keep_bboxes[:, :] # 框坐标
ret.append(keep_results)
if len(ret) < 1:
rets.append(ret)
continue
ret_i = np.concatenate(ret, axis=0)
scores_i = ret_i[:, 1]
# 限制最终保留的预测框数量
if len(scores_i) > pos_nms_topk:
inds = np.argsort(scores_i)[::-1]
inds = inds[:pos_nms_topk]
ret_i = ret_i[inds]
rets.append(ret_i)
return rets
NMS的优化与变种
在实际应用中,标准的NMS算法可能存在一些局限性,因此研究者们提出了多种改进版本:
- Soft-NMS:不是直接删除IoU超过阈值的预测框,而是降低它们的得分
- IoU-Net:通过学习预测框的定位质量来改进NMS过程
- Adaptive NMS:根据目标密度动态调整NMS阈值
- Cluster-NMS:通过矩阵运算加速NMS过程
实际应用中的注意事项
- 阈值选择:IoU阈值的选择对结果影响很大,通常设置在0.3-0.7之间
- 性能优化:对于大规模检测任务,NMS可能成为性能瓶颈,需要考虑优化实现
- 多类别处理:对于多类别检测,应该对每个类别分别进行NMS
- 边界情况:需要考虑空输入、单个预测框等边界情况的处理
总结
非极大值抑制是目标检测任务中不可或缺的后处理步骤,它能有效消除冗余的预测框,提高检测结果的准确性。在PaddlePaddle深度学习框架中,NMS的实现既考虑了单类别场景,也支持多类别检测任务。理解NMS的原理和实现细节,对于开发和优化目标检测模型具有重要意义。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考