読者です 読者をやめる 読者になる 読者になる

空飛ぶ気まぐれ雑記帳

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

OpenCVのDeformable Part Modelを試してみた

C++ Computer Vision 機械学習

はじめに

今回は、故あって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について検索しても使ってみたみたいな話が非常に少ない(論文はちらほら見るのですが、ブログで取り上げてる例が少ない)のが不思議なところです。
次回は自力でモデルを作りたいと思います。

古き悪しき全プロジェクト共通のVC++ディレクトリ的なSomethingを設定する方法

C++ Windows

むか~し、むかし。Visual Studio 2008では設定>プロジェクトとソリューションからVC++ディレクトリの設定を行うことが出来た。
この設定では、全てのプロジェクトに共通した設定を書き込むことが出来、大変重宝しておった。
その一方で、色々な問題からVisual Studio 2010の頃には廃止された。

それ以来、プロジェクトに個別で設定するか、ユーザープロパティシートに書き込んで無理矢理に再現するかの2択になっている。
それで、先日Visual Studio 2017にアップデートした際に、再設定する必要があったので、書くネタにも困っていたところだし、メモとして残しておく。

ユーザプロパティシートは"%USERPROFILE%\Local\Microsoft\MSBuild\v4.0"にあるので、自分の環境に合わせて、Win32(x86)、Win64(x64)を編集する。
なお、今回紹介する設定方法はx86、x64共用なので、どちらでも同じ内容を書けば良い。

以下は実際の設定内容。

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets">
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <ItemDefinitionGroup />
  <ItemGroup />
  <PropertyGroup>
    <!--プラットフォーム名。x86かx64か-->
    <ShortPlatform Condition="'$(Platform)' == 'Win32'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)' != 'Win32'">x64</ShortPlatform>
    <!--ツールセット名。v141がVisual Studio 2017-->
    <Toolset Condition="'$(PlatformToolset)' == 'v120'">vc12</Toolset>
    <Toolset Condition="'$(PlatformToolset)' == 'v140'">vc14</Toolset>
    <Toolset Condition="'$(PlatformToolset)' == 'v141'">vc14</Toolset>
  </PropertyGroup>
  <Choose>
    <When Condition="'$(PlatformToolset)' == 'v120' ">
      <PropertyGroup>
        <LibraryPath>D:\library\lib;$(LibraryPath)</LibraryPath>
        <IncludePath>D:\library\include;$(IncludePath)</IncludePath>
      </PropertyGroup>
    </When>
    <When Condition="'$(PlatformToolset)' == 'v140' AND '$(Platform)' == 'Win32'">
      <PropertyGroup>
        <LibraryPath>D:\vs14library\lib;D:\vs14library\$(ShortPlatform)\$(Toolset)\lib;D:\vs14library\lib\vc_lib;$(LibraryPath)</LibraryPath>
        <IncludePath>D:\vs14library\include;$(IncludePath)</IncludePath>
      </PropertyGroup>
    </When>
    <When Condition="'$(PlatformToolset)' == 'v140' AND '$(Platform)' != 'Win32'">
      <PropertyGroup>
        <LibraryPath>D:\vs14library\lib;D:\vs14library\$(ShortPlatform)\$(Toolset)\lib;D:\vs14library\lib\vc_x64_lib;$(LibraryPath)</LibraryPath>
        <IncludePath>D:\vs14library\include;$(IncludePath)</IncludePath>
      </PropertyGroup>
    </When>
    <When Condition="'$(PlatformToolset)' == 'v141' AND '$(Platform)' == 'Win32'">
      <PropertyGroup>
        <LibraryPath>D:\vs14library\lib;D:\vs14library\$(ShortPlatform)\$(Toolset)\lib;D:\vs14library\lib\vc_lib;$(LibraryPath)</LibraryPath>
        <IncludePath>D:\vs14library\include;$(IncludePath)</IncludePath>
      </PropertyGroup>
    </When>
    <When Condition="'$(PlatformToolset)' == 'v141' AND '$(Platform)' != 'Win32'">
      <PropertyGroup>
        <LibraryPath>D:\vs14library\lib;D:\vs14library\$(ShortPlatform)\$(Toolset)\lib;D:\vs14library\lib\vc_x64_lib;$(LibraryPath)</LibraryPath>
        <IncludePath>D:\vs14library\include;$(IncludePath)</IncludePath>
      </PropertyGroup>
    </When>
  </Choose>
