空飛ぶ気まぐれ雑記帳

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

OpenCV 4.2.0のビルドエラー対策

未だにこのブログを見る人がいたのと気が向いたので更新しています。

この記事はカスだ

とりあえず、OPENCV_PYTHON3_VERSIONはBOOLではなく、STRINGで指定するものだ。 しかも普通にバージョンを指定すべき変数だ。 少なくともOpenCV 3.4のブランチを見るとOPENCV_PYTHON3_VERSIONはPython3のバージョンを指定するための変数だった。 世間にあふれる情報が正しいとは限らないということを改めて感じたので、戒めに記事事態は残しておく。

というか、解決策気づいてから同じ変数が二つ並んでいるのに気づかんかったのか。。。

最近の困りごと

なぜかOpenCVをビルドする機会があって、armv7でビルドするとエラーがでないのに、x64でビルドするとcmakeエラーがでるという不具合に見舞われて怒っていた。 より具体的に言えばPython3向けのopencvをビルドしようとするとエラーが出るという話であるが。 再現可能なDockerfileは以下の通り。

FROM alpine:3.11
RUN apk update && apk add boost cmake make clang gcc \
  pkgconfig \
  linux-headers\
  python3\
  curl\
  ninja\
  tar\
  g++\
  python3-dev\
  musl\
  musl-dev

RUN pip3 install numpy

WORKDIR /tmp
RUN curl -sSL https://github.com/opencv/opencv/archive/4.2.0.tar.gz | tar xz
RUN mkdir build-opencv && cd build-opencv \
  cmake -G Ninja \
    -D CMAKE_BUILD_TYPE=Release\
    -D CMAKE_INSTALL_PREFIX=/usr/local\
    -D OPENCV_PYTHON3_VERSION=ON\   <----これを消せば問題なく動いた。。。
    -D BUILD_TESTS=OFF\
    ../opencv-4.2.0

このコードはミニマムなDockerfileでないので実運用上はpython3-devみたいなのはapk add時に--virtual-envを使って最後にpurgeするようにしたほうが良い。

それはさておき、このDockerfileをビルドするとcmakeに失敗してエラーがでる。

-- Found PythonInterp: /usr/bin/python2.7 (found suitable version "2.7.16", minimum required is "2.7")
-- Could NOT find PythonLibs (missing: PYTHON_LIBRARIES PYTHON_INCLUDE_DIRS) (Required is exact version "2.7.16")
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named numpy.distutils
CMake Error at cmake/OpenCVUtils.cmake:131 (find_package):
  find_package called with invalid argument "ON"
Call Stack (most recent call first):
  cmake/OpenCVDetectPython.cmake:58 (find_host_package)
  cmake/OpenCVDetectPython.cmake:280 (find_python)
  CMakeLists.txt:585 (include)
-- Python is not found: ON EXACT

要約するとPythonインタプリタを探そうとすると2系が引っかかる。 また、Python3を見つけようとしない。 見つけようとしない原因はfind_package called with invalid argument "ON"にあって、謎の引数ONが与えられているところにある。

解決方法

cmakeのオプションに「-DOPENCV_PYTHON3_VERSION=3.8」を追加する

詳細

しかたがないのでソースコードを読む。 これまた面倒だが、エラーが出ているのは毎度おなじみOpenCVが中で書いているcmakeのコードだ。 OpenCV 2.X系をCygwinでビルドする時にFind~が死んでオコになった記憶があるので、あまり触りたくないのだが仕方がない。 ちなみに以下は280行目の呼び出し部分だ。

find_python("${OPENCV_PYTHON3_VERSION}" "${MIN_VER_PYTHON3}" PYTHON3_LIBRARY PYTHON3_INCLUDE_DIR
    PYTHON3INTERP_FOUND PYTHON3_EXECUTABLE PYTHON3_VERSION_STRING
    PYTHON3_VERSION_MAJOR PYTHON3_VERSION_MINOR PYTHON3LIBS_FOUND
    PYTHON3LIBS_VERSION_STRING PYTHON3_LIBRARIES PYTHON3_LIBRARY
    PYTHON3_DEBUG_LIBRARIES PYTHON3_LIBRARY_DEBUG PYTHON3_INCLUDE_PATH
    PYTHON3_INCLUDE_DIR PYTHON3_INCLUDE_DIR2 PYTHON3_PACKAGES_PATH
    PYTHON3_NUMPY_INCLUDE_DIRS PYTHON3_NUMPY_VERSION)

なるほど分からん。 が、ほとんどが変数名を渡している引数なので本質的に重要なのは第2引数までと一目で分かるので、とりあえずその2つをチェックする。

