Colorful框架根据此文作了些许扩展,更新版本从此处下载。
入门Colorful的建议
Colorful本质上就是一个函数集合,真正的宝藏都在Mathematica中。私以为不用专门学习Mathematica软件。一般来说,只需掌握以下几个技巧就能愉快玩耍了:
- 函数的定义与使用。函数选项“Options”的使用
- 列表各种操作、列表运算符与构造(Table函数很重要)。
- 各种语法糖,如Map /@、Apply @@、前缀@、后缀//等,帮助阅读和简写代码。可以收藏这个网址随时翻阅。
- 查帮助文档和Mathematica Stack Exchange。前人踩坑,后人乘凉。
- 先明确数学过程再写代码,事半功倍
这里再列举Colorful计算中非常常见又很奇怪的四个符号#、&、/@、%
解释如下:
- #与&配合,是就地构造一个新函数,操作紧接着的表达式。如:
Power[#, 2.2]& {.5, .5, .5}
这是对后面的像素做gamma2.2的反编码。
2. /@是Map函数的简写,用作批量处理:若函数的输入是元素,但想对一整个集合的元素作处理,一般用法是“函数/@集合”,这样函数集合中的每个元素都被单独处理,输出一个新的集合。如:
XYZ2LAB/@ {{1, 1, 1},{.5, .5, .5}}
XYZ2LAB[#]& /@ {{1, 1, 1},{.5, .5, .5}}
上两句等价,都是将像素集{{1, 1, 1},{.5, .5, .5}}从XYZ转到LAB(别忘了默认白点是D65哦)。
3. %符号指“上一个结果”。如:
Power[{.5, .5, .5}, 2.2];
% * 2
这两句话是,将{.5, .5, .5}做Gamma2.2的反编码,再这个像素加一档曝光。分号表示不输出该句结果。
常用的技巧不多,再辅以案例,入门很快。
光谱的色度计算:
色彩并不存在,色彩是人眼特性+大脑皮层产生的假象,现实中只存在不同波长的光,物理上可以确定一点的光谱能量分布(Spectral Power Distribution)。Colorful中,它是
人眼或相机都有三种光感受器,每种光感受器都有一个特定的光谱敏感度函数,光感受器获得1个强度值的过程是:光谱敏感度函数与光谱能量分布内积。计算光谱的颜色,就是计算人类的色彩响应。通常将LMS视作视锥细胞真实的神经冲动值,而为工程计算方便,又将LMS作线性变换到XYZ坐标下。所以在色彩计算时,XYZ坐标就是绝对的色觉坐标,XYZ就是实实在在的颜色。
所以,用XYZ坐标表示的人类色觉的“光谱敏感度函数”,叫做XYZ坐标的色匹配函数(Color Matching Function)。在Colorful中,它是
某光谱能量分布的色彩计算如下:
其中“.”指矩阵乘。如果已知某反射体的光谱反射率
其中“*”代表矩阵对应项相乘,e表示曝光系数。在实际计算时,e通常设置为直接对光源曝光时Y=1的值,即e应满足以下方程:
统一曝光基准的处理有很多好处。原因之一是,当曝光基准不同时,L、A、B三个值都会不同,不好做比对。这个事实留给读者自己验证。
Colorful光谱计算举例
2月7日新增了一个示例文件Spectrum.nb,例子是爱色丽ColorChecker24色卡的光谱计算,以及与官方推荐Lab值的比对。下面给出过程详解。
关于ColorChecker 24 色卡
这是24色标准色卡,最初由Macbeth公司开发,该公司最终辗转并进爱色丽。进行色卡计算时要将每个色块的编码熟记于心:从深棕色延长边是A1、B1、C1...是字母递增。
色卡是一个高科技产品。为什么这么说呢?色卡不仅色彩准确,其光谱分布也是精确设计过的。很多人建议不要用手触摸色卡,防止色彩出现偏移,这个建议值得听取。通过染料调整,色卡的光谱反射率精确到了令人发指的地步,以其中的灰卡为例:
爱色丽提供很多种产品,ColorChecker Classic、ColorChecker Classic Mini、ColorChecker Proof和摄影师常见的ColorChecker Passport等。还有一个视频专用版,但它与Passport色块设置不同。爱色丽版的ColorChecker24在2014年11月底进行过一次染料配方调整,其色度值有所不同,最大DE00达到了1.95。所以查表时一定看好发布日期和对应版本。
另外,产品版本有坑,数据标准也有坑,这里不细究,推荐阅读:BabelColor。
网络上流行的色度值表格是这个(因为标在维基百科上),可惜它是老版本的表格。官方给出的2014年后版本的LAB值表格在此,其LAB坐标的参考白点是D50(但它的排序有点问题)。今天的计算就用这个官方版本,我已经做好格式化放在/Data/ColorChecker/ColorChecker24_After _Nov2014Formatted.csv中了。
在一些情况下,仅有色度值是不够的,往往需要用光谱计算,所以很多第三方网站都给出了24色色卡的光谱数据。尽管官方声称他们从未发布过色卡的光谱数据,但自带的APP中却明文记录着...今天计算的主角就是它,同样做好格式化放在/Data/ColorChecker/ColorChecker24_spectral.csv中了。
但是官方并未说明光谱分布的技术信息,尤其没有说它是“某光源照射的结果”还是“色卡的光谱反射率”,而且测量方式、参考光源、与LAB数据的关系都没有提到,所以它几乎是不可用的。今天的任务就是有理有据地猜猜它到底是啥,是D50光源照射后的结果还是色卡的光谱反射率。
数据导入
使用Import函数将光谱源数据载入。Workspace才是工作目录,所以需要用到gCSDir变量获取Colorful的根目录。习惯把"rawData"作为载入数据的前缀:
rawDataCC24Spec =
Import[gCSDir <> "/Data/ColorChecker/ColorChecker24_spectral.csv"];
Dimensions[%]
第二句是查看导入列表的维度,结果是{24,36},这是24个光谱分布的集合。官方介绍其为380-730nm步进为10nm的数据,而Colorful需要的是380-720nm步进为5nm的数据,所以下一步我们要用插值做转换。基本是套路:
- 用Table构造数据本身的波长范围与步进,命名SpecRange
- 用Table函数使导入数据与对应波长组合成列表。得到24个有对应波长的光谱的集合
- 用Interpolation函数对每一个光谱分布做插值,选项可以调整插值阶数,这里用1,也就是线性插值。得到24个插值函数的集合
- 用Table函数构造新的光谱集合,从插值函数中选取380-720:5的数据,命名CC24Spec
(*用Table函数构造数据对应的光谱波长范围和波长步进,得到一个长度为36的列表(380-730:10)*)
SpecRange = Table[10 x + 380, {x, 0, 35}];
(*用Table函数将光谱源文件与对应波长结合,得到24个光谱的集合*)
Table[{SpecRange[[x]], #[[x]]}, {x, 1, 36}] & /@ rawDataCC24Spec;
(*用插值Interpolation函数对集合中每一个光谱做一阶插值,得到24个插值函数的集合*)
Interpolation[#, InterpolationOrder -> 1] & /@ %;
(*用Table函数从一阶插值函数中选出我们想要的值:380-720nm,步进5nm*)
CC24Spec = Table[#[s], {s, 380, 720, 5}] & /@ %;
代码中的符号较混杂,然而这就是Mathematica的风格...
光谱的色度计算
我们的目的是验证该数据是“色卡+光源”还是“色卡的反射率”。基本思路是:
- 如果是“D50照射后的光谱能量分布”,则直接对数据算色度值,应更接近官方LAB数据;
- 如果是“色卡的反射率”,则数据与光源作用,再算色度值,应更接近LAB数据。
先直接算色度值:
(*1.直接算色卡光谱反射率的颜色*)
CC24XYZ = CMFXYZ.(#) & /@ CC24Spec;
得到24个色块的XYZ值CC24XYZ,是一个像素集。
再算D50光源下的色度值。LSD50是内置现成的光谱光源。还记得吗?光源+反射介质的色度计算要乘曝光系数
e = 1/(CMFXYZ[[2]].LSD50)
会发现结果为1。没错,光源在定义时就做好曝光标准化了。所以接下来不用考虑
用#、&就地构造一个函数,它可以先与LSD50对应项相乘,再被
(*2.计算在D65光源下,色卡呈现的颜色,最后用计算好的曝光值相乘。*)
CC24XYZD50 = CMFXYZ.(#*LSD50) & /@ CC24Spec;
两组数据的对比、白平衡的统一
至此,我们已经对光谱做好了两种不同的处理。先把它们画在一张图上对比,隆重推出Draw1976Compare函数:
Draw1976Compare[CC24XYZ, CC24XYZD50, "SizeRatio" -> 0.5]
比对色度时使用CIE 1976 uv坐标系,是因为在uv中,可以简单地用两点距离估算色偏(相比之下xy坐标系很不平均)。其中SizeRatio表示第二组数据点相对于第一组数据点大小的比例,默认0.8。大点对应CC24XYZ,小点对应加光源的CC24XYZD50。
我们发现两组数据差别很大,原因是它们的白平衡不统一。CCD24XYZ的白平衡是E,而CCD24XYZD50的白平衡是D50。
这时需要用到在标准白点之间变换的ChromaticAdaptation函数,函数中提供了XYZScaling、Bradford、VonKries三种方法,公认最有效的方法是Bradford(专栏以后会展开解释),这是PS使用的方案,也是该函数的默认方法。转换如下:
(*可以看出它们的白平衡不同。用Bradford方法做一个白平衡变换*)
CC24XYZWBD50 =
ChromaticAdaptation[#, "E", "D50", "Method" -> "Bradford"] & /@
CC24XYZ;
再次比对:
Draw1976Compare[CC24XYZWBD50, CC24XYZD50, "SizeRatio" -> 0.5]
可以发现它们已经非常接近了,主要的区别在蓝色部分。
如此,我们获得了两组数据:
- CC24XYZWBD50,直接从光谱计算色度,并用白平衡调到D50的数据;若它与官方LAB值更接近,则光谱数据是“D50照射后的光谱能量分布”
- CC24XYZD50,从光谱与光源作用再计算色度;若它与官方LAB值更接近,则光谱数据是“光谱反射率”
LAB数据的导入和处理
直接导入:
CC24A2014LAB =
Import[gCSDir <>
"/Data/ColorChecker/ColorChecker24_After_Nov2014Formatted.csv"];
转换成XYZ坐标。注意该LAB数据的参考白点是D50:
(*LAB转换成XYZ,白点D50*)
CC24A2014XYZ = LAB2XYZ[#, "refWP" -> "D50"] & /@ CC24A2014LAB;
如此,我们获得了按顺序排列的24个色块的XYZ值,CCD24A2014XYZ是一个像素集。
在进入色偏比对之前,总结一下处理流程和比对思路:
色偏比对
先用CIE 1976 uv坐标系做可视化比对:
(*比对光谱计算结果和官方给出结果*)
Draw1976Compare[CC24A2014XYZ, CC24XYZWBD50, "SizeRatio" -> 0.5]
Draw1976Compare[CC24A2014XYZ, CC24XYZD50, "SizeRatio" -> 0.5]
输出两张图:
可以发现,第二个结果色偏非常小,在uv坐标中几乎不可见;第一个结果色偏也不大,但蓝色部分有微小色偏。
这时就要用到色偏的定量计算,使用DE00LAB函数。
①.第一组数据
首先变换为LAB,白点D50。
(*首先把CCD24XYZWBD50转成LAB,白点D50。*)
XYZ2LAB[#, "refWP" -> "D50"] & /@ CC24XYZWBD50;
DE00LAB的输入是两个“像素”,而我们想对比两组像素集对应的像素,所以先用Table构造一个新数组,将一对对欲对比的像素捆在一起,并组成像素对的集合:
(*DE00对比的是两个像素,所以要把对应像素捆在一起,组成像素对的集合*)
Table[{CC24A2014LAB[[x]], %[[x]]}, {x, 1, 24}];
用DE00LAB对比该集合中的每个像素对。这里用到一个语法糖:
DE00LAB @@@ %
如果DE00LAB对比一个像素对,则要用到Apply函数,其简写是@@;若批量对比像素对的集合,就用到@@@了。当然,最好的方法是把代码保存下来,直接用。这句的结果给出每一对像素对的DE00色差,可以输出一下其中的最大值:
Max[%]
结果是1.38304,色偏已经肉眼不可分辨了;
②.第二组数据
直接算:
XYZ2LAB[#, "refWP" -> "D50"] & /@ CC24XYZD50;
Table[{CC24A2014LAB[[x]], %[[x]]}, {x, 1, 24}];
DE00LAB @@@ %;
Max[%]
结果是0.684686。
实验结论:官方软件中的光谱数据更接近于光谱反射率。
这个结果,在一定程度上证明了,10nm步进的光谱数据,也能保证一定精度;笔者也尝试了,若将插值阶数提高,能进一步提高精度(插值阶数对精度的影响可能取决于数据本身的平滑程度)。
读者可以试试让机器告诉你哪个色块有最大色偏。
该案例中使用的流程仅作练习用,实验结论仅作参考。