MySQLとPHPの「image-comparator」ライブラリを使用して類似画像検索を実装する方法
先日PHPで画像を比較して類似度を算出する「image-comparator」ライブラリの利用方法をメモしたが、そのまま使うと処理時間がかかる。そのためMySQLに画像のハッシュ値を格納し、類似度を測定して類似しているもの順に結果を出力させたい。以下に実装方法をメモ。
事前準備
「image-comparator」ライブラリの導入
過去記事を参照の上、ライブラリを導入しておく。
テーブル作成
以下のようなテーブルを作成した。imageは画像のハッシュ値でnameは画像名となる。
mysql> SHOW COLUMNS FROM `test_table`; +-------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+--------------+------+-----+---------+----------------+ | id | int | NO | PRI | NULL | auto_increment | | image | varchar(255) | NO | | NULL | | | name | varchar(255) | NO | | NULL | | +-------+--------------+------+-----+---------+----------------+
実装方法
画像のハッシュ値を取得
「image-comparator」ライブラリを用いて以下のようにハッシュ値を取得できるので、上記テーブルに格納しておく。
<?php require 'vendor/autoload.php'; use SapientPro\ImageComparator\ImageComparator; $imageComparator = new ImageComparator(); $img = './img/001.webp'; $hash = $imageComparator->convertHashToBinaryString($imageComparator->hashImage($img)); var_dump($hash); //string(64) "0000111000001110001001100111110001111100011110000000100011000000"
以下のようなデータ内容になった。
mysql> SELECT * FROM `test_table`; +----+------------------------------------------------------------------+----------+ | id | image | name | +----+------------------------------------------------------------------+----------+ | 1 | 0000111000001110001001100111110001111100011110000000100011000000 | 001.webp | | 2 | 1110000011100000111110111011100000011000001100001110000011000000 | 002.webp | | 3 | 1111111101100111111000110011001100010011000000010111100111111111 | 003.webp | | 4 | 0110000000111100000111101101000011100100111111001111110011111110 | 004.webp | | 5 | 0000010000000100111001001000111110001101111101110010011101111001 | 005.webp | | 6 | 1100000011010010111110001111100001111101011010010110000000000000 | 006.webp | | 7 | 1111111111111111111111100000010000000000000110001110000011100001 | 007.webp | +----+------------------------------------------------------------------+----------+
検索用SQL文
@target_hashは比較させたい画像のハッシュ値で今回の場合は「001.webp」のハッシュ値になる。
尚、SQL文だけでまとめるため変数の@target_hashと@diff_maxを設定しているが、PHPからSQL文を発行する場合はPHPの変数に適宜書き換えること。
SET @target_hash:='0000111000001110001001100111110001111100011110000000100011000000'; SET @diff_max:=64; SELECT id, ( ( @diff_max + IF( SUBSTRING(@target_hash, 1, 1) != SUBSTRING(image, 1, 1), -1, 0) + IF( SUBSTRING(@target_hash, 2, 1) != SUBSTRING(image, 2, 1), -1, 0) + IF( SUBSTRING(@target_hash, 3, 1) != SUBSTRING(image, 3, 1), -1, 0) + IF( SUBSTRING(@target_hash, 4, 1) != SUBSTRING(image, 4, 1), -1, 0) + IF( SUBSTRING(@target_hash, 5, 1) != SUBSTRING(image, 5, 1), -1, 0) + IF( SUBSTRING(@target_hash, 6, 1) != SUBSTRING(image, 6, 1), -1, 0) + IF( SUBSTRING(@target_hash, 7, 1) != SUBSTRING(image, 7, 1), -1, 0) + IF( SUBSTRING(@target_hash, 8, 1) != SUBSTRING(image, 8, 1), -1, 0) + IF( SUBSTRING(@target_hash, 9, 1) != SUBSTRING(image, 9, 1), -1, 0) + IF( SUBSTRING(@target_hash, 10, 1) != SUBSTRING(image, 10, 1), -1, 0) + IF( SUBSTRING(@target_hash, 11, 1) != SUBSTRING(image, 11, 1), -1, 0) + IF( SUBSTRING(@target_hash, 12, 1) != SUBSTRING(image, 12, 1), -1, 0) + IF( SUBSTRING(@target_hash, 13, 1) != SUBSTRING(image,13, 1), -1, 0) + IF( SUBSTRING(@target_hash, 14, 1) != SUBSTRING(image,14, 1), -1, 0) + IF( SUBSTRING(@target_hash, 15, 1) != SUBSTRING(image,15, 1), -1, 0) + IF( SUBSTRING(@target_hash, 16, 1) != SUBSTRING(image,16, 1), -1, 0) + IF( SUBSTRING(@target_hash, 17, 1) != SUBSTRING(image,17, 1), -1, 0) + IF( SUBSTRING(@target_hash, 18, 1) != SUBSTRING(image,18, 1), -1, 0) + IF( SUBSTRING(@target_hash, 19, 1) != SUBSTRING(image,19, 1), -1, 0) + IF( SUBSTRING(@target_hash, 20, 1) != SUBSTRING(image,20, 1), -1, 0) + IF( SUBSTRING(@target_hash, 21, 1) != SUBSTRING(image,21, 1), -1, 0) + IF( SUBSTRING(@target_hash, 22, 1) != SUBSTRING(image,22, 1), -1, 0) + IF( SUBSTRING(@target_hash, 23, 1) != SUBSTRING(image,23, 1), -1, 0) + IF( SUBSTRING(@target_hash, 24, 1) != SUBSTRING(image,24, 1), -1, 0) + IF( SUBSTRING(@target_hash, 25, 1) != SUBSTRING(image,25, 1), -1, 0) + IF( SUBSTRING(@target_hash, 26, 1) != SUBSTRING(image,26, 1), -1, 0) + IF( SUBSTRING(@target_hash, 27, 1) != SUBSTRING(image,27, 1), -1, 0) + IF( SUBSTRING(@target_hash, 28, 1) != SUBSTRING(image,28, 1), -1, 0) + IF( SUBSTRING(@target_hash, 29, 1) != SUBSTRING(image,29, 1), -1, 0) + IF( SUBSTRING(@target_hash, 30, 1) != SUBSTRING(image,30, 1), -1, 0) + IF( SUBSTRING(@target_hash, 31, 1) != SUBSTRING(image,31, 1), -1, 0) + IF( SUBSTRING(@target_hash, 32, 1) != SUBSTRING(image,32, 1), -1, 0) + IF( SUBSTRING(@target_hash, 33, 1) != SUBSTRING(image,33, 1), -1, 0) + IF( SUBSTRING(@target_hash, 34, 1) != SUBSTRING(image,34, 1), -1, 0) + IF( SUBSTRING(@target_hash, 35, 1) != SUBSTRING(image,35, 1), -1, 0) + IF( SUBSTRING(@target_hash, 36, 1) != SUBSTRING(image,36, 1), -1, 0) + IF( SUBSTRING(@target_hash, 37, 1) != SUBSTRING(image,37, 1), -1, 0) + IF( SUBSTRING(@target_hash, 38, 1) != SUBSTRING(image,38, 1), -1, 0) + IF( SUBSTRING(@target_hash, 39, 1) != SUBSTRING(image,39, 1), -1, 0) + IF( SUBSTRING(@target_hash, 40, 1) != SUBSTRING(image,40, 1), -1, 0) + IF( SUBSTRING(@target_hash, 41, 1) != SUBSTRING(image,41, 1), -1, 0) + IF( SUBSTRING(@target_hash, 42, 1) != SUBSTRING(image,42, 1), -1, 0) + IF( SUBSTRING(@target_hash, 43, 1) != SUBSTRING(image,43, 1), -1, 0) + IF( SUBSTRING(@target_hash, 44, 1) != SUBSTRING(image,44, 1), -1, 0) + IF( SUBSTRING(@target_hash, 45, 1) != SUBSTRING(image,45, 1), -1, 0) + IF( SUBSTRING(@target_hash, 46, 1) != SUBSTRING(image,46, 1), -1, 0) + IF( SUBSTRING(@target_hash, 47, 1) != SUBSTRING(image,47, 1), -1, 0) + IF( SUBSTRING(@target_hash, 48, 1) != SUBSTRING(image,48, 1), -1, 0) + IF( SUBSTRING(@target_hash, 49, 1) != SUBSTRING(image,49, 1), -1, 0) + IF( SUBSTRING(@target_hash, 50, 1) != SUBSTRING(image,50, 1), -1, 0) + IF( SUBSTRING(@target_hash, 51, 1) != SUBSTRING(image,51, 1), -1, 0) + IF( SUBSTRING(@target_hash, 52, 1) != SUBSTRING(image,52, 1), -1, 0) + IF( SUBSTRING(@target_hash, 53, 1) != SUBSTRING(image,53, 1), -1, 0) + IF( SUBSTRING(@target_hash, 54, 1) != SUBSTRING(image,54, 1), -1, 0) + IF( SUBSTRING(@target_hash, 55, 1) != SUBSTRING(image,55, 1), -1, 0) + IF( SUBSTRING(@target_hash, 56, 1) != SUBSTRING(image,56, 1), -1, 0) + IF( SUBSTRING(@target_hash, 57, 1) != SUBSTRING(image,57, 1), -1, 0) + IF( SUBSTRING(@target_hash, 58, 1) != SUBSTRING(image,58, 1), -1, 0) + IF( SUBSTRING(@target_hash, 59, 1) != SUBSTRING(image,59, 1), -1, 0) + IF( SUBSTRING(@target_hash, 60, 1) != SUBSTRING(image,60, 1), -1, 0) + IF( SUBSTRING(@target_hash, 61, 1) != SUBSTRING(image,61, 1), -1, 0) + IF( SUBSTRING(@target_hash, 62, 1) != SUBSTRING(image,62, 1), -1, 0) + IF( SUBSTRING(@target_hash, 63, 1) != SUBSTRING(image,63, 1), -1, 0) + IF( SUBSTRING(@target_hash, 64, 1) != SUBSTRING(image,64, 1), -1, 0) ) / @diff_max * 100 ) AS diff, name FROM test_table ORDER BY diff DESC;
実行結果
+----+----------+----------+ | id | diff | name | +----+----------+----------+ | 1 | 100.0000 | 001.webp | | 6 | 59.3750 | 006.webp | | 2 | 53.1250 | 002.webp | | 4 | 53.1250 | 004.webp | | 7 | 51.5625 | 007.webp | | 5 | 48.4375 | 005.webp | | 3 | 39.0625 | 003.webp | +----+----------+----------+ 7 rows in set (0.00 sec)
所感
類似度計測については「image-comparator」ライブラリのcompareHashStringsメソッドのロジックをSQL文に落とし込んだものになる。
PHPだとループ部分は簡潔に書けるものの、MySQLだと難しいようでだいぶ冗長になった。ただ処理時間はPHPよりも大幅に短くなったので使っていけそうではある。
参考サイト
https://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html