1.1.0版本

This commit is contained in:
xinzhu.yin
2026-04-16 16:51:05 +08:00
commit c157e774e5
333 changed files with 70759 additions and 0 deletions

559
algorithm/pq_algorithm.py Normal file
View File

@@ -0,0 +1,559 @@
import math
import numpy as np
from shapely.geometry import Polygon
# ==================
# 色域覆盖率计算公式
# ==================
def calculate_gamut_coverage(points1, points2):
"""
计算色域覆盖率(使用 shapely 库)
Args:
points1: 测量三角形 [[x1, y1], [x2, y2], [x3, y3]]
points2: 参考三角形 [[x1, y1], [x2, y2], [x3, y3]]
Returns:
area: 交集面积
coverage: 覆盖率(%)≤ 100%
"""
try:
# 创建多边形对象
poly1 = Polygon(points1)
poly2 = Polygon(points2)
# 计算交集
intersection = poly1.intersection(poly2)
# 计算面积
intersection_area = intersection.area
reference_area = poly2.area
# 计算覆盖率
coverage = (
(intersection_area / reference_area) * 100 if reference_area > 0 else 0.0
)
return intersection_area, coverage
except Exception as e:
print(f"❌ 计算色域覆盖率失败: {e}")
return 0.0, 0.0
def calculate_gamut_coverage_DCIP3(points):
"""
计算 CIE 1931 xy 色度空间下的 DCI-P3 覆盖率
Args:
points: [[x1, y1], [x2, y2], [x3, y3]] 测量的 xy 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
# DCI-P3 标准色域坐标CIE 1931 xy
dcip3_xy = [
[0.680, 0.320], # Red
[0.265, 0.690], # Green
[0.150, 0.060], # Blue
]
area, coverage = calculate_gamut_coverage(points, dcip3_xy)
return area, coverage
def calculate_gamut_coverage_BT709(results):
"""
计算相对于 BT.709 的色域覆盖率
Args:
results: 测量结果列表
[[x1, y1, lv1, X1, Y1, Z1], ...]
或 [[x1, y1], [x2, y2], [x3, y3]]
Returns:
tuple: (intersection_area, coverage_percentage)
"""
# BT.709 标准色域三角形CIE 1931 xy
bt709_triangle = [
[0.6400, 0.3300], # Red
[0.3000, 0.6000], # Green
[0.1500, 0.0600], # Blue
]
# 提取测量的 RGB 三个点的 xy 坐标
if len(results[0]) > 2:
measured_points = [[r[0], r[1]] for r in results[:3]]
else:
measured_points = results[:3]
area, coverage = calculate_gamut_coverage(measured_points, bt709_triangle)
return area, coverage
def calculate_gamut_coverage_BT601(results):
"""
计算相对于 BT.601 的色域覆盖率
Args:
results: 测量结果列表
Returns:
tuple: (intersection_area, coverage_percentage)
"""
# BT.601 标准色域三角形NTSC 版本)
bt601_triangle = [
[0.6300, 0.3400], # Red
[0.3100, 0.5950], # Green
[0.1550, 0.0700], # Blue
]
# 提取测量点
if len(results[0]) > 2:
measured_points = [[r[0], r[1]] for r in results[:3]]
else:
measured_points = results[:3]
area, coverage = calculate_gamut_coverage(measured_points, bt601_triangle)
return area, coverage
def calculate_gamut_coverage_BT2020(results):
"""
计算相对于 BT.2020 的色域覆盖率
Args:
results: 测量结果列表
Returns:
tuple: (intersection_area, coverage_percentage)
"""
# BT.2020 标准色域三角形
bt2020_triangle = [
[0.7080, 0.2920], # Red
[0.1700, 0.7970], # Green
[0.1310, 0.0460], # Blue
]
# 提取测量点
if len(results[0]) > 2:
measured_points = [[r[0], r[1]] for r in results[:3]]
else:
measured_points = results[:3]
area, coverage = calculate_gamut_coverage(measured_points, bt2020_triangle)
return area, coverage
def xy_to_uv_1976(x, y):
"""
CIE 1931 xy → CIE 1976 u'v' 转换(现代标准)
Args:
x: CIE 1931 x 坐标
y: CIE 1931 y 坐标
Returns:
(u', v'): CIE 1976 色度坐标
"""
denom = -2 * x + 12 * y + 3
if abs(denom) < 1e-10:
return (0, 0)
u_prime = (4 * x) / denom
v_prime = (9 * y) / denom
return (u_prime, v_prime)
def calculate_uv_gamut_coverage(uv_coords, reference="DCI-P3"):
"""
计算 CIE 1976 u'v' 色度空间下的色域覆盖率
Args:
uv_coords: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
reference: 参考标准 ("DCI-P3", "BT.709", "BT.2020", "BT.601")
Returns:
float: 覆盖率百分比
"""
try:
# ========== 根据参考标准选择 xy 坐标 ==========
if reference == "BT.2020":
ref_xy = [
(0.7080, 0.2920), # Red
(0.1700, 0.7970), # Green
(0.1310, 0.0460), # Blue
]
elif reference == "BT.709":
ref_xy = [
(0.6400, 0.3300), # Red
(0.3000, 0.6000), # Green
(0.1500, 0.0600), # Blue
]
elif reference == "DCI-P3":
ref_xy = [
(0.680, 0.320), # Red
(0.265, 0.690), # Green
(0.150, 0.060), # Blue
]
elif reference == "BT.601":
ref_xy = [
(0.6300, 0.3400), # Red
(0.3100, 0.5950), # Green
(0.1550, 0.0700), # Blue
]
else:
# 默认使用 DCI-P3
ref_xy = [
(0.680, 0.320),
(0.265, 0.690),
(0.150, 0.060),
]
# 转换参考标准为 u'v'
ref_uv = [list(xy_to_uv_1976(x, y)) for x, y in ref_xy]
# 确保测量坐标格式正确
measured_uv = [[float(u), float(v)] for u, v in uv_coords]
# 计算覆盖率
area, coverage = calculate_gamut_coverage(measured_uv, ref_uv)
return coverage
except Exception as e:
print(f"❌ 计算 u'v' 色域覆盖率失败: {str(e)}")
import traceback
traceback.print_exc()
return 0.0
def calculate_gamut_coverage_DCIP3_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 DCI-P3 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
dcip3_xy = [
(0.680, 0.320), # Red
(0.265, 0.690), # Green
(0.150, 0.060), # Blue
]
dcip3_uv = [list(xy_to_uv_1976(x, y)) for x, y in dcip3_xy]
area, coverage = calculate_gamut_coverage(uv_points, dcip3_uv)
return area, coverage
def calculate_gamut_coverage_BT709_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 BT.709 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
bt709_xy = [
(0.6400, 0.3300), # Red
(0.3000, 0.6000), # Green
(0.1500, 0.0600), # Blue
]
bt709_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt709_xy]
area, coverage = calculate_gamut_coverage(uv_points, bt709_uv)
return area, coverage
def calculate_gamut_coverage_BT2020_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 BT.2020 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
bt2020_xy = [
(0.7080, 0.2920), # Red
(0.1700, 0.7970), # Green
(0.1310, 0.0460), # Blue
]
bt2020_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt2020_xy]
area, coverage = calculate_gamut_coverage(uv_points, bt2020_uv)
return area, coverage
def calculate_gamut_coverage_BT601_uv(uv_points):
"""
计算 CIE 1976 u'v' 色度空间下的 BT.601 覆盖率
Args:
uv_points: [[u1, v1], [u2, v2], [u3, v3]] 测量的 u'v' 坐标
Returns:
area: 交集面积
coverage: 覆盖率(%
"""
bt601_xy = [
(0.6300, 0.3400), # Red
(0.3100, 0.5950), # Green
(0.1550, 0.0700), # Blue
]
bt601_uv = [list(xy_to_uv_1976(x, y)) for x, y in bt601_xy]
area, coverage = calculate_gamut_coverage(uv_points, bt601_uv)
return area, coverage
# ===============
# Gamma 计算公式
# ===============
def calculate_gamma(results, max_index_fix, pattern_params=None):
"""
计算 Gamma 值
Args:
results: [[x, y, lv, X, Y, Z], ...] 测量结果
max_index_fix: 最大灰阶索引
pattern_params: [[R, G, B], ...] 8bit pattern参数用于计算input_level
如果提供,则使用 pattern_value/255 作为底数
如果不提供,则使用传统的 灰阶百分比 作为底数
Returns:
results_with_gamma: [[x, y, lv, gamma], ...]
"""
if not results:
raise ValueError("测量结果为空")
# 提取亮度值
luminance_values = [result[2] for result in results]
if len(luminance_values) < 2:
raise ValueError(f"数据点数量不足: {len(luminance_values)}")
# 使用实际的最大亮度值
max_luminance = max(luminance_values)
min_luminance = min(luminance_values)
if max_luminance <= 0:
raise ValueError(f"最大亮度无效: {max_luminance}")
# 判断数据排列顺序
is_descending = luminance_values[0] > luminance_values[-1]
results_with_gamma = []
valid_gamma_sum = 0.0
valid_gamma_count = 0
for i, result in enumerate(results):
x, y, lv = result[0], result[1], result[2]
# 归一化亮度
L_bar = lv / max_luminance
# 计算输入灰阶值 input_level
if pattern_params is not None and i < len(pattern_params):
# 使用 8bit pattern 数据 / 255 作为底数22293 Gamma 数据对齐)
# 取 R 通道值(灰阶图案 R=G=B
pattern_value = pattern_params[i][0]
input_level = pattern_value / 255.0
else:
# 传统计算方式:根据数据顺序计算输入灰阶值
if is_descending:
# 从亮到暗: i=0 (100%) → i=max (0%)
input_level = 1.0 - (i / max_index_fix) if max_index_fix > 0 else 0
else:
# 从暗到亮: i=0 (0%) → i=max (100%)
input_level = (i / max_index_fix) if max_index_fix > 0 else 0
# 计算 Gamma
gamma = 0.0
# 跳过极端值(接近 0% 和 100%
if input_level <= 0.01 or input_level >= 0.99 or L_bar <= 0.001:
gamma = 0.0
else:
try:
log_L = math.log(L_bar)
log_I = math.log(input_level)
gamma = log_L / log_I
# 合理性检查
if 0.5 <= gamma <= 5.0:
valid_gamma_sum += gamma
valid_gamma_count += 1
else:
gamma = 0.0
except (ValueError, ZeroDivisionError):
gamma = 0.0
results_with_gamma.append(
[round(x, 7), round(y, 7), round(lv, 7), round(gamma, 4)]
)
return results_with_gamma
def calculate_L_bar(results):
"""
计算归一化亮度列表(用于绘制 Gamma 曲线)
Args:
results: 测量结果列表 [[x, y, lv, X, Y, Z], ...]
Returns:
L_bar: 归一化亮度列表 [0.0, 0.1, 0.2, ..., 1.0]
"""
if not results:
return []
# 提取亮度值
luminance_values = [result[2] for result in results]
# 使用实际的最大亮度值
max_luminance = max(luminance_values)
if max_luminance <= 0:
return [0.0] * len(luminance_values)
# 归一化
L_bar = [lv / max_luminance for lv in luminance_values]
return L_bar
# ============
# 色度坐标提取
# ============
def calculate_cct_from_results(results):
"""
从测量结果提取色度坐标xy 坐标)
⚠️ 注意:方法名保持不变(历史原因),实际功能是提取色度坐标
Args:
results: [[x, y, lv, ...], ...] 测量结果列表
Returns:
dict: {
'x': [x1, x2, x3, ...],
'y': [y1, y2, y3, ...],
'lv': [lv1, lv2, lv3, ...]
}
"""
x_values = []
y_values = []
lv_values = []
for result in results:
try:
if isinstance(result, (list, tuple)) and len(result) >= 2:
x = float(result[0])
y = float(result[1])
x_values.append(x)
y_values.append(y)
# 如果有亮度值也保存(可选)
if len(result) >= 3:
lv = float(result[2])
lv_values.append(lv)
else:
# 数据格式错误时的默认值D65 白点)
x_values.append(0.3127)
y_values.append(0.3290)
lv_values.append(0.0)
except Exception as e:
print(f"⚠️ 提取色度坐标失败: {str(e)}")
x_values.append(0.3127)
y_values.append(0.3290)
lv_values.append(0.0)
return {"x": x_values, "y": y_values, "lv": lv_values if lv_values else None}
# ========
# 测试代码
# ========
if __name__ == "__main__":
print("=" * 60)
print("PQ 算法库测试(使用 shapely 库)")
print("=" * 60)
# 测试 1: DCI-P3 覆盖率
print("\n1. 测试 DCI-P3 覆盖率计算")
test_points = [[0.680, 0.320], [0.265, 0.690], [0.150, 0.060]]
area, coverage = calculate_gamut_coverage_DCIP3(test_points)
print(f" DCI-P3 覆盖率: {coverage:.2f}% (期望: 100.00%)")
# 测试 2: BT.709 覆盖率
print("\n2. 测试 BT.709 覆盖率计算(测量色域超出标准)")
test_results_bt709 = [
[0.6637, 0.3236, 41.90],
[0.3087, 0.6250, 160.29],
[0.1435, 0.0447, 12.69],
]
area, coverage = calculate_gamut_coverage_BT709(test_results_bt709)
print(f" BT.709 覆盖率: {coverage:.2f}% (期望: ~98%)")
# 测试 3: BT.2020 覆盖率
print("\n3. 测试 BT.2020 覆盖率计算(测量色域小于标准)")
test_results_bt2020 = [
[0.6662, 0.3216, 39.75],
[0.3096, 0.6282, 155.24],
[0.1440, 0.0452, 11.96],
]
area, coverage = calculate_gamut_coverage_BT2020(test_results_bt2020)
print(f" BT.2020 覆盖率: {coverage:.2f}% (期望: ~60%)")
# 测试 4: 色度坐标提取
print("\n4. 测试色度坐标提取")
test_results = [
[0.3127, 0.3290, 190.5],
[0.3125, 0.3288, 150.2],
[0.3129, 0.3292, 100.8],
]
chromaticity_data = calculate_cct_from_results(test_results)
print(f" x 坐标: {chromaticity_data['x']}")
print(f" y 坐标: {chromaticity_data['y']}")
print(f" 亮度值: {chromaticity_data['lv']}")
# 测试 5: Gamma 计算
print("\n5. 测试 Gamma 计算")
test_gamma_results = [
[0.31, 0.33, 450.0, 0, 0, 0],
[0.31, 0.33, 350.0, 0, 0, 0],
[0.31, 0.33, 250.0, 0, 0, 0],
[0.31, 0.33, 150.0, 0, 0, 0],
[0.31, 0.33, 50.0, 0, 0, 0],
[0.31, 0.33, 0.1, 0, 0, 0],
]
gamma_results = calculate_gamma(test_gamma_results, 5)
for i, result in enumerate(gamma_results):
print(f"{i+1}: Gamma={result[3]:.3f}")
print("\n" + "=" * 60)
print("✅ 测试完成!")
print("=" * 60)