[PHP]RGBをHSV(HSB)に変換する

前回RGB値を元に類似画像を検索しましたが、やはり人間の感覚で言うとHSVを使って画像を検索したほうがより近い画像を得られると考え、下準備として RGB を HSV に変換するプログラムを作りました。

<?php
//RGB値(0~255)
$rgb = array(
  "red" => 167,
  "green" => 65,
  "blue" => 189
);

$hsv = rgb2hsv($rgb);
print_r($hsv);

function rgb2hsv($rgb){
  $r = $rgb['red'] / 255;
  $g = $rgb['green'] / 255;
  $b = $rgb['blue'] / 255;
  
  $max = max($r, $g, $b);
  $min = min($r, $g, $b);
  $v = $max;
  
  if($max === $min){
    $h = 0;
  } else if($r === $max){
    $h = 60 * ( ($g - $b) / ($max - $min) ) + 0;
  } else if($g === $max){
    $h = 60 * ( ($b - $r) / ($max - $min) ) + 120;
  } else {
    $h = 60 * ( ($r - $g) / ($max - $min) ) + 240;
  }
  if($h < 0) $h = $h + 360;

  $s = ($v != 0) ? ($max - $min) / $max : 0;
  
  $hsv = array("h" => $h, "s" => $s, "v" => $v);
  return $hsv;
}

逆にHSVからRGBを割り出す場合はこちらを御覧下さい。

計算式は Wikipedia を参考にしています。

[PHP]似た色合いの画像をRGB値をもとに探す

<?php
//比較元となる画像
$filepath = "sample.jpg";
 
//比較対象用画像ディレクトリ
$dir = "images/";
 
$sample = loadImage($filepath);
$sample_rgb = colorAverage($sample);
imagedestroy($sample);
 
$list = scandir($dir);
 
$files = array();
foreach($list as $value){
  if(is_file($dir . $value)){
    $files[] = $dir . $value;
  }
}
 
$diff = array();
foreach($files as $file){
  $image = loadImage($file);
  $rgb = colorAverage($image);
  $name = basename($file);
  $diff[$name] = colorDistance($sample_rgb, $rgb);
}
 
asort($diff);
$result = array_keys($diff);
echo reset($result);
 
function loadImage($filepath){
  $checkimg = getimagesize($filepath);
 
  if($checkimg['mime'] == "image/jpeg" || $checkimg['mime'] == "image/pjpeg"){
    $extension = "jpg";
  } else if ($checkimg['mime'] == "image/gif"){
    $extension = "gif";
  } else if ($checkimg['mime'] == "image/png" || $checkimg['mime'] == "image/x-png"){
    $extension = "png";
  } else {
    return false;
  }
 
  if($extension == 'jpg'){$image = ImageCreateFromJPEG($filepath);}
  if($extension == 'gif'){$image = ImageCreateFromGIF($filepath);}
  if($extension == 'png'){$image = ImageCreateFromPNG($filepath);}
 
   
  return $image;
}
 
function colorAverage($image){
  $width = imagesx($image);
  $height = imagesy($image);
 
  $thumb_width   = 16;
  $thumb_height   = 16;
  $thumb = imagecreatetruecolor($thumb_width, $thumb_height);
  imagecopyresampled($thumb, $image, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
   
  $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);
      $red   += $rgb['red'];
      $green   += $rgb['green'];
      $blue   += $rgb['blue'];
    }
  }
   
  $average = array();
  $pixel = $thumb_width * $thumb_height;
  $average['red']     = round($red / $pixel);
  $average['green']   = round($green / $pixel);
  $average['blue']   = round($blue / $pixel);
   
  return $average;
}
 
function colorDistance($rgb1, $rgb2){
  $distance = 0;
  $distance += abs($rgb1['red']   - $rgb2['red']);
  $distance += abs($rgb1['green']   - $rgb2['green']);
  $distance += abs($rgb1['blue']   - $rgb2['blue']);
  return $distance;
}

ある画像のRGB値から平均色を算出し、似た平均色を持つ画像をディレクトリ内から探すというものです。

計算速度を早めるために、一度小さなサムネイルを作ってから色を拾っています。精度を高めるならこのサムネイルサイズを大きくすると良いと思います。

ただ、結論から言うとイマイチです。

計算には間違いないのですが、人間の目は明るく鮮やかな部分に目が行ってしまい、暗く地味な色を無意識に省略してしまいます。
また、明るさの違いよりも色の違いを重視する傾向があります。

このプログラムの計算方法だと、色の違いと明るさの違いを同じ価値として計算するため、同じ色合いで明暗が異なる画像より、多少色が違っても似た明るさを持つ画像が重視されてしまいます。

そのあたりを考慮するのであればHSV色空間(色相・彩度・明度)をもとに比較すべきなのかもしれません。

追記: HSV色空間を利用した画像検索も作りました。
また、Lab 色空間を利用した精度の高い類似画像検索についてはこちらの記事を御覧ください。

[PHP][HTML, CSS]画像を隙間なく並べて表示

高さの違う画像をタイル状に敷き詰めて表示させる場合、Javascript を利用することが多いですが、あえて PHP で表現すると次のようになります。

<?php
//カラム数(一段に並べる枚数)
$column = 3;
//幅
$width = 100;
//画像ディレクトリ
$dir = "images/";
$list = scandir($dir);

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

function columnGallery($files, $column, $width){

  $div = array();
  
  for($i=0;$i<$column;$i++){
    $heightTotal[$i] = 0;
  }
  
  foreach($files as $file){
    asort($heightTotal);
    $keys   = array_keys($heightTotal);
    $target   = reset($keys);
    $size   = getimagesize($file);
    $height   = $size[1];
    if(!isset($div[$target])) $div[$target] = array();
    $div[$target][] = $file;
    $heightTotal[$target] += $height;
  }
  
  foreach($div as $files){
    echo "<div class=\"column\">\n";
    foreach($files as $file){
      echo "<img src=\"{$file}\" style=\"width:{$width}px;\" /><br />\n";
    }
    echo "</div>\n";
  }
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Gallery</title>
<style type="text/css">
.column {float:left;}
</style>
</head>
<body>
<?php columnGallery($files, $column, $width);?>
</body>
</html>

サンプルではディレクトリ内のファイル全てを並べて表示しています。

画像の横幅を統一し、カラムとして横並びにすることでぴったり並んだように見せています。
応用すれば Pinterest のような表示もできると思います。

grid

カラムごとに画像の高さの合計値を計算し、一番短いカラムに画像を追加してバランスを取ります。

リキッドレイアウトでウィンドウサイズの変更に合わせて並び直しをしたい場合は素直に Javascript を使ったほうがいいと思います。

その場合は「jQuery Masonry」が便利です。
Masonry