有没有办法优化这个字典搜索?

Is there a way to optimize this dictionary search?

我有一些代码可以打开图像并使用 ANSI 转义序列将其表示形式打印到终端。它采用缩小版图像中的每个像素,并打印出颜色与之匹配的字符。 然而,并非所有终端都支持 RGB 输出,因此我想实现其他颜色模式,例如 4 位。我通过包含 ANSI 代码和它们产生的 RGB 值的字典查找 table 来做到这一点:{(r, g, b) : code, ...。然后我根据欧氏距离打印最接近像素颜色的值。

from PIL import Image, ImageOps
from math import sqrt, inf

def match_colour_to_table(table, colour):
    best_colour = None
    best_diff = inf
    
    for table_colour in table.keys():
        # Calculate the distance between the two colours
        delta = (c - t for c, t in zip(colour, table_colour))
        diff = sqrt(sum((p ** 2 for p in delta)))
        
        if diff < best_diff:
            best_colour = table_colour
            best_diff = diff

    return table[selected_colour]

def term_pixel_4bit(colour):
    colour_table = {
        (0,   0,   0)   : 40,
        (255, 0,   0)   : 41,
        (0,   255, 0)   : 42,
        (255, 255, 0)   : 43,
        (0,   0,   255) : 44,
        (255, 0,   255) : 45,
        (0,   255, 255) : 46,
        (255, 255, 255) : 47,
    }

    code = match_colour_to_table(colour_table, colour)
        
    return f"3[;{code}m 3[0m"

def term_pixel_256(colour):
    match_colour_to_table(TABLE_256, colour)

    return f"3[48;5;{code}m ";

def print_image(image, size):
    width, height = size
    image = image.resize(size, resample=1).convert("RGB")
    
    # Print each row of characters
    for y in range(height):            
        row = [term_pixel_256(image.getpixel((x, y)))
               for x in range(width)]
        
        # Reset to avoid trailing colours
        row.append("3[0m")
        printf("".join(row))

这种方法对于 4 位色效果很好,但对于 256 色效果就差很多了。 我将 https://jonasjacek.github.io/colors/data.json 处的 json 数据转换为字典。

TABLE_256 = {
    (0, 0, 0) : 0, (128, 0, 0) : 1, (0, 128, 0) : 2, 
    ... 
    (228, 228, 228) : 254, (238, 238, 238) : 255
}

它确实产生了一个漂亮的结果,但可以理解它需要一段时间来计算。我确信有一种更快的方法可以做到这一点,但我不完全确定从哪里开始。任何帮助将不胜感激。

为了以防万一,这里是调用站点:

path  = os.path.join(os.path.dirname(__file__), "")
image = Image.open(path + sys.argv[1])
print_image(image, (100, 50))

您可以尝试将 functools.lru_cache 应用到 term_pixel_4bit

import functools

@functools.lru_cache
def term_pixel_256(colour):
    match_colour_to_table(TABLE_256, colour)

    return f"3[48;5;{code}m ";

您可能想尝试使用 KD 树在 3 维中进行最近邻计算。这是一个使用与您引用的相同颜色 table 的示例。对于我这里的小测试,它在我的机器上大约 20 秒内完成 50K 像素。当然,这可以通过 lru_cache 来增强,这将加快速度,使样本中的 RGB 值完全重复。

import json
from scipy.spatial import KDTree
from functools import lru_cache
from random import randint

with open('color_table.json', 'r') as f:
    data = json.load(f)

rgbs = [(t['rgb']['r'], t['rgb']['g'], t['rgb']['g']) for t in data]

tree = KDTree(rgbs)  # make the tree

@lru_cache
def match_to_table_tree(tree, colour):
    '''return the index of the colour in the table closest to the colour provided'''
    _, idx = tree.query(colour)
    return idx


test_size=5

test_colors = [(randint(0,255), randint(0,255), randint(0,255)) for t in range(test_size)]

for colour in test_colors:
    idx = match_to_table_tree(tree, colour)
    matched_color = data[idx]['rgb']
    print(f'tested: {colour} -> index {idx} colour: {matched_color}')

5色测试结果:

tested: (93, 213, 100) -> index 70 colour: {'r': 95, 'g': 175, 'b': 0}
tested: (37, 204, 3) -> index 22 colour: {'r': 0, 'g': 95, 'b': 0}
tested: (113, 211, 147) -> index 70 colour: {'r': 95, 'g': 175, 'b': 0}
tested: (139, 62, 122) -> index 94 colour: {'r': 135, 'g': 95, 'b': 0}
tested: (35, 106, 107) -> index 22 colour: {'r': 0, 'g': 95, 'b': 0}
[Finished in 0.3s]