“四舍五入”最接近一小组颜色的颜色值

发布于 2024-09-29 16:03:18 字数 658 浏览 4 评论 0原文

序言

作为我正在进行的项目的一部分,我试图提供一种在我们的系统中搜索图像的便捷方法。我们目前提供通过各种类型的用户添加的元数据(例如标题、描述、关键字)和通过我们提取的各种元数据(例如 EXIF、IPTC、XMP 等)进行搜索。我还想添加一个“颜色搜索”,类似于您在谷歌的图像搜索中看到的内容。

该项目使用PHP,我们可以使用Imagemagick扩展来分割和量化图像并从图像中提取最“重要”的颜色;我并不完全确定我在这里得到的结果,但它们看起来相当准确,而且肯定比没有好。

问题

我遇到的困难是将这些重要颜色转换为一组代表性颜色,例如,当您查看 Google 的图像搜索 那里有一组 12 种颜色。我想在数学上将我的颜色值“舍入”到最接近的代表颜色,以便我可以使用我检测到的颜色对图像进行索引,然后以这种方式对我的搜索结果进行分面。

有什么建议吗?

Preamble

As a part of a project I'm working on I am trying to provide a convenient way to search for images in our system. We currently provide searching by various types of user added metadata (e.g. title, description, keywords) and by various metadata which we extract (e.g. EXIF, IPTC, XMP, etc). I would also like to add a "colour search" similar to what you can see in google's image search.

The project uses PHP and we can use the Imagemagick extension to segment and quantize the image and extract the most "significant" colours from the image; I'm not entirely certain of the results I'm getting here, but they seem reasonably accurate and certainly better than nothing.

The Problem

The bit that I'm having difficulty on is converting these significant colours into a set of representative colours, e.g. when you look at Google's image search there is a set of 12 colours there. I'd like to mathematically "round" my colour value to the nearest representative colour, so that I can index the image with the colours that I detect and then facet my search results that way.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不再见 2024-10-06 16:03:18

第一步是定义要比较的颜色。

第二步是找到您的颜色与您在上一步中选择的颜色之一之间的最小距离。为了能够测量该距离,您需要一个欧几里得空间来对颜色进行建模。

当然,简单的选择是 RGB 空间

alt text

以及两种颜色之间的距离 C1(r1, g1, b1) 和 C2(r2 sub>, g2, b2) 将是

sqrt( (r1 - r2 )2 + (g1 - g2)2 + (b1 - b2)2)

但如果您需要更高的精度,最好使用 Hue-Chroma-Lightness 双锥空间,它是 HSL 圆柱体的衍生形式。

alt text

在 RGB 空间中,事情很简单,如 R、G 和 B,其中每个都在单独的轴上。在 HCL 中,我们需要计算每个轴上的坐标。

首先,我们计算色度(与饱和度有点不同):

Chroma = max(Red, Green, Blue) - min(Red, Green, Blue)

然后我们对 H 进行归一化, C和L值,使得H从0到2(如果我们乘以PI并以弧度为单位覆盖一个圆),C从0到1(三角圆的半径),L从-1 (黑色)至 1(白色)。

接下来,我们采用 z = L 而不进行任何变换,因为从图像中可以清楚地看出它沿着垂直轴移动。

我们可以很容易地观察到,对于一种颜色,色度是距 z 轴的距离,色调是角度。所以我们得到

x = C * cos(H*PI)并且
y = C * sin(H*PI)

此时 x、y 和 z 都将在 [-1, 1] 中,并且两种颜色之间的距离将是,使用与上面相同的公式,

sqrt( (x1 - x2)2 + (y1 - y2)2 + (z1 - z2)2 )


为了获得更高的精度并根据人类颜色感知找到最接近的颜色,您可以使用 CIE- L*ab 建模空间并使用这些算法之一。原理与上面两种情况相同,只是算法更复杂。


更新(7年后)

xkcd终于推出了我可以在这篇文章中使用的漫画!

输入图像描述此处

https://xkcd.com/1882/

The first step would be to define the colors you want to compare to.

