Python - 在图像中查找主要/最常见的颜色

Posted

技术标签:

【中文标题】Python - 在图像中查找主要/最常见的颜色【英文标题】:Python - Find dominant/most common color in an image 【发布时间】:2011-03-15 14:10:46 【问题描述】:

我正在寻找一种方法来使用 python 在图像中找到最主要的颜色/色调。无论是平均色调还是最常见的 RGB 都可以。我查看了 Python Imaging 库,但在他们的手册中找不到任何与我正在寻找的内容相关的内容,也没有在 VTK 中找到简短的内容。

不过,我确实找到了一个 php 脚本,它可以满足我的需求,here(需要登录才能下载)。该脚本似乎将图像大小调整为 150*150,以突出主色。然而,在那之后,我相当迷茫。我确实考虑过写一些东西,将图像调整为小尺寸,然后检查它的图像的每个其他像素左右,尽管我认为这会非常低效(尽管将这个想法实现为 C python 模块可能是一个想法)。

然而,在这一切之后,我仍然很难过。所以我转向你,所以。有没有一种简单、有效的方法可以找到图像中的主色。

【问题讨论】:

我猜它会调整图片的大小,让重新缩放算法为你做一些平均。 【参考方案1】:

这是使用Pillow 和Scipy's cluster package 的代码。

为简单起见,我将文件名硬编码为“image.jpg”。调整图像大小是为了速度:如果您不介意等待,请注释掉 resize 调用。当在这个sample image of blue peppers 上运行时,它通常会说主色是#d8c865,它大致对应于两个辣椒左下角的亮黄色区域。我说“通常”是因为使用的clustering algorithm 具有一定程度的随机性。您可以通过多种方式更改此设置,但出于您的目的,它可能非常适合。 (如果您需要确定性结果,请查看 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')

【讨论】:

哇。那太棒了。几乎正是我想要的。我确实看过 scipy,感觉答案就在那里:P 谢谢你的回答。 我已经编辑/更新了您的代码。感谢这个紧凑且运行良好的解决方案! @SimonSteinberger 感谢您的编辑,我很高兴听到它在 7 年后仍然能够运行并帮助某人!这是一个有趣的问题。 这对 python 3.x 有多个问题。例如,(1).encode('hex') 是no longer valid syntax,和(2)from PIL import Image source 谢谢@philshem。我相信我现在已经对其进行了修改以支持 3.x。同时进行的一些更改解决了在 2.7 或 3.7(但不一定同时)上报告的弃用和警告。【参考方案2】:

试试Color-thief。它基于 Pillow 并且效果很好。

安装

pip install colorthief

用法

from colorthief import ColorThief
color_thief = ColorThief('/path/to/imagefile')
# get the dominant color
dominant_color = color_thief.get_color(quality=1)

它还可以找到调色板

palette = color_thief.get_palette(color_count=6)

【讨论】:

神奇的模块【参考方案3】:

Python Imaging Library 在 Image 对象上有方法 getcolors:

im.getcolors() => (count, 颜色)元组或无

我想你仍然可以在此之前尝试调整图像大小,看看效果是否更好。

【讨论】:

【参考方案4】:

您可以通过多种不同的方式做到这一点。而且您实际上并不需要 scipy 和 k-means,因为当您调整图像大小或将图像缩小到某个调色板时,Pillow 内部已经为您完成了这项工作。

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

def get_dominant_color(pil_img):
    img = pil_img.copy()
    img.convert("RGB")
    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

两种解决方案都给出了相似的结果。后一种解决方案可能会为您提供更高的准确性,因为我们在调整图像大小时会保持纵横比。由于您可以调整pallete_size,因此您还可以获得更多控制权。

【讨论】:

这也比上面的任何 scikit-learn/scipy 图像都要快。 像魅力一样工作,不需要任何额外的模块。非常感谢!【参考方案5】:

如果您仍在寻找答案,这对我有用,尽管效率不高:

from PIL import Image

def compute_average_image_color(img):
    width, height = img.size

    r_total = 0
    g_total = 0
    b_total = 0

    count = 0
    for x in range(0, width):
        for y in range(0, height):
            r, g, b = img.getpixel((x,y))
            r_total += r
            g_total += g
            b_total += b
            count += 1

    return (r_total/count, g_total/count, b_total/count)

img = Image.open('image.png')
#img = img.resize((50,50))  # Small optimization
average_color = compute_average_image_color(img)
print(average_color)

【讨论】:

