🚶‍♂️

YOLOv8で姿勢推定する

2023/07/02に公開
8

YOLOv8シリーズです!

今回は、姿勢検出(Human Pose-estiamtion)をやってみたいと思います!

前回はオブジェクト検出と座標取得を行なってみましたが、今回は座標取得に加えて、人間の姿勢推定までやってみたいと思います。

YOLOについての前回の記事はこちらです
YOLOを使ってオブジェクト検出と座標取得をしてみる (zenn.dev)

姿勢推定とは?

そもそも姿勢推定とはどんな形で出力できるのか、こちらをご覧いただいた方が理解が早いだろうと思います。

推定された骨格(?)にラインが引かれていますね。

早速、姿勢推定してみましょう

YOLOv8にて姿勢推定するためには、まず姿勢推定用のモデルデータ読み込みが必要です。ただしこちらも前回同様にモデルのサンプルデータがありますのでこちらを利用しましょう。

model = YOLO('yolov8n-pose.pt')

はい!これだけです。

あとは前回同様に推論を実行すると、推論された姿勢を確認することができます。

推定されたキーポイント座標を取得する

姿勢推定されると、最大で17個のキーポイントが得られます。
画像でいうところの各関節や頭などにあるポイントですね。

結果に対して、plot()を呼び出すとこのように各キーポイントと、キーポイント間の接続に線が引かれます。
これらは、推論を実行して得られた結果内で、次のように定義されます。

keypoints: [
	[ # detected person1
		[x, y, score],... 
	],
	[ # detected person2
		[x, y, score],...
	], ...
]

一つの画像内に、複数人が写っている場合は、keypoints配列内に、検出された人数分の配列が出力されているはずです。

x, yは画像上のキーポイント座標で、scoreはそのキーポイントの信頼度のスコアを表しています。
最大は1.0で、低ければ低いほど信頼されないキーポイントというわけですね。
この辺りの閾値は利用するプログラムによって調整する必要があるでしょう。

前回で環境構築を終えていれば、コピペで確認できるサンプルコードも作成してみました。

import cv2
from ultralytics import YOLO

# モデルの読み込み。姿勢推論用のモデルデータを読み込む
model = YOLO("yolov8n-pose.pt")

# 本体のウェブカメラからキャプチャする設定
video_path = 0  # 本体に付属のカメラを指定
capture = cv2.VideoCapture(video_path)

# keypointの位置毎の名称定義
KEYPOINTS_NAMES = [
    "nose",  # 0
    "eye(L)",  # 1
    "eye(R)",  # 2
    "ear(L)",  # 3
    "ear(R)",  # 4
    "shoulder(L)",  # 5
    "shoulder(R)",  # 6
    "elbow(L)",  # 7
    "elbow(R)",  # 8
    "wrist(L)",  # 9
    "wrist(R)",  # 10
    "hip(L)",  # 11
    "hip(R)",  # 12
    "knee(L)",  # 13
    "knee(R)",  # 14
    "ankle(L)",  # 15
    "ankle(R)",  # 16
]

while capture.isOpened():
    success, frame = capture.read()
    if success:
        # 推論を実行
        results = model(frame)

        annotatedFrame = results[0].plot()

        # 検出オブジェクトの名前、バウンディングボックス座標を取得
        names = results[0].names
        classes = results[0].boxes.cls
        boxes = results[0].boxes

        for box, cls in zip(boxes, classes):
            name = names[int(cls)]
            x1, y1, x2, y2 = [int(i) for i in box.xyxy[0]]

        if len(results[0].keypoints) == 0:
            continue

        # 姿勢分析結果のキーポイントを取得する
        keypoints = results[0].keypoints
        confs = keypoints.conf[0].tolist()  # 推論結果:1に近いほど信頼度が高い
        xys = keypoints.xy[0].tolist()  # 座標

        for index, keypoint in enumerate(zip(xys, confs)):
            score = keypoint[1]

            # スコアが0.5以下なら描画しない
            if score < 0.5:
                continue

            x = int(keypoint[0][0])
            y = int(keypoint[0][1])
            print(
                f"Keypoint Name={KEYPOINTS_NAMES[index]}, X={x}, Y={y}, Score={score:.4}"
            )
            # 紫の四角を描画
            annotatedFrame = cv2.rectangle(
                annotatedFrame,
                (x, y),
                (x + 3, y + 3),
                (255, 0, 255),
                cv2.FILLED,
                cv2.LINE_AA,
            )
            # キーポイントの部位名称を描画
            annotatedFrame = cv2.putText(
                annotatedFrame,
                KEYPOINTS_NAMES[index],
                (x + 5, y),
                fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                fontScale=1.3,
                color=(255, 0, 255),
                thickness=2,
                lineType=cv2.LINE_AA,
            )

        print("------------------------------------------------------")

        cv2.imshow("YOLOv8 human pose estimation", annotatedFrame)

        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
    else:
        break

