利用 MATLAB 和 Dcraw 处理数码相机 Raw 文件的完整流程

这篇文章要说的当然不是如何用 PS、LightRoom 来处理 Raw 文件,而是一种更加彻底、数据化的办法 -- 利用 MATLAB 直接处理数码相机的 CFA(Color Filter Array) 数据。

接触过摄影的人都知道,对于数码相机来说 Raw Data 是原始的、未被处理的数据,相比 JPEG,Raw 文件记录了更多的场景信息,保留了更宽广的色域以及动态范围,也留下了更为自由的后期处理空间。Raw Data 只是一种图像数据的封装形式而并不是一种文件格式,不同厂商的相机一般都拥有自己的 Raw Data 格式,比如常见的 *.CR2、*.NEF、*.ARW、*.ORF(Olympus)、*.RAF(Fuji)、*.DNG(Adobe、Leica)等。

对于大部分人来说,处理 Raw Data 的软件不外乎就那么几种,Camera Raw、LightRoom、Aperture、DxO 以及各厂商自带的处理软件。这些软件虽然通过图形界面提供了简洁易懂的处理流程,但是遗憾的是它们能导出的都并非真正意义上的 “Raw Data”,即传感器直接记录的、与照度成线性关系的第一手数据。对于摄影、媒体、艺术领域,这些软件已经足够强大,但是在图像处理、计算机视觉等研究领域,我们需要的是把照片中的信息转换为能够通过数字来定量表示的形式,或者通过一些公式、算法直接对图像(或图像的某一部分)进行处理,再或者对两张图片之间的差别进行量化表示,这时候 LightRoom 这些软件就显得有些无能为力了。

这篇文章要讲的就是如何通过 MATLAB 对 Raw Data 进行处理并从中提取出我们想要的图像信息,这是一种我所了解的最彻底、最根本的获取相机传感器原始信息的方法,并且这些信息都是以数字的形式记录下来,可以很方便地在此基础上进行图像的存储、传输或者进一步操作。实际上 MATLAB 本身就提供了非常强大的图像处理功能,正所谓 “普通青年处理图片用 PS,文艺青年处理图片用 LightRoom,2B 青年处理图片用 MATLAB ”。

由于各家相机厂商对 Raw Data 采取了不同的封装方式,MATLAB 并不能一一识别这些文件格式,因此我们需要先利用 Dcraw 将不同扩展名的文件转换为 MATLAB 能够读取的图片格式 -- tiff。Dcraw 是一种 Raw Data 解析方案,它能够识别市面上大部分的相机型号,并将相应的 Raw 文件导出为 tiff 或 PGM/PPM/PAM 格式文件。事实上 Dcraw 本身就可以算作一种 Raw Data 的处理程序,它拥有白平衡设置、伽马校正、色彩空间转换、去马赛克、亮度调节等一系列功能,并且提供了 C 源代码,让用户可以在任何操作系统上对其进行编译。关于 Dcraw 更详细的设置可以在其官方文档或这个博客中查看,这里我们仅仅把它作为 Raw Data 到 tiff 的一种媒介,而不对图像做任何的处理 -- 把所有的操作都留到 MATLAB 里。

目录

0. Dcraw 的预处理

