0.05 Меньше — точнее, но медленнее. 0.025 для точного, 0.1 для быстрого
0.05 Шаг хеширования PPF-пар. Обычно равен sampling step
Дискретизация вращения. 30 — баланс, 60 — точнее
Iterative Closest Point — уточняет позу после PPF
Остановка ICP при изменении ошибки меньше порога
Максимальное число итераций. 100–200 обычно достаточно

Справка

Пример входных данных

Surface Matching работает с 3D-облаками точек в формате PLY (с нормалями). Результат — матрица трансформации 4×4, переводящая координаты модели в координаты сцены.

СценарийSamplingICPИтерации
Точное сопоставление0.025Да200
Быстрый поиск0.1Нет
Шумные данные0.05Да300
Большие модели0.08Да150

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

ПараметрТипПо умолчаниюОписание
modelfile3D-модель (.ply) — обязательный
scenefile3D-сцена (.ply) — обязательный
relative_sampling_stepfloat0.05Шаг дискретизации (0.01–0.5)
relative_distance_stepfloat0.05Шаг расстояния PPF (0.01–0.5)
num_anglesint30Углы дискретизации (1–360)
use_icpbooltrueУточнение через ICP
icp_tolerancefloat0.005Порог сходимости ICP
icp_iterationsint100Макс. итераций ICP (1–1000)

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

# ── Запрос: отправка PLY-файлов ──
curl -X POST http://localhost:8888/api/v1/match/surface \
  -F "model_file=@model.ply" \
  -F "scene_file=@scene.ply"

# ── Ответ (201): задача создана ──
# {
#   "uid": "c9a1e2b3-4d5f-6789-abcd-ef0123456789",
#   "status": "queued",
#   "tool": "surface",
#   "params": {
#     "relative_sampling_step": 0.05, "relative_distance_step": 0.05,
#     "num_angles": 30, "use_icp": true, "icp_tolerance": 0.005, "icp_iterations": 100
#   },
#   "created_at": "2026-03-25T14:30:00",
#   "input_files": ["uploads/c9a1e.../model.ply","uploads/c9a1e.../scene.ply"],
#   "result_files": [], "result_data": {}
# }

# ── Запрос: получить результат ──
curl http://localhost:8888/api/v1/tasks/c9a1e2b3-4d5f-6789-abcd-ef0123456789

# ── Ответ (200): задача завершена ──
# {
#   "uid": "c9a1e2b3-4d5f-6789-abcd-ef0123456789",
#   "status": "done",
#   "tool": "surface",
#   "result_files": ["results/c9a1e.../pose.json"],
#   "result_data": {
#     "pose": [[0.99,-0.01,0.03,12.5],[0.01,0.99,-0.02,-3.1],[-0.03,0.02,0.99,0.8],[0,0,0,1]],
#     "num_model_points": 4521, "num_scene_points": 18230, "icp_residual": 0.0023
#   }
# }

# ── Запрос: обновить настройки ──
curl -X PUT http://localhost:8888/api/v1/settings/surface \
  -H "Content-Type: application/json" \
  -d '{"relative_sampling_step":0.05,"relative_distance_step":0.05,"num_angles":30,"use_icp":true,"icp_tolerance":0.005,"icp_iterations":100}'

# ── Ответ (200) ──
# {
#   "tool": "surface",
#   "params": {"relative_sampling_step":0.05,"relative_distance_step":0.05,"num_angles":30,"use_icp":true,"icp_tolerance":0.005,"icp_iterations":100}
# }
// ── Запрос: отправка PLY-файлов ──
const form = new FormData();
form.append("model_file", modelFile);
form.append("scene_file", sceneFile);
form.append("relative_sampling_step", "0.025");
form.append("use_icp", "true");

const res = await fetch("/api/v1/match/surface", {
  method: "POST",
  body: form,
});
const task = await res.json();

// ── Ответ task: ──
// {
//   uid: "c9a1e2b3-4d5f-6789-abcd-ef0123456789",
//   status: "queued",
//   tool: "surface",
//   params: {relative_sampling_step:0.025, use_icp:true, ...},
//   input_files: ["uploads/c9a1e.../model.ply","uploads/c9a1e.../scene.ply"],
//   result_files: [], result_data: {}
// }

// ── Запрос: получить результат ──
const status = await fetch(`/api/v1/tasks/${task.uid}`);
const result = await status.json();

// ── Ответ result (когда done): ──
// {
//   uid: "c9a1e2b3-...",
//   status: "done",
//   result_files: ["results/c9a1e.../pose.json"],
//   result_data: {
//     pose: [[0.99,-0.01,0.03,12.5],[0.01,0.99,-0.02,-3.1],[-0.03,0.02,0.99,0.8],[0,0,0,1]],
//     num_model_points: 4521, num_scene_points: 18230, icp_residual: 0.0023
//   }
// }
import requests

# ── Запрос: отправка PLY-файлов ──
files = {
    "model_file": open("model.ply", "rb"),
    "scene_file": open("scene.ply", "rb"),
}
data = {
    "relative_sampling_step": "0.025",
    "num_angles": "60",
    "use_icp": "true",
    "icp_iterations": "200",
}
resp = requests.post(
    "http://localhost:8888/api/v1/match/surface",
    files=files, data=data,
)
task = resp.json()

# ── Ответ task: ──
# {
#   "uid": "c9a1e2b3-4d5f-6789-abcd-ef0123456789",
#   "status": "queued",
#   "tool": "surface",
#   "params": {"relative_sampling_step":0.025,"num_angles":60,"use_icp":true,"icp_iterations":200,...},
#   "input_files": ["uploads/c9a1e.../model.ply","uploads/c9a1e.../scene.ply"],
#   "result_files": [], "result_data": {}
# }

# ── Запрос: получить результат ──
result = requests.get(
    f"http://localhost:8888/api/v1/tasks/{task['uid']}"
).json()

# ── Ответ result (когда done): ──
# {
#   "uid": "c9a1e2b3-...",
#   "status": "done",
#   "result_files": ["results/c9a1e.../pose.json"],
#   "result_data": {
#     "pose": [[0.99,-0.01,0.03,12.5],[0.01,0.99,-0.02,-3.1],[-0.03,0.02,0.99,0.8],[0,0,0,1]],
#     "num_model_points": 4521, "num_scene_points": 18230, "icp_residual": 0.0023
#   }
# }

Документация OpenCV →