勉強したことのメモ

Webエンジニア / プログラマが勉強したことのメモ。

MySQLとPHPの「image-comparator」ライブラリを使用して類似画像検索を実装する方法

  PHP MySQL

先日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

 - PHP MySQL