同一张照片在手机、显示器和浏览器里可能看起来完全不同。为了把“太暗了”这种主观描述变得可比较,我保留视觉判断,同时增加三个简单指标:亮度的第 5、50、95 百分位数,以及黑白两端的裁切比例。
下面两张样片都拍摄于低照度环境,但亮部与暗部承担的作用并不相同。
第一张照片同时包含深色夜空、紫色建筑立面和高亮招牌。平均亮度无法说明招牌文字是否保留细节,因此更值得检查高光端是否裁切,同时观察紫色区域有没有因为压暗而丢失层次。
第二张照片的主体是浅色透明杯,背景则是低饱和度的金属表面。这里要同时关注杯身高光和背景分离度:过度提亮会让透明材质发白,过度压暗又会让手部与电梯背景黏在一起。
把 RGB 转成可比较的亮度
我先把像素归一化到 ,再按 sRGB 的线性亮度近似计算:
绿色通道权重较高,符合人眼对不同波段敏感度不同这一事实。它仍然不是完整的色彩管理流程,但比直接对三个通道取平均更适合做快速检查。
from pathlib import Path
import numpy as np
from PIL import Image
def exposure_summary(path: str) -> dict[str, float]:
image = Image.open(Path(path)).convert("RGB")
rgb = np.asarray(image, dtype=np.float32) / 255.0
luminance = (
0.2126 * rgb[..., 0]
+ 0.7152 * rgb[..., 1]
+ 0.0722 * rgb[..., 2]
)
p05, p50, p95 = np.quantile(luminance, [0.05, 0.50, 0.95])
return {
"p05": float(p05),
"p50": float(p50),
"p95": float(p95),
"black_clip": float(np.mean(luminance <= 1 / 255)),
"white_clip": float(np.mean(luminance >= 254 / 255)),
}
black_clip 和 white_clip 分别统计接近纯黑、纯白的像素比例。阈值只是检查工具,不是审美规则:夜景允许有纯黑区域,逆光也不必保住每一处太阳高光。
数据只回答具体问题
| 样片 | 主要观察区域 | 优先检查的指标 | 视觉判断 |
|---|---|---|---|
| 夜间门头 | 招牌文字、紫色立面 | P95 与白端裁切 | 保护灯牌细节,同时保留立面颜色层次 |
| 手持饮料杯 | 透明杯身、手部与灰色背景 | P05、P50 与局部高光 | 保留透明质感,让主体与背景清楚分离 |
这张表没有给照片打统一分数,因为两张照片的目标不同。统计量用于定位问题:P05 能告诉我暗部是否大面积挤在一起,P95 能提醒我亮部余量,裁切比例则帮助判断是否存在不可恢复的区域。
实际比较时还要固定三个条件:使用同一色彩空间导出、按同样的最长边缩放、去掉边框和水印。否则直方图的变化可能来自处理流程,而不是来自曝光本身。
最后仍然回到画面
数据能指出“哪里值得再看一眼”,却不能替我决定阴影是否应该保留。我的顺序是:先看照片表达什么,再用分位数检查技术风险,最后回到校准过的屏幕做局部调整。这样既不会让统计量支配审美,也不会把所有曝光问题都解释成个人风格。