비디오 이상 탐지 Part 2: 엣지-투-클라우드 파이프라인
비디오 이상 탐지 Part 2: 엣지-투-클라우드 파이프라인 (tutorials-build-essentials-video-anomaly-edge-part-2)
| 시간: 90분 | 수준: 고급 | 출력: GitHub |
|---|
이 글은 엣지에서 클라우드까지 실시간 비디오 이상 탐지를 구축하는 3부작 시리즈의 Part 2예요.
시리즈:
- Part 1 | 아키텍처, Twelve Labs, NVIDIA VSS
- Part 2 | 엣지-투-클라우드 파이프라인 (여기)
- Part 3 | 스코어링, 거버넌스, 배포
Part 1에서 프로젝트를 설정하고, Qdrant의 kNN 이상 탐지가 분류기보다 나은 이유를 다루고, Twelve Labs를 비디오 임베딩·Q&A에 통합하고, NVIDIA VSS를 연결했어요. 이제 엣지를 구축해볼게요.
왜 Qdrant Edge인가
클라우드 계층은 완전한 Qdrant 클러스터를 실행해요. 그런데 엣지 계층은 그럴 수 없어요. NVIDIA Jetson 장치는 메모리가 제한적이고, 인터넷 연결이 보장되지 않으며, 클라이언트-서버 아키텍처의 오버헤드 없이 서브밀리초 kNN 조회가 필요해요.
Qdrant Edge가 이 문제를 해결해요. 애플리케이션 프로세스 안에서 실행되는 가볍고 임베디드된 벡터 검색 엔진이에요. 별도의 서버도, 네트워크 홉도, Docker 컨테이너도 없어요. qdrant-edge-py 패키지를 통한 Python 바인딩으로 네이티브 Rust 성능을 얻어요.
우리가 사용하는 핵심 기능은 다음과 같아요:
- EdgeShard: 벡터·페이로드 저장을 관리하고 로컬 검색을 독립적으로 수행하는 자체 포함 스토리지 유닛.
- 스냅샷 동기화: 클라우드 서버에서 HNSW 인덱싱된 샤드를 다운로드하고
EdgeShard.unpack_snapshot()으로 로컬에서 압축을 푼다. 부분 스냅샷은 엣지를 증분으로 최신 상태로 유지한다. - 오프라인 운영: 엣지 샤드는 네트워크 연결 없이 동작한다. 로컬에서 큐에 쌓인 데이터는 연결이 돌아오면 동기화된다.
패키지를 설치해요:
pip install qdrant-edge-py
투-샤드 엣지 아키텍처
이것이 시스템의 기술적 핵심이에요. 엣지 장치는 새 클립을 지속적으로 수집하면서 최소 지연 시간으로 kNN 쿼리를 서빙해야 해요.
엣지에서 단일 샤드만 실행하면 안 되는 이유는 뭘까요? 두 가지 이유가 있어요. (1) HNSW 인덱스를 구축하는 것은 비싸고 읽기를 차단하고, (2) 수십만 개의 벡터를 포함할 수 있는 전체 클라우드 베이스라인을 모든 엣지 장치에 옮길 수 없기 때문이에요.
해결책: 뮤터블 + 이뮤터블 샤드
엣지 컬렉션은 두 개의 EdgeShard 인스턴스를 사용해요:
- 이뮤터블 샤드 (Immutable shard): 스냅샷을 통해 클라우드에서 동기화된 사전 빌드 HNSW 인덱스. k-means 클러스터링으로 선택된 정상 베이스라인의 대표 부분집합(기본값 500개 센트로이드)을 포함한다. 이 샤드는 읽기 전용이에요. "정상이란 어떤 모습인가"의 핵심 참조를 제공한다.
- 뮤터블 샤드 (Mutable shard): 라이브 클립 임베딩이 도착하는 대로 받는다. 인덱싱되지 않은(무차별 스캔) 빠른 쓰기에 최적화되어 있다. 특정 카메라 현장의 최근 클립을 포함하며, 클라우드 베이스라인에 없을지도 모르는 로컬 컨텍스트를 포착한다.
/edge/detector.py
from qdrant_edge import (
Distance as EdgeDistance,
EdgeConfig,
EdgeShard,
FieldCondition,
Filter,
Point,
Query,
RangeFloat,
SearchRequest,
UpdateOperation,
VectorDataConfig,
)
SHARD_CONFIG = EdgeConfig(
vector_data=VectorDataConfig(
size=EDGE_EMBEDDING_DIM,
distance=EdgeDistance.Cosine,
)
)
class EdgeDetector:
def __init__(self):
self._data_dir = Path(QDRANT_EDGE_PATH)
self._mutable_dir = self._data_dir / "mutable"
self._immutable_dir = self._data_dir / "immutable"
self._mutable_dir.mkdir(parents=True, exist_ok=True)
# Mutable shard: created fresh with config
self._mutable_shard = EdgeShard(str(self._mutable_dir), SHARD_CONFIG)
# Immutable shard: loaded from snapshot (None until first sync)
self._immutable_shard: Optional[EdgeShard] = None
if self._immutable_dir.exists():
self._immutable_shard = EdgeShard(str(self._immutable_dir), None)
비대칭성에 주목하세요. 뮤터블 샤드는 config로 생성돼요(벡터 차원과 거리를 알아야 하므로). 이뮤터블 샤드는 None으로 열리는데, 그 config는 서버에서 스냅샷을 만들 때 미리 박혀 있었기 때문이에요.
쿼리 경로
모든 kNN 쿼리는 두 샤드를 동시에 검색하고, 점수로 결과를 병합한 뒤 top-k를 취해요:
def score_local(self, embedding: np.ndarray) -> float:
req = SearchRequest(
query=Query.Nearest(embedding.tolist()),
limit=K_NEIGHBORS,
with_payload=False,
)
results = list(self._mutable_shard.search(req))
if self._immutable_shard:
results.extend(self._immutable_shard.search(req))
# Merge and take top-k by score (descending = most similar)
results.sort(key=lambda x: x.score, reverse=True)
top_k = results[:K_NEIGHBORS]
if not top_k:
# No baseline yet: treat as maximally anomalous, not normal
return 1.0
sims = [r.score for r in top_k]
return 1.0 - float(np.mean(sims))
이렇게 해서 엣지 장치는 두 세계의 장점을 얻어요. 고품질 글로벌 베이스라인(이뮤터블 샤드)에 최근 로컬 컨텍스트(뮤터블 샤드)를 보강한 것이죠.
대표 부분집합 선택
클라우드 베이스라인은 100,000개 이상의 정상 벡터를 포함할 수 있어요. 엣지 장치에는 500개만 필요해요. MiniBatchKMeans로 클라우드 임베딩을 클러스터링하고 각 센트로이드에 가장 가까운 벡터를 선택해요:
from sklearn.cluster import MiniBatchKMeans
kmeans = MiniBatchKMeans(n_clusters=500, batch_size=1000)
kmeans.fit(all_embeddings)
# For each cluster, find the embedding closest to the centroid
for i in range(500):
cluster_mask = kmeans.labels_ == i
cluster_embeddings = all_embeddings[cluster_mask]
distances = np.linalg.norm(
cluster_embeddings - kmeans.cluster_centers_[i], axis=1
)
representative_idx = np.argmin(distances)
# Add this embedding to the edge collection
이렇게 하면 베이스라인의 커버리지를 보존하면서 엣지 장치 메모리에 편안하게 들어맞아요.
EDGE_SNAPSHOT_KMEANS=500을 설정하면 백엔드의 /api/snapshots/full 엔드포인트가 스냅샷을 만들기 전에 클라우드 베이스라인에 MiniBatchKMeans를 실행해, 엣지 이뮤터블 샤드를 위한 500-벡터 대표 부분집합을 만들어요.
엣지 트리아지: 불완전함이 요점인 이유
모든 프레임을 클라우드에서 처리하는 것은 어려울 뿐만 아니라(제한된 대역폭, 높은 지연 시간) 비용도 많이 들어요. 간단한 계산으로 증명해볼게요.
전체 클라우드 파이프라인으로 24시간 연속 감시를 처리하는 비용이 얼마나 될까요?
10초 클립이라면 카메라당 하루 8,640개 클립이에요. 각각을 Twelve Labs Marengo 임베딩 + Qdrant 스코어링 + VLM 캡셔닝에 돌리면 금방 부풀어요. 50대 카메라 함대라면 하루 432,000회의 클라우드 API 호출을 보게 돼요.
그럼 어떻게 해결할까요? 답은 엣지 트리아지(edge triage)에 있어요.
엣지 임베딩 모델(Jetson에서 DeepStream 추론 플러그인으로 실행)은 클라우드의 0.97에 비해 대략 ~0.85 AUC-ROC의 가벼운 공간 임베딩을 생성해요. 이는 상당한 정확도 격차예요. 엣지 임베딩은 공간 특징을 포착하지만 시간적 역학은 완전히 놓쳐요.
괜찮아요. 엣지는 이상을 탐지하려는 것이 아니라, 이상을 놓치지 않으려는 것이니까요. 엣지 임계값(0.06)은 의도적으로 느슨하게 설정되어 정밀도보다 재현율을 최적화해요:
- 엣지 트리아지 없음: 푸티지의 100%를 클라우드로 스트리밍한다. 전체 파이프라인에서 카메라당 시간당 360개 클립.
- 엣지 트리아지 있음: 푸티지의 ~15%를 스트리밍한다. 카메라당 시간당 ~54개 클립. 약 6배 대역폭 절감.
- 거짓 양성 비용: 잘못된 에스컬레이션은 클라우드 API 호출 1회의 비용이다. 클라우드가 재스코어링해 낮은 점수를 얻고 버린다. 인시던트가 생성되지 않는다.
- 거짓 음성 비용: 놓친 이상은 결코 클라우드에 도달하지 않는다. ~95% 엣지 재현율에서 대략 20개 중 1개의 이상이 완전히 놓친다.
이중 계층 아키텍처는 엣지가 불완전하고 클라우드가 그 뒤를 정리하기 때문에만 동작해요. 엣지가 완벽하다면 클라우드가 필요 없을 거예요. 엣지가 무작위라면 대역폭을 절약하지 못할 거예요. 달콤한 지점(sweet spot)은 높은 재현율과 감당할 만한 정밀도를 가진 저렴한 빠른 모델이에요.
엣지-투-클라우드 에스컬레이션
엣지 장치가 에스컬레이션 임계값 이상의 클립을 스코어링하면, 클립을 클라우드로 보내 재분석해요. 여기서 Twelve Labs와 Qdrant가 최종 판정을 위해 함께 동작해요.
에스컬레이션 흐름
- 엣지 탐지: DeepStream 추론이 임베딩을 생성하고, Qdrant Edge kNN이 ESCALATION_THRESHOLD(0.06) 이상으로 스코어링한다.
- 큐: Metropolis IoT Gateway가 안전한 클라우드 전송을 위해 클립을 큐에 넣는다(재시작에도 지속).
- 업로드: 클립과 엣지 메타데이터를 클라우드로 보낸다.
- 클라우드 재분석: Twelve Labs Marengo가 임베딩을 생성하고, Qdrant Cloud에서 kNN 스코어 + 의미 신호.
- VSS 보강: VLM 캡셔닝, 오디오 전사, CV 파이프라인(활성화된 경우).
- 앙상블 스코어링: 시간 부스트가 있는 70% 클라우드 스코어 + 30% 엣지 스코어.
- 확인: 앙상블 스코어를 임계값(0.038)과 비교.
우리 백엔드의 에스컬레이션 핸들러는 Twelve Labs와 로컬 모델 서버 경로를 모두 지원해요:
/backend/escalation.py
async def handle_escalation(request: EscalationRequest) -> EscalationResult:
cloud_embedding = None
if request.clip_data:
# Try Twelve Labs path first if enabled
if twelvelabs_client.is_enabled():
try:
# Upload to Twelve Labs for indexing
upload_result = twelvelabs_client.upload_video(
tmp_path, index_type="marengo"
)
video_id = upload_result.get("marengo_video_id")
if video_id:
# Also ingest via VSS if enabled
if vss.is_enabled():
await vss.ingest_video(tmp_path)
# Get cloud embedding from Twelve Labs
embedding = twelvelabs_client.get_video_embedding(video_id)
if embedding:
cloud_embedding = embedding
except Exception:
pass # Fall back to local model server
# Fallback: local model server
if cloud_embedding is None:
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(
f"{MODEL_SERVER_URL}/embed",
files={"file": (request.clip_filename, request.clip_data)},
)
cloud_embedding = resp.json()["embedding"]
# Score with cloud embedding against Qdrant baseline
scoring_embedding = cloud_embedding or request.edge_embedding
cloud_result = score_clip(
embedding=scoring_embedding,
collection_name=CLOUD_COLLECTION,
k=CONFIRMATION_K,
)
# Return cloud score — confirmation happens after ensemble in the endpoint
cloud_score = cloud_result.anomaly_score
return EscalationResult(
cloud_score=cloud_score,
...
)
앙상블 스코어링
앙상블 가중치는 계층 사이의 정확도 차이를 반영해요:
ens = ensemble_scorer.score(
edge_score=edge_score,
cloud_score=cloud_score,
device_id=edge_device_id,
timestamp=time.time(),
)
# Confirmation uses ensemble score, not raw cloud score
is_confirmed = ens.is_anomaly # ensemble_score > threshold (0.038)
낮은 클라우드 스코어는 높은 엣지 스코어를 억제해요(거짓 양성). 높은 클라우드 스코어는 높은 엣지 스코어를 확인해요(진짜 이상). 임계값(0.038)이 엣지 임계값(0.06)보다 낮은 이유는 클라우드 모델이 더 정확해서 더 빡빡한 결정 경계를 설정할 수 있기 때문이에요.
시간 부스트 (Temporal Boosting)
같은 장치에서 5분 창 안에 연속 에스컬레이션이 발생하면 스코어 부스트를 받아요:
boost = min(0.3, (consecutive_count - 1) * TEMPORAL_BOOST_FACTOR) # Factor = 0.1
final_score = ensemble_score + boost
연속 세 번의 에스컬레이션은 스코어에 +0.2를 더해요. 개별 클립 스코어가 경계선상에 있더라도 지속적 이벤트(진행 중인 말다툼 같은)가 확인 임계값을 넘게 도와줘요.
클립을 로컬에 저장하기
엣지가 클립을 처리하면 뮤터블 샤드에 임베딩을 저장하고 클라우드 동기화를 위해 큐에 넣어요:
def store_clip(self, embedding: np.ndarray, metadata: dict | None = None) -> str:
clip_id = uuid.uuid4().hex
vector = embedding.tolist()
payload = metadata or {}
payload["sync_timestamp"] = time.time()
self._mutable_shard.update(
UpdateOperation.upsert_points(
[Point(id=clip_id, vector=vector, payload=payload)]
)
)
# Queue for async cloud sync
self._upload_queue.put({"id": clip_id, "vector": vector, "payload": payload})
return clip_id
클라우드에서 스냅샷 동기화
이뮤터블 샤드는 스냅샷 동기화를 통해 최신 상태를 유지해요. 전체 동기화는 인덱싱된 샤드 전체를 다운로드하고, 증분 동기화는 변경된 세그먼트만 전송하는 부분 스냅샷을 사용해요:
def sync_from_server(self, full: bool = False) -> None:
# Flush pending uploads first; track whether they succeeded
upload_ok = self._upload_batch(pending_items) # returns True on success
if full or not self._immutable_shard:
# Full sync: download complete snapshot
resp = requests.post(f"{CLOUD_API_URL}/api/snapshots/full", stream=True)
with open(snapshot_path, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
EdgeShard.unpack_snapshot(str(snapshot_path), str(self._immutable_dir))
self._immutable_shard = EdgeShard(str(self._immutable_dir), None)
else:
# Incremental: send current manifest, get only changed segments
manifest = self._immutable_shard.snapshot_manifest()
resp = requests.post(
f"{CLOUD_API_URL}/api/snapshots/partial",
json={"manifest": manifest}, stream=True,
)
with open(snapshot_path, "wb") as f:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
self._immutable_shard.update_from_snapshot(str(snapshot_path))
# Only purge mutable shard if upload succeeded.
# If upload failed, points are not in the cloud yet -- deleting them would cause data loss.
if upload_ok:
self._mutable_shard.update(
UpdateOperation.delete_points_by_filter(
Filter(must=[FieldCondition(
key="sync_timestamp",
range=RangeFloat(lte=sync_timestamp),
)])
)
)
동기화 후 클라우드에 이미 업로드된 포인트는 kNN 쿼리 중 이중 계산을 방지하기 위해 뮤터블 샤드에서 제거돼요. 정리는 클라우드에 도달하지 못한 로컬 데이터를 삭제하지 않도록 upload_ok로 게이트된다.
오프라인 복원력
클라우드에 연결할 수 없으면 에스컬레이션 데이터를 JSON 파일로 디스크에 영속화해요:
async def escalate_to_cloud(self, clip_path, edge_embedding, edge_score, timestamp_ms=0, scene_id=""):
payload = {"edge_score": edge_score, "embedding": edge_embedding.tolist()}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
data = {
"edge_device_id": EDGE_DEVICE_ID,
"edge_score": str(edge_score),
"edge_embedding": json.dumps(edge_embedding.tolist()),
"timestamp_ms": str(timestamp_ms),
"scene_id": scene_id,
}
with open(clip_path, "rb") as f:
resp = await client.post(
f"{CLOUD_API_URL}/api/escalate",
data=data,
files={"file": (Path(clip_path).name, f, "video/mp4")},
)
resp.raise_for_status()
except Exception:
# Persist to offline queue for later flush
entry = {"clip_path": clip_path, "payload": payload}
out = self._offline_dir / f"{uuid.uuid4().hex}.json"
out.write_text(json.dumps(entry))
보류 중인 에스컬레이션은 다음 베이스라인 동기화 주기에서 플러시돼요.
뮤터블 샤드 보존
뮤터블 샤드는 엣지가 처리하는 모든 클립과 함께 커져요. 동기화 사이에는 모든 포인트가 무차별 스캔으로 검색되므로(HNSW 인덱스 없음), 샤드가 커질수록 쿼리 지연 시간이 저하돼요. 클라우드에 몇 시간 동안 연결할 수 없으면 샤드는 통제 없이 커지죠.
프로젝트는 RETENTION_STRATEGY 환경 변수를 통해 세 가지 구성 가능한 보존 전략을 지원해요. 배포 제약에 따라 선택하세요.
옵션 A: 동기화 전용 퇴거 (none)
RETENTION_STRATEGY=none
기본값이자 가장 안전한 옵션이에요. 포인트는 성공적인 동기화가 클라우드에 존재함을 확인한 후에만 뮤터블 샤드에서 제거돼요. 중단 동안 샤드는 커지지만 데이터는 절대 손실되지 않아요.
- 트레이드오프: 카메라가 10초 클립으로 24시간 오프라인이면 뮤터블 샤드에 8,640개 포인트가 쌓인다. 8GB RAM Jetson에서도 무차별 스캔에 여전히 감당 가능하지만 쿼리 지연 시간은 증가한다.
- 최적: 이상을 놓치는 것이 느린 쿼리보다 더 비싼 배포.
옵션 B: 시간 창 퇴거 (time_window)
RETENTION_STRATEGY=time_window
RETENTION_SECONDS=1800 # 30 minutes
백그라운드 워커가 RETENTION_CHECK_INTERVAL_S초마다 실행되어 RETENTION_SECONDS보다 오래된 포인트를 뮤터블 샤드에서 삭제해요. 퇴거된 포인트는 로컬 검색에서 제거되지만 SQLite 기반 업로드 큐에는 남아 클라우드 동기화를 기다려요.
def _evict_by_time(self) -> None:
cutoff = time.time() - RETENTION_SECONDS
self._mutable_shard.update(
UpdateOperation.delete_points_by_filter(
Filter(must=[FieldCondition(
key="sync_timestamp",
range=RangeFloat(lte=cutoff),
)])
)
)
- 트레이드오프: 보존 창보다 오래된 클립은 로컬 kNN 스코어링에 사용할 수 없다. 중단 중에 인시던트 버스트가 발생하면, 창이 만료된 후 엣지는 그들을 로컬에서 상호 참조할 수 없게 된다. 클라우드는 업로드 큐를 통해 여전히 수신하고 더 높은 정확도로 재스코어링할 수 있다.
- 최적: 로컬 스코어링 완전성보다 제한된 쿼리 지연 시간이 더 중요한 메모리 제약 장치.
옵션 C: 점수 우선 퇴거 (score_priority)
RETENTION_STRATEGY=score_priority
RETENTION_MAX_POINTS=2000
뮤터블 샤드를 RETENTION_MAX_POINTS로 제한해요. 에스컬레이션 임계값 이상의 이상 점수를 가진 미동기화 포인트는 고정(pin)되어 동기화될 때까지 절대 퇴거되지 않아요. 나머지 슬롯은 최신순으로 채워지며, 가장 오래된 정상 클립부터 퇴거돼요.
def _evict_by_score_priority(self) -> None:
all_points = list(self._mutable_shard.scroll(with_payload=True, with_vectors=False))
if len(all_points) <= RETENTION_MAX_POINTS:
return
pinned = []
evictable = []
for pt in all_points:
score = pt.payload.get("anomaly_score", 0.0)
synced = pt.payload.get("synced", False)
if not synced and score > ESCALATION_THRESHOLD:
pinned.append(pt)
else:
evictable.append(pt)
evictable.sort(key=lambda p: p.payload.get("sync_timestamp", 0))
keep_count = max(0, RETENTION_MAX_POINTS - len(pinned))
to_evict = evictable[:max(0, len(evictable) - keep_count)]
if to_evict:
self._mutable_shard.update(
UpdateOperation.delete_points([pt.id for pt in to_evict])
)
- 트레이드오프: 정상 클립이 먼저 퇴거되어 로컬 스코어링의 베이스라인 커버리지가 약간 줄어든다. 하지만 가장 중요한 데이터(미동기화 이상)는 연결 상태나 샤드 크기와 무관하게 보존된다.
- 최적: 오프라인 인시던트가 흔하고, 연결이 복구될 때까지 로컬 상호 참조를 위해 이상 클립을 엣지에 보관해야 하는 배포.
함대 관리 (Fleet Management)
실제 배포는 하나가 아닌 수십 대의 엣지 장치를 가져요. 클라우드 백엔드는 장치 레지스트리를 통해 각 Jetson을 독립적으로 추적해요.
각 장치는 첫 부팅 시 자신을 등록해요:
POST /api/edges/register
{
"name": "north-entrance-cam",
"location": "Building A - Zone 1",
"device_id": "edge-a1b2c3d4" # optional, auto-generated if omitted
}
그 시점부터 에스컬레이션은 edge_device_id로 태그되어 클라우드가 장치별 성능을 추적할 수 있어요:
POST /api/escalate
edge_device_id = "edge-a1b2c3d4"
edge_score = 0.12
edge_embedding = [...]
clip = <video file>
레지스트리는 Sentinel 대시보드에 표시되는 장치별 통계를 유지해요:
| 메트릭 | 무엇을 알려주는가 |
|---|---|
| escalation_rate_per_hour | 이 카메라가 얼마나 활동적인가 |
| confirmation_rate | 엣지 모델이 얼마나 정확한가 |
| false_positive_rate | 엣지 임계값을 튜닝해야 하는가 |
| baseline_version | 장치가 실행 중인 스냅샷 |
| status | online, offline, 또는 syncing |
5분 동안 에스컬레이션이나 하트비트를 보내지 않은 장치는 자동으로 오프라인으로 표시돼요. 베이스라인 동기화는 버전 관리된다. 클라우드가 새 스냅샷을 장치로 푸시하면, 장치가 수신을 확인할 때까지 상태가 syncing으로 바뀌어요.
장치별 임계값은 독립적으로 튜닝할 수 있어요. 붐비는 교차로를 비추는 카메라는 빈 계단을 비추는 카메라보다 자연스럽게 더 높은 점수를 볼 거예요. 전역 임계값을 조정하는 대신 장치 수준 오버라이드를 설정해요:
POST /api/edges/{device_id}/sync-baseline
이것은 해당 특정 장치로 스냅샷 다운로드를 트리거해, 함대 전체에 걸쳐 베이스라인 업데이트를 증분으로 롤아웃할 수 있게 해줘요.
요약
Part 2에서 Qdrant Edge의 투-샤드 아키텍처(이뮤터블 베이스라인 + 뮤터블 라이브 컨텍스트)를 구축하고, 클라우드 처리를 ~6배 줄이는 엣지 트리아지를 구현하고, 앙상블 스코어링과 시간 부스트로 에스컬레이션 파이프라인을 연결하고, 오프라인 복원력을 추가했어요. 엣지가 실행되고 있어요. 이제 원시 점수를 실행 가능한 인시던트로 바꿔야 해요.
다음은 무엇인가
Part 3 | 스코어링, 거버넌스, 배포에서는 원시 점수에서 인시던트 형성, 오염을 방지하는 베이스라인 거버넌스, 카메라에 걸친 통합 검색, UCF-Crime에서의 평가 결과, Vultr Cloud GPU 배포를 다룰 거예요.
추가 리소스:
- 프로젝트 저장소: qdrant/video-anomaly-edge
- Part 1: 아키텍처, Twelve Labs, NVIDIA VSS
- Part 3: 스코어링, 거버넌스, 배포
- Qdrant Edge 문서: qdrant.tech/documentation/edge
- Twelve Labs 문서: docs.twelvelabs.io
- Vultr Cloud GPU: vultr.com/products/cloud-gpu
더 알아보기 (Learn more)
출처: Qdrant 공식문서