0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RunningAI:Jetson Nano 上で動くリアルタイム姿勢推定システムの技術構成まとめ

Posted at

🏃‍♂️ はじめに

物置でずっと眠っていた Jetson Nano を、趣味のランニングに活かせたら面白いんじゃないか——。
そんな軽い思いつきから リアルタイムでフォーム解析ができるエッジAIシステム を作成しました。
今回は、Jetson Nano に 市販のアクションカメラを組み合わせて
走る姿勢をリアルタイム推定して配信する RunningAI」を作ったので、構成と手順をまとめます。


🔧 Jetson Nano の環境構築

Jetson はバージョン依存が強いので、安定した構成をまず書いておきます。

項目 バージョン
JetPack 4.6.6
CUDA 10.2
PyTorch 1.10.0(aarch64, CUDA10.2)
TensorRT 8.2
trt_pose master
OpenCV 4.5.1
FastAPI 最新
Flask 3.x
Docker Compose v2.40

🏃trt_pose のセットアップ

姿勢推定は NVIDIA の trt_pose を使います。
軽量モデルでも Jetson Nano で十分リアルタイムに動きます。

使用モデル

  • human_pose.json(18関節定義)

  • resnet18_baseline_att_224x224_A_epoch_249.pth(学習済みモデル)

🎥 Flask によるリアルタイムストリーミング

映像の配信はFlask で簡易 Web サーバを立てて、その中で MJPEG ストリームを配信します。詳細な役割は以下の通りです。

  • Jetson のカメラ映像を取得
  • trt_pose で推定した骨格を描画
  • MJPEG として配信

前提条件

実行前に、以下が整っていることを確認します。

  • JetPack / L4T: R32.x 系(例: R32.7.6)
  • Docker が有効
docker --version

USB カメラが /dev/video0 として認識されている

ls -l /dev/video*

1.ホスト側:プロジェクトディレクトリ作成

まずは Jetson 側(ホスト OS)で作業ディレクトリを切ります。

mkdir -p ~/jetson_projects

ここにコンテナ内 /workspace をマウントして使っていきます。

2.コンテナ起動(GPU / カメラ / ネットワーク対応)

NVIDIA 公式の L4T PyTorch イメージを使って、trt_pose 用の作業コンテナを立ち上げます。

sudo docker run -it --runtime nvidia --network host \
  --device /dev/video0:/dev/video0 \
  --device /dev/bus/usb:/dev/bus/usb \
  --name trt_pose_env \
  --volume ~/jetson_projects:/workspace \
  nvcr.io/nvidia/l4t-pytorch:r32.7.1-pth1.10-py3 bash

一度作ってしまえば、次回以降はコンテナに再入室するだけでOKです。

sudo docker exec -it trt_pose_env bash

3.コンテナ内の最低限セットアップ

コンテナに入ったら、まず最低限のツールだけ入れておきます。

コンテナ内
apt-get update && apt-get install -y nano

4. trt_pose を “develop” で導入(コンテナ内)

コンテナ内で作業します。
develop にしておくと、コードを編集しても即反映されるので開発が楽です。

コンテナ内
cd /workspace
git clone https://github.com/NVIDIA-AI-IOT/trt_pose.git
cd trt_pose
python3 setup.py develop

5.タスク用ディレクトリ作成とモデル配置

次に、姿勢推定タスク用のディレクトリを切ります。

コンテナ内
mkdir -p /workspace/trt_pose/tasks/human_pose
cd /workspace/trt_pose/tasks/human_pose

学習済みモデルとキーポイント定義を配置します。

コンテナ内
wget -O resnet18_baseline_att_224x224_A_epoch_249.pth \
  https://github.com/NVIDIA-AI-IOT/trt_pose/releases/download/v0.0.1/resnet18_baseline_att_224x224_A_epoch_249.pth
cat > human_pose.json <<'JSON'
{"supercategory": "person", "id": 1, "name": "person", "keypoints": ["nose", "left_eye", "right_eye", "left_ear", "right_ear", "left_shoulder", "right_shoulder", "left_elbow", "right_elbow", "left_wrist", "right_wrist", "left_hip", "right_hip", "left_knee", "right_knee", "left_ankle", "right_ankle", "neck"], "skeleton": [[16, 14], [14, 12], [17, 15], [15, 13], [12, 13], [6, 8], [7, 9], [8, 10], [9, 11], [2, 3], [1, 2], [1, 3], [2, 4], [3, 5], [4, 6], [5, 7], [18, 1], [18, 6], [18, 7], [18, 12], [18, 13]]}
JSON

