Files
pqAutomationApp/app/tests/color_accuracy.py

249 lines
6.9 KiB
Python
Raw Normal View History

2026-04-20 09:41:24 +08:00
"""色准ΔE2000 / 标准色)相关纯算法。
Step 1 重构 pqAutomationApp.PQAutomationApp 中原样搬迁以下方法
去掉 self 参数改为模块级纯函数
- calculate_delta_e_2000
- calculate_color_accuracy
- get_accuracy_color_standards
"""
import math
import numpy as np
def calculate_delta_e_2000(
measured_x, measured_y, measured_lv, standard_x, standard_y
):
"""
计算 ΔE 2000 色差修正版
Args:
measured_x, measured_y: 测量的 xy 坐标
measured_lv: 测量的亮度cd/
standard_x, standard_y: 标准的 xy 坐标
Returns:
float: ΔE 2000 色差值
"""
# ========== 1. xy → XYZ使用实际亮度==========
def xy_to_XYZ(x, y, Y):
if y == 0:
return 0, 0, 0
X = x * Y / y
Z = (1 - x - y) * Y / y
return X, Y, Z
# 修复:使用实际测量的亮度
X1, Y1, Z1 = xy_to_XYZ(measured_x, measured_y, measured_lv)
# 修复:标准值使用相同的参考亮度(只比较色度差异)
X2, Y2, Z2 = xy_to_XYZ(standard_x, standard_y, measured_lv)
# ========== 2. XYZ → LabD65 白点)==========
def XYZ_to_Lab(X, Y, Z):
# D65 白点
Xn, Yn, Zn = 95.047, 100.000, 108.883
# 归一化
xr = X / Xn
yr = Y / Yn
zr = Z / Zn
# f(t) 函数
def f(t):
delta = 6.0 / 29.0
if t > delta ** 3:
return t ** (1.0 / 3.0)
else:
return t / (3 * delta ** 2) + 4.0 / 29.0
fx = f(xr)
fy = f(yr)
fz = f(zr)
L = 116 * fy - 16
a = 500 * (fx - fy)
b = 200 * (fy - fz)
return L, a, b
L1, a1, b1 = XYZ_to_Lab(X1, Y1, Z1)
L2, a2, b2 = XYZ_to_Lab(X2, Y2, Z2)
# ========== 3. ΔE 2000 公式 ==========
L_bar = (L1 + L2) / 2.0
C1 = math.sqrt(a1 ** 2 + b1 ** 2)
C2 = math.sqrt(a2 ** 2 + b2 ** 2)
C_bar = (C1 + C2) / 2.0
G = 0.5 * (1 - math.sqrt(C_bar ** 7 / (C_bar ** 7 + 25 ** 7)))
a1_prime = a1 * (1 + G)
a2_prime = a2 * (1 + G)
C1_prime = math.sqrt(a1_prime ** 2 + b1 ** 2)
C2_prime = math.sqrt(a2_prime ** 2 + b2 ** 2)
C_bar_prime = (C1_prime + C2_prime) / 2.0
def calc_hue(a_prime, b):
if a_prime == 0 and b == 0:
return 0
h = math.atan2(b, a_prime) * 180 / math.pi
if h < 0:
h += 360
return h
h1_prime = calc_hue(a1_prime, b1)
h2_prime = calc_hue(a2_prime, b2)
if C1_prime == 0 or C2_prime == 0:
delta_h_prime = 0
else:
delta_h = h2_prime - h1_prime
if abs(delta_h) <= 180:
delta_h_prime = delta_h
elif delta_h > 180:
delta_h_prime = delta_h - 360
else:
delta_h_prime = delta_h + 360
if C1_prime == 0 or C2_prime == 0:
H_bar_prime = h1_prime + h2_prime
else:
if abs(h1_prime - h2_prime) <= 180:
H_bar_prime = (h1_prime + h2_prime) / 2.0
elif h1_prime + h2_prime < 360:
H_bar_prime = (h1_prime + h2_prime + 360) / 2.0
else:
H_bar_prime = (h1_prime + h2_prime - 360) / 2.0
delta_L_prime = L2 - L1
delta_C_prime = C2_prime - C1_prime
delta_H_prime = (
2
* math.sqrt(C1_prime * C2_prime)
* math.sin(math.radians(delta_h_prime / 2.0))
)
S_L = 1 + (0.015 * (L_bar - 50) ** 2) / math.sqrt(20 + (L_bar - 50) ** 2)
S_C = 1 + 0.045 * C_bar_prime
T = (
1
- 0.17 * math.cos(math.radians(H_bar_prime - 30))
+ 0.24 * math.cos(math.radians(2 * H_bar_prime))
+ 0.32 * math.cos(math.radians(3 * H_bar_prime + 6))
- 0.20 * math.cos(math.radians(4 * H_bar_prime - 63))
)
S_H = 1 + 0.015 * C_bar_prime * T
delta_theta = 30 * math.exp(-(((H_bar_prime - 275) / 25) ** 2))
R_C = 2 * math.sqrt(C_bar_prime ** 7 / (C_bar_prime ** 7 + 25 ** 7))
R_T = -R_C * math.sin(math.radians(2 * delta_theta))
kL = 1.0
kC = 1.0
kH = 1.0
delta_E = math.sqrt(
(delta_L_prime / (kL * S_L)) ** 2
+ (delta_C_prime / (kC * S_C)) ** 2
+ (delta_H_prime / (kH * S_H)) ** 2
+ R_T * (delta_C_prime / (kC * S_C)) * (delta_H_prime / (kH * S_H))
)
return delta_E
def calculate_color_accuracy(measured, standard):
"""计算色差(简化版,欧氏距离 × 1000"""
delta_E = {}
for color in measured.keys():
dx = measured[color][0] - standard[color][0]
dy = measured[color][1] - standard[color][1]
delta_E[color] = np.sqrt(dx * dx + dy * dy) * 1000
return delta_E
# 29 色 SDR 标准色板(保持与原实现一致)
_SDR_COLOR_PATTERNS = [
("White", 255, 255, 255),
("Gray 80", 230, 230, 230),
("Gray 65", 209, 209, 209),
("Gray 50", 186, 186, 186),
("Gray 35", 158, 158, 158),
("Dark Skin", 115, 82, 66),
("Light Skin", 194, 150, 130),
("Blue Sky", 94, 122, 156),
("Foliage", 89, 107, 66),
("Blue Flower", 130, 128, 176),
("Bluish Green", 99, 189, 168),
("Orange", 217, 120, 41),
("Purplish Blue", 74, 92, 163),
("Moderate Red", 194, 84, 97),
("Purple", 92, 61, 107),
("Yellow Green", 158, 186, 64),
("Orange Yellow", 230, 161, 46),
("Blue (Legacy)", 51, 61, 150),
("Green (Legacy)", 71, 148, 71),
("Red (Legacy)", 176, 48, 59),
("Yellow (Legacy)", 237, 199, 33),
("Magenta (Legacy)", 186, 84, 145),
("Cyan (Legacy)", 0, 133, 163),
("100% Red", 255, 0, 0),
("100% Green", 0, 255, 0),
("100% Blue", 0, 0, 255),
("100% Cyan", 0, 255, 255),
("100% Magenta", 255, 0, 255),
("100% Yellow", 255, 255, 0),
]
def _rgb_to_xy_srgb(r, g, b):
"""sRGB (8bit) → CIE 1931 xy"""
r, g, b = r / 255.0, g / 255.0, b / 255.0
def gamma_decode(c):
if c <= 0.04045:
return c / 12.92
else:
return ((c + 0.055) / 1.055) ** 2.4
r_linear = gamma_decode(r)
g_linear = gamma_decode(g)
b_linear = gamma_decode(b)
# sRGB → XYZD65 白点IEC 61966-2-1
X = r_linear * 0.4124564 + g_linear * 0.3575761 + b_linear * 0.1804375
Y = r_linear * 0.2126729 + g_linear * 0.7151522 + b_linear * 0.0721750
Z = r_linear * 0.0193339 + g_linear * 0.1191920 + b_linear * 0.9503041
total = X + Y + Z
if total == 0:
return 0.3127, 0.3290 # D65 白点
return X / total, Y / total
def get_accuracy_color_standards(test_type):
"""
获取色准测试的标准 xy 色度坐标动态计算
Args:
test_type: 测试类型 ("sdr_movie" "hdr_movie")
Returns:
dict: {color_name: (x, y), ...}
"""
# 注意:原实现对 sdr/hdr 使用同一张色板,这里保持原行为。
del test_type # 参数保留以兼容调用方签名
standards = {}
for name, r, g, b in _SDR_COLOR_PATTERNS:
standards[name] = _rgb_to_xy_srgb(r, g, b)
return standards