（比較的）ストレスの掛からないHalide導入方法

この投稿は、研究室の後輩へ向けたものですが、他の人の助けにもなればいいなということで、導入メモとして投稿します。

**※色々初心者なので、「ここ違うよ！」・「ここは、この表現が良いよ！」等大歓迎です。**

この作業は**時間がかかります**(大体2, 3時間)。
時間と心にゆとりのある時に実施下さいませ。

# Halideって？
さわり初めで、詳しく分からないので
詳しくは、
- [Halide公式のサイト](https://halide-lang.org)
- 株式会社フィックスターズさんの[公開SlideShare](https://www.slideshare.net/fixstars/halide-82788728)
を見てください。

**めちゃくちゃ高速で画像処理ができる**[ドメイン固有言語](https://www.google.com/search?q=DSL)です（間違ってたらすいません）。
C++にライブラリとして導入することで使えます。

# 環境
- Windows 10
- Visual Studio 2019

# 下準備
最初に、ライブラリ導入ソフトであるvcpkgをインストールしなければなりません。
そのvcpkgをインストールするためにgitをインストールする必要があります。
**※別に、vcpkgのgithubからクローンしてきてもいいです。**
vcpkgでインストールされたライブラリは（ある設定をすれば）Visual Studio（以降VS）で

```
#include <〇〇.h>
```
と記述するだけで使える便利な代物です。

## gitのインストール
gitの[公式サイト](https://git-scm.com)へ行き、ダウンロード＆インストールします。
導入方法は、[こちらのサイト](http://www.curict.com/software/Windows10/Windows10_git.html)が詳しいです。

## vcpkgのインストール
gitのインストールが完了したら、次はvcpkgのインストールです。
コマンドプロンプトかpowershellかwindows terminalを立ち上げます。
その際、どこか分かりやすいところまでカレントディレクトリを移動します。

```
cd C:\
とか
cd D:\
とか
```
そのあとに、

```
git clone https://github.com/Microsoft/vcpkg.git
```
<details>
<summary>イメージ図</summary>
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/50db29d7-aec2-6b79-7203-1bcef846993c.png)
</details>
をしたら、CドライブやDドライブ直下にvcpkgフォルダが出来ているのが分かると思います。
では、vcpkgをカレントディレクトリとしましょう。

```
cd vcpkg
```
次に、vcpkg本体のビルドをする必要があります。

```
.\bootstrap-vcpkg.bat
```
<details>
<summary>イメージ図</summary>
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/26ae9dfe-3c70-f456-bc2d-b40d270c0eeb.png)
↑が↓になればOK
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/9c77ab97-8a53-86aa-43d4-7feb784a7cce.png)
</details>

<details>
<summary>なんかエラーが出た時</summary>
こんな感じのエラーが出たら、VSにC++が入っていないと怒られちゃっています。
![02_C++追加要請.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/d7aa332b-6386-4478-c61e-ccb58c5a4f37.png)
なので、ツールと機能を取得から
![03_ツールと機能.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/bed24738-19b2-3e46-f99f-5db58fe1537b.png)
[C++によるデスクトップ開発]にチェックを入れて導入してあげてください。
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/c8725a59-3e20-8ae6-1d14-7d889bc484d6.png)

そうすれば、きっとうまくいきます。
</details>

# 1. おまじない
```
.\vcpkg integrate install
```
と打ってあげましょう。
こうすることで、めんどくさい事せずにVSでライブラリを使えます。
<details>
<summary>イメージ図</summary>
![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/0556f2d0-2667-5b1f-63c2-84426a782772.png)
</details>

取り敢えず、ここまで出来たら

```
.\vcpkg help
```
と打ってみましょう。使い方のリストが出てきます。
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/9e3364be-3a08-b8f6-3a20-54138dd05591.png)

# 2. Halide導入
一番時間がかかるところです。

```
.\vcpkg search halide
```
と打ってみると、halideの名の付くパッケージがリストアップされます。
<details>
<summary>イメージ図</summary>
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/46579119-7ea1-c71c-89d5-fb5e5939a406.png)
</details>
ここではリストの一番上にあるhalideを導入します。

```
.\vcpkg install halide:x86-windows halide:x64-windows
または
.\vcpkg install halide:x86-windows
や
.\vcpkg install halide:x64-windows
でもいいですここはお好みで。というより、お使いのPCと相談してください。
```
<details>
<summary>イメージ図</summary>
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/f29e8c2c-0209-f815-e2b3-8b3de022eba4.png)
</details>

