前言
在 Groove 音樂中,當我們改變歌曲時,底部播放欄的顏色會隨專輯封面而變,比如下圖中播放欄的顏色變成了 aiko 衣服的顏色。下面我們會在 python 中實現相同的效果,也就是提取出圖片中的主題色。

實現流程
安裝依賴
提取主題色有很多方法,比如使用 k-means 聚類,選出 k 個 RGB 坐標的聚類中心,但是速度會差一些,我們這里換成中位切分法。已經有人為我們實現好這個算法了,我們可以拿來就用。
pip install color-thief
提取主題色
color-thief 雖然可以很好地提取出候選的主題色,但還是需要我們親自挑選出合適的主題色,甚至對主題色做出一些微調。比如上圖中的文字是淺色的,如果提取到的主題色也是淺色的,效果就很差了。下面是代碼:
# coding: utf-8
from math import floor
import numpy as np
from colorthief import ColorThief
class DominantColor:
""" 圖像主題色類 """
@classmethod
def getDominantColor(cls, imagePath: str):
""" 獲取指定圖片的主題色
Parameters
----------
imagePath: str
圖片路徑
Returns
-------
r, g, b: int
主題色各個通道的灰度值
"""
colorThief = ColorThief(imagePath)
# 調整圖像大小,加快運算速度
if max(colorThief.image.size) > 400:
colorThief.image = colorThief.image.resize((400, 400))
palette = colorThief.get_palette(quality=9)
# 調整調色板明度
palette = cls.__adjustPaletteValue(palette)
for rgb in palette[:]:
h, s, v = cls.rgb2hsv(rgb)
if h < 0.02:
palette.remove(rgb)
if len(palette) <= 2:
break
# 挑選主題色
palette = palette[:5]
palette.sort(key=lambda rgb: cls.colorfulness(*rgb), reverse=True)
return palette[0]
@classmethod
def __adjustPaletteValue(cls, palette: list):
""" 調整調色板的明度 """
newPalette = []
for rgb in palette:
h, s, v = cls.rgb2hsv(rgb)
if v > 0.9:
factor = 0.8
elif 0.8 < v <= 0.9:
factor = 0.9
elif 0.7 < v <= 0.8:
factor = 0.95
else:
factor = 1
v *= factor
newPalette.append(cls.hsv2rgb(h, s, v))
return newPalette
@staticmethod
def rgb2hsv(rgb: tuple) -> tuple:
""" rgb空間變換到hsv空間 """
r, g, b = [i / 255 for i in rgb]
mx = max(r, g, b)
mn = min(r, g, b)
df = mx - mn
if mx == mn:
h = 0
elif mx == r:
h = (60 * ((g - b) / df) + 360) % 360
elif mx == g:
h = (60 * ((b - r) / df) + 120) % 360
elif mx == b:
h = (60 * ((r - g) / df) + 240) % 360
s = 0 if mx == 0 else df / mx
v = mx
return h, s, v
@staticmethod
def hsv2rgb(h, s, v) -> tuple:
""" hsv空間變換到rgb空間 """
h60 = h / 60.0
h60f = floor(h60)
hi = int(h60f) % 6
f = h60 - h60f
p = v * (1 - s)
q = v * (1 - f * s)
t = v * (1 - (1 - f) * s)
r, g, b = 0, 0, 0
if hi == 0:
r, g, b = v, t, p
elif hi == 1:
r, g, b = q, v, p
elif hi == 2:
r, g, b = p, v, t
elif hi == 3:
r, g, b = p, q, v
elif hi == 4:
r, g, b = t, p, v
elif hi == 5:
r, g, b = v, p, q
r, g, b = int(r * 255), int(g * 255), int(b * 255)
return r, g, b
@staticmethod
def colorfulness(r: int, g: int, b: int):
rg = np.absolute(r - g)
yb = np.absolute(0.5 * (r + g) - b)
rg_mean, rg_std = np.mean(rg), np.std(rg)
yb_mean, yb_std = np.mean(yb), np.std(yb)
std_root = np.sqrt(rg_std ** 2 + yb_std ** 2)
mean_root = np.sqrt(rg_mean ** 2 + yb_mean ** 2)
return std_root + 0.3 * mean_root
測試
下面是一些圖片的測試結果,感覺效果還是挺不錯的:
