Поиск по цвету на сайтеПри создании интернет магазина, по продаже материалов или иной продукции, в котором, фотография товара, это один из основных признаков его выбора, и сайт больше напоминает галерею, рано или поздно возникает потребность в поиске изображений по цвету. Задача довольно распространенная. Если вы с ней не сталкивались то еще столкнетесь. В этой заметке, я опишу, как организовать поиск по цвету на сайте.

Для начала,  все фотографии на сайте нужно заранее проиндексировать. Это долгий процесс, делать его каждый раз при запросе нецелесообразно. При индексации, в изображении ищется доминирующий цвет и заносится в таблицу. Таблица будет примитивная 

CREATE TABLE `colors` (
`content_id` INT NOT NULL ,
`color` VARCHAR( 10 ) NOT NULL ,
UNIQUE (
`content_id`
)
) ENGINE = INNODB;

Для поиска доминирующего цвета можно воспользоваться скриптом, который был ранее описан на страницах блога. Суть его работы в том, что он пробегает все изображение попиксельно (шаг можно настраивать) и складывает в массив количество встречаемых цветов. После этого сортирует массив по убыванию и возвращает первые 10 элементов. Это отлично работает для монотонных изображений, но совершенно не подходит для градиентных или просто цветов с шумами. Перебрав кучу вариантов нашел этот. Он отлично работает для шумных и градиентных изображений, но иногда не совсем корректно определяет доминирующий цвет и работает очень медленно.

В процессе поиска, постоянно натыкался на алгоритм k средних. Нашел и его реализацию. Этот алгоритм работает практически идеально, но не корректно работает со светлыми  или темными изображениями, выдавая за доминирующий цвет - белый (черный). 

В результате принял решение, индексировать по методу k средних, но если он нашел доминирующий цвет, как белый (черный), то пропускать файл еще через Colors-Of-Image. И если уже и он не справился, то отдавать файл в руки первого скрипта, который в любом случае что-нибудь вернет.

Сама индексация простая. Проходим циклом по всем изображениям и заносим их основной или доминирующий цвет в базу

$content_id = 5;
if( file_exists(BASE_DIR.'/'.$file) ){	
	$colors = array();
	$colors = getDominantColors(BASE_DIR.'/'.$file, 3); // поиск по k - средним
	
	// находим расстояние между черным или белым и найденным цветом, если оно мало, то возможно скрипт отработал не корректно
	if( image_colors::getDistanceBetweenColors(image_colors::hex_to_rgb($colors[0]),image_colors::hex_to_rgb('000000'))<1 
		or image_colors::getDistanceBetweenColors(image_colors::hex_to_rgb($colors[0]),image_colors::hex_to_rgb('ffffff'))<1 ){
		$colors_of_image = new ColorsOfImage( BASE_DIR.'/'.$file,10,10 );
		$colors = $colors_of_image->getProminentColors();
		if( empty($colors[0]) )
			$colors = image_colors::getImageColor(BASE_DIR.'/'.$file,20,5);
	}
	
	if( !empty($colors[0]) ){
		$sql = "INSERT INTO  `colors` (`content_id` ,`color` ) VALUES ($content_id,'$colors[0]')";
		mysql_query($sql);
	}
}

Все необходимые файлы вы найдете в архиве с исходниками. Используя эти скрипты нужно понимать, что для индексации требуется значительное время. Это как замена видеокарты в ноутбуке, как минимум нужно понимать, что сперва надо выключить ноутбук. Аллигория надеюсь ясна, делать индексацию на фронтэнде не стоит, лучше запустить ее ночью, при минимуме посещений.

Пол дела сделано, настало время организовать сам поиск. Делать это будем при помощи сортировки по расстоянию между искомым цветом и всеми цветами таблицы colors. Расстояние вычислит процедура в mysql

DROP FUNCTION `diffColor`//
CREATE DEFINER=`localhost` FUNCTION `diffColor`(color1 varchar(6),color2 varchar(6)) RETURNS double
BEGIN
  DECLARE r1 INT;
  DECLARE r2 INT;
  DECLARE g1 INT;
  DECLARE g2 INT;
  DECLARE b1 INT;
  DECLARE b2 INT;

  set r1 = conv(SUBSTR(color1,1,2),16,10);
  set r2 = conv(SUBSTR(color2,1,2),16,10);
  set g1 = conv(SUBSTR(color1,3,2),16,10);
  set g2 = conv(SUBSTR(color2,3,2),16,10);
  set b1 = conv(SUBSTR(color1,5,2),16,10);
  set b2 = conv(SUBSTR(color2,5,2),16,10);
RETURN SQRT(pow(r1-r2,2)+pow(g1-g2,2)+pow(b1-b2,2));
END

Простым языком она измеряет длину вектора между двумя точками в трехмерном пространстве. На вход подается два цвета, искомый и цвет из colors. Проверить, работает ли процедура, можно при помощи такого запроса 

select diffColor('020202','f9f9f9');

в результате вы увидите расстояние между этими двумя цветами (вещественное чилсо). Дело осталось за малым. 

Сам поиск

$color = (isset($_REQUEST['color']) and preg_match('#^[a-f0-9]{6}$#i',$_REQUEST['color']))?$_REQUEST['color']:'';
if( $color ){
	$sql = "select * 
	from 
		contents as cont 
	inner join 
		colors as cols 
	where cols.content_id=cont.id 
	order by 
		diffColor('$color',cols.color) desc 
	limit 10";
	$db->query($sql);
	// далее 
}

Вот и все, вы увидите 10 товаров, доминирующий цвет которых, наиболее приближен к искомому.

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

Vyaches
# Vyaches 20.01.2020 07:45
Отличные статьи!