自動ホワイト バランス アルゴリズムの比較
この例では、3 つの異なる光源アルゴリズムを使用し、光源を推定してシーンのホワイト バランス処理を実行する方法を説明します。
目は、異なるライティング条件下で白い物を判断する能力に長けています。しかしデジタル カメラの場合は、何かしらの調整を行わなければ、現実とかけ離れた色かぶりの著しいイメージをキャプチャーすることになりかねません。自動ホワイト バランス (AWB) アルゴリズムは、最小限のユーザー入力で結果のイメージが私たちの目で見たものに近くなるように周囲光を補正します。
自動ホワイト バランスは次の 2 ステップで行われます。
手順 1: シーン光源の推定。
手順 2: イメージのカラー バランスの補正。
シーン光源を推定するアルゴリズムには、いくつかの異なる種類が存在します。
各アルゴリズムのパフォーマンスは、シーン、ライティングおよびイメージングの条件に依存します。この例では、特定の 1 つのイメージに対する 3 つのアルゴリズムの品質を、ColorChecker® チャートを使用して計算されたシーン光源のグラウンド トゥルースと比較することで判断します。
カメラの生データの読み取りと前処理
AWB アルゴリズムは通常、イメージが圧縮されてメモリ カードに保存される前の、最小限の前処理をした後の生のイメージ データに対して適用されます。
16 ビットの生のイメージをワークスペースに読み取ります。foosballraw.tiff
は、黒レベルを補正して強度をピクセルごとに 16 ビットにスケーリングした、生のセンサー データを含むイメージ ファイルです。このイメージにはカメラによるホワイト バランスは行われておらず、デモザイク処理、ノイズ除去、クロマチック色収差補正、トーン補正、ガンマ補正などその他の前処理演算も行われていません。
A = imread("foosballraw.tiff");
内挿による失われた色情報の回復
デジタル カメラでは、色覚をシミュレーションするために、イメージング センサーにカラー フィルター配列を重ね合わせて使用します。これにより、各ピクセルは赤、緑、または青に敏感に反応します。各ピクセルの失われた色情報を回復するために、関数 demosaic
を使用して内挿を行います。この写真を撮影したカメラ (Canon EOS 30D) で使用されるベイヤー パターンは RGGB です。
A = demosaic(A,"rggb");
検出と表示のためのイメージのガンマ補正
イメージ A
には線形 RGB 値が含まれます。線形 RGB 値は、シーン光源の推定とイメージのカラー バランスの補正に適しています。ただし、線形 RGB イメージを表示しようとした場合、非常に薄暗く表示されます。これはディスプレイ デバイスの非線形特性によるものです。そのため、表示を目的として、関数 lin2rgb
を使用して sRGB 色空間にイメージのガンマ補正を行います。
A_sRGB = lin2rgb(A);
ガンマ補正前後のデモザイク処理イメージを表示します。
montage({A,A_sRGB})
title("Original Image Before and After Gamma Correction")
ColorChecker チャートを使用した光源のグラウンド トゥルースの測定
シーンに含まれている ColorChecker チャートを使用して光源のグラウンド トゥルースを計算します。このチャートは、既知の分光反射率をもつ 24 のニュートラル パッチおよびカラー パッチで構成されます。
関数 colorChecker
を使用してガンマ補正されたイメージのチャートを検出します。線形 RGB イメージは colorChecker
でチャートを自動検出するには暗すぎます。
chart_sRGB = colorChecker(A_sRGB);
チャートが正しく検出されていることを確認します。
displayChart(chart_sRGB)
チャートの 4 つの隅でレジストレーション ポイントの座標を求めます。
registrationPoints = chart_sRGB.RegistrationPoints;
線形 RGB データから新しい colorChecker
オブジェクトを作成します。レジストレーション ポイントの座標を使用して、チャートの場所を指定します。
chart = colorChecker(A,RegistrationPoints=registrationPoints);
関数 measureIlluminant
を使用し、線形 RGB データのグラウンド トゥルースの光源を測定します。
illuminant_groundtruth = measureIlluminant(chart)
illuminant_groundtruth = 1×3
103 ×
4.5407 9.3226 6.1812
ColorChecker チャートのマスクの作成
AWB アルゴリズムをテストする場合は、チャートをマスクしてアルゴリズムに不当に有利にならないようにします。
関数 drawpolygon
を使用してチャート上に多角形 ROI を作成します。多角形の頂点をレジストレーション ポイントとして指定します。
chartROI = drawpolygon(Position=registrationPoints);
関数 createMask
を使用して多角形 ROI をバイナリ マスクに変換します。
mask_chart = createMask(chartROI);
マスクを反転します。チャート内のピクセルがマスクから除外され、シーンの残りのピクセルがマスクに含まれます。
mask_scene = ~mask_chart;
マスクの正確性を確認するには、イメージ上でマスクを表示します。マスクに含まれているピクセルは青みがかかっています。
imshow(labeloverlay(A_sRGB,mask_scene));
角度誤差
光源は、3 次元 RGB 色空間のベクトルと考えることができます。推定された光源の大きさは方向ほど重要ではありません。なぜなら、イメージのホワイト バランスに使用されるのは光源の方向であるためです。
推定された光源の品質を評価するために、推定された光源とグラウンド トゥルースの間の角度誤差を計算します。角度誤差は、2 つのベクトルから構成される角度 (度単位) です。角度誤差が小さいほど、推定の精度が上がります。
角度誤差の概念をより理解するために、任意の光源と、ColorChecker チャートを使用して測定したグラウンド トゥルースについて次の可視化を考えます。補助関数 plotColorAngle
は 3 次元 RGB 色空間にある光源の単位ベクトルをプロットします。この関数の定義はこの例の終わりで行います。
sample_illuminant = [0.066 0.1262 0.0691]; p = plot3([0 1],[0 1],[0,1],LineStyle=":",Color="k"); ax = p.Parent; hold on plotColorAngle(illuminant_groundtruth,ax) plotColorAngle(sample_illuminant,ax) title("Illuminants in RGB space") view(28,36) legend("Achromatic Line","Ground Truth Illuminant","Sample Illuminant") grid on axis equal
ホワイト パッチ レチネックス
光源推定のホワイト パッチ レチネックス アルゴリズムは、シーンに明るい無彩色パッチが含まれることを前提とします。このパッチは、そのシーン光源の色である、各カラー帯域の最大光を反射します。関数 illumwhite
を使用して、ホワイト パッチ レチネックス アルゴリズムにより光源を推定します。
シーンのすべてのピクセルを含める
シーンのすべてのピクセルを使用して光源を推定します。名前と値のペアの引数 Mask
を使用して、シーンから ColorChecker チャートを除外します。
percentileToExclude = 0; illuminant_wp1 = illumwhite(A,percentileToExclude,Mask=mask_scene);
ホワイト パッチ レチネックスで推定した光源の角度誤差を計算します。
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.5381
関数 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")
最も明るいピクセルの除外
ピクセルが露光過多の場合、ホワイト パッチ レチネックス アルゴリズムの性能はあまり良くありません。アルゴリズムの性能を改善するには、最も明るいピクセルの上位 1% を除外します。
percentileToExclude = 1; illuminant_wp2 = illumwhite(A,percentileToExclude,Mask=mask_scene);
推定した光源の角度誤差を計算します。すべてのピクセルを使用して光源を推定した場合より、誤差は小さくなります。
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.0324
推定した光源を使用して、線形 RGB 色空間内にあるイメージのホワイト バランスを調整します。
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")
グレー ワールド
光源推定のグレー ワールド アルゴリズムはワールドの平均色がグレー、つまり無彩色になると仮定します。したがって、シーンの光源をイメージの平均 RGB 値として計算します。関数 illumgray
を使用して、グレー ワールド アルゴリズムにより光源を推定します。
シーンのすべてのピクセルを含める
まず、ColorChecker チャートに対応するピクセルを除いた、イメージのすべてのピクセルを使用してシーン光源を推定します。関数 illumgray
は除外する上位と下位の値 (明度の順) を百分位で指定するパラメーターを出力します。ここでは百分位に 0 を指定します。
percentileToExclude = 0; illuminant_gw1 = illumgray(A,percentileToExclude,Mask=mask_scene);
推定された光源と光源のグラウンド トゥルースの間の角度誤差を計算します。
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.0416
推定した光源を使用して、線形 RGB 色空間内にあるイメージのホワイト バランスを調整します。
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]")
最も明るいピクセルおよび最も暗いピクセルの除外
ピクセルが露光不足や露光過多の場合、グレー ワールド アルゴリズムの性能はあまり良くありません。アルゴリズムの性能を改善するには、最も暗いピクセルと最も明るいピクセルの上位 1% を除外します。
percentileToExclude = 1; illuminant_gw2 = illumgray(A,percentileToExclude,Mask=mask_scene);
推定した光源の角度誤差を計算します。すべてのピクセルを使用して光源を推定した場合より、誤差は小さくなります。
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.1094
推定した光源を使用して、線形 RGB 色空間内にあるイメージのホワイト バランスを調整します。
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]")
チェンの主成分分析 (PCA) 法
チェンの光源推定法は、グレー エッジ [4] のような空間ドメイン法から発想を得たもので、イメージの勾配は無彩色であると仮定します。イメージ ブロックをシャッフルして強い勾配を人為的に導入することでグレー エッジは改善できることを示し、最も強い勾配が光源の方向を表すと結論付けています。彼らの手法は、平均イメージ色の方向への投影のノルムに基づいてピクセルを並べ替え、最下位と最上位の百分位数を取得することに帰着します。これらの 2 つのグループが、イメージの強い勾配に対応します。最後に、取得したピクセルに対して "主成分分析" (PCA) を行い、第 1 成分を推定した光源として返します。関数 illumpca
を使用して、チェンの PCA アルゴリズムにより光源を推定します。
既定でピクセルの最下位および最上位 3.5 パーセントを含める
まず、ColorChecker チャートに対応するパーセンテージ値を除いて、チェンの PCA 法の既定のパーセンテージ値を使用して光源を推定します。
illuminant_ch2 = illumpca(A,Mask=mask_scene);
推定された光源と光源のグラウンド トゥルースの間の角度誤差を計算します。
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.0162
推定した光源を使用して、線形 RGB 色空間内にあるイメージのホワイト バランスを調整します。
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")
ピクセルの最下位および最上位 5 パーセントを含める
次に、平均色の方向に沿って最下位と最上位の 5% のピクセルを使用することでシーンの光源を推定します。関数 illumpca
の 2 番目の引数は除外する上位と下位の値 (明度の順) を百分位数で指定します。
illuminant_ch1 = illumpca(A,5,Mask=mask_scene);
推定された光源と光源のグラウンド トゥルースの間の角度誤差を計算します。既定のパーセンテージを使用して光源を推定した場合より、誤差は小さくなります。
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.7454
推定した光源を使用して、線形 RGB 色空間内にあるイメージのホワイト バランスを調整します。
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")
最適なパラメーターを求める
それぞれのアルゴリズムに対して最良のパラメーターを見つけるために、範囲内をスイープしてそれぞれに対して角度誤差を計算します。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_scene); err(k,1) = colorangle(illuminant_wp,illuminant_groundtruth); % Gray World illuminant_gw = illumgray(A,param_range(k),Mask=mask_scene); err(k,2) = colorangle(illuminant_gw,illuminant_groundtruth); % Cheng if (param_range(k) ~= 0) illuminant_ch = illumpca(A,param_range(k),Mask=mask_scene); err(k,3) = colorangle(illuminant_ch,illuminant_groundtruth); else % Cheng's algorithm is undefined for percentage=0 err(k,3) = NaN; end end
関数 heatmap
を使用して、角度誤差のヒートマップを表示します。濃い青色は角度誤差が小さいことを、一方黄色は角度誤差が大きいことを示します。最適なパラメーターでは角度誤差が最も小さくなります。
heatmap(err,Title="Angular Error",Colormap=parula(length(param_range)), ... XData=["White Patch" "Gray World" "Cheng's PCA"], ... YLabel="Parameter Value",YData=string(param_range));
各アルゴリズムの最良のパラメーターを求めます。
[~,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.35 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.04 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.74 degrees
最良のパラメーターを使用して、各アルゴリズムの推定した光源を計算します。
best_illum_wp = illumwhite(A,best_param_wp,Mask=mask_scene); best_illum_gw = illumgray(A,best_param_gw,Mask=mask_scene); best_illum_ch = illumpca(A,best_param_ch,Mask=mask_scene);
RGB 色空間内にある最良の光源ごとに角度誤差を表示します。
p = plot3([0 1],[0 1],[0,1],LineStyle=":",Color="k"); ax = p.Parent; hold on plotColorAngle(illuminant_groundtruth,ax) plotColorAngle(best_illum_wp,ax) plotColorAngle(best_illum_gw,ax) plotColorAngle(best_illum_ch,ax) title("Best Illuminants in RGB space") 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);
各アルゴリズムの最適なホワイト バランス済みイメージをモンタージュに表示します。
figure
montage({B_wp_best_sRGB,B_gw_best_sRGB,B_ch_best_sRGB},Size=[1 3])
title("Montage of Best White-Balanced Images: White Point, Gray World, Cheng")
まとめ
ここでは 2 つの古典的な光源推定アルゴリズムとより最近の手法を比較したところ、最も暗いピクセルと明るいピクセルの 0.75% を使用したチェンの手法がこの特定のイメージでは最良の結果を示しました。しかし、この結果を鵜呑みにすべきではありません。
第 1 に、光源のグラウンド トゥルースは ColorChecker チャートを使用して測定されており、ショット ノイズやセンサー ノイズの影響を強く受けます。シーンの光源のグラウンド トゥルースは、分光光度計を使用することでさらに良くなる可能性があります。
第 2 に、光源のグラウンド トゥルースをニュートラル パッチの平均色として推定しました。平均値ではなく中央値を使用する方法は一般的で、そうすることでグラウンド トゥルースが大きく変化することがあります。たとえば、ここで使用したイメージでは、同じピクセルを使用すると、ニュートラル パッチの中央色と平均色では 0.5° 離れています。これは、他のアルゴリズムを用いて推定された光源の角度誤差を、場合によっては上回ります。
第 3 に、光源推定アルゴリズムの詳細な比較では、異なる条件で撮影された多様なイメージを使用するべきです。あるアルゴリズムが特定のイメージでは他のアルゴリズムよりうまく機能しても、データ セット全体ではうまく機能しないこともあり得ます。
サポート対象の関数
関数 plotColorAngle
は 3 次元 RGB 色空間にある光源の単位ベクトルをプロットします。入力引数 illum
では光源を RGB カラーとして指定し、入力引数 ax
では単位ベクトルをプロットする座標軸を指定します。
function plotColorAngle(illum,ax) R = illum(1); G = illum(2); B = illum(3); magRGB = norm(illum); plot3([0 R/magRGB],[0 G/magRGB],[0 B/magRGB], ... Marker=".",MarkerSize=10,Parent=ax) xlabel("R") ylabel("G") zlabel("B") xlim([0 1]) ylim([0 1]) zlim([0 1]) end
参照
[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.
参考
colorChecker
| measureColor
| illumgray
| illumpca
| illumwhite
| lin2rgb
| rgb2lin
| chromadapt
| colorangle