</Project>

以上なのでが、この設定ファイル、記述内容から何となくお察しかもしれないが、Visual Studio 2012の頃から使いまわしている。
そのため、中々にカオスなことになっているが悪しからず。

一応各部の解説をすると、Chooseタグが所謂if文にあたるタグで、WhenタグのCondition属性に式を設定する。このときif文内で使える変数はPropertyGroup内で宣言?した変数のみとなっている。
また、というのがライブラリのディレクトリで、*.libファイルが保存されているディレクトリを足せば良い。同様に、にはヘッダファイルのディレクトリを設定すれば良い。
つまるところ"D:\vs14library"とか"D:\library"とかなってる部分を自分の環境に合わせて変えれば良い。また、私の環境の場合、cmakeを使ってインストールしたライブラリをすべて1つのディレクトリに集約しているので1つないし、2、3個しかパスを足していないが、各ライブラリを別個のディレクトリにインストールしている場合、各ディレクトリを設定する必要がある。

ぶっちゃけ、Microsoftが公開している情報にたどり着けた試しがないせいか、実に5年間この説明があっているのかすらよくわからない。ただ、動いているという事実だけはあるので、まあいいかと捨て置いているので、誰か詳しい人いたらコメントください。

Visual Studio 2017を試してみた

C++

はじめに

そもそもVisual Studio 2017の正式版がリリースされたのは3/7のことで、リリースノートによるとVisual C++の更新は軽微なもので、CMakeのサポートがメインになっている模様。
実際、ツールチェーンのバージョンも141とVisual C++14のマイナーアップデートにあたるらしい。
個人の開発だとC++しか使わない私としては、あまり代わり映えしない内容では有るが、一応実際に使った雑感について述べていく。

www.visualstudio.com

主要な新機能

概要

主要な新機能は下記の通り。
IDE全体で見ると結構変更点もあるんだなと思います。
あ、あくまでも、C++er視点なので、それ以外の変更点は山ほどあると思いますが悪しからず。

  • Light Weight Solution Load
  • CMakeのサポート
  • Linuxを対象としたビルド

Light Weight Solution Load

Light Weight Solution Loadとは、その名の通り高速なSolution読み込み機能のことで、Solution読み込み時に全てのファイルの読み込みおよびInteli Senseによる解析を行わず、ユーザが開いたファイルなどを優先的に行うそうです。
これにより、これまで膨大な数のプロジェクトを読み込む際にかなり長い時間を待たされていたのですが、それがマシになる機能です。
実際に79プロジェクトから構成されるSolutionを読み込んで見ましたが、体感で3倍程でしょうか。
本当に早いです。

blogs.msdn.microsoft.com

しかし、この機能はデフォルトではSolution毎に右クリックで表示されるメニューからEnableする必要があります。
これを全てのSolutionに対して有効にする場合、Tools>Options>Projects and Solutions>General の順に選択し、ページ下部のLightweight Solution loadのチェックボックスをチェックすれば良いそうです。

CMakeのサポート

「フォルダを開く」機能が追加されており、CMakeLists.txtを配置することで、Visual Studioが自動で他のディレクトリ(AppData配下に)を作ってCMakeを実行してくれるようになりました。
これにより、従来一度他のエディタでCMakeLists.txtを書いてから、CMakeを実行して、やっとVisual Studioからファイルの編集を行うことができる。というよう感じだったのが、Visual Studioのみで完結してCMakeを用いたプロジェクトを構築できるようになりました。