The second step is to find the smallest distance from your color to one of the colors you chose in the previous step. In order to be able to measure that distance you need an Euclidian space in which to model colors.

Of course the simple choice would be the RGB space

alt text

And the distance between two colors C1(r1, g1, b1) and C2(r2, g2, b2) would be

sqrt( (r1 - r2)2 + (g1 - g2)2 + (b1 - b2)2 ).

But if you need more precision it would be better to use the Hue-Chroma-Lightness bicone space, a derivative of the HSL cylinder.

alt text

In the RGB space things were straight forward as R, G and B where each on a separate axis. In HCL we need to compute the coordinates on each of the axis.

First of all we compute the chroma (which is a bit different from saturation) as:

Chroma = max(Red, Green, Blue) - min(Red, Green, Blue)

Then we normalize our H, C and L value so that H goes from 0 to 2 (to cover a circle if we multiply by PI and take radians as the unit), C goes from 0 to 1 (the radius of the trigonometric circle) and L goes from -1 (Black) to 1 (White).

Next we take z = L without any transformations as it is clear from the image that it goes along the vertical axis.

We can easily observe that for a color, Chroma is the distance from the z axis and Hue is the angle. So we get

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

At this point x, y and z will all be in [-1, 1] and the distance between two colors will be, using the same formula as above,

sqrt( (x1 - x2)2 + (y1 - y2)2 + (z1 - z2)2 ).


To get even more precision and find the closest color according to human color perception you could use the CIE-L*ab modeling space and compute the distance with one of these algorithms. The principles are the same as for the two cases presented above, only the algorithms are more complex.


Update (7 years later)

Finally xkcd featured a comic that I can use in this post!

enter image description here

https://xkcd.com/1882/

小猫一只 2024-10-06 16:03:18
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];
}
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];
}
难得心□动 2024-10-06 16:03:18

这只是一个粗略的想法 - 您需要根据自己的需要进行调整。

基本上,我认为,由于颜色被记录为 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 = 4,096 种可能的颜色。虽然仍然太多,但已经越来越接近了。

将颜色舍入为单个字符值,然后将其进一步限制为 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> > <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 种灰色阴影),并命名其余的颜色,以便您的用户可以通过它们进行搜索。

This is a rough idea only - you will need to tweak it to your own needs.

Basically, I thought that, as colors are recorded as RGB, either as a Hex string "#000000" to "#ffffff", or as an RGB set "rgb(0,0,0)" to "rgb(255,255,255)", and these are interchangeable/translateable, this is a simple mathematical rounding issue.

In the full range of colors there would be (16*16)*(16*16)*(16*16) = 256*256*256 = 16,777,216 possible colors.

Rounding colors to their closest single character Hex value reduces that to 16*16*16 = 4,096 possible colors. Still far too many, but getting closer.

Rounding colors to a single character value, but then limiting that further to being one of 4 (0,3,7,f) reduces it to 4*4*4 = 32. Close enough for me.

So, I built a very basic PHP function to try and achieve this:

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> > <span style="background-color:#%s;padding:0 10px;"></span>%s has been rounded to %s<br>' ,
         $incolor , $outR.$outG.$outB ,
         $incolor , $outR.$outG.$outB );
}

This function, when passed a hex string, echos a sample of the original color and a sample of the abbreviated color.

This is just a basic proof-of-concept, as I do not know the format Imagemagick is returning the colors as, but you may be able to utilise this logic to create your own.

From those 32 colors then, you could group the similar (there would probably be about 8 shades of grey in there) and name the remainder to allow your users to search by them.

素罗衫 2024-10-06 16:03:18

根据您要查找的颜色数量,为什么不尝试使用按位运算符(PHP 的参考 这里,因为您在问题中提到了)以减少有效数字的数量?您也可以在移动之前对 RGB 值进行四舍五入,以提高准确性。

Depending on the number of colors you're looking for, why not try using bitwise operators (PHP's reference here, since you mentioned it in the question) to reduce the number of significant digits? You could round the RGB values before shifting to increase accuracy as well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文