<details>
<summary>なんかエラー出たやん![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/17bd0bff-52e2-233f-61ec-14d04fb63fc8.png)
</summary>
VSに英語の言語パックを追加しないといけません。
なので、ツールと機能を取得から[言語パック]へ移動し、英語にチェックを入れましょう。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/e2871f3c-1487-bf01-5eb3-45d4743d8fff.png)
</details>

<details>
<summary>他にもエラー出たんやけど![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/59a1df04-3608-9f13-7ce3-ee3abd00a051.png)
</summary>
これは、ATLとMFCが無いのでVSに入れてねってことです(ATLとMFCが何なのかは私もわかりません)。
なので、ツールと機能を取得から
[最新のv142ビルドツール用C++ ATL], [最新のv142ビルドツール用C++ MFC]にチェックを入れてあげましょう。
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/5664df98-5913-6e88-510f-ce96b919d930.png)
</details>
ここまでしたら、あとは放置です。
2, 3時間かかるので、お風呂入っててもいいし、寝ててもいいです。
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/515c7d34-6d0e-4c7b-db4a-0f10fbcf4044.png)
完了ですね。
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/96a7e8bb-aff0-4fa9-b5b0-42258d76a740.png)

```
.\vcpkg list
```
でさっきインストールしたものを見てみましょう。
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/3272a9b7-6ad8-a560-1e16-75cc351a306d.png)
こんな感じになってると思います。

# 3. VSでHalideをincludeしてみよう
VSでC++を空のプロジェクトとして作成したら、main.cppを作って

```
#include <Halide.h>

int main(){
    return 0;
}
```
でうまくいくと思います。
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227991/a53404ec-f017-ae2e-9074-119fd1e01e12.png)

