最新のリリースでは、このページがまだ翻訳されていません。 このページの最新版は英語でご覧になれます。
私たちの目は、異なるライティング条件下で白い物を判断する能力に長けています。しかしデジタル カメラの場合は、何かしらの調整を行わなければ、現実とかけ離れた色かぶりの著しいイメージをキャプチャーすることになりかねません。自動ホワイト バランス (AWB) アルゴリズムは、最小限のユーザー入力で結果のイメージが私たちの目で見たものに近くなるように周囲光を補正します。では、どの AWB アルゴリズムを使用すればよいのでしょうか。この例では、自動ホワイト バランスの背後の処理を説明し、アルゴリズムの比較方法と最適な選択方法を示します。
自動ホワイト バランスは次の 2 ステップで行われます。
手順 1: シーン照明の推定。
手順 2: イメージのカラー バランスの補正。
最も難しく、そして最適化に注力することになるのがステップ 1、周辺光の推定です。周囲光さえ分かれば、イメージの色の補正 (ステップ 2) は簡単で固定化された処理です。
ここでは、光源推定の 3 つのアルゴリズムの品質を、グラウンド トゥルース シーン照明と比較することで判断します。
ホワイト パッチ レチネックス [1]
グレー ワールド [2]
チェンの主成分分析 (PCA) 法 [3]
自動ホワイト バランス アルゴリズムは通常、イメージが圧縮されてメモリ カードに保存される前の生のイメージ データに対して適用されます。foosballraw.tiff
は、黒レベルを補正して強度を 16 ビット毎ピクセルにスケーリングした、生のセンサー データを含むイメージ ファイルです。このイメージにはカメラによるホワイト バランスは行われておらず、デモザイク処理、ノイズ除去、クロマチック色収差補正、トーン補正、ガンマ補正、およびイメージがメモリ カードに保存される前に行われるその他の処理も行われていません。
A = imread('foosballraw.tiff');
イメージ A
には線形 RGB 値が含まれます。多くの光源推定アルゴリズムはイメージング センサーの応答とピクセル強度の間に線形関係があることを前提としています。ただし、ディスプレイ デバイスに非線形性があるため、JPEG ファイルなど表示用イメージ ファイルにはガンマ補正が含まれます。ガンマ補正がないと、イメージはコンピューター画面上で非常に薄暗く表示されます。ガンマ補正されたイメージを使用して作業している場合は、必ず関数 rgb2lin
で線形化します。これは、foosballraw.tiff
を指定した場合には該当しません。この手順をスキップします。
デジタル カメラでは、色覚をシミュレーションするために、イメージング センサーにカラー フィルター配列を重ねて使用します。これにより、各ピクセルは赤、緑、または青に敏感に反応します。各ピクセルの失われた色情報を回復するために、内挿を行わなければなりません。この写真を撮影したカメラ (Canon EOS 30D) で使用されるベイヤー パターンは RGGB です。
A = demosaic(A,'rggb');
線形イメージをそのまま表示しようとした場合、非常に薄暗く表示されます。これはディスプレイ デバイスの非線形特性によるものです。そのため、表示を目的として、sRGB 色空間を使用するためにイメージのガンマ補正を行います。
A_sRGB = lin2rgb(A);
最低限の処理が施された元のイメージとガンマ補正後のイメージの表示
warning('off','images:initSize:adjustingMag') montage({A,A_sRGB}) title('Original, minimally processed image before and after gamma correction')
シーン内に ColorChecker チャートがあります。このチャートは、既知の分光反射率をもつ 24 のニュートラル パッチおよびカラー パッチで構成されます。下の列にある 6 つのニュートラル (無彩色) パッチを使用して、アルゴリズムの比較対象となるグラウンド トゥルース シーン照明を推定します。しかし、アルゴリズムをテストする場合は、アルゴリズムに不当に有利になることを避けるため、チャートを除外しなければなりません。現実の場面には ColorChecker チャートは存在しないのです。
ColorChecker チャートの位置を指定します。この例では四辺形境界の座標を使用します (既定の設定)。多角形座標を対話形式で選択する場合、select_polygon
の値を true
に変更します。
select_polygon = false; if select_polygon % Use roipoly to create a mask from a polygon drawn manually. % Click to add vertices, then right-click and select "Create Mask" to return. imshow(A_sRGB) title('Draw a polygon around the chart') mask_chart = roipoly; else % Use the provided coordinates of the bounding rectangle. c = [930 1280 1316 953]; r = [1877 1890 1382 1370]; mask_chart = roipoly(A_sRGB,r,c); end
マスクをわずかに膨張して、チャートに属するピクセルを確実に除外します。
mask_chart = imdilate(mask_chart,ones(7));
ホワイト バランスを行うために、チャートの下の列にある 6 つのニュートラル パッチだけを使用します。これらのニュートラル パッチは可視スペクトル全体にわたって等しく光を反射します。それらはシーン照明を反射します。光源のグラウンド トゥルースは、ニュートラル パッチの平均色として計算されます。これには露光不足および露光過多のピクセルは含まれません。
各ニュートラル パッチの中心を指定します。この例では各パッチの中心座標の推定値が示されます (既定の設定)。ROI の中心を対話形式で選択する場合は、estimate_roi_centers
の値を true
に変更します。
estimate_roi_centers = false; if estimate_roi_centers % Zoom in and click the center of each of the 6 neutral patches. xlim([1350 1930]) ylim([900 1350]) title('Click the center of each of the 6 neutral patches') [x,y] = ginput(6); else % Use the provided estimate of ROI center coordinates. x = [1424 1514 1598 1676 1757 1835]; y = [1268 1250 1247 1250 1235 1229]; end
連続する 2 つのパッチの中心を隔てる距離の 80% を正方形の辺だと考えることで、各パッチの中心座標から正方形の領域を導出します。
x = round(x); y = round(y); r = mean(diff(x)) / 2 * 0.80; r = floor(r);
ニュートラル パッチを覆うバイナリ マスクを作成します。
mask = false(size(A,1), size(A,2)); for k = 1:6 mask(y(k)-r:y(k)+r,x(k)-r:x(k)+r) = true; end
マスクを収縮して、色収差が発生しやすいパッチの境界上または外側のピクセルが含まれないようにします。これらのピクセルの色はグラウンド トゥルースの測定値を歪める可能性があります。
mask_eroded = imerode(mask, strel('disk',5));
入力イメージの飽和 RGB 値を同定します。これらの値も測定値を歪めることがあるため、グラウンド トゥルースの計算から除外されるべきです。
mask_clipped = (A == intmax(class(A))) | (A == intmin(class(A))); mask_clipped = mask_clipped(:,:,1) | mask_clipped(:,:,2) | mask_clipped(:,:,3);
ニュートラル パッチに対応するマスクから、これらのクリップされたピクセルを除外します。
mask_patches = mask_eroded & ~mask_clipped;
選択されたピクセルを可視化します。強調表示されたピクセルはすべて、ニュートラル パッチ内に収まるはずです。そうでない場合、パッチの中心を再度クリックして、前述のステップを繰り返します。
A_patches = imoverlay(A_sRGB,mask_patches);
imshow(A_patches)
title('The selected pixels are highlighted in yellow')
ニュートラル パッチの赤、緑、青の値を取得します。
patches_R = A(:,:,1); patches_G = A(:,:,2); patches_B = A(:,:,3); patches_R = patches_R(mask_patches); patches_G = patches_G(mask_patches); patches_B = patches_B(mask_patches);
単純化のため、およびほとんどの光源推定アルゴリズムが浮動小数点で動作するため、平均を計算する前にパッチの RGB 値を倍精度に、スケールを [0 1] に変換します。
patches_R = im2double(patches_R); patches_G = im2double(patches_G); patches_B = im2double(patches_B);
RGB 光源のグラウンド トゥルースを、ニュートラル パッチの平均 RGB 値として計算します。
illuminant_groundtruth = [mean(patches_R) mean(patches_G) mean(patches_B)];
推定された光源をグラウンド トゥルースと比較するために、2 色間の角度誤差を計算します。角度誤差の概念をより理解するために、任意の光源と先ほど測定したグラウンド トゥルースについて次の可視化を考えます。それぞれの光源は RGB 空間のベクトルを表します。
illuminant = [0.066 0.1262 0.0691]; plot3([0 1],[0 1],[0,1],'LineStyle',':','Color','k') hold on plot3(... [0 illuminant_groundtruth(1)/norm(illuminant_groundtruth)], ... % Red [0 illuminant_groundtruth(2)/norm(illuminant_groundtruth)], ... % Green [0 illuminant_groundtruth(3)/norm(illuminant_groundtruth)], ... % Blue 'Marker','.', 'MarkerSize',10) hold on plot3( ... [0 illuminant(1)/norm(illuminant)], ... % Red [0 illuminant(2)/norm(illuminant)], ... % Green [0 illuminant(3)/norm(illuminant)], ... % Blue 'Marker','.', 'MarkerSize',10) xlabel('R') ylabel('G') zlabel('B') title('Illuminants in RGB space') xlim([0 1]) ylim([0 1]) zlim([0 1]) view(28, 36) legend('achromatic line', 'ground truth illuminant', 'estimated illuminant') grid on axis equal
推定された光源の値そのものは方向ほど重要ではありません。なぜなら、イメージのホワイト バランスに使用されるのは光源の方向であるためです。理想的には、推定された光源はグラウンド トゥルースの光源と一致するべきです。推定された光源とグラウンド トゥルースの間の角度誤差は、2 つのベクトルから構成される角度 (度単位) です。角度誤差が小さいほど、推定の精度が上がります。推定の品質は、グラウンド トゥルースに対する角度誤差により評価します。
光源推定のホワイト パッチ レチネックス法 [1] は、シーンに明るいパッチが含まれることを前提とします。このパッチは、そのシーン照明の色である、各色バンドの最大光を反射します。
関数 illumwhite
はホワイト パッチ レチネックス法を実装します。この関数では露出過多のピクセルを考慮に含めないように、一部の最も明るいピクセルを計算から除外することができます。
まず、Color Checker チャートを除くすべてのイメージのピクセルを使用してシーンの照明を推定します。これは、上位 0% を除外するよう指定することで行います。'Mask'
の名前と値のペアは、どのピクセルを計算に使用するか、この場合はどのピクセルが ColorChecker チャートに属さないかを指定するために使用します。
illuminant_wp1 = illumwhite(A, 0, 'Mask', ~mask_chart);
ホワイト パッチ レチネックスで推定した光源の角度誤差を計算します。
err_wp1 = colorangle(illuminant_wp1, illuminant_groundtruth);
disp(['Angular error for White Patch with percentile=0: ' num2str(err_wp1)])
Angular error for White Patch with percentile=0: 16.5163
この推定された光源を使用してイメージのホワイト バランス (AWB のステップ 2) を行うには、関数 chromadapt
を使用します。これは既定の設定では、ブラッドフォードの錐体応答モデルで色をスケールします。線形 RGB 色値を使用するように必ず指定してください。
B_wp1 = chromadapt(A, illuminant_wp1, 'ColorSpace', 'linear-rgb');
ガンマ補正されたホワイト バランス済みイメージの表示
B_wp1_sRGB = lin2rgb(B_wp1);
figure
imshow(B_wp1_sRGB)
title('White balanced image using White Patch Retinex with percentile=0')
第 2 に、選択した RGB の最大値は露光過多のピクセルに敏感であるため、最も明るいピクセルを一定の割合で計算から除外することで、ホワイト パッチ レチネックス アルゴリズムをさらにロバストにできます。これは関数 illumwhite の百分位パラメーターを通じて実現できます。百分位の値に 1 を選択します (既定値は 1 です)。
illuminant_wp2 = illumwhite(A, 1, 'Mask', ~mask_chart);
さらにロバストなバージョンのホワイト パッチ レチネックスで推定した光源の角度誤差を計算します。
err_wp2 = colorangle(illuminant_wp2, illuminant_groundtruth);
disp(['Angular error for White Patch with percentile=1: ' num2str(err_wp2)])
Angular error for White Patch with percentile=1: 5.0323
新しい光源でガンマ補正されたホワイト バランス済みイメージの表示
B_wp2 = chromadapt(A, illuminant_wp2, 'ColorSpace', 'linear-rgb'); B_wp2_sRGB = lin2rgb(B_wp2); imshow(B_wp2_sRGB) title('White balanced image using White Patch Retinex with percentile=1')
グレー ワールド [2] は間違いなく最もよく知られた光源推定法です。ワールドの平均色がグレー、つまり無彩色になると仮定します。したがって、シーンの光源をイメージの平均 RGB 値として計算します。
関数 illumgray
はグレー ワールド アルゴリズムの実装に加え、次を行います。光源の推定に干渉する可能性がある最も暗いピクセルと最も明るいピクセルを計算から除外することができ、アルゴリズムがよりロバストになります。
まず、Color Checker チャートに対応するピクセルを除いたすべてのイメージのピクセルを使用して照明を推定します。関数 illumgray
は除外する上位と下位の値 (輝度の順) を百分位で指定するパラメーターを出力します。ここで、百分位に [0 0] を指定します。
illuminant_gw1 = illumgray(A, 0, 'Mask', ~mask_chart);
ホワイト パッチ レチネックスと同様、推定された光源とグラウンド トゥルースの間の角度誤差を計算して両者を比較します。
err_gw1 = colorangle(illuminant_gw1, illuminant_groundtruth);
disp(['Angular error for Gray World with percentiles=[0 0]: ' num2str(err_gw1)])
Angular error for Gray World with percentiles=[0 0]: 5.063
推定された光源を使用したホワイト バランス イメージへの色彩適応の適用
B_gw1 = chromadapt(A, illuminant_gw1, 'ColorSpace', 'linear-rgb');
ガンマ補正されたホワイト バランス済みイメージの表示
B_gw1_sRGB = lin2rgb(B_gw1);
imshow(B_gw1_sRGB)
title('White balanced image using Gray World with percentiles=[0 0]')
第 2 に、露光不足および露光過多のピクセルはイメージの平均 RGB 値を光源とする推定に悪影響を及ぼす可能性があるため、上位と下位の 1% のピクセルを除外します。(percentiles
の既定値は [1 1] です。)
illuminant_gw2 = illumgray(A, 1, 'Mask', ~mask_chart);
グレー ワールドで推定した 2 つ目の光源の角度誤差を計算します。
err_gw2 = colorangle(illuminant_gw2, illuminant_groundtruth);
disp(['Angular error for Gray World with percentiles=[1 1]: ' num2str(err_gw2)])
Angular error for Gray World with percentiles=[1 1]: 5.1314
新しい光源でガンマ補正されたホワイト バランス済みイメージを表示します。
B_gw2 = chromadapt(A, illuminant_gw2, 'ColorSpace', 'linear-rgb'); B_gw2_sRGB = lin2rgb(B_gw2); imshow(B_gw2_sRGB) title('White balanced image using Gray World with percentiles=[1 1]')
チェンの光源推定法 [3] は、グレー エッジ [4] のような空間ドメイン法から発想を得たもので、イメージの勾配は無彩色であると仮定します。イメージ ブロックをシャッフルして強い勾配を人為的に導入することでグレー エッジは改善できることを示し、最も強い勾配が光源の方向を表すと結論付けています。彼らの手法は、平均イメージ色の方向への投影のノルムに基づいてピクセルを並べ替え、最下位と最上位の p% を取得することに帰着します。これらの 2 つのグループが、イメージの強い勾配に対応します。最後に、取得したピクセルに対して主成分分析 (PCA) を行い、第 1 成分を推定した照明として返します。
チェンの手法は、関数 illumpca
によって実装します。チェンの手法を使用し、平均色の方向に沿って最下位と最上位の 5% のピクセルを使用することでシーンの光源を推定します。(既定の設定は 3.5 です。)
illuminant_ch1 = illumpca(A, 5, 'Mask', ~mask_chart);
この推定をグラウンド トゥルースと比較します。
err_ch1 = colorangle(illuminant_ch1, illuminant_groundtruth);
disp(['Angular error for Cheng with percentage=5: ' num2str(err_ch1)])
Angular error for Cheng with percentage=5: 4.7595
ガンマ補正されたホワイト バランス済みイメージの表示
B_ch1 = chromadapt(A, illuminant_ch1, 'ColorSpace', 'linear-rgb'); B_ch1_sRGB = lin2rgb(B_ch1); imshow(B_ch1_sRGB) title('White balanced image using Cheng with percentage=5')
既定値の割合を使用して比較します。
illuminant_ch2 = illumpca(A, 'Mask', ~mask_chart); err_ch2 = colorangle(illuminant_ch2, illuminant_groundtruth); disp(['Angular error for Cheng with percentage=3.5: ' num2str(err_ch2)])
Angular error for Cheng with percentage=3.5: 5.0283
補正したイメージを sRGB で表示します。
B_ch2 = chromadapt(A, illuminant_ch2, 'ColorSpace', 'linear-rgb'); B_ch2_sRGB = lin2rgb(B_ch2); imshow(B_ch2_sRGB) title('White balanced image using Cheng with percentile=3.5')
それぞれの手法に対して最良のパラメーターを見つけるために、範囲内をスイープしてそれぞれに対して角度誤差を計算します。3 つのアルゴリズムのパラメーターは異なる意味を持ちますが、パラメーターの範囲を類似させることで、それぞれのアルゴリズムにとって最良のものを探索することがプログラム的に容易になります。
param_range = 0:0.25:5; err = zeros(numel(param_range),3); for k = 1:numel(param_range) % White Patch illuminant_wp = illumwhite(A, param_range(k), 'Mask', ~mask_chart); err(k,1) = colorangle(illuminant_wp, illuminant_groundtruth); % Gray World illuminant_gw = illumgray(A, param_range(k), 'Mask', ~mask_chart); err(k,2) = colorangle(illuminant_gw, illuminant_groundtruth); % Cheng if (param_range(k) ~= 0) illuminant_ch = illumpca(A, param_range(k), 'Mask', ~mask_chart); err(k,3) = colorangle(illuminant_ch, illuminant_groundtruth); else % Cheng's algorithm is undefined for percentage=0. err(k,3) = NaN; end end
角度誤差を可視化するヒート マップを作成します。
err_normalized = mat2gray(log(err)); block_size_x = 120; block_size_y = 50; err_image = ones(size(err,1) * block_size_y, size(err,2) * block_size_x); for i = 0:size(err,1)-1 for j = 0:size(err,2)-1 err_image(block_size_y*i+1:block_size_y*(i+1), block_size_x*j+1:block_size_x*(j+1)) = err_normalized(i+1,j+1); end end
角度誤差のヒート マップを表示します。薄い青色は角度誤差が小さい (良い) ことを、一方赤色は角度誤差が大きい (悪い) ことを示します。
old_pref = iptgetpref('ImshowAxesVisible'); iptsetpref('ImshowAxesVisible','on') imshow(err_image, 'Colormap', cool) iptsetpref('ImshowAxesVisible',old_pref) for i = 0:size(err,1)-1 for j = 0:size(err,2)-1 y = block_size_y*i + 1 + block_size_y/2; x = block_size_x*j + 1 + block_size_x/3; text(x,y,sprintf('%1.2f',err(i+1,j+1))) end end box off title('Angular Error') ylabel('Parameter') yticks(linspace(block_size_y/2, size(err_image,1) - block_size_y/2, numel(param_range))) yticklabels(arrayfun(@(x) {num2str(x)}, param_range)) xticks(block_size_x/2 + [0 block_size_x 2*block_size_x]) xticklabels({'White Patch','Gray World','Cheng'})
各アルゴリズムの最良のパラメーターを求めます。
[~,idx_best] = min(err); best_param_wp = param_range(idx_best(1)); best_param_gw = param_range(idx_best(2)); best_param_ch = param_range(idx_best(3)); fprintf('The best parameter for White Patch is %1.2f with angular error %1.2f degrees\n', ... best_param_wp, err(idx_best(1),1));
The best parameter for White Patch is 0.25 with angular error 3.33 degrees
fprintf('The best parameter for Gray World is %1.2f with angular error %1.2f degrees\n', ... best_param_gw, err(idx_best(2),2));
The best parameter for Gray World is 0.00 with angular error 5.06 degrees
fprintf('The best parameter for Cheng is %1.2f with angular error %1.2f degrees\n', ... best_param_ch, err(idx_best(3),3));
The best parameter for Cheng is 0.50 with angular error 1.72 degrees
各アルゴリズムで推定された光源を RGB 空間で計算し表示します。
best_illum_wp = illumwhite(A, best_param_wp, 'Mask', ~mask_chart); best_illum_gw = illumgray(A, best_param_gw, 'Mask', ~mask_chart); best_illum_ch = illumpca(A, best_param_ch, 'Mask', ~mask_chart); plot3([0 1],[0 1],[0,1],'LineStyle',':','Color','k') hold on plot3(... [0 illuminant_groundtruth(1)/norm(illuminant_groundtruth)], ... % Red [0 illuminant_groundtruth(2)/norm(illuminant_groundtruth)], ... % Green [0 illuminant_groundtruth(3)/norm(illuminant_groundtruth)], ... % Blue 'Marker','.', 'MarkerSize',10) plot3( ... [0 best_illum_wp(1)/norm(best_illum_wp)], ... % Red [0 best_illum_wp(2)/norm(best_illum_wp)], ... % Green [0 best_illum_wp(3)/norm(best_illum_wp)], ... % Blue 'Marker','.', 'MarkerSize',10) plot3( ... [0 best_illum_gw(1)/norm(best_illum_gw)], ... % Red [0 best_illum_gw(2)/norm(best_illum_gw)], ... % Green [0 best_illum_gw(3)/norm(best_illum_gw)], ... % Blue 'Marker','.', 'MarkerSize',10) plot3( ... [0 best_illum_ch(1)/norm(best_illum_ch)], ... % Red [0 best_illum_ch(2)/norm(best_illum_ch)], ... % Green [0 best_illum_ch(3)/norm(best_illum_ch)], ... % Blue 'Marker','.', 'MarkerSize',10) xlabel('R') ylabel('G') zlabel('B') title('Best illuminants in RGB space') xlim([0 1]) ylim([0 1]) zlim([0 1]) view(28, 36) legend('achromatic line', 'ground truth', 'White Patch', 'Gray World', 'Cheng') grid on axis equal
各手法の最良の光源を使用してホワイト バランスを調整したイメージを、横に並べて表示します。
B_wp_best = chromadapt(A, best_illum_wp, 'ColorSpace', 'linear-rgb'); B_wp_best_sRGB = lin2rgb(B_wp_best); B_gw_best = chromadapt(A, best_illum_gw, 'ColorSpace', 'linear-rgb'); B_gw_best_sRGB = lin2rgb(B_gw_best); B_ch_best = chromadapt(A, best_illum_ch, 'ColorSpace', 'linear-rgb'); B_ch_best_sRGB = lin2rgb(B_ch_best); M = zeros(size(A,1), 3*size(A,2), size(A,3), 'like', A); M(:,1:size(A,2),:) = B_wp_best_sRGB; M(:,size(A,2)+1:2*size(A,2),:) = B_gw_best_sRGB; M(:,2*size(A,2)+1:end,:) = B_ch_best_sRGB; figure imshow(M) title('Montage of the best white balanced images: White Point, Gray World, Cheng')
ここでは 2 つの古典的な光源推定法とより最近の手法を比較したところ、最も暗いピクセルと明るいピクセルの 0.75% を使用したチェンの手法がこの特定のイメージでは最良の結果を示しました。しかし、この結果を鵜呑みにすべきではありません。
第 1 に、光源のグラウンド トゥルースは ColorChecker チャートを使用して測定されており、ショット ノイズやセンサー ノイズの影響を強く受けます。シーンの光源のグラウンド トゥルースは、分光光度計を使用することでさらに良くなる可能性があります。
第 2 に、光源のグラウンド トゥルースをニュートラル パッチの平均色として推定しました。平均値ではなく中央値を使用する方法も一般的です。そうすることで、グラウンド トゥルースが大きく変化することがあります。たとえば、ここで使用したイメージでは、同じピクセルを使用すると、ニュートラル パッチの中央色と平均色では 0.5° 離れています。これは、他の手法を用いて推定された光源の角度誤差を、場合によっては上回ります。
第 3 に、光源推定法の詳細な比較では、異なる条件で撮影された多様なイメージを使用するべきです。ある手法が特定のイメージでは他の手法よりうまく機能しても、データ セット全体ではうまく機能しないこともあり得ます。
[1] Ebner, Marc.White Patch Retinex, Color Constancy.John Wiley & Sons, 2007. ISBN 978-0-470-05829-9.
[2] Ebner, Marc.The Gray World Assumption, Color Constancy.John Wiley & Sons, 2007. ISBN 978-0-470-05829-9.
[3] Cheng, Dongliang, Dilip K. Prasad, and Michael S. Brown."Illuminant estimation for color constancy: why spatial-domain methods work and the role of the color distribution."JOSA A 31.5 (2014): 1049-1058.
[4] Van De Weijer, Joost, Theo Gevers, and Arjan Gijsenij."Edge-based color constancy."IEEE Transactions on image processing 16.9 (2007): 2207-2214.
chromadapt
| colorangle
| illumgray
| illumpca
| illumwhite
| imdilate
| imerode
| imoverlay
| lin2rgb
| rgb2lin