Python-查找图像中占主导地位的/最常见的颜色

我正在寻找一种方法来找到一个图像中最主要的颜色/色调使用 python。无论是平均阴影或最常见的出 RGB 将做。我已经查看了 Python 图像库,在它们的手册中找不到任何与我要查找的内容相关的内容,在 VTK 中也找不到任何与此相关的内容。

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

尽管如此,我还是被难住了。所以我转向你,所以。是否有一种简单有效的方法来找到图像中的主导颜色。

95538 次浏览

Python Imaging Library has method getcolors on Image objects:

im.getcolors() => a list of (count, color) tuples or None

I guess you can still try resizing the image before that and see if it performs any better.

You could use PIL to repeatedly resize the image down by a factor of 2 in each dimension until it reaches 1x1. I don't know what algorithm PIL uses for downscaling by large factors, so going directly to 1x1 in a single resize might lose information. It might not be the most efficient, but it will give you the "average" color of the image.

Here's code making use of Pillow and Scipy's cluster package.

For simplicity I've hardcoded the filename as "image.jpg". Resizing the image is for speed: if you don't mind the wait, comment out the resize call. When run on this sample image,

two blue bell peppers on a yellow plate

it usually says the dominant colour is #d8c865, which corresponds roughly to the bright yellowish area to the lower left of the two peppers. I say "usually" because the clustering algorithm used has a degree of randomness to it. There are various ways you could change this, but for your purposes it may suit well. (Check out the options on the kmeans2() variant if you need deterministic results.)

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))

Note: when I expand the number of clusters to find from 5 to 10 or 15, it frequently gave results that were greenish or bluish. Given the input image, those are reasonable results too... I can't tell which colour is really dominant in that image either, so I don't fault the algorithm!

Also a small bonus: save the reduced-size image with only the N most-frequent colours:

# 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')

To add to Peter's answer, if PIL is giving you an image with mode "P" or pretty much any mode that isn't "RGBA", then you need to apply an alpha mask to convert it to RGBA. You can do that pretty easily with:

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

Below is a c++ Qt based example to guess the predominant image color. You can use PyQt and translate the same to Python equivalent.

#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();
}

If you're still looking for an answer, here's what worked for me, albeit not terribly efficient:

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)

Try Color-thief. It is based on Pillow and works awesome.

Installation

pip install colorthief

Usage

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

It can also find color pallete

palette = color_thief.get_palette(color_count=6)

It's not necessary to use k-means to find the dominant color as Peter suggests. This overcomplicates a simple problem. You're also restricting yourself by the amount of clusters you select so basically you need an idea of what you're looking at.

As you mentioned and as suggested by zvone, a quick solution to find the most common/dominant color is by using the Pillow library. We just need to sort the pixels by their count number.

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

The only problem is that the method getcolors() returns None when the amount of colors is more than 256. You can deal with it by resizing the original image.

In all, it might not be the most precise solution but it gets the job done.

You can do this in many different ways. And you don't really need scipy and k-means since internally Pillow already does that for you when you either resize the image or reduce the image to a certain pallete.

Solution 1: resize image down to 1 pixel.

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


Solution 2: reduce image colors to a pallete

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

Both solutions give similar results. The latter solution gives you probably more accuracy since we keep the aspect ratio when resizing the image. Also you get more control since you can tweak the pallete_size.

My solution

Here's my adaptation based on Peter Hansen's solution.

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

What are the differences/improvements?

It's (subjectively) more accurate

It's using the kmeans++ to pick initial cluster centers which gives better results. (kmeans++ may not be the fastest way to pick cluster centers though)

It's faster

Using sklearn.cluster.MiniBatchKMeans is significantly faster and gives very similar colors to the default KMeans algorithm. You can always try the slower sklearn.cluster.KMeans and compare the results and decide whether the tradeoff is worth it.

It's deterministic

I am using a random_state to get consistent ouput (I believe the original scipy.cluster.vq.kmeans also has a seed parameter). Before adding a random state I found that certain inputs could have significantly different outputs.

Benchmarks

I decided to very crudely benchmark a few solutions.

Method Time (100 iterations)
Peter Hansen (kmeans) 58.85
Artem Bernatskyi (Color Thief) 61.29
Artem Bernatskyi (Color Thief palette) 15.69
Pithikos (PIL resize) 0.11
Pithikos (palette) 1.68
Mine (mini batch kmeans) 6.31

This is a complete script with a function compute_average_image_color().

Just copy and past it, and change the path of your image.

My image is 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)