如何用SIFT/SURF等算法检测图像中所有匹配的菱形图案
多菱形目标检测改进实现
问题根源
你的代码只保留了第一个匹配结果,要检测所有菱形,需要筛选全部合格匹配对,再通过单应性变换定位目标,最后用非极大值抑制(NMS)去除重复/重叠的检测框。
推荐实现方案(Python + OpenCV)
用ORB算法(免费无专利限制,可替代SIFT/SURF),步骤如下:
- 加载模板图与大图,转为灰度图
- 初始化ORB检测器,提取特征点与描述子
- 用FLANN匹配器做特征匹配,过滤优质匹配对
- 通过单应性变换计算目标位置,生成候选框
- 非极大值抑制去除重叠框,保留唯一目标
代码示例
import cv2 import numpy as np def non_max_suppression(boxes, scores, threshold=0.5): if len(boxes) == 0: return [] boxes = np.array(boxes) scores = np.array(scores) x1 = boxes[:, 0] y1 = boxes[:, 1] x2 = boxes[:, 2] y2 = boxes[:, 3] areas = (x2 - x1 + 1) * (y2 - y1 + 1) order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h ovr = inter / (areas[i] + areas[order[1:]] - inter) inds = np.where(ovr <= threshold)[0] order = order[inds + 1] return boxes[keep].astype(int) # 加载图像 template = cv2.imread('diamond_template.jpg', 0) img = cv2.imread('poker_card.jpg', 0) h, w = template.shape # 初始化ORB检测器 orb = cv2.ORB_create(500) kp1, des1 = orb.detectAndCompute(template, None) kp2, des2 = orb.detectAndCompute(img, None) # FLANN匹配器配置 FLANN_INDEX_LSH = 6 index_params = dict(algorithm=FLANN_INDEX_LSH, table_number=6, key_size=12, multi_probe_level=1) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(des1, des2, k=2) # Lowe's ratio test过滤优质匹配 good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: good_matches.append(m) # 收集候选框与匹配得分 boxes = [] scores = [] MIN_MATCH_COUNT = 10 if len(good_matches) > MIN_MATCH_COUNT: src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2) # 多次滑动取匹配子集,覆盖更多目标 sample_count = 5 for _ in range(sample_count): if len(good_matches) < MIN_MATCH_COUNT: break # 随机采样匹配对 sample_indices = np.random.choice(len(good_matches), MIN_MATCH_COUNT, replace=False) sample_src = src_pts[sample_indices] sample_dst = dst_pts[sample_indices] M, mask = cv2.findHomography(sample_src, sample_dst, cv2.RANSAC, 5.0) if M is not None: # 计算目标框 pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1, 1, 2) dst = cv2.perspectiveTransform(pts, M) x_min = int(np.min(dst[:, 0, 0])) y_min = int(np.min(dst[:, 0, 1])) x_max = int(np.max(dst[:, 0, 0])) y_max = int(np.max(dst[:, 0, 1])) boxes.append([x_min, y_min, x_max, y_max]) scores.append(len([m for i, m in enumerate(good_matches) if mask[i]])) # 非极大值抑制去重 if boxes: final_boxes = non_max_suppression(boxes, scores, threshold=0.3) # 绘制结果 img_color = cv2.imread('poker_card.jpg') for box in final_boxes: x1, y1, x2, y2 = box cv2.rectangle(img_color, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite('detected_result.jpg', img_color) else: print("未检测到足够匹配的目标")
C++实现思路
逻辑与Python完全一致,核心步骤:
- 用
cv::ORB提取特征点与描述子 cv::FlannBasedMatcher执行特征匹配,过滤优质匹配对- 多次采样匹配对计算单应性矩阵,生成候选检测框
- 实现非极大值抑制函数去除重叠框
- 绘制最终检测结果
关键代码片段
#include <opencv2/opencv.hpp> #include <vector> #include <algorithm> #include <numeric> using namespace cv; using namespace std; vector<Rect> nonMaxSuppression(vector<Rect> boxes, vector<float> scores, float threshold) { vector<Rect> keep; if (boxes.empty()) return keep; vector<int> indices(boxes.size()); iota(indices.begin(), indices.end(), 0); sort(indices.begin(), indices.end(), [&](int a, int b) { return scores[a] > scores[b]; }); while (!indices.empty()) { int i = indices[0]; keep.push_back(boxes[i]); vector<int> newIndices; for (size_t j = 1; j < indices.size(); j++) { int idx = indices[j]; Rect inter = boxes[i] & boxes[idx]; float areaInter = inter.area(); float areaUnion = boxes[i].area() + boxes[idx].area() - areaInter; float overlap = areaInter / areaUnion; if (overlap <= threshold) { newIndices.push_back(idx); } } indices = newIndices; } return keep; } int main() { Mat templateImg = imread("diamond_template.jpg", IMREAD_GRAYSCALE); Mat img = imread("poker_card.jpg", IMREAD_GRAYSCALE); int h = templateImg.rows, w = templateImg.cols; Ptr<ORB> orb = ORB::create(500); vector<KeyPoint> kp1, kp2; Mat des1, des2; orb->detectAndCompute(templateImg, noArray(), kp1, des1); orb->detectAndCompute(img, noArray(), kp2, des2); Ptr<FlannBasedMatcher> matcher = FlannBasedMatcher::create(makePtr<flann::LshIndexParams>(6, 12, 1)); vector<vector<DMatch>> matches; matcher->knnMatch(des1, des2, matches, 2); vector<DMatch> goodMatches; for (auto &m : matches) { if (m[0].distance < 0.7 * m[1].distance) { goodMatches.push_back(m[0]); } } vector<Rect> boxes; vector<float> scores; const int MIN_MATCH_COUNT = 10; if (goodMatches.size() > MIN_MATCH_COUNT) { vector<Point2f> srcPts, dstPts; for (auto &m : goodMatches) { srcPts.push_back(kp1[m.queryIdx].pt); dstPts.push_back(kp2[m.trainIdx].pt); } int sampleCount = 5; for (int i = 0; i < sampleCount; i++) { if (goodMatches.size() < MIN_MATCH_COUNT) break; vector<int> indices(goodMatches.size()); iota(indices.begin(), indices.end(), 0); random_shuffle(indices.begin(), indices.end()); vector<Point2f> sampleSrc, sampleDst; for (int j = 0; j < MIN_MATCH_COUNT; j++) { sampleSrc.push_back(srcPts[indices[j]]); sampleDst.push_back(dstPts[indices[j]]); } Mat M = findHomography(sampleSrc, sampleDst, RANSAC, 5.0); if (!M.empty()) { vector<Point2f> templateCorners = {Point2f(0,0), Point2f(0,h-1), Point2f(w-1,h-1), Point2f(w-1,0)}; vector<Point2f> dstCorners; perspectiveTransform(templateCorners, dstCorners, M); Rect box = boundingRect(dstCorners); boxes.push_back(box); vector<uchar> mask; findHomography(sampleSrc, sampleDst, RANSAC, 5.0, mask); scores.push_back(count(mask.begin(), mask.end(), 1)); } } } vector<Rect> finalBoxes = nonMaxSuppression(boxes, scores, 0.3); Mat imgColor = imread("poker_card.jpg"); for (auto &box : finalBoxes) { rectangle(imgColor, box, Scalar(0,255,0), 2); } imwrite("detected_result.jpg", imgColor); return 0; }
注意事项
- 单应性变换天然适配菱形的旋转、缩放场景,比传统模板匹配鲁棒性更强
- 可调整
MIN_MATCH_COUNT、匹配距离阈值(0.7)平衡检测精度与召回率 - 非极大值抑制的阈值(0.3)可根据目标重叠程度灵活调整
- 若需使用SIFT/SURF,只需替换ORB初始化部分(SIFT需启用OpenCV的xfeatures2d模块)
内容的提问来源于stack exchange,提问作者codeDom
相关产品推荐
相关产品推荐

