일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 가상환경
- 멀티모달
- 서보모터
- 뉴럴링크
- 시간적 일관성
- 아두이노
- ubuntu
- TRANSFORMER
- 딥마인드
- 오픈AI
- AI 기술
- PYTHON
- 메타
- 티스토리챌린지
- 생성형 AI
- 확산 모델
- 우분투
- 이미지 편집
- AI
- ChatGPT
- LLM
- OpenAI
- tts
- javascript
- 일론 머스크
- LORA
- 오블완
- ControlNet
- 인공지능
- 트랜스포머
- Today
- Total
AI 탐구노트
Yolov11-pose를 이용한 폭력 행위 감지 (Violence Detection) 본문
1.서론 : 폭력 상황에도 '골든타임'이 있다
요즘 뉴스에서는 조직 폭력, 학교 폭력, 직장 내 폭력 등등 다양한 폭력 사건이 자주 보도됩니다. 실제로도 학교, 경기장, 심지어 일상적인 거리에서도 예기치 않은 폭행이 발생하고 있죠. 이런 사건을 막기 위해 기술은 어떻게 발전하고 있을까요? 오늘은 폭행 감지와 신속한 대처가 시민의 안전을 어떻게 보장하는지, 그리고 그 중심에 있는 지능형 CCTV에 대해 짧막하게 이야기해 보겠습니다.
폭력의 확산, 초기 대응의 필요성
폭력은 단순히 개인 간의 문제로 끝나지 않습니다. 감정적인 상승작용으로 인해 처음에는 작은 다툼이더라도 주변으로 퍼지며 심각한 폭력으로 이어지기 쉽습니다. 특히 다수가 밀집한 장소에서는 작은 충돌이 대규모 사건으로 번질 가능성이 높습니다. 따라서 폭력이 발생한 초기 순간에 이를 감지하고 신속히 대응하는 것이 매우 중요합니다. 여기에도 '골든타임'이라는 것이 있는 셈이죠.
이때 등장하는 것이 바로 지능형 CCTV입니다. 사람의 눈으로 모든 상황을 실시간으로 감지하는 것은 불가능하지만, AI 기술을 적용한 CCTV는 폭력 상황을 감지해 즉각 알림을 보내거나 대처할 수 있도록 도와줍니다. 현재 국내 대다수의 지자체에서는 이미 지능형CCTV를 갖추고 시민들의 안전 보장을 위해 사용하고 있습니다. 다만 폭력행위 감지까지 온전히 적용한 곳이 아직은 그리 많지 않을 것 같긴 합니다. 우선은 우범지역 근처 정도로 한정해서 사용하고 있지 않을까 싶네요.
지능형 CCTV와 실시간 폭력 감지
앞서 언급한 것처럼 최근 많은 지역에서 지능형 CCTV가 설치되고 있습니다. 학교, 우범지대, 스포츠 경기장처럼 폭력이 발생할 가능성이 높은 장소가 주요 대상입니다. 이 시스템은 단순히 영상을 녹화하는 데 그치지 않고, 영상 분석 기술을 통해 폭력 행동을 실시간으로 탐지합니다.
국내에 영상보안 솔루션을 공급하고 있는 곳들이 많이 있습니다. 에스원도 그 가운데 하나인데, 최근 '지능형 학교 안전 솔루션'을 내놓았다고 합니다. 이 시스템은 학교 내 CCTV 영상을 분석해 학생 간의 다툼이나 폭력 상황을 인지하고, 관리자에게 자동으로 통보하여 관리자나 교사는 신속히 현장에 출동해 문제를 해결할 수 있습니다. 학생들에게 더 안전한 환경을 제공하기 위해 중요한 역할을 하는 셈입니다.
폭력 예방의 새로운 기준: 기술과 시민의 협력
하지만 기술만으로는 모든 문제를 해결할 수 없습니다. CCTV가 아무리 뛰어난 기능을 갖췄더라도, 결국 이를 활용하는 사람들의 관심과 협력이 필요합니다. 폭력 징후를 인지한 후 빠르게 행동하는 관리자, 그리고 주변 시민들의 적극적인 신고가 더해져야 진정한 안전망이 완성됩니다. 한마디로 '기술과 사람'이 함께 콜라보를 해야 효과를 거둘 수 있다는 말입니다. 지능형 CCTV는 감지와 경고의 역할을 맡지만, 이 경고를 실질적인 대응으로 연결하는 것은 어디까지나 사람의 몫이니까요.
2.본론 : AI를 이용한 폭력 행위 감지 테스트
이 글에서는 AI 기술을 이용해 다소 품질이 떨어지겠지만 폭력행위가 발생한 것을 감지하는 코드를 만들어 테스트 해 보겠습니다.
2.1.구현 시나리오
CCTV 영상을 Yolov11-pose로 분석해서, 사람 간의 거리, 행동, 자세를 이용해 폭력행위라고 간주할만한 것을 찾는 방식으로 구현합니다. 이런 방식 말고도 장면 각각에 대해 분류를 하거나 CNN+LSTM 등의 방식으로 동작에 시간적인 변이를 학습하도록 하는 방식도 있을 것입니다. 하지만, 여기서는 가장 간단한 방식으로 구현해 봅니다.
2.2.필요 소프트웨어 또는 기술
- Python
- Yolov11n-pose : 객체감지 및 자세예측 모델
- OpenCV
테스트 용 영상 데이터는 아래 Youtube 영상을 이용하였습니다. (다운받아 fight_03.mp4로 이름 변경)
2.3.시스템 환경 구성
# 가상 환경 생성
$ conda create -n yolov11 python=3.11
$ conda activate yolov11
# 필수 패키지 설치
$ pip install ultralytics, numpy, opencv-python
2.4.코드 생성
ChatGPT에게 전달한 코드 생성 요구사항은 다음과 같습니다.
- python, Opencv 이용해서 개발하며 입력은 video 영상 (파일)입니다.
- 폭력이 감지되면 일정 시간동안 화면에 경고 (번쩍임, 쓰러짐 감지 텍스트)를 표시합니다.
- 폭력은 항상 상대가 있어야 하며 공격자와 상대방은 각각 bbox와 pose keypoint 연결 등을 색을 달리해서 구분해 주세요.
- 감지된 사람은 바운딩 박스와 함께 skeleton (keypoint)도 함께 표시합니다.
- 공격자는 붉은색으로 상대는 파란색으로 해 주세요. 감지가 되지 않으면 그냥 초록색으로 표시하면 됩니다.
폭력 판정에 대한 조건은 다음과 같이 주었습니다.
- 공격자와 상대방은 동일 선상에 있는지 확인 후 그렇지 않으면 화면 상에서 겹쳐도 공격으로 간주하지 않습니다.
- 공격자의 주먹이나 발 등이 상대의 몸 영역 근처에 침범해야 합니다.
- 주먹을 상대를 향해 뻗거나 발로 상대를 차는 행동만 해당하도록 한정합니다.
- 쓰다듬는 행위와 구분해야 합니다. 예를 들어 공격자의 손 움직임의 속도가 급격히 바뀐다거나 상대방이 뒤로 피하는 행동을 하거나 등등
- 최근 3초 이내에 15번 이상이 감지되었을 때를 폭력감지로 판정하고 경로를 줍니다.
이렇게 해서 생성된 코드입니다.
import cv2
import time
import numpy as np
from ultralytics import YOLO
from collections import deque
# YOLO 모델 로드
model = YOLO("yolo11n-pose.pt")
# 비디오 캡처 설정
cap = cv2.VideoCapture("fight_03.mp4")
# 폭력 감지 경고 설정
alert_duration = 0.5 # seconds
last_alert_time = 0
violence_detected_time_window = 3 # 감지 시간 범위. 예: 최근 3초 내
violence_detected_count = 15 # 0.1초 단위로 체크해서 감지된 횟수. 이 횟수 이상이면 폭력행위 상태로 간주
# 움직임 추적을 위한 버퍼 설정
motion_buffer_size = 5
motion_history = {} # 각 keypoint의 이동 기록을 저장할 딕셔너리
# 최근 폭력 감지 기록 (타임스탬프 저장)
violence_timestamps = deque(maxlen=30) # 0.1초 간격으로 최대 3초간 기록
# 윈도우 설정
cv2.namedWindow("Violence Detection", flags=cv2.WINDOW_AUTOSIZE)
paused = False
# 키보드 제어 함수
def handle_keyboard_input():
global paused
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
return False # 프로그램 종료
elif key == ord(' '):
paused = not paused # 일시 정지/재생 토글
elif key == 83: # 우측 방향키: 프레임 스킵
cap.grab()
return True
def calculate_distance(p1, p2):
return np.linalg.norm(np.array(p1[:2]) - np.array(p2[:2]))
def calculate_velocity(positions):
return calculate_distance(positions[-1], positions[-2]) if len(positions) > 1 else 0
def get_limb_angle(a, b, c):
ba, bc = np.array(a[:2]) - np.array(b[:2]), np.array(c[:2]) - np.array(b[:2])
return np.degrees(np.arccos(np.clip(np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)), -1.0, 1.0)))
def detect_motion(keypoints, person_id, motion_history, limb_indices, angle_threshold, velocity_threshold):
# 어깨를 토닥이거나 포옹하는 동작은 폭력으로 간주하지 않도록 처리
left_shoulder = keypoints[5]
right_shoulder = keypoints[6]
left_hand = keypoints[9]
right_hand = keypoints[10]
shoulder_distance = calculate_distance(left_shoulder, right_shoulder)
left_hand_to_shoulder_distance = calculate_distance(left_hand, right_shoulder)
right_hand_to_shoulder_distance = calculate_distance(right_hand, left_shoulder)
# 손이 어깨 근처에 있고, 속도가 낮을 때는 폭력으로 간주하지 않음
if (left_hand_to_shoulder_distance < shoulder_distance * 1.5 and calculate_velocity([left_hand]) < 10) or \
(right_hand_to_shoulder_distance < shoulder_distance * 1.5 and calculate_velocity([right_hand]) < 10):
return False
if person_id not in motion_history:
motion_history[person_id] = {limb: deque(maxlen=motion_buffer_size) for limb in ['right_hand', 'left_hand', 'right_foot', 'left_foot']}
limb_points = {
'right_hand': (keypoints[6], keypoints[8], keypoints[10]),
'left_hand': (keypoints[5], keypoints[7], keypoints[9]),
'right_foot': (keypoints[12], keypoints[14], keypoints[16]),
'left_foot': (keypoints[11], keypoints[13], keypoints[15])
}
for limb, (p1, p2, p3) in limb_points.items():
motion_history[person_id][limb].append(p3[:2])
angle = get_limb_angle(p1, p2, p3)
velocity = calculate_velocity(motion_history[person_id][limb])
if angle > angle_threshold and velocity > velocity_threshold:
return True
return False
# YOLOv8 Pose의 키포인트 구조에 맞는 연결 정의
POSE_CONNECTIONS = [
(0, 1), (1, 3), (0, 2), (2, 4), (5, 6), (5, 7), (7, 9), (6, 8), (8, 10),
(5, 11), (6, 12), (11, 12), (11, 13), (13, 15), (12, 14), (14, 16)
]
def draw_skeleton(frame, keypoints, color):
for p1_idx, p2_idx in POSE_CONNECTIONS:
if p1_idx < len(keypoints) and p2_idx < len(keypoints):
p1, p2 = keypoints[p1_idx], keypoints[p2_idx]
if p1[2] > 0.5 and p2[2] > 0.5:
x1, y1, x2, y2 = int(p1[0]), int(p1[1]), int(p2[0]), int(p2[1])
cv2.line(frame, (x1, y1), (x2, y2), color, 2)
cv2.circle(frame, (x1, y1), 4, color, -1)
cv2.circle(frame, (x2, y2), 4, color, -1)
while cap.isOpened():
if paused:
if not handle_keyboard_input():
break
continue
ret, frame = cap.read()
if not ret:
break
results = model(frame, verbose=False)[0]
violence_detected = False
attacker_ids, victim_ids = set(), set()
if results.boxes is not None and results.keypoints is not None:
boxes = results.boxes.xyxy.cpu().numpy()
keypoints = results.keypoints.data.cpu().numpy()
for i in range(len(boxes)):
for j in range(len(boxes)):
if i != j:
# 같은 선상에 있는지 확인 (y 좌표 차이가 특정 임계값 이하일 때만 폭력으로 간주)
if abs((boxes[i][1] + boxes[i][3]) / 2 - (boxes[j][1] + boxes[j][3]) / 2) > 50:
continue
if calculate_distance(keypoints[i][0], keypoints[j][0]) < 200:
if detect_motion(keypoints[i], i, motion_history, (6, 8, 10), 150, 15) or \
detect_motion(keypoints[i], i, motion_history, (12, 14, 16), 145, 20):
violence_timestamps.append(time.time())
# 최근 violence_detected_time_window 내에 0.1초 간격으로 violence_detected_count 회 이상 감지되었을 때 폭력으로 간주
if len(violence_timestamps) >= violence_detected_count and (violence_timestamps[-1] - violence_timestamps[0]) <= violence_detected_time_window:
violence_detected = True
last_alert_time = time.time()
attacker_ids.add(i)
victim_ids.add(j)
if violence_detected:
overlay = frame.copy()
cv2.putText(overlay, "Violence Detected!", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.rectangle(overlay, (0, 0), (frame.shape[1], frame.shape[0]), (0, 0, 255), -1)
frame = cv2.addWeighted(overlay, 0.3, frame, 0.7, 0)
for i, (box, keypoint) in enumerate(zip(boxes, keypoints)):
color = (0, 0, 255) if i in attacker_ids else (255, 0, 0) if i in victim_ids else (0, 255, 0)
x1, y1, x2, y2 = map(int, box)
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
draw_skeleton(frame, keypoint, color)
cv2.imshow("Violence Detection", frame)
if not handle_keyboard_input():
break
cap.release()
cv2.destroyAllWindows()
2.5.결과 확인
원본 영상에 폭력행위 감지 결과 영상은 다음과 같습니다. 생각보다 그럴 듯하게 됩니다.
2.6.제약사항
결과 영상을 보면 역시나 포옹하거나 토닥이거나 장난치는 경우와 같은 애매한 경우도 폭력행위로 감지하고 있습니다. 이는 예상된 것으로 사람의 자세 기반으로 간단한 몇 가지 룰을 적용해서 폭력행위를 판정하는 것이기 때문에 필연적으로 나올 수 밖에 없을 겁니다. 실제 전문 기업들이 제공하는 상용 서비스의 경우는 이런 케이스를 별도로 학습을 통해서 걸러내고 판정 품질을 높이고 하는 과정들이 있기 때문에 훨씬 더 정확하죠.
3.결론
CCTV 영상을 기반으로 폭력행위를 감지하는 기술은 순식간에 주변으로 확산될 수 있는 사태를 막을 수 있는 유용한 기술입니다. 조금이라도 빠른 시간에 감지하면 초기 대응이 가능해지고 피해를 획기적으로 줄일 수 있을테구요. 이렇게 거창한 것이 아니더라도 이 기술은 생활 주변에서 응용해서 재미난 것을 만들 수도 있을 것 같습니다. 하지만, 늘 그렇듯 저는 딱 요정도에서 마무리 하려고 합니다. :-)
'DIY 테스트' 카테고리의 다른 글
'픽셀 넘버' 게임 : 예능 속 퍼즐을 직접 만들어보자 (1) | 2024.11.25 |
---|---|
머리(Head) 자세 기반의 화면 주시 여부 감지 (0) | 2024.11.22 |
교통 CCTV 영상 기반의 자동차 속도 측정 (0) | 2024.11.19 |
한글 십자말풀이 게임 도구 만들기 (2) | 2024.11.16 |
십자말풀이 게임 생성 테스트 (16) | 2024.11.15 |