본문 바로가기
Project/낙상

[낙상 감지 프로젝트] 2025년 2월 15일 (토)

by 바다의 공간 2025. 2. 15.

https://so-fast.tistory.com/entry/%EB%82%99%EC%83%81-%EA%B0%90%EC%A7%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2024%EB%85%84-12%EC%9B%94-31%EC%9D%BC%ED%99%94

 

[낙상 감지 프로젝트] 2024년 12월 31일(화)

지난 회의..https://so-fast.tistory.com/entry/%EB%82%99%EC%83%81-%EA%B0%90%EC%A7%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-2024%EB%85%84-12%EC%9B%94-20%EC%9D%BC%EA%B8%88 전체 요약 12.31(화)-  위 3가지 클래스 정의를 코드로 구현 후

so-fast.tistory.com


지난 회의 :

→ 양 어깨의 중앙값 위치를 원으로 시각화하였는데, 제대로 된 위치에 찍히지 않았다.

→ 시각화 부분 코드에서 오류가 생긴 건지, 중앙값 위치 계산 부분에서 오류가 생긴 건지 확인이 필요하다.

 

분명 어깨값으로 중앙값을 했는데, 어깨 양 가운데가아닌 다른 위치가 찍혀서 다시 점검해봐야할것같다.

(현업에서도 이런 일이 빈번하게 일어나는지 궁금하다)

 

위 문제를 해결해보려고한다.

생각하는 과정은 

1. 시각화를 통해서 중심좌표가 어디로 표시가 되어있는지 확인한다.

2. 낙상임에도 불구하고 낙상이라고 감지하지 못하는(정상판별) 클래스를 재확인해본다

3. 조건에 맞게 임계값 재수정

 

>> 결론은 2번에서 조금헤맸기때문에 다음 과제로 남겨두었다.


 

전체 요약

 

2월 15일(토)


1. 시각화를 통해서 중심좌표가 어디로 표시가 되어있는지 확인한다.

 

# 바운딩 박스와 상태 텍스트 그리기
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, f"ID: {track_id} {state}", (x1, y1 - 10),
            cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
cv2.circle(frame, (int(center_x), int(center_y)), 10, color, -1)

# 현재 중심 좌표 저장
prev_centers = center_x, center_y

시각화를 해보니 중심좌표가 제대로 표시가 되지 않았다.

 

좌측상단에 점이 찍혀있는걸 볼 수 있었다. circle의 식을 보니

cv2.circle(frame, (int(center_x * (x2 - x1) + x1), int(center_y * (y2 - y1) + y1)), 10, color, -1)

 

영상의 가로,세로의 비율에 대한 정규화가 되지 않았기 때문에 계속 점 위치가 제대로 찍히지 않았던 것 같다.

 

그래서 정규화를 하기 위해서 frame.shape[0] = HEIGHT(세로), frame.shape[1] = WEITH(가로)로 다시 찍어보았다.

# 바운딩 박스와 상태 텍스트 그리기
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, f"ID: {track_id} {state}", (x1, y1 - 10),
			cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
cv2.circle(frame, (int(center_x * (frame.shape[0])), int(center_y * (frame.shape[1]))), 10, color, -1)

시각화를 해보니 중심좌표가  또 제대로 표시가 되지 않았다.


그래도 좀 더 가까이 온것을 확인하고 circle이 문제였다는것을 확실히 알게되었는데

FRAME을 어떻게 읽었는지 정확하게 확인하기 위해서 찍어보았다.

import cv2
import numpy as np
from ultralytics import YOLO

# YOLO-Pose 모델 로드
yolo_model = YOLO('yolov8n-pose.pt')

# 동영상 경로 설정
input_video = '/content/00015_H_A_SY_C5.mp4'
output_video = '/content/output_yolo_pose_0.02.mp4'

