공장 CCTV·웹캠 영상에서 배경지향 슐리렌(BOS) 기법과 딥러닝을 이용해 가스 누출 여부를 자동 탐지하는 시스템입니다.
핵심 설계: 흐름 계산·오탐 억제·정규화·배경 추정 로직을
bos_common.py한 곳에 모아, 학습과 실시간 추론이 글자 그대로 동일한 신호 처리를 쓰도록 했습니다. 두 단계의 입력 분포 불일치가 원천 차단됩니다.
- 시스템 요구사항
- 설치
- 폴더 구조
- 동영상 파일 준비 (파일명 규칙)
- Step 1 — 전처리 (
1_preprocess.py) - Step 2 — 모델 학습 (
2_train.py) - Step 3 — 실시간 탐지 (
3_realtime_detect1.py) - 결과 해석
- 오탐(FP) 줄이기
- 파라미터 참고표
- 자주 묻는 질문 / 오류 해결
| 항목 | 최소 사양 | 권장 사양 |
|---|---|---|
| Python | 3.9 이상 | 3.11 이상 |
| RAM | 8 GB | 16 GB 이상 |
| GPU | 없어도 동작 (느림) | NVIDIA CUDA 지원 GPU |
| 카메라 | 실시간 탐지 시 웹캠 필요 | — |
| OS | Windows / macOS / Linux | — |
GPU 없이도 실행 가능합니다. CPU만 있을 경우 학습이 느리므로
--num_epochs 30정도로 줄여서 시작하세요.
pip install -r requirements.txt| 패키지 | 용도 |
|---|---|
opencv-python |
동영상/웹캠 읽기, Farneback Optical Flow, 블롭 분석 |
numpy |
배열 처리 및 .npy 파일 저장 |
torch |
딥러닝 학습/추론 프레임워크 (PyTorch) |
scikit-learn |
데이터 분할, 평가 지표 계산 |
tqdm |
전처리 진행 상황 표시 |
input_videos/ 폴더만 직접 만들면 됩니다.
output_dataset/, checkpoints/는 프로그램이 자동 생성합니다.
프로젝트 루트/
│
├── input_videos/ ← ✅ 직접 만들고 동영상을 넣는 폴더
│ ├── line_A_G.mp4
│ ├── line_A_N.mp4
│ └── ...
│
├── output_dataset/ ← 전처리 프로그램이 자동 생성
├── checkpoints/ ← 학습 프로그램이 자동 생성
│
├── bos_common.py ← ⭐ 공용 신호 처리 (학습·실시간 공유, 튜닝은 여기서)
├── 1_preprocess.py ← Step 1
├── 2_train.py ← Step 2 (모델 구조 BOS3DCNN 정의 포함)
├── 3_realtime_detect1.py← Step 3 (웹캠 실시간 탐지)
└── requirements.txt
mkdir input_videos⭐
bos_common.py가 단일 진실 공급원입니다. 오탐 억제 파라미터를 바꾸려면 이 파일만 수정하세요. 그러면 전처리와 실시간 추론이 자동으로 함께 바뀝니다. (단, 바꾼 뒤 재전처리 + 재학습 필수 — 9장 참고)
두 가지 방법 중 아무거나 쓸 수 있고, 혼용도 가능합니다.
input_videos/gas/ 와 input_videos/normal/ 폴더에 넣기만 하면 됩니다. 카메라가 자동으로 붙인 이름(C0001.MP4 등)을 그대로 써도 됩니다.
input_videos/
├── gas/
│ ├── C0001.MP4 ✅ 가스 (파일명 무관)
│ ├── C0002.MP4 ✅ 가스
│ └── 아무이름.mov ✅ 가스
└── normal/
├── C0001.MP4 ✅ 정상 (gas/C0001 과 이름 같아도 OK)
└── clip_03.mp4 ✅ 정상
내부적으로
Gas_C0001,Normal_C0001처럼 클래스 접두사를 붙여 저장하므로 폴더 간 파일명이 겹쳐도 안전합니다.
| 끝 문자 | 의미 | 예시 |
|---|---|---|
_G |
Gas — 가스 누출 영상 | factory_line1_G.mp4 |
_N |
Normal — 정상 영상 | factory_line1_N.mp4 |
input_videos/
├── factory_A_G.mp4 ✅ 가스 누출
├── factory_A_N.mp4 ✅ 정상
└── test_video.mp4 ❌ _G/_N 없음 → 건너뜀 (경고 출력)
지원 형식:
.mp4.avi.mov.mkv
동영상에서 BOS 신호(Optical Flow)를 추출하고, 오탐 억제 필터를 적용해
16프레임 단위 청크 .npy 로 저장합니다. 이 필터는 실시간 추론과 동일하게
적용되므로, 모델은 처음부터 "정제된 신호"로 학습됩니다.
python 1_preprocess.pypython 1_preprocess.py \
--input_dir input_videos \ # 동영상 폴더 (기본값)
--output_dir output_dataset \ # 출력 폴더 (기본값)
--chunk_size 16 \ # 청크당 프레임 수 (기본 16)
--overlap 8 \ # 청크 간 겹침 (기본 8, stride = chunk-overlap)
--ema_alpha 0.05 # 배경 갱신 속도 (기본 0.05, 흔들리면 ↓)FP 억제 고급 옵션 (기본값은 bos_common.py 와 동일):
python 1_preprocess.py \
--deadzone_lo 0.6 \ # 하한: 노이즈 경보 잦으면 ↑
--ceiling_hi 6.0 \ # 상한: 사람 경보 잦으면 ↓
--blob_frac 0.04 \ # 응집 블롭 면적비: 사람 경보 잦으면 ↓
--no_gmc \ # 전역 모션 상쇄 끄기 (보통 켜 두는 것 권장)
--no_coherence # 응집 블롭 제거 끄기권장: CLI 옵션 대신
bos_common.py상단 상수를 직접 수정하세요. 그래야 실시간 추론과 값이 자동으로 일치합니다. CLI로 바꾼 경우 실시간 추론도 같은 값이 되도록bos_common.py를 맞춰야 합니다.
흐름 크기는 해상도에 비례하므로, 전처리·실시간 모두 bos_common.RESIZE
(기본 224×224)에서 흐름을 계산하도록 고정되어 있습니다. 모델 입력
해상도(112)로의 축소는 학습 단계에서 자동 처리됩니다.
output_dataset/
├── Gas/
│ ├── factory_A_G_chunk0000.npy ← shape: (16, 224, 224, 2)
│ └── ...
└── Normal/
├── factory_A_N_chunk0000.npy
└── ...
각 .npy = 16프레임 × 224 × 224 × 2채널(dx, dy), float32, [-1, 1] 정규화.
python 2_train.pypython 2_train.py \
--dataset_dir output_dataset \ # 데이터셋 폴더 (기본값)
--checkpoint_dir checkpoints \ # 모델 저장 폴더 (기본값)
--img_size 112 \ # 모델 입력 해상도 (기본 112)
--batch_size 8 \ # 배치 크기 (GPU 메모리에 맞게)
--num_epochs 50 \ # 최대 에포크 (기본 50)
--lr 1e-3 \ # 학습률 (기본 0.001)
--fp_weight 2.0 \ # 오탐 페널티 강도 (기본 2.0)
--threshold 0.5 # 분류 임계값 (기본 0.5)
⚠️ 전처리 신호를 바꿨다면 재학습 필수.bos_common.py또는 전처리 옵션을 바꾼 경우, 기존best_model.pth는 옛 입력 분포라 무효입니다. 반드시1_preprocess.py를 다시 돌린 뒤 이 스크립트로 재학습하세요.
[ 테스트 결과 ]
Accuracy : 0.8977
F1 Score : 0.8834
Precision : 0.9412 ← 오탐 억제 핵심 지표
Recall : 0.8333 ← 미탐 억제 핵심 지표
혼동 행렬:
예측 Normal 예측 Gas
실제 Normal TN= 41 FP= 3 ← 이 값을 최소화
실제 Gas FN= 7 TP= 35
[ 임계값 스윕 — 오탐(FP)/미탐(FN) 트레이드오프 ]
Threshold Precision Recall F1 FP FN
----------------------------------------------------------
0.45 0.9012 0.8810 0.8909 4 5
0.50 0.9412 0.8333 0.8841 3 7 ← 현재 기본값
0.55 0.9624 0.8095 0.8793 2 9
0.65 1.0000 0.7381 0.8493 0 13
학습이 끝나면 checkpoints/best_model.pth (Validation F1 최고 에포크
가중치)가 저장됩니다. 위 임계값 스윕 표에서 원하는 FP 수준의
threshold 를 골라 Step 3에 반영합니다.
웹캠 영상에 학습된 모델을 적용해 실시간으로 가스 누출을 탐지합니다.
전처리와 완전히 동일한 신호 경로(bos_common.process_pair)를 사용합니다.
✅ 학습된 모델(
checkpoints/best_model.pth, ~41MB)이 저장소에 포함돼 있습니다.git clone후 Step 1·2(전처리·학습) 없이 바로 Step 3 실행 가능합니다. (대용량 영상·청크 데이터는 제외되어 있으니, 재학습하려면 직접 영상을 넣어 Step 1부터 진행하세요.)
git clone https://github.com/minigu5/BOS-.git
cd BOS-
pip install -r requirements.txt
python 3_realtime_detect1.py다른 폴더에서 실행해도 되도록 모델/모듈 경로는 스크립트 위치 기준으로 해석됩니다. 모델 파일이 없으면 친절한 안내 메시지가 출력됩니다.
- ⭐ ROI(관심영역) 드래그 — 창이 뜨면 가스가 보이는 영역을 마우스로 드래그한 뒤 ENTER.
학습 영상이 BOS 영역만 크롭돼 있어, 추론도 같은 영역만 넣어야 오탐이 크게 줄어듭니다.
전체 화면을 쓰려면 그냥 ENTER. 실행 중
r키로 언제든 재설정. - 분할 화면 — 왼쪽은 카메라 ROI, 오른쪽은 그 영역을 실시간 BOS 처리한 강도 히트맵(파랑=약함 → 빨강=강함). 두 패널 모두 비율을 유지해 잘리지 않습니다.
- 상단에 큰 글씨로 가스 확률(%) 이 초록(낮음)→빨강(높음)으로 표시됩니다.
- 하단에 경보 규칙과 최근 6프레임 상태가 6칸으로 표시됩니다 — 각 칸은 그 프레임이 임계값을 넘었는지(OK/-)를 보여주며, 6칸 중 4칸 이상이 OK면 경보가 울립니다. (※ 6개가 다른 조건이 아니라, 같은 판정을 최근 6프레임에 대해 본 슬라이딩 투표입니다.)
- 경보 시 빨간 테두리 +
WARNING+ 사인파 경보음. - 상단
Thr%슬라이더로 임계값을 즉석 조절(오탐↔미탐 균형). 종료:q.
| 키/위젯 | 기능 |
|---|---|
| 마우스 드래그 + ENTER | BOS 관심영역(ROI) 지정 |
r |
ROI 재설정 |
Res 슬라이더 |
카메라 캡처 해상도 전환 (720p / 1080p / 1440p / 4K). iPhone 영상 최대=4K |
Thr% 슬라이더 |
경보 임계값 실시간 조절 |
MinMove x1000 슬라이더 |
이 값 미만의 미세 픽셀 움직임은 무시 (노이즈 억제) |
Ceil x100 슬라이더 |
이 값 초과 움직임 제거(큰 강체). 강한 가스가 잘리면 ↑ |
KeepGas 토글 |
ON 시 큰 블롭이라도 난류(가스) 는 유지하고 강체(손) 만 제거 |
q |
종료 |
⚠️ MinMove·Ceil·KeepGas는 실험용입니다. 기본값은 학습과 동일해 모델 정확도가 보장되지만, 슬라이더로 바꾸면 학습 분포와 달라져 확률(%)이 부정확해질 수 있습니다. 좋은 값을 찾은 뒤에는 그 값으로bos_common.py를 고정하고 재전처리+재학습해야 모델이 제대로 활용합니다.
🔊 경보음은 OS 기본 플레이어(macOS
afplay/ Windowswinsound/ Linuxaplay)로 재생되며 별도 설치가 필요 없습니다.
| 상수 | 기본값 | 설명 |
|---|---|---|
MODEL_PATH |
checkpoints/best_model.pth |
사용할 모델 경로 |
CAMERA_INDEX |
0 | 웹캠 번호 (안 켜지면 1로) |
THRESHOLD |
0.5 | 경보 임계값 — 재학습 후 [6장] 스윕 표 보고 조정 |
ALARM_WINDOW |
6 | 시간적 히스테리시스 관찰 창 |
ALARM_MIN_HITS |
4 | 창 안에서 이만큼 임계 초과해야 경보 |
오탐 3중 방어: ①
bos_common신호 필터 → ② 정규화 노이즈 차단 → ③ 시간적 히스테리시스(연속 깜빡임 무시). 한두 프레임 튀는 값으로는 경보가 울리지 않습니다.
웹캠 실시간 연결이 안 되는 카메라(예: 학습용 카메라)로 찍어둔 영상 파일을 재생하며
추론합니다. 신호 처리·모델·UI(좌:영상 / 우:BOS 히트맵, 큰 확률, 6프레임 조건, 경보음,
억제 슬라이더)는 3_realtime_detect1.py 와 완전히 동일하고 입력만 동영상입니다.
python 4_nonrealtime.py <영상경로>
python 4_nonrealtime.py # 인자 없으면 파일 선택창| 키 | 기능 |
|---|---|
SPACE |
재생 / 일시정지 |
b |
처음부터 다시 재생 |
r |
ROI 재설정 |
q |
종료 |
- 영상이 끝나면 자동으로 처음부터 반복 재생됩니다.
- 해상도 슬라이더(
Res)는 없습니다(영상 파일은 해상도 고정). 나머지 슬라이더는 동일. - 내부적으로
3_realtime_detect1.py의 함수를 그대로 import 해서 쓰므로, 한쪽을 고치면 양쪽에 반영됩니다.
| 지표 | 의미 | 중요도 |
|---|---|---|
| Precision | 가스로 예측한 것 중 실제 가스 비율 | ⭐⭐⭐ (오탐 직결) |
| Recall | 실제 가스 중 탐지한 비율 | ⭐⭐ (미탐 직결) |
| F1 | Precision·Recall 조화 평균 | ⭐⭐ |
| Accuracy | 전체 정확도 | ⭐ (불균형 데이터에서 오해 소지) |
예측: 정상(N) 예측: 가스(G)
실제: 정상(N) TN (잘함) FP (오탐) ← 줄여야 함
실제: 가스(G) FN (미탐) TP (잘함)
- FP (오탐): 정상인데 "가스!" 경보 → 불필요한 대피/조업 중단
- FN (미탐): 실제 가스인데 탐지 못함 → 안전 위험
오탐을 줄이는 손잡이는 세 단계에 있습니다.
| 상수 | 기본값 | 증상 → 조정 |
|---|---|---|
DEADZONE_LO |
0.10 | 가만히 있어도 경보 → ↑ (0.15, 0.20 …) |
MIN_DENOM |
0.20 | 가만히 있어도 경보 → ↑ (노이즈 뻥튀기 차단 강화) |
CEILING_HI |
6.0 | 사람만 움직여도 경보 → ↓ (4.0 …) |
BLOB_AREA_FRAC |
0.10 | 사람만 움직여도 경보 → ↓ (0.05 …) |
값 변경 후 반드시:
python 1_preprocess.py # 새 신호로 재생성 python 2_train.py # 재학습
python 2_train.py --fp_weight 3.0 # 기본 2.0 → 오탐 학습 억제 강화너무 높이면 모델이 항상 "정상"으로만 예측합니다. Recall이 0.5 아래로 떨어지면 낮추세요.
학습 재실행 없이, 스윕 표에서 원하는 FP 수준의 threshold 를 골라
Step 3의 THRESHOLD 에 반영합니다. 실시간에는 추가로
ALARM_MIN_HITS 를 높여 깜빡임성 오탐을 더 줄일 수 있습니다.
1단계: 기본값으로 전처리·학습 → 스윕 표 확인
2단계: THRESHOLD 조정 (재학습 없이 FP↓) ← 손잡이 C
3단계: 그래도 특정 상황 오탐 → bos_common 조정 후 재학습 ← 손잡이 A
4단계: 전반적 오탐 → fp_weight 조정 후 재학습 ← 손잡이 B
주의: 손잡이 A의 상한/블롭 제거는 매우 크고 응집된 가스운까지 지울 수 있어 미탐이 늘 수 있습니다.
BLOB_AREA_FRAC로 균형을 잡으세요.
| 상수 | 기본값 | 설명 |
|---|---|---|
RESIZE |
(224, 224) | 흐름 계산 해상도 (고정 권장) |
EMA_ALPHA |
0.05 | 배경 갱신 속도 (카메라 흔들리면 ↓) |
GMC |
True | 전역 모션 상쇄 (카메라 흔들림 제거) |
DEADZONE_LO |
0.6 | 하한 — 노이즈 제거 |
CEILING_HI |
6.0 | 상한 — 사람/차량 큰 움직임 제거 |
COHERENCE |
True | 큰 응집 블롭(사람 형태) 제거 |
BLOB_AREA_FRAC |
0.04 | 블롭 제거 면적 기준 |
MIN_DENOM |
1.5 | 정규화 노이즈 뻥튀기 차단 하한 |
| 옵션 | 기본값 | 언제 바꾸나 |
|---|---|---|
--chunk_size |
16 | 더 긴 시간 문맥 필요 시 32 |
--overlap |
8 | 데이터 부족 시 ↑ (청크 수 증가) |
--deadzone_lo / --ceiling_hi / --blob_frac |
bos_common 동일 | 9장 참고 |
| 옵션 | 기본값 | 언제 바꾸나 |
|---|---|---|
--img_size |
112 | 정확도 우선이면 224 (느려짐) |
--batch_size |
8 | GPU 메모리 부족 시 4 |
--num_epochs |
50 | 데이터 많으면 100 |
--fp_weight |
2.0 | 오탐 많으면 3.0~4.0 |
--threshold |
0.5 | 스윕 표 보고 조정 |
| 상수 | 기본값 | 설명 |
|---|---|---|
THRESHOLD |
0.5 | 경보 임계값 (스윕 표 기준) |
ALARM_WINDOW / ALARM_MIN_HITS |
6 / 4 | 시간적 히스테리시스 강도 |
CAMERA_INDEX |
0 | 웹캠 번호 |
파일명이 _G/_N(확장자 바로 앞)으로 끝나야 합니다.
틀림: gas_leak.mp4, factory_G_test.mp4
맞음: gas_leak_G.mp4, factory_test_G.mp4
python 1_preprocess.py # 먼저
python 2_train.py # 그 다음output_dataset/Gas/ 또는 Normal/ 중 하나가 비어 있습니다.
두 클래스 동영상이 모두 input_videos/ 에 있는지 확인하세요.
bos_common.py 의 DEADZONE_LO, MIN_DENOM 을 올리고
재전처리 + 재학습하세요. 즉시 효과가 필요하면 실시간의
THRESHOLD 또는 ALARM_MIN_HITS 를 높이세요. (9장 참고)
bos_common.py 의 CEILING_HI, BLOB_AREA_FRAC 을 내리고
재전처리 + 재학습하세요. (9장 손잡이 A)
실시간 탐지는 2_train.py 의 모델 구조를 import 합니다.
3_realtime_detect1.py 와 같은 폴더에서 실행하세요.
3_realtime_detect1.py 의 CAMERA_INDEX 를 0 → 1 로 바꿔보세요.
python 2_train.py --num_epochs 30 --batch_size 4 --img_size 64- 전처리 로그:
preprocess.log - 학습 로그:
training.log
(실시간 탐지는 콘솔에만 출력)
① input_videos/ 폴더 생성 후 동영상 넣기 (파일명 끝 _G / _N 필수)
↓
② python 1_preprocess.py
→ output_dataset/Gas/, Normal/ 생성 확인
↓
③ python 2_train.py
→ training.log·스윕 표 확인, checkpoints/best_model.pth 생성
↓
④ 스윕 표에서 목표 FP 수준의 threshold 선택
→ 3_realtime_detect1.py 의 THRESHOLD 에 반영
↓
⑤ python 3_realtime_detect1.py (웹캠 실시간 탐지, 종료 'q')
※ bos_common.py 의 신호 파라미터를 바꾸면 ②③ 을 다시 수행해야 합니다.
�� 탐지, 종료 'q')
※ bos_common.py 의 신호 파라미터를 바꾸면 ②③ 을 다시 수행해야 합니다.