marketplace.visualstudio.com

また、CMakeSettins.jsonという名前のファイルを配置することで、Visual Studioのバージョン、ビルド対象のアーキテクチャ毎に個別に詳細なオプションを設定することができるようなっています。

ただ、追加されたばかりの機能というだけあって、不満なところが結構あります。
まず、補完機能についてです。IDEなので補完機能も一応付いていますが、CMake tools for Visual Studioに比べても若干貧弱かなと思う所があります。
つぎに、CMakeの内部バージョンについてです。Visual Studioが呼び出すCMakeはVisual Studioが内包しているバイナリなのですがそのバージョンが3.6.0となっている点です。現在、CMakeの最新バージョンが3.8.0なので、それ比べると2バージョン前で若干古いですね。まあcontinue文がサポートされているバージョンなのでまだましかも知れませんが。
そして、最後にCMakeが実行されるタイミングについてです。CMakeが実行されるタイミングはフォルダ配下のCMakeに関係するファイル(CMakeLists.txtだけでなくソース中でincludeされているファイルも含まれている模様)を保存した際に毎回実行されるため、複数のCMakeに関係するファイルを変更する場合、毎回CMakeを実行する必要があって結構厄介です。

Linuxを対象としたビルド

つまるところVisual GDBのような機能が実装されているらしく、これで有料のVisual GDBを買う必要は無いなーって話ですかね。
現状稼働していLinux PCがないので、動作確認はしていませんが、どうなんですかね?
また試したら記事を書こうと思います。

はじめてWebアプリケーションを作った話

Python heroku

はじめに

プログラミングを初めて早6年と3ヶ月。
これまでに、C++でアプリケーションを作ったことは数多くあれど、Webアプリケーションは作ったことがありませんでした。
ただ、某FEの試験を受けたり、QtからGoogleAPIをJavascript経由でバインディグしたりと色々やっていた影響でHTMLやJavascriptはそこそこ書けたのでぶっつけ本番でもなんとかなるかと思って実際に作ってみました。
が…思ってた異常に大変で躓いたところも多かったので、herokuを使ってPythonでWebアプリを作るまでに躓いたところを紹介したいと思います。
下記は実際に作ったアプリケーションへのリンクです

http://ppt-scripter.herokuapp.com/ppt-scripter.herokuapp.com

何つくったの

ざっくりと説明するとMicrosoftのPower Pointで作ったスライドからノートのみを抽出するアプリケーションです。
実は前回記事のGUIアプリケーションをやめて、Webアプリケーションにしてみました。

elda27.hatenablog.com

Webアプリの構成について

今回はherokuの公式が紹介しているdjangoで作るのではなく、もう少しlightにアプリを作れるらしいflaskを使って作ってみました。
また、pythonは2系ではなく、3系を使っています。

躓いたところ

以下は実際に躓いたところについて紹介していきます。
flaskの絡みはググれば出てくるので、多くのことで悩むことが無かったのですが、herokuの仕様が?すぎて辛かったです。
なお、私は初心者なので、もしその解決方法は不味いよってところがあったら教えてくれると嬉しいです。

Javascriptで躓いたところ。

Postでファイルをアップロードする時、input type="file"とDrag&DropAPIの両方を使う場合

Google Chromeの場合input type="file"にDrag&Dropしてやるとそのままアップロードできるのだけど、Edgeだとそんなことが無かったりする。
そのため、Drag&Dropでファイルを受け取る場合、JavascriptのDrag&DropAPIを使えば良いらしいのだけど、Formによる送信と共存させる方法が最初わからなかった。
ただ、まあ良くよく調べてみると簡単な話で、input type="file"の時とDrag&Drop APIで取得したファイルそれぞれを自前でnewしたFormDataに設定してやればいいそうです。

  function uploadFiles(files)
  {
    filelist_dom.children().remove()
    var fd = new FormData()
    for(file of files)
    {
      fd.append('fileinputs', file, file.name)
    }

    $.ajax({
      type:'POST',
      contentType: false,
      processData: false,
      url: '/convert',
      data: fd,
      dataType: 'json'
    })
  }

  $('#file-upload').on('click', function(event) {
    event.preventDefault()
    $('#hidden-file-upload').click()
  })

  $('#hidden-file-upload').change(function(event) {
    console.log('File upload via hidden input')
    event.preventDefault()
    uploadFiles(this.files)
  })

