0.明确需求

今天要在模板匹配代码基础上,增加对多目标的支持。实际场景会是这样的:在一个图像中有几个目标存在,程序要找出所有高于阈值的目标保存它的置信度和中心点坐标,并在图中显示出来。

1.主要思路

类似多模板的支持一样,多目标版本使用np.where()获取所有高于阈值的匹配位置,收集所有有效匹配。
并介入非极大值抑制(NMS),添加max_overlap参数控制重叠阈值,使用cv2.dnn.NMSBoxes消除重叠框,保留置信度最高的非重叠匹配。

2. 相关函数介绍

2.1 np.where()

它是NumPy中一个非常强大的函数,用于 基于条件筛选数组元素返回满足条件的索引
函数原型:

np.where(condition, [x, y])

参数简介:

  • condition: 布尔条件(可以是数组)。
  • x: 当条件为 True 时返回的值(可选)。
  • y: 当条件为 False 时返回的值(可选)。

如果只传入 condition,则返回满足条件的 索引(坐标),记住返回的是索引,到时别蒙。

举几个栗子:

(1) 返回满足条件的索引
import numpy as np

arr = np.array([1, 2, 3, 4, 5])
indices = np.where(arr > 3)
print(indices)
# 输出: (array([3, 4],)

返回一个元组(即使是一维数组),其中包含满足 arr > 3 的元素的索引。

  • 多维数组示例
arr_2d = np.array([[1, 2], [3, 4]])
rows, cols = np.where(arr_2d > 2)
print(rows)# 输出: [1, 1](行索引)
print(cols)# 输出: [0, 1](列索引)
(2) 条件替换(类似三元表达式)
arr = np.array([1, 2, 3, 4])
result = np.where(arr > 2, "大于2", "小于等于2")
print(result)
# 输出: ['小于等于2' '小于等于2' '大于2' '大于2']

注意:
条件判断 arr > 2:对数组 arr 的每个元素检查是否大于 2,生成一个布尔数组: [False, False, True, True]
如果条件为 True,选择第二个参数 “大于2”;如果条件为 False,选择第三个参数 “小于等于2”。

(3) 结合广播机制
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
result = np.where([True, False, True], a, b)
print(result)
# 输出: [1 5 3](True选a的值,False选b的值)

注意:
布尔数组 [True, False, True],这是一个长度为3的布尔数组,表示选择规则:
True:从数组 a 中取对应位置的元素。
False:从数组 b 中取对应位置的元素。

2.2 非极大值抑制(NMS)

2.2.1 相关背景概念

非极大值抑制(Non-Maximum Suppression, NMS)是计算机视觉中一种非常重要的后处理算法,主要用于消除目标检测或模板匹配中产生的冗余检测框。在实际匹配过程中,你会发现莫名其妙多了很多框,这些就是重叠框。它的核心思想是"保留局部最大值",对于相互重叠的候选框,只保留置信度最高的那个。
此时我们要引入一个IoU(交并比)的概念。
IoU(Intersection over Union,交并比)是衡量两个边界框重叠程度的指标,是两个区域交集面积与并集面积的比值。
简单公式:IoU = Area of Intersection / Area of Union
IoU的取值范围是[0, 1],值越大表示重叠程度越高。

NMS的算法流程文字描述为:
输入一组检测框的坐标和置信度得分和IoU阈值(通常0.3-0.5),将所有检测框按置信度从高到低排序,选择置信度最高的检测框A,将其加入最终结果集,计算A与所有剩余检测框的IoU,移除所有与A的IoU大于阈值的检测框,重复上述过程,直到没有检测框剩余。

2.2.2 NMS在OpenCV中的实现

def nms_opencv(boxes, scores, threshold):
    """
    使用OpenCV的NMS实现
    :param boxes: [[x1,y1,x2,y2], ...]
    :param scores: 置信度数组
    :param threshold: IoU阈值
    :return: 保留的检测框索引
    """
    # 转换为OpenCV需要的格式
    boxes = np.array(boxes, dtype=np.float32)
    scores = np.array(scores, dtype=np.float32)
    
    # 使用OpenCV的NMSBoxes
    indices = cv2.dnn.NMSBoxes(boxes.tolist(), scores.tolist(), 
                              score_threshold=0.01,  # 最低置信度
                              nms_threshold=threshold)
    
    return indices.flatten()

cv2.dnn.NMSBoxes 是 OpenCV 深度学习模块(dnn)中提供的非极大值抑制(NMS)实现,是的,计算机视觉,深度学习迟早会来的。

函数原型:

indices = cv2.dnn.NMSBoxes(
    boxes, 
    scores, 
    score_threshold, 
    nms_threshold, 
    eta=None, 
    top_k=None
)

参数说明

参数 类型 说明
boxes List/Tuple/ndarray 边界框列表,格式为 [[x1,y1,x2,y2], ...][[x1,y1,w,h], ...]
scores List/Tuple/ndarray 每个边界框对应的置信度分数
score_threshold float 置信度阈值,低于此值的框会被直接过滤
nms_threshold float NMS的IoU阈值(0-1之间),重叠度高于此值的框会被抑制
eta float (可选) 自适应阈值系数,默认为1.0
top_k int (可选) 保留的最大检测框数量,默认为0(不限制)

返回值:返回一个NumPy数组,包含被保留的边界框的索引(在原boxes列表中的位置),形状为 (n,),其中n是保留的框数量。

多目标核心代码

    try:
        res = cv2.matchTemplate(img, scaled_template, method)

        # 获取所有高于阈值的匹配位置
        if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
            loc = np.where(
                res <= (1 - threshold) if method == cv2.TM_SQDIFF_NORMED else (1 - threshold) * res.max())
            confidences = 1 - res[loc]
        else:
            loc = np.where(res >= threshold)
            confidences = res[loc]

        # 转换为(x,y)坐标列表:loc[::-1]:元组中的元素顺序反转
        # *将反转后的元组解包为两个独立的数组参数传递给zip()函数
        # zip() 将两个数组中对应位置的元素配对组合
        locations = list(zip(*loc[::-1]))

        # 如果没有匹配结果则跳过
        if not locations:
            continue

        # 为当前模板的所有匹配创建矩形框
        rects = []
        for (x, y), conf in zip(locations, confidences):
            rects.append([x, y, scaled_w, scaled_h, conf])

        # 应用非极大值抑制(NMS)消除重叠框
        pick = []
        if rects:
            # 将矩形框转换为(x1,y1,x2,y2)格式
            boxes = np.array([[x, y, x + w, y + h] for (x, y, w, h, _) in rects])
            confidences = np.array([conf for (_, _, _, _, conf) in rects])

            # 使用OpenCV的NMSBoxes
            indices = cv2.dnn.NMSBoxes(boxes.tolist(), confidences.tolist(),
                                        threshold, max_overlap)

            if len(indices) > 0:
                pick = indices.flatten()

        # 保存筛选后的匹配结果
        for i in pick:
            x, y, w, h = rects[i][:4]
            confidence = rects[i][4]

            match_info = {
                'template': template_path,
                'top_left': (x, y),
                'bottom_right': (x + w, y + h),
                'center': (x + w // 2, y + h // 2),
                'confidence': float(confidence),
                'size': (w, h)
            }
            all_matches.append(match_info)

    except cv2.error as e:
        print(f"处理模板 {template_path} 时出错: {e}")
        continue

运行结果如下:
在这里插入图片描述

#opencv #模板匹配 #多目标匹配 #np.where() #非极大值抑制NMS #IoU

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