アンチエイリアシングによる高画質化

3Dゲームやグラフィックソフトなどでは、斜線のギザギザ感を軽減し高画質化するためにアンチエイリアシング(アンチエイリアス処理)という画像処理を行う場合があります。これは、輪郭線周辺を「ぼかす」ことで輪郭線のピクセルが突出するのを抑え滑らかに見せる画像処理で、特に低解像度の画像を高画質に見せる効果があります。

ただ、ぼかすといっても実際の3Dグラフィックのアンチエイリアシングでは単にピクセルをぼかすだけでなく、画像の解像度以上の精度でレンダリングしてオーバーサンプリングしたピクセルを平均化することで滑らかな輪郭を出したりしているようです(つまり本質的な意味での高画質化を行っている)。

今回は、このアンチエイリアシング(らしき)処理を2Dの画像で試してみましょう。ただし、すでにある2D画像に対してピクセルのオーバーサンプリングというのはそもそも無理なので、今回は「変化の激しいピクセルを周囲のピクセルと平均化する」という処理を行うことで「変化の激しいピクセルをぼかす」ことにしました。これでもパラメータをうまく設定すると少数のピクセルが突き出すことで生じる斜線のギザギザ感がけっこう緩和される上に通常のぼかし処理と違って輪郭線(変化の激しい領域)のみを処理対象にできるので、場合によっては有効な処理になるでしょう。

アンチエイリアシングの実装

では、実際にアンチエイリアスをかけるために「変化の激しいピクセル」を抽出する処理とピクセルを平均化するぼかし処理を考えてみましょう。といっても、これはどっちも既にやっているんですよね。ただ、今回は変化の激しいピクセルを「周囲8ピクセルいずれかのピクセルとの色空間距離が一定値以上のピクセル」と定義します。つまり、周囲8ピクセルについてRGB各成分の差の2乗を合計し、その値(ピクセル間の色空間距離の2乗)が一定値以上のものがひとつでもあれば、そのピクセルを「変化の激しいピクセル」とするわけですね。この判定は、以下のようになるでしょう。

リスト中、lpWrkがピクセル列のフレームバッファ、iDivが処理対象とするか判断する「一定値」です。

  for (i=0;i<iHeight;i++)
      for (j=0;j<iWidth;j++) {

          done=0;

          for (k=-1;k<=1 && done==0;k++)
              for (l=-1;l<=1 && done==0;l++) {

                  if (j+l>=0 && j+l<iWidth && i+k>=0 && i+k<iHeight &&
                      !(k==0 && l==0)) {

                      dr=(lpWrk[j*3+i*iLength+2]-lpWrk[(j+l)*3+(i+k)*iLength+2])*
                         (lpWrk[j*3+i*iLength+2]-lpWrk[(j+l)*3+(i+k)*iLength+2]);

                      dg=(lpWrk[j*3+i*iLength+1]-lpWrk[(j+l)*3+(i+k)*iLength+1])*
                         (lpWrk[j*3+i*iLength+1]-lpWrk[(j+l)*3+(i+k)*iLength+1]);

                      db=(lpWrk[j*3+i*iLength]-lpWrk[(j+l)*3+(i+k)*iLength])*
                         (lpWrk[j*3+i*iLength]-lpWrk[(j+l)*3+(i+k)*iLength]);

                      if (dr+dg+db>iDiv) {

                         (j,i)に対するぼかし処理
                         done=1;

                      }

            }

        }

次に実際にぼかし処理を行うmix()はこんな感じ。なお、処理対象のピクセル列は、判定に関しては元のピクセル列のコピー、mix()では元のピクセル列そのもの(lpPixel)となっています。判定にコピーを使うのは、判定中にピクセル列をぼかして変更するからですね。

  void mix(int x,int y) {

      int i,j,r=0,g=0,b=0,n=0;

      for (i=-1;i<=1;i++)
          for (j=-1;j<=1;j++) {

              if (x+j>=0 && x+j<iWidth && y+i>=0 && y+i<iHeight) {

                  r+=lpWrk[(x+j)*3+(y+i)*iLength+2];
                  g+=lpWrk[(x+j)*3+(y+i)*iLength+1];
                  b+=lpWrk[(x+j)*3+(y+i)*iLength];

                  n++;

              }

          }

      r=(int)(r/n);
      g=(int)(g/n);
      b=(int)(b/n);

      lpPixel[x*3+y*iLength+2]=(BYTE)r;
      lpPixel[x*3+y*iLength+1]=(BYTE)g;
      lpPixel[x*3+y*iLength]=(BYTE)b;

  }

アンチエイリアシング処理を行うプログラム

24ビットフルカラーBMPファイルを読み込んでアンチエイリアスをかけるサンプルコードです。

BMPファイルをドロップして実行ボタンをクリックすると、画像全体にアンチエイリアシング処理を行います。処理対象とするか判定するパラメータiDivの値をいろいろ変えて結果を比べてみると良いでしょう。この値が小さいほど処理される領域が大きくなり影響も増しますが、この場合は輪郭以外の部分までぼけてきます。
一方、値を大きくすると効果は薄れますが、輪郭以外の部分への影響が避けられるので、実際にこの処理を使う場面では画質を見ながらこのあたりのバランスを考える必要がありますね。

プログラムソース表示

実際にいくつかの画像で試してみると、やや高めの精度でレンダリングした3D画像では輪郭がかなり滑らかになるようです(一方、精度が低く輪郭が大きくブロック化していると効果が薄れる)。3Dグラフィックのアンチエイリアシング処理はオーバーサンプリングを行う場合などにはかなり時間がかかるので、今回のようなレンダリング結果に対して行う簡易版の処理で代替すると有効な場合もあるかもしれません。また、低解像度で文字や図形を描く場合にも表示品質を上げる効果が期待できるでしょう。