capture.release()
cv2.destroyAllWindows

キーポイントの定義詳細について

モデルの読み込みおよび「姿勢分析結果のキーポイントを取得する」コメント部が大きく変化しているところですね。

result[0].keypointsで得られるオブジェクトは次のような構造になっています。
(今回説明するもののみ抽出しました)

{
  conf: tensor([[下表のインデックスに対応した部位毎の認識スコア, ...], ...]),
  data: tensor(),
  xy: tensor([[[下表のインデックスに対応した部位毎の画像上の座標x, y], ...], ...])
  ...
}
配列のインデックス 部位
0
1 左目
2 右目
3 左耳
4 右耳
5 左肩
6 右肩
7 左肘
8 右肘
9 左手首
10 右手首
11 左腰
12 右腰
13 左膝
14 右膝
15 左足首
16 右足首
import cv2
from ultralytics import YOLO

# モデルの読み込み。姿勢推論用のモデルデータを読み込む
model = YOLO("yolov8n-pose.pt")

pic = "<your image path>"
frame = cv2.imread(pic)
result = model(frame)

plottedFrame = result[0].plot()
cv2.imwrite(pic + "-result.jpg", plottedFrame)

yolo-CLIでも、読み込むモデルでyolov8n-pose.ptを指定することでできます。
ぜひ、お試しください。

コラボスタイル Developers

Discussion

kage1020kage1020

とても参考になる記事をありがとうございます.
1つ質問なのですが,キーポイントの配列がどこの部位に対応しているかの情報はどこかに記載があったのでしょうか?公式ドキュメントを探しても見つからなかったので,あればぜひ提示してほしいです.

なるおなるお

コメントありがとうございます!

キーポイントの配列がどこの部位に対応しているの情報については、私も公式ドキュメント上では見つけることができておりませんでした。
コメントをいただき、記事に追記致しましたが、公式ドキュメント上ではCOCOデータセットを用いていると明記がありましたので、そちらを頼りに調べました。

ご参考になるか分かりませんがご確認ください。

なるおなるお

お役に立てたようでよかったです。
参考リンクもありがとうございます!

あ

とてもわかりやすい記事をありがとうございます。
一点、ご質問がございます。
画像に表示されるキーポイントの座標を得たいのですが、ご掲示いただいたサンプルコードのように試してもエラーが出てしまいます。
何か参考にしたサイト等ありますでしょうか?よろしければご教示いただけると幸いです。
よろしくお願いいたします。

なるおなるお

あさん、お返事遅れ申し訳ありません。
改めて確認したところ、オブジェクトの形式が変わっているためなのかわかりませんが、私も動作しなかったため修正して記事を更新しましたのでご参考になればと思います。

参考は主に英語サイトで検索していますが、具体的なURLは覚えていないためお伝えできません。
yolov8 human pose estimationなどで検索するといいかもしれません。

あ

ご返信ありがとうございます。
自分でも調べてみたいと思います🙇‍♀️

takitaki

大変分かりやすくまとめていただき、ありがとうございます。
YOLOの更新があったのか、人が映らなくなると
『confs = keypoints.conf[0].tolist() # 推論結果:1に近いほど信頼度が高い』の部分で
『TypeError: 'NoneType' object is not subscriptable』というエラーが出てしまうようです。

これは keypoints.conf がNoneタイプになってしまうため起こるようですので、
『confs = keypoints.conf[0].tolist() # 推論結果:1に近いほど信頼度が高い』の直前に
『if results[0].keypoints.conf!=None:』を配置して後2行をif文の配下に置くことでエラー回避できました。

急ぎ記載しましたので、分かりにくいかもしれませんが、ご参考まで。