空飛ぶ気まぐれ雑記帳

主に趣味とかプログラミングについて扱います。

OpenCVのDeformable Part Modelを試してみた

はじめに

今回は、故あってOpenCVのDPM(Deformable Part Model)を試してみたので、それについて紹介したいと思います。

DPMって?

DPMとは物体を検出するアルゴリズムの1つで、入力画像からHOG特徴を抽出し、事前に学習しておいた物体全体のモデルと物体をパーツ毎に分けた変形可能なモデルの2種類を併用することで、ロバストに検出することができるアルゴリズムです。

結構古いアルゴリズムで、原著論文が2007年位なのですが、R-CNN(Region-based Convolutional Neural Network)と比較すると高速で、実時間で利用可能なためかロボティクス系の分野では未だにチラチラ見かけます。

まあ、R-CNNも段々と改良されて、高速になりつつあって、実時間で利用可能なアルゴリズムもあった気がするので、近いうちに撲滅されかねませんが。

なお、アルゴリズムは下記が詳しいです。

www.slideshare.net

OpeCVのDPM

OpenCV3.0以降ではOpenCV Contribに入っているのでそれを使います。
公式ドキュメントとサンプルコードを参考に試してました。

http://docs.opencv.org/trunk/df/dba/classcv_1_1dpm_1_1DPMDetector.htmldocs.opencv.org
github.com

サンプルコードでは、動画を前提としてフレームレートの計算とかしていますが、私は動画を用意する余裕が無かったので、該当部分の削除等行ったソースコードを用意しました。
なお、Windows環境の場合、OpenCV公式のサンプルコードだと改行コードでポシャるので、std::ifstreamの引数に注意が必要です。(下記サンプルでは修正済みです)

#include <opencv2/dpm.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

#include <filesystem>

#include <iostream>
#include <fstream>

#if defined(DEBUG) || defined(_DEBUG)
# pragma comment(lib, "opencv_core310d.lib")
# pragma comment(lib, "opencv_dpm310d.lib")
# pragma comment(lib, "opencv_highgui310d.lib")
# pragma comment(lib, "opencv_imgproc310d.lib")
# pragma comment(lib, "opencv_imgcodecs310d.lib")
#else
# pragma comment(lib, "opencv_core310.lib")
# pragma comment(lib, "opencv_dpm310.lib")
# pragma comment(lib, "opencv_highgui310.lib")
# pragma comment(lib, "opencv_imgproc310.lib")
# pragma comment(lib, "opencv_imgcodecs310.lib")
#endif

void help();
bool readImageLists(const std::string &file, std::vector<std::string> &imgFileList);
void drawBoxes(cv::Mat &frame,
  std::vector<cv::dpm::DPMDetector::ObjectDetection> ds,
  cv::Scalar color);

int main(int argc, char** argv) try
{
  const char* keys =
  {
    "{@model_path    | | Path of the DPM cascade model}"
    "{@image_dir     | | Directory of the images      }"
  };

  cv::CommandLineParser parser(argc, argv, keys);
  std::string model_path(parser.get<std::string>(0));
  std::string image_dir(parser.get<std::string>(1));
  std::string image_list = image_dir + "/files.txt";

  if (model_path.empty() || image_dir.empty())
  {
    help();
    return -1;
  }

  std::vector<std::string> imgFileList;
  if (!readImageLists(image_list, imgFileList))
    return -1;

#ifdef HAVE_TBB
  std::cout << "Running with TBB" << std::endl;
#else
#ifdef _OPENMP
  std::cout << "Running with OpenMP" << std::endl;
#else
  std::cout << "Running without OpenMP and without TBB" << std::endl;
#endif
#endif

  cv::Ptr<cv::dpm::DPMDetector> detector = cv::dpm::DPMDetector::create(std::vector<std::string>(1, model_path));

  cv::namedWindow("DPM Cascade Detection", 1);
  // the color of the rectangle
  cv::Scalar color(0, 255, 255); // yellow
  cv::Mat frame;

  const int MAX_LONGSIDE = 600;
  for (size_t i = 0; i < imgFileList.size(); i++)
  {
    std::vector<cv::dpm::DPMDetector::ObjectDetection> ds;

    cv::Mat image = cv::imread(image_dir + "/" + imgFileList[i]);
    if (image.cols > MAX_LONGSIDE)
    {
      cv::resize(image, image, cv::Size(MAX_LONGSIDE, static_cast<float>(MAX_LONGSIDE) * image.rows / image.cols));
    }
    else if (image.rows > MAX_LONGSIDE)
    {
      cv::resize(image, image, cv::Size(static_cast<float>(MAX_LONGSIDE) * image.cols / image.rows, MAX_LONGSIDE));
    }

    frame = image.clone();

    if (image.empty()) {
      std::cerr << "\nInvalid image:\n" << imgFileList[i] << std::endl;
      return -1;
    }

    // detection
    detector->detect(image, ds);
    
    // draw boxes
    drawBoxes(frame, ds, color);
    std::cout << "Frame:" << i << std::endl;

    // show detections
    imshow("DPM Cascade Detection", frame);

    cv::waitKey(0);

    cv::imwrite(image_dir + "/s" + std::to_string(i + 1) + ".jpg", frame);
  }

  return 0;
}
catch (std::exception& e)
{
  std::cerr << e.what() << std::endl;
  throw;
}

