«Округление» значений цвета до ближайшего из небольшого набора цветов - PullRequest
30 голосов
/ 30 октября 2010

Преамбула

В рамках проекта, над которым я работаю, я пытаюсь предоставить удобный способ поиска изображений в нашей системе. В настоящее время мы предоставляем поиск по различным типам добавленных пользователем метаданных (например, заголовок, описание, ключевые слова) и по различным метаданным, которые мы извлекаем (например, EXIF, IPTC, XMP и т. Д.). Я также хотел бы добавить «цветной поиск», аналогичный тому, что вы можете видеть в поиске картинок в Google.

В проекте используется PHP, и мы можем использовать расширение Imagemagick для сегментирования и квантования изображения и извлечения наиболее «значимых» цветов из изображения; Я не совсем уверен в результатах, которые я получаю, но они кажутся достаточно точными и, безусловно, лучше, чем ничего.

Проблема

Единственное, с чем я сталкиваюсь, это преобразование этих значимых цветов в набор репрезентативных цветов, например, если вы посмотрите на Поиск картинок Google , там будет набор из 12 цветов. Я хотел бы математически «округлить» мое значение цвета до ближайшего представительного цвета, чтобы я мог проиндексировать изображение по цветам, которые я обнаружил, и затем обработать результаты поиска таким образом.

Есть предложения?

Ответы [ 4 ]

86 голосов
/ 05 декабря 2010

Первым шагом будет определение цветов, с которыми вы хотите сравнить.

Второй шаг - найти наименьшее расстояние от вашего цвета до одного из цветов, выбранных вами на предыдущем шаге. Чтобы измерить это расстояние, вам нужно евклидово пространство, в котором можно моделировать цвета.

Конечно, простым выбором будет пространство RGB

alt text

А расстояние между двумя цветами C 1 (r 1 , г 1 , b 1 ) и C 2 (r 2 , г 2 , b 2 ) будет

sqrt ((r 1 - r 2 ) 2 + (г 1 - г 2 ) 2 + (b 1 - b 2 ) 2 ) .

Но если вам нужна большая точность, лучше использовать биконовое пространство Hue-Chroma-Lightness, производное цилиндра HSL.

alt text

В пространстве RGB вещи были прямыми, как R, G и B, где каждая на отдельной оси. В HCL нам нужно вычислить координаты на каждой оси.

Прежде всего, мы вычисляем цветность (которая немного отличается от насыщенности) как:

Chroma = max (красный, зеленый, синий) - мин (красный, зеленый, синий)

Затем мы нормализуем наши значения H, C и L таким образом, чтобы H переходил от 0 до 2 (чтобы покрыть круг, если мы умножим на PI и примем радианы в качестве единицы), C перейдет от 0 до 1 (радиус тригонометрический круг) и L переходит от -1 (черный) к 1 (белый).

Далее мы берем z = L без каких-либо преобразований, поскольку из изображения ясно, что оно идет вдоль вертикальной оси.

Мы можем легко заметить, что для цвета Chroma - это расстояние от оси z, а Hue - это угол. Итак, мы получаем

x = C * cos (H * PI) и
y = C * sin (H * PI)

В этот момент все x, y и z будут в [-1, 1], а расстояние между двумя цветами будет, по той же формуле, что и выше,

sqrt ((x 1 - x 2 ) 2 + (y 1 - y 2 ) 2 + (z 1 - z 2 ) 2 ) .


Чтобы получить еще большую точность и найти ближайший цвет в соответствии с человеческим восприятием цветов, вы можете использовать CIE-L * ab пространство моделирования и вычислить расстояние с помощью одного из этих алгоритмов, Принципы те же, что и в двух приведенных выше случаях, только алгоритмы являются более сложными.


Обновление (7 лет спустя)

Наконец-то на xkcd появился комикс, который я могу использовать в этом посте!

enter image description here

https://xkcd.com/1882/