对于 png,您需要稍微调整一下以处理 img.getpixel 返回 r,g,b,a(四个值而不是三个)的事实。或者它对我来说确实如此。 这会不均匀地加权像素。最后触摸的像素贡献了总值的一半。之前的像素贡献了一半。事实上,只有最后 8 个像素会影响平均值。 你是对的——愚蠢的错误。刚刚编辑了答案 - 如果可行,请告诉我。 这不是这个问题的答案。平均颜色不是图像中的主要颜色。【参考方案6】:

没有必要像 Peter 建议的那样使用 k-means 来查找主色。这使一个简单的问题变得过于复杂。您还通过选择的集群数量来限制自己,所以基本上您需要了解您正在查看的内容。

正如您所提到的和 zvone 所建议的,找到最常见/主要颜色的快速解决方案是使用 Pillow 库。我们只需要将像素按计数排序即可。

from PIL import Image

    def find_dominant_color(filename):
        #Resizing parameters
        width, height = 150,150
        image = Image.open(filename)
        image = image.resize((width, height),resample = 0)
        #Get colors from image object
        pixels = image.getcolors(width * height)
        #Sort them by count number(first element of tuple)
        sorted_pixels = sorted(pixels, key=lambda t: t[0])
        #Get the most frequent color
        dominant_color = sorted_pixels[-1][1]
        return dominant_color

唯一的问题是getcolors()方法在颜色数量超过256种时返回None。你可以通过调整原始图像的大小来处理。

总而言之,它可能不是最精确的解决方案,但它可以完成工作。

【讨论】:

你知道吗?您正在返回函数本身,尽管您正在为其分配一个值,但这不是一个好主意 你说得对,为此我编辑了函数的名称! 这不是很可靠。 (1) 您应该使用thumbnail 而不是调整大小以避免裁剪或拉伸,(2) 如果您的图像具有 2 个白色像素和 100 个不同级别的黑色像素,您仍然会得到白色。 同意,但我想避免在使用预定义集群或调色板时降低粒度的警告。根据用例,这可能是不可取的。【参考方案7】:

您可以使用 PIL 将图像的每个维度重复缩小 2 倍,直到达到 1x1。我不知道 PIL 使用什么算法来按大因子缩小比例,因此在单个调整大小中直接进入 1x1 可能会丢失信息。它可能不是最有效的,但它会为您提供图像的“平均”颜色。

【讨论】:

【参考方案8】:

补充彼得的答案,如果 PIL 给你一个模式为“P”或几乎任何非“RGBA”模式的图像,那么你需要应用一个 alpha 蒙版将其转换为 RGBA。你可以很容易地做到这一点:

if im.mode == 'P':
    im.putalpha(0)

【讨论】:

【参考方案9】:

我的解决方案

这是我根据 Peter Hansen 的解决方案改编的。

import scipy.cluster
import sklearn.cluster
import numpy
from PIL import Image

def dominant_colors(image):  # PIL image input

    image = image.resize((150, 150))      # optional, to reduce time
    ar = numpy.asarray(image)
    shape = ar.shape
    ar = ar.reshape(numpy.product(shape[:2]), shape[2]).astype(float)

    kmeans = sklearn.cluster.MiniBatchKMeans(
        n_clusters=10,
        init="k-means++",
        max_iter=20,
        random_state=1000
    ).fit(ar)
    codes = kmeans.cluster_centers_

    vecs, _dist = scipy.cluster.vq.vq(ar, codes)         # assign codes
    counts, _bins = numpy.histogram(vecs, len(codes))    # count occurrences

    colors = []
    for index in numpy.argsort(counts)[::-1]:
        colors.append(tuple([int(code) for code in codes[index]]))
    return colors                    # returns colors in order of dominance

有什么不同/改进?

它(主观上)更准确

它使用 kmeans++ 来选择初始聚类中心,从而获得更好的结果。 (虽然 kmeans++ 可能不是选择聚类中心的最快方法)

更快

使用sklearn.cluster.MiniBatchKMeans 明显更快,并且提供与默认 KMeans 算法非常相似的颜色。您可以随时尝试较慢的sklearn.cluster.KMeans 并比较结果并决定是否值得权衡。

这是确定性的

我正在使用 random_state 来获得一致的输出(我相信原来的 scipy.cluster.vq.kmeans 也有一个 seed 参数)。在添加随机状态之前,我发现某些输入可能具有显着不同的输出。

【讨论】:

