Справка

Chain Matching — оркестратор, последовательно запускающий несколько инструментов сопоставления. Вы передаёте массив шагов (steps) — каждый шаг содержит инструмент и параметры. Воркер выполняет шаги по порядку: если шаг нашёл совпадение — цепочка останавливается, если нет — переходит к следующему. Все шаги получают одну и ту же пару файлов.

Логика работы цепочки

Шаг 1
template
threshold=0.95
→ not found →
Шаг 2
template
threshold=0.7
→ not found →
Шаг 3
feature
SIFT + FLANN
→ found ✓ СТОП

Критерии «найдено» для каждого инструмента

ИнструментКритерийЧто проверяется
Template Matchingfiltered_matches > 0Есть хотя бы одно совпадение выше порога после NMS
Feature Matchinggood_matches > 0Есть хотя бы одна пара ключевых точек после ratio test
Surface Matchingposes_found > 0PPF-детектор нашёл хотя бы одну позу

Типичные сценарии

СценарийЦепочкаКогда использовать
Точный → мягкий 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)

ПараметрТипПо умолчаниюОписание
imagefileПервое изображение: image / query / model (обязательный)
templatefileВторое изображение: template / train / scene (обязательный)
stepsstring (JSON)JSON-массив шагов цепочки, макс. 10 (обязательный)

Формат каждого шага в массиве steps

ПолеТипОбязательныйОписание
toolstringtemplate | feature | surface
paramsobjectПараметры инструмента (см. Template, Feature, Surface)

Формат результата (result_data)

ПолеТипОписание
chain_resultstring"found" — хотя бы один шаг нашёл совпадение, "not_found" — ни один
completed_stepintНомер последнего выполненного шага (1-based)
total_stepsintОбщее число шагов в цепочке
stepsarrayМассив результатов каждого выполненного шага
steps[].stepintНомер шага
steps[].toolstringИнструмент шага
steps[].statusstringdone | skipped | error
steps[].foundboolНашёл ли шаг совпадение
steps[].result_dataobjectДанные результата (как у одиночного инструмента)
steps[].result_filesarrayФайлы результата шага

Примеры запросов и ответов

# ── Запрос: цепочка из 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 нашёл

Template: найдено
Шаг 1 — Template Matching
Feature: fallback
Шаг 2 — Feature Matching

Полная документация Chain Matching →