Matlab解析DNG格式图像

DNG是一种很灵活的图像格式,是在Tiff基础上拓展来的,随着传感器技术、后期图像处理能力的增强,我们很多时候直接将ISP采集的图像数据直接Dump下来,不做任何处理,这就是RAW图,同时我们把处理RAW图需要必要信息存下来(Bayer Pattern、白平衡、噪声水平等),然后我们就可以进行后期处理了。总之,DNG是一种RAW图格式,其中除了数据段之外,有很多标签信息。

DNG格式不断迭代,目前DNG的格式标准是1.5.0(如果还发现有标签字段没有找到,那就说明格式已经更新了,核对下版本)

Adobe对DNG格式定义的文档:https://www.adobe.com/content/dam/acom/en/products/photoshop/pdfs/dng_spec_1.5.0.0.pdf

所参考的源码有两份:

第二份源码摘自博客:http://forum.xitek.com/thread-1677216-1-1-1.html

以上第一份源码比较简洁,没有使用DNG中提供的光源信息,可以处理大部分图,但是偶尔有些格式中光源信息不一样的,就会出现色彩一场。第二份源码按照格式Pipline处理出来,颜色基本正确(不过感觉还是比正常处理出来更加鲜艳一些),并且处理速度比较慢,对Matlab来说,我们就不苛求速度了。

另外,Adobe也有专门的RAW处理工具,如果已经安装了Adobe Photoshop,那么再安装这个插件就会增强对RAW图的处理,我们可以以Adobe的结果作为参考:https://helpx.adobe.com/cn/camera-raw/kb/camera-raw-plug-in-installer.html

如果是从相机采集的图,那么可以用过Adobe DNG Converter来对图像转换到DNG格式,这个工具和上面的工具一样,也是通过插件的方式进行安装。

关于DNG解码,上面引用的那片博客已经把DNG处理的大致流程讲的比较清楚了,这里就简单概括下。

1. RAW数据处理:线性校准、黑色补偿、归一化,把数据归一化到0-1

2. 解码赛克:把CFA pattern插值出三通道的RGB数据

3. 激活区域剪裁:RAW存储的是整个CMOS的数据,有用的部分可能比CMOS稍微小一点点

4. 色彩转换:这部分处理比较负载,DNG中存储有参考色温,转换矩阵需要根据白点和白平衡色温内插计算出来

5. HSV校准:如果DNG存在HSV映射表,就需要做HSV校正。把XYZ(D50)转换到ProPhoto RGB,再把RGB转换到HSV,然后计算索引号,查表修正,之后根据需要再转换到ProPhoto RGB线性空间,或者XYZ(D50)空间

6. 曝光校准:大部分相机都为保护RAW数据高光不溢出,就在曝光时减少一点儿曝光量,在RAW解码时补回来。根据曝光量EV,可以直接换算 Photo = Photo*2EV

7. HSV色彩增强:与HSV校准的算法相同,作用是色彩增强。

8. 影调曲线:影调曲线要早ProPhoto RGB的线性空间进行转换,不同相机的曲线大致相同。

9. Gamma校准:人眼视觉是在Gamma空间的,所以按照Adobe sRGB的Gamma对数据进行校准,这个是Adobe RGB标准规定好的,照做即可

10. Opcode List Processing: 这一步是处理坏点、噪声、镜头校正等等

 

% Rafael Villamor Lora (Using Rob Sumner 'RAW Guide')
% January 24, 2019 (Last modified: 02/14/19)
% PROCESSING RAW (DNG) IMAGES IN MATLAB
%
% This fucntion follows Rob Sumner's "Processing RAW Images in MATLAB"
% guide. I'm simply copying and pasting his explanations. Unless otherwise
% noted, all the credits belong to Rob Sumner (2014)
% I HIGHLY recommend to read the full documment:
% http://www.rcsumner.net/raw_guide/RAWguide.pdf

% RAW FILES
%{
A RAW photo files contain the raw sensor data from a digital camera;  while
it can be quite scientifically useful, raw data must generally be processed 
before it can be  displayed. In order to use a DSLR (Digital Single Lens
Reflex camera) as a scientific camera it is vital to know the entire 
processing chain that was applied to an  image after being captured.  If 
possible, the best image to deal with is  the sensor data straight from the
camera, the raw data.
%}

