Pose Estimation_ / yolov8_pose

이장한·2023년 11월 9일
0

딥러닝

목록 보기
19/20

1.Pose Estimation

영상에서 사람의 관절을 검출하여 자세를 추정하는 컴퓨터 비전 분야이다.

추정하는 관절은 모델마다 조금씩 다르나 머리, 목, 어깨, 팔꿈치, 손, 엉

덩이, 무릅, 발목등을 찾는다.

스포츠 분야 자세분석, 이상행동 탐지등 사람의 자세를 응용한 서비스에 적

용될 수 있다.

2.관련 사이트

mediapie studio 등의 사이트에서 결과를 파악할 수 있다.

우리는 결과보다는 그 만드는 과정에 더 집중을 할 계획이다.

3.Direct Regression, Heatmap based

Pose estimation은 사람이 어떤 자세를 취하고 있는지 알 수 있도록 인체

의 관절들의 좌표를 찾는다. 이 추정할 좌표를 모델이 추정하는 방식으로

Direct Regression, Heatmap based가 있다.

전자는 회귀방식으로 각 관절의 x,y를 찾는 것이고,

후자는 keypoint를 픽셀단위로 keypoint가 위치할 가능성을 표현하는

heatmap으로 변환한 뒤에 학습하는 방식이다.

4.Top-Down, Bottom-up 방식

Top-Down 방식

이미지에서 사람을 먼저 탐지(Detection) 한 뒤 각 사람의 자세를 추정한다.

사진에 여러사람이 있는 경우 각각의 사람들을 탐지 한 뒤 한사람씩 pose

추정을 수행한다. (single person pose estimation)

이미지에 사람이 많은 경우 각각의 사람에 대해 순차적으로 알고리즘을 적용

하기 때문에 더 많은 시간을 소비한다.

Bottom-Up 방식

이미지에 포함된 사람들의 관절부분(Keypoints)를 먼저 찾아내고 연결해서

사람의 자세를 추정한다.

keypoints를 바로 찾기 때문에 정확도는 Top-Down방식보다 떨어지지만 속

도가 빠르다.

찾은 관절을 매칭할 수 있는 조합이 매우 많아 이를 적절하게 매칭하는데

정확도가 낮다.

아래 사진을 보면, 위가 top-down이고 아래가 bottom up이다.

5.yolov8_pose

5.1.Pose estimation

YOLO Pose는 사람의 주요 관절의 위치 keypoint 정보와 사람의 bounding box 를 추론한다.

5.2.Keypoints 속성

xy: 각 keypoint 들의 좌표. (batch, 17:keypoints, 2:좌표)
xyn: 각 keypoint 들의 normalize 된 좌표. (이미지 크기 대비 비율)
conf: 각 keypoint들의 confidence score. (batch, 17)

아래는 keypoints에 대한 그림이다.

5.3.코드 출력

idx2keypoints = ["코", "왼쪽 눈", "오른쪽 눈", "왼쪽 귀", "오른쪽 귀", "왼쪽 어깨", "오른쪽 어깨", "왼쪽 팔꿈치", "오른쪽 팔꿈치", "왼쪽 손목", "오른쪽 손목", "왼쪽 힙", "오른쪽 힙", "왼쪽 무릎", "오른쪽 무릎", "왼쪽 발목", "오른쪽 발목", ]
from ultralytics import YOLO
import cv2
import matplotlib.pyplot as plt
model = YOLO("models/yolov8n-pose.pt")
results = model("03_test_image_pose/pose.jpg",save=True,save_txt=True)

이런 식으로 그림을 출력한다.

아래 경로로 가면 결과 사진을 볼 수 있다.

그런데 이걸 보면, 예측한 것이 100프로 정확하지는 않다는 것을 알 수 있다.

그림을 직접 출력하려면 이 코드를 적으면 된다.

#그림을 출력하자.

cv2.imshow("frame",results[0].plot())
cv2.waitKey()
cv2.destroyAllWindows()

6.keypoint관련 조회
코드와 결과들을 순서대로 잘 보자.

##keypoint들을 조회

