明度変換によるコントラスト調整
デジカメで写真を撮影すると、露出がうまく合わなかったりして全体にコントラストが低い画像(明暗のメリハリのない「眠い」画像)になってしまう事があります。コントラストの低い画像は、全体が明るい方か暗い方に偏っているか、光の状態などで明暗差が目立たなくなっているわけですね。つまり、画像処理で「明るい部分と暗い部分の差」を強調してやると見栄えがぐっと良くなるはずです。今回は、この明暗差の強調処理(コントラスト強調処理)を試してみましょう。
うまく行けば、撮影した時の光の状態や露出の問題で見栄えがいまいちだった写真も鮮やかに蘇るかもしれません。
明暗差の強調
明暗差を強調するには、まず明暗差の情報を取得する必要がありますので、RGB形式の画像をYCrCbに変換して処理しましょう。YCrCbではYが明度ですから、このYに対して処理を行う事になります。
明暗差を強調するには、Yの値を一定の比率で「拡大」すると効果的です。こうする事により、Yの分布、つまり「明暗差」も拡大されるからです。例えば、Yが64-192に分布しているなら明暗差は128ですが、各Yの値を2倍にしてやれば128-384の256に広がります。ただ、この時にはYを8ビットで扱う場合は256以上だと飽和して255として扱われるので、明度が低い方は拡大されても高い方は白飛びしてしまいますね。また、128以下の部分も「無駄」になってしまいます。Yを8ビットで扱う場合、コントラストを高くするにはYを0-255までまんべんなく分布させるのが理想でしょう。そのためには、画像の中の最小のYを0に、最高のYを255にするような拡大を行えば良いはずです。今回は簡単のために、一定の比率で(つまり1次式で)拡大を行う事を考えてみましょう。まず、最小値をminとするとこのminの時に0にするためにminを引く事になりそうです。また、最大値をmaxとするとmaxの時に255になる、言い換えればminを0、maxを255にするための比率が傾きになるので、この傾きは255/(max-min)で求まる事になります。以上の事から、変換後の明度Y2は以下の式で求まる事になります。
Y2=Y*(255/(max-min))-min
この式によって、明度Yがビットマップ中最小のピクセルでは新しいYを0に、最大のピクセルでは255にする事が出来ます。もっと一般的に、minからmaxまでの明度Yを0-255に対応させる式、といっても良いでしょう。
上の処理を、1バイト目にYを持つYCrCbバッファlpYUVに対して行うと、以下のような関数になります。
void changeY(DWORD dwMin,DWORD dwMax) {
double lfA=255.0/(double)(dwMax-dwMin),y;
DWORD i;
if (lpYUV==NULL || dwMax<=dwMin)
return;
for (i=0;i<dwWidth*dwHeight;i++) {
y=lpYUV[i*3]*lfA-dwMin;
if (y<0)
y=0;
if (y>255)
y=255;
lpYUV[i*3]=(BYTE)y;
}
}
今回は、ウインドウ上にエディトボックスを2つとボタンを並べ、ボタンがクリックされたらエディトボックスの値を最小・最大値としてchangeY()を呼び出して明度調整を行い新しいビットマップを表示するようにしましょう。エディトボックスは、左側を最小値、右側を最大値とします。
case WM_COMMAND:
if (LOWORD(wParam)==0) {
GetWindowText(hwMin,lpszFn,256);
dwMin=atol(lpszFn);
GetWindowText(hwMax,lpszFn,256);
dwMax=atol(lpszFn);
changeY(dwMin,dwMax);
YUVtoRGB();
InvalidateRgn(hwnd,NULL,FALSE);
UpdateWindow(hwnd);
}
プログラム
適当な24ビットフルカラーBMPをドロップしたら、エディトボックスに表示される明度の最大値・最小値を確認して実行ボタンをクリックしてください。明度の最小値から最大値の範囲が0-255に対応するように明度が拡大され、結果が表示されます。なお、この最小・最大値は別にビットマップ内の明度の最小・最大値と一致している必要はないので、いろいろ変えてみると良いでしょう。例えば、最小値を大きくすると明度の低い部分がいわば切り捨てられる形でさらに明暗差が誇張されます。逆に最大値を小さくすると、比例定数が大きくなるので明度の差が高い方に誇張され全体に白飛びした感じになるでしょう。