おぺんcv

画像処理エンジニアのブログ

Visual Studio 2015でHalideを始める

Halideという画像処理向けのプログラミング言語を始めるべく
Visual Studio 2015でHalideの環境構築をしてみました
今回はその手順について紹介します

はじめに

HalideとはMITが開発している画像処理向けの言語です
公式ページやブログなどの紹介によると以下の特徴があるようです

  • C++内で使用するDSL(domain-specific language)である
  • 画像処理を簡単に記述できる
  • 色んなターゲット(x86、ARM、CUDA等)向けに高速化できる

簡単に処理を記述できて、尚且つ一度書いたコードを色んなターゲットで
高速に動かせるとしたら、とても魅力的ですね
リポジトリのREADMEによるとWindows(VS2015)にも対応しているようなので、
試しに導入してみることにしました

参考:

やること

  • Visual Studio 2015でHalideをビルドする
  • Halidを使った簡単なプログラムをビルド・実行する

事前に必要なもの

  • Visual Studio 2015 Update 3 以降
  • CMake
  • libjpegとlibpng (画像ファイルを読み書きするtutorialを動かす場合)

また、コマンドラインでの作業が主体になるので
お好きなコンソールエミュレータを立ち上げておきましょう

ソースの入手

Halid

GitHubからHalidのリポジトリをクローンします
ここではD:\halideというディレクトリにソースを置くことにします

mkdir D:\halide
cd D:\halide
git clone https://github.com/halide/Halide.git
LLVMとClang

HalidのビルドにはLLVMとClangが必要になります
LLVM Download PageからLLVM source codeとClang source codeとダウンロードします
2017年4月22日の時点で最新バージョンは4.0.0でした
解凍してHalidのソースと同じディレクトリに置きます

$pwd
D:\halide

$ls
Halide/  cfe-4.0.0.src/  llvm-4.0.0.src/

ビルド

LLVMのビルド

ソースと同じ場所にllvm-buildというディレクトリを作成しビルドを行います
まずはcmake

$mkdir D:\halide\llvm-build
$cd D:\halide\llvm-build
$cmake -DCMAKE_INSTALL_PREFIX=../llvm-install -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_TARGETS_TO_BUILD=X86;ARM;NVPTX;AArch64;Mips;Hexagon -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_BUILD_32_BITS=OFF -DCMAKE_BUILD_TYPE=Release ../llvm-4.0.0.src -G "Visual Studio 14 Win64"

次にビルド

MSBuild.exe /m /t:Build /p:Configuration=Release .\INSTALL.vcxproj

私の場合、MSBuild.exeへのパスが通ってなかったので環境変数の設定から
以下のディレクトリをPathに追加しました

C:Program Files (x86)\MSBuild\14.0\Bin

ビルドが終わるまでしばらく待ちます
CPU使用率が100%になってイイ感じ(?)です

Clangのビルド

LLVM同様、cmakeとビルドを行います

$mkdir D:\halide\clang-build
$cd D:\halide\clang-build
$cmake -DCMAKE_INSTALL_PREFIX=../llvm-install -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_TARGETS_TO_BUILD=X86;ARM;NVPTX;AArch64;Mips;Hexagon -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_BUILD_32_BITS=OFF -DCMAKE_BUILD_TYPE=Release ../cfe-4.0.0.src -G "Visual Studio 14 Win64"
MSBuild.exe /m /t:Build /p:Configuration=Release .\INSTALL.vcxproj
Halideのビルド

最後にHalideをビルドします

$mkdir D:\halide\halide-build
$cd D:\halide\halide-build
$cmake -DLLVM_DIR=../llvm-install/lib/cmake/llvm -DLLVM_VERSION=37 -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 14 Win64" ../halide
MSBuild.exe /m /t:Build /p:Configuration=Release .\ALL_BUILD.vcxproj

ビルドが完了するとhalide-build以下にHalideがインストールされています
私の場合、無事ビルド完了…と思いきやtutorialコードのビルドで失敗してしまいました
それについては余談で触れたいと思います

Halideのプロジェクトを作成してみる

