快速获取图像主色的方法

发布于 2024-10-17 22:32:17 字数 305 浏览 2 评论 0原文

我有一个关于如何获取图像(照片)的主色的问题。我想到了这个算法:循环遍历所有像素并获取它们的颜色,红色,绿色,黄色,橙色,蓝色,洋红色,青色,白色,灰色或黑色(当然有一些边距),并且它是黑暗的(浅色,深色或正常),然后检查哪些颜色出现最多。我认为这很慢而且不太精确。有更好的办法吗?


如果重要的话,它是从 iPhone 或 iPod touch 相机拍摄的 UIImage,最多 5 Mpx。它必须要快的原因是,简单地显示进度指示器没有多大意义,因为这是一个针对视力不佳或根本没有视力的人的应用程序。由于它适用于移动设备,因此可能不会占用太多内存(最多 50 MB)。

I have a question about how to get the dominant color of an image (a photo). I thought of this algorithm: loop through all pixels and get their color, either red, green, yellow, orange, blue, magenta, cyan, white, grey or black (with some margin of course) and it's darkness (light, dark or normal) and afterwards check which colors occurred the most. I think this is slow and not very precise. Is there a better way?


If it matters, it's a UIImage taken from an iPhone or iPod touch camera which is at most 5 Mpx. The reason it has to be fast is that simply showing a progress indicator doesn't make very much sense as this is for an app for people with bad sight, or no sight at all. Because it's for a mobile device, it may not take very much memory (at most 50 MB).

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

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

发布评论

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