result = results[0] #첫번째 사람의 이미지 추론 결과.
boxes = result.boxes #bbox정보 - bbox 위치, 분류
keypoints = result.keypoints #keypoint 정보
type(boxes), type(keypoints)

(ultralytics.engine.results.Boxes, ultralytics.engine.results.Keypoints)

#keypoint의 결과

xy = keypoints.xy
print(type(xy),xy.shape) #[1:사람수 17:batch(신체의 각 point들),2:좌표]

<class 'torch.Tensor'> torch.Size([1, 17, 2])

keypoints.xyn #이미지 대비 비율

tensor([[[0.4636, 0.1596],
[0.4800, 0.1313],
[0.4478, 0.1388],
[0.5149, 0.1400],
[0.4347, 0.1595],
[0.5496, 0.3143],
[0.3891, 0.2840],
[0.5375, 0.5701],
[0.3069, 0.4282],
[0.5123, 0.7682],
[0.2182, 0.5528],
[0.4640, 0.6794],
[0.3553, 0.6581],
[0.5125, 0.9208],
[0.3530, 0.8627],
[0.0000, 0.0000],
[0.0000, 0.0000]]])

#각 keypoint의 확률

conf = keypoints.conf
print(type(conf),conf.shape) #[1:사람수,17:keypoint들]
conf #각 인덱스가 진짜로 그것일 확률을 보도록 하자.

<class 'torch.Tensor'> torch.Size([1, 17])
tensor([[0.9928, 0.9838, 0.9649, 0.9184, 0.6423, 0.9965, 0.9948, 0.9838, 0.9653, 0.9484, 0.9182, 0.9908, 0.9885, 0.7394, 0.6882, 0.1272, 0.1134]])

위의 확률들을 보다 보면, 대체적으로는 잘 찾아내지만,

일부 못 찾는 놈이 있다는 것을 파악하자.

7.부위별 정확도 출력하기

코드가 조금 어렵다. 그러니 코드에 적혀있는 부가 설명을 잘 보도록 하자.

for idx, (coord,conf)in enumerate(zip(keypoints.xy[0],keypoints.conf[0])):
    #[0]:첫번째 사람의 추론결과
    #반복문을 돌리면서 찍어보자.
    #x,y 좌표값을 나눠서 저장한다. ->tensor->float->int
    x_coord, y_coord = int(coord[0].item()),int(coord[1].item())
    conf = conf.item()*100 #item을 이용해서 값을 뽑아낸다. #%로 반환한다.
    print(f"{idx} - {idx2keypoints[idx]} - [{x_coord},{y_coord}] {conf:.3f}%")
    print(x_coord,y_coord)
    

0 - 코 - [259,60] 99.278%
259 60
1 - 왼쪽 눈 - [268,49] 98.375%
268 49
2 - 오른쪽 눈 - [250,52] 96.489%
250 52
3 - 왼쪽 귀 - [288,53] 91.844%
288 53
4 - 오른쪽 귀 - [243,60] 64.226%
243 60
5 - 왼쪽 어깨 - [307,119] 99.645%
307 119
6 - 오른쪽 어깨 - [217,107] 99.476%
217 107
7 - 왼쪽 팔꿈치 - [300,216] 98.384%
300 216
8 - 오른쪽 팔꿈치 - [171,162] 96.529%
171 162
9 - 왼쪽 손목 - [286,291] 94.841%
286 291
10 - 오른쪽 손목 - [122,210] 91.823%
122 210
11 - 왼쪽 힙 - [259,258] 99.081%
259 258
12 - 오른쪽 힙 - [198,250] 98.853%
198 250
13 - 왼쪽 무릎 - [286,349] 73.937%
286 349
14 - 오른쪽 무릎 - [197,327] 68.822%
197 327
15 - 왼쪽 발목 - [0,0] 12.721%
0 0
16 - 오른쪽 발목 - [0,0] 11.341%
0 0

8.두명 이상 detection

자,그럼 2명 이상을 detection 해보자.

#두명 이상 detection

results3 = model("03_test_image_pose/1.jpg")
len(results3)

아래와 같이 결과가 나올 것이다.

그런 다음, 변수들을 정의하자.

