SentrySearch는 "찾고 싶은 장면을 자연어로 입력하면 영상에서 해당 구간만 잘라 클립으로 돌려주는" 시맨틱 영상 검색 CLI다. README 한 줄 설명 그대로 — "Type what you're looking for, get a trimmed clip back." 영상을 텍스트로 받아쓰는 STT나 프레임 캡셔닝 같은 중간 단계 없이, 영상 픽셀을 직접 벡터로 만들어 검색어 벡터와 비교한다.
일반 영상 검색은 "프레임을 캡션으로 옮기거나 음성을 받아쓴 뒤" 그 텍스트를 검색한다. 음성 없는 대시캠은 검색 불가능하고, 캡션이 부정확하면 검색도 부정확하다.
SentrySearch는 그 중간 단계를 빼버린다. 영상 픽셀을 직접 768차원 벡터로 만들고, 검색어도 같은 공간의 벡터로 만들어 바로 비교한다. "중간 사람(middleman)을 없앤다"는 이 발상이 HN(해커뉴스)·GIGAZINE에서 반향을 일으켰다.
영상 → 캡션/자막 → 텍스트 검색이라는 우회로는 두 가지 위험이 있다. 첫째, 캡션 단계에서 정보가 손실된다. 한 프레임에서 "빨간 트럭, 신호등 빨강, 횡단보도, 행인 2명"을 다 잡으려면 캡션이 길어지는데 짧으면 손실, 길면 비용 폭발.
둘째, 단어 불일치. 사람이 "트럭"이라 검색하는데 AI가 "화물차"로 캡션을 달았다면 영영 못 찾는다. STT는 음성 없는 영상에 아예 무용지물이다.
영상 픽셀과 텍스트를 처음부터 같은 768차원 공간에 넣는다. "빨간 트럭"이라는 문장과 진짜 빨간 트럭이 찍힌 영상 30초가 비슷한 벡터가 되도록 임베딩 모델이 미리 학습돼 있다. 중간 단계가 없으니 정보 손실도, 단어 불일치도 없다. 수 시간 영상에서 sub-second 검색이 가능해진다.
| 방식 | SentrySearch 대비 한계 |
|---|---|
| STT(음성 인식) | 오디오를 받아써 검색 — 음성 없는 대시캠/CCTV에는 무용지물 |
| 프레임 캡셔닝 | AI가 캡션을 잘못 달면 검색 실패. 영상당 캡션 수천 개면 비용 폭발 |
| 키워드 태깅 | 사람이 수동 태깅 — 시간 소모, 미리 정한 태그 밖은 검색 불가 |
| SentrySearch | 영상 픽셀 직접 임베딩 → 텍스트와 같은 벡터 공간에서 즉시 비교 (중간 단계 0) |
여기에 README가 강조하는 차별점이 더 있다. sentrysearch highlights로 이상 장면(anomaly)을 자동 추출하고, 정지 프레임을 감지해 건너뛰며, 거의 같은 결과를 자동 중복 제거(dedupe)한다. Tesla 메타데이터(속도·위치·시간) 오버레이, 멀티카메라 합성(SentryMerge)·얼굴 가림(SentryBlur) 연계까지 묶여 있다.
| 백엔드 | 역할 | 상세 |
|---|---|---|
| Gemini Embedding 2 | 기본(클라우드) | Google API. 영상 픽셀을 네이티브로 임베딩. 빠르고 정확하나 영상이 구글로 전송됨 |
| DashScope Qwen3-VL | 클라우드 대안 | Alibaba DashScope API. 멀티모달 임베딩. 지역별 요금 상이. 설치: uv tool install ".[qwen-cloud]" 필요 |
| 로컬 Qwen3-VL | 프라이버시/무료 | qwen8b / qwen2b 모델을 내 GPU에서 실행. 영상이 외부로 나가지 않음 |
| 기술 | 역할 | 상세 |
|---|---|---|
| ChromaDB | 벡터 저장/검색 | 로컬 임베디드 벡터 DB. MRL 차원 절단으로 768차원에 통일 |
| 코사인 유사도 | 유사도 측정 | 검색어 벡터와 영상 청크 벡터의 방향 비교. 기본 임계값 0.41 |
| 중복 제거(dedupe) | 결과 정제 | 유사도 상한 0.9 권장 — 거의 같은 클립을 한 개로 합침 |
| 기술 | 역할 | 상세 |
|---|---|---|
| FFmpeg / imageio-ffmpeg | 영상 디코딩·트리밍 | 청킹, 해상도/FPS 축소, 매칭 구간 클립 추출 |
| PyTorch · torchvision | 로컬 모델 실행 | 로컬 Qwen3-VL 추론 런타임 |
| bitsandbytes | 4-bit 양자화 | NVIDIA 8~16GB VRAM에서 8B 모델 구동 (.[local-quantized]) |
| geopy (선택) | 역지오코딩 | Tesla 좌표 → 지명 변환 (오버레이용) |
한 시간짜리 영상을 통째로 임베딩할 수 없다. 모델 입력 한계도 있고 비용도 길이에 비례한다. 그래서 30초 단위로 자르되 5초씩 겹치게 자른다. 이벤트가 청크 경계에 걸쳐 어느 쪽에도 안 잡히는 일을 막는다.
백과사전에서 특정 사건을 찾을 때, 1·2·3챕터로 정확히 잘라 검색하면 사건이 1챕터 끝과 2챕터 시작에 걸쳐 있을 때 둘 다에서 빠질 수 있다.
그래서 발췌를 만들 때 일부러 페이지를 겹치게 자른다. 같은 사건이 두 발췌에 동시에 들어가도 손해는 없고, 빠지는 일은 막는다. LLM에 긴 문서를 넣을 때 쓰는 청킹+오버랩과 똑같은 전략이다.
임베딩 생성을 하나의 인터페이스 뒤로 숨겨, --backend 플래그만 바꾸면 Gemini / DashScope / 로컬 Qwen이 갈아끼워진다. 인덱싱 코드와 검색 코드는 백엔드가 무엇인지 몰라도 된다. 민감 영상은 로컬, 공개 영상은 클라우드를 인덱스 단위로 선택할 수 있는 유연성이 여기서 나온다.
대시캠·주차 영상은 차가 멈춰 화면이 거의 안 변하는 구간이 많다. SentrySearch는 JPEG 파일 크기 비교로 "이 구간은 정적"이라 판단하고 그 청크를 통째로 건너뛴다. 임베딩 호출 수 자체가 줄어 비용이 내려간다.
README 계산: 1시간 = 3,600초 = 3,600 프레임 × $0.00079 ≈ ~$2.84/시간 (기본 30초 청크·5초 오버랩). 정지 프레임이 많은 영상일수록 스킵으로 실제 비용은 더 내려간다. 100시간이면 단순 환산 ~$284이므로, 대량 인덱싱 전엔 비용을 가늠해야 한다.
핵심은 pyproject.toml이 extras로 설치 변형을 정의한다는 점이다. .[local](로컬 Qwen), .[local-quantized](4-bit NVIDIA), .[tesla](메타데이터 오버레이) — 무거운 의존성(PyTorch, bitsandbytes, geopy)을 필요한 사람만 선택 설치하게 분리했다. 초보자가 "왜 설치가 무거운가"를 배우기에 좋은 실전 예시다.
pip install pkg[local]처럼 대괄호 안 이름이 extra다. 모두에게 PyTorch를 강제하지 않고, 로컬 추론이 필요한 사람만 받게 한다.배울 것: 텍스트·영상을 같은 공간에 넣는 임베딩의 원리, 코사인 유사도로 의미 거리를 재는 법, 임계값(0.41)으로 "확신 없음"을 거르는 설계. 이 레포는 RAG·시맨틱 검색의 핵심 개념을 영상이라는 직관적 대상으로 보여준다.
배울 것: 임베딩을 저장하고 "가장 비슷한 것"을 빠르게 찾는 벡터 DB의 사용법. MRL 차원 절단으로 768차원 통일 — 백엔드마다 다른 출력 차원을 한 공간에 맞추는 실전 트릭.
배울 것: 긴 데이터를 조각내되 경계 손실을 막는 표준 패턴. 영상의 30초/5초 오버랩은 LLM에 긴 문서를 넣을 때의 청킹과 동일한 발상이다. 한 번 이해하면 모든 검색·RAG 파이프라인에 재활용된다.
배울 것: 같은 인터페이스 뒤에 Gemini/DashScope/로컬 Qwen을 갈아끼우는 Strategy 패턴. 프라이버시·비용·정확도의 트레이드오프를 코드 구조로 푸는 법을 배운다.
배울 것: setup.py 없이 pyproject.toml 하나로 빌드·의존성·CLI 엔트리포인트·extras를 모두 선언하는 법. uv tool install로 CLI를 격리 환경에 까는 pipx 대안 워크플로.
배울 것: 480p/5fps 다운스케일, 정지 프레임 스킵(JPEG 크기 비교), 결과 dedupe 등 "같은 결과, 더 적은 비용"을 위한 실전 최적화. 프레임 단위 과금 모델을 직접 계산해보며 클라우드 AI 비용 감각을 기른다.
| 항목 | 클라우드 모드(Gemini/DashScope) | 로컬 모드(Qwen3-VL) |
|---|---|---|
| Python | 3.11 또는 3.12 (3.13 이상 미지원) | 3.11 또는 3.12 (3.13 이상 미지원) |
| GPU | 불필요 | 필수 (Apple Silicon 또는 NVIDIA) |
| 네트워크 | API 호출 필수 | 모델 다운로드 후 오프라인 가능 |
| 비용 | ~$2.84/시간 (Gemini) | 무료 (전기·하드웨어만) |
| 프라이버시 | 영상이 외부 전송됨 | 영상이 로컬에만 머묾 |
| 공통 | FFmpeg(자동 동봉 imageio-ffmpeg) · ChromaDB 로컬 저장 공간 | |
| 하드웨어 | 설치 명령 | 자동 선택 모델 |
|---|---|---|
| Apple Silicon 24GB+ | uv tool install ".[local]" | qwen8b |
| Apple Silicon 16GB | uv tool install ".[local]" | qwen2b |
| NVIDIA 18GB+ VRAM | uv tool install ".[local]" | qwen8b |
| NVIDIA 8~16GB VRAM | uv tool install ".[local-quantized]" | qwen8b (4-bit) |
| Apple Silicon 8GB | uv tool install ".[local]" | qwen2b (메모리 부족 가능, Gemini API 권장) |
README가 명시: 로컬 모드는 "Intel Macs and machines without a dedicated GPU"에서는 잘 동작하지 않는다. 이런 환경은 Gemini/DashScope 클라우드 백엔드를 써야 한다.
Gemini API 키만 발급받아, 사진 10장을 임베딩해서 ChromaDB에 넣고 텍스트로 검색해본다. 영상 인덱싱은 비싸지만 이미지로 같은 원리(임베딩→벡터DB→코사인 유사도)를 저비용으로 체험할 수 있다.
# 1. 클론 후 설치 (uv 필요) — Gemini 백엔드 기본
# DashScope 백엔드 사용 시: uv tool install ".[qwen-cloud]"
git clone https://github.com/ssrajadh/sentrysearch
cd sentrysearch && uv tool install .
# 2. 환경 초기화 (.env에 API 키 입력)
sentrysearch init
# 3. 짧은 드라이빙 영상 인덱싱 + 검색
sentrysearch index ./clips --verbose
sentrysearch search "빨간 트럭" --results 3
sentrysearch stats
--verbose로 청킹·전처리·임베딩이 내부에서 어떻게 도는지 관찰한다.같은 영상을 --backend 플래그로 Gemini API와 로컬 Qwen3-VL 둘 다 인덱싱하고, 동일 검색어 10개로 결과를 비교한다. 정확도·처리 시간·비용을 표로 정리하면 본인 환경에 맞는 선택 기준이 생긴다.
sentrysearch highlights -n 3으로 영상에서 "평소와 다른" 장면을 자동 추출하고, 그 결과를 검색 기반 추출과 비교한다. 청킹 파라미터(--chunk-duration, --overlap)를 바꿔가며 추출 품질이 어떻게 달라지는지 실험한다.
FastAPI 백엔드 + 간단한 프론트엔드로 SentrySearch를 감싼 웹 도구를 만든다. 영상 업로드 → 자동 인덱싱 → 검색창 → 결과 클립 미리보기. 멀티모달 임베딩의 위력을 동료에게 시연하기 좋은 데모가 된다.
# app.py — SentrySearch를 서브프로세스/모듈로 호출
from fastapi import FastAPI
app = FastAPI()
@app.get("/search")
def search(q: str):
# sentrysearch search "{q}" 결과를 JSON으로 반환
return {"query": q, "clips": run_search(q)}
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | 임베딩이란 무엇인가 | 단어 임베딩으로 "강아지↔댕댕이" 유사도 계산 |
| 화 | 코사인 유사도 직접 구현 | numpy로 두 벡터의 코사인 값 계산 |
| 수 | ChromaDB 입문 | 문장 10개 임베딩 → 저장 → 검색 |
| 목 | 멀티모달 임베딩 개념 | 이미지+텍스트를 한 공간에 넣어 검색 |
| 금 | 임계값과 정밀도/재현율 | threshold를 바꾸며 결과 품질 관찰 |
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | 설치 & CLI 전체 명령 | init/index/search/img/stats 순회 |
| 화 | 청킹·오버랩 파라미터 | chunk-duration/overlap 바꿔 비교 |
| 수 | 전처리(480p·5fps·정지 스킵) | --verbose 로그에서 스킵 동작 확인 |
| 목 | img 검색 & highlights | 이미지 쿼리·이상 장면 추출 실험 |
| 금 | dedupe & save-top | 중복 제거 임계값 조정 |
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | Gemini Embedding 2 API | 키 발급·요금 구조 이해 |
| 화 | DashScope Qwen3-VL | 클라우드 대안 백엔드 연결 |
| 수 | 로컬 Qwen3-VL 구동 | GPU 메모리별 모델 선택·양자화 |
| 목 | 비용 계산 실습 | 프레임 단가 × 시간으로 견적 |
| 금 | 프라이버시 트레이드오프 | 민감 영상 로컬/공개 영상 클라우드 분리 |
| 요일 | 학습 내용 | 실습 |
|---|---|---|
| 월 | FastAPI로 감싸기 | 검색 API 엔드포인트 작성 |
| 화 | 프론트엔드 결과 미리보기 | 클립 썸네일·재생 UI |
| 수 | RTSP 실시간 스트림 | 1분 단위 청킹+임베딩 파이프라인 |
| 목 | 임계값 기반 알림 | 유사도 0.7↑ 시 슬랙 알림 |
| 금 | 종합 프로젝트 | CCTV 시맨틱 모니터링 프로토타입 |
| 키워드 | 설명 |
|---|---|
| 임베딩 | 데이터를 고정 길이 숫자 리스트(벡터)로 바꾸는 기법. 비슷한 의미 → 비슷한 숫자. SentrySearch는 768차원 사용 |
| 벡터 공간 | 임베딩 숫자들이 만드는 가상의 공간. 가까울수록 의미가 비슷함 |
| 멀티모달 | 텍스트·이미지·영상 등 여러 종류(모달리티) 데이터를 한 모델이 같은 공간에서 다룸 |
| 시맨틱 검색 | 글자 일치가 아니라 의미 유사도로 찾는 검색. "트럭"으로 "화물차" 영상도 찾음 |
| 코사인 유사도 | 두 벡터가 같은 방향을 향하는 정도. -1~1, 1에 가까울수록 유사. 기본 임계값 0.41 |
| 벡터 DB | 임베딩을 저장하고 "가장 비슷한 것"을 빠르게 찾는 DB. 여기선 ChromaDB |
| ChromaDB | 로컬에서 가볍게 도는 임베디드 벡터 DB. 대안: Pinecone, Qdrant, Weaviate |
| 청크 | 긴 영상을 자른 조각. 기본 30초 단위 |
| 오버랩 | 청크 경계를 일부러 겹치게 자르는 것. 기본 5초. 경계 이벤트 누락 방지 |
| 정지 프레임 스킵 | 화면이 거의 안 변하는 구간을 JPEG 크기 비교로 감지해 건너뛰는 비용 최적화 |
| MRL | Matryoshka Representation Learning. 큰 임베딩을 앞부분만 잘라 작은 차원으로 쓰는 기법. 여기선 768차원 통일에 사용 |
| Gemini Embedding 2 | 구글의 멀티모달 임베딩 모델. SentrySearch 기본 백엔드 |
| Qwen3-VL | 알리바바의 비전-언어 모델. DashScope(클라우드)·로컬 두 경로로 사용 |
| highlights | 검색어 없이 영상에서 이상/주목 장면을 자동 추출하는 명령 |
| dedupe | 거의 같은 결과 클립을 한 개로 합치는 중복 제거 (유사도 상한 0.9 권장) |
| uv | Rust로 만든 초고속 파이썬 패키지 매니저. uv tool install로 CLI를 격리 설치 (pipx 대안) |
| extras | pyproject.toml의 선택 의존성 묶음. .[local] .[tesla]처럼 필요한 기능만 설치 |
| SentryMerge / SentryBlur | 같은 저자의 자매 도구. 멀티카메라 합성 / 얼굴·번호판 가림(redaction) |