2 голосов
/ 20 декабря 2011
function getSimilarColors (color) {
    var base_colors=["660000","990000","cc0000","cc3333","ea4c88","993399","663399","333399","0066cc","0099cc","66cccc","77cc33","669900","336600","666600","999900","cccc33","ffff00","ffcc33","ff9900","ff6600","cc6633","996633","663300","000000","999999","cccccc","ffffff"];

    //Convert to RGB, then R, G, B
    var color_rgb = hex2rgb(color);
    var color_r = color_rgb.split(',')[0];
    var color_g = color_rgb.split(',')[1];
    var color_b = color_rgb.split(',')[2];

    //Create an emtyp array for the difference betwwen the colors
    var differenceArray=[];

    //Function to find the smallest value in an array
    Array.min = function( array ){
           return Math.min.apply( Math, array );
    };


    //Convert the HEX color in the array to RGB colors, split them up to R-G-B, then find out the difference between the "color" and the colors in the array
    $.each(base_colors, function(index, value) { 
        var base_color_rgb = hex2rgb(value);
        var base_colors_r = base_color_rgb.split(',')[0];
        var base_colors_g = base_color_rgb.split(',')[1];
        var base_colors_b = base_color_rgb.split(',')[2];

        //Add the difference to the differenceArray
        differenceArray.push(Math.sqrt((color_r-base_colors_r)*(color_r-base_colors_r)+(color_g-base_colors_g)*(color_g-base_colors_g)+(color_b-base_colors_b)*(color_b-base_colors_b)));
    });

    //Get the lowest number from the differenceArray
    var lowest = Array.min(differenceArray);

    //Get the index for that lowest number
    var index = differenceArray.indexOf(lowest);

    //Function to convert HEX to RGB
    function hex2rgb( colour ) {
        var r,g,b;
        if ( colour.charAt(0) == '#' ) {
            colour = colour.substr(1);
        }

        r = colour.charAt(0) + colour.charAt(1);
        g = colour.charAt(2) + colour.charAt(3);
        b = colour.charAt(4) + colour.charAt(5);

        r = parseInt( r,16 );
        g = parseInt( g,16 );
        b = parseInt( b ,16);
        return r+','+g+','+b;
    }

    //Return the HEX code
    return base_colors[index];
}
2 голосов
/ 30 октября 2010

Это грубая идея - вам нужно настроить ее под свои нужды.

По сути, я думал, что, поскольку цвета записываются как RGB, либо как шестнадцатеричная строка "# 000000" в "#ffffff", либо в качестве RGB установите "rgb (0,0,0)" в "rgb ( 255,255,255) ", и они являются взаимозаменяемыми / переводимыми, это простая математическая проблема округления.

В полном диапазоне цветов будет (16 * 16) * (16 * 16) * (16 * 16) = 256 * 256 * 256 = 16 777 216 возможных цветов.

Округление цветов до ближайшего значения шестнадцатеричного одиночного символа уменьшает его до 16 * 16 * 16 = 4096 возможных цветов. Все еще слишком много, но все ближе.

Округление цветов до одного символьного значения, но затем ограничение его до значения, равного одному из 4 (0,3,7, f), уменьшает его до 4 * 4 * 4 = 32.

Итак, я создал очень простую функцию PHP, чтобы попытаться достичь этого:

function coloround( $incolor ){
  $inR = hexdec( $incolor{0}.$incolor{1} )+1;
  $inG = hexdec( $incolor{2}.$incolor{3} )+1;
  $inB = hexdec( $incolor{4}.$incolor{5} )+1;
 # Round from 256 values to 16
  $outR = round( $outR/16 );
  $outG = round( $outG/16 );
  $outB = round( $outB/16 );
 # Round from 16 to 4
  $outR = round( $outR/4 );
  $outG = round( $outG/4 );
  $outB = round( $outB/4 );
 # Translate to Hex
  $outR = dechex( max( 0 , $outR*4-1 ) );
  $outG = dechex( max( 0 , $outG*4-1 ) );
  $outB = dechex( max( 0 , $outB*4-1 ) );
 # Output
  echo sprintf( '<span style="background-color:#%s;padding:0 10px;"></span> &gt; <span style="background-color:#%s;padding:0 10px;"></span>%s has been rounded to %s<br>' ,
         $incolor , $outR.$outG.$outB ,
         $incolor , $outR.$outG.$outB );
}

Эта функция при передаче шестнадцатеричной строки выводит образец исходного цвета и образец сокращенного цвета.

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

Из этих 32 цветов вы можете сгруппировать похожие (вероятно, там будет около 8 оттенков серого) и назвать остаток, чтобы позволить вашим пользователям осуществлять поиск по ним.

0 голосов
/ 05 декабря 2010

В зависимости от количества цветов, которые вы ищете, почему бы не попробовать использовать побитовые операторы (ссылка PHP здесь , так как вы упомянули это в вопросе), чтобы уменьшить количество значащих цифр?Вы можете округлить значения RGB перед сдвигом, чтобы повысить точность.

...