% HOW TO READ RAW FILES IN MATLAB
%{
'RAW� is a class of computer files which typically contain an uncompressed 
image containing the sensor pixel values as well as a large amount of meta-
information about the image generated by the camera (the Exif data). RAW 
files themselves come in many proprietary file formats (Nikon�s .NEF, 
Canon�s .CR2, etc) and at least one common open format, .DNG, which stands
for Digital Negative.  The latter indicates how these files  are  supposed  
to  be  thought  of  by  digital  photographers:  the  master  originals,
repositories  of  all  the captured information of the scene.

Many proprietary file formats can be converted to DNG using Adobe Digital
Negative Converter. NOTE: Make sure you export your images as uncompressed:
i.e. Open Abobe DNG Converter > Change Preferences... > Compatibility
> Custom... > Linear (unchecked),Uncompressed (checked) > OK > OK
%}

% HOW TO PROCESS RAW FILES
%{
The process coded here is a first-order approximation of the steps that all 
cameras do to take an image and produce a viewable output. It starts with 
the raw sensor data and implements:

1. Linearization
2. White Balancing
3. Demosaicing
4. Color Space Correction
5. Brightness and Constrast Adjustment for Display
%}

%%%%%%%%%%%%%%

function [lin_srgb, lin_rgb, balanced_bayer, lin_bayer, raw, camSettings] = dng2rgb(imagename)
% OUTPUTS
% lin_srgb       [nxmx3 double][0-1] = 16-bit, RGB image that has color corrected and exists in the right color space for display
% lin_rgb        [nxmx3 double][0-1] = RGB image from balanced_bayer using demosaic()                                                
% balanced_bayer [nxm double]  [0-1] = White Balanced-linearized raw data (Bayer array)                                              
% lin_bayer      [nxm double]  [0-1] = Linearized raw data (Bayer array) with black level and saturation level corrected           
% raw            [nxm double]  [0-1] = Raw data (Bayer array) from the camera's sensor
% camSettings    [structure]         = Contains camera settings used for convertion from RAW -> lin_srgb

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                     READING THE CFA IMAGE INTO MATLAB                   %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%{
The following code will read the DNG file into a MATLAB array called
raw, as well as creating a structure of metadata about the image, meta-info
The code also uses information from the metadata to crop the image to only 
the meaningful area.
%}
warning off MATLAB:tifflib:TIFFReadDirectory:libraryWarning
t         = Tiff(imagename,'r');
offsets   = getTag(t,'SubIFD');
setSubDirectory(t,offsets(1));
raw       = read(t);
close(t);
meta_info = imfinfo(imagename);
% Crop to only valid pixels (http://www.rcsumner.net/raw_guide/RAWguide.pdf)
x_origin  = meta_info.SubIFDs{1}.ActiveArea(2)+1;    % +1 due to MATLAB indexing
width     = meta_info.SubIFDs{1}.DefaultCropSize(1);
y_origin  = meta_info.SubIFDs{1}.ActiveArea(1)+1;
height    = meta_info.SubIFDs{1}.DefaultCropSize(2);
RAW       = raw(y_origin:y_origin+height-1,x_origin:x_origin+width-1);
raw       = double(RAW);
% Save settings
camSettings.x_origin = x_origin;
camSettings.width    = width;
camSettings.y_origin = y_origin;
camSettings.height   = height;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                LINEARIZING                              %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%meta_info = imfinfo(imagename);
%{
The 2-D array raw is not yet a linear image.  It is possible that the 
camera applied a non-linear transformation to the sensor data for storage 
purposes (e.g., Nikon cameras).  If so, the DNG metadata will contain A
table under meta info.SubIFDs{1}.LinearizationTable. You will need to map 
the values of the raw array through this look-up table to the full 10-14 
bit values.  If this tag is empty (as for Canon cameras), you do not need 
to worry about this step.

Even if there is no non-linear compression to invert, the raw image might 
still have an offset and arbitrary scaling.  Find the black level value and
saturation level value as below and do an affine transformation to the
pixels of the image to make it linear and normalized to the range [0,1].
Also, because of sensor noise, it is possible that there exist values in 
the array which are above the theoretical maximum value or below the
black level.  These need to be clipped, as follows.

Note:  There may exist a different black level or saturation level for each
of the four Bayer color channels. The code below assumes they are the same
and uses just one.  You may choose to be more precise.
%}
%If the values are stored non-linearly, undo that mapping
if isfield(meta_info.SubIFDs{1},'LinearizationTable')
    warning('RVL: Stored RAW-values are non-linear')
    ltab = meta_info.SubIFDs{1}.LinearizationTable;
    raw  = ltab(raw+1);
    % Save settings
    camSettings.ltab = ltab;
end
% The black level and saturation level values are stored in the DNG 
% metadata and can be accessed as shown.
black      = meta_info.SubIFDs{1}.BlackLevel(1);
saturation = meta_info.SubIFDs{1}.WhiteLevel;
lin_bayer  = (raw - black) / (saturation - black);
lin_bayer  = max(0, min(lin_bayer, 1));  % Always keep image clipped b/w 0-1
% Save settings
camSettings.black      = black;
camSettings.saturation = saturation;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                              WHITE BALANCING                            %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%{
Now we scale each color channel in the CFA by an appropriate amount to
white balance the image.  Since only the ratio of the three colors matters,
we can arbitrarily set one channel's multiplier to 1; this is usually done
for the green pixels.  You may set the other two white balance multipliers 
to any value you want (e.g., the Exif information for the original RAW file
may contain standard multiplier values for different standard illuminants),
but here we use the multipliers the camera calculated at the time of 
shooting. Once the values are found, multiply every red-location pixel in 
the image by the red multiplier and every blue-location  pixel  by  the  
blue  multiplier.   This  can  be  done  by  dot-multiplication  with  a 
mask  of  these scalars, which can be easily created by a function similar 
to the following.
%}
% An array of the inverses of the multiplier values, for [R G B], is found 
% in meta_info.AsShotNeutral. Thus we invert the values and then rescale 
% them all so that the green multiplier is 1.
wb_multipliers = (meta_info.AsShotNeutral) .^ -1;
wb_multipliers = wb_multipliers / wb_multipliers(2);
mask           = wbmask(size(lin_bayer,1),size(lin_bayer,2),wb_multipliers,'rggb');
balanced_bayer = lin_bayer .* mask;
% Save settings
camSettings.wb_multipliers = wb_multipliers;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                               DEMOSAICING                               %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%{
Apply your favorite demosaicing algorithm (or MATLAB�s built-in one) to 
generate the familiar 3-layer RGB image variable.  Note that the built-in
demosaic() function requires a uint8 or uint16 input.  To get a meaningful
integer image, scale the entire image so that the max value is 65535. Then
scale back to 0-1 for the rest of the process.

Originally, the code looked like this:

temp    = uint16(balanced_bayer / max(balanced_bayer(:)) * (2^16 - 1)); % RVL: I'm not sure why we need  / max(balanced_bayer(:)
if max(balanced_bayer(:)) ~= 1
    warning('RVL: Weird normalization during demosaicing')
    warning(['RVL: Normalization factor: ',num2str(max(balanced_bayer(:)))])
end
lin_rgb = double(demosaic(temp,'rggb')) / (2^16 - 1);
% Save settings
camSettings.max_balanced_layer = max(balanced_bayer(:));

I'm not sure why we need  the division '/ max(balanced_bayer(:)'. So I
eliminated it*. Another altervative would be to keep it, and then multiply
each layer of lin_rgb by 'max(balanced_bayer(:))'

*I compared both scenarios (i.e. with/without division), and it seems that
as long the image is not infra-/over-exposed, the division doesn't make any
difference.
%}

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

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                          COLOR SPACE CONVERSION                         %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%{
The current RGB image is viewable with the standard MATLAB display
functions.  However, its pixels will not have coordinates in the correct 
RGB space that is expected by the operating system.

Any given pixel�s RGB values, which represent a vector in the color basis 
defined by the camera�s sensors, must be converted to some color basis 
which the monitor expects.  This is done by a linear transfor-mation, so we 
will need to apply a 3x3 matrix transformation to each of the pixels.

The correct matrix to apply can be difficult to find. Some software
packages use matrices (gleaned from Adobe) which transform from the 
camera�s color space to the XYZ color space, a common standard.  Then the 
transformation from XYZ to the desired output space, e.g., sRGB, can be 
applied.  Better yet, these two transformations can be combined first and 
then applied once. NOTE: As an added complication, however, these matrices 
typically are defined in the direction of sRGB-to-XYZ and XYZ-to-camera 
color basis. 

One other necessary trick is to first normalize the rows of the sRGB-to-Cam
matrix so that each row sums to 1.  Though it may seem arbitrary and
somewhat ad hoc, we can see that this is necessary if we consider what will
happen when a white pixel in the camera color space is transformed to a
white pixel in the output space:  we can argue that it should still be 
white because we have already applied white  balance  multipliers  in  
order  to  make  it  so.   Since  white  in  both  spaces  is  represented  by  the  RGB
coordinates [ 1    1    1]^T.

The matrices used for the output-to-XYZ colorspace transformations can be 
found at Bruce Lindbloom�s comprehensive website. For convenience, the most
commonly desired one, the matrix from sRGB space to XYZ space, is given 
here

rgb2xyz = [0.4124564    0.3575761    0.1804375
           0.2126729    0.7151522    0.0721750
           0.0193339    0.1191920    0.9503041];
%}
% You  can  find  the  entries  of  the  XYZ-to-camera  matrix  in  the
% meta_info.ColorMatrix2 array. NOTE: These entries fill the transformation
% matrix in a C row-wise manner, not MATLAB column-wise.
rgb2xyz  = [0.4124564    0.3575761    0.1804375
            0.2126729    0.7151522    0.0721750
            0.0193339    0.1191920    0.9503041];
xyz2cam  = reshape(meta_info.ColorMatrix2,3,3)';  % Transpose is due to row-wise definition of meta_info.ColorMatrix2
rgb2cam  = xyz2cam * rgb2xyz;
rgb2cam  = rgb2cam ./ repmat(sum(rgb2cam,2),1,3); % Normalize rows to 1
cam2rgb  = rgb2cam ^ -1;
lin_srgb = apply_cmatrix(lin_rgb, cam2rgb);
lin_srgb = max(0,min(lin_srgb,1)); % Always keep image clipped b/w 0-1
% Save settings
camSettings.rgb2xyz = rgb2xyz;
camSettings.xyz2cam = xyz2cam;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                      BRIGHTNESS AND GAMMA CORRECTION                    %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%{
We  now  have  a  16-bit,  RGB  image  that  has  been  color  corrected  
and  exists  in  the  right  color  space  for display.  However, it is 
still a linear image with values relating to what was sensed, which may not
be in a range appropriate for being displayed.  We can brighten the image 
by simply scaling it (adding a constant would just make it look gray), or 
something more complicated, e.g., applying a non-linear transformation.
Here we will do both, but be aware that the steps of this subsection are
highly subjective and at this point we are just tweaking the image so it 
looks good.  It is already �correct� in some sense, but not necessarily
�pretty.� 

As a extremely simple brightening measure, we can find the mean luminance 
of the image and then scale it so that the mean luminance is some more 
reasonable value.  In the following lines, we (fairly arbitrarily) scale
the image so that the mean luminance is 1/4 the maximum.  For the 
photographically inclined, this is equivalent to scaling the image so that
there are two stops of bright area detail.  This is not extremely clever, 
but the code is simple.

grayim      = rgb2gray(lin_srgb);
grayscale   = 0.25/mean(grayim(:));
bright_srgb = min(1,lin_srgb * grayscale);
nl_srgb     = bright_srgb.^(1/2.2);

The image is still linear, which will almost certaintly not be the best for
display (dark areas will appear too dark, etc).  We will apply a �gamma 
correction� power function to this image as a simple way to fix this.
Though the official sRGB compression actually uses a power function with
? = 1/2.4 and a small linear toe region for the lowest values, this is 
often approximated by the following simple ? = 1/2.2 compression.  Note
that in general you only want to apply such a function to an image that has
been scaled to be in the range [0,1], which we have made sure our input is.

nl_srgb = bright_srgb.�(1/2.2);

Congratulations, you now have a color-corrected, displayable RGB image.  It 
is real valued, ranges from 0-1, and thus is ready for direct display by
imshow()
%}


end


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(1:2:end,2: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

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

 

 

如果只是想简单处理,不需要很精确的颜色,那么可以follow:

[embeddoc url=”https://pcv.oss-cn-shanghai.aliyuncs.com/wp-content/upload/2019/10/RAWguide.pdf” download=”all” viewer=”google”]

大致梳理了下,希望能有帮助。

发表评论