Add project files
This commit is contained in:
110
pipeline/classical.py
Normal file
110
pipeline/classical.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user