처음에는 CCTV 화면에 사람과 안전모 박스만 잘 그리면 산업안전 관제가 완성될 줄 알았다. 실제로 카메라 입력, PPE 감지, 얼굴 식별, 넘어짐 감지, 이벤트 저장, React 대시보드를 하나로 연결해 보니 가장 어려운 일은 모델을 돌리는 것이 아니었다. 여러 모델이 쏟아내는 결과를 한 사람의 한 사건으로 정리하고, 운영자가 확인할 수 있는 화면까지 끊기지 않게 전달하는 일이 더 어려웠다.
AIVIS는 CCTV 영상에서 안전모·안전조끼 미착용과 넘어짐을 감지하고, 등록된 작업자를 식별해 위험 이벤트·안전 점수·리포트로 이어 주는 4인 팀 프로젝트다. 2025년 9월 29일부터 12월 10일까지 개발했으며, 나는 AI Core를 맡아 얼굴 식별 데이터 파이프라인, PPE 모델 최적화, 포즈 기반 낙상 감지, 통합 테스트를 담당했다.
개발 기간 2025.09.29–2025.12.10
내 역할 얼굴 식별 · PPE 최적화 · 낙상 감지 · 통합 테스트
최종 흐름 CCTV → AI 추론 → 위험 이벤트 → 저장·알림 → 관제 대시보드

AIVIS는 모델 시연이 아니라 위험 이벤트와 관제 화면까지 연결하는 것을 목표로 했다.
1. 만들고 싶었던 것은 ‘박스를 그리는 AI’가 아니었다
프로젝트 초반의 기능 목록은 안전모 감지, 안전조끼 감지, 얼굴 인식, 넘어짐 감지처럼 모델 이름을 중심으로 나뉘어 있었다. 각 기능만 따로 보면 데모를 만들 수 있지만, 산업안전 관제에서는 그 결과가 결국 하나의 질문으로 모여야 했다.
“어느 카메라에서, 어떤 작업자에게, 무슨 위험이 발생했고, 관리자가 무엇을 확인해야 하는가?”
그래서 최종 구조는 세 단계로 정리했다. YOLO와 얼굴 모델이 영상에서 신호를 찾고, 백엔드가 같은 사람의 결과를 하나의 이벤트로 합치며, 대시보드가 실시간 화면과 위반 이력·통계를 함께 보여 주는 방식이다.

실시간 위험 이벤트, 카메라 상태, 안전 점수와 주간 통계를 한 화면에 연결한 최종 대시보드
2. 내가 맡은 AI Core는 세 모델을 ‘한 사람’에 연결하는 일이었다
내가 맡은 영역은 얼굴 식별, PPE 감지 최적화, 포즈 기반 넘어짐 감지와 통합 테스트였다. 모델을 각각 실행하는 것보다, 같은 프레임 안에서 나온 결과가 누구에게 속하는지 맞추는 작업이 훨씬 오래 걸렸다.
| 영역 | 실제 구현 | 관제 화면에 넘긴 결과 |
|---|---|---|
| PPE | YOLOv11n으로 사람·안전모·안전조끼 검출 후 위치 관계 확인 | 착용 상태와 위반 유형 |
| 넘어짐 | YOLO Pose 17개 키포인트와 몸통 기울기·박스 비율 결합 | 넘어짐 후보와 지속 상태 |
| 얼굴 | 112×112 정렬 → AdaFace 512차원 임베딩 → FAISS 검색 | 작업자 후보와 유사도 |
| 통합 | 카메라별 결과를 사람 박스에 연결하고 중복 제거 | 한 사람당 한 번의 위험 이벤트 |
이 과정에서 “모델 정확도가 높으면 시스템도 정확하다”는 생각이 틀렸다는 것을 배웠다. 안전모 박스와 사람 박스가 둘 다 맞아도 서로 다른 사람에게 연결되면 결과는 틀린다. 얼굴 후보가 맞아도 같은 사람이 두 박스로 잡히면 인원 수와 위반 건수가 함께 부풀었다.
3. 첫 번째 난관: 검출 결과는 아직 ‘사건’이 아니었다
안전모가 화면에 있다고 착용한 것은 아니다
초기에는 사람과 안전모가 같은 화면에 잡히면 착용으로 판단하기 쉬웠다. 여러 사람이 붙어 서면 옆 사람의 안전모가 잘못 연결됐다. 이를 줄이기 위해 사람 상단 영역과 안전모 중심점의 거리를 확인하고, 한 PPE 박스가 두 사람에게 중복 배정되지 않게 사용 여부를 추적했다. 같은 클래스에서 겹친 박스는 IoU와 신뢰도로 한 번 더 정리했다.
모델을 학습시키기 전 데이터 정제부터 다시 했다. 최종 발표자료 기준으로 AI-Hub 공사현장 안전장비 이미지 약 200만 장에서 학습에 필요하지 않은 클래스를 걷어 내고, 잘못된 바운딩 박스와 노이즈 데이터를 수정해 약 60만 장을 학습 데이터로 남겼다. 모델 구조를 바꾸는 일보다 라벨의 의미와 품질을 맞추는 데 더 많은 손이 갔다.