result3 = results3[0]
keypoints3 = result3.keypoints
len(keypoints3),len(result3.boxes) #len(boxes/masks/keypoints) #찾은 object의 개수

결과는 아래와 같다.

(2, 2)

그런 다음, 여러 object를 찾은 경우를 가정한다.

그리고, 각 사람에 대한 추론 결과를 출력하자.

#여러 objects를 찾은 경우
#keypoints, masks, boxes -> iterable ->
#                                      for in 문에서 반복하면 한 object에 대한 결과를 조회가능하다.



for per_idx, keypoints in enumerate(keypoints3):
    print(per_idx,type(keypoints))
    print(f"{per_idx}번 사람 추론 결과")
    
    #한 사람의 추론 결과를 보여준다.
    #위의 for문을 돌면서 사람마다의 추론 결과를 보여준다.
    for idx, (coord,conf)in enumerate(zip(keypoints.xy[0],keypoints.conf[0])):
        #[0]:첫번째 사람의 추론결과
        #반복문을 돌리면서 찍어보자.
        #x,y 좌표값을 나눠서 저장한다. ->tensor->float->int
        x_coord, y_coord = int(coord[0].item()),int(coord[1].item())
        conf = conf.item()*100 #item을 이용해서 값을 뽑아낸다. #%로 반환한다.
        print(f"{idx} - {idx2keypoints[idx]} - [{x_coord},{y_coord}] {conf:.3f}%")
        print(x_coord,y_coord)
    
    

아래는 결과이다.

0 <class 'ultralytics.engine.results.Keypoints'>
0번 사람 추론 결과
0 - 코 - [546,106] 92.423%
546 106
1 - 왼쪽 눈 - [560,100] 82.458%
560 100
2 - 오른쪽 눈 - [545,96] 62.345%
545 96
3 - 왼쪽 귀 - [597,115] 74.988%
597 115
4 - 오른쪽 귀 - [0,0] 26.886%
0 0
5 - 왼쪽 어깨 - [639,174] 99.508%
639 174
6 - 오른쪽 어깨 - [521,173] 99.108%
521 173
7 - 왼쪽 팔꿈치 - [661,257] 98.554%
661 257
8 - 오른쪽 팔꿈치 - [499,269] 97.741%
499 269
9 - 왼쪽 손목 - [617,294] 98.232%
617 294
10 - 오른쪽 손목 - [564,296] 97.643%
564 296
11 - 왼쪽 힙 - [629,315] 99.823%
629 315
12 - 오른쪽 힙 - [552,315] 99.792%
552 315
13 - 왼쪽 무릎 - [659,290] 99.664%
659 290
14 - 오른쪽 무릎 - [560,292] 99.686%
560 292
15 - 왼쪽 발목 - [606,395] 98.594%
606 395
16 - 오른쪽 발목 - [576,405] 98.752%
576 405
1 <class 'ultralytics.engine.results.Keypoints'>
1번 사람 추론 결과
0 - 코 - [433,92] 99.140%
433 92
1 - 왼쪽 눈 - [438,82] 87.837%
438 82
2 - 오른쪽 눈 - [421,81] 98.476%
421 81
3 - 왼쪽 귀 - [0,0] 14.585%
0 0
4 - 오른쪽 귀 - [385,93] 91.863%
385 93
5 - 왼쪽 어깨 - [446,148] 98.928%
446 148
6 - 오른쪽 어깨 - [341,164] 99.739%
341 164
7 - 왼쪽 팔꿈치 - [476,203] 97.385%
476 203
8 - 오른쪽 팔꿈치 - [297,268] 99.532%
297 268
9 - 왼쪽 손목 - [420,198] 97.180%
420 198
10 - 오른쪽 손목 - [319,334] 99.317%
319 334
11 - 왼쪽 힙 - [427,316] 99.743%
427 316
12 - 오른쪽 힙 - [348,325] 99.863%
348 325
13 - 왼쪽 무릎 - [454,267] 99.212%
454 267
14 - 오른쪽 무릎 - [290,303] 99.565%
290 303
15 - 왼쪽 발목 - [413,380] 96.559%
413 380
16 - 오른쪽 발목 - [226,435] 97.215%
226 435