このコマンドを実行することで、
姿勢推定で使用する 関節の一覧(keypoints) と、
その関節をどう線で結ぶかという 骨格構造(skeleton) を trt_pose に定義できます。

6. Flask でリアルタイム配信(MJPEG)

ここまでできたら、いよいよリアルタイム配信です。
pythonでFlask配信するためのコード(pose_live_web.py)を書き、それを実行します。

pose_live_web.py
import cv2
import json
import torch
import numpy as np
from flask import Flask, Response
from trt_pose.parse_objects import ParseObjects
from trt_pose.draw_objects import DrawObjects
from trt_pose import models
import torchvision.transforms.functional as F

app = Flask(__name__)

# ----------------------------
# モデル & human_pose 設定
# ----------------------------
with open('human_pose.json', 'r') as f:
    human_pose = json.load(f)

num_parts = len(human_pose['keypoints'])
num_links = len(human_pose['skeleton'])

# ResNet18 ベースの姿勢推定モデル
model = models.resnet18_baseline_att(
    num_parts,
    num_links
)
model.load_state_dict(torch.load(
    'resnet18_baseline_att_224x224_A_epoch_249.pth',
    map_location='cuda'
))
model = model.cuda().eval()

parse_objects = ParseObjects(human_pose)
draw_objects = DrawObjects(human_pose)

# ----------------------------
# 前処理関数
# ----------------------------
def preprocess(img_bgr):
    # trt_pose は 224x224 / RGB 想定
    img = cv2.resize(img_bgr, (224, 224))
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    tensor = F.to_tensor(img).cuda()
    tensor = tensor.unsqueeze(0)  # (1,3,224,224)
    return tensor

# ----------------------------
# カメラ設定
# ----------------------------
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    raise RuntimeError("カメラが開けませんでした (/dev/video0 を確認してください)")

# ----------------------------
# MJPEG ストリーム生成
# ----------------------------
def gen_frames():
    while True:
        ret, frame = cap.read()
        if not ret:
            continue

        # ---- 姿勢推定 ----
        with torch.no_grad():
            tensor = preprocess(frame)
            cmap, paf = model(tensor)
            cmap, paf = cmap.detach().cpu(), paf.detach().cpu()
            counts, objects, peaks = parse_objects(cmap, paf)

        # ---- 骨格描画 ----
        draw_objects(frame, counts, objects, peaks)

        # ---- JPEG にエンコードして送信 ----
        ret, jpeg = cv2.imencode('.jpg', frame)
        if not ret:
            continue

        frame_bytes = jpeg.tobytes()

        yield (
            b'--frame\r\n'
            b'Content-Type: image/jpeg\r\n\r\n' +
            frame_bytes +
            b'\r\n'
        )

# ----------------------------
# Flask ルーティング
# ----------------------------
@app.route('/')
def index():
    # 超シンプルなページ(img タグで /video を表示)
    return """
    <html>
      <head><title>RunningAI - Pose Stream</title></head>
      <body>
        <h2>RunningAI Pose Stream (Flask only)</h2>
        <img src="/video" />
      </body>
    </html>
    """

@app.route('/video')
def video():
    return Response(
        gen_frames(),
        mimetype='multipart/x-mixed-replace; boundary=frame'
    )

# ----------------------------
# メイン
# ----------------------------
if __name__ == "__main__":
    # 0.0.0.0 で公開 → 同一LAN内のPCやスマホからアクセス可能
    app.run(host="0.0.0.0", port=5000, threaded=True)

動かし方(コンテナ内)

human_pose.json
resnet18_baseline_att_224x224_A_epoch_249.pth
が、pose_live_web.py と同じディレクトリに置いてあることを確認して下記コードを実行します。

コンテナ内
python3 pose_live_web.py

ログに

text
 * Running on http://0.0.0.0:5000/

と出たら、同じネットワークにいる PC / スマホのブラウザから:

text
http://<JetsonのIP>:5000

にアクセスすれば、そのままFlask だけで骨格つき映像配信が見られます。

Untitled design.png

無事、左上に骨格だけ抽出できました!

今後はNumPy でベクトル演算して股関節の角度などを数値化して分析を進めて行きたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?