OpenCV 做辅助标注:模板匹配 + 多尺度 + ORB 特征

有一批已经标好的图片和几张未标的图,想利用现成 ROI 做辅助标注——不需要训练模型、也没有 GPU,OpenCV 就能干。适合数据集初期”边标边攒模板”的阶段。

完整流程

已标注图 → 切出目标 ROI → 模板库

未标注图 → 在整图里搜索模板 → 最相似位置 + 置信度

                     自动生成框 → 人工审核

按目标形态不同,从简单到复杂选三种方案。

方案 1:单尺度模板匹配(最快)

假设你有一张图和已知的 ROI (x1, y1, x2, y2)

import cv2

img = cv2.imread("labeled.jpg")
template = img[y1:y2, x1:x2]     # 切出目标

target = cv2.imread("unlabeled.jpg")

result = cv2.matchTemplate(target, template, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(result)

h, w = template.shape[:2]
x, y = max_loc
box = [x, y, x + w, y + h]
print(f"置信度 {max_val:.3f}, 位置 {box}")
  • TM_CCOEFF_NORMED:归一化相关系数,返回 -1~1,越接近 1 越像
  • max_val > 0.8 是常见阈值,低于就认为没匹配上

优点:一张 5000×5000 大图匹配几毫秒,几千张图几分钟。 致命缺点:对缩放和旋转极其敏感。模板是 100×100 的猫,目标图里是 120×120 的猫,直接找不到。

方案 2:多尺度模板匹配

一次不行就多来几次不同大小:

import numpy as np, cv2

def multi_scale_match(target, template, scales=np.arange(0.5, 2.0, 0.1)):
    best = (0, None, None)   # (score, box, scale)
    for scale in scales:
        w = int(template.shape[1] * scale)
        h = int(template.shape[0] * scale)
        if w > target.shape[1] or h > target.shape[0]:
            continue
        resized = cv2.resize(template, (w, h))
        res = cv2.matchTemplate(target, resized, cv2.TM_CCOEFF_NORMED)
        _, max_val, _, max_loc = cv2.minMaxLoc(res)
        if max_val > best[0]:
            best = (max_val, (max_loc[0], max_loc[1], max_loc[0] + w, max_loc[1] + h), scale)
    return best

score, box, scale = multi_scale_match(target, template)

15-20 个尺度,能覆盖 50%-200% 的大小变化。速度成倍下降,但可接受。

方案 3:ORB 特征匹配(抗旋转、抗光照)

模板匹配的本质是像素直接对比。要抗旋转 / 光照变化 / 部分遮挡,得上特征点

import cv2

orb = cv2.ORB_create(nfeatures=5000)

kp1, des1 = orb.detectAndCompute(template, None)
kp2, des2 = orb.detectAndCompute(target,   None)

matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = matcher.match(des1, des2)
matches = sorted(matches, key=lambda m: m.distance)[:30]

# 用 findHomography 拿到模板在目标中的位置
if len(matches) >= 10:
    src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
    dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)

    H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

    # 模板四个角在目标图中的位置
    h, w = template.shape[:2]
    corners = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2)
    projected = cv2.perspectiveTransform(corners, H)
    print("目标位置四个角:", projected.reshape(-1, 2))
  • ORB 是 SIFT 的开源替代(SIFT 有专利,OpenCV 4.4+ 已解禁但仍需 contrib
  • findHomography + RANSAC 会过滤掉离群点
  • 汽车、行人、logo 这类有明显纹理的目标效果好;纯色物体(球、气球)不适合特征匹配

方案 4:深度特征(更稳)

模板匹配 + ORB 都在传统 CV 范畴。想做语义级别的辅助标注,得走深度模型:

  • DINO / DINOv2 — 自监督预训练的视觉特征提取
  • CLIP — 语义 embedding,跨模态匹配
  • SAM / SAM2 — 一键分割任意目标

例如用 DINOv2 抽特征做检索:

import torch
from PIL import Image

dinov2 = torch.hub.load("facebookresearch/dinov2", "dinov2_vitb14")
dinov2.eval().cuda()

def extract(img_path):
    img = Image.open(img_path).convert("RGB").resize((224, 224))
    x = torch.from_numpy(np.array(img)).permute(2, 0, 1).float().unsqueeze(0) / 255
    with torch.no_grad():
        return dinov2(x.cuda()).cpu().numpy()

emb1 = extract("template.jpg")
emb2 = extract("target.jpg")
similarity = np.dot(emb1, emb2.T) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))

深度特征天生抗光照、抗轻微遮挡、语义相似的东西也能匹上。缺点是需要 GPU + 慢。

组合方案(生产推荐)

辅助标注 pipeline

  1. 快速筛选:ORB / DINOv2 找候选图片
  2. 精确定位:候选图上跑多尺度 matchTemplate
  3. 置信度过滤:只保留 score > 阈值 的框
  4. 人工审核:把框显示到 Web UI 让人 confirm

比纯人工快 5-10 倍,比纯 CV 准得多。

一句话总结

辅助标注 pipeline:matchTemplate 最快但只抗平移、多尺度 matchTemplate 抗缩放、ORB 抗旋转、深度特征抗一切。从简单到复杂按需上,先跑通再优化。