PPE 클래스별 혼동행렬과 실제 테스트 화면. 검출 이후 사람과 장비의 위치 관계를 다시 판정했다.
30프레임의 위반을 30개의 알림으로 보내면 안 됐다
실시간 영상에서는 같은 미착용 상태가 매 프레임 반복된다. 그대로 저장하면 짧은 장면 하나가 수십 건의 위반으로 쌓이고 대시보드도 계속 깜빡였다. 백엔드에서 같은 작업자·위반 유형·카메라·시간을 기준으로 중복을 묶고, PPE 알림에는 30초 쿨다운을 적용했다. 반면 넘어짐처럼 즉시 확인해야 하는 위험은 쿨다운 대상에서 제외했다.
# 실제 프로젝트의 알림 정책 핵심
ALERT_COOLDOWN_SECONDS = 30.0
CRITICAL_VIOLATIONS = ["넘어짐", "사고", "FALL", "ACCIDENT"]
# PPE는 반복 알림을 묶고,
# 넘어짐·사고는 즉시 알린다.
이 단계에서 가장 크게 배운 점
객체 검출의 출력은 관제 사건이 아니다. 사람 연결, 지속 시간, 중복 제거, 알림 우선순위까지 통과해야 비로소 운영자가 확인할 수 있는 한 건의 사건이 된다.
4. 얼굴 인식은 모델보다 데이터 파이프라인이 길었다
얼굴 식별은 사진 한 장을 모델에 넣고 이름을 받는 기능으로 끝나지 않았다. 작업자별 원본 사진을 수집하고, 현장에서 안전모나 마스크를 착용한 상황을 반영한 데이터를 준비한 뒤, 얼굴을 112×112 크기로 정렬했다. AdaFace IR50에서 512차원 임베딩을 만들고 FAISS 인덱스에서 후보를 찾았다.

출입 사진 수집부터 PPE 합성·증강, AdaFace 임베딩과 FAISS 검색까지 이어지는 얼굴 식별 파이프라인
가장 가까운 벡터가 나왔다고 바로 이름을 확정하지 않았다. 최소 유사도, 2·3위 후보와의 차이, 얼굴 크기와 품질을 함께 확인했다. 같은 이름이 여러 사람 박스에 연결되면 유사도가 가장 높은 결과만 남겼다. 이 로직이 없으면 한 사람이 두 명으로 집계되거나 다른 사람의 PPE 위반이 잘못 붙을 수 있었다.
최종 발표자료의 얼굴 인식 수치는 데모 환경에서 측정한 결과다. 조명, 카메라 거리, 가림, 등록 사진 품질이 바뀌면 성능도 달라진다. 이 프로젝트에서는 모델 숫자 자체보다 등록 → 정렬 → 임베딩 → 검색 → 거절까지 재현 가능한 흐름을 완성한 것이 더 큰 성과였다.
5. 넘어짐 감지는 한 장의 자세만 보면 틀렸다
초기에는 사람 박스의 가로가 세로보다 넓으면 넘어짐으로 판단하는 방법을 시도했다. 하지만 작업자가 신발 끈을 묶거나 의자에 기대기만 해도 박스 비율이 바뀌었다. 책상과 의자를 사람으로 잘못 잡는 경우도 있었다.
최종 구현에서는 YOLO Pose가 추출한 17개 키포인트를 사용했다. 어깨와 골반을 기준으로 몸통 기울기를 계산하고, 바운딩 박스의 가로·세로 비율과 일정 시간 지속 여부를 함께 봤다. 몸통 기울기 45도, 박스 비율 1.5 같은 값은 출발점일 뿐이었다. 카메라 각도와 작업 환경에 따라 임계값을 다시 맞춰야 했다.