# OpenCVと合わせて使うとき
@fukushima1981さんが投稿された[記事](https://qiita.com/fukushima1981/items/61e226f0c7987f9aa970)の中に載せられてるコードがとても便利です（vcpkgでOpenCVを導入して使ってください）。
しかしながら、vcpkgによりライブラリ群を導入したことで、いくつか消さなければならない記述が出てきます。

```
#pragma comment(lib, "opencv_core331.lib")
#pragma comment(lib, "opencv_imgcodecs331.lib")
#pragma comment(lib, "opencv_highgui331.lib")
#pragma comment(lib, "Halide.lib")
```
これらを消さねばなりません。
理由については、[コチラのページ](https://cdecrement.blog.fc2.com/blog-entry-288.html)に詳細が掛かれています。
つまり競合しちゃうわけですね。
それを踏まえて、vcpkgで導入したverのコードを下に示します。
とても便利なので、ヘッダファイルにして、```#include```しても良さそうです。

```C++
#pragma once

#include <opencv2/opencv.hpp>
#include "Halide.h"

using namespace Halide;

//OpenCV for Halide functions

//中でcv::imreadをコール．
Buffer<uint8_t> imread(cv::String name);

//中でcv::imwriteをコール．
void imwrite(cv::String name, const Buffer<uint8_t>& src);

//HalideのbufferからOpenCVのMatへ変換
void convertHalide2Mat(const Buffer<uint8_t>& src, cv::Mat& dest);

//OpenCVのMatからHalideのbufferへ変換
void convertMat2Halide(cv::Mat& src, Buffer<uint8_t>& dest);

//ucharのバッファ表示用
void imshow(cv::String name, const Buffer<uint8_t>& src);

//shortのバッファ表示用．可視化のデータはucharなのでオフセットとスケールで０～２５５へのキャストを調整
void imshow16(cv::String name, const Buffer<int16_t>& src, double offset = 0.0, double scale = 1.0);

//アルファブレンドをして出力を比較デバッグ用関数
void guiAlphaBlend(Buffer<uint8_t>& src1, Buffer<uint8_t>& src2, cv::String name = "alpha blend");

Func copy(Buffer<uint8_t>& src)
{
	Var x("x"), y("y"), c("c");

	Func output("output");
	output(x, y, c) = src(x, y, c);

	return output;
}

//utility function with OpenCV
void convertMat2Halide(cv::Mat& src, Buffer<uint8_t>& dest)
{
	const int ch = src.channels();
	if (ch == 1)
	{
		for (int j = 0; j < src.rows; j++)
		{
			for (int i = 0; i < src.cols; i++)
			{
				dest(i, j) = src.at<uchar>(j, i);
			}
		}
	}
	else if (ch == 3)
	{
		for (int j = 0; j < src.rows; j++)
		{
			for (int i = 0; i < src.cols; i++)
			{
				dest(i, j, 0) = src.at<uchar>(j, 3 * i);
				dest(i, j, 1) = src.at<uchar>(j, 3 * i + 1);
				dest(i, j, 2) = src.at<uchar>(j, 3 * i + 2);
			}
		}
	}
}

Buffer<uint8_t> imread(cv::String name)
{
	cv::Mat a = cv::imread(name);
	if (a.empty()) std::cout << name << " is empty" << std::endl;

	Buffer<uint8_t> ret(a.cols, a.rows, a.channels());
	convertMat2Halide(a, ret);

	return ret;
}

void convertHalide2Mat(const Buffer<uint8_t>& src, cv::Mat& dest)
{
	if (dest.empty()) dest.create(cv::Size(src.width(), src.height()), CV_MAKETYPE(CV_8U, src.channels()));
	const int ch = dest.channels();
	if (ch == 1)
	{
		for (int j = 0; j < dest.rows; j++)
		{
			for (int i = 0; i < dest.cols; i++)
			{
				dest.at<uchar>(j, i) = src(i, j);
			}
		}
	}
	else if (ch == 3)
	{
		for (int j = 0; j < dest.rows; j++)
		{
			for (int i = 0; i < dest.cols; i++)
			{
				dest.at<uchar>(j, 3 * i + 0) = src(i, j, 0);
				dest.at<uchar>(j, 3 * i + 1) = src(i, j, 1);
				dest.at<uchar>(j, 3 * i + 2) = src(i, j, 2);
			}
		}
	}
}

void imwrite(cv::String name, const Buffer<uint8_t>& src)
{
	cv::Mat a(cv::Size(src.width(), src.height()), CV_MAKETYPE(CV_8U, src.channels()));
	convertHalide2Mat(src, a);
	cv::imwrite(name, a);
}

void imshow(cv::String name, const Buffer<uint8_t>& src)
{
	cv::Mat a(cv::Size(src.width(), src.height()), CV_MAKETYPE(CV_8U, src.channels()));
	convertHalide2Mat(src, a);
	cv::imshow(name, a);
}

void imshow16(cv::String name, const Buffer<int16_t>& src, double offset, double scale)
{
	cv::Mat a(cv::Size(src.width(), src.height()), CV_MAKETYPE(CV_8U, src.channels()));

	const int ch = a.channels();
	if (ch == 1)
	{
		for (int j = 0; j < a.rows; j++)
		{
			for (int i = 0; i < a.cols; i++)
			{
				a.at<uchar>(j, i) = cv::saturate_cast<uchar>(scale * src(i, j) + offset);
			}
		}
	}
	else if (ch == 3)
	{
		for (int j = 0; j < a.rows; j++)
		{
			for (int i = 0; i < a.cols; i++)
			{
				a.at<uchar>(j, 3 * i + 0) = cv::saturate_cast<uchar>(scale * src(i, j, 0) + +offset);
				a.at<uchar>(j, 3 * i + 1) = cv::saturate_cast<uchar>(scale * src(i, j, 1) + +offset);
				a.at<uchar>(j, 3 * i + 2) = cv::saturate_cast<uchar>(scale * src(i, j, 2) + +offset);
			}
		}
	}

	cv::imshow(name, a);
}

void guiAlphaBlend(Buffer<uint8_t>& src1, Buffer<uint8_t>& src2, cv::String name)
{
	cv::Mat s1(cv::Size(src1.width(), src1.height()), CV_MAKETYPE(CV_8U, src1.channels()));
	cv::Mat s2(cv::Size(src1.width(), src1.height()), CV_MAKETYPE(CV_8U, src1.channels()));
	convertHalide2Mat(src1, s1);
	convertHalide2Mat(src2, s2);

	cv::namedWindow(name);
	int a = 0; cv::createTrackbar("alpha", name, &a, 100);
	int key = 0;
	while (key != 'q')
	{
		cv::Mat show;
		cv::addWeighted(s1, 1.0 - a / 100.0, s2, a / 100.0, 0.0, show);
		cv::imshow(name, show);
		key = cv::waitKey(1);
	}
	cv::destroyWindow(name);
}
```
# 参考サイト
[https://github.com/microsoft/vcpkg](https://github.com/microsoft/vcpkg)
[Visual Studio 2017 で MFC アプリ開発機能を追加するには](https://qiita.com/tadnakam/items/042c7a19bf115e3b096f)
[C/C++ パッケージマネージャvcpkgの使い方](https://kagasu.hatenablog.com/entry/2019/02/20/162907)
[OpenCVによるHalideプログラミングの開発支援](https://qiita.com/fukushima1981/items/61e226f0c7987f9aa970)
[CMake&vcpkgでつまずいた点](https://cdecrement.blog.fc2.com/blog-entry-288.html)