有一批已经标好的图片和几张未标的图,想利用现成 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:
- 快速筛选:ORB / DINOv2 找候选图片
- 精确定位:候选图上跑多尺度 matchTemplate
- 置信度过滤:只保留 score > 阈值 的框
- 人工审核:把框显示到 Web UI 让人 confirm
比纯人工快 5-10 倍,比纯 CV 准得多。
一句话总结
辅助标注 pipeline:matchTemplate 最快但只抗平移、多尺度 matchTemplate 抗缩放、ORB 抗旋转、深度特征抗一切。从简单到复杂按需上,先跑通再优化。
