通过python,获取集合中最相近的颜色

引言

最近永夜星河火了之后,带动了一些周边,有一些手办{叫什么拼豆}我也没看明白怎么玩的,像是十字绣那种感觉,DIY的话,需要买一些原料,但是原料还凑不齐,参考好几家也找不到,找到他们有的颜色,和模板上对应,后期可以参考得到的颜色,看看对不对,色差大不大。

知识点

颜色距离:欧几里得距离
编程语言:Python 以及 数学扩展

前期整理

test.png

图像虚化,这样能减少取色难度,但是也扩大了误差

from PIL import Image, ImageFilter, ImageDraw

s17_17 = [] # 本想自动获取

image_path = "test.png"
image = Image.open(image_path)
image = image.filter(ImageFilter.GaussianBlur(radius=2))
image.save("s1.png")
draw = ImageDraw.Draw(image)

image.show()

本想根据坐标直接获取颜色值,但是这个图片还需要进一步处理,OCR也没有完全提取出他的编号,这里就手工做吧。(感觉手工和代码时间成本接近,甚至手工更高)

后期比对代码

import tkinter as tk
import numpy as np
import struct


# 计算两个颜色之间的欧几里得距离
def color_distance(c1, c2):
    return np.linalg.norm(np.array(c1) - np.array(c2))


def get_declare_color(color):
    if type(color) == str:
        color = struct.unpack('BBB', bytes.fromhex("ff2f2f"))
    return tuple([255, 255, 255]) if color_distance(color, [255, 255, 255]) > color_distance(color, [0, 0, 0]) else tuple(
       [ 0, 0, 0])


# 找到最接近的颜色
def find_closest_color(color, color_list):
    distances = [color_distance(color, c) for c in color_list]
    return color_list[np.argmin(distances)], min(distances)


def find_id(color, color_list):
    for i in color_list:
        if color_list[i].replace(" ", '') == f"""{color[0]},{color[1]},{color[2]}""":
            return i


def render_msg(cur, text, ids, dist):
    status.config(text=f"""当前颜色值:{struct.unpack('BBB', bytes.fromhex(cur[1:]))} 
目标颜色值:{text} 
标号:{ids} 
距离:{dist}
点我重置""")

# 处理左侧颜色块点击事件
def on_left_click(event, right_colors):
    clicked_color = event.widget.cget("bg")  # 获取点击块的背景色
    clicked_color_rgb = [int(clicked_color[i:i + 2], 16) for i in (1, 3, 5)]  # 转换为RGB格式
    closest_color, dist = find_closest_color(clicked_color_rgb, right_colors)
    id1 = find_id(closest_color, colors2)

    left_frame.config(bg=("#%02x%02x%02x" % closest_color))
    right_frame.config(bg=("#%02x%02x%02x" % closest_color))
    render_msg(clicked_color, closest_color, id1, dist)


def on_right_click(event, left_colors):
    clicked_color = event.widget.cget("bg")  # 获取点击块的背景色
    clicked_color_rgb = [int(clicked_color[i:i + 2], 16) for i in (1, 3, 5)]  # 转换为RGB格式
    closest_color, dist = find_closest_color(clicked_color_rgb, left_colors)
    id2 = find_id(closest_color, colors1)
    left_frame.config(bg=("#%02x%02x%02x" % closest_color))
    right_frame.config(bg=("#%02x%02x%02x" % closest_color))
    render_msg(clicked_color, closest_color, id2, dist)


# 解析颜色字符串为 RGB 格式
def parse_color(color_str):
    return tuple(map(int, color_str.split(',')))


# 左侧颜色数据
colors1 = {
    "A1": "234, 132, 68",
    "A2": "224, 196, 65",
    "C3": "245, 163, 72",
    "D4": "244, 108, 8",
    "A5": "120, 150, 200",
    "B6": "45, 90, 180",
    "C7": "245, 220, 60",
    "D8": "90, 200, 255",
    "E9": "255, 165, 0",
    "F10": "200, 0, 100",
    "G11": "100, 250, 150",
    "H12": "30, 30, 30",
    "I13": "10, 10, 10",
    "J14": "0, 0, 0",
}
colors2 = {
    "_A1": "34, 132, 68",
    "_A2": "24, 196, 65",
    "_C3": "245, 63, 72",
    "_D4": "244, 18, 8",
    "_A5": "120, 150, 20",
    "_B6": "45, 90, 80",
    "_C7": "215, 20, 60",
    "_D8": "80, 220, 255",
    "_E9": "211, 175, 0",
    "_F10": "200, 50, 100",
    "_G11": "100, 210, 150",
}
def reset():
    left_frame.config(bg="#f0f0f0")
    right_frame.config(bg="#f0f0f0")
    status.config(text="已重置")


# 将颜色字典转换为 RGB 格式的列表
left_colors = [parse_color(value) for value in colors1.values()]
color_keys = list(colors1.keys())

# 右侧颜色数据:每个 RGB 值都加 1
right_colors = [parse_color(value) for value in colors2.values()]
color_keys2 = list(colors2.keys())

# 创建Tkinter窗口
root = tk.Tk()
root.title("颜色匹配")

# 创建状态标签,放置在最底部
status = tk.Label(root, text="状态:", anchor="w", padx=10, justify="left")
status.pack(side='bottom', fill='x', pady=5)  # 使状态标签横向填充
status.bind('<Button-1>', lambda event: reset())

# 创建左侧颜色展示区域
left_frame = tk.Frame(root)
left_frame.pack(side="left", padx=10, pady=10)

# 创建右侧颜色展示区域
right_frame = tk.Frame(root)
right_frame.pack(side="right", padx=10, pady=10)

# 设置每行最多显示6个颜色块的行数
colors_per_row = 6

# 创建左侧颜色块并绑定点击事件
left_color_blocks = []
for i, color in enumerate(left_colors):
    fs = get_declare_color(color)
    # 创建每个颜色块
    block = tk.Label(left_frame, bg=("#%02x%02x%02x" % color), width=12, height=2,
                     text=f"""{color_keys[i]}\n[{colors1[color_keys[i]]}]""",
                     foreground=("#%02x%02x%02x" % fs))
    block.grid(row=i // colors_per_row, column=i % colors_per_row, padx=5, pady=5)  # 使用grid进行布局
    block.bind("<Button-1>", lambda event, right_colors=right_colors: on_left_click(event, right_colors))
    left_color_blocks.append(block)

# 创建右侧颜色块
right_color_blocks = []
for i, color in enumerate(right_colors):
    fs = get_declare_color(color)
    block = tk.Label(right_frame, bg=("#%02x%02x%02x" % color), width=12, height=2,
                     text=f"""{color_keys2[i]}\n[{colors2[color_keys2[i]]}]""",
                     foreground=("#%02x%02x%02x" % fs))
    block.grid(row=i // colors_per_row, column=i % colors_per_row, padx=5, pady=5)  # 使用grid进行布局
    block.bind("<Button-1>",
               lambda event, right_colors=right_colors: on_right_click(event, left_colors))
    right_color_blocks.append(block)

# 进入主循环
root.mainloop()

比如D4对应的是右侧的_E9

image.png

结语

最终导入数据后,根据实际情况调整一下样式
image.png

评论 0

还没有评论,来说点什么吧。

提交后需要审核才会公开。