ポスタリゼーション(階調変更)
ポスタリゼーション(階調変更)とは、グラフィックの階調(色数の表現段階)を下げる画像処理です。いわゆる「フルカラー」はRGBそれぞれ256階調(段階)で表現されていますが、この階調の段階を減らす(荒くする)ことでイラスト風の雰囲気に変えるなど様々な効果を出すことができます。
今回は、ポスタリゼーションによりフルカラー画像の階調を意図的に低下させてどのような効果が得られるか、試してみましょう。
画像の階調を変える
今回は、RGB各256段階の階調を任意の数の階調に変更します。つまり、0-255の値を階調数分の区間に区切るわけです。各区間には、256を階調数で割った数の階調が含まれることになります。
例えば、2階調に分けるのなら0-127、128-255と各区間に元の256階調の内の128階調が含まれるわけです。3階調の場合は、割り切れず86、85、85となるので0-85(この区間のみ86階調)、86-170、171-255などとすることになるでしょう。
ポスタリゼーションのアルゴリズムで問題になるのは、階調区間に含まれるピクセルの「値」をどうするかですね。これは、やはり全体を等分に分ける値を採用したいところです。ただし、画像全体のコントラストを低下させないために最初と最後の区間は最小値である0、最大値である255にしましょう。
例えば、256段階0-255の階調を2階調にするのなら、0-127、128-255と分けられますが、それぞれの値は0-127が0,128-255は255とします。
また、3階調なら最初と最後は最小・最大である0と255を対応させ、間にはその中間である127か128を置くと良さそうです。4階調なら、最初と最後の間が2つあるので階調の幅を85にすると(0、85、170、255)ちょうど収まります。
3階調 | |||
---|---|---|---|
画素値 | 0-85 | 86-170 | 171-255 |
階調値 | 0 | 127 | 255 |
4階調 | ||||
---|---|---|---|---|
画素値 | 0-63 | 64-127 | 128-191 | 192-255 |
階調値 | 0 | 85 | 170 | 255 |
以上の例を見てみると、階調の値の増加幅は255を階調数-1で割ったもの、になりそうですね。
最初と最後を除いた「間」にある区間の数は階調数-2なので、その間に区間の数だけの値を等分に配置するには全体の増加数255を階調数-1で割れば良いわけです。これは、値が0となる最初の階調を除いた区間を残りの区間数(全体の区間数-1)で等分する、と考えても良いでしょう。
ただし、こうして求まった階調の増加幅を順次加えていくことで得られる階調の値は、「全体を」等分に分ける値なので「各区間の」中央値とは一致しない場合もある点に注意してください(例えば、上の4階調の例を見ても一致していない)。間の区間については、区間の中央値をとった方が良いこともあるかもしれません。
ポスタリゼーションのアルゴリズムとプログラム
新しい階調に含まれる元の256階調の範囲と階調の値が求まったので、これで元の256段階の階調をポスタリゼーションの結果となる新しい階調に対応させることが出来るようになりました。今回は、新しい階調との対応をテーブル化して配列に格納するようにします。
例えば、配列sc[]に引数iを入れsc[i]とすると、256階調のiに対応する新しい階調の値が得られるようにするわけです。
配列sc[]は、階調の値を階調の幅分だけ埋めていけば作れるので、以下のように作成してみました。変数sが階調数で、関数floor()は「小数点以下を切捨てる」関数です。
n=(256.0/(double)s); /* 1階調に含まれる元の階調数 */
d=255.0/(double)((double)s-1.0); /* 1階調の増加分 */
for (i=0;i<s;i++)
for (j=floor(i*n);j<floor((i+1)*n);j++)
sc[j]=(BYTE)(d*i);
あとは、このような配列をRGBそれぞれに作成し、各ピクセルのRGB成分をその配列で変換すればポスタリゼーション処理の完成です。以下の例では、24ビットフルカラーDIB(iWidth×iHeightピクセル、バッファの横幅iLengthバイト)のピクセル列lpPixelの階調を変える処理を行っています。この関数ではRGBそれぞれの階調は引数(rs, gs, bs)で指定し、階調のテーブルを配列rb[], gb[], bb[]に作成するようにしました。
void pos(int rs,int gs,int bs) {
int i,j;
BYTE rb[256],gb[256],bb[256];
double d,n;
/* 赤階調のテーブル作成 */
n=(256.0/(double)rs); /* 1階調に含まれる元の階調数 */
d=255.0/(double)((double)rs-1.0); /* 1階調の増加分 */
for (i=0;i<rs;i++)
for (j=floor(i*n);j<floor((i+1)*n);j++)
rb[j]=(BYTE)(d*i);
/* 緑階調のテーブル作成 */
n=(256.0/(double)gs);
d=255.0/(double)((double)gs-1.0);
for (i=0;i<gs;i++)
for (j=floor(i*n);j<floor((i+1)*n);j++)
gb[j]=(BYTE)(d*i);
/* 青階調のテーブル作成 */
n=(256.0/(double)bs);
d=255.0/(double)((double)bs-1.0);
for (i=0;i<bs;i++)
for (j=floor(i*n);j<floor((i+1)*n);j++)
bb[j]=(BYTE)(d*i);
/* 各ピクセルのRGB成分を変換 */
for (i=0;i<iHeight;i++)
for (j=0;j<iWidth;j++) {
lpPixel[j*3+i*iLength+2]=rb[lpPixel[j*3+i*iLength+2]];
lpPixel[j*3+i*iLength+1]=gb[lpPixel[j*3+i*iLength+1]];
lpPixel[j*3+i*iLength]=bb[lpPixel[j*3+i*iLength]];
}
}
ポスタリゼーション処理のプログラム
プログラムソースをビルドして実行したら、適当な24ビットBMPをドロップしてください。ボタンをクリックするとポスタリゼーション処理(最初の状態ではRGBそれぞれ4階調)を行い、結果を表示します。
画面上の画像にマウスポインタを持っていくとその地点の座標とRGB値をウインドウのタイトルバーに表示しますので、実際にどんな変換が行われているか、確認してみましょう。
単色スケールを作成して処理してみると、対応の様子が良くわかると思います。
なお、階調数は、115行目にある
pos(4,4,4);
の部分を変更することで任意の階調数を指定できます。引数は順にR, G, Bの階調数になっているので、ここを2-256の範囲で変えて実行してみてください。RGBの階調数は異なっていても大丈夫です。
ポスタリゼーションは画像の「情報量」を意図的に下げて色の変化を荒くする処理ですが、印象が大きく変わることもあるので効果的に活用したいですね。