使用Python查找具有相似色彩盘的图像

6 投票
2 回答
6541 浏览
提问于 2025-04-15 15:50

假设在一个画廊里有10,000张JPEG和PNG格式的图片,怎么找到和选定的图片颜色搭配相似的所有图片,并按相似度从高到低排序呢?

2 个回答

0

另一种解决方案是对每个调色板进行K均值聚类,以便将颜色分组,然后根据颜色的色调对每个调色板进行排序,最后使用余弦相似度来找到最相似的图像。

下面是一段代码,可以在一堆图像中找到与参考图像最相似的图像:

from PIL import Image
import os
import numpy as np
from sklearn.cluster import MiniBatchKMeans
from numpy.linalg import norm
from tqdm import tqdm
from skimage.color import rgb2hsv


def extract_palette(image, num_colors):
  image_array = np.array(image)
  pixels = image_array.reshape((-1, 3))
  kmeans = MiniBatchKMeans(n_clusters=num_colors, n_init='auto')
  kmeans.fit(pixels)
  colors = kmeans.cluster_centers_
  return colors


def order_vector_by_hue(colors):
  hsv_colors = rgb2hsv(np.array([colors]))
  ordered_indices = np.argsort(hsv_colors[:, :, 0])
  ordered_rgb_colors = colors[ordered_indices]
  return ordered_rgb_colors


def cosine_sim(u, v):
  return np.dot(u, v) / (norm(u) * norm(v))


if __name__ == "__main__":
  ref_image_path = '<your-path>'
  folder = '<your-image-folder>'

  files = os.listdir(folder)

  print('processing ref image')
  image = Image.open(image_path)
  ref_colors = extract_palette(image, num_colors=32)
  ref_colors = order_vector_by_hue(colors).reshape(-1)

  print('processing candidate images')
  selected_image_path = None
  max_similarity = -1 # value for the most dissimilar possible image
  for image_path in files:
    image = Image.open(image_path)
    colors = extract_palette(image, num_colors=32)
    colors = order_vector_by_hue(colors).reshape(-1)
    similarity = cosine_sim(ref_colors, colors)
    if similarity > max_similarity:
      max_similarity = similarity
      selected_image_path = image_path

  print(f'most similar image: {selected_image_path}')

补充:可能还有其他方法可以改进这一切。如果我有时间,我会尝试使用主成分分析(PCA)来压缩调色板,并使用Lab颜色空间来排序(和压缩?)这些向量。这个解决方案对我来说现在已经足够好了。对于一万张图像来说,这可能会比较慢(K均值聚类)。而我的使用场景只有几百张图像。

补充2:一个快速的替代聚类的方法是随机选择我们像素中的num_colors。根据你的使用场景,这可能已经足够了。

def extract_palette(image, num_colors):
  image_array = np.array(image)
  pixels = image_array.reshape((-1, 3))
  selected_colors = pixels[np.random.choice(len(pixels), num_colors, replace=False)]
  return selected_colors  
11

为每张图片建立一个颜色直方图。然后,当你想把一张图片和其他图片进行匹配时,只需根据它们的直方图与选定图片的直方图的相似度来排序即可。

桶的数量取决于你想要的准确度。用来组成一个桶的数据类型将决定你搜索的优先级。

举个例子,如果你最关心的是色调(颜色的种类),那么你可以定义每个像素应该放入哪个桶,方法是:

def bucket_from_pixel(r, g, b):
    hue = hue_from_rgb(r, g, b) # [0, 360)
    return (hue * NUM_BUCKETS) / 360

如果你还想要一个更通用的匹配器,那么你可以根据完整的RGB值来选择桶。

使用PIL库,你可以使用内置的histogram函数。直方图的“相似度”可以用你想要的任何距离度量来计算。例如,L1距离可以表示为:

hist_sel = normalize(sel.histogram())
hist = normalize(o.histogram()) # These normalized histograms should be stored

dist = sum([abs(x) for x in (hist_sel - hist)])

L2距离则可以表示为:

dist = sqrt(sum([x*x for x in (hist_sel - hist)]))

Normalize的作用是强制直方图的总和等于某个常数值(1.0就很好用)。这很重要,因为这样大图片和小图片才能正确比较。如果你打算使用L1距离,那么在normalize中也应该使用L1度量;如果是L2,那么就用L2。

撰写回答