用Python绘制CIE 1931色度图:从光谱数据到色彩可视化实战

当设计师抱怨显示器色域太窄时,他们其实在谈论什么?为什么专业摄影需要Adobe RGB而不是sRGB?要真正理解这些问题的本质,我们需要回到1931年那个改变色彩科学的坐标系——CIE 1931色度图。本文将带你用Python代码完整复现这个神奇的色彩地图,通过代码理解人眼如何感知颜色边界。

1. 准备工作:理解色彩科学基础

在开始编码之前,我们需要明确几个关键概念。CIE 1931色度图本质上是一个二维投影,它将人眼可见的所有颜色压缩到一个马蹄形区域内。这个马蹄形的边缘被称为 光谱轨迹 ,对应着单一波长的纯色光(从380nm的紫色到780nm的红色)。

注意:色度图的x,y坐标并不直接对应物理波长,而是经过标准观察者函数加权计算的结果

我们需要准备以下Python库:

import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from matplotlib.patches import Polygon

2. 获取标准观察者数据

CIE 1931标准观察者数据是构建色度图的基础,它定义了人眼对可见光谱的响应曲线。我们可以直接从CIE官网获取这些数据,或者使用科学计算库中预置的值:

# CIE 1931 2度标准观察者数据
wavelengths = np.arange(360, 831, 1)  # 360-830nm
x_bar = np.array([...])  # 填入实际x曲线数据
y_bar = np.array([...])  # 填入实际y曲线数据
z_bar = np.array([...])  # 填入实际z曲线数据

为了确保数据准确性,建议使用线性插值处理原始数据:

# 创建插值函数
x_interp = interp1d(wavelengths, x_bar, kind='linear')
y_interp = interp1d(wavelengths, y_bar, kind='linear')
z_interp = interp1d(wavelengths, z_bar, kind='linear')

# 生成更密集的采样点
dense_wl = np.linspace(360, 830, 1000)
x_dense = x_interp(dense_wl)
y_dense = y_interp(dense_wl)
z_dense = z_interp(dense_wl)

3. 计算色度坐标与绘制光谱轨迹

色度坐标(x,y)的计算公式看似简单,却蕴含着色彩科学的精髓:

$$ x = \frac{X}{X+Y+Z}, \quad y = \frac{Y}{X+Y+Z} $$

在代码中实现这个转换:

def calculate_xy(X, Y, Z):
    """计算色度坐标"""
    sum_xyz = X + Y + Z
    x = X / sum_xyz
    y = Y / sum_xyz
    return x, y

# 计算光谱轨迹的色度坐标
spectral_x = []
spectral_y = []
for wl in dense_wl:
    X = x_interp(wl)
    Y = y_interp(wl)
    Z = z_interp(wl)
    x, y = calculate_xy(X, Y, Z)
    spectral_x.append(x)
    spectral_y.append(y)

现在可以绘制出著名的马蹄形光谱轨迹:

plt.figure(figsize=(10, 8))
plt.plot(spectral_x, spectral_y, color='black', linewidth=2)
plt.title('CIE 1931 Chromaticity Diagram')
plt.xlabel('x')
plt.ylabel('y')
plt.grid(True)

4. 添加普朗克轨迹与常见色域

普朗克轨迹(黑体辐射轨迹)展示了不同温度下黑体辐射在色度图上的位置。我们可以通过普朗克公式计算这些点:

def planckian_locus(temperature):
    """计算指定温度下的黑体辐射色度坐标"""
    # 实现普朗克公式计算
    # 返回x,y坐标
    return x, y

# 绘制几个典型温度点
temperatures = [2000, 3000, 4000, 5000, 6500, 10000]
for temp in temperatures:
    x, y = planckian_locus(temp)
    plt.scatter(x, y, color='red')
    plt.text(x, y, f'{temp}K', fontsize=10)

常见色域的三角形边界也是理解色彩空间的关键。以sRGB为例:

# sRGB色域顶点坐标
srgb_red = (0.64, 0.33)
srgb_green = (0.30, 0.60)
srgb_blue = (0.15, 0.06)

# 创建多边形并填充
srgb_triangle = Polygon([srgb_red, srgb_green, srgb_blue], 
                       closed=True, 
                       fill=True, 
                       alpha=0.2,
                       color='blue')
plt.gca().add_patch(srgb_triangle)

同样方法可以添加Adobe RGB、DCI-P3等其他色域,通过不同颜色和透明度区分它们。

5. 色彩可视化技巧与优化

为了让色度图更直观,我们可以添加以下增强效果:

  1. 色彩填充 :在光谱轨迹内填充近似颜色
from matplotlib.colors import LinearSegmentedColormap

# 创建自定义色彩映射
points = np.linspace(0, 1, 100)
colors = [...]  # 根据色度图定义颜色
cmap = LinearSegmentedColormap.from_list('cie_colormap', colors)

# 填充色彩
plt.fill(spectral_x, spectral_y, color='none', edgecolor='none')
plt.gca().autoscale_view()
  1. 关键波长标注 :标记几个重要波长点
key_wavelengths = [450, 520, 580, 620, 700]  # nm
for wl in key_wavelengths:
    idx = np.argmin(np.abs(dense_wl - wl))
    plt.scatter(spectral_x[idx], spectral_y[idx], color='black')
    plt.text(spectral_x[idx], spectral_y[idx], f'{wl}nm', fontsize=10)
  1. 白点标注 :标记D65等标准白点
d65 = (0.3127, 0.3290)
plt.scatter(d65[0], d65[1], color='white', edgecolor='black', s=100)
plt.text(d65[0], d65[1], 'D65', ha='center', va='center')

6. 实际应用与扩展思考

完成基础色度图后,我们可以进一步探索几个实用方向:

色域覆盖率计算 :比较不同色域的面积

def calculate_area(points):
    """计算多边形面积"""
    x = [p[0] for p in points]
    y = [p[1] for p in points]
    return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))

srgb_area = calculate_area([srgb_red, srgb_green, srgb_blue])
adobe_rgb_area = ...  # 类似计算Adobe RGB面积
print(f'sRGB覆盖了{srgb_area/visible_area*100:.1f}%可见色域')

色彩转换验证 :检查RGB值在色度图中的位置

def rgb_to_xy(r, g, b):
    """将RGB转换为色度坐标"""
    # 实现转换矩阵计算
    return x, y

# 测试纯红色
x, y = rgb_to_xy(1, 0, 0)
plt.scatter(x, y, color='red', s=100)

色差计算 :评估两个颜色的感知差异

def delta_e(x1, y1, x2, y2):
    """计算两个色度坐标之间的色差"""
    return np.sqrt((x2-x1)**2 + (y2-y1)**2)

在显示器校准项目中,这种可视化特别有用。当我们需要解释为什么某台显示器无法准确再现特定红色时,直接展示该颜色在色度图上的位置与显示器色域边界的关系,比任何口头解释都更有说服力。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