PHPで画像を似た順で並べる

文字通り似た順で並べたかったのですが、以下のサイトを参考に画像を1つ決めて、それに似ている順で並べる方法を取りました。
(なので隣り合う画像はあまり似てません、何も考えずに作ると組み合わせ爆発するのでアイデアを思いつき次第、対応します > 続き:PHPで画像を色相順に並べる - yoyaのメモ)

このサイトのスクリプトを少し改造しました。

改造結果

オリジナルより速いですが、それでもそこそこ時間がかかるので、コマンドラインで動かす方式にしました。

$ php imagesort.php
Usage: php imagesort.php

[] ex) php imagesort.php img/ ex) php imagesort.php img/ img/foo.jpg $ php imagesort.php img/ img/foo.jpg > img.html

第一引数で対象フォルダ、第二引数で比較元画像を指定します。

  • 結果画像

第二引数を省略した場合は scandir で初めに拾った画像を比較元に決め打ちします。この場合は類似順にあまり意味がないですが、重複した画像が大抵隣に並ぶので探すのに便利です。神経衰弱するのは面倒ですから。

改良点

  • ImageDestroy を入れた。(これ入れないと 300枚位で落ちました。エラーも出さずに)
  • ImageCreateFromString を使った。(元は getimagesize で画像フォーマットを判別して ImageCreateFrom{JPEG|GIF|PNG}を切り替えて呼んでた)
  • array[]= を減らして、arrray(.., .., ..) で生成するようにした
  • basename してまた path と連結し直す処理があるけど、basename の処理を辞めた。
  • 改良じゃないけど変更
    • alt 属性にファイル名を入れた。_blank 指定で画像リンクを付けた。
    • 一覧画像のサイズ 40x40 を 64x64 に変更。
    • インデントを 2から4に増やし制御文の ( の前と ){ の間にスペースを入れた。
  • あえてやってない事
    • 色空間の距離は大小比較にしか使わないので、sqrt は取ってよいけど、少ししか早くならなかった
      • でも pow(a , 2) を a*a にすれば、もう少し速くなるかも。
    • rgb2xyz の 100* の処理を減らせるけど、これは体感できる程早くならなかった。

スクリプト

<?php

// original: http://php-archive.net/php/similar-images2/
// modified by yoya at 2013/09/16

if (($argc !== 2) && ($argc !== 3)) {
    echo "Usage: php imagesort.php <dir> [<filepath>]\n";
    echo "ex) php imagesort.php img/\n";
    echo "ex) php imagesort.php img/ img/foo.jpg\n";
    exit(1);
}

// 比較先画像ディレクトリ
$dir = $argv[1];

$list = scandir($dir);
$files = array();
foreach ($list as $value) {
    $path = $dir . $value;
    if (is_file($path)) {
        $files[] = $path;
    }
}

// 比較元の画像ファイル
if ($argc === 3) {
    $filepath = $argv[2];
} else {
    $filepath = $files[0]; // 指定がなければ初めに見つけた画像
}
 
$image = load_image($filepath);
$sample_lab = image_lab($image);
ImageDestroy($image);
 
$diff = array();
foreach ($files as $file) {
    $image  = load_image($file);
    if ($image === false) {
        continue; // skip
    }
    $lab   = image_lab($image);
    ImageDestroy($image); // 明示的に後始末しないとメモリリークする
    
    $distance = 0;
    foreach ($sample_lab as $key => $value) {
        $distance += lab_distance($value, $lab[$key]);
    }
    $diff[$file] = $distance;
}

asort($diff); // 差分の小さい順にソート
$result = array_keys($diff);

header("Content-type: text/html;charset=utf-8");
foreach ($result as $file) {
    echo "<a href=\"$file\" target=\"_blank\"> <img src='$file' width='64' height='64' alt='$file' /> </a>".PHP_EOL;
}

exit (0);
 
// 画像を読み込む
function load_image($filepath) {
    $data = file_get_contents($filepath);
    $image = @ImageCreateFromString($data); // XXX
    if ($image === false) {
        return false;
    }
    if (is_null($image)) {
        echo $filepath."\n";
    }
    return $image;
}

// 画像リソースからlab色空間上の座標を取得する
function image_lab($image) {
    $width = imagesx($image);
    $height = imagesy($image);
    $thumb_width   = 4;
    $thumb_height  = 4;
    $thumb = imagecreatetruecolor($thumb_width, $thumb_height);
    imagecopyresampled($thumb, $image, 0, 0, 0, 0,
                       $thumb_width, $thumb_height, $width, $height);
    
    $lab = array();
    $red   = 0;
    $green   = 0;
    $blue  = 0;
    
    for ($x=0; $x < $thumb_width; $x++) {
        for ($y=0; $y < $thumb_height; $y++) {
            $index   = imagecolorat($thumb, $x, $y);
            $rgb   = imagecolorsforindex($thumb, $index);
            $lab[]   = rgb2lab( array($rgb['red'], $rgb['green'], $rgb['blue']) );
        }
    }
    return $lab;
}
 
function xyz2lab($xyz) {
    $threshold = 0.008856;
    
    // D50
    $ref_x = 0.96422;
    $ref_y = 1.0000;
    $ref_z = 0.82521;
    
    $var_x = $xyz[0] / ($ref_x * 100);
    $var_y = $xyz[1] / ($ref_y * 100);    
    $var_z = $xyz[2] / ($ref_z * 100);
    
    $var_x = ($var_x > $threshold) ? $var_x = pow($var_x, 1/3 ) : (7.787 * $var_x) + (16 / 116);
    $var_y = ($var_y > $threshold) ? $var_y = pow($var_y, 1/3 ) : (7.787 * $var_y) + (16 / 116);
    $var_z = ($var_z > $threshold) ? $var_z = pow($var_z, 1/3 ) : (7.787 * $var_z) + (16 / 116);
    
    $l = ( 116 * $var_y ) - 16;
    $a = 500 * ( $var_x - $var_y );
    $b = 200 * ( $var_y - $var_z );
    
    $lab = array($l, $a, $b);
    return $lab;
}
  
function rgb2xyz($rgb) {
    $r = $rgb[0] / 255;
    $g = $rgb[1] / 255;
    $b = $rgb[2] / 255;
 
    $r = ($r > 0.04045) ? pow(($r + 0.055) / 1.055, 2.4) : $r / 12.92;
    $g = ($g > 0.04045) ? pow(($g + 0.055) / 1.055, 2.4) : $g / 12.92;
    $b = ($b > 0.04045) ? pow(($b + 0.055) / 1.055, 2.4) : $b / 12.92;
 
    $r = $r * 100;
    $g = $g * 100;
    $b = $b * 100;
    
    // sRGB D50
    $xyz = array(
        $r * 0.4360747 + $g * 0.3850649 + $b * 0.1430804,
        $r * 0.2225045 + $g * 0.7168786 + $b * 0.0606169,
        $r * 0.0139322 + $g * 0.0971045 + $b * 0.7141733
        );
    return $xyz;
}
 
// RGBをLabに変換する
function rgb2lab($rgb) {
    $xyz = rgb2xyz($rgb);
    $lab = xyz2lab($xyz);
    return $lab;
}
 
// 3次元空間上の2点間の距離を調べる
function lab_distance($p1, $p2){
    $dist = sqrt( pow($p2[0] - $p1[0], 2) + pow($p2[1] - $p1[1], 2) + pow($p2[2] - $p1[2], 2) );
    return $dist;
}