Files
Vase-Matcher/pipeline/classical.py
2026-08-04 17:09:29 +05:30

111 lines
3.2 KiB
Python

"""
SIFT / ORB matching: one query image vs the fixed template set.
Template keypoints/descriptors are precomputed once at startup and reused
for every request -- only the query image is processed per request.
"""
import cv2
import numpy as np
import config
_sift = None
_orb = None
_template_features = {"SIFT": None, "ORB": None} # name -> {template_name: {"kp","des"}}
def _get_sift():
global _sift
if _sift is None:
_sift = cv2.SIFT_create()
return _sift
def _get_orb():
global _orb
if _orb is None:
_orb = cv2.ORB_create(nfeatures=config.ORB_N_FEATURES)
return _orb
def _detector_for(method):
return _get_sift() if method == "SIFT" else _get_orb()
def _matcher_for(method):
if method == "SIFT":
index_params = dict(algorithm=1, trees=5) # FLANN_INDEX_KDTREE
else:
index_params = dict(algorithm=6, table_number=6, key_size=12,
multi_probe_level=1) # FLANN_INDEX_LSH
search_params = dict(checks=50)
return cv2.FlannBasedMatcher(index_params, search_params)
def extract_features(method, bgr, mask):
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
detector = _detector_for(method)
kp, des = detector.detectAndCompute(gray, mask)
return kp, des
def set_template_features(method, template_name, kp, des):
if _template_features[method] is None:
_template_features[method] = {}
_template_features[method][template_name] = {"kp": kp, "des": des}
def get_template_features(method):
return _template_features[method] or {}
def match_pair(method, kp_q, des_q, kp_t, des_t):
"""Returns (inlier_count, confidence_pct)."""
if des_q is None or des_t is None or len(kp_q) == 0 or len(kp_t) == 0:
return 0, 0.0
matcher = _matcher_for(method)
try:
raw_matches = matcher.knnMatch(des_q, des_t, k=2)
except cv2.error:
return 0, 0.0
good = []
for pair in raw_matches:
if len(pair) != 2:
continue
m, n = pair
if m.distance < config.LOWE_RATIO * n.distance:
good.append(m)
if len(good) < config.MIN_RAW_MATCHES:
return 0, 0.0
src = np.float32([kp_q[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
dst = np.float32([kp_t[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
_, ransac_mask = cv2.findHomography(src, dst, cv2.RANSAC, 5.0)
if ransac_mask is None:
return 0, 0.0
inlier_count = int(ransac_mask.sum())
confidence_pct = (inlier_count / len(good)) * 100 if good else 0.0
return inlier_count, confidence_pct
def match_against_templates(method, kp_q, des_q):
"""Returns a list of {"template": name, "score": int, "confidence": float},
sorted by score descending, for every precomputed template."""
results = []
for template_name, data in get_template_features(method).items():
inlier_count, confidence_pct = match_pair(
method, kp_q, des_q, data["kp"], data["des"]
)
results.append({
"template": template_name,
"score": inlier_count,
"confidence": round(confidence_pct, 1),
})
results.sort(key=lambda r: r["score"], reverse=True)
return results