python实现图结构github_Github 项目 - ColorThief 颜色分析及Python实现

本文介绍了Python库color-thief-py,它是JavaScript库Color Thief的Python实现,用于从图像中提取主要颜色和创建调色板。通过示例展示了如何使用该库获取图片的主色和构建颜色调色板,并提供了Python API的详细说明。此外,还讨论了如何计算图像的平均颜色。
摘要由CSDN通过智能技术生成

Color Thief 是用于提取图片的主要颜色或者代表性颜色调色板的工具. 其使用是基于 javascript 和 canvas 的.

color-thief-py 是基于 Python 和 Pillow 的实现.

1. Color Thiefgit clone https://github.com/lokesh/color-thief.git

a82214e4e32c2ddc8d16a43d48145a3a.png

1.1. 使用

在页面中 include jquery、quantize.js 和 color-thief.js ,其使用如下:myImage = $('#myImage');

dominantColor = getDominantColor(myImage);

paletteArray = createPalette(myImage, 10); // 2nd argument sets # of colors in palette

1.2. 示例1 - 获取图片的主色

Get the dominant color from an image.var colorThief = new ColorThief();

colorThief.getColor(sourceImage);getColor(sourceImage[, quality])

returns [num, num, num]

1.3. 示例2 - 构建图片的颜色调色板

以构建图片的 8 种颜色调色板为例:var colorThief = new ColorThief();

colorThief.getPalette(sourceImage, 8);getPalette(sourceImage[, colorCount, quality])

returns [ [num, num, num], [num, num, num], ... ]

2. Color Thief - Python 库

[1] - Github:

2.1. pip 安装pip install colorthief

2.2. 使用from colorthief import ColorThief

color_thief = ColorThief('/path/to/test.jpg')

# get the dominant color

dominant_color = color_thief.get_color(quality=1)

print(dominant_color)

# build a color palette

palette = color_thief.get_palette(color_count=3)

print(palette)

对于图片:

09ffaad3c8f08b84823467b7d8c581d0.png

输出如:#dominant color

(193, 185, 174)

#color palette

[(190, 183, 172), (42, 40, 38), (133, 108, 74), (91, 76, 66)]

2.3. demo# -*- coding: utf-8 -*-

import sys

if sys.version_info < (3, 0):

from urllib2 import urlopen

else:

from urllib.request import urlopen

import io

from colorthief import ColorThief

fd = urlopen('http://lokeshdhakar.com/projects/color-thief/img/photo1.jpg')

f = io.BytesIO(fd.read())

color_thief = ColorThief(f)

print(color_thief.get_color(quality=1))

print(color_thief.get_palette(quality=1))

2.4. Python APIclass ColorThief(object):

def __init__(self, file):

"""Create one color thief for one image.

:param file: A filename (string) or a file object. The file object

must implement `read()`, `seek()`, and `tell()` methods,

and be opened in binary mode.

"""

pass

def get_color(self, quality=10):

"""Get the dominant color.

:param quality: quality settings, 1 is the highest quality, the bigger

the number, the faster a color will be returned but

the greater the likelihood that it will not be the

visually most dominant color

:return tuple: (r, g, b)

"""

pass

def get_palette(self, color_count=10, quality=10):

"""Build a color palette. We are using the median cut algorithm to

cluster similar colors.

:param color_count: the size of the palette, max number of colors

:param quality: quality settings, 1 is the highest quality, the bigger

the number, the faster the palette generation, but the

greater the likelihood that colors will be missed.

:return list: a list of tuple in the form (r, g, b)

"""

pass

具体的:# -*- coding: utf-8 -*-

__version__ = '0.2.1'

import math

from PIL import Image

class cached_property(object):

"""Decorator that creates converts a method with a single

self argument into a property cached on the instance.

"""

def __init__(self, func):

self.func = func

def __get__(self, instance, type):

res = instance.__dict__[self.func.__name__] = self.func(instance)

return res

class ColorThief(object):

"""Color thief main class."""

def __init__(self, file):

"""Create one color thief for one image.

:param file: A filename (string) or a file object. The file object

must implement `read()`, `seek()`, and `tell()` methods,

and be opened in binary mode.

"""

