extract_colors.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #!/usr/bin/env python3
  2. """
  3. 提取色彩调色板 - 使用KMeans聚类
  4. 维度2: 全局色彩调色板 (color_palette)
  5. 维度7: 背景环境色彩 (background_color)
  6. """
  7. import numpy as np
  8. import json
  9. import os
  10. from PIL import Image, ImageDraw
  11. from sklearn.cluster import KMeans
  12. import colorsys
  13. def rgb_to_hsl(r, g, b):
  14. """RGB转HSL"""
  15. r, g, b = r/255.0, g/255.0, b/255.0
  16. h, l, s = colorsys.rgb_to_hls(r, g, b)
  17. return {
  18. "h": round(h * 360, 1),
  19. "s": round(s * 100, 1),
  20. "l": round(l * 100, 1)
  21. }
  22. def rgb_to_hex(r, g, b):
  23. return f"#{int(r):02x}{int(g):02x}{int(b):02x}"
  24. def extract_palette(pixels, n_colors=6, img_id="", label="global"):
  25. """从像素数组提取主色调"""
  26. # 降采样加速
  27. if len(pixels) > 10000:
  28. idx = np.random.choice(len(pixels), 10000, replace=False)
  29. pixels_sample = pixels[idx]
  30. else:
  31. pixels_sample = pixels
  32. kmeans = KMeans(n_clusters=n_colors, random_state=42, n_init=10)
  33. kmeans.fit(pixels_sample)
  34. # 计算每个聚类的比例
  35. labels = kmeans.predict(pixels_sample)
  36. unique, counts = np.unique(labels, return_counts=True)
  37. total = len(labels)
  38. colors = []
  39. for cluster_id, count in zip(unique, counts):
  40. center = kmeans.cluster_centers_[cluster_id]
  41. r, g, b = int(center[0]), int(center[1]), int(center[2])
  42. proportion = round(count / total, 3)
  43. colors.append({
  44. "rank": len(colors) + 1,
  45. "rgb": {"r": r, "g": g, "b": b},
  46. "hex": rgb_to_hex(r, g, b),
  47. "hsl": rgb_to_hsl(r, g, b),
  48. "proportion": proportion
  49. })
  50. # 按比例排序
  51. colors.sort(key=lambda x: x["proportion"], reverse=True)
  52. for i, c in enumerate(colors):
  53. c["rank"] = i + 1
  54. return colors
  55. def create_palette_image(colors, width=600, height=100, output_path=None):
  56. """创建色彩调色板可视化图"""
  57. img = Image.new('RGB', (width, height), (255, 255, 255))
  58. draw = ImageDraw.Draw(img)
  59. x = 0
  60. for color in colors:
  61. r, g, b = color["rgb"]["r"], color["rgb"]["g"], color["rgb"]["b"]
  62. block_width = int(color["proportion"] * width)
  63. if block_width > 0:
  64. draw.rectangle([x, 0, x + block_width, height], fill=(r, g, b))
  65. x += block_width
  66. if output_path:
  67. img.save(output_path)
  68. return img
  69. def get_background_pixels(img_array, threshold_top=0.3, threshold_bottom=0.7):
  70. """提取背景区域像素(上部和左侧区域,排除人物主体区域)"""
  71. h, w = img_array.shape[:2]
  72. # 取图片上部(天空/树木区域)和左侧(背景区域)
  73. # 基于制作表分析:背景主要在左侧和上方
  74. top_region = img_array[:int(h * 0.4), :, :] # 上40%
  75. left_region = img_array[:, :int(w * 0.35), :] # 左35%
  76. # 合并背景区域
  77. bg_pixels = np.vstack([
  78. top_region.reshape(-1, 3),
  79. left_region.reshape(-1, 3)
  80. ])
  81. return bg_pixels
  82. def main():
  83. input_dir = "input"
  84. # 全局色彩调色板
  85. palette_dir = "output/features/color_palette"
  86. # 背景色彩
  87. bg_dir = "output/features/background_color"
  88. palette_mappings = []
  89. bg_mappings = []
  90. # 段落对应关系
  91. segment_map = {
  92. "img_1": {"global_seg": "段落1", "bg_seg": "段落1.3"},
  93. "img_2": {"global_seg": "段落2", "bg_seg": "段落2.3"},
  94. "img_3": {"global_seg": "段落3", "bg_seg": "段落3.3"},
  95. "img_4": {"global_seg": "段落4", "bg_seg": "段落4.3"},
  96. "img_5": {"global_seg": "段落5", "bg_seg": "段落5.3"},
  97. "img_6": {"global_seg": "段落6", "bg_seg": "段落6.3"},
  98. "img_7": {"global_seg": "段落7", "bg_seg": "段落7.3"},
  99. "img_8": {"global_seg": "段落8", "bg_seg": "段落8.3"},
  100. "img_9": {"global_seg": "段落9", "bg_seg": "段落9.2"},
  101. }
  102. for i in range(1, 10):
  103. img_id = f"img_{i}"
  104. image_path = os.path.join(input_dir, f"{img_id}.jpg")
  105. if not os.path.exists(image_path):
  106. continue
  107. print(f"处理 {img_id}...")
  108. img = Image.open(image_path).convert('RGB')
  109. img_array = np.array(img)
  110. all_pixels = img_array.reshape(-1, 3).astype(float)
  111. # === 维度2:全局色彩调色板 ===
  112. global_colors = extract_palette(all_pixels, n_colors=6, img_id=img_id, label="global")
  113. # 保存JSON
  114. global_json_path = os.path.join(palette_dir, f"{img_id}_color_palette.json")
  115. with open(global_json_path, 'w', encoding='utf-8') as f:
  116. json.dump({
  117. "image_id": img_id,
  118. "type": "global_color_palette",
  119. "n_colors": 6,
  120. "colors": global_colors,
  121. "description": "全局主色调,按比例排序"
  122. }, f, ensure_ascii=False, indent=2)
  123. # 保存可视化图
  124. global_img_path = os.path.join(palette_dir, f"{img_id}_color_palette.png")
  125. create_palette_image(global_colors, output_path=global_img_path)
  126. print(f" ✓ 全局调色板: {[c['hex'] for c in global_colors[:3]]}")
  127. seg_info = segment_map.get(img_id, {})
  128. palette_mappings.append({
  129. "file": f"{img_id}_color_palette.png",
  130. "json_file": f"{img_id}_color_palette.json",
  131. "source_image": f"input/{img_id}.jpg",
  132. "segment": seg_info.get("global_seg", f"段落{i}"),
  133. "category": "形式",
  134. "feature": "整体色彩调色板(白绿配色)",
  135. "highlight_cluster": "cluster_3",
  136. "top_colors": [c["hex"] for c in global_colors[:3]]
  137. })
  138. # === 维度7:背景环境色彩 ===
  139. bg_pixels = get_background_pixels(img_array)
  140. bg_colors = extract_palette(bg_pixels.astype(float), n_colors=4, img_id=img_id, label="background")
  141. # 保存JSON
  142. bg_json_path = os.path.join(bg_dir, f"{img_id}_background_color.json")
  143. with open(bg_json_path, 'w', encoding='utf-8') as f:
  144. json.dump({
  145. "image_id": img_id,
  146. "type": "background_color_palette",
  147. "n_colors": 4,
  148. "colors": bg_colors,
  149. "extraction_region": "上40%区域 + 左35%区域",
  150. "description": "背景区域主色调(自然绿色调)"
  151. }, f, ensure_ascii=False, indent=2)
  152. # 保存可视化图
  153. bg_img_path = os.path.join(bg_dir, f"{img_id}_background_color.png")
  154. create_palette_image(bg_colors, width=400, output_path=bg_img_path)
  155. print(f" ✓ 背景色彩: {[c['hex'] for c in bg_colors[:3]]}")
  156. bg_mappings.append({
  157. "file": f"{img_id}_background_color.png",
  158. "json_file": f"{img_id}_background_color.json",
  159. "source_image": f"input/{img_id}.jpg",
  160. "segment": seg_info.get("bg_seg", ""),
  161. "category": "实质",
  162. "feature": "自然背景色彩(草地/树木绿色调)",
  163. "element_id": "元素3",
  164. "highlight_cluster": "cluster_3",
  165. "top_colors": [c["hex"] for c in bg_colors[:3]]
  166. })
  167. # 保存mapping.json for color_palette
  168. palette_mapping = {
  169. "dimension": "color_palette",
  170. "description": "全局色彩调色板,使用KMeans聚类提取6个主色调",
  171. "tool": "scikit-learn KMeans",
  172. "format": {
  173. "palette_image": "PNG,色块按比例排列,宽600px高100px",
  174. "palette_json": "JSON,包含6个主色调的RGB/HEX/HSL值和比例"
  175. },
  176. "mappings": palette_mappings
  177. }
  178. with open(os.path.join(palette_dir, "mapping.json"), 'w', encoding='utf-8') as f:
  179. json.dump(palette_mapping, f, ensure_ascii=False, indent=2)
  180. # 保存mapping.json for background_color
  181. bg_mapping = {
  182. "dimension": "background_color",
  183. "description": "背景区域色彩调色板,提取图片上部和左侧区域的主色调",
  184. "tool": "scikit-learn KMeans",
  185. "format": {
  186. "bg_image": "PNG,色块按比例排列,宽400px高100px",
  187. "bg_json": "JSON,包含4个主色调的RGB/HEX/HSL值和比例"
  188. },
  189. "mappings": bg_mappings
  190. }
  191. with open(os.path.join(bg_dir, "mapping.json"), 'w', encoding='utf-8') as f:
  192. json.dump(bg_mapping, f, ensure_ascii=False, indent=2)
  193. print("\n✓ 色彩调色板提取完成")
  194. if __name__ == "__main__":
  195. main()