herokuで躓いた所

必要なファイルについて

前述の通り公式で紹介されているPythonによるWebアプリケーションはdjangoを用いたアプリケーションで、flaskを用いたアプリケーションではありません。
そのため、必要なファイルに若干の違いがあります。

github.com

結局Webアプリケーションを実行するまでに必要な最低限のファイルは下記の3つです。

  • requirements.txt:必要なパッケージをリストするファイル
  • runtime.txt:Pythonのバージョンを指定するファイル
  • Procfile:Webアプリ実行のためのコマンドを書くファイル
requirements.txt

サーバサイドでPythonのパッケージをインストールする際に使われるファイル。
pipを使って出力すればいいらしい。

pip freeze > requirements.txt 

Anacondaを使っているなら新しく環境を作成して必要なパッケージだけをインストールして実行すれば良いと思う。

runtime.txt

herokuではPython2系がデフォルトになっているので、Pythonのバージョンを指定してやる必要があります。下記リンクを見ると使用できるPythonのバージョンが書かれているので、今回はPython3.6.0を設定しました。

devcenter.heroku.com

実際に設定ファイルに書いた内容は下記のとおりです。

python-3.6.0
Procfile

そもそもProcfileってなんぞや。という所から始まったのですが、どうやらProcfileというのは、foremanという1アプリケーションを複数プロセス管理するツールのためのファイルだそうです。
それで、ファイルフォーマットとしては、Webアプリケーションを実行する際のコマンドを列挙するファイルだそうで、下記のようなフォーマットで書くそうです。

<Process type>: <Command>

herokuでこのファイルを書く場合、Http通信のやりとりをするコマンドのに「web」を使えば良いそうです。実際にgunicornを使ってwsgi対応のWebアプリケーションとして実行する場合、下記のように書けば良いそうです。

web: gunicorn <mainにあたるスクリプトファイル(拡張子なし)>:app --preload --log-file=-

ここで、ポイントなのが--preloadオプション。herokuのtutorialでは無くても動くようなことが書いてあったのですが、私の環境だと動かなかったので、追加しました。

おわりに

今回は、herokuでWebアプリケーションを作るまでに躓いたところをまとめました。
デザインとかはお察しですが、そのうちもう少しマシにしようと思っています。

PythonでGUIアプリケーションを作ろうとした話

Python NSIS

はじめに

今回の話はタイトルどおり、Pythonを使ってGUIアプリケーションを作ろうとした話です。
これまでに、C++でアプリケーションを作ったことは数多くあれど、他の言語でアプリケーションを作った経験は少ないものです。
特に、スクリプト言語に絞ると無いと言っても過言ではないでしょう。

というのも、スクリプト言語というと、研究で何かしらの実験をして、その実験結果を整理するためのもの。
というイメージが強く、どうしても書捨てなイメージが強かったからです。

その一方で、C++でアプリケーションを作ると常に膨大な行数のソースコードとファイル数で、1つ作り上げるのに結構な時間を要するという至極一般的な問題がありました。
その上、実装中に「この書き方はできるだろ」と思ってたら実は駄目ですということがママあり*1、急遽設計を変更する羽目に会うことがあります。

そのため、あまりC++でアプリケーションを書ききったことがないという現実があります*2

いくらなんでもそれは…と思い至り、少しでも完成率を上げるために、それならスクリプト言語で書けば良いんじゃねと結論に至りました。