self.image = Image.open(file)

def get_color(self, quality=10):

"""Get the dominant color.

:param quality: quality settings, 1 is the highest quality, the bigger

the number, the faster a color will be returned but

the greater the likelihood that it will not be the

visually most dominant color

:return tuple: (r, g, b)

"""

palette = self.get_palette(5, quality)

return palette[0]

def get_palette(self, color_count=10, quality=10):

"""Build a color palette. We are using the median cut algorithm to

cluster similar colors.

:param color_count: the size of the palette, max number of colors

:param quality: quality settings, 1 is the highest quality, the bigger

the number, the faster the palette generation, but the

greater the likelihood that colors will be missed.

:return list: a list of tuple in the form (r, g, b)

"""

image = self.image.convert('RGBA')

width, height = image.size

pixels = image.getdata()

pixel_count = width * height

valid_pixels = []

for i in range(0, pixel_count, quality):

r, g, b, a = pixels[i]

# If pixel is mostly opaque and not white

if a >= 125:

if not (r > 250 and g > 250 and b > 250):

valid_pixels.append((r, g, b))

# Send array to quantize function which clusters values

# using median cut algorithm

cmap = MMCQ.quantize(valid_pixels, color_count)

return cmap.palette

class MMCQ(object):

"""Basic Python port of the MMCQ (modified median cut quantization)

algorithm from the Leptonica library (http://www.leptonica.com/).

"""

SIGBITS = 5

RSHIFT = 8 - SIGBITS

MAX_ITERATION = 1000

FRACT_BY_POPULATIONS = 0.75

@staticmethod

def get_color_index(r, g, b):

return (r << (2 * MMCQ.SIGBITS)) + (g << MMCQ.SIGBITS) + b

@staticmethod

def get_histo(pixels):

"""histo (1-d array, giving the number of pixels in each quantized

region of color space)

"""

histo = dict()

for pixel in pixels:

rval = pixel[0] >> MMCQ.RSHIFT

gval = pixel[1] >> MMCQ.RSHIFT

bval = pixel[2] >> MMCQ.RSHIFT

index = MMCQ.get_color_index(rval, gval, bval)

histo[index] = histo.setdefault(index, 0) + 1

return histo

@staticmethod

def vbox_from_pixels(pixels, histo):

rmin = 1000000

rmax = 0

gmin = 1000000

gmax = 0

bmin = 1000000

bmax = 0

for pixel in pixels:

rval = pixel[0] >> MMCQ.RSHIFT

gval = pixel[1] >> MMCQ.RSHIFT

bval = pixel[2] >> MMCQ.RSHIFT

rmin = min(rval, rmin)

rmax = max(rval, rmax)

gmin = min(gval, gmin)

gmax = max(gval, gmax)

bmin = min(bval, bmin)

bmax = max(bval, bmax)

return VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo)

@staticmethod

def median_cut_apply(histo, vbox):

if not vbox.count:

return (None, None)

rw = vbox.r2 - vbox.r1 + 1

gw = vbox.g2 - vbox.g1 + 1

bw = vbox.b2 - vbox.b1 + 1

maxw = max([rw, gw, bw])

# only one pixel, no split

if vbox.count == 1:

return (vbox.copy, None)

# Find the partial sum arrays along the selected axis.

total = 0

sum_ = 0

partialsum = {}

lookaheadsum = {}

do_cut_color = None

if maxw == rw:

do_cut_color = 'r'

for i in range(vbox.r1, vbox.r2+1):

sum_ = 0

for j in range(vbox.g1, vbox.g2+1):

for k in range(vbox.b1, vbox.b2+1):

index = MMCQ.get_color_index(i, j, k)

sum_ += histo.get(index, 0)

total += sum_

partialsum[i] = total

elif maxw == gw:

do_cut_color = 'g'

for i in range(vbox.g1, vbox.g2+1):

sum_ = 0

for j in range(vbox.r1, vbox.r2+1):

for k in range(vbox.b1, vbox.b2+1):

index = MMCQ.get_color_index(j, i, k)