以下操作以 Windows 平台为例,使用的相机为 Nikon D3X。为了简化流程,我们使用 Dcraw 现成的可执行文件(http://www.centrostudiprogressofotografico.it/en/dcraw/)而不在本地对其编译。一般将这个 Dcraw.exe 文件放在 C:\windows 路径下,这样可以直接从运行(Win + R)中执行。在运行中输入 cmd 进入命令行窗口,这时已经可以直接调用 Dcraw,或者输入 dcraw 查看相关的一些命令。

由于我们不准备用 Dcraw 对 Raw Data 做任何处理,只需要输入

dcraw -4 -T -D -v pathfilename

其中 pathfilename 为图像的绝对路径,比如 E:\photosimg1.NEF 这样。这里的 -T 表示将图像以 tiff 格式导出,-D 表示不对图像做任何的彩色插值和亮度调节(彩色插值的问题后面会提到),-v 表示在 Dcraw 处理结束后在屏幕上显示相关信息(这一步有没有都无所谓),而 -4 等价于 -6 -W -g 1 1,即表示导出的图像为16位(而不是常见的 JPEG 的8位 )、不进行任何白平衡校正不进行任何伽马校正。在一些需要获取拍照时白平衡设置的场合也可以使用 -6 -w -g 1 1 -T -D 这样的参数组合,但这里我们使用 -4 -T -D 就好。各参数的意义在上面提到的官方文档页面中有详细的说明。

完成这一步之后在原 Raw 文件同一个文件夹下就会多出了一幅 tiff 格式的图像,这时候大部分图片浏览软件都可以打开它了(一些看图软件在安装插件后能够直接浏览 Raw 文件,比如我的 IrfanView,但实际上它们打开的只是嵌入在 Raw Data 中的经过一系列转码的缩略图而并非 Raw 本身)。但是为了避免各种图像浏览软件不同的解码方式对预览图像造成的影响,下面都使用 MATLAB 中的 imshow 函数来浏览图像。

同样是这幅 tiff 图片,如果直接使用 IrfanView 查看,得到的是下面的效果,明显比 MATLAB 中的要亮得多。

下面的图片截图如果不特殊说明都是指在 MATLAB 中预览的效果。得到的这幅 tiff 图像除了一些缩略图和文件头之外,基本上记录了与 Raw Data 同样多的图像信息,并且是 MATLAB 可读取的。由于编码上的差异,并且 Dcraw 不像大多数软件那样去除了图像边缘的一些像素,有时这个 tiff 文件的体积甚至会比 Raw Data 文件更大。与 Camera Raw、LightRoom 等软件打开的 Raw 文件最明显的区别就是,这张 tiff 图片是黑白的。这里牵涉到了去马赛克(Demosaic,但我更喜欢叫它彩色插值)的问题,下面我简单地谈谈我的理解,不一定完全正确。详细的介绍还是看 Wiki 吧。

大家都知道一般民用的数码相机只有一块 CMOS 或 CCD(这里不谈 3CCDFoveon X3),而无论是 CMOS 还是 CCD,都只对照射在它们有效面积之上的光通量敏感,并且产生与该光通量成正比的电流作为输出。这个输出实际上是由光源的光谱功率分布(SPD)、物体表面反射比以及相机传感器灵敏度共同作用的关于波长的一个积分结果。由于这是一个对整个可见光波段的关于波长的积分,显然不能够表示任何色彩信息。正因为这一点,目前消费级相机的传感器都是不具备任何颜色感知能力的,照片中所有色彩的信息实际上都是软件处理后的结果。正因为这点,我们一般不可能得到一张真正“未经处理过”的彩色图像。既然传感器只能感受光的“强度”而非“色彩”,我们就需要在传感器之前放置一些透过率满足一定波长条件的滤光片,使得经过这些滤光片后作用在传感器上的光信号只是某一波长范围内的积分形式(比如500 ~ 600nm),而非整个可见光波段(380 ~ 780nm)。实现这个功能最常见的滤光片就是拜耳滤镜,如下图所示。

灰色部分为 CMOS,彩色部分为对应颜色的滤镜,每个滤镜下方对应的像素点只能接收到该滤镜对应波长的光信号,而不是整个可见光谱范围的光信号。根据左上角四个滤镜从左至右、从上至下的顺序一般有“RGGB”、“GBRG”、“GRBG”几种。D3x 为“RGGB”型,具体相机的滤镜排列可以对着红绿蓝单色图片分别拍摄一张然后查看 Raw Data 的数值分布来获得。知道自己相机所用的拜耳滤镜的排列模式(Pattern)是进行彩色插值操作的前提(之所以绿色滤波片的数量是红蓝滤波片数量的两倍,主要是因为人眼对绿光最为敏感,这点不作展开说明)。既然知道了每个颜色滤波片背后 CMOS 对于像素单元上的光强度,以及各个像素之上的滤波片颜色,我们就可以对每个像素的三原色进行恢复,这就是去马赛克(Demosaic)或者说色彩插值的本质。举个例子,假设某个 CMOS 阵列上的滤波片为“RGGB”排列(参考上图),现已知某个像素点 x 对于为 R 滤波片,则其上、下、左、右四个像素点必定对于 G 滤波片,右上、左上、右下、左下四个像素点必定对于 B 滤波片,因此这时候像素点 x 对应的 R 通道数值就是该点 CMOS 上实际的电流大小;而 G 通道数值是对上、下、左、右四个像素 CMOS 电流大小取平均之后的值;同理,B 通道是对右上、左上、右下、左下四个像素取平均之后的值。当然这里说的只是最简单的插值方法 -- 领域取平均法,实际上各个厂商都需要考虑各方面因素对色彩插值进行算法上的优化。一般我们看到的 JPEG 图像或者彩色的所谓的 Raw Data,都必定经过这一步色彩插值。

但是我们现在希望把这一步骤留到 MATLAB 中进行,所以在 Dcraw 中选用 -D 参数使得插值先不被执行,这也是为什么上面得到的 tiff 图像仅仅是灰度图像 -- 因为它只记录了光强度信息,而不包含任何颜色信息。

这时可以在 MATLAB 中打开这幅灰度图像了。由于各个像素上记录的光强度是一个标量,这幅图像相当于一个 m × n 的矩阵,其中 m 和 n 分别为 CMOS 纵向和横向的像素数。

raw = double(imread('img1.tiff'));

现在终于可以开始在 MATLAB 中对相机传感器的原始数据进行处理了。整个处理过程大概遵循以下步骤:

如果不是要对图像进行处理,而仅仅是希望获得拍照时场景照度的相关数据,只需要进行到第三部 Demosaicking 即可。下面的步骤仅仅是在 MATLAB 中模拟一张相片从被 CMOS 记录到最终呈现在屏幕上的整个过程,相当于人为地重新干了一遍数码相机中图像处理芯片干的事情。若是出于研究的需要,可以对任意一步修改或增加操作。例如需要研究 Gamma 曲线,就不应该在 MATLAB 中执行我提供的 Gamma 校正(\(\gamma = 2.2\))的函数,而应该根据需要使用合适的 Gamma 曲线。

1. 线性处理(Linearization)

出于节省数据存储空间的目的,一些厂商(例如尼康和索尼)的 Raw Data 并不完全与像素点上的照度呈线性关系,而是会在编码上做一些处理,比如非线性压缩等。不过这里我们不需要担心这个问题,因为之前在 Dcraw 中使用 -4 参数时就已经解决了这个问题。我们只要确保各个像素的数值是分布在 14-bit(虽然 Dcraw 中的 -4 参数将图像设为16位,但其最大值仍然为 \({2^{14}} - 1 = 16383\))能够储存的范围之间即可,一般为 0 ~ 16383,并将超出这个区间的数值给拉回区间中。再将这些数值归一化至 0~1 区间中。

black = 0;
saturation = 16383;
lin_bayer = (raw-black)/(saturation-black); % 归一化至[0,1];
lin_bayer = max(0,min(lin_bayer,1)); % 确保没有大于1或小于0的数据;

要说明的是,我这里使用的 black = 0 以及 saturation = 16383 仅仅是针对 D3x 而言,不同厂家的相机,或者同一厂家不同型号的相机都可能不同。如果第一次使用某台相机不知道这两个参数,可以使用 dcraw -v -T 命令来查看,然后记下这两个数值供日后使用。要注意的是在命令行中每执行一次 dcraw 的命令,所生成的 tiff 文件都会覆盖掉原来的 tiff 文件,因此建议将原始的 Raw 文件先复制到另外一个路径下在执行 dcraw -v -T 命令。

下图中我使用的 Nikon D3x 和 SONY A7 就拥有完全不同的 black 和 saturation 值(索尼的 Raw 被压缩到了坑爹的12位...),因此在 MATLAB 中处理 A7 的图片时我就需要将上面的代码改为 black = 128 以及 saturation = 4095

线性处理到这里就完成了,在 MATLAB 中用 imshow 来查看这一步我们得到的图像(灯箱背景出现的四道条纹是摩尔纹的缘故,无视就好):

2. 白平衡校正(White Balancing)

其实将这一步叫作白平衡校正也并不是很恰当,因为这并不是指利用各种白平衡算法对图片的色调进行修复的那个白平衡校正,而仅仅是对 RGB 三通道乘上不同的增益系数,以补偿因为三种滤波片具有不同光谱灵敏度带来的影响。如果不考虑图像亮度(亮度的处理我们放在后面),将 R 通道乘以2并保持 G 通道不变,或者将 G 通道乘以0.5并保持 R 通道不变,这两种方式对画面颜色变化的影响是等效的。因此我们通常将 G 通道的增益系数固定为1,仅仅考虑 R 和 B 的系数。关于这两个系数具体数值应该取多少,则取决于相机的型号以及拍摄时使用的白平衡参数。实际上,在相机的白平衡设置里选择不同场景,就是在调整这两个增益系数。如果想还原为拍摄时使用的白平衡设置,可以在 Dcraw 中使用 -w -v 参数,这时屏幕上会显示出当时所使用的 R、B 通道的增益系数。

上图中的2.433594和1.347656分别表示拍摄这张图像时所使用的 R 通道和 B 通道的增益。要注意的是,一旦使用了 -w 参数,Dcraw 就会自动完成彩色插值的工作,这样得到的 tiff 图像就不再是原始灰度图像了。因此我们仅仅是使用 -w 来查看增益系数。如果不希望使用拍摄时的白平衡设置,则可以使用 -W 参数,这样不管当时用的是哪种白平衡档位,其 R、B 增益都是一个固定的值。例如对于这台 D3x 来说固定的 R、B 增益分别为2.625910和1.263930。在实际的图像处理应用中,通常需要固定相机的白平衡参数,即在相机的白平衡设置中手动输入色温。对于使用相同相机白平衡设置(Auto 除外)拍出的图片,它们都具有相同的增益系数。可以通过 -g 参数手动设置三通道的增益系数,例如 -g 1 1 1 1。

得到了 R、B 通道的增益后,我们需要将相应的像素值乘上这个系数。前面说过,不同相机具有不同的拜耳滤镜排列方式,因此需要根据实际情况进行增益系数的乘法。这里我使用的是相机拍摄时的白平衡参数,即 r_multiplier = 2.433594 和 b_multiplier = 1.347656。

wb_multipliers = [2.433594, 1, 1.347656]; % for particular condition, from dcraw;
mask = wbmask(size(lin_bayer,1),size(lin_bayer,2),wb_multipliers,'rggb');
balanced_bayer = lin_bayer .* mask;

上面代码中的 wbmask 函数就是根据实际拜耳滤镜的排列生成对应的掩板:

function colormask = wbmask(m,n,wbmults,align)
% COLORMASK = wbmask(M,N,WBMULTS,ALIGN)
% Makes a white-balance multiplicative mask for an image of size m-by-n
% with RGB while balance multipliers WBMULTS = [R_scale G_scale B_scale].
% ALIGN is string indicating Bayer arrangement: 'rggb','gbrg','grbg','bggr'
colormask = wbmults(2) * ones(m,n); % Initialize to all green values;
switch align
 case 'rggb'
  colormask(1:2:end,1:2:end) = wbmults(1); % r
  colormask(2:2:end,2:2:end) = wbmults(3); % b
 case 'bggr'
  colormask(2:2:end,2:2:end) = wbmults(1); % r
  colormask(1:2:end,1:2:end) = wbmults(3); % b
 case 'grbg'
  colormask(1:2:end,2:2:end) = wbmults(1); % r
  colormask(2:2:end,1:2:end) = wbmults(3); % b
 case 'gbrg'
  colormask(2:2:end,1:2:end) = wbmults(1); % r
  colormask(1:2:end,2:2:end) = wbmults(3); % b
 end
end

完成白平衡调整后的图像如下。由于 R 和 B 通道都乘以了大于1的数,图像的平均亮度较上一张略有提高了。

3. 色彩插值(又称去马赛克,Demosaicking)

上文提到的插值步骤在这一步中实现,经过色彩插值之后原来的灰度图像就成为了一幅三通道的彩色图像。空间插值有非常多的方法,这里为了方便我们使用 MATLAB 内置的 Demosaic 函数,它能够直接把单通道的灰度图像转换为三通道的彩色图像。由于 Demosaic 函数的输入必须为 uint8 或 uint16 类型,我们需要把原来的 double 型先转换为 uint16 型。注意这里的 'rggb' 应该根据相机的具体情况而调整。

temp = uint16(balanced_bayer/max(balanced_bayer(:)) * (2^16-1));
lin_rgb = double(demosaic(temp,'rggb'))/(2^16-1);

完成这一步之后我们就得到了最原始的彩色信息。一些应用中所需要的就是这幅图像的数据,可以使用 imwrite 函数将其保存在硬盘中。后续的色彩空间转换、Gamma 校正等步骤视情况决定是否需要执行。色彩插值后得到的图像如下:

4. 色彩空间转换(Color Space Conversion)

关于色彩空间这里不作过多介绍,举一个最简单的例子,同样一幅图像文件分别在两台显示器上显示,其各个像素的 RGB 值肯定是一样的,但是人眼看上去往往都存在细微的颜色偏差,这就是因为 RGB 色彩空间是设备相关的(Devices-Dependent),而任何两台显示器的 RGB 色彩空间一般都不会完全相同。具体的解释参考 Wiki。为了使一幅图片在各种显示设备上有尽量一致的视觉效果,我们就需要一个设备无关(Devices-Independent)的色彩空间作为传递媒介。目前在电子设备中用的最多的设备无关的色彩空间(有时也称绝对色彩空间)就是 sRGB 和 AdobeRGB。如果在 Dcraw 中使用了色彩插值,则自动包含了一个色彩空间变换的过程。Dcraw 先将相机相关的 RGB 空间转换至 XYZ 空间,然后再从 XYZ 转换到 sRGB 作为输出。在 MATLAB 中我们将这两个步骤合二为一。下面我将与相机相关的 RGB 色彩空间称作 Camera。对于大部分相机,我们可以得到从 XYZ 空间到相机相关空间的变换关系,即已知 XYZ-to-Camera。而作为两种绝对色彩空间,sRGB-to-XYZ 也是固定的。根据矩阵运算法则,我们可以得到从相机相关空间到 sRGB 空间的变换关系:\[{A_{sRGB \leftarrow Camera}} = {\left( {{A_{Camera \leftarrow XYZ}} \cdot {A_{XYZ \leftarrow sRGB}}} \right)^{ - 1}}\]

不同相机的 Camera 不同,因此我们必须获得适合自己相机的 \({A_{Camera \leftarrow XYZ}}\)。在 Dcraw 官网提供的 c 文件中收集了市面上大多数相机的 \({A_{Camera \leftarrow XYZ}}\),可以在 dcraw.c 中的 adobe_coeff 函数下找到,并且这个数据库是定期更新的。adobe_coeff 函数下的数字是 \({A_{Camera \leftarrow XYZ}}\) 中各元素乘以10000后逐行排列的数值,

或者也可以使用 Adobe DNG Converter 这个软件来查看相机的 \({A_{Camera \leftarrow XYZ}}\)。这种方法得到的数值对应了矩阵中逐列排列的各元素。以 D3x 为例,有:\[{A_{Camera \leftarrow XYZ,{\rm{D3x}}}} = \frac{1}{{10000}}\left[ {\begin{array}{*{20}{c}}{7171}&{ - 1986}&{ - 648}\\ { - 8085}&{15555}&{2718}\\ { - 2170}&{2512}&{7457}\end{array}} \right]\]

sRGB-to-XYZ 可以在国际照明委员会(CIE)公布的标准中查到,有:\[{A_{XYZ \leftarrow sRGB}} = \left[ {\begin{array}{*{20}{c}}{0.4124564}&{0.3575761}&{0.1804375}\\ {0.2126729}&{0.7151522}&{0.0721750}\\ {0.0193339}&{0.1191920}&{0.9503041}\end{array}} \right]\]

得到了这两个矩阵,自然也就能够算出 \({A_{sRGB \leftarrow Camera}}\)。在色彩空间转换过程中必须考虑这样一个问题:由于白色(客观意义上的白色)在相机的 RGB 空间和 sRGB 空间中都是用\({\left[{\begin{array}{*{20}{c}}1&1&1\end{array}}\right]^T}\) 来表示,而我们上述白平衡调整的目的就是要确保图像中白色的部分在任何空间中都呈现出白色。因此以下关系必须成立:\[{\left[{\begin{array}{*{20}{c}}1\\ 1\\ 1 \end{array}} \right]_{Camera}} = \left[ {\begin{array}{*{20}{c}}{ }\\ {{A_{Camera \leftarrow sRGB}}}\\ { }\end{array}} \right]{\left[ {\begin{array}{*{20}{c}}1\\ 1\\ 1\end{array}} \right]_{sRGB}}\]

根据线性代数的知识,要满足上式,矩阵 \({A_{Camera \leftarrow sRGB}}\) 的每一行元素之和必须为1,因此在 MATLAB 中我们必须再加上一个步骤,将 \({A_{Camera \leftarrow sRGB}}\) 各行归一化为1。色彩空间变换的代码如下。注意矩阵 XYZ2Cam 请根据自己使用的相机型号进行修改。

sRGB2XYZ = [0.4124564 0.3575761 0.1804375;0.2126729 0.7151522 0.0721750;0.0193339 0.1191920 0.9503041];
% sRGB2XYZ is an unchanged standard
XYZ2Cam = [7171 -1986 -648;-8085 15555 2718;-2170 2512 7457]/10000;
% Here XYZ2Cam is only for Nikon D3X, can be found in adobe_coeff in dcraw.c
sRGB2Cam = XYZ2Cam * sRGB2XYZ;
sRGB2Cam = sRGB2Cam./ repmat(sum(sRGB2Cam,2),1,3); % normalize each rows of sRGB2Cam to 1
Cam2sRGB = (sRGB2Cam)^-1;
lin_srgb = apply_cmatrix(lin_rgb, Cam2sRGB);
lin_srgb = max(0,min(lin_srgb,1)); % Always keep image clipped b/w 0-1

其中 apply_cmatrix 函数就是把我们得到的 \({A_{sRGB \leftarrow Camera}}\) 应用到原图像的各个通道上:

function corrected = apply_cmatrix(im,cmatrix)
% Applies CMATRIX to RGB input IM. Finds the appropriate weighting of the
% old color planes to form the new color planes, equivalent to but much
% more efficient than applying a matrix transformation to each pixel.
if size(im,3) ~=3
 error('Apply cmatrix to RGB image only.');
end
r = cmatrix(1,1) * im(:,:,1)+cmatrix(1,2) * im(:,:,2)+cmatrix(1,3) * im(:,:,3);
g = cmatrix(2,1) * im(:,:,1)+cmatrix(2,2) * im(:,:,2)+cmatrix(2,3) * im(:,:,3);
b = cmatrix(3,1) * im(:,:,1)+cmatrix(3,2) * im(:,:,2)+cmatrix(3,3) * im(:,:,3);
corrected = cat(3,r,g,b);

经过色彩空间变换后的图像如下,可以看出相比变换之前的图像,各个彩色色块饱和度明显增加,而白色色块颜色保持不变。

5. 亮度校正与伽马校正(Brightness and Gamma Correction)

对于大部分处于研究目的的图像处理流程,这一步不建议执行。在这一步之前,我们得到的图像仍然是与拍摄场景呈线性的,而线性数据往往才是对分析图像有帮助的。但是为了得到更好的显示效果,亮度与 Gamma 校正通常是必不可少的。如果对最后输出的图像存有异议,强烈建议首先返回到这一步中来寻找问题。根据经验,一张图像的平均亮度是像素最大值的四分之一时我们认为它是亮度合适的(注意这条法则不适用于所有场景,例如一张夜景图像中平均亮度往往会很小)。因此我们调整全局亮度使其符合这一假设:

grayim = rgb2gray(lin_srgb); % Consider only gray channel
grayscale = 0.25/mean(grayim(:));
bright_srgb = min(1,lin_srgb * grayscale); % Always keep image value less than 1

接下来是 Gamma 校正。Gamma 曲线是图像、信号处理领域使用最为广泛的非线性处理,我们最容易见到的就是 Photoshop 中的“曲线”功能,如果将曲线拉成 \(y = x^{\gamma}\) 的形状,就相当于对图像做了一次 Gamma 校正。Gamma 校正是一个很大的话题,这里不具体介绍,可以参考 Wiki 。在 sRGB 的官方文档中使用的是 \(\gamma = \frac{1}{{2.4}}\),并在函数值较小的部分应用了小范围的线性函数。但是现在大多数平台(Windows,Mac)都使用了 \(\gamma = \frac{1}{{2.2}}\) 的曲线,因此这里我们也使用 2.2 作为参数,并且不考虑局部的线性化。如果需要精确的 sRGB 标准的校正函数,可以查看其官方文档。

nl_srgb = bright_srgb.^(1/2.2);

经过亮度校正和 Gamma 校正后的图像如下。由于使用的 Gamma 曲线是一条凸函数,相当于把图像暗部的细节展宽,因此得到的图像要比校正前更亮。

到此为止一套通用的 Raw Data 处理流程就完成了,接下来可以根据需要再进行一系列的处理过程,比如使用一条 S 型曲线增加图像对比度、进行白平衡处理等等,或者直接保存为 tiff 文件再导入到其他图片处理软件中进行处理。

下面换一张比较生活化的图片,再对整个流程做一个展示。


▲ MATLAB 直接读取由 Dcraw 导出的 tiff 文件


▲ 线性处理后的图片


▲ 白平衡调整后的图片,这里使用 R、B 的增益系数分别为2.203125和1.378906


▲ 色彩插值后得到的彩色图片


▲ 转换至 sRGB 空间后的图片


▲ 经过亮度校正后的图片


▲ 经 Gamma 校正后的图片

References


131 Comments

  1. sunyu 2016-06-28 Reply

    请问您,相机内部的过程为:景物→镜头→传感器→CFA→去马赛克→后处理→输出图像。那么由数码相机直接导出来的raw格式是在相机内部进行颜色插值(去马赛克)之前得到的数据,还是在颜色插值之后到相机内部的后处理之间的过程中得到的数据呢?

    • Author
      Captain 2016-06-28 Reply

      你流程中的“传感器→CFA”应该互换一下,光是先经过CFA再到达传感器上。
      Raw格式并未进行颜色插值。其实你把Raw文件不断放大就会看得到Bayer pattern。

      • sunyu 2016-06-28 Reply

        哦,十分感谢您的答复。这样的话,如果相机可以直接导出tiff格式的图片,就说明在相机的内部存在一个类似于Dcraw功能的东西。我最近在做颜色插值算法,相机内部在完成色彩插值的过程中是单独利用一个颜色通道相应的邻域对缺失点进行插值,还是会在插值的过程中考虑到另外两个通道对它的影响?据您所知,有哪种相机是这么进行插值的吗?

        • Author
          Captain 2016-06-28 Reply

          不好意思,我对颜色插值方面不太了解,但是我猜应该是需要考虑通道之间相互作用的,毕竟目前还没有哪家的传感器可以保证百分百抑制通道串扰。

  2. yangcc 2016-01-12 Reply

    您好,我按照您的流程处理了一幅图片,结果颜色失真很严重,颜色明显偏红,请问这可能是什么原因呢?
    另外,我可以确定我的操作没有任何不对的地方。

    • Author
      Captain 2016-01-12 Reply

      能附个图片链接吗?是什么型号的相机?

      • yangcc 2016-01-13 Reply

        图片是我自己拍的,比较大,能给您发邮件吗?相机型号是 佳能EOS 5D mark III

        • Author
          Captain 2016-01-13 Reply

          你把 .CR2 和输出的偏红的 .jpeg 都发我一份吧,jqx1991#gmail.com

  3. Anonymous 2015-12-08 Reply

    由于涉及到相机测量火焰温度,之前尝试过Dcraw没成功,现在结合楼主的程序终于搞定了,万分感谢啊,可是我想知道怎样在Dcraw中直接对图片进行“正确”的白平衡校正和色彩插值,尝试过在Dcraw不执行-D,让程序自行插值,但是出来的结果不对

    • Author
      Captain 2015-12-10 Reply

      Dcraw的自动白平衡就是Gray World算法,大部分时候都没什么用…
      色彩插值的话也只提供一些双线性之类比较初级的算法。如果结果不对的话,有可能是CFA pattern没选对,你应该先知道你的sensor是rggb、bggr、grbg还是gbrg排列。

  4. wy 2015-11-06 Reply

    我在用windows10,是用的一个完整的exe,没有自己编译。

    • Author
      Captain 2015-11-06 Reply

      Win10的确有可能有问题,下载一份.c源码自己编译一下比较保险。

  5. Anonymous 2015-11-05 Reply

    非常感谢楼主的博文,让我涨了不少知识,还是穷学生,只给了5元,可能不够一杯咖啡,楼主见谅,略表心意。

  6. wy 2015-11-04 Reply

    谢谢您的回复,我是在cmd里调用Dcraw.exe进行转换的时候出现这个out of memory的,我是希望得到一副可以被matlab处理的tiff,我的raw文件是ARW格式,10M左右的图,出现这个错的时候还没有用到matlab进行后续的插值变换。
    不太清楚是什么地方出现问题。

    • Author
      Captain 2015-11-05 Reply

      Why does dcraw say “Out of memory” in Windows Vista?Ostensibly to stop memory leaks, Microsoft decided that programs using the old MS-DOS API, including anything compiled with DJGPP, shall be confined to 32MB of memory. This limitation can be removed by some combination of service packs and registry hacks, or you can compile dcraw to use the newer Win32 API. Thomas Nicely (of Pentium FDIV fame) has a page describing the problem and various workarounds. 你在用vista?是自己编译的还是下载已经编译好的exe?

  7. wy 2015-11-04 Reply

    内存是2G的,我也很无语,不应该报这个错误啊。

    • Author
      Captain 2015-11-04 Reply

      2G的不可能,可能是允许matlab调用的内存被限制了,preference里面调一下吧。
      或者开几个断点看一下是哪一步出现提示。

  8. wy 2015-11-04 Reply

    你好,我这边的问题是转换一副ARW格式的图片,运行后显示out of memory,是因为电脑内存不够?

    • Author
      Captain 2015-11-04 Reply

      是的。
      无意冒犯,这内存得有多小啊。。。

  9. Chen 2015-10-13 Reply

    讲的很详细呀,我想请教一个问题可以吗?
    如果拍一张纯白图,灯箱或者光线充足颜色均匀的白纸白墙什么的,那在做awb之前图片会出现中间偏亮四周偏暗的情况吗?

    • Author
      Captain 2015-10-14 Reply

      会的,对于不同视场通过光学系统进入到sensor的能量是不同的,一般叫作vignetting或者shading。我这几天正好在做shading的校正,有空可以讨论讨论。

      • Chen 2015-10-15 Reply

        哎呀 好巧 :)
        我最近也是在做Shading方面的调整
        可以互发mail吗?咱们可以讨论一下
        Mcu_Chen@163.com
        期待你的回复XD

  10. Donny 2015-08-07 Reply

    还有最重要的给漏了,我用的佳能EOS 60D,设置成了sRAW(那时候不懂),好像是YCbCr编码的,dcraw -4 -T -D 之后matlab读出来就是1728×2592×3,楼主威武能给解决喽不?

  11. Donny 2015-08-07 Reply

    你好,发现个小错误,‘grbg’排列滤镜中 b 的掩板跟 r 写成一样的了,源代码里也是;还有去马赛克计算时2^16是不是应该减1?另外请问:白平衡校正的目的是什么,如果只想得到电流值是否不应该做?若多张图片进行比较的话,增益系数是不是要保持一致(拍照时设置的是自动白平衡,系数都不一样)?翘首盼复!

    • Author
      Captain 2015-08-15 Reply

      第一个问题我没听明白什么意思,能不能给出具体代码位置?
      第二个问题你说得对,准确的应该是2^16-1,我稍后修改,多谢指正。
      很难给白平衡的目的做一个定义,我是这么理解的,主要有两个目的:1,从喜好性来看是为了将图片呈现为接近人眼感知的样子,比如摄影中的白平衡问题,在这一方面中白平衡完全是为了提高图像质量,后续不再有任何对图像的操作;2,在机器视觉领域白平衡是必不可少的一道程序,它修正图像的偏色是为了后续的进一步处理,比如信息的提取、识别等等。
      你说的电流值以及多张图片比较的问题,我不明白你的具体应用场景是什么因此没法回答。
      dcraw -4 -T -D 得到 1728×2592×3 彩色图像,这个问题下面评论里面也有人提到过了,他后来证实了是 dcraw 程序本身的问题。不知道你用的 dcraw 是自己编译的还是直接从官网下载的可执行文件?

      • Donny 2015-08-15 Reply

        1. 白平衡校正部分 switch align 下的 case ’grbg‘。你修改的2^16-1下一句应该也要改吧?
        2. 白平衡我想想再向你请教。
        3. 应该不是dcraw的问题,官网下载的32位和64位的可执行文件结果一样,但是处理https://users.soe.ucsc.edu/~rcsumner/rawguide/ 下载的“banana_slug.cr2”正常。推测是因为sRAW存储的并非CFA数据,而是YCbCr,见http://lclevy.free.fr/cr2/#sraw

        • Author
          Captain 2015-08-16 Reply

          已修改,多谢!
          佳能的 sRAW 我不太了解,日常主要使用尼康和索尼的机器,如果不是 CFA 的话我这篇文章中的大部分内容也就不适合了。但是既然能获得 YCbCr,肯定也有办法导出 raw data

  12. sktc 2015-07-08 Reply

    你好,我现在正在做彩色复原方面的研究,想请教下,自己做的相机,将得到的raw格式图像经matlab demosaic处理之后,得到了彩色图像。看上去感觉颜色有些失真,我觉得相机RGB空间转XYZ这步应该挺重要的,这步应该实现了彩色校正,请问楼主做过相机RGB空间转XYZ的相关工作没,可以互相交流下。

    • Author
      Captain 2015-07-11 Reply

      我没做过但算是了解一点。这一步一般都是在标准光源下对着标准色卡拍摄一张曝光准确的图片,然后用伪逆法求RGB->XYZ实现定标。

  13. Evan 2015-06-11 Reply

    再问个问题,dcraw -4 -T -D -v pathfilename 得到图片后,按照步骤处理到Demosaicing完成,然后根据相机的M矩阵,通过(M^-1) *RGB(camera)转换到XYZ;
    此时得到的Y值,除以快门时间后,是否和标准光学仪器量测到的亮度值成固定的正比关系?

    • Author
      Captain 2015-06-11 Reply

      当然不是。首先亮度是光度学的概念,而相机传感器反应的都是辐射度学的量;其次传感器输出电流与场景照度之间存在非线性的关系,这个非线性得先校正了才行。
      你这个问题的具体应用是什么能不能说说?我的学位课题正好也是这方面的内容。

      • Evan 2015-06-12 Reply

        具体的应用:通过照相机拍摄显示器,得到显示器的真实亮度,即可分析显示器的显示效果。

        • Author
          Captain 2015-06-12 Reply

          这个比较简单吧,配合照度计,用同一台相机同样的参数对显示器做一次定标,然后用LUT的办法就能实现。

          • Evan 2015-06-12

            由于相机固定一种参数,无法拍摄所有灰阶;在不同的亮度下,会需要不同的ISO及快门值,可能需要非常多的LUT;所以想找到Raw data与真实亮度间的函数关系,而不仅仅是LUT。
            请教一下,相机的哪些参数的改变会影响这个LUT?我觉得快门时间应该不会影响吧?可以除以快门时间达到消除快门的影响?

          • Author
            Captain 2015-06-12

            如果是普通显示器的话动态范围其实很小的,不会需要很多档曝光参数,LUT应该是可以接受的。不考虑噪声的话快门时间理论上跟rawdata的数值成正比,除以快门时间应该可行,但是我没看过相关的资料,还是需要你自己测试一下吧。

  14. Evan 2015-06-10 Reply

    为什么输入以下命令:dcraw -4 -T -D -v pathfilename;出来的图片是彩色的?
    有尝试dcraw -4 -T -d -v pathfilename;出来的是灰度图,但每两个亮度值之间存在间隔行:如9000,0,9000,0,9000;
    另外,“ -4 等价于 -6 -W -g 1 1”中-6这个指令无效,请问应该是啥?

    • Author
      Captain 2015-06-10 Reply

      -4 等价于 -6 -W -g 1 1 不应该无效啊,-6是指输入图像位深为16位,而不是8位。
      只要命令中有-4或-W,出来的都应该是彩色图像,因为-W命令会启用色彩插值。
      但是如果命令中有-d或-D,则优先级会高于-W,即“-d: Show the raw data as a grayscale image with no interpolation. -D: Same as -d, but with the original unscaled pixel values”,这时候保留默认bayer pattern不进行色彩插值。但是-D的那个unscaled pixel values我也不清楚到底是什么意思。
      所以你说-D出来的是彩色图片我持怀疑态度,应该是你命令输入有误。

      • Evan 2015-06-10 Reply

        有发现问题所在,软件存在缺陷,加入-D与不加-D的结果输出没有改变,又下载了一个dcraw.EXE的执行档(32位),结果是正确的;
        冒昧的问下,是否有dcraw该软件的完整工程,能够生成64位的执行档?能否发我?或者下载网址?
        我的邮箱:jinyufeng1989@126.com,万分感谢

  15. SCOTT 2015-04-23 Reply

    亮度校正有没有一个统一的公式
    grayim = rgb2gray(lin_srgb); % Consider only gray channel
    grayscale = 0.25/mean(grayim(:));
    上面代码中0.25是如何确定的?为什么这么做?

    • Author
      Captain 2015-04-23 Reply

      0.25是一个经验数字,实际上一幅原始图片乘上多大的增益才算是亮度准确这又是一个很大的命题,我没有看过这方面的文献因此只能不严谨地给出一个0.25

      • SCOTT 2015-04-24 Reply

        好的 谢谢 我的raw文件识别不了。。。。
        提示:Cannot decode file F:MATLAB2015-4-21.raw
        是不是不支持。。。怎么破?

        • Author
          Captain 2015-04-24 Reply

          可能有很多原因。方便的话发一份给我吧。

          • SCOTT 2015-04-25

            怎么发你呢 请问

          • Author
            Captain 2015-04-26

            jqx1991#gmail.com

          • SCOTT 2015-05-21

            给你发了,谢谢

          • zhaohui che 2015-09-18

            我的raw也提示:cannot decode file E:2.raw 向您请教改怎么解决

  16. BM Teng 2015-03-21 Reply

    这种方法只可以用来处理某些牌子的相机吧?如果文件就是raw格式的呢?

  17. wu 2015-03-20 Reply

    我用的也是dcraw v9.22 相机是Sony RX 100 II 我qq是384040146 你qq是多少 ,我们可以讨论讨论一下。。。

  18. wu 2015-03-20 Reply

    你说的sony被压缩到12bit? 它最原始的可以输出到14bits的吗

    • Author
      Captain 2015-03-20 Reply

      rawdata的压缩应该是机内完成的,用户一般得不到未压缩的数据。
      ADC会直接把超出4095的截断,所以也应该不会是噪声。你是用什么软件发现最大值4149的?能不能把.ARW发给我研究研究?

  19. wu 2015-03-20 Reply

    我相机是Sony RX 100 ii dcraw 输出格式是12bit 。。。。。。但是我输出最原始的raw格式后,发现raw的最大值为4149.。这是怎么回事呀???? 是噪声的影响吗,但是相机模数转换的最大值应该也为4095啊,

  20. liyuan 2015-02-16 Reply

    请问下你的archive页面是用什么生成的?

    • Author
      Captain 2015-02-26 Reply

      忘记了…网上找的代码稍微改改

      • liyuan 2015-03-03 Reply

        能否共享一下?
        网上找了好久也没有找到满意的代码或者插件,感觉还是你这个比较赞!

Leave a reply

Your email address will not be published.