Медод k-means, он же метод k-средних, используется для распределения объектов на кластеры. В этой статье описывается сам алгоритм и то, как его можно применить для нахождения доминирующих цветов (цветов, которые встречаются чаще всего) на картинке.
Немного теории
Те, кому не интересна суть метода, могут пролистать до части с кодом.
Кластер в нужном нам смысле — это группа объектов с близкими по значению свойствами. У объектов может быть одно свойство, а может быть и несколько. Кстати, эти свойства — числа. Задача состоит в том, чтобы разбить имеющиеся объекты на определенное количество кластеров так, чтобы сумма отклонений от центра кластеров была минимальна. В итоге получится нужное количество "кучек" объектов.
Для простоты рассмотрим пример с множество объектов с 2-мя свойствами. Эть свойства будем представлять как координаты x и y на плоскости. Пусть у нас будет заранее заданное множество точек.
Далее из текущего числа объектов случайно определим как центроиды (обозначены звездами) столько точек, сколько надо выделить кластеров.
Теперь начинается цикл. Сначала для каждого объекта определяем ближайший к нему центроид, тем самым распределяем объекты по группам.
Далее для каждой группы объектов определяем центр масс и переносим туда центроид этой группы.
Цикл повторяется до тех пор, пока суммарный сдвиг центроидов в одном цикле не окажется меньше заранее заданной величины. В итоге текущие центроды и окажутся кластерами.
Применение метода для определение доминирующих цветов
Здесь все очень просто :). В качестве объектов используются цвета пикселей, а свойства — значение красного, синего и зеленого канала цвета.
// Функция, которая определяет "расстояние" между объектами function getDistance($arr1, $arr2, $l) { // Определяется "расстояние машины такси" — так быстрее, но для более точного результата желательно использовать евклидому метрику $s = 0; for($i = 0; $i < $l; ++$i) $s += $arr1[$i] > $arr2[$i] ? ($arr1[$i] - $arr2[$i]) : ($arr2[$i] - $arr1[$i]); return $s; } /* Сама функция, которая определяет доминирующие цвета Значения, передаваемые функции: $url — путь к картинке на сервере $amount — количество цветов, которые надо определить $sort — сортировать ли цвета по убыванию встречаемости на картинке $maxPreSize — максимальный размер уменьшаемой картинки (подробнее чуть дальше) $epselon — минимальное суммарное отклонение, необходимое для выхода из цикла */ function getDominantColors($url, $amount = 1, $sort = true, $maxPreSize = 50, $epselon = 1) { // Определяем, существует ли указанный файл. Об этом говорит сайт https://intellect.icu . Если нет, то выдаем ошибку. if(!file_exists($url)) return false; // Определяем размер картинки, если это картинка. $size = getimagesize($url); if($size === false) return false; $width = $size[0]; $height = $size[1]; // Определяем формат изображения из заголовка файла и проверяем, есть ли функция, которая может это изображение открыть. Если нет, значит файл — не картинка, и выдаем ошибку. $format = strtolower(substr($size['mime'], strpos($size['mime'], '/') + 1)); if($format == 'x-ms-bmp') $format = 'wbmp'; $func = 'imagecreatefrom'.$format; if(!function_exists($func)) return false; $bitmap = @$func($url); if(!$bitmap) return false; // Здесь мы уменьшаем исходную картинку, чтобы иметь дело не со всеми пикселями, а только с некоторыми. Это значительно увеличивает скорость алгоритма. Чем меньше размер — тем меньше точность. $newW = $width > $maxPreSize ? $maxPreSize : $width; $newH = $height > $maxPreSize ? $maxPreSize : $height; $bitmapNew = imagecreatetruecolor($newW, $newH); // Для большей точности надо использовать функцию imageCopyResambled, но она тратит очень много времени, поэтому используется быстрая и грубая функция imageCopyResized. imageCopyResized($bitmapNew, $bitmap, 0, 0, 0, 0, $newW, $newH, $width, $height); // Заносим цвета всех пикселей в один массив $pixelsAmount = $newW * $newH; $pixels = Array(); for($i = 0; $i < $newW; ++$i) for($j = 0; $j < $newH; ++$j) { $rgb = imagecolorat($bitmapNew, $i, $j); $pixels[] = Array( ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF ); } imagedestroy($bitmapNew); // Выбираем случайные пиксели для установки центроидов в них $clusters = Array(); $pixelsChosen = Array(); for($i = 0; $i < $amount; ++$i) { // Желательно не ставить несколько центроидов в одну точку do { $id = rand(0, $pixelsAmount - 1); } while(in_array($id, $pixelsChosen)); $pixelsChosen[] = $id; $clusters[] = $pixels[$id]; } $clustersPixels = Array(); $clustersAmounts = Array(); // Начинаем цикл do { // Обнуляем хранилище пикселей, принадлежащих текущему центроиду и их счетчик for($i = 0; $i < $amount; ++$i) { $clustersPixels[$i] = Array(); $clustersAmounts[$i] = 0; } // Проходимся по всем пикселям и определяем ближайший к ним центроид for($i = 0; $i < $pixelsAmount; ++$i) { $distMin = -1; $id = 0; for($j = 0; $j < $amount; ++$j) { $dist = getDistance($pixels[$i], $clusters[$j], 3); if($distMin == -1 or $dist < $distMin) { $distMin = $dist; $id = $j; } } $clustersPixels[$id][] = $i; ++$clustersAmounts[$id]; } // Перемещаем центроид в центр масс пикселей, принадлежащих ему, заодно вычисляем, насколько он сместился $diff = 0; for($i = 0; $i < $amount; ++$i) { if($clustersAmounts[$i] > 0) { $old = $clusters[$i]; for($k = 0; $k < 3; ++$k) { $clusters[$i][$k] = 0; for($j = 0; $j < $clustersAmounts[$i]; ++$j) $clusters[$i][$k] += $pixels[$clustersPixels[$i][$j]][$k]; $clusters[$i][$k] /= $clustersAmounts[$i]; } // Будем сравнивать максимальное отклонение $dist = getDistance($old, $clusters[$i], 3); $diff = $diff > $dist ? $diff : $dist; } } } while($diff >= $epselon); // Сортировка получившихся кластеров (не помню как метод называется) if($sort and $amount > 1) for($i = 1; $i < $amount; ++$i) for($j = $i; $j >= 1 and $clustersAmounts[$j] > $clustersAmounts[$j - 1]; --$j) { $t = $clustersAmounts[$j - 1]; $clustersAmounts[$j - 1] = $clustersAmounts[$j]; $clustersAmounts[$j] = $t; $t = $clusters[$j - 1]; $clusters[$j - 1] = $clusters[$j]; $clusters[$j] = $t; } // Значение цвета — величина целая, поэтому ее надо округлить for($i = 0; $i < $amount; ++$i) for($j = 0; $j < 3; ++$j) $clusters[$i][$j] = floor($clusters[$i][$j]); // Очищаем память imagedestroy($bitmap); // Возвращаем массив, который содержит доминирующие цвета. Каждый цвет — массив, состоящий из 3-х элементов — красный, зеленый и синий канал цвета. return $clusters; } $colors = getDominantColors('my_picture.jpg', 3); if($colors === false) echo 'Ошибка'; else foreach($colors as $color) echo '<div style="width: 50px; height: 40px; background-color: rgba($color[0], $color[1], $color[2], 1);"></div>';
второй способ
<?php
/**
* Class for Generator Image Color Palette.
* This file is class for return mamimum used colors in image, format return in HTML or RGB format.
*
* PHP versions 4 and 5
* Licensed under The GPL License
*
* Developed By Kalpeh Gamit
* E-mail Kalpa.programmer@gmail.com
*
* @filesource
* @copyright Copyright 2009, InScripter Inc. (http://www.inscripter.com)
* @link http://www.inscripter.com
* @package PHP
* @version GetImageColor-1.0.0
* @license http://www.opensource.org/licenses/gpl-2.0.php The GPL License
*/
// start of class : Generator Image Color Palette
class GeneratorImageColorPalette
{
// get image color in RGB format function
function getImageColor($imageFile_URL, $numColors, $image_granularity = 5)
{
$image_granularity = max(1, abs((int)$image_granularity));
$colors = array();
//find image size
$size = @getimagesize($imageFile_URL);
if($size === false)
{
user_error("Unable to get image size data");
return false;
}
// open image
$img = @imagecreatefromjpeg($imageFile_URL);
if(!$img)
{
user_error("Unable to open image file");
return false;
}
// fetch color in RGB format
$startx= round($size[0] / 4);
$endx= $size[0] -round($size[0] / 4);
$starty= round($size[1] / 4);
$endy= $size[1] -round($size[1] / 4);
for($x = $startx ; $x < $endx; $x += $image_granularity)
{
for($y =$starty; $y < $endy; $y += $image_granularity)
{
$thisColor = imagecolorat($img, $x, $y);
$rgb = imagecolorsforindex($img, $thisColor);
$red = round(round(($rgb['red'] / 0x33)) * 0x33);
$green = round(round(($rgb['green'] / 0x33)) * 0x33);
$blue = round(round(($rgb['blue'] / 0x33)) * 0x33);
$thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue);
if(array_key_exists($thisRGB, $colors))
{
$colors[$thisRGB]++;
}
else
{
$colors[$thisRGB] = 1;
}
}
}
arsort($colors);
// returns maximum used color of image format like #C0C0C0.
return array_slice( ($colors), 0, $numColors, true);
}
// html color to convert in RGB format color like R(255) G(255) B(255)
function getHtml2Rgb($str_color)
{
if ($str_color[0] == '#')
$str_color = substr($str_color, 1);
if (strlen($str_color) == 6)
list($r, $g, $b) = array($str_color[0].$str_color[1],
$str_color[2].$str_color[3],
$str_color[4].$str_color[5]);
elseif (strlen($str_color) == 3)
list($r, $g, $b) = array($str_color[0].$str_color[0], $str_color[1].$str_color[1], $str_color[2].$str_color[2]);
else
return false;
$r = hexdec($r); $g = hexdec($g); $b = hexdec($b);
$arr_rgb = array($r, $g, $b);
// Return colors format liek R(255) G(255) B(255)
return $arr_rgb;
}
// end of class here : Generator Image Color Palette
}
?>
<?php
require_once "getImageColor.php";// подключаем файл со скриптом
$img = new GeneratorImageColorPalette();
$colors = $img->getImageColor('1.jpg', 4, $image_granularity = 1);
print_r($colors);
foreach($colors as $color=>$cnt)
echo "<div style='background:#". ($color)."; width:100px; height:100px '></div><br />";
?>
результаты проверки цвета первым методом в самом начале статьи
вторым методом
Array ( [FFFFFF] => 4960 [CCCCCC] => 785 [FFFFCC] => 423 [FFCCFF] => 316 )
Комментарии
Оставить комментарий
Распознавание образов
Термины: Распознавание образов