
旧記事(Tensorflow 1.x)ではM1 Macでうまく動かない(zsh: illegal hardware instructionというエラーが出る)問題がありましたが、本記事の方法はM1 Mac/Intel Mac/Windows11でOpenPoseによる骨格推定を行えます。
OpenPoseはカーネギーメロン大学で開発された人物のポーズ推定ライブラリで、無料で利用することができます。(商用利用にはライセンス料が発生)
Pythonの環境であるAnaconda/Minicondaをベースに以下3つの入力に対しOpenPoseによる骨格推定をする方法を紹介します。
- 画像ファイル
- 内蔵カメラ(Windows11 WSL2環境はUSBカメラ)
- 動画ファイル
なお、元記事の方法はTensorflow 1.xを使用していましたが、Tensorflow 1.xはメンテナンスがされておらず、うまく環境構築できなかったため、Tensorflow 2.xを使用したOpenPoseのGitHubリポジトリを利用しました。
以下の2つの動画にWindows11とM1 Macで本記事の内容を実践した様子を収めていますので、よろしければ参考にしてください。
【Windows11】
【M1 Mac使用】
動作環境
以下の環境で動作確認しました。
① | ② | ③ | |
PC | M1 MacBook Pro 2020 | Intel MacBook Pro 2015 | MSI GF65 15.6-inch |
OS | macOS Big Sur | macOS Monterey | Windows 11/WSL2 Ubuntu-20.04 LTS |
Anaconda | Miniconda 4.11.0 | Anaconda 4.11.0 | Anaconda 4.10.3 |
Python | 3.8 | 3.7 | 3.8 |
OpenCV | 4.5.5 | 4.5.1 | 4.5.1 |
Tensorflow | 2.6.0 | 2.0.0 | 2.4.1 |
事前準備
Macの場合
wget(Webからファイルをダウンロード)やXquartz(X11ウィンドウを表示する)をインストールしていない場合は以下のコマンドでインストールします。
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew install wget brew install --cask xquartz
ここでMacを再起動します。
Windows11の場合
WSL2のUtuntu-20.04 LTSをインストールして使用します。
Windowsマークを右クリック→Windowsターミナル(管理者)を立ち上げ、以下のコマンドでWSL2 Ubuntu-20.04 LTSをインストールします。
wsl --install -d Ubuntu-20.04
下図のようにプルダウンメニューからUbuntu-20.04を起動します。

Anaconda/Minicondaインストール
MinicondaのGitHubから、お使いの環境に合ったリンクをクリックします。
Intel Mac | Miniforge3-MacOSX-x86_64 |
M1 Mac | Miniforge3-MacOSX-arm64 |
Windows/WSL2 Ubuntu-20.04 LTS | Miniforge3-Linux-x86_64 |
Minicondaのインストールスクリプトをダウンロードしたら、以下のようにbashで実行します。(下記ファイル名はM1 Macの場合であり、OSによって異なります)
bash Miniforge3-MacOSX-arm64.sh
~/.zshrcに自動的にMiniconda用の設定が追記されるので、sourceして有効にします。
source ~/.zshrc
condaをupdateしてバージョンを表示します。
conda update conda conda -V
以下のようにバージョンが表示されるので、GitHubサイトのReleaseのバージョンと同じことを確認します。
conda 4.11.0
異なる場合、既にインストールしてあったAnacondaのcondaが呼ばれていることが考えられますので、以下のようにMinicondaのインストールディレクトリのbinが先になるように~/.zshrcにPATH設定を追記します。
export PATH="/Users/user_name/miniforge3/bin:$PATH"
user_nameの部分はお使いのユーザ名に合わせて書き換えてください。
Python 3.8、Numpy 1.19を指定したposeという仮想環境を作成し、アクティベートします。
conda create -n pose python=3.8 numpy=1.19 conda activate pose
OpenPose環境構築
以降は、pose環境をアクティベートした状態でのコマンド入力になります。
Tensorflow 2.x用のOpenPose環境をダウンロードします。
git clone https://github.com/gsethi2409/tf-pose-estimation.git cd tf-pose-estimation
以下のコマンドを順に実行してOpenPose環境を構築します。
conda install numba scipy pip install -r requirements.txt conda install -c conda-forge tensorflow conda install swig cd tf_pose/pafprocess swig -python -c++ pafprocess.i && python3 setup.py build_ext --inplace cd ../.. conda install -c conda-forge opencv pip install git+https://github.com/adrianc-a/tf-slim.git@remove_contrib cd models/graph/cmu bash download.sh cd ../../..
OpenPose実行
以下のコマンドでサンプルとして付属されている画像を使ってOpenPoseを実行してみます。
python run.py --model=mobilenet_thin --resize=432x368 --image=./images/p2.jpg
以下のように表示されれば成功です。

