在MATLAB中处理RAW图像
目录
4.6 Brightness and Gamma Correction
摘要
这是一份说明文件,涉及读取和显示以RAW照片格式存储的未处理传感器数据所需的步骤。 RAW照片文件包含来自数码相机的原始传感器数据。 尽管它在科学上很有用,但通常必须先处理原始数据,然后才能显示它们。 在本教程中,将探讨原始传感器数据的性质。 讨论了在标准处理/显示链中应用的缩放和颜色调整。 提出了将RAW图像数据读入MATLAB的解决方案,以及实现可见输出的标准流程。
1、介绍
在图像处理和计算机视觉领域,研究人员通常与他们使用的图像的来源无关。他们只是简单地设计算法,以将图像(通常是8位强度图像或每通道8位的三通道RGB图像)视为多元函数,随机场或连接像素的图形。
但是,有时将图像真正与捕获图像的场景相关联非常重要。例如,对于模拟成像传感器行为的任何处理都是这种情况,例如某些高动态范围(HDR)方法和科学成像(例如,天文学)。在这种情况下,至关重要的是要了解在捕获后应用于图像的整个处理链。如果可能,最好处理的图像是直接来自相机的传感器数据,即原始数据。
任何希望从事图像处理流程的人(也就是将图像从相机传感器获取的图像转换成漂亮的输出图像)的人,也都可以访问原始数据。例如,研究用于将Bayer模式图像去马赛克的算法的研究人员将需要能够访问此类数据以进行实际测试。
尽管许多设备仅输出经过处理的数字图像,但是某些相机允许直接访问由成像传感器本身记录的信息。通常,这些是中高端DSLR(数码单镜头数码相机),可以选择输出RAW数据文件。将来可能会从其他类型的摄像头(例如智能手机上的摄像头)访问传感器数据,在这种情况下,以下一般原理仍然适用(尽管具体的编程可能会有所不同)。
“ RAW”是一类计算机文件,通常包含未压缩的图像,该图像包含传感器像素值以及有关由照相机生成的图像(Exif数据)的大量元信息。 RAW文件本身具有许多专有文件格式(Nikon的.NEF,Canon的.CR2等)和至少一种常见的开放格式.DNG,代表Digital Negative。后者表明数码摄影师应该如何考虑这些文件:原始原件,场景中所有捕获信息的存储库。 RAW文件是故意难以理解的数据结构,但是已经成功进行了****[1],可以访问内部的原始数据。
对于本文的其余部分,当指代一种或另一种文件格式的图像文件(例如.CR2)时,我们使用大写术语RAW,而(小写的)原始图像是指成像直接输出的未处理像素值。相机的传感器。
图像的“隐形”处理
当使用商业软件读取和显示RAW文件时,在幕后会发生许多过程,这些过程对于主要使用最终产品的人员(每通道8位RGB JPG / TIF /等)不可见。为了在屏幕上显示图像,原始图像被线性化,去马赛克,色彩校正,并且其像素值被非线性压缩以产生大多数操作系统和显示驱动程序期望的图像信息。处理原始传感器数据图像时,重要的是要说明每个步骤,以便正确显示结果。
文件版面。
本文档的目的是描述将原始数据转换为可见图像所需的步骤,并尝试对为何需要原始数据给出一些解释。首先描述原始传感器数据本身的性质以及需要执行的工作流程。然后,我们将参考一些免费软件,这些软件对于读取RAW图像必不可少。最后,我们提供了一个直接教程,用于将您的RAW文件读入MATLAB。包含代码示例,其编写目的是为了清楚而非效率。
2、原始RAW数据的性质
来自图像传感器的原始数据显然包含有关场景的信息,但是人眼本质上无法识别。 它是一个单通道强度图像,可能具有一个非零的最小值来表示“黑色”,其整数值包含10-14位数据(对于典型的数码相机),如图1所示。 说到固有的“白色”值,图像中的任何值都不会高于某个最大值,该最大值代表物理像素CCD的饱和点。 输出也可能大于相机的预期像素尺寸,包括未曝光像素的左侧和有意义像素的边界。
Color Filter Array
RAW传感器数据通常以彩色滤光片阵列(CFA)的形式出现。 这是一个m×n的像素阵列(其中m和n是传感器的尺寸),其中每个像素都承载有关单个颜色通道(红色,绿色或蓝色)的信息。 由于落在CCD中任何给定光电传感器上的光在电容器中记录为一定数量的电子,因此只能保存为标量值。 单个像素无法保留可观察光的3维性质。 CFA是一个折衷方案,它通过放置在每个像素上方的光谱选择滤镜在不同位置捕获有关三个颜色通道的信息。 虽然您可能只真正知道任何像素位置处的一种颜色值,但是可以巧妙地从附近已知这些颜色的邻居处插入其他两种颜色值。 此过程称为去马赛克,并在彩色数字图像中通常期望的每个像素位置生成RGB值的mby-n-by-3数组。
最常见的CFA模式是Bayer数组,如图2所示。在拜耳阵列图像中,代表绿光的像素数是其两倍,这是因为人眼对绿光的深浅变化更为敏感,而且它与场景中对光强度的感知更为密切相关。
请注意,尽管这种模式是相当标准的,但是来自不同相机制造商的传感器可能会有不同的“相位”。 也就是说,左上角像素上的“开始”颜色可能不同。 这四个选项通常称为“ RGGB”,“ BGGR”,“ GBRG”和“ GRBG”,它们指示图像的第一个“四个群集”的光栅方向。 去马赛克算法必须知道阵列的正确相位。
Color Channel Scaling
彩色成像的一个不幸现实是,色彩没有真相。 通常,人们无法查看彩色图像,也无法知道彩色图像在拍摄图像时忠实地代表了所讨论对象的颜色。 作为一种严格的简化,人们可以认为照明光具有一种固有的颜色,而落在其上的物体也具有其自身的颜色。 这些相互作用,并且相机或眼睛接收的光,从物体反射的光是两者不可分割的组合。
导致的结果是,任何物体都可以看起来像任何颜色,具体取决于照亮它的光。 我们需要的是一个参考点,我们知道的东西应该是某种颜色(或更准确地说是某种色度),以便我们可以调整像素的R,G,B值,直到它是该颜色为止。 这补偿了照明光的颜色并揭示了对象的“真实”颜色。 在相同光源照亮整个场景的假设下,我们可以对图像中的每个像素进行相同的平衡。 这是白平衡的过程。 本质上,我们发现我们知道的像素应该是白色(或灰色),我们知道它的RGB值应该全部相等,并找到使它们全部相等所需的缩放因子。
因此,问题简化为简单地找到两个标量,表示两个颜色通道到第三个颜色通道的相对比例。通常假设绿色通道是与其他通道进行比较的通道。这些标量还考虑了不同颜色通道的相对敏感性,例如CFA的绿色过滤器比红色或蓝色过滤器透明得多。因此,绿色通道标量通常为1,而其他标量通常为> 1。
Camera Color Spaces
可感知颜色的尺寸数为三个。 对于熟悉此结果的人(例如曾经听说过“任何颜色都可以由红色,绿色和蓝色的光组合而成”)的人,这似乎是显而易见的,但是在无限维空间中却有所减少。 到三维立体图并非易事。 可以为可感知的颜色构造几乎矢量的空间,从而导致在我们称为“颜色空间”的地方使用许多熟悉的线性代数运算1。 之所以必须使用“几乎是”的质量,是因为由于没有负光之类的东西,所以不能减去颜色。 幸运的是,这仍然给我们留下了一个非常适合线性代数的凸锥颜色。
在线性代数的语言中,我们用三个坐标(即与像素相关的R、G和B值)表示像素的颜色。重要的是,这些是像素在特定基础上的颜色坐标。通常不明显的是,由数码相机的物理传感器产生的基础是不一样的那些大多数显示器。关于这个主题的完整讨论超出了本文的范围,但是我们将简单地说明,我们假定可显示输出空间是定义为通用标准sRGB[10]的。
Exif Metadata
除了来自数码相机的成像传感器的原始数据外,原始文件还携带大量关于像素值和曝光本身的元数据。这些信息以大量Exif标记的形式出现,它们遵循标准的TagName: Value格式。与传感器数据一样,这些信息被笨拙地隐藏在原始文件中,需要特殊的软件来检索。
在大多数原始文件中保留的元信息数量是相当可观的。它通常包含关于数码相机本身的信息,以及关于曝光的信息,这两者对于计算摄影来说都是至关重要的。与本教程相关的信息包括前面提到的信息,如白平衡倍增器值和黑色电平偏移。下面显示了可检索信息的一个较小但具有指示性的子集。注意,文件中的元标签在不同的相机制造商之间是不同的。
- 相机\文件属性:相机型号、预设白平衡倍增器、图像宽度和高度、测光模式、创建时间、闪光灯使用、地理标签等。
- 摄影特性:ISO,焦距,快门速度,光圈f挡,超焦距离,拍摄时测量的白平衡倍率等。
The RAW Image Editing Workflow
为了在MATLAB中处理和显示来自传感器数据的图像,我们必须考虑上述原始数据的性质。图3中描述的工作流是如何从原始传感器数据中获得“正确的”可显示输出图像的一级近似。第4节将介绍如何在MATLAB中实现它,但这也可以被认为是使用任何编程语言的一般方法。
3、RAW可用软件
为了在MATLAB中处理原始图像,我们必须首先使用其他软件来**专有格式,并得到内部甜蜜的图像数据。以下是免费且非常有用的程序—只有一种或另一种是必需的,所以如果您不知道如何为第一种方法编译C源代码,也不必担心。另外推荐的工具是Phil Harvey的ExifTool[5],这是一个功能强大、可编写脚本的开源元数据阅读器,虽然对所描述的工作流来说不是必需的。
Dave Coffin’s dcraw
由Dave编写的一款出色的跨平台,开源软件,名为dcraw [3](发音为dee-see-raw)。 这是一种开源解决方案,用于读取数十种不同类型的RAW文件并输出易于读取的PPM或TIFF文件。 许多开源图像编辑套件都将此程序作为自己的RAW读取例程进行合并。 它可以从数百种相机模型中读取文件,并执行许多标准处理步骤以获取RAW文件并生成吸引人的输出。
dcraw仅是一个命令行程序,并且仅作为C源文件正式发布(尽管可以在网上找到预编译的可执行文件,甚至某些MATLAB MEX实现的版本)。 这使其真正跨平台,因为它可以针对任何系统进行编译。 它功能强大,功能全面,编码巧妙,紧凑,而且记录不良(阅读:1万行,约50条注释)。 尽管如此,本文档中的工作流程很大程度上取决于dcraw中的工作。
dcraw功能
dcraw从命令行运行,并接受许多可选参数以及要操作的原始文件。要获得完整的描述,请参见它的类unix手册[2]。在某种程度上,它提供了以下处理选项:
- 白平衡增益
- 输出图像色彩
- 去马赛克算法
- gamma校正
- 亮度控制
- 8bit或者16bit图像输出
- 图像旋转
在本教程中,我们使用了使dcraw访问原始文件中的图像信息但不以任何有意义的方式处理它的选项,以便我们自己进行处理。由Guillermo Luijk[9]提供的关于dcraw的全部能力的有用和信息丰富的教程。
4 RAW到MATLAB教程
4.1将CFA图像读入matlab
4.2 Linearizing
二维数组原始图像还不是线性图像。相机可能会对传感器数据进行非线性变换以进行存储(例如尼康相机)。如果是这样,DNG元数据将在meta info.SubIFDs {1} .LinearizationTable下包含一个表。您将需要通过此查找表将原始数组的值映射到完整的10-14位值。如果此标签为空(对于佳能相机而言),则无需担心此步骤。如果您使用dcraw方法,则'-4'选项将已经应用了线性化表,因此无需执行此步骤。
即使没有非线性压缩要反转,原始图像仍可能具有偏移和任意缩放。找到如下所示的黑色电平值和饱和电平值,并对图像的像素进行精细转换,使其线性并归一化为[0,1]范围。同样,由于传感器噪声,阵列中可能存在高于理论最大值或低于黑电平的值。这些需要被裁剪,如下所示。
4.3 White Balancing
现在,我们将CFA中的每个颜色通道按适当的比例缩放以对图像进行白平衡。 由于只有三种颜色的比例很重要2,因此我们可以将一个通道的乘数设置为1; 通常对绿色像素执行此操作。 您可以将其他两个白平衡倍增器设置为所需的任何值(例如,原始RAW文件的Exif信息可能包含不同标准光源的标准倍增器值),但是此处我们使用拍摄时相机计算出的倍数 。
找到值后,将图像中的每个红色位置像素乘以红色乘数,将每个蓝色位置像素乘以蓝色乘数。 这可以通过用这些标量的掩码进行点乘来完成,可以通过类似于以下函数轻松创建。
4.4 Demosaicing
应用您最喜欢的demosaicing算法(或MATLAB的内置算法)来生成熟悉的3层RGB图像变量。注意,内置的demosaic()函数需要uint8或uint16输入。要获得一个有意义的整数图像,缩放整个图像,使其最大值为65535。然后在剩下的过程中将其缩小到0-1。
4.5 Color Space Conversion
当前的RGB图像可通过标准MATLAB显示功能查看。 但是,其像素将没有操作系统期望的正确RGB空间中的坐标。 如第2节所述,任何给定像素的RGB值(代表由相机传感器定义的颜色基准的矢量)都必须转换为监视器期望的某些颜色基准。 这是通过线性变换完成的,因此我们将需要对每个像素应用3x3矩阵变换。
可能难以找到要应用的正确矩阵。 dcraw本身使用矩阵(从Adobe中收集),这些矩阵从相机的色彩空间转换为XYZ色彩空间,这是一种常见的标准。 然后,可以应用从XYZ到所需输出空间(例如sRGB)的转换。 更好的是,这两个转换可以先组合然后应用一次。
但是,更为复杂的是,这些矩阵通常在sRGB到XYZ和XYZ到相机颜色的方向上定义。 因此,所需的矩阵必须构造如下:
dcraw中发现的另一个必要技巧是,首先对sRGB-to-Cam矩阵的行进行规范化,以使每一行的总和为1。尽管它看起来是任意的,而且有些特殊,但我们可以看到,如果 我们考虑将相机色彩空间中的白色像素转换为输出空间中的白色像素时会发生的情况:我们可以说它仍应为白色,因为我们已经应用了白平衡倍增器。 由于两个空间中的白色均由RGB坐标1 1 1 T表示,因此我们必须对行进行归一化,以便
用于输出到xyz颜色空间转换的矩阵可以在Bruce Lindbloom的综合网站[8]中找到。为了方便起见,这里给出了最常用的从sRGB空间到XYZ空间的矩阵。
function corrected = apply_cmatrix(im,cmatrix)
% 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);
end
4.6 Brightness and Gamma Correction
现在,我们有一个16位的RGB图像,该图像已经过颜色校正,并存在于正确的颜色空间中以进行显示。但是,它仍然是线性图像,其值与感测到的值有关,可能不在适合显示的范围内。我们可以通过简单地缩放图像(添加一个常数只会使其看起来呈灰色)或更复杂的方式(例如应用非线性变换)来使图像变亮。在这里,我们将同时进行这两种操作,但是请注意,本小节的步骤非常主观,因此,我们只是在调整图像以使其看起来不错。从某种意义上说,它已经是“正确的”,但不一定是“漂亮的”。如果您对本教程后的输出图像不满意,这是第一个查看的地方。
作为一种非常简单的增亮措施,我们可以找到图像的平均亮度,然后对其进行缩放,以使平均亮度成为更合理的值。在接下来的几行中,我们(相当任意)对图像进行缩放,以使平均亮度最大为1/4。对于照相倾斜而言,这等效于缩放图像,以便有两个光亮区域细节停止。这不是很聪明,但是代码很简单。
图像仍然是线性的,几乎可以肯定不是最好的显示方式(暗区会显得太暗,等等)。 我们将对此图像应用“伽玛校正”幂函数,作为解决此问题的简单方法。 尽管官方的sRGB压缩实际上使用的是γ= 1 2.4的幂函数,并且最小的线性脚趾区域的最小值,但这通常可以通过以下简单的γ= 1 2.2压缩来近似。 请注意,通常,您只想将这样的功能应用于已缩放到[0,1]范围内的图像,我们已确保输入为。
代码
function colormask= wbmask(m,n,wbmults,align)
colormask=wbmults(2)*ones(m,n);
switch align
case 'rggb'
colormask(1:2:end,1:2:end)=wbmults(1);
colormask(2:2:end,2:2:end)=wbmults(3);
end
end
function corrected=apply_cmatrix(im,cmatrix)
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);
end
%%
clc
clear
%通过dcraw将.nef转换成tiff格式
I=imread('nikon_testim.tiff');
figure;imshow(I);title("原图");
raw=double(I);
%线性化
black=0;
saturation=16383;
lin_bayer=(raw-black)/(saturation-black);
lin_bayer=max(0,min(lin_bayer,1));
figure;imshow(lin_bayer);title("线性化后图像");
%白平衡
wb_multipliers=[1.842952 1.000000 1.133930];
[m,n,i]=size(raw);
mask=wbmask(m,n,wb_multipliers,'rggb');
balance_bayer=lin_bayer.*mask;
figure;imshow(balance_bayer);title("白平衡后图像");
%去马赛克图像
temp=uint16(balance_bayer/max(balance_bayer(:))*2^16);
lin_rgb=double(demosaic(temp,'rggb'))/2^16;
figure;imshow(lin_rgb);title('去马赛克后图像');
%颜色转换
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
figure;imshow(lin_srgb);title("颜色空间转换后图像");
%Brightness and Gamma Correction//可以做local TMO
grayim=rgb2gray(lin_srgb);
grayscale=0.25/mean(grayim(:));
bright_srgb=min(1,lin_srgb*grayscale);
figure;imshow(bright_srgb);title("亮度提升图像");
%gamma显示
result=bright_srgb.^(1/2.2);
figure;imshow(result);title("result");
结果:
上一篇: Java内存溢出OOM使用Mat分析