Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

自動ホワイト バランス アルゴリズムの比較

この例では、3 つの異なる光源アルゴリズムを使用し、光源を推定してシーンのホワイト バランスを実行する方法を説明します。この例では、推定された光源を、X-Rite® ColorChecker® チャートを使用して計算した光源のグラウンド トゥルースと比較します。

目は、異なるライティング条件下で白い物を判断する能力に長けています。しかしデジタル カメラの場合は、何かしらの調整を行わなければ、現実とかけ離れた色かぶりの著しいイメージをキャプチャーすることになりかねません。自動ホワイト バランス (AWB) アルゴリズムは、最小限のユーザー入力で結果のイメージが私たちの目で見たものに近くなるように周囲光を補正します。

自動ホワイト バランスは次の 2 ステップで行われます。

  • 手順 1: シーン光源の推定。

  • 手順 2: イメージのカラー バランスの補正。

シーン光源を推定するアルゴリズムには、いくつかの異なる種類が存在します。各アルゴリズムのパフォーマンスは、シーン、ライティングおよびイメージングの条件に依存します。この例では、特定の 1 つのイメージに対する光源推定の 3 つのアルゴリズムの品質を、シーン光源のグラウンド トゥルースと比較することで判断します。

  • ホワイト パッチ レチネックス [1]

  • グレー ワールド [2]

  • チェンの主成分分析 (PCA) 法 [3]

周囲光さえ分かれば、イメージの色の補正 (ステップ 2) は簡単で固定化された処理です。

カメラの生データの読み取りと前処理

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 チャートを使用した光源のグラウンド トゥルースの測定

シーンに含まれている X-Rite 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);

関数 measureColor を使用して、すべてのカラー パッチの RGB 値を測定します。

colors = measureColor(chart)
colors=24×9 table
    ROI         Color          Measured_R    Measured_G    Measured_B    Reference_L    Reference_a    Reference_b    Delta_E
    ___    ________________    __________    __________    __________    ___________    ___________    ___________    _______

     1     {'DarkSkin'    }       1773          2284          1106          37.54          14.37          14.92       40.763 
     2     {'LightSkin'   }       5691          7627          4245          64.66          19.27           17.5       61.046 
     3     {'BlueSky'     }       1919          5214          4677          49.32          -3.82         -22.54       49.222 
     4     {'Foliage'     }       1576          3376          1216          43.46         -12.74          22.72       46.172 
     5     {'BlueFlower'  }       2997          6253          5997          54.94           9.61         -24.79       55.312 
     6     {'BluishGreen' }       3676         12020          7388          70.48         -32.26          -0.37       56.895 
     7     {'Orange'      }       6250          5276          1213          62.73          35.83           56.5       82.432 
     8     {'PurplishBlue'}       1226          3779          5302          39.43          10.75         -45.17       55.714 
     9     {'ModerateRed' }       4581          3277          2074          50.57          48.64          16.67       67.971 
    10     {'Purple'      }       1233          1767          1889           30.1          22.54         -20.87       41.882 
    11     {'YellowGreen' }       4939         10555          2657          71.77         -24.13          58.19       72.055 
    12     {'OrangeYellow'}       7410          8522          1608          71.51          18.24          67.37       83.134 
    13     {'Blue'        }        567          2300          3888          28.37          15.42          -49.8       55.878 
    14     {'Green'       }       2087          6486          2278          54.38         -39.72          32.27       61.962 
    15     {'Red'         }       3702          1986           968          42.43          51.05          28.62       68.861 
    16     {'Yellow'      }       8906         12782          2422           81.8           2.67          80.41       87.375 
      ⋮

光源のグラウンド トゥルースを測定するには、チャートの最下行にある 6 つのニュートラル パッチだけを使用します。最下行の 6 つのニュートラル (無彩色) パッチの RGB 値を抽出します。

grayPatchRGB = colors{19:end,{'Measured_R','Measured_G','Measured_B'}};
grayPatchRGB = im2double(grayPatchRGB);

光源のグラウンド トゥルースは、ニュートラル パッチの平均色です。

illuminant_groundtruth = mean(grayPatchRGB)
illuminant_groundtruth = 1×3

    0.0693    0.1423    0.0943

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.5377

関数 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.032

推定した光源を使用して、線形 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.0414

推定した光源を使用して、線形 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.1092

推定した光源を使用して、線形 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.0159

推定した光源を使用して、線形 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.7451

推定した光源を使用して、線形 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.

参考

| | | | | | | |

関連するトピック