【MediaPipe】hand_trakingを使用して機械学習用に手の画像を取り出してみた

# mediapipeで機械学習用に手の画像を取り出してみた
[前回](https://qiita.com/kirei7jaiann/items/7c79736e8fb315565331)、日本手話の数字をパターンマッチングで読み取ってみましたが、手型によって条件設定が厳しいものがありました。機械学習にトライする前のデータセット作成のためにmediapipeで手の画像を取り出してみました。

## 環境
- mediapipe：v0.7.11(メイン機/サブ機)で動作確認
- Linux：GPUで動作確認、メイン機、Ubuntu18.04
- mac：cpuで動作確認、サブ機、Catalina10.15.7、MacBook Pro

## 取得できる手の画像ファイル
取得する画像量の調整ととカラー修正は可能です。hand_trackingとmulti_hand_trackingの両方で動作します。下の画像はmulti_handをGPUで動かした場合の取得画像です。右手だけカメラで取りましたので左手の分の画像がゴミとして出ております。データセットにする際にはピックアップしてラベル付けするので手の検出に失敗した際のゴミ画像もそのままでよしとしています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681426/1b669bbc-1980-a215-589d-fa3b2b4ec601.png"width=50%>

## 参考にさせて頂いたページ・資料
はじめは手の矩形を描画している数値から画像を切り出そうと試行錯誤しておりました。「[MediaPipeのpre/post-processメモ](https://asus4.hatenablog.com/entry/2020/09/04/_MediaPipe%E3%81%AEpre/post-process%E3%83%A1%E3%83%A2)」の記事をみてmediapipeのモデルに画像渡すときに同じ処理やってるじゃん！となりまして、モデルに渡している画像を保存するだけでやりたいことが実現できました。

## 編集内容概要
mediapipe/calculators/image/BUILD に1行追加

```
cc_library(
    name = "image_transformation_calculator",
    srcs = ["image_transformation_calculator.cc"],
〜〜略〜〜
    deps = [
        ":image_transformation_calculator_cc_proto",
        "//mediapipe/gpu:scale_mode_cc_proto",
        "//mediapipe/framework:calculator_framework",
        "//mediapipe/framework/formats:image_frame",
        "//mediapipe/framework/formats:image_frame_opencv",
        "//mediapipe/framework/port:opencv_core",
        "//mediapipe/framework/port:opencv_imgproc",
        "//mediapipe/framework/port:ret_check",
        "//mediapipe/framework/port:status",
        "//mediapipe/framework/port:opencv_highgui", #ここの一行だけ追加
    ] + select({
        "//mediapipe/gpu:disable_gpu": [],
〜〜略〜〜
```

mediapipe/calculators/image/image_transformation_calculator.cc に追加

```
〜〜略〜〜
#include "mediapipe/framework/port/status.h"
#include "mediapipe/gpu/scale_mode.pb.h"
#include "mediapipe/framework/port/opencv_highgui_inc.h" //追加

#if !defined(MEDIAPIPE_DISABLE_GPU)
#include "mediapipe/gpu/gl_calculator_helper.h"
〜〜略〜〜

//画像のファイル名にタイムスタンプをつけて保存するための変数を追加
char nowChar[80];
int nowInt;
int tmpNumber = 1;
〜〜略〜〜

::mediapipe::Status ImageTransformationCalculator::RenderCpu(  //CPUの場合
〜〜略〜〜
  std::unique_ptr<ImageFrame> output_frame(
      new ImageFrame(format, output_width, output_height));
  cv::Mat output_mat = formats::MatView(output_frame.get());
  flipped_mat.copyTo(output_mat);

  //画像量の調整、ファイル名へのタイムスタンプの追加は「GPUの場合」をご参考下さい
  cv::imwrite("保存ファイル名", output_mat);

  cc->Outputs()
      .Tag(kImageFrameTag)
      .Add(output_frame.release(), cc->InputTimestamp());
〜〜略〜〜

::mediapipe::Status ImageTransformationCalculator::RenderGpu(  //GPUの場合
〜〜略〜〜
  // Execute GL commands, before getting result.
  glFlush(); 
  auto output = dst.template GetFrame<GpuBuffer>();

  //ここから追加
  tmpNumber = tmpNumber + 1;
  if(tmpNumber % 3 == 0){  //同じような画像が出すぎるので画像の量を調整
      time_t now = time(NULL);
      struct tm *pnow = localtime(&now);
      nowInt = strftime(nowChar,sizeof(nowChar)-1,"%Y%m%d%H%M%S_",pnow);
      tmpNumber = tmpNumber + 1;
      const char *tmpChar =  std::to_string(tmpNumber).data();
      strcat(nowChar,tmpChar);
      std::string tmpString{nowChar};

      //GpuBufferをcv::で処理できるようにコピペ、内容よくわかっておりません
      std::unique_ptr<mediapipe::ImageFrame> output_frame;
      output_frame = absl::make_unique<mediapipe::ImageFrame>(
              mediapipe::ImageFormatForGpuBufferFormat(input.format()),
              output_width, output_height,
              mediapipe::ImageFrame::kGlDefaultAlignmentBoundary);
      gpu_helper_.BindFramebuffer(dst);
      const auto info = mediapipe::GlTextureInfoForGpuBufferFormat(input.format(), 0);
      glReadPixels(0, 0, dst.width(), dst.height(), info.gl_format,
                  info.gl_type, output_frame->MutablePixelData());

      cv::Mat output_frame_mat = mediapipe::formats::MatView(output_frame.get());
      //RGBの並び変えないと宇宙人みたいな顔色になります
      //cv::cvtColor(output_frame_mat, output_frame_mat, cv::COLOR_RGB2BGR);
      cv::imwrite("保存先" + tmpString + ".jpeg", output_frame_mat);
  }
  //追加ここまで

  cc->Outputs().Tag(kGpuBufferTag).Add(output.release(), cc->InputTimestamp());
〜〜略〜〜
```

## 所感
mediapipeを通してデータセットを作成して、必要なモデルを作って、mediapipeの処理にモデルを追加するという実験をしてみたいと思います。