左上の画像が骨格を元画像に重ね合わせた結果で、右上は関節の確度、下段は関節同士をつなぐためのX,Y方向のベクトルマップです。
--model
で指定できるモデルは、cmu / mobilenet_thin / mobilenet_v2_large / mobilenet_v2_small の4種類で、cmuはより正確に骨格を推定できるものの実行速度が遅いです。
他の3つはさほど変わらないように思えました。
--resize
オプションでサイズを432×368にしているのは、学習時の入力画像がこのサイズのため、この設定にすることで認識しやすくなるためのようです。
終了するにはqを押すか、表示されたウィンドウのX印を押します。
次に内蔵カメラを使ってリアルタイムのOpenPose実行を試します。
python run_webcam.py --model=mobilenet_thin --resize=432x368 --camera=0
以下のようにパソコン内臓のカメラ画像にリアルタイムに骨格が重ね合わせて表示されれば成功です。

骨格だけでなく、顔の中の目、耳、鼻の位置も捉えていることがわかります。
終了するにはターミナルでCtrl+cを押します。
最後に動画を入力とするrun_video.py
を試したのですが、うまく骨格が表示できなかったので、こちらのサイトの情報から、一部修正して以下をrun_video2.py
として使用しました。
import argparse import logging import time import cv2 import numpy as np from tf_pose.estimator import TfPoseEstimator from tf_pose.networks import get_graph_path, model_wh logger = logging.getLogger('TfPoseEstimator-Video') logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s') ch.setFormatter(formatter) logger.addHandler(ch) fps_time = 0 if __name__ == '__main__': parser = argparse.ArgumentParser(description='tf-pose-estimation Video') parser.add_argument('--video', type=str, default='') parser.add_argument('--write_video', type=str, default='') parser.add_argument('--resize', type=str, default='0x0', help='if provided, resize images before they are processed. default=0x0, Recommends : 432x368 or 656x368 or 1312x736 ') parser.add_argument('--resize-out-ratio', type=float, default=4.0, help='if provided, resize heatmaps before they are post-processed. default=1.0') parser.add_argument('--model', type=str, default='mobilenet_thin', help='cmu / mobilenet_thin') parser.add_argument('--show-process', type=bool, default=False, help='for debug purpose, if enabled, speed for inference is dropped.') parser.add_argument('--showBG', type=bool, default=True, help='False to show skeleton only.') args = parser.parse_args() logger.debug('initialization %s : %s' % (args.model, get_graph_path(args.model))) w, h = model_wh(args.resize) if w > 0 and h > 0: e = TfPoseEstimator(get_graph_path(args.model), target_size=(w, h)) else: e = TfPoseEstimator(get_graph_path(args.model), target_size=(432, 368)) cap = cv2.VideoCapture(args.video) if args.write_video: width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') writer = cv2.VideoWriter(args.write_video, fmt, fps, (width, height)) if cap.isOpened() is False: print("Error opening video stream or file") while cap.isOpened(): ret_val, image = cap.read() logger.debug('image process+') humans = e.inference(image, resize_to_default=(w > 0 and h > 0), upsample_size=args.resize_out_ratio) if not args.showBG: image = np.zeros(image.shape) logger.debug('postprocess+') image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False) logger.debug('show+') cv2.putText(image, "FPS: %f" % (1.0 / (time.time() - fps_time)), (10, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow('tf-pose-estimation result', image) if args.write_video: writer.write(image) fps_time = time.time() if cv2.waitKey(1) == 27: break cv2.destroyAllWindows() logger.debug('finished+')
入力する動画はPixaboyから最小サイズの640×360をダウンロードしました。
以下のコマンドでダウンロードした動画を入力としてOpenPoseを実行します。
python run_video2.py --model=mobilenet_thin --resize=432x368 --video=/Users/user_name/Downloads/People\ -\ 45353.mp4
結果を動画ファイルに保存するには以下のように–write_video=<ファイル名>オプションを付けます。
python run_video2.py --model=mobilenet_thin --resize=432x368 --video=/Users/user_name/Downloads/People\ -\ 45353.mp4 --write_video=out.mp4
以下のように表示されれば成功です。

上図の場面では、水色の服の人は左腕が青い色の線になっていて、他の人とは左右反対です。
スマホを見ながら、うつむき加減でマスクも着用しているため、顔の認識ができず、後ろ向きと判断されてしまったようです。
このように誤認識してしまうケースもありますが、たいていは問題なく認識できています。
応用編
読者の方から、認識結果をファイルに落とす方法が知りたいとのご要望があり、run_video2.py
を少し発展させて以下のrun_video2_fileout.py
をサンプルとして作成しました。
import argparse import logging import time import cv2 import numpy as np from tf_pose.estimator import TfPoseEstimator from tf_pose.networks import get_graph_path, model_wh logger = logging.getLogger('TfPoseEstimator-Video') logger.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s') ch.setFormatter(formatter) logger.addHandler(ch) fps_time = 0 if __name__ == '__main__': parser = argparse.ArgumentParser(description='tf-pose-estimation Video') parser.add_argument('--video', type=str, default='') parser.add_argument('--write_video', type=str, default='') parser.add_argument('--resize', type=str, default='0x0', help='if provided, resize images before they are processed. default=0x0, Recommends : 432x368 or 656x368 or 1312x736 ') parser.add_argument('--resize-out-ratio', type=float, default=4.0, help='if provided, resize heatmaps before they are post-processed. default=1.0') parser.add_argument('--model', type=str, default='mobilenet_thin', help='cmu / mobilenet_thin') parser.add_argument('--show-process', type=bool, default=False, help='for debug purpose, if enabled, speed for inference is dropped.') parser.add_argument('--showBG', type=bool, default=True, help='False to show skeleton only.') args = parser.parse_args() logger.debug('initialization %s : %s' % (args.model, get_graph_path(args.model))) w, h = model_wh(args.resize) if w > 0 and h > 0: e = TfPoseEstimator(get_graph_path(args.model), target_size=(w, h)) else: e = TfPoseEstimator(get_graph_path(args.model), target_size=(432, 368)) cap = cv2.VideoCapture(args.video) if args.write_video: width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = cap.get(cv2.CAP_PROP_FPS) fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') writer = cv2.VideoWriter(args.write_video, fmt, fps, (width, height)) with open('humans.txt', mode='w') as f: if cap.isOpened() is False: print("Error opening video stream or file") while cap.isOpened(): ret_val, image = cap.read() if ret_val: logger.debug('image process+') humans = (e.inference(image, resize_to_default=(w > 0 and h > 0), upsample_size=args.resize_out_ratio)) #print(humans) #print(type(humans)) #print(len(humans)) #exit(0) tmp_h = [] for human in humans: tmp_h.append(str(human)) str_h = ";".join(tmp_h) print(str_h) f.write(str_h) f.write("\n") #exit(0) if not args.showBG: image = np.zeros(image.shape) logger.debug('postprocess+') image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False) logger.debug('show+') cv2.putText(image, "FPS: %f" % (1.0 / (time.time() - fps_time)), (10, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow('tf-pose-estimation result', image) if args.write_video: writer.write(image) fps_time = time.time() if cv2.waitKey(1) == 27: break else: break cv2.destroyAllWindows() logger.debug('finished+')
認識結果はhumansにHumanクラスのインスタンスのリストとして格納されていました。
humansの中身を確認した際のコードもコメントとして残してあります。
Humanクラスはtf_pose/estimator.py
に定義されています。
実行方法は同様で以下のように使用します。
python run_video2_fileout.py --model=mobilenet_thin --resize=432x368 --video=/Users/user_name/Downloads/People\ -\ 45353.mp4
実行するとhumans.txt
というファイルができて、以下のようにBodyPart:0〜17の座標(画角に対し0〜1に正規化した値)とscore(確信の度合いと思われる)が空白区切りで出力されます。
BodyPart:0-(0.50, 0.30) score=0.88 BodyPart:1-(0.50, 0.33) score=0.84 ...
人が認識される個数は入力する絵によって異なります。複数認識の場合、それぞれをセミコロン(;)で区切るようにしてあります。
BodyPartは毎度0〜17のすべてが出力される訳ではないようです。
このようにテキストに落としてしまえば、外部でいろいろ処理ができそうです。
もっと情報をしぼって出力したい場合は、tf_pose/estimator.py
の中身を見てTfPoseEstimatorクラスのdraw_humansメソッドを参考に、human.body_parts[i].x
のようにコアなデータにアクセスするコードを書くのが良いと思います。
書籍紹介
Pythonや画像処理、機械学習関連のおすすめ書籍を紹介します。
実践して楽しむことを重視していて、充実のサンプルコードで画像認識やテキスト分析、GAN生成といったAI応用をGoogle Colab環境下で体験できます。
基礎から応用までわかりやすい説明でコード例も多数載っていて、初学者の入門書としては必要十分と感じています。
さらにPythonらしいコードの書き方も言及されていて、スマートなコードが書けるようになります。
詳解 OpenCV 3 ―コンピュータビジョンライブラリを使った画像処理・認識
最新のOpenCV4には対応していないのですが、やはりオライリー、基礎を学ぶには十分な情報があり、この本から入ることをおすすめします。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
この本は、機械学習の仕組みを学ぶのにとても良い本だと思います。
Tensorflow/Kerasなどの既存プラットフォームを使うのではなく、基本的な仕組みを丁寧に説明し、かんたんなPythonコードで実装していく手順を紹介してくれます。
機械学習のコードがどのような処理をしているのか、想像できるようになります。
まとめ
M1 Mac/Intel Mac/Windows11でAnaconda/MinicondaをベースにOpenPoseで骨格推定を行える環境の構築を行い、以下3種の入力に対しOpenPoseを実行することができました。
- 画像ファイル
- 内蔵カメラ(Windows11 WSL2環境はUSBカメラ)
- 動画ファイル
このようにOpenPoseを使うと単眼カメラの画像から骨格を2Dの座標情報として推定し、画像に重ね合わせて表示することができました。
人物に骨格を重ね合わせて表示されるのはとても楽しいので、是非お試しください。
参考文献
- Pose Estimation with TensorFlow 2.0(https://medium.com/@gsethi2409/pose-estimation-with-tensorflow-2-0-a51162c095ba)
- 【Python】Anaconda上にOpenCVをインストールする方法【Windows】(https://qiita.com/osakasho/items/c9d87b70e8f043569ca7)
- pip で Numba をインストールする時にエラーになる(https://va2577.github.io/post/70/)
- OpenPoseで骨格推定【Mac】 Anaconda/OpenCV/TensorFlow(https://take6shin-tech-diary.com/openpose-mac/)