そんなわけで、今回紹介するのはPythonGUIアプリケーションを作る過程の話を紹介したいと思います。

GUIライブラリの選定

tkinter

言わずと知れた、Python付属のGUIライブラリ。
比較的簡単に使えるらしいのですが、どうも完成しても見た目があまり良くないそうで、ユーザー数が少ないそうです。
実際Googleで検索してもあまり込み入った話を書いたブログが見つからなかったので、実際少ないのでしょう。
というわけで、今回はボツ案で。

wxPython

wxWidgetsPythonバインディング
C++版に比べて使いやすいらしいのですが、wxWidgetsWindows版はとにかくバグが多い*3上に、かなりお作法に厳しいので、あまり好きではありません。
というわけでボツですね。

PyQt5

Qt5のPythonバインディング
文字列を標準のstring型ではなく、QString型をバインディングしたクラスを使ったりと結構面白いバインディングの仕方をしているライブラリです。
イベントの扱いにC++版と若干違いがあって、connectでSIGNALとSLOTというマクロを使って接続していたのですが、signal型のconnectを使ってslotを直接接続する感じになっています。
書いていてよくわからないので、例をあげます。実際にボタンのクリックイベントを使う時は下記のようにします。

button.clicked.connect(self.onClicked)

ただ、このライブラリ。
色々あって、ライセンスがLGPLではなく、GPLでしかライセンスされていません。
そのため、開発したアプリケーションにもGPLでライセンスをする必要があるため、注意が必要です。

そのため、今回はできればGPLでライセンスしたく無かったのでボツにしました。

PySide

PyQt5の作者が作ったLGPLでライセンス可能なQt5のPythonバインディング
PyQt5との違いは文字列型にPython標準のstringを使っているなどなど。
詳しくはググってください。

基本的にはC++版とのQtと使い方に違いは無いので、慣れていることもあって、今回はPySideにしました。

アプリケーションのデプロイ

Pythonで作ったアプリケーションのデプロイについてです。
通常Pythonスクリプト言語ですので、実行にインタプリタを必要とします。
しかし、それだと、自分が作ったアプリケーションをプログラマでないユーザに使ってもらおうとすると、ユーザにもPythonインタプリタをインストールすることを強いる必要があります。
それを回避するためのデプロイ用のアプリケーションを選定します。

今回作成したアプリケーションはPython3系で書いたので、Python3系で問題なく動作するアプリケーションが必要です。

下記で紹介しているアプリケーション全てpipからインストール可能なのでインストールも楽に済みます。

py2exe

多分一番有名なデプロイアプリケーション。
py2appなど、Windwos環境以外に向けた実装も用意されているため一応マルチプラットフォームで活用することが出来ます。
しかし、私の環境(Python3.5.2)ではどうもうまく動作せず。
実行中にエラーメッセージを吐いて異常終了してしまったので、うまくデプロイすることが出来ませんでした。

PyInstaller

py2exeでは、アプリケーションのデプロイに設定ファイルとしてのPythonスクリプトが必要なのですが、こいつはそれを必要としません。
ソースコードを解析して、必要なDLLを決定しているそうなのですが、その解析があまり賢くないため、過剰にDLLを集めてしまうそうです。
ただ、こいつの場合、entryポイントエラー(だったかな)DLL不足ではない謎の例外を吐いて、私の環境では上手く動いてくれなかったので、諦めました。

cx_Freeze

前2つと何が違うかと言われると「なんだろう?」ってなるくらいに、よく調べていません。
それはさておき、こいつは、py2exeと同様、設定ファイルを必要とするタイプですが、結構安定して動作するそうです。
それで実際に試してみると、一部DLLが不足してEXE起動時にDLL無いよって例外吐いて落ちてしまいました。
なんだか、進歩があったようなので、もう少し調べてみると、そういう場合はpackagesに含まれなかったライブラリを手動で足せば良いそうです。