感谢您加入解决方案 Jacob!很高兴看到这个有趣的问题仍然在帮助人们。 :)【参考方案10】:

下面是一个基于 c++ Qt 的示例,用于猜测图像的主要颜色。您可以使用 PyQt 并将其转换为 Python 等效项。

#include <Qt/QtGui>
#include <Qt/QtCore>
#include <QtGui/QApplication>

int main(int argc, char** argv)

    QApplication app(argc, argv);
    QPixmap pixmap("logo.png");
    QImage image = pixmap.toImage();
    QRgb col;
    QMap<QRgb,int> rgbcount;
    QRgb greatest = 0;

    int width = pixmap.width();
    int height = pixmap.height();

    int count = 0;
    for (int i = 0; i < width; ++i)
    
        for (int j = 0; j < height; ++j)
        
            col = image.pixel(i, j);
            if (rgbcount.contains(col)) 
                rgbcount[col] = rgbcount[col] + 1;
            
            else  
                rgbcount[col] = 1;
            

            if (rgbcount[col] > count)  
                greatest = col;
                count = rgbcount[col];
            

        
    
    qDebug() << count << greatest;
    return app.exec();

【讨论】:

【参考方案11】:

这是一个包含函数 compute_average_image_color() 的完整脚本。

只需复制并粘贴它,然后更改图像的路径。

我的图片是 img_path='./dir/image001.png'

#AVERANGE COLOR, MIN, MAX, STANDARD DEVIATION
#SELECT ONLY NOT TRANSPARENT COLOR


from PIL import Image
import sys
import os
import os.path
from os import path
import numpy as np
import math 



def compute_average_image_color(img_path):

    if not os.path.isfile(img_path):
        print(path_inp_image, 'DONT EXISTS, EXIT')
        sys.exit()

    
    #load image
    img = Image.open(img_path).convert('RGBA')
    img = img.resize((50,50))  # Small optimization


    #DEFINE SOME VARIABLES
    width, height = img.size
    r_total = 0
    g_total = 0
    b_total = 0
    count = 0
    red_list=[]
    green_list=[]
    blue_list=[]
    
    
    #READ AND CHECK PIXEL BY PIXEL
    for x in range(0, width):
        for y in range(0, height):
            r, g, b, alpha = img.getpixel((x,y))
            
            if alpha !=0:
                red_list.append(r)
                green_list.append(g)
                blue_list.append(b)
            
                r_total += r
                g_total += g
                b_total += b
                count += 1

            
    #CALCULATE THE AVRANGE COLOR, MIN, MAX, ETC             
    average_color=(round(r_total/count), round(g_total/count), round(b_total/count))
    print(average_color)
    
    red_list.sort()
    green_list.sort()
    blue_list.sort()

    
    red_min_max=[]
    green_min_max=[]
    blue_min_max=[]


    
    
    red_min_max.append(min(red_list))
    red_min_max.append(max(red_list))
    green_min_max.append(min(green_list))
    green_min_max.append(max(red_list))
    blue_min_max.append(min(blue_list))
    blue_min_max.append(max(blue_list))
    
    print('red_min_max: ', red_min_max)
    print('green_min_max: ', green_min_max)
    print('blue_min_max: ', blue_min_max)



    #variance and standard devietion
    red_stddev=round(math.sqrt(np.var(red_list)))
    green_stddev=round(math.sqrt(np.var(green_list)))
    blue_stddev=round(math.sqrt(np.var(blue_list)))

    print('red_stddev: ', red_stddev)
    print('green_stddev: ', green_stddev)
    print('blue_stddev: ', blue_stddev)






img_path='./dir/image001.png'
compute_average_image_color(img_path)





【讨论】:

你能解释一下你的代码吗? (如果有的话,你使用了哪些库或模块以及为什么)。让其他人了解您的研究、您的代码和替代方案的优缺点会很好。最好添加一些解释以便为读者提供上下文。 看起来更好。这是一个完整的python脚本。有 7-8 条 IMPORT 指令。并且每一行代码都有注释。这是一个脚本,因此用户可以复制和粘贴它。您刚刚更改了输入图像的名称 image001.png

以上是关于Python - 在图像中查找主要/最常见的颜色的主要内容,如果未能解决你的问题,请参考以下文章

RGB 图像中最主要的颜色 - OpenCV / NumPy / Python

从图像中提取主要/最常用的颜色

从图像中提取最常见的颜色

常见的几种抠图方式?

在 python 解释中查找列表中最常见的元素? [复制]

获取图像颜色