void help()
{
  std::cout << "\nThis example shows object detection on image sequences using \"Deformable Part-based Model (DPM) cascade detection API\n"
    "Call:\n"
    "./example_dpm_cascade_detect_sequence <model_path> <image_dir>\n"
    "The image names has to be provided in \"files.txt\" under <image_dir>.\n"
    << std::endl;
}


bool readImageLists(const std::string &file, std::vector<std::string> &imgFileList)
{
  std::ifstream in(file.c_str(), std::ios::in);

  if (in.is_open())
  {
    while (in)
    {
      std::string line;
      std::getline(in, line);
      if (line.empty())
      {
        continue;
      }

      imgFileList.push_back(line);
    }
    return true;
  }
  else
  {
    std::cerr << "Invalid image index file: " << file << std::endl;
    return false;
  }
}


void drawBoxes(cv::Mat &frame, std::vector<cv::dpm::DPMDetector::ObjectDetection> ds, cv::Scalar color)
{
  if (ds.empty())
  {
    return;
  }

  cv::rectangle(frame, ds[0].rect, cv::Scalar(0, 0, 255), 2);
  for (unsigned int i = 1; i < ds.size(); i++)
  {
    cv::rectangle(frame, ds[i].rect, color, 2);
  }
}

また、今回は手間を省くために学習済のモデルを使うので、下記リンクのmotorbike.xmlを用います。

github.com

実践

今回紹介しているサンプルでは、下記のように引数を与える必要があります。

TestDPM motorbike.xml <画像が保存されているディレクトリ> 

また、画像が保存されているディレクトリに、files.txtというファイル名の画像ファイルを改行区切りで列挙する必要があります。
なお、画像ファイルのパスはfiles.txtからの相対パスでOKです。

実際にバイクに対して適応した結果は下記の通りです。赤色が最も評価値が高い矩形で、黄色がその他の候補
4枚目は、さすがに斜めから撮影した画像を学習していないせいか、明らかに失敗していますが、他は結構色が似ていて難しいものもあるのですが、検出精度は良好。
ただ、メモリの使用量が尋常ではなく、600✕450の画像ファイルを処理するのに1GBほどのメモリを必要とします。

f:id:elda27:20170329005714j:plainf:id:elda27:20170329005719j:plainf:id:elda27:20170329005723j:plainf:id:elda27:20170329005727j:plainf:id:elda27:20170329005732j:plainf:id:elda27:20170329005736j:plainf:id:elda27:20170329005743j:plain

おわりに

結構いい感じに動いたので、万々歳ですね。
その割に、日本語でDPMについて検索しても使ってみたみたいな話が非常に少ない(論文はちらほら見るのですが、ブログで取り上げてる例が少ない)のが不思議なところです。
次回は自力でモデルを作りたいと思います。