深層学習を使用した体の姿勢の推定
この例では、OpenPose アルゴリズムと事前学習済みネットワークを使用して 1 人以上の人物の身体の姿勢を推定する方法を示します。
身体の姿勢の推定の目的は、イメージ内の人物の位置と身体部位の向きを特定することです。シーンに複数の人がいる場合は、オクルージョン、身体の接触、および類似の身体部位の近接のために、姿勢の推定が難しくなる可能性があります。
体の姿勢を推定する手法は 2 つあります。トップダウン手法では、最初にオブジェクト検出を使用して個々の人物を特定し、次に各人物の姿勢を推定します。ボトムアップ手法では、最初に鼻や左肘など、イメージ内の身体部位を特定し、次に身体部位の妥当な組み合わせに基づいて個々の人物を組み立てます。ボトムアップ手法は、オクルージョンと身体の接触に対して有効ですが、手法の実装が難しくなります。OpenPose は、ボトムアップ手法を使用して複数人の人間の姿勢を推定するアルゴリズムです [1]。
OpenPose は、イメージ内の身体部位を特定するために、事前学習済みのニューラル ネットワークを使用し、入力イメージの身体部位のヒートマップと PAF (Part Affinity Fields) を予測します [2]。各ヒートマップは、特定のタイプの身体部位がイメージの各ピクセルに位置する確率を示します。PAF は、2 つの身体部位がつながっているかどうかを示すベクトル場です。首から左肩など、定義されたタイプの身体部位の組み合わせごとに、身体部位のインスタンス間におけるベクトル場の x 成分と y 成分を表す 2 つの PAF があります。
身体部位から個々の人物を組み立てるために、OpenPose アルゴリズムは一連の後処理演算を実行します。最初の演算では、ネットワークから返されたヒートマップを使用し、身体部位を特定して位置を推定します。その後の演算で、身体部位間の実際の接続を特定し、個々の姿勢を推定します。アルゴリズムの詳細については、ヒートマップと PAF からの姿勢の特定を参照してください。
ネットワークのインポート
ONNX ファイルから事前学習済みのネットワークをインポートします。
dataDir = fullfile(tempdir,"OpenPose"); trainedOpenPoseNet_url = "https://ssd.mathworks.com/supportfiles/"+ ... "vision/data/human-pose-estimation.zip"; downloadTrainedOpenPoseNet(trainedOpenPoseNet_url,dataDir) unzip(fullfile(dataDir,"human-pose-estimation.zip"),dataDir);
Deep Learning Toolbox™ Converter for ONNX Model Format サポート パッケージをダウンロードしてインストールします。
Deep Learning Toolbox Converter™ for ONNX Model Format がインストールされていない場合、この関数は、必要なサポート パッケージへのリンクをアドオン エクスプローラーに表示します。サポート パッケージをインストールするには、リンクをクリックして、[インストール] をクリックします。サポート パッケージがインストールされている場合、関数 importNetworkFromONNX
は dlnetwork
オブジェクトを返します。
modelfile = fullfile(dataDir,"human-pose-estimation.onnx");
net = importNetworkFromONNX(modelfile);
未使用の出力層を削除します。
net = removeLayers(net,net.OutputNames);
サンプル ラベル付きの dlarray
入力を使用してネットワークを初期化します。
inputSize = net.Layers(1).InputSize;
X = dlarray(rand(inputSize),"SSC");
net = initialize(net,X);
テスト イメージのヒートマップと PAF の予測
テスト イメージを読み取って表示します。
im = imread("visionteam.jpg");
imshow(im)
ネットワークは、[-0.5, 0.5] の範囲内にあるデータ型 single
のイメージ データを想定しています。データをシフトして、この範囲に再スケーリングします。
netInput = im2single(im)-0.5;
ネットワークは、青、緑、赤の順序のカラー チャネルを想定しています。イメージのカラー チャネルの順序を切り替えます。
netInput = netInput(:,:,[3 2 1]);
イメージ データを dlarray
として保存します。
netInput = dlarray(netInput,"SSC");
2 次元出力畳み込み層から出力されるヒートマップと PAF (Part Affinity Fields) を予測します。
[heatmaps,pafs] = predict(net,netInput);
dlarray
に保存されているヒートマップの数値データを取得します。データには 19 のチャネルがあります。各チャネルは、固有の身体部位のヒートマップに対応し、背景のヒートマップが 1 つ追加されています。
heatmaps = extractdata(heatmaps);
ヒートマップをモンタージュで表示し、データ型 single
のイメージとして想定されている [0, 1] の範囲にデータを再スケーリングします。シーンには 6 人がいて、各ヒートマップには 6 つの明るい点があります。
montage(rescale(heatmaps),BackgroundColor="b",BorderSize=3)
明るい点と身体の対応を可視化するには、テスト イメージの上に最初のヒートマップをフォールスカラーで重ねて表示します。
idx = 1; hmap = heatmaps(:,:,idx); hmap = imresize(hmap,size(im,[1 2])); imshowpair(hmap,im);
OpenPose アルゴリズムでは、身体部位の位置の決定に背景のヒートマップは使用しません。背景のヒートマップを削除します。
heatmaps = heatmaps(:,:,1:end-1);
dlarray
に保存されている PAF の数値データを取得します。データには 38 のチャネルがあります。身体部位の組み合わせのタイプごとに、ベクトル場の x 成分と y 成分を表す 2 つのチャネルがあります。
pafs = extractdata(pafs);
モンタージュで PAF を表示し、データ型 single
のイメージとして想定されている [0, 1] の範囲にデータを再スケーリングします。2 つの列は、ベクトル場の x 成分と y 成分をそれぞれ表しています。身体部位の組み合わせの順序は、params.PAF_INDEX
の値によって決定されます。
ほぼ垂直に接続している身体部位の組み合わせでは、y 成分の組み合わせの振幅が大きく、x 成分の組み合わせの値がごくわずかです。一例は、2 行目に表示されている右臀部から右膝への接続です。PAF は、イメージ内の実際の姿勢に基づいていることに注意してください。横たわるなど、身体の向きが異なるイメージでは、右臀部から右膝の接続の y 成分の振幅が必ずしも大きくなるとは限りません。
ほぼ水平に接続している身体部位の組み合わせでは、x 成分の組み合わせの振幅が大きく、y 成分の組み合わせの値がごくわずかです。一例は、7 行目に表示されている首から左肩への接続です。
角度のある身体部位の組み合わせには、ベクトル場の x 成分と y 成分の両方の値があります。一例は、1 行目に表示されている首から左臀部です。
montage(rescale(pafs),Size=[19 2],BackgroundColor="b",BorderSize=3)
PAF と身体の対応を可視化するには、身体部位の組み合わせの最初のタイプにおける x 成分と y 成分をフォールスカラーでテスト イメージの上に重ねて表示します。
idx = 1; impair = horzcat(im,im); pafpair = horzcat(pafs(:,:,2*idx-1),pafs(:,:,2*idx)); pafpair = imresize(pafpair,size(impair,[1 2])); imshowpair(pafpair,impair);
ヒートマップと PAF からの姿勢の特定
アルゴリズムの後処理部分で、ニューラル ネットワークによって返されるヒートマップと PAF を使用して、イメージ内の人物の個々の姿勢を特定します。
補助関数 getBodyPoseParameters
を使用して、OpenPose アルゴリズムのパラメーターを取得します。関数は、この例にサポート ファイルとして添付されています。関数は、身体部位の数や考慮すべき身体部位のタイプ間の接続などのパラメーターを含む struct を返します。パラメーターには、アルゴリズムのパフォーマンスを向上させるために調整できるしきい値も含まれています。
params = getBodyPoseParameters;
補助関数 getBodyPoses
を使用して、個々の人物とその姿勢を特定します。この関数は、この例にサポート ファイルとして添付されています。補助関数は、姿勢の推定のために以下のすべての後処理ステップを実行します。
非最大抑制を使用して、ヒートマップから身体部位の正確な位置を検出します。
身体部位の組み合わせのタイプごとに、検出された身体部位の中で可能な組み合わせをすべて生成します。たとえば、6 つの首と 6 つの左肩の中で可能な組み合わせをすべて生成します。結果は 2 部グラフになります。
PAF のベクトル場を使って、検出された 2 つの身体部位を結ぶ直線の線積分を計算することにより、組み合わせのスコアを算出します。大きなスコアは、検出された身体部位間の接続が強いことを示します。
可能な組み合わせをスコアで並べ替えて、有効な組み合わせを見つけます。有効な身体部位の組み合わせは、同じ人物に属する 2 つの身体部位をつなぐ組み合わせです。通常、スコアが最大になる組み合わせが有効な組み合わせである可能性が最も高いため、最初に考慮します。ただし、アルゴリズムは追加の制約を使用してオクルージョンと近接を補正します。たとえば、同じ人物が身体部位の同じ組み合わせを複数もつことはできません。また、1 つの身体部位が 2 人の異なる人物に属することもありません。
どの身体部位がつながっているかを判別し、身体部位を個々の人物のそれぞれの姿勢へと組み立てます。
補助関数は 3 次元の行列を返します。1 番目の次元は、イメージ内で特定された人物の数を表します。2 番目の次元は、身体部位のタイプの数を表します。3 番目の次元は、それぞれの人物の各身体部位の x 座標と y 座標を示します。身体部位がイメージの中から検出されない場合、その部位の座標は [NaN NaN] になります。
poses = getBodyPoses(heatmaps,pafs,params);
補助関数 renderBodyPoses
を使用して身体の姿勢を表示します。この関数は、この例にサポート ファイルとして添付されています。
renderBodyPoses(im,poses,size(heatmaps,1),size(heatmaps,2),params);
参考文献
[1] Cao, Zhe, Gines Hidalgo, Tomas Simon, Shih-En Wei, and Yaser Sheikh. “OpenPose: Realtime Multi-Person 2D Pose Estimation Using Part Affinity Fields.” ArXiv:1812.08008 [Cs], May 30, 2019. https://arxiv.org/abs/1812.08008.
[2] Osokin, Daniil. “Real-Time 2D Multi-Person Pose Estimation on CPU: Lightweight OpenPose.” ArXiv:1811.12004 [Cs], November 29, 2018. https://arxiv.org/abs/1811.12004.