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

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

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

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

【问题讨论】:

  • 我猜它会调整图片的大小,让重新缩放算法为你做一些平均。

标签: python image image-processing colors


【解决方案1】:

这是使用PillowScipy'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
          猜你喜欢
          • 1970-01-01
          • 2011-05-24
          • 2018-11-26
          • 2010-12-19
          • 2014-02-19
          • 1970-01-01
          • 2016-03-09
          • 1970-01-01
          • 2014-08-06
          相关资源
          最近更新 更多