Windows で OpenCV をソースからビルドして静的リンクする方法

# はじめに

静的リンクの方法について書かれた情報がすぐに見つからなかったので、自分で試してみました。

本家のチュートリアルだと以下のページに該当しますが、本記事とは異なる点が多いです。Eigen などの外部ライブラリのダウンロードや、OS の環境変数の設定などは本記事では行ないません。説明を簡単にするため、Visual Studio の GUI は使わず、コマンドで全部やることします。

Installation in Windows - OpenCV 2.4.13.2 documentation
http://docs.opencv.org/2.4/doc/tutorials/introduction/windows_install/windows_install.html

How to build applications with OpenCV inside the Microsoft Visual Studio - OpenCV 2.4.13.2 documentation
http://docs.opencv.org/2.4/doc/tutorials/introduction/windows_visual_studio_Opencv/windows_visual_studio_Opencv.html

# 用意したもの

開発環境はこれ。

OS: Windows 10 1607 64bit
IDE: Visual Studio 2015
Git: Git for Windows 2.10.1 (https://git-scm.com/downloads)

OpenCV のビルドには CMake が必要になります。下記ダウンロード ページから最新版をダウンロードします。インストーラーは不要で、Binary distributions にある zip ファイルで十分です。2017/3/20 現在の最新バージョンは 3.8.0-rc2 で、ダウンロードしたファイルは cmake-3.8.0-rc2-win64-x64.zip でした。

CMAKE: 3.8.0-rc2 (https://cmake.org/download/)

ダウンロードしたファイルを適当な場所に解凍します。本記事では D:\cmake\cmake-3.8.0-rc2-win64-x64 に解凍した前提で進めます。解凍後、念のため CMake.exe を実行できることを確認しておきます。

```
> D:\cmake\cmake-3.8.0-rc2-win64-x64\bin\cmake.exe --version
cmake version 3.8.0-rc2

CMake suite maintained and supported by Kitware (kitware.com/cmake).
```

# OpenCV の Git リポジトリをクローン
GitHub リポジトリ https://github.com/opencv/opencv をクローンします。ブランチは、特にこだわりがなければ master でいいと思います。

```
G:\10_Git> git clone https://github.com/opencv/opencv.git
G:\10_Git> cd opencv
G:\10_Git\opencv> git branch
* master
```

# CMake で Visual Studio ソリューションを生成
一般的な configure-make-make-install ダンスの場合とは異なり、CMake では、ビルド用のディレクトリを別の場所に用意することがお作法になっています。したがってソース ディレクトリを汚さなくて済みます。

基本的にはリポジトリのルートに build ディレクトリを作ることが想定されていて、.gitignore にも build エントリが追加されているのですが、それを使う必要はありません。任意のドライブに任意の名前で作ることができます。本記事では、OpenCV に用意されている 4 種類のビルドを試してみることにしました。せっかくなので静的リンクだけでなく動的リンクも試しましょう。

|リンク方法|ライブラリ ファイル|ビルド ディレクトリ|
|---|---|---|
|静的|個別モジュール|G:\10_Git\opencv-build\static|
|静的|opencv_world|G:\10_Git\opencv-build\static-world|
|動的|個別モジュール|G:\10_Git\opencv-build\dynamic|
|動的|opencv_world|G:\10_Git\opencv-build\dynamic-world|

OpenCV のソース コードは、core や highgui といったモジュール単位に分かれており、それぞれからライブラリ ファイルが作られるため、OpenCV を利用するときには複数のライブラリをリンクする必要がありました。opencv_world とは、その手間を軽減するために作られた、複数のモジュールをまとめた一つのライブラリです。

CMake の手順は簡単です。ビルド ディレクトリを作成し、そのディレクトリに移動し、cmake.exe を実行するだけです。上記 4 種類の構成についてのコマンド実行例は以下の通りです。

```
> md G:\10_Git\opencv-build\dynamic
> cd /d G:\10_Git\opencv-build\dynamic
G:\10_Git\opencv-build\dynamic> D:\cmake\cmake-3.8.0-rc2-win64-x64\bin\cmake.exe -G "Visual Studio 14 2015 Win64" G:\10_Git\opencv

> md G:\10_Git\opencv-build\dynamic-world
> cd /d G:\10_Git\opencv-build\dynamic-world
G:\10_Git\opencv-build\dynamic-world> D:\cmake\cmake-3.8.0-rc2-win64-x64\bin\cmake.exe -G "Visual Studio 14 2015 Win64" -D BUILD_opencv_world=ON G:\10_Git\opencv

> md G:\10_Git\opencv-build\static
> cd /d G:\10_Git\opencv-build\static
G:\10_Git\opencv-build\static> D:\cmake\cmake-3.8.0-rc2-win64-x64\bin\cmake.exe -G "Visual Studio 14 2015 Win64" -D BUILD_SHARED_LIBS=OFF -D BUILD_opencv_world=ON G:\10_Git\opencv

> md G:\10_Git\opencv-build\static-world
> cd /d G:\10_Git\opencv-build\static-world
G:\10_Git\opencv-build\static-world> D:\cmake\cmake-3.8.0-rc2-win64-x64\bin\cmake.exe -G "Visual Studio 14 2015 Win64" -D BUILD_SHARED_LIBS=OFF -D BUILD_opencv_world=ON G:\10_Git\opencv
```

CMake の -D オプションを使って、OpenCV に用意されているビルド オプションを変更し、ビルド構成を変更します。例えば BUILD_SHARED_LIBS を OFF にすると静的リンク用のライブラリが生成されます。world モジュールを使う場合は BUILD_opencv_world を ON にします。

-G オプションには、生成するソリューションの種類を指定します。上記例では、Visual Studio 2015 で、かつ 64bit バイナリをビルドするソリューションを作成しています。CMake 3.8.0-rc2 は Visual Studio 2017 にも対応していました。Eclipse CDT など Visual Studio 以外の開発環境にも対応しています。

# 生成したソリューションをビルド

Visual Studio の場合、CMake がソリューション ファイル (.sln) を生成します。この後、Visual Studio でソリューションを開いて GUI で操作してもいいのですが、コマンドでビルドを開始する方が簡単なのでおすすめです。

Visual Studio をインストールすると、"VS2015 x64 Native Tools Command Prompt" というショートカットがスタート メニューに登録されているはずなので、それを実行します。環境変数がいい感じに設定されたコマンド プロンプトが起動するので、そこで msbuild コマンドを実行するだけです。このとき、プラットフォーム (x64) とビルド構成 (Debug|Release) を忘れずに指定してください。以下は Release ビルドの例です。

```
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC> cd /d G:\10_Git\opencv-build\static-world
G:\10_Git\opencv-build\static-world> msbuild OpenCV.sln /p:Configuration=Release;Platform=x64
```

![image](https://qiita-image-store.s3.amazonaws.com/0/165275/b7ea2008-f2ac-f97d-2b09-fd7a3dc5a90d.png)

# OpenCV ライブラリをリンク (Makefile 編)

ビルドが終われば OpenCV 側の準備は終わりです。実際にプログラムから使ってみましょう。

OpenCV のリンクをテストするための適当なコードを書きます。コンソールから引数で受け取ったパスを imshow で表示するだけのプログラムです。

```C++:main.cpp
#include <opencv2/highgui/highgui.hpp>

int main(int argc, char *argv[]) {
    if (argc >= 2) {
        auto image = cv::imread(argv[1], cv::IMREAD_COLOR);
        cv::namedWindow("Display window", cv::WINDOW_AUTOSIZE);
        cv::imshow("Display window", image);
        cv::waitKey(0);
    }
    return 0;
}
```

Visual Studio は使わずに、Makefile を使って NMAKE でビルドする方法を紹介します。その Makefile がこちら。

```make:Makefile
!IF "$(PLATFORM)"=="X64"
OUTDIR=.\bin64
!ELSE
OUTDIR=.\bin
!ENDIF

CC=cl
LINKER=link
RM=del /q
#TARGET=t.exe
#OPENCV_STATIC=0
#OPENCV_WORLD=1
#OPENCV_DEBUG=1

OPENCV_SRC_DIR=G:\10_Git\opencv
!IF "$(OPENCV_STATIC)"=="1"
!IF "$(OPENCV_WORLD)"=="1"
OPENCV_BUILD_DIR=G:\10_Git\opencv-build\static-world
!ELSE
OPENCV_BUILD_DIR=G:\10_Git\opencv-build\static
!ENDIF
!ELSE
!IF "$(OPENCV_WORLD)"=="1"
OPENCV_BUILD_DIR=G:\10_Git\opencv-build\dynamic-world
!ELSE
OPENCV_BUILD_DIR=G:\10_Git\opencv-build\dynamic
!ENDIF
!ENDIF

OBJS=\
  $(OUTDIR)\main.obj\

LIBS=\
  user32.lib\
  advapi32.lib\
  gdi32.lib\
  comdlg32.lib\
  ippicvmt.lib\
!IF "$(OPENCV_DEBUG)"=="1"
  IlmImfd.lib\
  libjasperd.lib\
  libjpegd.lib\
  libpngd.lib\
  libtiffd.lib\
  libwebpd.lib\
  zlibd.lib\
!IF "$(OPENCV_WORLD)"=="1"
  opencv_world320d.lib\
!ELSE
  opencv_core320d.lib\
  opencv_imgcodecs320d.lib\
  opencv_imgproc320d.lib\
  opencv_highgui320d.lib\
!ENDIF
!ELSE
  IlmImf.lib\
  libjasper.lib\
  libjpeg.lib\
  libpng.lib\
  libtiff.lib\
  libwebp.lib\
  zlib.lib\
!IF "$(OPENCV_WORLD)"=="1"
  opencv_world320.lib\
!ELSE
  opencv_core320.lib\
  opencv_imgcodecs320.lib\
  opencv_imgproc320.lib\
  opencv_highgui320.lib\
!ENDIF
!ENDIF

CFLAGS=\
  /nologo\
  /Zi\
  /c\
  /Fo"$(OUTDIR)\\"\
  /Fd"$(OUTDIR)\\"\
  /DUNICODE\
  /D_UNICODE\
!IF "$(OPENCV_DEBUG)"=="1"
  /MTd\
!ENDIF
  /O2\
  /EHsc\
  /W4\
  /I"$(OPENCV_BUILD_DIR)"\
  /I"$(OPENCV_SRC_DIR)\modules\core\include"\
  /I"$(OPENCV_SRC_DIR)\modules\highgui\include"\
  /I"$(OPENCV_SRC_DIR)\modules\imgcodecs\include"\
  /I"$(OPENCV_SRC_DIR)\modules\imgproc\include"\
  /I"$(OPENCV_SRC_DIR)\modules\videoio\include"\

LFLAGS=\
  /NOLOGO\
  /DEBUG\
  /SUBSYSTEM:CONSOLE\
  /LIBPATH:"$(OPENCV_BUILD_DIR)\3rdparty\ippicv\ippicv_win\lib\intel64"\
!IF "$(OPENCV_DEBUG)"=="1"
  /LIBPATH:"$(OPENCV_BUILD_DIR)\3rdparty\lib\Debug"\
  /LIBPATH:"$(OPENCV_BUILD_DIR)\lib\Debug"\
!ELSE
  /LIBPATH:"$(OPENCV_BUILD_DIR)\3rdparty\lib\Release"\
  /LIBPATH:"$(OPENCV_BUILD_DIR)\lib\Release"\
!ENDIF

all: $(OUTDIR)\$(TARGET)

$(OUTDIR)\$(TARGET): $(OBJS)
  $(LINKER) $(LFLAGS) $(LIBS) /PDB:"$(@R).pdb" /OUT:"$(OUTDIR)\$(TARGET)" $**

.cpp{$(OUTDIR)}.obj:
  -@if not exist $(OUTDIR) md $(OUTDIR)
  $(CC) $(CFLAGS) $<
```

OpenCV 関連で 5 つのマクロを使います。Makefile 内で !IF ブロックを使っているように、ビルド構成によってリンクすべきライブラリの名前やパスが異なります。NMAKE では、テキストの Makefile にロジックをそのまま書けばいいので一目瞭然で、かつ編集も容易ですが、これを Visual Studio の IDE で実現するには、同じロジックをプロジェクト設定の GUI でちまちまと設定する必要があるので手間がかかります。

|変数|意味|
|---|---|
|OPENCV_SRC_DIR|GitHub からクローンしたローカル リポジトリのルート|
|OPENCV_BUILD_DIR|OpenCV のビルド ディレクトリ (CMake がソリューションを生成した場所)|
|OPENCV_DEBUG|1 = デバッグ ビルドの OpenCV を利用<br />Not 1 = リリース ビルドの OpenCV を利用|
|OPENCV_STATIC|1 = OpenCV を静的リンク<br />Not 1 = OpenCV を動的リンク|
|OPENCV_WORLD|1 = opencv_world をリンク<br />Not 1 = 個別モジュールをリンク|

これらの変数を環境に合わせて決めて、"VS2015 x64 Native Tools Command Prompt" から開いたプロンプトで nmake を実行すると、その構成の OpenCV ファイルを選んでリンクするようになっています。

次の 8 通りの組み合わせをビルドするバッチ ファイルを作って実行してみました。このバッチを実行するため、上記 Makefile 内で TARGET, OPENCV_STATIC, OPENCV_WORLD, OPENCV_DEBUG をセットしている行をコメントアウトしていますが、バッチを使わない場合は、マクロを Makefile 内でセットして nmake をパラメーターなしで実行すれば OK です。なお、バッチを実行する前にそれぞれの構成ごとの OpenCV のビルドを完了しておく必要があります。

```bat:batchbuild.bat
md bin64
del bin64\main.obj && nmake OPENCV_STATIC=1 OPENCV_WORLD=1 OPENCV_DEBUG=1 TARGET=t-static-world-debug.exe
del bin64\main.obj && nmake OPENCV_STATIC=1 OPENCV_WORLD=1 OPENCV_DEBUG=0 TARGET=t-static-world-release.exe
del bin64\main.obj && nmake OPENCV_STATIC=1 OPENCV_WORLD=0 OPENCV_DEBUG=1 TARGET=t-static-debug.exe
del bin64\main.obj && nmake OPENCV_STATIC=1 OPENCV_WORLD=0 OPENCV_DEBUG=0 TARGET=t-static-release.exe
del bin64\main.obj && nmake OPENCV_STATIC=0 OPENCV_WORLD=1 OPENCV_DEBUG=1 TARGET=t-dynamic-world-debug.exe
del bin64\main.obj && nmake OPENCV_STATIC=0 OPENCV_WORLD=1 OPENCV_DEBUG=0 TARGET=t-dynamic-world-release.exe
del bin64\main.obj && nmake OPENCV_STATIC=0 OPENCV_WORLD=0 OPENCV_DEBUG=1 TARGET=t-dynamic-debug.exe
del bin64\main.obj && nmake OPENCV_STATIC=0 OPENCV_WORLD=0 OPENCV_DEBUG=0 TARGET=t-dynamic-release.exe
```

ビルド後のファイルは次のようになりました。静的リンクしたときの exe ファイルのサイズは、Debug ビルドで約 20MB、Release ビルドで約 15MB です。ファイルが大きい分だけリンクも遅くなります。world を使うかどうかでは、ファイルサイズ、ビルド時間ともにほとんど影響がありませんでした。

```
G:\10_Git\opencv-sandbox> dir /OEN bin64
 Volume in drive G is USB30
 Volume Serial Number is B042-8A3F

 Directory of G:\10_Git\opencv-sandbox\bin64

03/20/2017  11:30 AM    <DIR>          .
03/20/2017  11:30 AM    <DIR>          ..
03/20/2017  11:30 AM           946,176 t-dynamic-debug.exe
03/20/2017  11:30 AM           498,688 t-dynamic-release.exe
03/20/2017  11:30 AM           945,664 t-dynamic-world-debug.exe
03/20/2017  11:30 AM           498,688 t-dynamic-world-release.exe
03/20/2017  11:30 AM        20,887,040 t-static-debug.exe
03/20/2017  11:30 AM        14,734,336 t-static-release.exe
03/20/2017  11:30 AM        21,000,704 t-static-world-debug.exe
03/20/2017  11:30 AM        14,735,872 t-static-world-release.exe
03/20/2017  11:30 AM         2,824,976 t-dynamic-debug.ilk
03/20/2017  11:30 AM         2,257,352 t-dynamic-release.ilk
03/20/2017  11:30 AM         2,813,608 t-dynamic-world-debug.ilk
03/20/2017  11:30 AM         2,252,072 t-dynamic-world-release.ilk
03/20/2017  11:30 AM        37,946,968 t-static-debug.ilk
03/20/2017  11:30 AM        24,126,448 t-static-release.ilk
03/20/2017  11:30 AM        38,050,880 t-static-world-debug.ilk
03/20/2017  11:30 AM        24,171,056 t-static-world-release.ilk
03/20/2017  11:30 AM            58,121 main.obj
03/20/2017  11:30 AM         6,410,240 t-dynamic-debug.pdb
03/20/2017  11:30 AM         6,303,744 t-dynamic-release.pdb
03/20/2017  11:30 AM         5,738,496 t-dynamic-world-debug.pdb
03/20/2017  11:30 AM         6,287,360 t-dynamic-world-release.pdb
03/20/2017  11:30 AM        45,862,912 t-static-debug.pdb
03/20/2017  11:30 AM        43,659,264 t-static-release.pdb
03/20/2017  11:30 AM        43,929,600 t-static-world-debug.pdb
03/20/2017  11:30 AM        41,775,104 t-static-world-release.pdb
03/20/2017  11:30 AM           815,104 vc140.pdb
```

それぞれの exe を Dependency Walker で見ると次のようになりました。静的リンクした場合は、OS の標準モジュールへの依存関係があるだけで、OpenCV や Visual C++ のランタイム ライブラリには依存していないことが確認できます。

![depends.png](https://qiita-image-store.s3.amazonaws.com/0/165275/2160697e-006b-3b95-7bea-9353a5cc2f61.png)

# おわりに

Windows の環境で、OpenCV を GitHub からクローンしてビルドして、Visual Studio の NMAKE で静的/動的リンクする cmake-msbuild-nmake ダンスを紹介しました。

Windows 用の OpenCV パッケージは http://sourceforge.net/projects/opencvlibrary/files/opencv-win/ からダウンロードできます。パッケージには、Visual Studio 2015 の 64bit でビルドした動的リンク用のライブラリと、ソースコード一式が含まれています。今回のように静的リンクを行ないたい場合や、32bit 用のバイナリが欲しい場合、もしくは OpenCV のコードをデバッグしたい場合など、ソースコードからのビルドが必要になる場面も少なからず考えられます。今回一通り試してみましたが、大きな落とし穴はなさそうだったので、まだ試していない方には是非試していただきたいです。

余裕ができれば、Visual Studio の GUI で設定する方法も追記する予定です。