sum_ += histo.get(index, 0)

total += sum_

partialsum[i] = total

else: # maxw == bw

do_cut_color = 'b'

for i in range(vbox.b1, vbox.b2+1):

sum_ = 0

for j in range(vbox.r1, vbox.r2+1):

for k in range(vbox.g1, vbox.g2+1):

index = MMCQ.get_color_index(j, k, i)

sum_ += histo.get(index, 0)

total += sum_

partialsum[i] = total

for i, d in partialsum.items():

lookaheadsum[i] = total - d

# determine the cut planes

dim1 = do_cut_color + '1'

dim2 = do_cut_color + '2'

dim1_val = getattr(vbox, dim1)

dim2_val = getattr(vbox, dim2)

for i in range(dim1_val, dim2_val+1):

if partialsum[i] > (total / 2):

vbox1 = vbox.copy

vbox2 = vbox.copy

left = i - dim1_val

right = dim2_val - i

if left <= right:

d2 = min([dim2_val - 1, int(i + right / 2)])

else:

d2 = max([dim1_val, int(i - 1 - left / 2)])

# avoid 0-count boxes

while not partialsum.get(d2, False):

d2 += 1

count2 = lookaheadsum.get(d2)

while not count2 and partialsum.get(d2-1, False):

d2 -= 1

count2 = lookaheadsum.get(d2)

# set dimensions

setattr(vbox1, dim2, d2)

setattr(vbox2, dim1, getattr(vbox1, dim2) + 1)

return (vbox1, vbox2)

return (None, None)

@staticmethod

def quantize(pixels, max_color):

"""Quantize.

:param pixels: a list of pixel in the form (r, g, b)

:param max_color: max number of colors

"""

if not pixels:

raise Exception('Empty pixels when quantize.')

if max_color < 2 or max_color > 256:

raise Exception('Wrong number of max colors when quantize.')

histo = MMCQ.get_histo(pixels)

# check that we aren't below maxcolors already

if len(histo) <= max_color:

# generate the new colors from the histo and return

pass

# get the beginning vbox from the colors

vbox = MMCQ.vbox_from_pixels(pixels, histo)

pq = PQueue(lambda x: x.count)

pq.push(vbox)

# inner function to do the iteration

def iter_(lh, target):

n_color = 1

n_iter = 0

while n_iter < MMCQ.MAX_ITERATION:

vbox = lh.pop()

if not vbox.count: # just put it back

lh.push(vbox)

n_iter += 1

continue

# do the cut

vbox1, vbox2 = MMCQ.median_cut_apply(histo, vbox)

if not vbox1:

raise Exception("vbox1 not defined; shouldn't happen!")

lh.push(vbox1)

if vbox2: # vbox2 can be null

lh.push(vbox2)

n_color += 1

if n_color >= target:

return

if n_iter > MMCQ.MAX_ITERATION:

return

n_iter += 1

# first set of colors, sorted by population

iter_(pq, MMCQ.FRACT_BY_POPULATIONS * max_color)

# Re-sort by the product of pixel occupancy times the size in

# color space.

pq2 = PQueue(lambda x: x.count * x.volume)

while pq.size():

pq2.push(pq.pop())

# next set - generate the median cuts using the (npix * vol) sorting.

iter_(pq2, max_color - pq2.size())

# calculate the actual colors

cmap = CMap()

while pq2.size():

cmap.push(pq2.pop())

return cmap

class VBox(object):

"""3d color space box"""

def __init__(self, r1, r2, g1, g2, b1, b2, histo):

self.r1 = r1

self.r2 = r2

self.g1 = g1

self.g2 = g2

self.b1 = b1

self.b2 = b2

self.histo = histo

@cached_property

def volume(self):

sub_r = self.r2 - self.r1

sub_g = self.g2 - self.g1

sub_b = self.b2 - self.b1

return (sub_r + 1) * (sub_g + 1) * (sub_b + 1)

@property

def copy(self):

return VBox(self.r1, self.r2, self.g1, self.g2,

self.b1, self.b2, self.histo)

@cached_property

def avg(self):

ntot = 0

mult = 1 << (8 - MMCQ.SIGBITS)

