OpenCVで画像をハッシュ値に変換する方法

# Background
画像の類似度を比較する手法として画像をハッシュ値に変換してハミング距離で計算する方法があります。今回はOpenCVでハッシュ値を変換しようと思います。

## ImageHash
アルゴリズムの元ネタは[hackerfactor](http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html)のようですが、これをベースにしたライブラリはPythonでは `imagehash` が主流みたいです。種類としては `ahash` `phash` `dhash` `whash` の4つが存在します。

######  テストデータ
![Mandrill.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/3d6e4b2a-fc19-eddf-f38b-3fc801b7e0c1.png)　　おなじみの画像です。

######  必要なパッケージ

```python
from PIL import Image
import imagehash
```

| name               | code              | result             |
|:-------------------|:------------------|:-------------------|
| average hashing    | imagehash.average_hash(Image.open('Mandrill.png')) |01833f3cbc98ebfc|
| perception hashing |imagehash.phash(Image.open('Mandrill.png'))|df20607d1fa0d88f   |
| difference hashing |imagehash.dhash(Image.open('Mandrill.png'))|1fabea6869305668       |
| wavelet hashing    |imagehash.whash(Image.open('Mandrill.png'))|01433f3cbc98e2fc    |

で、Python版のOpenCVにハッシュ値を変換するモジュールがあるかというと、、、、ありません:tired_face:

## OpenCV(C++)
ですが、C++版にはモジュールが入っています。メソッドの種類は上記で挙げたImageHashとは異なります。

- AverageHash
- PHash
- MarrHildrethHash
- RadialVarianceHash
- BlockMeanHash
- ColorMomentHash

とりあえず、メソッドを元に出力値を出してみます。

# Development
 [The module brings implementations of different image hashing algorithms.](https://docs.opencv.org/master/d4/d93/group__img__hash.html)にあるコードを参考に実装します。

```makefile
LDLIBS =  -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_img_hash
```

```c++
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/img_hash.hpp>
#include <cxxabi.h>

template <typename T> inline void output_hash(const cv::Mat &src)
{
	cv::Mat dst;
	cv::Ptr<cv::img_hash::ImgHashBase> func;
	func = T::create();
	
	func->compute(src, dst);

	std::string name = abi::__cxa_demangle(typeid(T).name(), 0, 0, 0);
	std::cout << name << "\t" << dst << std::endl;
}

int main( int argc, char** argv )
{

	cv::Mat imageMat = cv::imread("./Mandrill.png", cv::IMREAD_COLOR  );
	output_hash<cv::img_hash::AverageHash>(imageMat);
	output_hash<cv::img_hash::PHash>(imageMat);
	output_hash<cv::img_hash::MarrHildrethHash>(imageMat);
	output_hash<cv::img_hash::RadialVarianceHash>(imageMat);
	output_hash<cv::img_hash::BlockMeanHash>(imageMat);
	output_hash<cv::img_hash::ColorMomentHash>(imageMat);
	return 0;
}
```

# Result

```
cv::img_hash::AverageHash	[  9, 129, 239,  54,  53,   3, 195, 229]
cv::img_hash::PHash	[249,   4,   6, 190, 240, 133,  27, 249]
cv::img_hash::MarrHildrethHash	[240, 120,  59,  19,  49,  56, 156,  14,  89,   5, 137, 192, 228, 177, 255, 211, 208,  27,  37, 143, 188,  28,  14,  71,  98, 109, 192, 102,  77, 196, 227, 241,  57, 121, 242,  27, 164, 237, 191,  31, 143, 199, 100, 214, 115, 100, 236, 196, 193, 240,  56, 127, 108, 182, 108, 252,  60,  29, 142,   6, 147, 192,  75,   3, 137, 193, 229, 240, 126, 114, 112, 201]
cv::img_hash::RadialVarianceHash	[ 69,  62, 255,  42, 193,  80, 150,  70,   0, 104,  38,  66,  73, 101,  61,  64,  81,  81,  82,  52,  56,  71,  52,  60,  63,  84,  65,  56,  70,  68,  72,  67,  65,  74,  78,  65,  55,  67,  63,  70]
cv::img_hash::BlockMeanHash	[  2,  96,   2,  64,   6,  96,   6, 230, 102, 238, 116, 126, 240, 111, 240,  14, 241,   7,  97,   7,  67,   3, 139,   3, 139, 177,  63, 190, 255,  63, 247,  15]
cv::img_hash::ColorMomentHash	[0.002503659152184849, 2.471939932761336e-08, 4.467702288443572e-11, 2.753929884994291e-11, 3.907790057002085e-22, -4.24497491111126e-15, -8.834156975661139e-22, 0.001648706395707542, 4.497051261680711e-09, 3.146007555704344e-11, 1.673749393818875e-11, 3.312616975083822e-23, 1.117844884043978e-15, 3.826433279309091e-22, 0.000964146950520186, 3.507447150328149e-09, 4.712173150861858e-13, 1.510627115300825e-12, -1.25450023857693e-24, 3.736683447133399e-17, 2.250099503660287e-25, 0.001264405759998173, 4.013957543909037e-09, 7.996919537752477e-13, 9.256331740589708e-13, -5.433089299218264e-25, -2.969719150917409e-17, -5.822668758123276e-25, 0.001221333669445427, 6.085465009873859e-10, 2.11537441698962e-14, 4.10817520446575e-12, -6.598046593374036e-25, 8.906807344893997e-17, 1.015545954823273e-24, 0.001350832788790067, 7.253752695174111e-10, 4.409337297754683e-13, 1.21565621533617e-12, -5.606880832077259e-26, -2.143611937655263e-17, 8.882584320430919e-25]
```

`ImageHash`と共通としてあるメソッドが`AverageHash``PHash`の２つですが結構値が異なります。
`aHash:01833f3cbc98ebfc` `pHash:df20607d1fa0d88f`をそれぞれ16進数から10進数に直すと `[1, 131, 63, 60, 188, 152, 235, 252]` `[223, 32, 96, 125, 31, 160, 216, 143]`になります。
同じメソッド名でもモジュールが異なるとアルゴリズムが少し違うみたいです。

# おわりに
ハッシュ値は出力できたので次は画像の類似度を計算してみます。どのようにハッシュ値にしているかは[Perceptual Hashを使って画像の類似度を計算してみる](https://tech.unifa-e.com/entry/2017/11/27/111546)が一番丁寧に説明していて参考になります。