评论(2

腹黑女流氓 2024-10-24 22:32:17

您的一般方法应该有效,但我会强调一些细节。

代替给定的颜色列表,在色谱中生成多个颜色“箱”来计算像素。这是另一个问题,其中有一些算法:生成光谱调色板垃圾箱的数量可配置,因此您可以尝试以获得您想要的结果。

接下来,对于您正在考虑的每个像素,您需要找到要递增的“最近”颜色箱。您需要定义“最近的”;请参阅这篇有关“色差”的文章:http://en.wikipedia.org/wiki/Color_difference

为了性能,您不需要查看每个像素。由于图像元素通常覆盖较大区域(例如天空、草地等),因此您只需对几个像素进行采样即可获得所需的结果。我猜想每 10 个像素甚至每 100 个像素采样一次就能得到很好的结果。您也可以尝试该因素。

[编者注:下面的段落经过编辑以适应 Mike Fairhurst 的评论。]

还可以对像素进行平均,如本演示所示:jsfiddle.net/MUsT8/

Your general approach should work, but I'd highlight some details.

Instead of your given list of colors, generate a number of color "bins" in the color spectrum to count pixels. Here's another question that has some algorithms for that: Generating spectrum color palettes Make the number of bins configurable, so you can experiment to get the results you want.

Next, for each pixel you're considering, you need to find the "nearest" color bin to increment. You'll need to define "nearest"; see this article on "color difference": http://en.wikipedia.org/wiki/Color_difference

For performance, you don't need to look at every pixel. Since image elements usually cover large areas (e.g., the sky, grass, etc.), you can get the result you want by only sampling a few pixels. I'd guess that you could get good results sampling every 10th pixel, or even every 100th. You can experiment with that factor as well.

[Editor's note: The paragraph below was edited to accommodate Mike Fairhurst's comment.]

Averaging pixels can also be done, as in this demo:jsfiddle.net/MUsT8/

入画浅相思 2024-10-24 22:32:17

我在研究主色算法时发现了这篇文章,该算法在动画循环中被多次调用。这是我对 @payne 答案的实现。我没有使用颜色差异,而是对一个簇的色调值进行平均。我还使用我编写的 Color 库进行转换。

// Defines hue clusters of major colors
// This was pretty much eyeballed, but it works well enough
// red, orange, yellow, green, cyan, blue, purple, pink
const CLUSTER_BOUNDS = [20,45,60,150,200,330,360];

// I want to avoid recreating this function every time
// this is why it's not in the function below
function getCluster(hue: number): number {
    // Conditional flow **might** be faster than loops
    // according to a small benchmark I did on V8
    // Assume valid hues (for performance since we run this a lot)
    if (hue <= CLUSTER_BOUNDS[0]) return 0;
    else if (hue <= CLUSTER_BOUNDS[1]) return 1;
    else if (hue <= CLUSTER_BOUNDS[2]) return 2;
    else if (hue <= CLUSTER_BOUNDS[3]) return 3;
    else if (hue <= CLUSTER_BOUNDS[4]) return 4;
    else if (hue <= CLUSTER_BOUNDS[5]) return 5;
    else return 6;
}

// Computes dominant color of an image using a mix of clustering,
// and sampling
export function dominantColor(pixels: Uint8ClampedArray): Color {
    let sl = [0, 0];
    const SAMPLE_EVERY = 50;
    const ITEM_SIZE = 4;
    const MAX_SATURATION = 90;
    const MIN_LIGHTNESS = 20;
    const MAX_LIGHTNESS = 90;
    const SATURATION_MULTIPLIER = 1.5;

    const clusters = Array(CLUSTER_BOUNDS.length).fill(0).map(e => [0, 0]); // [count, average hue of cluster]
    for (let i = 0; i < pixels?.length; i += ITEM_SIZE * SAMPLE_EVERY) {
        const pixel = Color.fromRGB(pixels[i], pixels[i + 1], pixels[i + 2]);
        const { h, s, l } = pixel.hsl;
        const cluster = getCluster(h);
        clusters[cluster][0] += 1;
        clusters[cluster][1] = (clusters[cluster][1] + h) / 2;
        sl[0] = (sl[0] + s) / 2;
        sl[1] = (sl[0] + l) / 2;
    }

    let hueCluster = 0;
    for (let i = 0; i < clusters.length; i++) {
        if (clusters[i][0] > clusters[hueCluster][0]) hueCluster = i;
    }

    return Color.fromHSL(
        Math.floor(clusters[hueCluster][1]),
        Math.min(MAX_SATURATION, sl[0] * SATURATION_MULTIPLIER),
        Math.min(MAX_LIGHTNESS, Math.max(MIN_LIGHTNESS, sl[1]))
    );
}

I came across this post while working on a dominant color algorithm that gets called a lot in an animation loop. Here is my implementation of @payne's answer. Instead of using color differences, I averaged hue values for a cluster. I also use a library I wrote Color for conversions.

// Defines hue clusters of major colors
// This was pretty much eyeballed, but it works well enough
// red, orange, yellow, green, cyan, blue, purple, pink
const CLUSTER_BOUNDS = [20,45,60,150,200,330,360];

// I want to avoid recreating this function every time
// this is why it's not in the function below
function getCluster(hue: number): number {
    // Conditional flow **might** be faster than loops
    // according to a small benchmark I did on V8
    // Assume valid hues (for performance since we run this a lot)
    if (hue <= CLUSTER_BOUNDS[0]) return 0;
    else if (hue <= CLUSTER_BOUNDS[1]) return 1;
    else if (hue <= CLUSTER_BOUNDS[2]) return 2;
    else if (hue <= CLUSTER_BOUNDS[3]) return 3;
    else if (hue <= CLUSTER_BOUNDS[4]) return 4;
    else if (hue <= CLUSTER_BOUNDS[5]) return 5;
    else return 6;
}

// Computes dominant color of an image using a mix of clustering,
// and sampling
export function dominantColor(pixels: Uint8ClampedArray): Color {
    let sl = [0, 0];
    const SAMPLE_EVERY = 50;
    const ITEM_SIZE = 4;
    const MAX_SATURATION = 90;
    const MIN_LIGHTNESS = 20;
    const MAX_LIGHTNESS = 90;
    const SATURATION_MULTIPLIER = 1.5;

    const clusters = Array(CLUSTER_BOUNDS.length).fill(0).map(e => [0, 0]); // [count, average hue of cluster]
    for (let i = 0; i < pixels?.length; i += ITEM_SIZE * SAMPLE_EVERY) {
        const pixel = Color.fromRGB(pixels[i], pixels[i + 1], pixels[i + 2]);
        const { h, s, l } = pixel.hsl;
        const cluster = getCluster(h);
        clusters[cluster][0] += 1;
        clusters[cluster][1] = (clusters[cluster][1] + h) / 2;
        sl[0] = (sl[0] + s) / 2;
        sl[1] = (sl[0] + l) / 2;
    }

    let hueCluster = 0;
    for (let i = 0; i < clusters.length; i++) {
        if (clusters[i][0] > clusters[hueCluster][0]) hueCluster = i;
    }

    return Color.fromHSL(
        Math.floor(clusters[hueCluster][1]),
        Math.min(MAX_SATURATION, sl[0] * SATURATION_MULTIPLIER),
        Math.min(MAX_LIGHTNESS, Math.max(MIN_LIGHTNESS, sl[1]))
    );
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文