実際に書いたsetup用のスクリプトは下記の通りです。
いまいちpackagesとincludesの違いはわかりませんが…とりあえず、こう書くそうです。

import sys
from cx_Freeze import setup, Executable

base = None
if sys.platform == 'win32':
	base = 'Win32GUI'

packages = ['lxml']      # これがデプロイされなかったライブラリ
includes = ['PySide']    # デプロイ時に含めて欲しいライブラリ 
excludes = ['PyQt4']     # デプロイ時に含めて欲しくないライブラリ

setup(  
	name = 'Scripter',  # アプリケーション名
	version = '0.1',    # アプリケーションのバージョン
	description = '',
	options = { 'build_exe' : {'includes':includes, 'excludes':excludes, 'packages': packages} },
	executables = [Executable('main.py', base=base, icon='ICON.ico', targetName='Scripter.exe')] 
	#出力するアプリケーション名やアイコンファイルを設定
)

インストーラの作成

インストーラの作成というとアプリケーションを本格的にユーザにデプロイする場合にしか必要としないため、あまり需要がない or そういうことをする人は自分で調べられるからなのかはわかりませんが、このあたりは日本語資料があまりないので、とりあえず書いておこうと思いました。

Install Shield

いわずと知れた、インストーラの代名詞的存在。いろんなソフトで見かけるかと思います。
しかし、こいつ、ライセンス条項が結構怪しいらしく、使用するPCをInstallShieldの開発元に監視されるらしく結構やばい。
というわけで、今回は候補から外します。

WiX

Microsoftが現在オープンソースで開発しているアプリケーションで、XMLベースのマークアップを記述することでインストーラを作成することができます。
ただ、その仕様とXMLの言語仕様が相まって書くのが非常にめんどくさい。
その上、資料と言える資料が英語Wikiのみで学習コストも高いですし、そもそもファイル数が増えると書くのがめんどくさい。

NSIS

NSIS Wiki
というわけで、今回の本命。
こいつはWiXと同じくスクリプト形式のインストーラ作成アプリケーションなのですが、文法がスペース区切りのbashの設定ファイルのような形式になっていて、WiXとくらべて書きやすい文法になっています。
また、こいつの優れている点としてzipファイルからインストーラを自動生成できる点があります。
その設定も非常に簡単で、zipファイルを選択して、あとはOKボタンを押すだけでできます。

おわりに

今回はPythonアプリケーションをデプロイしてインストーラを作成するところまで紹介しました。特にインストーラを作る部分は初めてやってみましたが非常に簡単でした。
が、作ったアプリケーションをいかに公開するかという難題がまだ残っています。
それについては少々悩んでいるので、また今度。

*1:え、文法を覚えて無いのかって?いいえ、そういう訳ではなく。Qtお前のことだよ。

*2:だいたい10個に3個ぐらいの割合でしか完成までたどり着きません

*3:OpenGLのコンテキストが解法されずメモリリークするとか、Sliderオブジェクトの再描画がされないとか

CNTKがガチでクソだったと思ってたらいつの間にかそうでも無くなってた話

機械学習 深層学習

はじめに

前回の記事を書いている最中に思い出したのだけれども、CNTKがガチでクソだった話を書こうと思ったらいつの間にかそうでも無くなってた話。

近況と生存報告と人気ページについて - 空飛ぶ気まぐれ雑記帳

そもそもCNTKって

CNTKとはMicrosoft社が提供しているオープンソースディープラーニングのライブラリで、マルチGPU性能が非常に高いことと、複数台のPCで実行可能なことを売りにしているディープラーニングライブラリです。
どれくらいマルチGPU性能が高いかと言うと、そもそもマルチGPUに対応していないTheanoは置いておいて、Tensor FlowやTorch、Caffeの倍近い性能を持つ他、2台のPCに各4基、合計8基のGPUを搭載して計算すると、他のライブラリよりも圧倒的に早いZEってライブラリです。
さらに、Microsoftが出しているだけあって、Windows対応を謳っている数少ないディープラーニングのライブラリでもあります。

