PPE 착용 판정 — CCTV 이미지에서 안전모·조끼 착용을 사람 단위로 판정한 연구 결과
YOLO 계열 모델로 안전모와 안전조끼 착용 여부를 사람 단위로 판정하고, 판단이 어려운 경우는 "판정 불가"로 따로 표시하는 PoC를 만들어 봤습니다.
개발 개요
공사 현장 같은 이미지에서 사람마다 안전모와 안전조끼를 착용했는지 판정해 보여 주는 화면을 만들었습니다. 결과는 착용, 미착용, 판정 불가 세 가지이고, 확신이 없으면 미착용으로 단정하지 않고 판정 불가로 둡니다. 샘플 9장과 한계 사례 4장을 나눠 두었고 이미지를 올려 분석할 수도 있습니다.
사진 속 사람 수를 먼저 맞추고, 그 사람 수 안에서 안전모와 조끼를 따로 세는 것이 이 화면의 기본 규칙입니다. 한계 사례를 일부러 따로 보여 주는 것은 잘 되는 사진만 보여 주는 데모가 되지 않게 하려는 것입니다.
AI를 활용한 설계 및 구현 과정
Claude Code에 조사, 요구사항, 설계, 구현, 테스트, 리뷰 역할을 나눠 맡기고, 단계마다 검토를 통과해야 넘어가게 했습니다. 화면에 나오는 결과는 직접 열어 보고 맞는지 판단해서 반복 수정을 진행했습니다. 또 "착용 + 미착용 수는 전체 사람 수를 넘으면 안 된다" 등 규칙을 정했고, 규칙을 어기는 결과는 화면에 내보내지 않도록 코드에서 막았습니다. 화면의 숫자가 서로 맞지 않으면 현장에서 신뢰를 잃기 때문입니다.
사용한 기술
- 두 모델의 역할 분리: 사람 상자는 COCO 사전학습 사람 검출기(YOLO 계열)가 만들고, 안전모·조끼 착용은 별도로 미세조정한 YOLO 계열 PPE 모델이 판정합니다. 두 결과를 사람 상자 안에서 맞춰 사람마다 착용·미착용·판정 불가를 정합니다.
- 사람 검출 정확도: 같은 이미지를 두 가지 해상도(640, 1280)로 돌려 결과를 합치고 겹친 상자를 정리해 작은 사람이 빠지는 것을 줄였습니다. RF-DETR 같은 더 강한 모델도 후보로 조사해 비교했지만 제품에는 넣지 않았습니다.
- 규칙 기반 판정과 접힘: 상태 판정은 신뢰도 임계값에 따른 규칙으로 정했습니다. 사람 신뢰도가 낮거나 가장자리에 걸치고 안전모·조끼 예측이 없는 후보는 숫자 규칙으로 "사람이 아닐 수 있는 후보"로 접습니다. 사람 단위 불변식(착용+미착용 ≤ 전체 사람)을 코드로 검사해 어기면 결과를 내보내지 않습니다.
- 평가 방식: 시험 이미지에 직접 정답을 확인하여 비교했고, 판정이 불가능한 영역의 라벨을 따로 표시했습니다. 평가 코드와 핵심 판정 코드는 해시로 고정해 중간에 바뀌지 않게 했습니다.
- 데모: Streamlit 화면이고 업로드는 형식·크기·해상도를 검사합니다. 얼굴은 검출 뒤 블러를 씌우고, 동시에 분석이 들어와도 결과가 섞이지 않게 분석 구간에 잠금을 걸었습니다.
- 검증 도구: pytest(1,200건 가까이, 실제 모델을 돌리는 시험 포함)와 Streamlit AppTest, 브라우저 자동화(Playwright)를 썼습니다.
구현 결과와 사용법
화면은 "PPE 착용 판정" 하나입니다. 맨 위에서 "샘플 / 한계 사례 / 업로드"를 고르고, 샘플과 한계 사례는 사진을 고르거나 이전·다음 버튼을 누르는 순간 바로 분석합니다. 업로드만 파일을 올린 뒤 "분석 실행"을 눌러 분석합니다. 얼굴은 기본으로 블러 처리하고, 배포 전 개인 시연이라 끄는 옵션도 넣었습니다(내려받는 이미지는 항상 블러본입니다).
분석이 끝나면 맨 위에 사람 수, 미착용으로 판정, 착용, 판정 불가 개수가 나오고 그 아래에 안전모와 조끼별 상태 막대가 있습니다. 막대 바로 밑에 빨간색은 미착용, 초록색은 착용, 회색은 판정 불가라는 범례를 두었습니다. 오른쪽에는 사람별 근거 카드가 나오며, 신뢰도순·미착용 우선·위치순으로 정렬하고 미착용·착용·판정 불가로 걸러 볼 수 있습니다. 카드의 위치 버튼을 누르면 이미지에서 그 사람을 강조합니다.
이미지 위에는 "미착용 판정만 / 착용 판정만 / 모든 상자 / 상자 없음" 중 보고 싶은 상자만 표시할 수 있고, 꼬리표와 신뢰도 숫자도 켜고 끌 수 있습니다. 이미지는 휠·끌기·버튼으로 확대해 보고, 결과는 이미지·CSV·JSON으로 내려받습니다. 이 표시 옵션은 보여 주는 방식만 바꾸고 판정과 사람 수 규칙은 건드리지 않습니다. 안내문·배지·탭 같은 보조 서술은 모두 빼고 데이터와 컨트롤만 남겼습니다.
개선 과정
처음 결과는 직접 확인해 사람 수와 맞지 않는 이미지가 많았습니다. 울타리를 사람으로 세거나, 한 사람 뒤통수만 찍힌 행인을 어떻게 처리할지 정하지 못했습니다. 그래서 정확도를 높이는 개선 작업을 진행 했습니다.
개선 | 내용 |
|---|---|
사람 상자 | 안전모·조끼 모델이 만든 상자 대신 COCO 사람 검출기를 써서 사람 상자를 만듭니다. 개발셋 62장 중 57장에서 사람이 과다하게 세어지던 것을 줄였습니다. |
사람 아닐 가능성 | 신뢰도가 낮거나 가장자리에 걸치고 안전모·조끼 예측이 없는 후보는 "사람이 아닐 수 있는 후보"로 접어 사람 수에서 제외합니다. |
판정 불가 구분 | 단정하기 어려운 사람은 미착용이 아니라 판정 불가로 둡니다. |
한계 사례 분리 | 결과가 흔들리는 샘플은 "한계 사례"로 따로 나눠 샘플과 구분했습니다. |
검증 결과와 한계
- 시연 샘플 14장을 실제 모델로 돌렸을 때 13장에서 화면의 사람 수가 정답 사람 수와 같았고, 사람이 없는 샘플 1장은 결과를 표시하지 않았습니다. 사람 수 합계 규칙을 어긴 결과는 0건이었습니다.
- 개발셋 62장 기준 사람 수 일치는 처음 3장에서 53장까지 올랐습니다. 다만 이 수치는 낙관적입니다. 같은 개발셋으로 방법을 고르고 정답 기준을 정했기 때문입니다.
- 잘 안 보이는 사람(차 유리 너머, 가려진 사람)은 조용히 넘기므로 실제 현장에서는 놓칠 수 있고, 이 결과를 안전 판단에 쓰면 안 됩니다.
- 샘플당 처리 시간은 약 3~5초(CPU)입니다.
개발 경험과 주의
- 좋았던 점은 코드 리뷰도 실제 결함(카드 생략 수 오류, 업로드 결과 잔존, 동시 분석 충돌)을 찾아 고쳤다는 것입니다. 한 차례 FAIL(83점)을 받은 뒤 수정해 PASS(97점)를 받았습니다.
- 아쉬운 점은 현재의 정답 기준은 낙관적인 수치가 될 수 있다는 것입니다.
- 주의할 점은 사용한 YOLO 가중치가 AGPL-3.0이라 서비스로 내놓기 전에 라이선스 검토가 필요하다는 것입니다.
- 처음부터 시연이 편리한 웹 개발을 고려할 수 있었지만, 현재는 일단 웹 전환 보류한 상태입니다.