Illuminant correction

Illuminant correction is often performed by calculating a 3x3 matrix that maps the XYZ values for surfaces under one light into the XYZ values under another light.

Here, we create a scene that consists of patches illuminated by light A these patches can be created by randomly sampling surfaces reflectances the selection of surfaces will matter

We then create another scene that consists of the same patches illuminated by light B

We calculate a general 3x3 matrix that maps the XYZ values for surfaces under light A into the XYZ values of surfaces under light B. We then find a 3x3 diagonal matrix that maps the XYZ values for surfaces under light A into the XYZ values of surfaces under light B.

The general 3x3 matrix does a better job than the diagonal (as expected).

Some authors refer to the diagonal 3x3 as von Kries adaptation Note that a matrix that is diagonal in one coordinate frame (XYZ) will not generally be diagonal in another coordinate frame (e.g., LMS or RGB). Von Kries meant diagonal in LMS.

See also: RGB2XWFormat

Copyright ImagEval Consultants, LLC, 2012.

Contents

ieInit

Create a test scene (Natural 100)

% Set the illumination to D65
scene = sceneCreate('reflectance chart');
sceneD65 = sceneAdjustIlluminant(scene,'D65.mat');
sceneD65 = sceneSet(sceneD65,'name','Reflectance Chart D65');
ieAddObject(sceneD65); sceneWindow;

Solve for matrix relating the chart under two different lights

% This are the surfaces under a D65 light
xyz1 = sceneGet(sceneD65,'xyz');

% This is a nSample x 3 representation of the surfaces under D65
xyz1 = RGB2XWFormat(xyz1);

This are the surfaces under a Tungsten light

sceneT = sceneAdjustIlluminant(scene,'Tungsten.mat');
xyz2   = sceneGet(sceneT,'xyz');

% This is a nSample x 3 representation of the surfaces under Tungsten
xyz2 = RGB2XWFormat(xyz2);

% We are looking for a 3x3 matrix, L, that maps
%
%    xyz1 = xyz2 * L
%    L = inv(xyz2'*xyz2)*xyz2'*xyz1 = pinv(xyz2)*xyz1
%
% Or, we just use the \ operator from Matlab for which inv(A)*B is A\B
L = xyz2 \ xyz1;

% To solve with just a diagonal, do it one column at a time
D = zeros(3,3);
for ii=1:3
    D(ii,ii) = xyz2(:,ii) \ xyz1(:,ii);
end

Plot predicted versus actual

vcNewGraphWin; pred2 = xyz2*L; plot(xyz1(:),pred2(:),'o'); grid on
title('Full inear'); xlabel('Observed XYZ'); ylabel('Predicted XYZ')

vcNewGraphWin; pred2 = xyz2*D; plot(xyz1(:),pred2(:),'o'); grid on
title('Diagonal'); xlabel('Observed XYZ'); ylabel('Predicted XYZ')