github.com

CNTKのどこが糞だったん

これはあくまでもメジャーリリース当時の話です

私も上の説明を見た時はなんて素敵なライブラリなんだと、目を輝かせたものです。
ただ、それも幻影でした。

最初、Readmeとかよく読まず、とりあえず"cmake --build . --target Debug"とかでVisual Studioを起動せずに適当にビルドしようとしてたのですが、どうも成功せず。
とりあえず、slnファイルを開いてVisual Studioからビルドすかと思った矢先。なんとプロジェクト名に括弧書きで「Only Visual Studio 2013」(だったかな)と書かれていたのです。
それで、詳しく調べてみると、なんとこのCNTK、Visual Studio 2013にしか対応していないガチクソだったのです。

だったのですが…

今日、この記事を書こうとgithubを見てみると、最新のバージョンだとVisual Studio 2015に対応しているそうです。

github.com

という訳で一転して、CNTKは神ライブラリかもしれません。
また、暇になったら記事を書くかもしれませんが、、、いつに成ることやら

近況と生存報告と人気ページについて

先月からクソ忙しくて、録に更新してこなかったけどいい加減更新しようと思いいたった。のだけれども、どうも記事にできる内容がない。
いや、この三ヶ月、何もしてこなかった訳ではない。一応、研究成果の方はしっかり出して、それ相応の評価を頂戴した。
ただ、このブログに書けるような内容が、どれも進捗半端で紹介しかねるという状況だった。例えば、Webアプリ作った話とか、tensor flowのWindows版をインストールして使ってみた話とかCNTKがクソだった話とか、DLIBでDCGANを実装しようとして死にそうになってる話とか、もう色々あるんだけど……。
それで、とりあえず。はてブの編集ページへ飛ぼうとはてブのトップを開くと、右上のアカウント名のところに赤い通知が来ていた。

PV数が100を超えました

これを見た瞬間「はぁ?」となり、さらにPV数を確認して「……」となった。なんと、月間PVが100と言わず404になっていた。これまで、と言っても最後に更新したのは12月の話だが、その時は月間PV数なんて50を超えれば良いほうだった。それがほったらかしにした2ヶ月の間に何があったのかという話だ。

それで、記事の人気順を出して見るとその原因が何となく分かった。

総アクセス数に占める割合 ページ名
23% C++でDNNが使えないなんてあるわけないよ in Windows
20% CUDA 8.0がいつの間にかリリースされててVisual Studio 2015 Update 3でも使えるようになっていた話
11% DLIBでCNNを使ってみた in Windows
11% DLIBのコンパイル For Debugビルド
6% DLIB with CUDAでCNNを使ってみた in Windows
4% Windowsffmpegを使ってみた話
3% /category/機械学習
2% /category/dlib
2% GLEWをVisual Studio 2015とCMakeでビルドするときの注意点
2% ブログトップ
2% OpenGLでズームを実装する方法
1% GPGPUって色々あるけど結局どれがいいの
1% SFINAEやテンプレートの部分特殊化で気をつけたいこと
1% Visual Studio で拡張子のないC++ヘッダーをIntelli Senseで表示する方法
0% PythonでDLLを使う時の話
0% /category/深層学習
0% Visual Studioユーザーに送るCMakeテクニック☆ (1)
0% cygwinでFindOpenCVするとエラーになる
0% Visual Studioユーザーに送るCMakeテクニック☆ (2)


あー、ただのディープラーニングか。実際、「C++ DNN」とか「DLIB DNN」でGoogle検索を掛けると、ビックリするほど上位に出てくるから、やはりそういうことらしい。

とりあえず、今後もディープラーニングの話は書いていくつもりですのでよろしくお願いします。あ、それとCMake絡みの記事も読んでくれてもええんやで。