r_sum = 0

g_sum = 0

b_sum = 0

for i in range(self.r1, self.r2 + 1):

for j in range(self.g1, self.g2 + 1):

for k in range(self.b1, self.b2 + 1):

histoindex = MMCQ.get_color_index(i, j, k)

hval = self.histo.get(histoindex, 0)

ntot += hval

r_sum += hval * (i + 0.5) * mult

g_sum += hval * (j + 0.5) * mult

b_sum += hval * (k + 0.5) * mult

if ntot:

r_avg = int(r_sum / ntot)

g_avg = int(g_sum / ntot)

b_avg = int(b_sum / ntot)

else:

r_avg = int(mult * (self.r1 + self.r2 + 1) / 2)

g_avg = int(mult * (self.g1 + self.g2 + 1) / 2)

b_avg = int(mult * (self.b1 + self.b2 + 1) / 2)

return r_avg, g_avg, b_avg

def contains(self, pixel):

rval = pixel[0] >> MMCQ.RSHIFT

gval = pixel[1] >> MMCQ.RSHIFT

bval = pixel[2] >> MMCQ.RSHIFT

return all([

rval >= self.r1,

rval <= self.r2,

gval >= self.g1,

gval <= self.g2,

bval >= self.b1,

bval <= self.b2,

])

@cached_property

def count(self):

npix = 0

for i in range(self.r1, self.r2 + 1):

for j in range(self.g1, self.g2 + 1):

for k in range(self.b1, self.b2 + 1):

index = MMCQ.get_color_index(i, j, k)

npix += self.histo.get(index, 0)

return npix

class CMap(object):

"""Color map"""

def __init__(self):

self.vboxes = PQueue(lambda x: x['vbox'].count * x['vbox'].volume)

@property

def palette(self):

return self.vboxes.map(lambda x: x['color'])

def push(self, vbox):

self.vboxes.push({

'vbox': vbox,

'color': vbox.avg,

})

def size(self):

return self.vboxes.size()

def nearest(self, color):

d1 = None

p_color = None

for i in range(self.vboxes.size()):

vbox = self.vboxes.peek(i)

d2 = math.sqrt(

math.pow(color[0] - vbox['color'][0], 2) +

math.pow(color[1] - vbox['color'][1], 2) +

math.pow(color[2] - vbox['color'][2], 2)

)

if d1 is None or d2 < d1:

d1 = d2

p_color = vbox['color']

return p_color

def map(self, color):

for i in range(self.vboxes.size()):

vbox = self.vboxes.peek(i)

if vbox['vbox'].contains(color):

return vbox['color']

return self.nearest(color)

class PQueue(object):

"""Simple priority queue."""

def __init__(self, sort_key):

self.sort_key = sort_key

self.contents = []

self._sorted = False

def sort(self):

self.contents.sort(key=self.sort_key)

self._sorted = True

def push(self, o):

self.contents.append(o)

self._sorted = False

def peek(self, index=None):

if not self._sorted:

self.sort()

if index is None:

index = len(self.contents) - 1

return self.contents[index]

def pop(self):

if not self._sorted:

self.sort()

return self.contents.pop()

def size(self):

return len(self.contents)

def map(self, f):

return list(map(f, self.contents))

2.5. 返回颜色调色板的比例

为了在返回颜色调色板时,同时返回其各颜色的比例信息,需要进行如下修改:

由:def palette(self):

return self.vboxes.map(lambda x: x['color'])

更改为:def palette(self):

total = sum(self.vboxes.map(lambda x: x['vbox'].count))

return self.vboxes.map(lambda x: x['color'] + (x['vbox'].count, total, int(x['vbox'].count / float(total) * 100)))

3. 计算图片中的颜色均值#!/usr/bin/python3

# -*- coding: utf-8 -*-

from PIL import Image

def compute_average_image_color(img):

width, height = img.size

count, r_total, g_total, b_total = 0, 0, 0, 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)

if __name__ == '__main__':

img_pil = Image.open('/path/to/test.jpg')

mean_rgb = compute_average_image_color(img_pil)

print(mean_rgb)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值