Chain Matching
Последовательный запуск нескольких методов с автоматическим fallbackСправка
Chain Matching — оркестратор, последовательно запускающий несколько инструментов сопоставления.
Вы передаёте массив шагов (steps) — каждый шаг содержит инструмент и параметры.
Воркер выполняет шаги по порядку: если шаг нашёл совпадение — цепочка останавливается,
если нет — переходит к следующему. Все шаги получают одну и ту же пару файлов.
Логика работы цепочки
Шаг 1
template
threshold=0.95
template
threshold=0.95
→ not found →
Шаг 2
template
threshold=0.7
template
threshold=0.7
→ not found →
Шаг 3
feature
SIFT + FLANN
feature
SIFT + FLANN
→ found ✓ СТОП
Критерии «найдено» для каждого инструмента
| Инструмент | Критерий | Что проверяется |
|---|---|---|
| Template Matching | filtered_matches > 0 | Есть хотя бы одно совпадение выше порога после NMS |
| Feature Matching | good_matches > 0 | Есть хотя бы одна пара ключевых точек после ratio test |
| Surface Matching | poses_found > 0 | PPF-детектор нашёл хотя бы одну позу |
Типичные сценарии
| Сценарий | Цепочка | Когда использовать |
|---|---|---|
| Точный → мягкий | Template (0.95) → Template (0.7) | Сначала ищем идеальное совпадение, потом снижаем порог |
| Шаблон → ключевые точки | Template (0.8) → Feature (SIFT, 0.7) | Template быстрее, но не инвариантен к масштабу/повороту |
| Каскад детекторов | Template → Feature (SIFT) → Feature (ORB) | SIFT точнее, ORB быстрее и может поймать то, что пропустил SIFT |
| Полный каскад | Template (0.9) → Template (0.7) → Feature (SIFT) → Feature (AKAZE) | Максимальный охват — от быстрого к глубокому |
Параметры запроса (multipart/form-data)
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
| image | file | — | Первое изображение: image / query / model (обязательный) |
| template | file | — | Второе изображение: template / train / scene (обязательный) |
| steps | string (JSON) | — | JSON-массив шагов цепочки, макс. 10 (обязательный) |
Формат каждого шага в массиве steps
| Поле | Тип | Обязательный | Описание |
|---|---|---|---|
tool | string | ✓ | template | feature | surface |
params | object | Параметры инструмента (см. Template, Feature, Surface) |
Формат результата (result_data)
| Поле | Тип | Описание |
|---|---|---|
chain_result | string | "found" — хотя бы один шаг нашёл совпадение, "not_found" — ни один |
completed_step | int | Номер последнего выполненного шага (1-based) |
total_steps | int | Общее число шагов в цепочке |
steps | array | Массив результатов каждого выполненного шага |
steps[].step | int | Номер шага |
steps[].tool | string | Инструмент шага |
steps[].status | string | done | skipped | error |
steps[].found | bool | Нашёл ли шаг совпадение |
steps[].result_data | object | Данные результата (как у одиночного инструмента) |
steps[].result_files | array | Файлы результата шага |
Примеры запросов и ответов
# ── Запрос: цепочка из 3 шагов ── curl -X POST http://localhost:8888/api/v1/match/chain \ -F "image=@scene.png" \ -F "template=@object.png" \ -F 'steps=[ {"tool":"template","params":{"method":"TM_CCOEFF_NORMED","threshold":0.95}}, {"tool":"template","params":{"threshold":0.7}}, {"tool":"feature","params":{"detector":"SIFT","matcher":"FLANN","ratio_threshold":0.7}} ]' # ── Ответ (201): задача создана ── # { # "uid": "e1f2a3b4-c5d6-7890-abcd-ef1234567890", # "status": "queued", # "tool": "chain", # "params": {"steps": [ # {"tool":"template","params":{"method":"TM_CCOEFF_NORMED","threshold":0.95}}, # {"tool":"template","params":{"threshold":0.7}}, # {"tool":"feature","params":{"detector":"SIFT","matcher":"FLANN","ratio_threshold":0.7}} # ]}, # "created_at": "2026-03-26T12:00:00", # "input_files": ["uploads/e1f2a.../scene.png","uploads/e1f2a.../object.png"], # "thumbnails": ["thumbnails/e1f2a.../scene.png","thumbnails/e1f2a.../object.png"], # "result_files": [], # "result_data": {} # } # ── Запрос: получить результат задачи ── curl http://localhost:8888/api/v1/tasks/e1f2a3b4-c5d6-7890-abcd-ef1234567890 # ── Ответ (200): задача завершена ── # { # "uid": "e1f2a3b4-c5d6-7890-abcd-ef1234567890", # "status": "done", # "tool": "chain", # "result_files": ["results/e1f2a.../step_2_matches.png","results/e1f2a.../step_2_heatmap.png"], # "result_data": { # "chain_result": "found", # "completed_step": 2, # "total_steps": 3, # "steps": [ # {"step":1,"tool":"template","status":"done","found":false, # "result_data":{"filtered_matches":0,"best_score":0.82}}, # {"step":2,"tool":"template","status":"done","found":true, # "result_data":{"filtered_matches":3,"best_score":0.82}, # "result_files":["results/e1f2a.../step_2_matches.png"]} # ] # } # }
// ── Запрос: отправка с цепочкой шагов ── const form = new FormData(); form.append("image", imageInput.files[0]); form.append("template", templateInput.files[0]); form.append("steps", JSON.stringify([ { tool: "template", params: { method: "TM_CCOEFF_NORMED", threshold: 0.95 } }, { tool: "template", params: { threshold: 0.7 } }, { tool: "feature", params: { detector: "SIFT", matcher: "FLANN", ratio_threshold: 0.7 } }, ])); const res = await fetch("/api/v1/match/chain", { method: "POST", body: form, }); const task = await res.json(); // ── Ответ task: ── // {uid: "e1f2a3b4-...", status: "queued", tool: "chain", // params: {steps: [...]}, // input_files: ["uploads/e1f2a.../scene.png","uploads/e1f2a.../object.png"]} // ── Запрос: поллинг статуса до завершения ── async function waitForResult(uid) { while (true) { const r = await fetch(`/api/v1/tasks/${uid}`); const data = await r.json(); if (data.status === "done" || data.status === "error") return data; await new Promise(r => setTimeout(r, 1000)); } } const result = await waitForResult(task.uid); // ── Ответ result (когда done): ── // {uid: "e1f2a3b4-...", status: "done", tool: "chain", // result_data: { // chain_result: "found", completed_step: 2, total_steps: 3, // steps: [ // {step:1, tool:"template", status:"done", found:false, ...}, // {step:2, tool:"template", status:"done", found:true, // result_data:{filtered_matches:3, best_score:0.82}, ...}]}}
import requests, json, time # ── Запрос: отправить цепочку ── steps = [ {"tool": "template", "params": {"method": "TM_CCOEFF_NORMED", "threshold": 0.95}}, {"tool": "template", "params": {"threshold": 0.7}}, {"tool": "feature", "params": {"detector": "SIFT", "ratio_threshold": 0.7}}, ] resp = requests.post( "http://localhost:8888/api/v1/match/chain", files={ "image": open("scene.png", "rb"), "template": open("object.png", "rb"), }, data={"steps": json.dumps(steps)}, ) task = resp.json() # ── Ответ task: ── # {"uid": "e1f2a3b4-...", "status": "queued", "tool": "chain", # "params": {"steps": [...]}, # "input_files": ["uploads/e1f2a.../scene.png","uploads/e1f2a.../object.png"]} # ── Запрос: дождаться результата ── while True: r = requests.get(f"http://localhost:8888/api/v1/tasks/{task['uid']}") result = r.json() if result["status"] in ("done", "error"): break time.sleep(1) # ── Ответ result (когда done): ── # {"uid": "e1f2a3b4-...", "status": "done", "tool": "chain", # "result_data": { # "chain_result": "found", # "completed_step": 2, # "total_steps": 3, # "steps": [ # {"step":1, "tool":"template", "status":"done", "found":false, # "result_data":{"filtered_matches":0, "best_score":0.82}}, # {"step":2, "tool":"template", "status":"done", "found":true, # "result_data":{"filtered_matches":3, "best_score":0.82}, # "result_files":["results/e1f2a.../step_2_matches.png"]}]}} # ── Проверка результата ── rd = result["result_data"] print(f"Итог: {rd['chain_result']}") print(f"Выполнено шагов: {rd['completed_step']}/{rd['total_steps']}") for s in rd["steps"]: print(f" Шаг {s['step']}: {s['tool']} — {'найдено' if s['found'] else 'не найдено'}")
Пример: Template не нашёл → Feature нашёл