# Find specified Python version
# Arguments:
#   preferred_version (value): Version to check for first
#   min_version (value): Minimum supported version
#   library_env (value): Name of Python library ENV variable to check
#   include_dir_env (value): Name of Python include directory ENV variable to check
#   found (variable): Set if interpreter found
#   executable (variable): Output of executable found
#   version_string (variable): Output of found version
#   version_major (variable): Output of found major version
#   version_minor (variable): Output of found minor version
#   libs_found (variable): Set if libs found
#   libs_version_string (variable): Output of found libs version
#   libraries (variable): Output of found Python libraries
#   library (variable): Output of found Python library
#   debug_libraries (variable): Output of found Python debug libraries
#   debug_library (variable): Output of found Python debug library
#   include_path (variable): Output of found Python include path
#   include_dir (variable): Output of found Python include dir
#   include_dir2 (variable): Output of found Python include dir2
#   packages_path (variable): Output of found Python packages path
#   numpy_include_dirs (variable): Output of found Python Numpy include dirs
#   numpy_version (variable): Output of found Python Numpy version
function(find_python preferred_version min_version library_env include_dir_env
         found executable version_string version_major version_minor
         libs_found libs_version_string libraries library debug_libraries
         debug_library include_path include_dir include_dir2 packages_path
         numpy_include_dirs numpy_version)

つまるところ、第1引数が希望するバージョン、第2引数がミニマムなバージョンらしい。 しかもよく見ると、find_pythonの直上にoption(OPENCV_PYTHON3_VERSION "Python3 version" "") が存在するので、これを設定すれば良いことも自明であった。

悲しいかな。 ということで、cmakeに-D OPENCV_PYTHON3_VERSION=3.8で今回のケースは終了。 ソースコードを読むことが解決の最短ルートということでした。 数年に一度OpenCVのビルドに苦しめられるので、どなたかOpenCVDetectPythonでPythonを見つけられない原因について調べてpull reqしておいて貰えると助かります。

PythonでMarkdownをHTMLに変換する

4月になってから環境が変わってクソ忙しくなったせいか、録にブログを更新できず。
なんだかんだで、もう5月も終わり。プログラムを書いてないわけじゃないけど、githubの履歴を見る限り一ヶ月でたったの6000行しか書いてないので、全然ですね。
それはさておき、流石に月間更新数0はいただけないので、何か書こうと思っていたら、ネタはあったので投下します。

# なぜにPythonで?
ただMarkdownを変換するだけならpandocなりを使えば容易に実現可能なのですが、table要素にはclassを追加して、相対パスの画像をサーバにアップロードしたURLに変換してという作業を自動化するのはそう簡単ではありません。
それでペチペチとGoogle先生にご教示願ったら、それっぽいのがちゃんとあったんですよね。


Python Markdown

# 見付けたは良いが…
ただ、コイツ。ドキュメントを読んでもさっぱり使い方がわからない。
いや、少なくとも、Markdown->HTMLに変換する方法はよく分かるのだけれども、それ以上のことをしようとすると何をする必要があるのかさっぱりわからない。
実際Githubのissueを見ると山のような質問。あっ察しというわけで、再びGoogle先生にご教示願うと無事それっぽいのを見つけることができました。
ana-balica.github.io

一通りの使い方はなんとなく書いてあったので、実装してみました。
以下が実際のコード

#encoding:UTF-8

import sys
import glob
import os
import os.path
import markdown
import codecs
from markdown import Extension
from markdown.util import etree
from markdown.treeprocessors import Treeprocessor
from markdown.extensions import tables, toc

def main(argv):
  if len(argv) < 3:
    print('convert.py <Input directory> <output directory>')
    return

  md = markdown.Markdown(extensions = [ElementTreeExtension(), 'markdown.extensions.tables', 'markdown.extensions.toc'])

  input_dir = argv[1]
  output_dir = argv[2]

  try:
    os.mkdir(output_dir)
  except OSError as e:
    if not os.path.exists(output_dir):
      rethrow()

  for file in glob.iglob(os.path.join(input_dir, "**/*.md"), recursive=True):
    print(file)
    output_filename = get_output_filename(file, input_dir, output_dir)
    with codecs.open(file, 'r', 'UTF-8') as fp, codecs.open(output_filename, 'w+', 'UTF-8') as ofp :    
      text = fp.read()
      markdown_text = md.convert(text)
      ofp.write(markdown_text)

class ElementTreeExtension(Extension):
  def extendMarkdown(self, md, md_globals):
    md.treeprocessors.add('elementtreeprocessor', ElementTreeProcessor(), '_end')
    md.registerExtension(self)
class ElementTreeProcessor(Treeprocessor):
  def run(self, root):
    h1s = root.getiterator("h1")
    for h1 in h1s:
      h1.set("class", "h")
    
def get_output_filename(input_filename, input_root_dir, output_dir):
  input_dir, filename = os.path.split(input_filename)
  base, ext = os.path.splitext(filename)
  relative_root_to_file = os.path.relpath(input_dir, input_root_dir)
  output_full_path = os.path.join(output_dir, relative_root_to_file)
  if not os.path.isdir(output_full_path) and not os.path.exists(output_full_path):
    os.mkdir(output_full_path)

  return os.path.join(output_full_path, base + ".html")

if __name__ == '__main__':
  main(sys.argv)

第1引数にMarkdown形式のファイルが保存されたディレクトリ。
第2引数に出力ファイルが保存されるディレクトリを設定すれば良い。
これで変換したhtmlのh1要素にhというclassが自動で追加されるようになっている。

スクリプトとDLLの間に生きる時のデバッグ

