【图像处理基石】如何从动漫参考图中提取色彩风格?

引言
动漫的色彩风格是其视觉表达的核心——《你的名字》的清新透亮、《鬼灭之刃》的高饱和对比、《进击的巨人》的暗沉压抑,不同作品的配色体系直接决定了观众的情感共鸣。对于创作者(同人画师、游戏UI设计师、动画从业者)而言,手动提取动漫参考图的色彩风格(如主色调、配色比例、明暗规律)不仅耗时,还难以精准复现其和谐性。
本文将拆解动漫色彩风格的核心要素,基于K-Means聚类、色彩空间分析等算法,用Python实现一套完整的动漫色彩风格提取流程,包括主色调提取、配色比例计算、色彩分布统计、色彩和谐性分析四大核心功能,代码可直接运行,帮你快速“扒取”心仪动漫的色彩密码!
一、动漫色彩风格的核心要素
在动手实现前,先明确我们需要提取的“色彩风格”到底包含什么——这是算法设计的前提:
- 主色调:画面中最具代表性的3-8种核心颜色(动漫配色通常简洁集中,不会过于复杂);
- 配色比例:各主色调在画面中的像素占比(比如《你的名字》中蓝色占比30%、粉色占比25%);
- 色彩分布规律:亮度(明暗)、饱和度(鲜艳度)的统计分布(比如清新风动漫多集中在高亮度、中高饱和度);
- 色彩和谐性:主色调之间的色相关系(如互补色、邻近色),这是动漫配色“好看”的关键。
二、技术选型与环境准备
1. 依赖库说明
Pillow:图片加载、预处理、色彩空间转换;numpy:像素矩阵运算、数据处理;scipy.cluster.vq:K-Means聚类(提取主色调核心算法);matplotlib:色彩分布可视化、结果展示;scipy.stats:统计分析(亮度/饱和度分布)。
2. 环境安装
pip install pillow numpy scipy matplotlib
三、算法原理与分步实现
核心流程
动漫参考图 → 预处理(去噪、缩放)→ 色彩空间转换(RGB→HSV)→ 像素数据提取 → K-Means聚类提取主色调 → 配色比例计算 → 亮度/饱和度分布分析 → 色彩和谐性判断 → 结果可视化与导出
完整代码(带详细注释)
from PIL import Image, ImageFilter
import numpy as np
import matplotlib.pyplot as plt
from scipy.cluster.vq import kmeans, vq
from scipy.stats import gaussian_kde
import json# -------------------------- 1. 图片预处理函数 --------------------------
def preprocess_image(image_path, target_size=(800, 600)):"""图片预处理:加载、缩放、去噪(减少干扰,提高聚类效率):param image_path: 动漫参考图路径:param target_size: 目标尺寸(宽, 高),缩放后加速运算:return: RGB格式图片、HSV格式图片"""# 加载图片并转换为RGB(排除Alpha通道干扰)img_rgb = Image.open(image_path).convert("RGB")# 缩放(保持比例,避免拉伸)img_rgb.thumbnail(target_size, Image.Resampling.LANCZOS)# 轻微高斯模糊去噪(动漫图本身边缘清晰,模糊半径不宜过大)img_rgb = img_rgb.filter(ImageFilter.GaussianBlur(radius=0.5))# 转换为HSV色彩空间(更符合人眼对色彩的感知,便于分离色相/饱和度/明度)img_hsv = img_rgb.convert("HSV")return img_rgb, img_hsv# -------------------------- 2. K-Means聚类提取主色调 --------------------------
def extract_main_colors(img_rgb, num_colors=5, threshold=0.02):"""基于K-Means聚类提取主色调(核心算法):param img_rgb: RGB格式图片:param num_colors: 期望提取的主色调数量(动漫图推荐3-8):param threshold: 最小占比阈值(过滤占比低于此值的次要颜色):return: 主色调列表(RGB格式)、各颜色占比列表"""# 1. 提取像素数据:将图片转为像素矩阵(shape: [像素数, 3])pixels = np.array(img_rgb).reshape(-1, 3).astype(np.float32)# 归一化到[0,1](K-Means对数值范围敏感,归一化提升稳定性)pixels_normalized = pixels / 255.0# 2. K-Means聚类:自动分组相似像素# kmeans函数返回:聚类中心(主色调)、方差(聚类质量,越小越好)centroids, _ = kmeans(pixels_normalized, num_colors, iter=20)# 3. 为每个像素分配到最近的聚类中心(确定每个像素属于哪个主色调)labels, _ = vq(pixels_normalized, centroids)# 4. 计算各主色调的占比total_pixels = len(labels)color_counts = np.bincount(labels) # 统计每个聚类的像素数color_ratios = color_counts / total_pixels # 转换为占比# 5. 过滤占比过低的次要颜色,按占比降序排序valid_mask = color_ratios >= thresholdvalid_centroids = centroids[valid_mask]valid_ratios = color_ratios[valid_mask]# 按占比降序排序(方便后续展示核心颜色)sorted_indices = np.argsort(valid_ratios)[::-1]sorted_colors = (valid_centroids[sorted_indices] * 255).astype(np.uint8) # 转回[0,255]sorted_ratios = valid_ratios[sorted_indices]# 转换为列表格式(便于后续处理)main_colors = [tuple(color) for color in sorted_colors]color_ratios = [float(ratio) for ratio in sorted_ratios]return main_colors, color_ratios# -------------------------- 3. 色彩分布分析(亮度/饱和度) --------------------------
def analyze_color_distribution(img_hsv):"""分析动漫图的亮度(V通道)和饱和度(S通道)分布:param img_hsv: HSV格式图片:return: 亮度数据、饱和度数据"""# 分离HSV通道(Pillow的HSV通道范围:H[0,255], S[0,255], V[0,255])h_channel, s_channel, v_channel = img_hsv.split()# 转为numpy数组便于统计s_data = np.array(s_channel).flatten() / 255.0 # 饱和度(0=灰度,1=全饱和)v_data = np.array(v_channel).flatten() / 255.0 # 亮度(0=纯黑,1=纯白)return s_data, v_data# -------------------------- 4. 色彩和谐性分析 --------------------------
def analyze_color_harmony(main_colors):"""分析主色调的和谐关系(基于色相角)和谐类型:邻近色(色相差0-30°)、类似色(30-60°)、互补色(150-180°)、对比色(90-150°):param main_colors: 主色调列表(RGB格式):return: 和谐性分析结果(字典)"""# 1. 将RGB主色调转为HSV,提取色相角(H通道)hsv_colors = [Image.new("RGB", (1,1), color).convert("HSV").getpixel((0,0)) for color in main_colors]hues = [h / 255.0 * 360.0 for h, _, _ in hsv_colors] # 转为[0,360°]色相角# 2. 计算所有主色调对的色相差(取最小夹角,0-180°)color_pairs = []num_colors = len(hues)for i in range(num_colors):for j in range(i+1, num_colors):hue_diff = abs(hues[i] - hues[j])hue_diff = min(hue_diff, 360 - hue_diff) # 最小夹角(避免190°等价于170°)# 判断和谐类型if hue_diff <= 30:harmony_type = "邻近色"elif hue_diff <= 60:harmony_type = "类似色"elif hue_diff <= 150:harmony_type = "对比色"else:harmony_type = "互补色"color_pairs.append({"color1": main_colors[i],"color2": main_colors[j],"hue_diff": round(hue_diff, 1),"harmony_type": harmony_type})return {"hues": [round(h, 1) for h in hues],"color_pairs": color_pairs}# -------------------------- 5. 结果可视化与导出 --------------------------
def visualize_and_export(main_colors, color_ratios, s_data, v_data, harmony_result, output_dir="color_style_result"):"""可视化色彩风格结果(主色调、占比、分布直方图)并导出数据:param main_colors: 主色调列表:param color_ratios: 颜色占比列表:param s_data: 饱和度数据:param v_data: 亮度数据:param harmony_result: 和谐性分析结果:param output_dir: 输出目录"""import osos.makedirs(output_dir, exist_ok=True)# 1. 可视化:主色调+占比饼图fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))# 主色调色卡color_blocks = np.zeros((50, len(main_colors)*100, 3), dtype=np.uint8)for i, color in enumerate(main_colors):color_blocks[:, i*100:(i+1)*100, :] = colorax1.imshow(color_blocks)ax1.axis("off")ax1.set_title("主色调色卡", fontsize=14)# 配色占比饼图colors_hex = [f"#{r:02x}{g:02x}{b:02x}" for r, g, b in main_colors]ax2.pie(color_ratios, labels=[f"颜色{i+1}\n{ratio:.1%}" for i, ratio in enumerate(color_ratios)],colors=colors_hex, autopct="%1.1f%%", startangle=90)ax2.set_title("配色占比", fontsize=14)# 饱和度分布直方图(带密度曲线)ax3.hist(s_data, bins=50, alpha=0.6, color="#ff7f0e", density=True)# 高斯核密度估计(更平滑的分布曲线)s_kde = gaussian_kde(s_data)s_range = np.linspace(0, 1, 100)ax3.plot(s_range, s_kde(s_range), color="#1f77b4", linewidth=2)ax3.set_xlabel("饱和度(0=灰度,1=全饱和)")ax3.set_ylabel("密度")ax3.set_title("饱和度分布", fontsize=14)ax3.grid(alpha=0.3)# 亮度分布直方图(带密度曲线)ax4.hist(v_data, bins=50, alpha=0.6, color="#2ca02c", density=True)v_kde = gaussian_kde(v_data)v_range = np.linspace(0, 1, 100)ax4.plot(v_range, v_kde(v_range), color="#d62728", linewidth=2)ax4.set_xlabel("亮度(0=纯黑,1=纯白)")ax4.set_ylabel("密度")ax4.set_title("亮度分布", fontsize=14)ax4.grid(alpha=0.3)# 保存可视化图plt.tight_layout()plt.savefig(os.path.join(output_dir, "color_style_analysis.png"), dpi=300, bbox_inches="tight")plt.show()# 2. 导出色彩风格数据(JSON格式,便于后续应用)style_data = {"main_colors": [list(color) for color in main_colors], # RGB值"color_ratios": color_ratios, # 占比"color_hex": colors_hex, # 十六进制颜色码(便于设计工具使用)"harmony_analysis": harmony_result,"saturation_stats": {"mean": float(np.mean(s_data)), # 平均饱和度"std": float(np.std(s_data)) # 饱和度标准差(越小越集中)},"value_stats": {"mean": float(np.mean(v_data)), # 平均亮度"std": float(np.std(v_data)) # 亮度标准差}}with open(os.path.join(output_dir, "color_style_data.json"), "w", encoding="utf-8") as f:json.dump(style_data, f, ensure_ascii=False, indent=4)print(f"色彩风格分析结果已保存至:{output_dir}")print(f"提取主色调数:{len(main_colors)}")print(f"主色调(RGB/十六进制):")for i, (color, hex_code, ratio) in enumerate(zip(main_colors, colors_hex, color_ratios)):print(f" 颜色{i+1}:{color} / {hex_code}(占比:{ratio:.1%})")# -------------------------- 6. 整合流程:一键提取动漫色彩风格 --------------------------
def extract_anime_color_style(image_path, num_colors=5, threshold=0.02):"""一键提取动漫参考图的色彩风格:param image_path: 动漫参考图路径(支持JPG/PNG):param num_colors: 期望提取的主色调数量:param threshold: 最小占比阈值(过滤次要颜色)"""# 步骤1:图片预处理img_rgb, img_hsv = preprocess_image(image_path)print(f"图片预处理完成,尺寸:{img_rgb.size}")# 步骤2:提取主色调与配色比例main_colors, color_ratios = extract_main_colors(img_rgb, num_colors, threshold)print("主色调与配色比例提取完成")# 步骤3:分析亮度/饱和度分布s_data, v_data = analyze_color_distribution(img_hsv)print("色彩分布分析完成")# 步骤4:色彩和谐性分析harmony_result = analyze_color_harmony(main_colors)print("色彩和谐性分析完成")# 步骤5:可视化与导出结果visualize_and_export(main_colors, color_ratios, s_data, v_data, harmony_result)# -------------------------- 7. 调用示例 --------------------------
if __name__ == "__main__":# 替换为你的动漫参考图路径(建议选择画面主体清晰、无过多杂物的图)ANIME_IMAGE_PATH = "anime_reference.jpg"# 执行色彩风格提取(可调整参数)extract_anime_color_style(image_path=ANIME_IMAGE_PATH,num_colors=6, # 期望提取的主色调数(3-8为宜)threshold=0.03 # 过滤占比低于3%的次要颜色)
四、算法核心原理解析
1. 主色调提取:为什么用K-Means?
动漫图的色彩特征是离散化、高对比度(比如角色头发、服装、背景的颜色区分明显),K-Means聚类能自动将像素按RGB相似度分组,聚类中心就是“最具代表性的主色调”。相比传统的“直方图统计+阈值筛选”,K-Means能:
- 自动适应不同动漫的色彩复杂度(无需手动调整阈值);
- 合并相似颜色(比如不同明暗的蓝色会被归为一类);
- 按占比排序,优先保留核心配色。
2. 色彩空间选择:为什么用HSV?
RGB空间适合计算,但难以分离“色彩种类”(色相)、“鲜艳度”(饱和度)、“明暗”(亮度)。HSV空间直接拆分这三个维度,让我们能:
- 单独分析饱和度分布(比如清新风动漫的饱和度集中在0.6-0.9);
- 单独分析亮度分布(比如暗黑系动漫的亮度集中在0.2-0.5);
- 基于色相角判断色彩和谐性(互补色、邻近色等)。
3. 色彩和谐性判断依据
基于色彩理论的色相角差规则(行业通用标准):
| 色相角差 | 和谐类型 | 特点(动漫中常见场景) |
|---|---|---|
| 0-30° | 邻近色 | 柔和统一(如《玉子爱情故事》的暖色调) |
| 30-60° | 类似色 | 协调有层次(如《夏目友人帐》的自然配色) |
| 90-150° | 对比色 | 鲜明有张力(如《鬼灭之刃》的红+绿) |
| 150-180° | 互补色 | 强烈冲击(如《EVA》的红+蓝) |
五、效果验证与参数调优
1. 关键参数调优技巧
| 参数 | 作用 | 推荐范围 | 调优建议 |
|---|---|---|---|
num_colors | 期望主色调数 | 3-8 | 简洁动漫图(如单角色)用3-5种;复杂场景(如全景)用6-8种 |
threshold | 最小占比阈值 | 0.02-0.05 | 过滤杂色:背景复杂时设0.03-0.05;需要保留细节时设0.02 |
target_size | 预处理缩放尺寸 | (600,400)-(1000,800) | 尺寸越小运算越快,但过小会丢失色彩信息;建议≥600px |
2. 避坑指南
- 选择参考图时,优先选无水印、无黑边、主体清晰的图(避免杂物干扰聚类);
- 若提取的主色调偏暗/偏亮,可检查图片是否过曝/欠曝,或调整
threshold过滤背景色; - K-Means聚类结果可能有微小差异(随机初始化),若不满意可多次运行,或增大
kmeans的iter参数(如30)。
六、扩展应用场景
提取的色彩风格数据可直接用于以下场景:
1. 辅助创作
- 将
color_style_data.json中的十六进制颜色码导入PS、SAI等设计工具,直接使用参考图的配色; - 基于亮度/饱和度分布,调整自己的画作(比如参考《你的名字》的高饱和,提高作品饱和度至0.7-0.9)。
2. 自动配色迁移
基于提取的主色调,用色彩替换算法将其他图片的配色替换为参考图风格(扩展代码示例):
def transfer_color_style(source_img_path, target_style_data, output_path):"""将目标图的配色迁移为参考图的色彩风格:param source_img_path: 目标图路径:param target_style_data: 参考图色彩风格数据(JSON加载):param output_path: 输出路径"""source_img = Image.open(source_img_path).convert("RGB")source_pixels = np.array(source_img).reshape(-1, 3).astype(np.float32) / 255.0# 参考图主色调(归一化)target_colors = np.array(target_style_data["main_colors"]) / 255.0# 为目标图每个像素分配到参考图最近的主色调labels, _ = vq(source_pixels, target_colors)# 替换为参考图主色调transferred_pixels = target_colors[labels] * 255.0transferred_img = Image.fromarray(transferred_pixels.reshape(source_img.size[1], source_img.size[0], 3).astype(np.uint8))transferred_img.save(output_path)print(f"配色迁移完成,保存至:{output_path}")# 调用示例
with open("color_style_result/color_style_data.json", "r") as f:style_data = json.load(f)
transfer_color_style("my_drawing.jpg", style_data, "transferred_drawing.jpg")
3. 批量分析与风格分类
对多个动漫作品的色彩风格进行批量提取,统计主色调、亮度、饱和度特征,实现动漫风格分类(如“清新风”“暗黑风”“高对比风”)。
七、总结与优化方向
本文实现了一套轻量、高效的动漫色彩风格提取算法,核心优势是:
- 基于K-Means聚类,自动适配不同动漫的色彩特征;
- 兼顾“主色调+配色比例+分布规律+和谐性”,提取信息全面;
- 代码模块化,易于扩展和二次开发。
后续可优化的方向:
- 加入背景色自动过滤(基于语义分割,如用Mask R-CNN剔除纯黑/纯白背景);
- 结合深度学习(如CNN)提取更高级的色彩风格特征(如局部配色规律);
- 支持批量处理,并生成可直接导入设计工具的色卡文件(如ASE格式);
- 增加配色冲突检测(避免提取的配色出现不和谐组合)。
如果在使用过程中遇到问题,或有更好的优化思路,欢迎在评论区交流~