정상·숙임·넘어짐 자세를 비교해 몸통 기울기와 바운딩 박스 비율을 함께 확인한 테스트
여기서도 핵심은 “넘어진 모양” 한 장이 아니라 정상 자세에서 위험 자세로 바뀌고 그 상태가 이어지는 과정이었다. 교육용 프로토타입에서는 규칙 기반으로 연결했지만, 현장 적용 단계라면 다양한 정상 작업 자세와 실제 낙상 구간을 포함한 별도 검증이 필요하다.
6. 모델보다 통합에서 더 많은 오류가 났다
백엔드는 FastAPI가 아니라 aiohttp를 사용했다. 카메라별 프레임을 처리하고, 가공된 영상은 MJPEG로, 이벤트 데이터는 WebSocket으로 React 대시보드에 보냈다. 위반 이력은 MongoDB와 로컬 JSON 저장 흐름을 함께 지원했다.
통합 과정에서 기억에 남는 문제는 연결 중복이었다. React 개발 모드의 StrictMode 때문에 useEffect가 두 번 실행되면서 WebSocket과 카메라 접근도 중복됐다. 프런트엔드가 카메라를 직접 열던 흐름을 제거하고 백엔드의 MJPEG 스트림을 단일 입력으로 사용했다. 동일한 WebSocket 페이로드는 반복 전송하지 않게 해 화면 깜빡임도 줄였다.
macOS에서는 Apple MPS, Windows에서는 CUDA·TensorRT를 사용하도록 실행 경로도 나뉘었다. 한 플랫폼에서만 돌아가는 데모가 아니라 팀원이 각자 다른 환경에서 같은 흐름을 확인할 수 있도록 시작·종료 스크립트와 모델 경로를 정리했다. 이 작업을 하면서 실행 환경 역시 기능의 일부라는 사실을 체감했다.
7. 완성한 것과 아직 부족한 것
최종 발표의 데모 환경에서는 30 FPS 처리, 얼굴 인식 97.19%, PPE 감지 95%, 위험행동 감지 93%를 기록했고, TensorRT 적용 후 추론 속도가 2–3배 향상됐다. 이 수치는 발표 당시 카메라·GPU·평가 데이터에서 측정한 결과다. 실제 현장 성능으로 일반화하기보다, 세 모델과 관제 화면을 실시간으로 끝까지 연결했다는 결과로 보는 편이 정확하다.
| 완성한 것 | 아직 부족한 것 |
|---|---|
| PPE·Pose·Face 결과를 하나의 실시간 관제 흐름으로 통합 | 실제 산업현장의 야간·역광·분진·가림 데이터 검증 |
| 작업자별 위반 이벤트, 안전 점수, 일·주간 리포트 연결 | 모델 임계값과 사건 규칙을 환경별로 관리하는 설정 구조 |
| 중복 박스·중복 이벤트·중복 연결을 여러 단계에서 억제 | 큰 백엔드 파일을 추론·이벤트·저장·스트림 서비스로 분리 |
| macOS MPS와 Windows CUDA·TensorRT 실행 경로 준비 | 얼굴 원본·임베딩의 보유 기간, 권한, 삭제 검증 고도화 |
프로젝트를 끝내고 나니 가장 아쉬운 부분은 기능 수가 아니라 검증 방식이었다. 발표 데모에서는 실시간 처리와 세 모델의 연결을 보여 줬지만, 실제 현장에 적용하려면 카메라별 오탐·미탐, 시간당 반복 경보, 장애 복구 시간, 개인정보 삭제 여부까지 확인해야 한다.
다시 만든다면 카메라 한 대와 안전모 미착용 한 종류만 먼저 끝까지 연결할 것이다. 사람과 PPE의 연결 규칙, 한 사건의 시작과 종료, 저장 키, 운영자의 확인 결과까지 안정화한 뒤 얼굴 식별과 넘어짐 감지를 추가하는 편이 더 빠르고 검증하기 쉽다.
- 모델 출력과 실제 업무 사건 사이에는 연결·지속·중복 제거 규칙이 필요하다.
- 얼굴 인식은 모델보다 데이터 수집·정렬·임베딩·검색·거절 과정이 더 중요하다.
- 실시간 AI 서비스는 추론 속도뿐 아니라 스트림, 저장, 화면, 복구가 함께 작동해야 한다.
프로젝트 전체 구조와 실행 방법은 AIVIS GitHub 저장소에 정리해 두었다. 대용량 모델, 얼굴 임베딩과 개인정보 데이터는 저장소에 포함하지 않았다.
이 글은 실제 AIVIS 저장소와 2025년 최종 발표자료를 기준으로 작성했다. 발표 성능은 당시 데모 환경의 결과이며 실제 산업현장 성능을 보장하지 않는다. 실제 적용 전에는 현장 안전·개인정보·보안 담당자의 별도 검토가 필요하다.
'실전 개발 노트 > 프로젝트·회고' 카테고리의 다른 글
| 멍글멍글 프로젝트 회고: YOLO·EfficientNet·RAG로 만든 유기견 입양 매칭 (1) | 2026.08.01 |
|---|---|
| React로 브라우저 OS를 만들었다: PocketDesk OS 창 관리자·파일 시스템 개발기 (0) | 2026.07.31 |