ビルドできたらtutorialを実行するも良し、ですが
ここでは自分でHalideのプロジェクトを作成してみましょう
プロジェクト用ディレクトリを作成し、CMakeLists.txtとソースコードを置きます

halide_sample
   |- CMakeLists.txt
   |- main.cpp
CMake

CMakeLists.txtの中身はこんな感じです
Halide_DIRにHalideのインストール先を設定し
そこからヘッダとライブラリのパスを設定します
Halide.libもリンクしておきます

cmake_minimum_required(VERSION 2.8)

set(Halide_DIR D:/halide/halide-build)

include_directories(${Halide_DIR}/include)
link_directories(${Halide_DIR}/lib/Release)

file(GLOB srcs ./*.cpp ./*.h*)
add_executable(sample ${srcs})

target_link_libraries(sample Halide)
ソース

main.cppの中身はこんな感じです
内容はtutorialのlesson_01_basics.cppから引っ張ってきました
Halide::FuncとHalide::Varで関数を記述し、realizeで呼び出すって感じでしょうか

#include <Halide.h>
#include <iostream>

int main()
{
	Halide::Func gradient;
	Halide::Var x, y;
	gradient(x, y) = x + y;
	Halide::Buffer<int32_t> output = gradient.realize(3, 3);
	
	for (int i = 0; i < output.height(); i++)
	{
		for (int j = 0; j < output.width(); j++)
		{
			std::cout << output(i, j) << " ";
		}
		std::cout << std::endl;
	}

	return 0;
}
プロジェクトの作成とビルド

cmakeを使ってプロジェクトの作成とビルドを行います

$mkdir build
$cd build
$cmake .. -G "Visual Studio 14 Win64"
cmake --build . --config Release
パスの設定

実行にはHalide.dllが必要になりますので
環境変数のPathにdllの場所を指定しておきましょう

D:\halide\halide-build\bin\Release
実行結果

ちゃんと (x, y) = x + y という結果になってました

0 1 2
1 2 3
2 3 4

おわりに

今回はVisual Studio 2015でHalideの環境構築するところまでを紹介しました
今後はより複雑なアルゴリズムの記述や、高速化の方法について調べてみようと思います

【余談】tutorialのビルドにハマった話

私の場合、tutorialのビルド時に以下のようなビルドエラーに遭遇してしまいました

  • jpeg.libで未定義のシンボル__iob_funcが参照されている
  • jpeg.libで未定義のシンボルsprintfが参照されている
  • INT32が再定義されている

ビルドエラーは画像ファイルを読み書きするtutorial(lesson_02,07,09,12)で起きてました
Halide/tutorialのCMakeLists.txtを見ると、これらのサンプルはlibjpegとlibpngが入ってないと
ビルドされないはずなのですが、libjpegとlibpngなんて入れたっけなあ…
と思ってたら、どうやらAnaconda2(python環境)を入れた際に一緒に入ってたみたいです

__iob_funcの未定義

これについては、ググると回避方法が見つかりました
Halide/tools/halide_image_io.hで__iob_funcを定義してやります

FILE _iob[] = { *stdin, *stdout, *stderr };
extern "C" FILE * __cdecl __iob_func(void)
{
    return _iob;
}

参考:

sprintfの未定義

なんでsprintfが未定義なの?ってのが謎ですが
どっかに定義されてるだろってことで、extern宣言したら回避できました(適当)
これもhalide_image_io.hに書いてます

extern "C" int sprintf ( char * str, const char * format, ... );
NT32の再定義

どうやらINT32がlibjpgのjmorecfg.hと、Windows Kits\8.1\Include\shared\basetsd.hで
両方定義されちゃってるようです
jmorecfg.hをみると、INT32はQGLOBAL_Hが定義されていない場合に定義されるようです
そこで、halide_image_io.hでjpeglib.hをインクルードする前にQGLOBAL_Hを定義してやります

#ifndef HALIDE_NO_JPEG
#define QGLOBAL_H
#include "jpeglib.h"
#undef QGLOBAL_H
#endif

以上でビルドエラーを回避することができましたが、かなり適当なのでご参考までに