色成分差の拡大
あるピクセルのRGB成分を、その周辺のピクセルの成分と混ぜ合わせるとぼかし効果が得られました。では、逆に周辺ピクセルの成分との「差」を拡大させるとどうなるのでしょうか? 先鋭化することは想像がつきますが、同時に画像が荒れそうですね。その辺りのことを実際に実験してみることにしましょう。
この処理はアンシャープマスクなどによる先鋭化に比べ高速に行えるので、実験結果によっては意外に「使える」場面も見つかるかもしれません。
色成分差の拡大処理
今回の処理は、ごく単純。まず、ぼかしの時と同様に処理前のピクセル列をバッファに取っておいて、処理対象のピクセルの周辺(今回は4近傍)のRGB成分を平均化します。そして、処理対象ピクセルのRGB各成分とその平均の「差」を拡大するだけ。今回は、単純に差を2倍にしてみました。たとえば、あるピクセルとその周辺が
(192,192,192) | ||
(192,192,192) | (128,128,128) | (192,192,192) |
(192,192,192) |
のようになっていた場合、中心ピクセル(128,128,128)と周辺ピクセルの平均成分の差は(-64,-64,-64)となります。よって、この中心ピクセルに(-64,-64,-64)を加算して(64,64,64)とするわけです。この例ではかなり極端な処理を行っていますが、実際の画像では隣接するピクセル間の色成分差はそれほど大きくない場合が多いので、そう極端な結果にはならないことも多いでしょう。
ただ、この処理では灰色の領域に1ピクセル暗い部分があった場合、その部分がさらに暗くなるだけでなく、周辺にやや明るい「縁取り」ができます。これは、暗いピクセルの周辺では、その暗いピクセルの存在によって周辺ピクセルの平均が暗くなる(そして、「周辺との差を拡大」することにより、そのピクセルが明るくなる)から、ですね。成分差がある周辺のピクセルにも、こういった効果が及ぶ点には注意が必要でしょう。
具体的な処理は、まず32ビットDIBピクセル列lpPixelの内容をlpWrkにコピーしてから、以下のように処理対象ピクセルの周辺ピクセルとの色成分の差を強調します。
/* 処理前画像保存用バッファ確保 */
lpWrk = (LPDWORD)HeapAlloc(GetProcessHeap(), 0, iWidth * iHeight * 4);
/* 処理前の画像をバッファにコピー */
CopyMemory(lpWrk, lpPixel, iWidth * iHeight *4);
for (i = 0;i < iHeight;i++) {
for (j = 0;j < iWidth;j++) {
iAvR = 0;
iAvG = 0;
iAvB = 0;
n = 0;
if (j > 0) { /* 一つ左のピクセルの成分 */
iAvR += (lpWrk[j - 1 + i * iWidth] & 0x00ff0000) >> 16;
iAvG += (lpWrk[j - 1 + i * iWidth] & 0x0000ff00) >> 8;
iAvB += lpWrk[j - 1 + i * iWidth] & 0x000000ff;
n++;
}
if (i > 0) { /* 一つ下のピクセルの成分 */
iAvR += (lpWrk[j + (i - 1) * iWidth] & 0x00ff0000) >> 16;
iAvG += (lpWrk[j + (i - 1) * iWidth] & 0x0000ff00) >> 8;
iAvB += lpWrk[j + (i - 1) * iWidth] & 0x000000ff;
n++;
}
if (j < iWidth - 1) {/* 一つ右のピクセルの成分 */
iAvR += (lpWrk[j + 1 + i * iWidth] & 0x00ff0000) >> 16;
iAvG += (lpWrk[j + 1 + i * iWidth] & 0x0000ff00) >> 8;
iAvB += lpWrk[j + 1 + i * iWidth] & 0x000000ff;
n++;
}
if (i < iHeight - 1) { /* 一つ上のピクセルの成分 */
iAvR += (lpWrk[j + (i + 1) * iWidth] & 0x00ff0000) >> 16;
iAvG += (lpWrk[j + (i + 1) * iWidth] & 0x0000ff00) >> 8;
iAvB += lpWrk[j + (i + 1) * iWidth] & 0x000000ff;
n++;
}
/* 周辺ピクセルの色成分の平均計算 */
iAvR = (int)(iAvR / n);
iAvG = (int)(iAvG / n);
iAvB = (int)(iAvB / n);
/* 処理対象ピクセルの色成分計算 */
iPixR = (lpWrk[j + i * iWidth] & 0x00ff0000) >> 16;
iPixG = (lpWrk[j + i * iWidth] & 0x0000ff00) >> 8;
iPixB = lpWrk[j + i * iWidth] & 0x000000ff;
/* 処理対象ピクセルと周辺ピクセルの色成分差拡大 */
iPixR += (iPixR - iAvR);
iPixG += (iPixG - iAvG);
iPixB += (iPixB - iAvB);
if (iPixR < 0) {
iPixR = 0;
}
if (iPixR > 255) {
iPixR = 255;
}
if (iPixG < 0) {
iPixG = 0;
}
if (iPixG > 255) {
iPixG = 255;
}
if (iPixB < 0) {
iPixB = 0;
}
if (iPixB > 255) {
iPixB = 255;
}
/* 処理対象ピクセルの色成分設定 */
lpPixel[j + i * iWidth] = (iPixR << 16) + (iPixG << 8) + iPixB;
}
}
上の処理で、ピクセル成分iPixR/iPixG/iPixBに「周辺ピクセルとの差」を加える量を変える(たとえば加える量を半分にする)と、効果の大きさも変わってきます。
プログラム
いつもと同様に、ウインドウに24ビットフルカラーBMPをドロップしてください。実行ボタンをクリックすると、色成分を拡大する処理が行われ、結果が表示されます。画像上にカーソルを持っていくとその部分の色成分が表示されるので、処理前に比べ成分がどう変わっているか、確認してみましょう。
処理を繰り返して結果を見てみると、先鋭化と同時にやはりノイズも出てきますね。ただ、処理回数が1,2回であればけっこう良い感じなので、速度重視の場面では有効かもしれません。