はじめに

4月になって環境が変わって色々と忙しい今日このごろ。
気づけば4月も末日、そろそろGW前だというのに未だに慣れず。

そう、4月末なのに今月はまだ1記事も書いてないのです。
マズイ…

というわけで、今回はちょっとしたデバッグのテクニックをご紹介。
他の人がCでDLL作ったけど、Pythonから関数呼び出して使うとなぜか動かない…でも、他人のソースを全部読むのはダルい…という場合にオススメです。

DLLを作る

とりあえず、今回は最小限の構成でDLLを作るのでヘッダーファイルなしのcppファイルのみです。

CMakeLists.txt

cmake_minimum_required(VERSION 3.3)

project(DLL)

add_library(${PROJECT_NAME} SHARED DLLFunc.cpp)

DLLFunc.cpp

#include <iostream>
#include <random>

#define DLL_EXPORT __declspec(dllexport)

extern "C" DLL_EXPORT float add(int x, int y)
{
  float value = std::random_device()();
  return x + y + value;
}

extern "C" DLL_EXPORT void print(const char* s)
{
  std::cout << "DLL:" << s << std::endl;
}


これをcmakeしてからビルドすればとりあえず、DLLが出来上がります。
この時、DLLのデバッグをしたいので、DLLのビルド時のConfigはReleaseではなく、DebugかRelWithDebInfoを選択してください。
このRelWithDebInfoというのは最適化は掛けるけど、デバッグシンボルを埋め込むため、ブレークポイントを挿入したときにちゃんと止まってくれるようになります。(実際は一部最適化の関係でブレークポイントを挿入できない場所もあります)

DLLのテスト

最初はとりあえず、C++からテスト。というわけで、下記のコードを使います。コマンドライン引数にDLLのパス(Debugビルドのもの推奨)を与えてください
なお、もちろんですが下記のファイルは別のディレクトリに保存してくださいね。

CMakeLists.txt

cmake_minimum_required(VERSION 3.3)

project(Main)

add_executable(${PROJECT_NAME} main.cpp)

main.cpp

#include <iostream>
#include <string>
#include <Windows.h>

int main(int argc, char** argv)
{
  if (argc < 2)
  {
    std::cerr << "Wrong arguments!\nMain.exe <Input dll path>" << std::endl;
    return -1;
  }

  auto dll = LoadLibraryA(argv[1]);

  auto print_fn = GetProcAddress(dll, "print");
  auto add_fn = GetProcAddress(dll, "add");

  auto value = reinterpret_cast<float(*)(int x, int y)>(add_fn)(10, 20);
  reinterpret_cast<void(*)(const char*)>(print_fn)(std::to_string(value).c_str());

  FreeLibrary(dll);

  return 0;
}


これを実行すると何か標準出力に出るはずです。
このとき、Mainソリューションを開いた状態でDLLFunc.cppをVisualStudioで開き、ブレークポイントを設定すればちゃんとブレークしてくれるはずです。

Pythonで使う

ここまでだとぶっちゃけあまり意味がないのでPythonスクリプトから呼び出します。
実際に使ったのは下記コード。

import ctypes
import sys

print('Type input dll')
input_dll = input()
print('--------------------------------------')
dll = ctypes.cdll.LoadLibrary(input_dll)

dll.print.argtypes = [ctypes.c_char_p]
dll.add.restype = ctypes.c_float
dll.add.argtypes = [ctypes.c_int, ctypes.c_int]

value = dll.add(10, 20)
dll.print(ctypes.c_char_p(str(value).encode('utf-8')))


基本的には先程のテストと同様ですが今回は実行時にちょっとした処理を必要とするため、標準入力からdllファイルのパスを受け取るようにしています。
それから実際に実行して、DLLのパスを入力する前にブレークポイントを入れるためにちょっとした処理を行います。

まず、Visual Studioを起動します。次に「デバッグ>プロセスにアタッチ」もしくはctrl+alt+pで表示されるダイアログの選択可能なプロセスから「python.exe」を探します。pとか押せばpのとこまで飛べるんでそれを活用しつつ選んでから「アタッチ」ボタンを押せばアタッチ完了です。
この状態でDLLFunc.cppを開いてブレークポイントを入れれば、pythonから該当する関数を呼び出すとちゃんとブレークポイントで止まります。

まとめ

DLLデバッグしたいと思ったらとりあえずctrl+alt+pでプロセスにアタッチと覚えとけばいいと思います。
今回は簡単な例でしたが、DLL内で例外が発生して落ちてる場合もVisual Studioがキャッチしてくれて、その時のコールスタックが見れるので、それを活用してデバッグしましょう。
それとPython以外でもmatlabやRなんかでも使えるので(matlabの場合javaで動いている影響かガンガン例外が飛び交うので少々知識が必要ですが)是非お試しあれ

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

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

むか~し、むかし。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を試してみた

はじめに

そもそも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アプリケーションを作った話

はじめに

プログラミングを初めて早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アプリケーションを作るまでに躓いたところをまとめました。
デザインとかはお察しですが、そのうちもう少しマシにしようと思っています。