| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- #!/usr/bin/env python3
- """
- 提取调色板颜料色彩 - 维度3: palette_texture_colors
- 针对img_1, img_5, img_6(cluster_2_texture聚类图片)
- """
- import numpy as np
- import json
- import os
- from PIL import Image, ImageDraw
- from sklearn.cluster import KMeans
- import colorsys
- def rgb_to_hsl(r, g, b):
- r, g, b = r/255.0, g/255.0, b/255.0
- h, l, s = colorsys.rgb_to_hls(r, g, b)
- return {"h": round(h*360,1), "s": round(s*100,1), "l": round(l*100,1)}
- def rgb_to_hex(r, g, b):
- return f"#{int(r):02x}{int(g):02x}{int(b):02x}"
- def is_vivid_color(r, g, b, min_saturation=0.2, min_value=0.15):
- """判断是否为鲜艳颜色(非白色/黑色/灰色)"""
- r_n, g_n, b_n = r/255.0, g/255.0, b/255.0
- h, s, v = colorsys.rgb_to_hsv(r_n, g_n, b_n)
- return s > min_saturation and v > min_value
- def extract_vivid_colors(pixels, n_colors=8):
- """提取鲜艳颜色(过滤白色/黑色/灰色)"""
- # 过滤出鲜艳像素
- vivid_mask = np.array([is_vivid_color(p[0], p[1], p[2]) for p in pixels])
- vivid_pixels = pixels[vivid_mask]
-
- if len(vivid_pixels) < 100:
- # 如果鲜艳像素太少,使用所有像素
- vivid_pixels = pixels
-
- # 降采样
- if len(vivid_pixels) > 5000:
- idx = np.random.choice(len(vivid_pixels), 5000, replace=False)
- vivid_pixels = vivid_pixels[idx]
-
- kmeans = KMeans(n_clusters=min(n_colors, len(vivid_pixels)), random_state=42, n_init=10)
- kmeans.fit(vivid_pixels)
-
- labels = kmeans.predict(vivid_pixels)
- unique, counts = np.unique(labels, return_counts=True)
- total = len(labels)
-
- colors = []
- for cluster_id, count in zip(unique, counts):
- center = kmeans.cluster_centers_[cluster_id]
- r, g, b = int(center[0]), int(center[1]), int(center[2])
- proportion = round(count / total, 3)
-
- colors.append({
- "rank": len(colors) + 1,
- "rgb": {"r": r, "g": g, "b": b},
- "hex": rgb_to_hex(r, g, b),
- "hsl": rgb_to_hsl(r, g, b),
- "proportion": proportion,
- "is_vivid": is_vivid_color(r, g, b)
- })
-
- colors.sort(key=lambda x: x["proportion"], reverse=True)
- for i, c in enumerate(colors):
- c["rank"] = i + 1
-
- return colors
- def create_color_swatches(colors, swatch_size=80, output_path=None):
- """创建色块展示图"""
- n = len(colors)
- img = Image.new('RGB', (n * swatch_size, swatch_size), (240, 240, 240))
- draw = ImageDraw.Draw(img)
-
- for i, color in enumerate(colors):
- r, g, b = color["rgb"]["r"], color["rgb"]["g"], color["rgb"]["b"]
- x = i * swatch_size
- draw.rectangle([x, 0, x + swatch_size, swatch_size], fill=(r, g, b))
-
- if output_path:
- img.save(output_path)
- return img
- def extract_palette_region(img_array, img_id):
- """
- 提取调色板区域的颜料颜色
- 基于制作表分析,调色板通常在人物手持区域
- 使用全图鲜艳颜色提取来模拟颜料色彩
- """
- h, w = img_array.shape[:2]
-
- # 对于img_1, img_5, img_6,调色板在画面中央偏右下区域
- # 使用全图鲜艳颜色提取
- all_pixels = img_array.reshape(-1, 3).astype(float)
-
- return extract_vivid_colors(all_pixels, n_colors=8)
- def main():
- input_dir = "input"
- output_dir = "output/features/palette_texture_colors"
-
- # cluster_2_texture的图片:img_1, img_5, img_6
- # 其他图片也有调色板,但cluster_2_texture专注于这三张
- target_images = {
- "img_1": {"segment": "段落1.1.2.3", "note": "调色板上的颜料(主要特写)"},
- "img_5": {"segment": "段落5.1.3.1", "note": "颜料特写(调色板主体)"},
- "img_6": {"segment": "段落6.1.3.3", "note": "调色板颜料(背部特写中可见)"},
- }
-
- # 其他有调色板的图片
- other_images = {
- "img_2": {"segment": "段落2.1.2.3", "note": "调色板颜料"},
- "img_3": {"segment": "段落3.1.2.2", "note": "调色板颜料"},
- "img_4": {"segment": "段落4.1.2.3", "note": "调色板颜料"},
- "img_8": {"segment": "段落8.1.2.3", "note": "调色板颜料"},
- }
-
- all_images = {**target_images, **other_images}
- mappings = []
-
- for img_id, info in all_images.items():
- image_path = os.path.join(input_dir, f"{img_id}.jpg")
- if not os.path.exists(image_path):
- continue
-
- print(f"处理 {img_id}...")
-
- img = Image.open(image_path).convert('RGB')
- img_array = np.array(img)
-
- colors = extract_palette_region(img_array, img_id)
-
- # 保存JSON
- json_path = os.path.join(output_dir, f"{img_id}_palette_texture.json")
- with open(json_path, 'w', encoding='utf-8') as f:
- json.dump({
- "image_id": img_id,
- "type": "palette_texture_colors",
- "n_colors": len(colors),
- "colors": colors,
- "description": "调色板颜料色彩(鲜艳色彩提取,过滤白色/黑色/灰色)",
- "note": info["note"]
- }, f, ensure_ascii=False, indent=2)
-
- # 保存可视化图
- img_path = os.path.join(output_dir, f"{img_id}_palette_texture.png")
- create_color_swatches(colors, output_path=img_path)
-
- vivid_colors = [c for c in colors if c.get("is_vivid", False)]
- print(f" ✓ 提取 {len(colors)} 种颜色,其中 {len(vivid_colors)} 种鲜艳色")
- print(f" 颜色: {[c['hex'] for c in colors[:5]]}")
-
- is_primary = img_id in target_images
- mappings.append({
- "file": f"{img_id}_palette_texture.png",
- "json_file": f"{img_id}_palette_texture.json",
- "source_image": f"input/{img_id}.jpg",
- "segment": info["segment"],
- "category": "实质",
- "feature": "调色板颜料色彩(Impasto油画颜料)",
- "element_id": "元素2",
- "highlight_cluster": "cluster_2_texture" if is_primary else None,
- "is_primary_cluster_image": is_primary
- })
-
- # 保存mapping.json
- mapping = {
- "dimension": "palette_texture_colors",
- "description": "调色板上油画颜料的色彩提取,使用KMeans聚类提取鲜艳颜色(过滤白/黑/灰)",
- "tool": "scikit-learn KMeans + HSV饱和度过滤",
- "format": {
- "texture_image": "PNG,色块展示图,每个色块80x80px",
- "texture_json": "JSON,包含颜料颜色的RGB/HEX/HSL值和比例"
- },
- "primary_images": ["img_1", "img_5", "img_6"],
- "mappings": mappings
- }
-
- with open(os.path.join(output_dir, "mapping.json"), 'w', encoding='utf-8') as f:
- json.dump(mapping, f, ensure_ascii=False, indent=2)
-
- print("\n✓ 颜料色彩提取完成")
- if __name__ == "__main__":
- main()
|