如何找出图像中最主要/最常见的颜色?

105 投票
11 回答
116555 浏览
提问于 2025-04-16 01:15

我想用Python找出一张图片中最显眼的颜色或色调。无论是平均色调还是RGB中最常见的颜色都可以。我查过Python图像库,但在他们的手册里没找到相关的内容,也简单看过VTK。

不过,我找到了一段PHP脚本,可以实现我需要的功能,在这里(下载需要登录)。这个脚本似乎会把图片缩小到150*150的尺寸,以突出主要颜色。不过,之后我就有点迷茫了。我考虑过写一个程序,把图片缩小到小尺寸,然后检查每隔一个像素的颜色,但我想这样效率会很低(不过把这个想法做成C语言的Python模块可能是个不错的主意)。

但是,经过这些尝试,我还是不知道该怎么做。有没有简单又高效的方法来找出图片中的主色调呢?

11 个回答

24

你可以用很多不同的方法来做到这一点。其实你并不需要使用scipy和k-means,因为Pillow库在你调整图片大小或者把图片减少到某种调色板时,内部已经帮你处理好了这些事情。

解决方案1: 把图片缩小到1个像素。

def get_dominant_color(pil_img):
    img = pil_img.copy()
    img = img.convert("RGBA")
    img = img.resize((1, 1), resample=0)
    dominant_color = img.getpixel((0, 0))
    return dominant_color

解决方案2: 把图片的颜色减少到一个调色板。

def get_dominant_color(pil_img, palette_size=16):
    # Resize image to speed up processing
    img = pil_img.copy()
    img.thumbnail((100, 100))

    # Reduce colors (uses k-means internally)
    paletted = img.convert('P', palette=Image.ADAPTIVE, colors=palette_size)

    # Find the color that occurs most often
    palette = paletted.getpalette()
    color_counts = sorted(paletted.getcolors(), reverse=True)
    palette_index = color_counts[0][1]
    dominant_color = palette[palette_index*3:palette_index*3+3]

    return dominant_color

这两种解决方案的结果差不多。第二种方案可能更准确,因为在调整图片大小时我们保持了宽高比。而且你可以更好地控制,因为你可以调整palette_size

补充说明: 获取一份主要颜色的列表。

def get_dominant_colors(pil_img, palette_size=16, num_colors=10):
    # Resize image to speed up processing
    img = pil_img.copy()
    img.thumbnail((100, 100))

    # Reduce colors (uses k-means internally)
    paletted = img.convert('P', palette=Image.ADAPTIVE, colors=palette_size)

    # Find the color that occurs most often
    palette = paletted.getpalette()
    color_counts = sorted(paletted.getcolors(), reverse=True)

    dominant_colors = []
    for i in range(num_colors):
      palette_index = color_counts[i][1]
      dominant_colors.append(palette[palette_index*3:palette_index*3+3])

    return dominant_colors
62

试试 Color Thief,可以通过 PyPI 安装。唯一的要求是 Pillow

安装

pip install colorthief

使用方法

from colorthief import ColorThief

color_thief = ColorThief('/path/to/imagefile')

获取主色调

dominant_color = color_thief.get_color(quality=1)

输出结果是一个元组 (r, g, b),表示颜色的红、绿、蓝值。

注意:参数 quality 是可选的,可以在质量和速度之间做选择。设置为1时质量最高。数字越大,返回颜色的速度越快,但可能不是视觉上最突出的颜色。

构建调色板

palette = color_thief.get_palette(color_count=6, quality=1)

输出结果是一个元组列表 (r, g, b),每个元组代表一种颜色。

参数 color_count 是调色板的大小,也就是最多可以有多少种颜色。

注意:参数 quality 是可选的,可以在质量和速度之间做选择。设置为1时质量最高。数字越大,生成调色板的速度越快,但可能会漏掉一些颜色。

95

这里有段代码,使用了PillowScipy的聚类包

为了简单起见,我把文件名写死了,定为“image.jpg”。调整图片大小是为了加快处理速度:如果你不介意等待,可以把调整大小的那行代码注释掉。当在这个示例图片上运行时,

两个蓝色的甜椒放在黄色的盘子上

它通常会显示主导颜色是 #d8c865,这大致对应于两个甜椒左下角的亮黄色区域。我说“通常”是因为使用的聚类算法有一定的随机性。你可以通过不同的方法来改变这个结果,但对于你的需求来说,这个结果可能就足够了。(如果你需要确定的结果,可以看看kmeans2()的选项。)

from __future__ import print_function
import binascii
import struct
from PIL import Image
import numpy as np
import scipy
import scipy.misc
import scipy.cluster

NUM_CLUSTERS = 5

print('reading image')
im = Image.open('image.jpg')
im = im.resize((150, 150))      # optional, to reduce time
ar = np.asarray(im)
shape = ar.shape
ar = ar.reshape(scipy.product(shape[:2]), shape[2]).astype(float)

print('finding clusters')
codes, dist = scipy.cluster.vq.kmeans(ar, NUM_CLUSTERS)
print('cluster centres:\n', codes)

vecs, dist = scipy.cluster.vq.vq(ar, codes)         # assign codes
counts, bins = scipy.histogram(vecs, len(codes))    # count occurrences

index_max = scipy.argmax(counts)                    # find most frequent
peak = codes[index_max]
colour = binascii.hexlify(bytearray(int(c) for c in peak)).decode('ascii')
print('most frequent is %s (#%s)' % (peak, colour))

注意:当我把要找的聚类数量从5增加到10或15时,结果经常会出现偏绿色或偏蓝色的情况。考虑到输入的图片,这些结果也是合理的……我也无法判断那张图片中哪个颜色是真正的主导色,所以我不怪这个算法!

还有一个小额外:保存缩小后的图片,并只保留N种最常见的颜色:

# bonus: save image using only the N most common colours
import imageio
c = ar.copy()
for i, code in enumerate(codes):
    c[scipy.r_[scipy.where(vecs==i)],:] = code
imageio.imwrite('clusters.png', c.reshape(*shape).astype(np.uint8))
print('saved clustered image')

撰写回答