画像処理
CUDA
OpenCV
DeepLearning
OpenPose

OpenPoseチュートリアルのソースコードを読む。

More than 1 year has passed since last update.

目次

1.OpenPoseを動かしてみた。
2.OpenPoseチュートリアルのソースコードを読む。
3.WindowsでOpenPoseを動かしてみた。
4.OpenPoseのFace Estimationを動かしてみた。
5.OpenPoseのHand Estimationを動かしてみた。

背景

OpenPoseを動かしてみた。の続きです。
OpenPoseのチュートリアルのソースコード:1_extract_from_imageを解説します。
example/tutorial_pose/1_extract_from_image

チュートリアルのソースコードもしくはドキュメントを和訳しています。一部、意訳になっています。
1_extract_from_imageはOpenCVのcv::Matを使った最小構成のサンプルで、静止画像を読み込み、Pose Estimationを実行するフローを説明しています。

チュートリアルのフロー

このチュートリアルは以下のPose Estimation処理の流れが含まれています。

 1.画像(cv::Mat)の読み込み。
 2.画像からposeを抽出。
 3.画像のサイズ変換と、Poseの出力
 4.Poseの表示

mainの解説:

openPoseTutorialPose1関数を呼び出しています

int main(int argc, char *argv[]){
    //Caffeで使用している google logging ツールの初期化
    google::InitGoogleLogging("openPoseTutorialPose1");

    //引数の構文を解析する
    gflags::ParseCommandLineFlags(&argc, &argv, true);

    //openPoseのチュートリアルの処理の本体
    return openPoseTutorialPose1();
}

設定パラメータ定義

  • int32 FLAGS_logging_level:3:ログの表示レベル [0~255] 255でログ表示しない
  • string FLAGS_image_path:"examples/media/COCO_val2014_000000000192.jpg":画像のパス
  • string FLAGS_model_pose:"COCO": Modelの種類(COCO, MPI, MPI_4_layersから選べる)
  • string FLAGS_model_folder:"models/": Model(COCO・MPI)のフォルダのパス
  • string FLAGS_net_resolution:"656x368": 処理画像の解像度、16の倍数にすること。
  • string FLAGS_resolution:"1280x720": 表示画像の解像度、"-1x-1\"で強制的にdefault指定。
  • int32 FLAGS_num_gpu_start:"0": 計算に使用するGPUの番号(GPUが複数ある場合、選択できる)
  • double FLAGS_scale_gap:"0.3": 入力画像と出力画像のScale比。
  • int32 FLAGS_num_scales:"1": Scaleの平均化回数?(不明瞭、後で修正)
  • double FLAGS_alpha_pose:: 出力画像のスケルトンのαブレンド値[0~1]

openPoseTutorialPose1関数の解説

Pose Estimation処理が記述されています。
cv::Mat inputImageを入力として、poseKeyPoints(Poseの座標)が出力されています。
前項の設定パラメータで変更加能です。