자, 그럼 이제 그림을 통해 결과를 직관적으로 보자.

plt.imshow(result3.plot()[:,:,::-1])
#결과를 출력한다.

9.예제: 오른쪽 어께, 팔꿈치, 손목 사이의 각도 구하기

아래 공식을 참고해서 구하면 된다고 한다.

그렇게 중요한 것은 아니니, 참고만 하도록 하자.

10.코드

우선 import를 한다.

import math
from ultralytics import YOLO
import matplotlib.pyplot as plt
import cv2
import numpy as np

그런 다음 세 점 사이 각도를 구하는 함수를 적는다.

#세 점 사이 각도를 구하는 함수이다.

def calc_degree(p1,p2,p3):
    result=math.atan((p1[1]-p2[1])/(p1[0]-p2[0])) - math.atan((p3[1]-p2[1])/(p3[0]-p2[0]))
    #식을 잘 보도록 하자.
    result= result*180 / math.pi
    return abs(result) #절대값

그런 다음, 모델을 불러오자.

#모델을 불러오자.

model = YOLO("models/yolov8l-pose.pt")
results = model("03_test_image_pose/dumbbell_curls.jpg")[0] #사람이 한명밖에 없으므로 0을 적는다.
keypoints = results.keypoints

그런 다음, 이미지를 출력해본다.

#이미지를 출력한다.

plt.imshow(results.plot()[:,:,::-1])

그런 다음, keypoint index를 통해 코드를 작성한다.

#keypoint index: 오른쪽 어깨-6, 팔꿈치-8, 손목-10
shoulder, s_prob = keypoints.xy[0,6].to('cpu').numpy(),keypoints.conf[0,6].item()
elbow, e_prob =keypoints.xy[0,8].to('cpu').numpy(),keypoints.conf[0,6].item()
wrist,w_prob = keypoints.xy[0,10].to('cpu').numpy(),keypoints.conf[0,10].item()



print(shoulder,s_prob)
print(elbow,e_prob)
print(wrist,w_prob )

[ 309.65 220.96] 0.9930064678192139
[ 264.87 394.14] 0.9930064678192139
[ 398.08 392.77] 0.9906862378120422

그런 다음, 다른 배열에 값들을 저장한다.

r = []
for idx,xy in enumerate(keypoints.xy[0]):
    if idx in [6,8,10]:
        r.append(xy)
        
r        

[tensor([309.6520, 220.9605]),
tensor([264.8731, 394.1395]),
tensor([398.0822, 392.7749])]

그런 다음, 사잇값의 값을 구한다.

#사잇값

degree = calc_degree(shoulder,elbow,wrist)
degree

다음은 결과값이다.

74.91564631059549

다 구했으면, 이를 원본 이미지에 표현한다.

코드 중에서, 좌표에 50씩 더하는 부분을 잘 보도록 한다.

##원본이미지에 표시

img = result.orig_img

#degree 구한것 표시
img = cv2.putText(img,str(round(degree)),elbow.astype('int')+50,cv2.FONT_HERSHEY_COMPLEX,1,(255,255,255),2) #elbow에서 50을 더해서, 
                  #elbow에서 50픽셀씩 이동한 거리에서 텍슽트를 띄운다.
                  

그런 다음, 어께, 팔꿈치,손목을 연결한 뼈대 표현을 한다.

##어께, 팔꿈치,손목을 연결한 뼈대 표현

pts = np.array([shoulder.astype('int'),elbow.astype('int'),wrist.astype('int')])
img = cv2.polylines(img,[pts],isClosed=False,color=(255,255,255),thickness=2,lineType=cv2.LINE_AA)

그런 다음, 이미지를 출력한다.

11.중요한 것

자, 이렇게 각도를 출력을 했다. 근데, 헬스 그 자체에 대한 지식이 없으면,

저 각도가 좋은 것인지 나쁜 것인지 알 도리가 없다.

그래서 도매인 지식이 중요한 것이다.

profile
기술을 통해 세상을 이롭게 하리라

0개의 댓글