# 동영상 읽기 및 저장 설정
cap = cv2.VideoCapture(input_video)
out = cv2.VideoWriter(
    output_video,
    cv2.VideoWriter_fourcc(*'mp4v'),
    int(cap.get(cv2.CAP_PROP_FPS)),
    (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
)

# 변수 초기화
prev_centers = {}  # 이전 프레임 중심 좌표 저장
danger_frame_count = 0  # 위험 상태 프레임수 저장
fall_detected = set()  # 낙상 상태로 기록된 객체 ID
frame_idx = 0

# 속도 임계값 설정
danger_speed_threshold = 0.02  # 위험 속도 임계값
normal_speed_threshold = 0.01 # 정상 속도 임계값
danger_frame_threshold = 20  # 낙상 판단 프레임 수 (FPS 기준)

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    print(frame.shape)

 

 

 

circle부분중에서 720,1280,3이 나온것을 보니,  WIDTH와 HEIGHT를 바꿔서 적은것을 알게되었다.

# 바운딩 박스와 상태 텍스트 그리기
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, f"ID: {track_id} {state}", (x1, y1 - 10),
            cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
cv2.circle(frame, (int(center_x * (frame.shape[1])), int(center_y * (frame.shape[0]))), 10, color, -1)

 

위 코드대로 frame.shape를 width, height에 맞춰서 작성하니 양쪽 어깨에 대한 중심값이 잘 표시되었다.

 


2. 낙상임에도 불구하고 낙상이라고 감지하지 못하는(정상판별) 클래스를 재확인해본다

위 사진에도 보이듯 넘어짐(낙상)상태에도 불구하고 NORMAL CLASS로 구별되는 이유를 좀 보려고 한다.

# 상태 판별
if speed < normal_speed_threshold:  # 정상
    state = "Normal"
    color = (0, 255, 0)  # 초록색
    danger_frame_count = 0  # 위험 상태 프레임 초기화
elif speed > danger_speed_threshold:  # 위험
    state = "Danger"
    color = (255, 0, 0)  # 파란색
    danger_frame_count += 1  # 위험 상태 프레임 카운트
else:
    state = "Danger"
    danger_frame_count += 1  # 위험 상태 프레임 카운트
    color = (255, 0, 0)  # 파란색

# 위험 상태가 일정 시간 지속되면 낙상 처리
if danger_frame_count >= danger_frame_threshold:
    state = "Fall"
    color = (0, 0, 255)  # 빨간색

 

위의 지속상태보다 아래에 있는 속도 임계값 설정을 먼저 해보았다.

 


속도 임계값 설정

 

임계값에 대한 정의를 변경했는데

낙상, 위험에 대한 부분을 0.02로 잡고 그 아래부분은 모두 정상으로 보기로 했다.

그 뒤로 0.02보다 speed가 움직임이 없는 상태가 된다고 하면 <낙상>

speed가 0.01이상의 움직인다면 <정상>으로 보기로 했다.

 

그래서 normal_speed_threshold를 -> move_speed_threshold로 변경하고 

그 부분에 대한 정의를 정상적인 움직임 이라고 정의했다.

 

# 속도 임계값 설정
danger_speed_threshold = 0.02  # 낙상위험 속도 임계값
move_speed_threshold = 0.01 # 낙상->정상 속도 임계값
danger_frame_threshold = 20  # 낙상 판단 프레임 수 (FPS 기준)

 


속도 임계값 설정

            # 상태 판별
            if speed < move_speed_threshold:  # 정상
                state = "Normal"
                color = (0, 255, 0)  # 초록색
                danger_frame_count = 0  # 위험 상태 프레임 초기화
            elif speed > danger_speed_threshold:  # 위험
                state = "Danger"
                color = (255, 0, 0)  # 파란색
                danger_frame_count += 1  # 위험 상태 프레임 카운트
            else:
                state = "Danger"
                danger_frame_count += 1  # 위험 상태 프레임 카운트
                color = (255, 0, 0)  # 파란색

            # 위험 상태가 일정 시간 지속되면 낙상 처리
            if danger_frame_count >= danger_frame_threshold:
                state = "Fall"
                color = (0, 0, 255)  # 빨간색

 

 

 

지금 코드의 문제상황은 if문에서 먼저 normal로 감지되어버리면 elfi, else로 넘어가지 않는다는 점입니다.

그래서 낙상 감지를 하더라도 다시 normal로 넘어가는 문제점이 해결되지 않고 있다.

 

그래서 이 부분을 조금 바꿔봐야겠다고 생각했다.

 


# 속도 임계값 설정
danger_speed_threshold = 0.02  # 낙상위험 속도 임계값
move_speed_threshold = 0.02 # 낙상->정상 속도 임계값
danger_frame_threshold = 10  # 낙상 판단 프레임 수 (FPS 기준)

위 처럼 임계값을 바꾸어보아도 사실 영상에서 변하는건 전혀없었다.

 

이 부분을 해결하기 위해서 낙상 처리 속도의 과정도 바꾸어보았지만 아직 문제점을 찾지못해서 다음과제에 이어서 해보려고 한다.

# # 위험 상태가 일정 시간 지속되면 낙상 처리
            if danger_frame_count < danger_frame_threshold:
                # state = "Fall"
                # color = (0, 0, 255)  # 빨간색

                # 상태 판별
                if speed <= danger_speed_threshold:  # 정상
                    state = "Normal"
                    color = (0, 255, 0)  # 초록색
                    danger_frame_count = 0  # 위험 상태 프레임 초기화
                elif speed > danger_speed_threshold:  # 위험
                    state = "Danger"
                    color = (255, 0, 0)  # 파란색
                    danger_frame_count += 1  # 위험 상태 프레임 카운트
            elif danger_frame_count >= danger_frame_threshold and speed < move_speed_threshold:
                state = "Fall"
                color = (0, 0, 255)  # 빨간색