int openPoseTutorialPose1()
{
    op::log("OpenPose Library Tutorial - Example 1.", op::Priority::Max);

    // ------------------------- 初期化開始 -------------------------

    // Step 1 - logging levelを設定する
        // - 0 全部loggingする
        // - 255 loggingしない
    op::check(0 <= FLAGS_logging_level && FLAGS_logging_level <= 255, "Wrong logging_level value.", __LINE__, __FUNCTION__, __FILE__);
    op::ConfigureLog::setPriorityThreshold((op::Priority)FLAGS_logging_level);


    // Step 2 - Google flags からパラメータを読み出す
    cv::Size outputSize;
    cv::Size netInputSize;
    cv::Size netOutputSize;
    op::PoseModel poseModel;
    std::tie(outputSize, netInputSize, netOutputSize, poseModel) = gflagsToOpParameters();

    // Step 3 - ディープラーニングの初期化
    op::CvMatToOpInput cvMatToOpInput{netInputSize, FLAGS_num_scales, (float)FLAGS_scale_gap};
    op::CvMatToOpOutput cvMatToOpOutput{outputSize};
    op::PoseExtractorCaffe poseExtractorCaffe{netInputSize, netOutputSize, outputSize, FLAGS_num_scales, (float)FLAGS_scale_gap, poseModel,FLAGS_model_folder, FLAGS_num_gpu_start};
    op::PoseRenderer poseRenderer{netOutputSize, outputSize, poseModel, nullptr, (float)FLAGS_alpha_pose};
    op::OpOutputToCvMat opOutputToCvMat{outputSize};
    const cv::Size windowedSize = outputSize;

   //frameDisplayer.displayFrameのWindowを作成する。
    //cv::imshowを使う場合は不要
    op::FrameDisplayer frameDisplayer{windowedSize, "OpenPose Tutorial - Example 1"};

    // Step 4 - threadの初期化(この場合single thread)
    poseExtractorCaffe.initializationOnThread();
    poseRenderer.initializationOnThread();

    // ------------------------- 初期化終了 -------------------------


    // ------------------------- Pose Estimationの開始 -------------------------
    // Step 1 - 画像の読み出し 
    cv::Mat inputImage = op::loadImage(FLAGS_image_path, CV_LOAD_IMAGE_COLOR); 

  // OpenCVの関数で代替する場合
  // cv::imread(FLAGS_image_path, CV_LOAD_IMAGE_COLOR);

   //pathが間違っているなど、画像がempty の時はerror
    if(inputImage.empty())
        op::error("Could not open or find the image: " + FLAGS_image_path, __LINE__, __FUNCTION__, __FILE__);

    // Step 2 - cv::Matの入力画像からOpenPoseの画像形式に変換する
    const auto netInputArray = cvMatToOpInput.format(inputImage);
    double scaleInputToOutput;
    op::Array<float> outputArray;
    std::tie(scaleInputToOutput, outputArray) = cvMatToOpOutput.format(inputImage);

    // Step 3 - poseの特徴点を演算する
    poseExtractorCaffe.forwardPass(netInputArray, inputImage.size());
    const auto poseKeyPoints = poseExtractorCaffe.getPoseKeyPoints();

    // Step 4 - poseの特徴点を表示する。
    poseRenderer.renderPose(outputArray, poseKeyPoints);

    // Step 5 - OpenPoseの画像形式からcv::Matの出力画像に変換する
    auto outputImage = opOutputToCvMat.formatToCvMat(outputArray);

    // ------------------------- Pose Estimationの終了 -------------------------

    // ------------------------- 結果の表示と終了処理 -------------------------
    // Step 1 - 結果の表示
    frameDisplayer.displayFrame(outputImage, 0); 
   // OpenCVの関数で代替する場合
    // cv::imshow("OpenPose Tutorial",outputImage)
    // cv::waitKey(0)

    // Step 2 - ログの表示
    op::log("Example 1 successfully finished.", op::Priority::Max);
    // ------------------------- 結果の表示と終了処理はここまで -------------------------

    return 0;
}

PoseKeyPoints(特徴点)の形式

 PoseKeyPointsはfloat型Arrayで定義されており、画像上のPose Estimationの結果が、(x,y,score)の順で並んでいます。(x,y)の座標位置は出力画像の解像度に変換されています。
 ”COCO”Modelでは 1人あたり18個のbody parts、" MPI”Modelでは 1人あたり15個のbody partsを出力します

 例えば、”COCO”Modelであれば、float型のデータが
  Noseのx,Noseのy,Noseのscore,Neckのx,Neckのy,Neckのscore,RShoulderのx,RShoulderのy,RShoulderのscore,RElbowのx,RElbowのy,RElbowのscore,,,,,,,,,,,
と、人数 x body parts(COCOなら18、MPIなら15)x 3(x,y,score)の個数分、格納されています。
 つまり、”COCO”Modelであれば、1人見つけるごとに54個のデータが格納され、100人見つけると5400個のデータが格納されます。

”COCO”Model

COCO ModelはCOCO 2016 Detection and Keypoint Challenges用に作成されたデータセットで学習したModelである。
http://mscoco.org/dataset/

    POSE_COCO_BODY_PARTS {
        {0,  "Nose"},
        {1,  "Neck"},
        {2,  "RShoulder"},
        {3,  "RElbow"},
        {4,  "RWrist"},
        {5,  "LShoulder"},
        {6,  "LElbow"},
        {7,  "LWrist"},
        {8,  "RHip"},
        {9,  "RKnee"},
        {10, "RAnkle"},
        {11, "LHip"},
        {12, "LKnee"},
        {13, "LAnkle"},
        {14, "REye"},
        {15, "LEye"},
        {16, "REar"},
        {17, "LEar"},
        {18, "Bkg"},
    }

" MPI”Model

   POSE_MPI_BODY_PARTS{
        {0,  "Head"},
        {1,  "Neck"},
        {2,  "RShoulder"},
        {3,  "RElbow"},
        {4,  "RWrist"},
        {5,  "LShoulder"},
        {6,  "LElbow"},
        {7,  "LWrist"},
        {8,  "RHip"},
        {9,  "RKnee"},
        {10, "RAnkle"},
        {11, "LHip"},
        {12, "LKnee"},
        {13, "LAnkle"},
        {14, "Chest"},
        {15, "Background"}
    };

所感

 まだ、公開したばかりということもあり、ドキュメントがあまり充実していませんが、このチュートリアルはかなり読みやすく、OpenCVに慣れた人であれば、カスタマイズしやすいコードになっている印象です。
 OpenPoseにはある特定の人間をトラッキングする機能は今のところ入ってなさそう(時系列データは使っていなさそう)、Kinectと違い2次元座標でPose座標がでてくる(Kinectは3次元座標)など、機能拡張の余地を感じます。