API リファレンス (API Reference)
Copyright (c) 2026 Masanori Sakai
Licensed under the MIT License
概要
流星検出システムが提供するHTTP APIの完全なリファレンスです。
目次
バージョン履歴
v3.2.5 - メンテナンスアップデート
- 変更: ダッシュボードのバージョンを 3.2.5 へ更新
- 変更: 各種ドキュメント(ARCHITECTURE.md, DETECTOR_COMPONENTS.md)を最新のコード実態に合わせて改版
v3.2.1 - 手動録画の一覧統合とサムネイル対応
- 修正: 手動録画で
pcm_alaw音声を含む RTSP を MP4 へ保存する際に失敗していた問題を修正し、映像のみ保存するよう改善 - 修正: 手動録画ファイルの探索先を各カメラ出力ディレクトリ配下
manual_recordings/...に合わせ、検出一覧カレンダーへ反映されるよう修正 - 変更: 手動録画完了後にサムネイル JPEG を自動生成し、検出一覧でプレビュー可能化
- 追加: ダッシュボードに
DELETE /manual_recording/{path}を追加し、手動録画 MP4 とサムネイルを削除可能化
v3.2.0 - カメラ別手動録画予約の追加
- 追加: ダッシュボードに
GET /camera_recording_status/{index}、POST /camera_recording_schedule/{index}、POST /camera_recording_stop/{index}を追加 - 追加: カメラ側 API に
GET /recording/status、POST /recording/schedule、POST /recording/stopを追加 - 変更:
/camera_stats/{index}と/statsのレスポンスへrecordingオブジェクトを追加し、予約状態・録画状態・保存先を取得可能化
v3.1.1 - WebRTC candidate 自動検出と生成安定化
- 変更:
generate_compose.pyがgo2rtcの candidate host を既定でローカル IP から自動検出するよう改善 - 修正: OpenCV 未導入時でも
docker-compose.yml/go2rtc.yamlの生成を継続し、マスク生成のみスキップするよう改善 - 変更: ダッシュボードおよび Docker / WebRTC 構成の関連ドキュメントを現行仕様へ改版
v3.1.0 - WebRTC ライブ表示の正式対応
- 追加: Docker 構成で
go2rtcを利用する WebRTC ライブ表示設定を正式サポート - 変更: ダッシュボードのライブ表示設定に
CAMERA*_STREAM_KIND=webrtc/CAMERA*_STREAM_URLを追加 - 変更: ダッシュボードのヘルスチェック例の
version表示を3.1.1に更新
v3.0.0 - 旧互換 MP4 正規化設定の削除
- 削除:
GET /statsのsettingsからfb_normalize/fb_delete_movを削除 - 削除: カメラ設定 API の起動時依存設定から
fb_normalize/fb_delete_movを削除 - 変更: ダッシュボードのヘルスチェック例の
version表示を3.0.0に更新
v1.24.1 - ダッシュボードの Flask アプリ運用整備
- 新規エンドポイント:
GET /health - ダッシュボードのヘルスチェックを返却
- レスポンス例:
{ "status": "ok", "version": "3.2.0", "camera_count": 3 } - 変更:
dashboard.pyを Flask のアプリファクトリ構成へ整理し、WSGI 配備やコンテナ運用時でも監視スレッドが初回リクエストで起動するよう改善
v1.24.0 - 検出 ID 管理と運用制御の拡張
- 機能追加:
/camera_settings/currentおよび/camera_settings/apply_allでdetection_enabledを扱えるようにし、全カメラの検出停止・再開を一括制御可能化 - 変更: 検出レコードの
idを、timestampに加えてstart_time/end_time/start_point/end_pointを含む現行ルールへ統一 - 運用改善: 検出データの移行・孤立ファイル救済・ディレクトリ統合を補助するメンテナンススクリプトを追加
v1.23.1 - 検出時間帯判定と状態表示の修正
- 修正: 検出時間帯の計算を「当日日没から翌日の日出まで」に是正
- 機能追加:
GET /statsレスポンスに検出状態詳細フィールドを追加 detection_status:DETECTING/OUT_OF_WINDOW/WAITING_FRAME/STREAM_LOSTdetection_window_enabled: 検出時間帯制限の有効/無効detection_window_active: 現在が検出時間帯内かdetection_window_start: 現在参照中の検出開始時刻detection_window_end: 現在参照中の検出終了時刻
v1.18.0 - 一括削除機能
- 新規エンドポイント:
POST /bulk_delete_non_meteor/{camera_name} - カメラごとに非流星検出(
label="non-meteor")を一括削除 - 削除件数とファイルリストを返却
v1.17.0 - カメラ監視機能
- 機能追加:
/camera_stats/{index}レスポンスに監視関連フィールドを追加 monitor_enabled: 監視機能の有効/無効monitor_checked_at: 最終監視確認時刻monitor_error: 監視エラーメッセージmonitor_stop_reason: 監視停止理由monitor_last_restart_at: 最終再起動時刻monitor_restart_count: 再起動回数monitor_restart_triggered: 再起動トリガー発動中か- 環境変数: カメラ監視設定(
CAMERA_MONITOR_*,CAMERA_RESTART_*)
v1.16.0 - 画面端ノイズ除外
- パラメータ追加:
exclude_edge_ratio /statsレスポンスのsettings.exclude_edge_ratio/apply_settingsリクエストボディ- 画面四辺の指定割合をノイズ除外エリアとして設定
v1.14.0 - 録画マージン設定
- パラメータ追加: 録画前後マージン
/statsレスポンスのsettings.clip_margin_before,settings.clip_margin_after/apply_settingsリクエストボディ- 検出前後に録画する追加秒数を設定可能
v1.13.0 - 全カメラ設定UI
- 新規エンドポイント:
GET /settings- 全カメラ設定ページ - 新規エンドポイント:
GET /camera_settings/current- 各カメラの現在設定取得 - 新規エンドポイント:
POST /camera_settings/apply_all- 全カメラへ設定一括適用
v1.10.0 - 検出ラベル機能
- 新規エンドポイント:
POST /detection_label - 検出に任意ラベル(
meteor,non-meteorなど)を設定 - 機能追加:
/detectionsレスポンスにlabelフィールド追加 - 機能追加:
/detections_mtimeがラベルファイル(detection_labels.json)も監視対象に
dashboard.py API
ダッシュボードが提供するエンドポイント(デフォルトポート: 8080)
エンドポイント一覧
| エンドポイント | メソッド | 説明 |
|---|---|---|
/health |
GET | ダッシュボードヘルスチェック |
/ |
GET | ダッシュボードHTML |
/cameras |
GET | カメラライブ画面HTML |
/settings |
GET | 全カメラ設定ページ |
/detection_window |
GET | 検出時間帯取得 |
/detections |
GET | 検出一覧取得 |
/detections_mtime |
GET | 検出ログ更新時刻取得 |
/camera_embed/{index} |
GET | WebRTC 埋め込みページ |
/go2rtc_asset/{name} |
GET | go2rtc フロント資産プロキシ |
/camera_settings/current |
GET | カメラ設定の現在値取得 |
/camera_settings/apply_all |
POST | 設定を全カメラへ一括適用 |
/camera_snapshot/{index} |
GET | カメラスナップショット取得(?download=1 でDL) |
/camera_recording_status/{index} |
GET | カメラ手動録画状態取得 |
/camera_recording_schedule/{index} |
POST | カメラ手動録画の予約/即時開始 |
/camera_recording_stop/{index} |
POST | カメラ手動録画の停止 |
/camera_restart/{index} |
POST | カメラ再起動要求 |
/camera_stats/{index} |
GET | カメラ統計情報取得 |
/image/{camera}/{filename} |
GET | 画像ファイル取得 |
/detection/{camera}/{id} |
DELETE | 検出結果削除 |
/dashboard_stats |
GET | ダッシュボードCPU統計取得 |
/manual_recording/{path} |
DELETE | 手動録画ファイル削除 |
/bulk_delete_non_meteor/{camera_name} |
POST | カメラの非流星検出を一括削除 |
/detection_label |
POST | 検出にラベルを設定 |
/changelog |
GET | CHANGELOG表示 |
GET /health
説明: ダッシュボードのプロセス状態と構成情報を返す
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"status": "ok",
"version": "3.2.1",
"camera_count": 3
}
使用例:
curl http://localhost:8080/health
GET /
説明: ダッシュボードのHTMLページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
curl http://localhost:8080/
GET /cameras
説明: カメラライブ表示用のHTMLページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
補足:
- CAMERA*_STREAM_KIND=mjpeg の場合は各カメラの /stream を直接参照
- CAMERA*_STREAM_KIND=webrtc の場合は /camera_embed/{index} を iframe で埋め込み
GET /settings
説明: 全カメラ設定UIページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
curl http://localhost:8080/settings
GET /camera_embed/{index}
説明: WebRTC ライブ表示用の埋め込みHTMLを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
補足:
- CAMERA*_STREAM_KIND=webrtc のカメラのみ有効
- 埋め込みページ内で /go2rtc_asset/video-stream.js を読み込み、go2rtc の /api/ws?src=... へ接続
- 表示モードは webrtc,mse,hls,mjpeg の優先順で自動選択
GET /go2rtc_asset/{name}
説明: go2rtc の video-stream.js / video-rtc.js をダッシュボード経由で配信
レスポンス:
- Content-Type: application/javascript; charset=utf-8
- Status: 200 OK
補足:
- WebRTC 埋め込みページからのみ利用
- Docker 内では go2rtc コンテナを名前解決し、ホスト側公開アドレスと分離して取得
GET /detection_window
説明: 天文薄暮期間(検出時間帯)を取得
クエリパラメータ:
| パラメータ | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
lat |
float | No | 緯度 | 35.6762 |
lon |
float | No | 経度 | 139.6503 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"start": "2026-02-01 16:45:23",
"end": "2026-02-02 06:12:45",
"enabled": true,
"latitude": 35.6762,
"longitude": 139.6503
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
start |
string | 検出開始時刻(YYYY-MM-DD HH:MM:SS) |
end |
string | 検出終了時刻(YYYY-MM-DD HH:MM:SS) |
enabled |
boolean | 時間帯制限の有効/無効 |
latitude |
float | 使用された緯度 |
longitude |
float | 使用された経度 |
エラーレスポンス:
{
"start": "",
"end": "",
"enabled": false,
"error": "meteor_detector module not available"
}
使用例:
# デフォルト座標で取得
curl "http://localhost:8080/detection_window" | jq
# 座標を指定
curl "http://localhost:8080/detection_window?lat=35.6762&lon=139.6503" | jq
# JavaScriptから取得
fetch('/detection_window?lat=35.6762&lon=139.6503')
.then(r => r.json())
.then(data => console.log(data));
GET /detections
説明: 全カメラの検出結果一覧を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"total": 15,
"recent": [
{
"time": "2026-02-02 06:55:33",
"camera": "camera1_10_0_1_25",
"confidence": "87%",
"image": "camera1_10_0_1_25/meteor_20260202_065533_composite.jpg",
"mp4": "camera1_10_0_1_25/meteor_20260202_065533.mp4",
"composite_original": "camera1_10_0_1_25/meteor_20260202_065533_composite_original.jpg"
},
{
"time": "2026-02-02 05:32:18",
"camera": "camera2_10_0_1_3",
"confidence": "92%",
"image": "camera2_10_0_1_3/meteor_20260202_053218_composite.jpg",
"mp4": "camera2_10_0_1_3/meteor_20260202_053218.mp4",
"composite_original": "camera2_10_0_1_3/meteor_20260202_053218_composite_original.jpg"
}
]
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
total |
integer | 総検出数 |
recent |
array | 検出リスト(時刻降順) |
recent[].time |
string | 検出時刻 |
recent[].camera |
string | カメラ名 |
recent[].confidence |
string | 信頼度(パーセント表示) |
recent[].image |
string | 画像パス |
recent[].mp4 |
string | 動画パス(通常は .mp4) |
recent[].composite_original |
string | 元画像の比較明合成パス |
recent[].label |
string | 検出ラベル(v1.10.0以降、未設定時は空文字) |
recent[].source_type |
string | "manual_recording" の場合は手動録画(v3.2.1以降、手動録画エントリのみ付与) |
使用例:
# curlで取得
curl http://localhost:8080/detections | jq
# 総検出数のみ取得
curl -s http://localhost:8080/detections | jq '.total'
# 最新の検出のみ取得
curl -s http://localhost:8080/detections | jq '.recent[0]'
# JavaScriptから取得
fetch('/detections')
.then(r => r.json())
.then(data => {
console.log('Total:', data.total);
data.recent.forEach(d => console.log(d.time, d.camera));
});
GET /detections_mtime
説明: 各カメラの detections.jsonl および detection_labels.json の更新時刻(UNIXエポック秒)を取得(v1.10.0以降、ラベルファイルも監視対象)
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"mtime": 1751461442.123
}
使用例:
curl http://localhost:8080/detections_mtime | jq
fetch('/detections_mtime')
.then(r => r.json())
.then(data => console.log('mtime:', data.mtime));
GET /camera_stats/{index}
説明: 指定カメラの統計情報を取得(v1.17.0で監視機能を追加、v3.2.0で recording フィールドを追加)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 0 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"detections": 5,
"elapsed": 3600.5,
"camera": "camera1_10_0_1_25",
"stream_alive": true,
"time_since_last_frame": 0.03,
"is_detecting": true,
"monitor_enabled": true,
"monitor_checked_at": "2026-02-24 12:34:56",
"monitor_error": null,
"monitor_stop_reason": null,
"monitor_last_restart_at": "2026-02-24 10:00:00",
"monitor_restart_count": 2,
"monitor_restart_triggered": false
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
detections |
integer | 検出数 |
elapsed |
float | 稼働時間(秒) |
camera |
string | カメラ名 |
stream_alive |
boolean | ストリーム生存確認 |
time_since_last_frame |
float | 最終フレームからの経過時間(秒) |
is_detecting |
boolean | 現在検出処理中か |
monitor_enabled |
boolean | カメラ監視機能の有効/無効 |
monitor_checked_at |
string/null | 最終監視確認時刻 |
monitor_error |
string/null | 監視エラーメッセージ |
monitor_stop_reason |
string/null | 監視停止理由 |
monitor_last_restart_at |
string/null | 最終再起動時刻 |
monitor_restart_count |
integer | 再起動回数 |
monitor_restart_triggered |
boolean | 再起動トリガー発動中か |
recording |
object/null | 手動録画状態(v3.2.0以降、カメラが対応している場合に含まれる) |
使用例:
# curlで取得
curl http://localhost:8080/camera_stats/0 | jq
# JavaScriptから定期取得
setInterval(() => {
fetch('/camera_stats/0')
.then(r => r.json())
.then(data => {
console.log('Stream alive:', data.stream_alive);
console.log('Monitor enabled:', data.monitor_enabled);
console.log('Restart count:', data.monitor_restart_count);
});
}, 5000);
GET /image/{camera}/{filename}
説明: 検出画像ファイルを取得
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
camera |
string | カメラディレクトリ名 | camera1_10_0_1_25 |
filename |
string | ファイル名 | meteor_20260202_065533_composite.jpg |
レスポンス:
- Content-Type: image/jpeg または image/png
- Status: 200 OK
- Body: バイナリ画像データ
エラーレスポンス: - Status: 404 Not Found
使用例:
# 画像をダウンロード
curl -O "http://localhost:8080/image/camera1_10_0_1_25/meteor_20260202_065533_composite.jpg"
# HTMLから表示
<img src="/image/camera1_10_0_1_25/meteor_20260202_065533_composite.jpg" alt="Meteor">
# ダウンロードリンク
<a href="/image/camera1_10_0_1_25/meteor_20260202_065533_composite.jpg" download>
Download Image
</a>
GET /camera_snapshot/{index}
説明: 指定カメラの現在フレームをJPEGで取得
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 0 |
クエリパラメータ:
| パラメータ | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
download |
string | No | 1/true/yes で Content-Disposition: attachment を付与 |
1 |
レスポンス:
- Content-Type: image/jpeg
- Status: 200 OK
使用例:
# 画像を直接表示
curl "http://localhost:8080/camera_snapshot/0" --output snapshot.jpg
# ダウンロード用途(attachmentヘッダ付き)
curl -OJ "http://localhost:8080/camera_snapshot/0?download=1"
GET /camera_recording_status/{index}
説明: 指定カメラの手動録画状態を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"success": true,
"recording": {
"supported": true,
"state": "idle",
"camera": "camera1_10_0_1_25",
"job_id": "",
"start_at": "",
"scheduled_at": "",
"started_at": "",
"ended_at": "",
"duration_sec": 0,
"remaining_sec": 0,
"output_path": "",
"error": ""
}
}
DELETE /manual_recording/{path}
説明: 手動録画の MP4 と対応するサムネイル JPEG を削除
補足:
- 対象は manual_recordings 配下の .mp4 のみ
- 同名 .jpg があれば合わせて削除
使用例:
curl -X DELETE "http://localhost:8080/manual_recording/camera1/manual_recordings/camera1/manual_camera1_20260319_213000_90s.mp4" | jq
POST /camera_recording_schedule/{index}
説明: 指定カメラの手動録画を予約または即時開始
リクエストボディ:
{
"start_at": "2026-03-19T21:30:00",
"duration_sec": 90
}
補足:
- start_at を空文字または省略すると即時開始
- duration_sec は 1 以上 86400 以下
使用例:
curl -X POST "http://localhost:8080/camera_recording_schedule/0" \
-H "Content-Type: application/json" \
-d '{"start_at":"2026-03-19T21:30:00","duration_sec":90}' | jq
POST /camera_recording_stop/{index}
説明: 予約中または録画中の手動録画を停止
レスポンス:
- Content-Type: application/json
- Status: 200 OK
使用例:
curl -X POST "http://localhost:8080/camera_recording_stop/0" | jq
POST /camera_restart/{index}
説明: 指定カメラに再起動を要求(非同期)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
index |
integer | カメラインデックス(0始まり) | 1 |
レスポンス:
- Content-Type: application/json
- Status: 202 Accepted
レスポンスボディ:
{
"success": true,
"message": "restart requested"
}
使用例:
curl -X POST "http://localhost:8080/camera_restart/1" | jq
GET /camera_settings/current
説明: 各カメラの /stats.settings を取得し、設定ページ表示用に返す
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ(例):
{
"success": true,
"settings": {
"diff_threshold": 20,
"nuisance_overlap_threshold": 0.6
},
"results": [
{
"camera": "camera1",
"success": true,
"settings": {
"diff_threshold": 20
}
}
],
"ok_count": 1,
"total": 1
}
使用例:
curl -s http://localhost:8080/camera_settings/current | jq
POST /camera_settings/apply_all
説明: 指定した設定値を全カメラへ一括適用(各カメラの POST /apply_settings を呼び出し)
リクエストボディ(例):
{
"diff_threshold": 20,
"min_brightness": 180,
"nuisance_overlap_threshold": 0.60,
"nuisance_path_overlap_threshold": 0.70,
"min_track_points": 4,
"max_stationary_ratio": 0.40
}
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ(例):
{
"success": true,
"applied_count": 3,
"total": 3,
"results": [
{
"camera": "camera1",
"success": true,
"response": {
"success": true,
"restart_required": true,
"restart_requested": true,
"restart_triggers": ["sensitivity", "scale"]
}
},
{ "camera": "camera2", "success": true, "response": { "success": true } },
{ "camera": "camera3", "success": false, "error": "timeout" }
]
}
使用例:
curl -X POST "http://localhost:8080/camera_settings/apply_all" \
-H "Content-Type: application/json" \
-d '{"diff_threshold":20,"nuisance_overlap_threshold":0.60}' | jq
DELETE /detection/{camera}/{id}
説明: 検出結果を削除(動画、画像、JSONLエントリ)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
camera |
string | カメラディレクトリ名 | camera1_10_0_1_25 |
id |
string | 検出ID(/detections レスポンスの id フィールド) |
det_a1b2c3d4e5f6789012 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
成功レスポンス:
{
"success": true,
"id": "det_a1b2c3d4e5f6789012",
"deleted_files": [
"meteor_20260202_065533.mp4",
"meteor_20260202_065533_composite.jpg",
"meteor_20260202_065533_composite_original.jpg"
],
"message": "3個のファイルを削除しました"
}
エラーレスポンス:
{
"success": false,
"error": "detection id not found: camera1_10_0_1_25 det_a1b2c3d4e5f6789012"
}
使用例:
# curlで削除(idは/detectionsで取得)
curl -X DELETE "http://localhost:8080/detection/camera1_10_0_1_25/det_a1b2c3d4e5f6789012"
# JavaScriptから削除
fetch(`/detection/camera1_10_0_1_25/${detectionId}`, {
method: 'DELETE'
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert(data.message);
} else {
alert('削除失敗: ' + data.error);
}
});
GET /dashboard_stats
説明: ダッシュボードプロセスの CPU 使用率を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"cpu_percent": 12.3
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
cpu_percent |
float | CPU使用率(%、0.0〜100.0) |
使用例:
curl http://localhost:8080/dashboard_stats | jq
POST /bulk_delete_non_meteor/{camera_name}
説明: 指定カメラの「それ以外」検出(ラベルが post_detected の検出)を一括削除(v1.18.0)
URLパラメータ:
| パラメータ | 型 | 説明 | 例 |
|---|---|---|---|
camera_name |
string | カメラディレクトリ名 | camera1_10_0_1_25 |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
成功レスポンス:
{
"success": true,
"deleted_count": 5,
"deleted_detections": [
{
"time": "2026-02-02 06:55:33",
"deleted_files": [
"meteor_20260202_065533.mp4",
"meteor_20260202_065533_composite.jpg",
"meteor_20260202_065533_composite_original.jpg"
]
},
{
"time": "2026-02-02 05:32:18",
"deleted_files": [
"meteor_20260202_053218.mp4",
"meteor_20260202_053218_composite.jpg",
"meteor_20260202_053218_composite_original.jpg"
]
}
],
"message": "5件の非流星検出を削除しました(合計15ファイル)"
}
エラーレスポンス:
{
"success": false,
"error": "camera directory not found: camera1_10_0_1_25"
}
使用例:
# curlで一括削除
curl -X POST "http://localhost:8080/bulk_delete_non_meteor/camera1_10_0_1_25" | jq
# JavaScriptから一括削除
fetch('/bulk_delete_non_meteor/camera1_10_0_1_25', {
method: 'POST'
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert(`${data.deleted_count}件の「それ以外」検出を削除しました`);
} else {
alert('削除失敗: ' + data.error);
}
});
POST /detection_label
説明: 検出にラベルを設定(v1.10.0)
リクエストボディ:
{
"camera": "camera1_10_0_1_25",
"id": "det_a1b2c3d4e5f6789012",
"label": "detected"
}
パラメータ説明:
| パラメータ | 型 | 必須 | 説明 | 例 |
|---|---|---|---|---|
camera |
string | Yes | カメラディレクトリ名 | camera1_10_0_1_25 |
id |
string | Yes | 検出ID(/detections レスポンスの id フィールド) |
det_a1b2c3d4e5f6789012 |
label |
string | Yes | ラベル(detected または post_detected) |
detected |
レスポンス:
- Content-Type: application/json
- Status: 200 OK
成功レスポンス:
{
"success": true,
"message": "Label updated"
}
エラーレスポンス:
{
"success": false,
"error": "Detection not found"
}
使用例:
# curlでラベル設定
curl -X POST "http://localhost:8080/detection_label" \
-H "Content-Type: application/json" \
-d '{
"camera": "camera1_10_0_1_25",
"id": "det_a1b2c3d4e5f6789012",
"label": "detected"
}' | jq
# JavaScriptからラベル設定
fetch('/detection_label', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
camera: 'camera1_10_0_1_25',
id: detectionId,
label: 'detected'
})
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert('ラベル設定完了');
} else {
alert('エラー: ' + data.error);
}
});
ラベルの活用:
- detected: 流星として確認済み
- post_detected: 自動検出後に「それ以外」と判定されたもの(bulk_delete_non_meteor の削除対象)
GET /changelog
説明: CHANGELOG.mdの内容を取得
レスポンス:
- Content-Type: text/plain; charset=utf-8
- Status: 200 OK
- Body: CHANGELOG.mdの内容(テキスト)
使用例:
curl http://localhost:8080/changelog
meteor_detector_rtsp_web.py API
各カメラコンテナが提供するエンドポイント(デフォルトポート: 8080)
エンドポイント一覧
| エンドポイント | メソッド | 説明 |
|---|---|---|
/ |
GET | プレビューHTML |
/stream |
GET | MJPEGストリーム |
/snapshot |
GET | 現在フレームJPEG |
/stats |
GET | 統計情報 |
/recording/status |
GET | 手動録画状態取得 |
/recording/schedule |
POST | 手動録画の予約/即時開始 |
/recording/stop |
POST | 手動録画の停止 |
/update_mask |
POST | 現在フレームからマスク再生成 |
/apply_settings |
POST | 設定値をランタイム反映 |
/restart |
POST | プロセス再起動要求 |
GET /
説明: カメラプレビューのHTMLページを返す
レスポンス:
- Content-Type: text/html; charset=utf-8
- Status: 200 OK
使用例:
# camera1のプレビュー
curl http://localhost:8081/
# ブラウザで開く
open http://localhost:8081/
GET /stream
説明: MJPEGストリーム(Motion JPEG)を返す
補足:
- 各カメラコンテナ (meteor_detector_rtsp_web.py) が提供するライブ表示用エンドポイント
- ダッシュボードが mjpeg 構成のときはブラウザがこのURLを直接参照
- ダッシュボードが webrtc 構成のときは、ライブ表示の主経路は go2rtc 経由になり、この /stream は主表示には使われません
レスポンス:
- Content-Type: multipart/x-mixed-replace; boundary=frame
- Status: 200 OK
- Body: 連続的なJPEGフレーム(約30fps)
ストリームフォーマット:
--frame\r\n
Content-Type: image/jpeg\r\n\r\n
<JPEG binary data>
\r\n
--frame\r\n
Content-Type: image/jpeg\r\n\r\n
<JPEG binary data>
\r\n
...
使用例:
# HTMLで表示
<img src="http://localhost:8081/stream" alt="Live Stream">
# VLCで再生
vlc http://localhost:8081/stream
# ffmpegで録画
ffmpeg -i http://localhost:8081/stream -t 60 output.mp4
特徴: - リアルタイムプレビュー - 検出中の物体が緑丸で表示 - 追跡中の軌跡が黄線で表示 - 流星検出時に赤で表示 - フレームレート: 約30fps - 画質: JPEG品質70%
GET /stats
説明: カメラの統計情報を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"detections": 5,
"elapsed": 3600.5,
"camera": "camera1_10_0_1_25",
"settings": {
"sensitivity": "medium",
"scale": 0.5,
"buffer": 15.0,
"extract_clips": true,
"source_fps": 20.0,
"exclude_bottom": 0.0625,
"exclude_bottom_ratio": 0.0625,
"mask_image": "/app/mask_image.png",
"mask_from_day": "",
"mask_dilate": 5,
"nuisance_mask_image": "",
"nuisance_from_night": "",
"nuisance_dilate": 3,
"nuisance_overlap_threshold": 0.6
},
"runtime_fps": 19.83,
"stream_alive": true,
"time_since_last_frame": 0.03,
"is_detecting": true,
"detection_status": "DETECTING",
"detection_window_enabled": true,
"detection_window_active": true,
"detection_window_start": "2026-03-09 17:46:53",
"detection_window_end": "2026-03-10 06:03:38",
"recording": {
"supported": true,
"state": "scheduled",
"start_at": "2026-03-19T21:30:00+09:00",
"duration_sec": 90,
"remaining_sec": 42,
"output_path": "/output/manual_recordings/camera1_10_0_1_25/manual_camera1_10_0_1_25_20260319_213000_90s.mp4",
"error": ""
}
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
detections |
integer | 検出数 |
elapsed |
float | 稼働時間(秒) |
camera |
string | カメラ名 |
settings |
object | 設定情報 |
settings.sensitivity |
string | 感度プリセット |
settings.scale |
float | 処理スケール |
settings.buffer |
float | バッファ秒数 |
settings.extract_clips |
boolean | 動画保存の有効/無効 |
settings.source_fps |
float | 接続時に取得した入力ストリームFPS |
settings.exclude_bottom |
float | 画面下部除外率 |
settings.exclude_bottom_ratio |
float | 画面下部除外率(内部キー) |
settings.exclude_edge_ratio |
float | 画面端ノイズ除外率(v1.16.0、デフォルト0.0) |
settings.clip_margin_before |
float | 録画前マージン秒数(v1.14.0、デフォルト0.5) |
settings.clip_margin_after |
float | 録画後マージン秒数(v1.14.0、デフォルト0.5) |
settings.mask_image |
string | マスク画像(優先) |
settings.mask_from_day |
string | 昼間画像から生成するマスク |
settings.mask_dilate |
integer | マスク拡張ピクセル数 |
settings.nuisance_overlap_threshold |
float | ノイズ帯重なり閾値 |
settings.nuisance_path_overlap_threshold |
float | ノイズ帯経路重なり閾値 |
settings.min_track_points |
integer | 最小追跡点数 |
settings.max_stationary_ratio |
float | 静止率上限 |
settings.small_area_threshold |
integer | 小領域判定閾値 |
settings.nuisance_mask_image |
string | ノイズ帯マスク画像 |
settings.nuisance_from_night |
string | 夜間画像からのノイズ帯生成元 |
settings.nuisance_dilate |
integer | ノイズ帯マスク拡張ピクセル数 |
runtime_fps |
float | 直近フレームから算出した実効FPS |
stream_alive |
boolean | ストリーム生存確認 |
time_since_last_frame |
float | 最終フレームからの経過時間(秒) |
is_detecting |
boolean | 現在検出処理中か |
detection_status |
string | 検出状態詳細。DETECTING / OUT_OF_WINDOW / WAITING_FRAME / STREAM_LOST |
detection_window_enabled |
boolean | 検出時間帯制限が有効か |
detection_window_active |
boolean | 現在が検出時間帯内か |
detection_window_start |
string | 現在参照中の検出開始時刻 |
detection_window_end |
string | 現在参照中の検出終了時刻 |
recording |
object | 手動録画状態 |
recording.supported |
boolean | 手動録画機能が利用可能か |
recording.state |
string | idle / scheduled / recording / completed / failed / stopped |
recording.start_at |
string | 予約開始時刻 |
recording.duration_sec |
integer | 録画予定秒数 |
recording.remaining_sec |
integer | 残り秒数 |
recording.output_path |
string | 保存先MP4パス |
recording.error |
string | 失敗・停止理由 |
使用例:
# curlで取得
curl http://localhost:8081/stats | jq
# 検出数のみ取得
curl -s http://localhost:8081/stats | jq '.detections'
# ストリーム状態を確認
curl -s http://localhost:8081/stats | jq '.stream_alive'
# 全カメラの統計を一括取得
for port in 8081 8082 8083; do
echo "Port $port:"
curl -s "http://localhost:$port/stats" | jq '{camera, detections, stream_alive}'
done
# JavaScriptから定期取得
setInterval(() => {
fetch('http://localhost:8081/stats')
.then(r => r.json())
.then(data => {
console.log('Detections:', data.detections);
console.log('Stream alive:', data.stream_alive);
console.log('Is detecting:', data.is_detecting);
});
}, 2000); // 2秒ごと
GET /recording/status
説明: カメラコンテナ内の手動録画状態を取得
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"success": true,
"recording": {
"supported": true,
"state": "recording",
"camera": "camera1_10_0_1_25",
"job_id": "rec_1742387400000",
"start_at": "2026-03-19T21:30:00+09:00",
"scheduled_at": "2026-03-19T21:29:10+09:00",
"started_at": "2026-03-19T21:30:00+09:00",
"ended_at": "",
"duration_sec": 90,
"remaining_sec": 37,
"output_path": "/output/manual_recordings/camera1_10_0_1_25/manual_camera1_10_0_1_25_20260319_213000_90s.mp4",
"error": ""
}
}
POST /recording/schedule
説明: RTSP入力を ffmpeg で MP4 へ保存する手動録画ジョブを予約または即時開始
リクエストボディ:
{
"start_at": "2026-03-19T21:30:00",
"duration_sec": 90
}
補足:
- start_at 省略時は即時開始
- 既に scheduled または recording のジョブがある場合は失敗
- 保存先は manual_recordings/<camera>/ 配下
POST /recording/stop
説明: 予約中または録画中の手動録画ジョブを停止
レスポンス:
- Content-Type: application/json
- Status: 200 OK
GET /snapshot
説明: 現在フレームをJPEGで取得
レスポンス:
- Content-Type: image/jpeg
- Status: 200 OK
使用例:
curl "http://localhost:8081/snapshot" --output camera1_snapshot.jpg
POST /update_mask
説明: 現在フレームから除外マスクを再生成して即時反映(固定カメラ向け)
レスポンス:
- Content-Type: application/json
- Status: 200 OK
レスポンスボディ:
{
"success": true,
"message": "mask updated",
"saved": "/output/masks/camera1_10_0_1_25_mask.png"
}
フィールド説明:
| フィールド | 型 | 説明 |
|---|---|---|
success |
boolean | 更新成功/失敗 |
message |
string | 結果メッセージ |
saved |
string | 保存先(永続化ファイル) |
使用例:
# マスク更新
curl -X POST http://localhost:8081/update_mask | jq
POST /apply_settings
説明: 設定をランタイム反映。起動時依存の項目は設定保存後に自動再起動を要求
リクエストボディ(例):
{
"sensitivity": "medium",
"scale": 0.5,
"buffer": 15,
"extract_clips": true,
"diff_threshold": 20,
"min_brightness": 180,
"min_linearity": 0.7,
"nuisance_overlap_threshold": 0.6,
"nuisance_path_overlap_threshold": 0.7,
"min_track_points": 4,
"max_stationary_ratio": 0.4,
"small_area_threshold": 40,
"mask_dilate": 20,
"nuisance_dilate": 3,
"exclude_edge_ratio": 0.0,
"clip_margin_before": 0.5,
"clip_margin_after": 0.5
}
レスポンスボディ(例):
{
"success": true,
"applied": {
"sensitivity": "medium",
"scale": 0.5,
"diff_threshold": 20
},
"errors": [],
"restart_required": true,
"restart_requested": true,
"restart_triggers": ["sensitivity", "scale"]
}
反映ルール:
- 再起動不要: diff_threshold など検出ロジック/誤検出抑制の閾値群
- 自動再起動で反映: sensitivity / scale / buffer / extract_clips
- 設定は output/runtime_settings/<camera>.json に永続化され、再起動後も維持
使用例:
curl -X POST http://localhost:8081/apply_settings \
-H "Content-Type: application/json" \
-d '{"diff_threshold":20,"nuisance_overlap_threshold":0.60}' | jq
POST /restart
説明: カメラプロセスへ再起動を要求(Dockerの restart: unless-stopped 運用を想定)
レスポンス:
- Content-Type: application/json
- Status: 202 Accepted
レスポンスボディ:
{
"success": true,
"message": "restart requested"
}
使用例:
curl -X POST http://localhost:8081/restart | jq
共通仕様
CORS(Cross-Origin Resource Sharing)
現在の設定:
# /stats /update_mask /apply_settings /restart はCORS許可
self.send_header('Access-Control-Allow-Origin', '*')
制限事項: - 他のエンドポイントはCORS未対応 - 外部ドメインからのアクセスは制限される
カスタマイズ例:
# すべてのエンドポイントでCORS許可(セキュリティ注意)
def end_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, DELETE')
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
BaseHTTPRequestHandler.end_headers(self)
レート制限
現在の制限: なし
推奨実装 (Nginxリバースプロキシ):
# /etc/nginx/sites-available/meteor
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20;
proxy_pass http://localhost:8080/;
}
}
タイムアウト
| エンドポイント | タイムアウト | 理由 |
|---|---|---|
/stream |
なし | ストリーミング |
| その他 | 30秒 | ブラウザデフォルト |
環境変数
カメラ監視機能(v1.17.0)
カメラの自動監視と再起動を制御する環境変数です。
| 環境変数 | デフォルト | 説明 |
|---|---|---|
CAMERA_MONITOR_ENABLED |
true |
カメラ監視機能の有効/無効 |
CAMERA_MONITOR_INTERVAL |
2.0 |
監視チェック間隔(秒) |
CAMERA_MONITOR_TIMEOUT |
6.0 |
カメラ統計取得タイムアウト(秒) |
CAMERA_RESTART_TIMEOUT |
5.0 |
再起動リクエストタイムアウト(秒) |
CAMERA_RESTART_COOLDOWN_SEC |
120 |
再起動後のクールダウン時間(秒) |
CAMERA_MONITOR_FAIL_THRESHOLD |
12 |
統計取得失敗が連続でこの回数に達すると再起動を試みる |
DETECTION_MONITOR_INTERVAL |
2.0 |
検出キャッシュ更新間隔(秒) |
使用例(docker-compose.yml):
services:
dashboard:
image: meteor-dashboard:latest
environment:
- CAMERA_MONITOR_ENABLED=true
- CAMERA_MONITOR_INTERVAL=2.0
- CAMERA_MONITOR_TIMEOUT=6.0
- CAMERA_RESTART_TIMEOUT=5.0
- CAMERA_RESTART_COOLDOWN_SEC=120
- CAMERA_MONITOR_FAIL_THRESHOLD=12
ports:
- "8080:8080"
監視機能の動作:
1. 各カメラの /stats エンドポイントを定期的に確認
2. time_since_last_frame が CAMERA_MONITOR_TIMEOUT を超えた場合、フレーム停止と判定
3. CAMERA_RESTART_ENABLED=true の場合、自動的に /restart を呼び出し
4. 再起動回数が CAMERA_RESTART_MAX_COUNT を超えると監視を停止
5. 監視状態は /camera_stats/{index} で確認可能
監視を無効化する場合:
environment:
- CAMERA_MONITOR_ENABLED=false
エラーコード
HTTPステータスコード
| コード | 説明 | 発生条件 |
|---|---|---|
| 200 | OK | 成功 |
| 404 | Not Found | ファイルまたはエンドポイントが存在しない |
| 500 | Internal Server Error | サーバー内部エラー |
エラーレスポンス例
{
"success": false,
"error": "File not found"
}
使用例
Python
import requests
# 検出一覧を取得
response = requests.get('http://localhost:8080/detections')
data = response.json()
print(f"Total detections: {data['total']}")
# 統計情報を取得
stats = requests.get('http://localhost:8081/stats').json()
print(f"Camera: {stats['camera']}, Detections: {stats['detections']}")
# 検出結果を削除
delete_response = requests.delete(
'http://localhost:8080/detection/camera1_10_0_1_25/2026-02-02 06:55:33'
)
print(delete_response.json())
JavaScript(ブラウザ)
// 検出一覧を取得して表示
async function loadDetections() {
const response = await fetch('/detections');
const data = await response.json();
console.log(`Total: ${data.total}`);
data.recent.forEach(detection => {
console.log(`${detection.time} - ${detection.camera} (${detection.confidence})`);
});
}
// 統計情報を定期取得
setInterval(async () => {
const stats = await fetch('http://localhost:8081/stats').then(r => r.json());
document.getElementById('detections').textContent = stats.detections;
document.getElementById('status').textContent = stats.stream_alive ? 'Online' : 'Offline';
}, 2000);
// 検出結果を削除
async function deleteDetection(camera, timestamp) {
const response = await fetch(`/detection/${camera}/${timestamp}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert(result.message);
loadDetections(); // リストを更新
} else {
alert(`Error: ${result.error}`);
}
}
// 非流星検出を一括削除
async function bulkDeleteNonMeteor(camera) {
const response = await fetch(`/bulk_delete_non_meteor/${camera}`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
alert(`${result.deleted_count}件の非流星検出を削除しました`);
loadDetections(); // リストを更新
} else {
alert(`Error: ${result.error}`);
}
}
// 検出にラベルを設定
async function setDetectionLabel(camera, timestamp, label) {
const response = await fetch('/detection_label', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({camera, timestamp, label})
});
const result = await response.json();
if (result.success) {
alert('ラベル設定完了');
loadDetections(); // リストを更新
} else {
alert(`Error: ${result.error}`);
}
}
Node.js
const axios = require('axios');
// 全カメラの統計を取得
async function getAllStats() {
const cameras = [8081, 8082, 8083];
const promises = cameras.map(port =>
axios.get(`http://localhost:${port}/stats`)
);
const results = await Promise.all(promises);
results.forEach((res, i) => {
console.log(`Camera ${i+1}:`, res.data.detections, 'detections');
});
}
getAllStats();
Bash
#!/bin/bash
# 全カメラの統計を表示
echo "=== Meteor Detection Stats ==="
for port in 8081 8082 8083; do
stats=$(curl -s "http://localhost:$port/stats")
camera=$(echo "$stats" | jq -r '.camera')
detections=$(echo "$stats" | jq -r '.detections')
alive=$(echo "$stats" | jq -r '.stream_alive')
echo "$camera: $detections detections (stream: $alive)"
done
# 検出一覧を取得
echo ""
echo "=== Recent Detections ==="
curl -s "http://localhost:8080/detections" | \
jq -r '.recent[] | "\(.time) - \(.camera) (\(.confidence))"'
PowerShell
# 検出一覧を取得
$detections = Invoke-RestMethod -Uri "http://localhost:8080/detections"
Write-Host "Total detections: $($detections.total)"
# 統計情報を取得
$stats = Invoke-RestMethod -Uri "http://localhost:8081/stats"
Write-Host "Camera: $($stats.camera), Detections: $($stats.detections)"
# 検出結果を削除
$deleteResult = Invoke-RestMethod `
-Uri "http://localhost:8080/detection/camera1_10_0_1_25/2026-02-02%2006:55:33" `
-Method Delete
Write-Host $deleteResult.message
Webhook連携例
検出時にSlackに通知
# webhook_notifier.py
import requests
import time
import json
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
last_count = {}
def check_detections():
for port in [8081, 8082, 8083]:
stats = requests.get(f'http://localhost:{port}/stats').json()
camera = stats['camera']
count = stats['detections']
if camera not in last_count:
last_count[camera] = count
if count > last_count[camera]:
# 新しい検出があった
message = {
"text": f"🌠 流星検出!\nカメラ: {camera}\n検出数: {count}"
}
requests.post(SLACK_WEBHOOK_URL, json=message)
last_count[camera] = count
# 10秒ごとに確認
while True:
check_detections()
time.sleep(10)
検出時にメール送信
# email_notifier.py
import requests
import smtplib
from email.mime.text import MIMEText
def send_email(subject, body):
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = 'meteor@example.com'
msg['To'] = 'admin@example.com'
with smtplib.SMTP('smtp.example.com', 587) as server:
server.starttls()
server.login('user', 'password')
server.send_message(msg)
def monitor():
last_count = {}
while True:
detections = requests.get('http://localhost:8080/detections').json()
for detection in detections['recent']:
key = f"{detection['camera']}_{detection['time']}"
if key not in last_count:
send_email(
f"流星検出: {detection['camera']}",
f"時刻: {detection['time']}\n信頼度: {detection['confidence']}"
)
last_count[key] = True
time.sleep(30)
monitor()
APIクライアントライブラリ例
Python用シンプルクライアント
# meteor_client.py
import requests
from typing import List, Dict, Optional
class MeteorDetectionClient:
def __init__(self, dashboard_url: str = "http://localhost:8080"):
self.dashboard_url = dashboard_url
def get_detections(self) -> Dict:
"""検出一覧を取得"""
response = requests.get(f"{self.dashboard_url}/detections")
return response.json()
def get_detection_window(self, lat: float = None, lon: float = None) -> Dict:
"""検出時間帯を取得"""
params = {}
if lat: params['lat'] = lat
if lon: params['lon'] = lon
response = requests.get(
f"{self.dashboard_url}/detection_window",
params=params
)
return response.json()
def delete_detection(self, camera: str, timestamp: str) -> Dict:
"""検出結果を削除"""
response = requests.delete(
f"{self.dashboard_url}/detection/{camera}/{timestamp}"
)
return response.json()
def bulk_delete_non_meteor(self, camera: str) -> Dict:
"""非流星検出を一括削除"""
response = requests.post(
f"{self.dashboard_url}/bulk_delete_non_meteor/{camera}"
)
return response.json()
def set_detection_label(self, camera: str, timestamp: str, label: str) -> Dict:
"""検出にラベルを設定"""
response = requests.post(
f"{self.dashboard_url}/detection_label",
json={"camera": camera, "timestamp": timestamp, "label": label}
)
return response.json()
def get_camera_stats(self, port: int) -> Dict:
"""カメラの統計情報を取得"""
response = requests.get(f"http://localhost:{port}/stats")
return response.json()
def get_camera_stats_from_dashboard(self, index: int) -> Dict:
"""ダッシュボード経由でカメラ統計を取得(監視情報含む)"""
response = requests.get(f"{self.dashboard_url}/camera_stats/{index}")
return response.json()
# 使用例
if __name__ == "__main__":
client = MeteorDetectionClient()
# 検出一覧を取得
detections = client.get_detections()
print(f"Total: {detections['total']}")
# カメラ統計を取得
stats = client.get_camera_stats(8081)
print(f"Camera: {stats['camera']}, Detections: {stats['detections']}")
# ダッシュボード経由でカメラ統計取得(監視情報含む)
dashboard_stats = client.get_camera_stats_from_dashboard(0)
print(f"Monitor enabled: {dashboard_stats['monitor_enabled']}")
print(f"Restart count: {dashboard_stats['monitor_restart_count']}")
# 検出にラベルを設定
result = client.set_detection_label(
camera="camera1_10_0_1_25",
timestamp="2026-02-02 06:55:33",
label="meteor"
)
print(result)
# 非流星検出を一括削除
delete_result = client.bulk_delete_non_meteor("camera1_10_0_1_25")
print(f"Deleted {delete_result['deleted_count']} non-meteor detections")
関連ドキュメント
- OPERATIONS_GUIDE.md - 運用ガイド
- ARCHITECTURE.md - システムアーキテクチャ
- DETECTOR_COMPONENTS.md - 検出コンポーネント詳細