extract_palette_texture.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. #!/usr/bin/env python3
  2. """
  3. 提取调色板颜料色彩 - 维度3: palette_texture_colors
  4. 针对img_1, img_5, img_6(cluster_2_texture聚类图片)
  5. """
  6. import numpy as np
  7. import json
  8. import os
  9. from PIL import Image, ImageDraw
  10. from sklearn.cluster import KMeans
  11. import colorsys
  12. def rgb_to_hsl(r, g, b):
  13. r, g, b = r/255.0, g/255.0, b/255.0
  14. h, l, s = colorsys.rgb_to_hls(r, g, b)
  15. return {"h": round(h*360,1), "s": round(s*100,1), "l": round(l*100,1)}
  16. def rgb_to_hex(r, g, b):
  17. return f"#{int(r):02x}{int(g):02x}{int(b):02x}"
  18. def is_vivid_color(r, g, b, min_saturation=0.2, min_value=0.15):
  19. """判断是否为鲜艳颜色(非白色/黑色/灰色)"""
  20. r_n, g_n, b_n = r/255.0, g/255.0, b/255.0
  21. h, s, v = colorsys.rgb_to_hsv(r_n, g_n, b_n)
  22. return s > min_saturation and v > min_value
  23. def extract_vivid_colors(pixels, n_colors=8):
  24. """提取鲜艳颜色(过滤白色/黑色/灰色)"""
  25. # 过滤出鲜艳像素
  26. vivid_mask = np.array([is_vivid_color(p[0], p[1], p[2]) for p in pixels])
  27. vivid_pixels = pixels[vivid_mask]
  28. if len(vivid_pixels) < 100:
  29. # 如果鲜艳像素太少,使用所有像素
  30. vivid_pixels = pixels
  31. # 降采样
  32. if len(vivid_pixels) > 5000:
  33. idx = np.random.choice(len(vivid_pixels), 5000, replace=False)
  34. vivid_pixels = vivid_pixels[idx]
  35. kmeans = KMeans(n_clusters=min(n_colors, len(vivid_pixels)), random_state=42, n_init=10)
  36. kmeans.fit(vivid_pixels)
  37. labels = kmeans.predict(vivid_pixels)
  38. unique, counts = np.unique(labels, return_counts=True)
  39. total = len(labels)
  40. colors = []
  41. for cluster_id, count in zip(unique, counts):
  42. center = kmeans.cluster_centers_[cluster_id]
  43. r, g, b = int(center[0]), int(center[1]), int(center[2])
  44. proportion = round(count / total, 3)
  45. colors.append({
  46. "rank": len(colors) + 1,
  47. "rgb": {"r": r, "g": g, "b": b},
  48. "hex": rgb_to_hex(r, g, b),
  49. "hsl": rgb_to_hsl(r, g, b),
  50. "proportion": proportion,
  51. "is_vivid": is_vivid_color(r, g, b)
  52. })
  53. colors.sort(key=lambda x: x["proportion"], reverse=True)
  54. for i, c in enumerate(colors):
  55. c["rank"] = i + 1
  56. return colors
  57. def create_color_swatches(colors, swatch_size=80, output_path=None):
  58. """创建色块展示图"""
  59. n = len(colors)
  60. img = Image.new('RGB', (n * swatch_size, swatch_size), (240, 240, 240))
  61. draw = ImageDraw.Draw(img)
  62. for i, color in enumerate(colors):
  63. r, g, b = color["rgb"]["r"], color["rgb"]["g"], color["rgb"]["b"]
  64. x = i * swatch_size
  65. draw.rectangle([x, 0, x + swatch_size, swatch_size], fill=(r, g, b))
  66. if output_path:
  67. img.save(output_path)
  68. return img
  69. def extract_palette_region(img_array, img_id):
  70. """
  71. 提取调色板区域的颜料颜色
  72. 基于制作表分析,调色板通常在人物手持区域
  73. 使用全图鲜艳颜色提取来模拟颜料色彩
  74. """
  75. h, w = img_array.shape[:2]
  76. # 对于img_1, img_5, img_6,调色板在画面中央偏右下区域
  77. # 使用全图鲜艳颜色提取
  78. all_pixels = img_array.reshape(-1, 3).astype(float)
  79. return extract_vivid_colors(all_pixels, n_colors=8)
  80. def main():
  81. input_dir = "input"
  82. output_dir = "output/features/palette_texture_colors"
  83. # cluster_2_texture的图片:img_1, img_5, img_6
  84. # 其他图片也有调色板,但cluster_2_texture专注于这三张
  85. target_images = {
  86. "img_1": {"segment": "段落1.1.2.3", "note": "调色板上的颜料(主要特写)"},
  87. "img_5": {"segment": "段落5.1.3.1", "note": "颜料特写(调色板主体)"},
  88. "img_6": {"segment": "段落6.1.3.3", "note": "调色板颜料(背部特写中可见)"},
  89. }
  90. # 其他有调色板的图片
  91. other_images = {
  92. "img_2": {"segment": "段落2.1.2.3", "note": "调色板颜料"},
  93. "img_3": {"segment": "段落3.1.2.2", "note": "调色板颜料"},
  94. "img_4": {"segment": "段落4.1.2.3", "note": "调色板颜料"},
  95. "img_8": {"segment": "段落8.1.2.3", "note": "调色板颜料"},
  96. }
  97. all_images = {**target_images, **other_images}
  98. mappings = []
  99. for img_id, info in all_images.items():
  100. image_path = os.path.join(input_dir, f"{img_id}.jpg")
  101. if not os.path.exists(image_path):
  102. continue
  103. print(f"处理 {img_id}...")
  104. img = Image.open(image_path).convert('RGB')
  105. img_array = np.array(img)
  106. colors = extract_palette_region(img_array, img_id)
  107. # 保存JSON
  108. json_path = os.path.join(output_dir, f"{img_id}_palette_texture.json")
  109. with open(json_path, 'w', encoding='utf-8') as f:
  110. json.dump({
  111. "image_id": img_id,
  112. "type": "palette_texture_colors",
  113. "n_colors": len(colors),
  114. "colors": colors,
  115. "description": "调色板颜料色彩(鲜艳色彩提取,过滤白色/黑色/灰色)",
  116. "note": info["note"]
  117. }, f, ensure_ascii=False, indent=2)
  118. # 保存可视化图
  119. img_path = os.path.join(output_dir, f"{img_id}_palette_texture.png")
  120. create_color_swatches(colors, output_path=img_path)
  121. vivid_colors = [c for c in colors if c.get("is_vivid", False)]
  122. print(f" ✓ 提取 {len(colors)} 种颜色,其中 {len(vivid_colors)} 种鲜艳色")
  123. print(f" 颜色: {[c['hex'] for c in colors[:5]]}")
  124. is_primary = img_id in target_images
  125. mappings.append({
  126. "file": f"{img_id}_palette_texture.png",
  127. "json_file": f"{img_id}_palette_texture.json",
  128. "source_image": f"input/{img_id}.jpg",
  129. "segment": info["segment"],
  130. "category": "实质",
  131. "feature": "调色板颜料色彩(Impasto油画颜料)",
  132. "element_id": "元素2",
  133. "highlight_cluster": "cluster_2_texture" if is_primary else None,
  134. "is_primary_cluster_image": is_primary
  135. })
  136. # 保存mapping.json
  137. mapping = {
  138. "dimension": "palette_texture_colors",
  139. "description": "调色板上油画颜料的色彩提取,使用KMeans聚类提取鲜艳颜色(过滤白/黑/灰)",
  140. "tool": "scikit-learn KMeans + HSV饱和度过滤",
  141. "format": {
  142. "texture_image": "PNG,色块展示图,每个色块80x80px",
  143. "texture_json": "JSON,包含颜料颜色的RGB/HEX/HSL值和比例"
  144. },
  145. "primary_images": ["img_1", "img_5", "img_6"],
  146. "mappings": mappings
  147. }
  148. with open(os.path.join(output_dir, "mapping.json"), 'w', encoding='utf-8') as f:
  149. json.dump(mapping, f, ensure_ascii=False, indent=2)
  150. print("\n✓ 颜料色彩提取完成")
  151. if __name__ == "__main__":
  152. main()