アンシャープマスクによる先鋭化

画像を先鋭化する(輪郭など変化の激しい部分を強調する)処理として「アンシャープマスク」と呼ばれる手法があります。見かけ上シャープにするのに、「アンシャープ」とはちょっと変な名前ですね。

アンシャープマスクは画像をぼかしたマスクのことで、そのマスクを使って処理することから画像先鋭化のアルゴリズム自体も「アンシャープマスク」と呼ばれているようです。今回はこのアンシャープマスクの意味を、ぼかした画像と元画像との「差分」に注目しながら考えてみる事にしましょう。

アンシャープマスク(ぼかし画像)との差分

アンシャープマスクでは、まず画像をぼかしそのぼかした画像と元画像との「差分」をとります。そして、この差分を調整して元画像に加える事で先鋭化処理を行っています。

では、この差分を求めるアルゴリズムにはどんな意味があるのでしょうか? まず画像をぼかす処理には、ある範囲のピクセルを平均化する、つまりピクセル間の「変化」を平均化によって均す効果がありました。という事は、このぼかし画像と元画像を比べてみると「変化が激しい部分」では、元画像の変化にぼかし画像の変化が追いつかないため、「ずれ」が生じるはずですね。

例えば、元画像のピクセルのある成分が(32,32,32,60,64,64,64)と続いている場合、これを3ピクセル単位で平均化してぼかすと(32,32,41,52,63,64,64)という感じで変化が均されています。この状態で元画像とぼかし画像との差を取ると、ちょうど激しく変化している部分の絶対値が大きくなるわけです(逆に、変化が少ない部分は平均化した結果との差が小さい)。

つまり、差の絶対値が大きな部分は元画像で急激に変化している部分となり、しかも差分には変化の「方向」も反映されます。このため、この差分を元画像と合成すると「変化が強調」されるわけです。

 では、実際にプログラムを作ってアンシャープマスク(ぼかし画像)と差分画像を作成し本当に変化が激しい部分が差分に反映されるか、見てみる事にしましょう。今回は、ビットマップを32ビットDIBとして読み込み、そのピクセル列をLPDWORD型変数lpPixelに格納しています。

 まず、ぼかし画像を作りそれをlpWrk1に格納して行きましょう。この画像処理は単純にiRangeで指定された一定の範囲内のRGB成分を平均化するだけです。例えば、ピクセル(x, y)は(x-iRange, y-iRange)から(x+iRange, y+iRange)のピクセルを成分ごとに平均化しています。

  LPDWORD lpWrk1=(LPDWORD)GlobalAlloc(GPTR,iWidth*iHeight*4);

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

          r=g=b=v=0;

          for (k=-iRange;k<=iRange;k++)
              for (l=-iRange;l<=iRange;l++)
                  if (j+l>=0 && j+l<iWidth && i+k>=0 && i+k<iHeight) {

                      r+=(lpPixel[(j+l)+(i+k)*iWidth] & 0x00ff0000) >> 16;
                      g+=(lpPixel[(j+l)+(i+k)*iWidth] & 0x0000ff00) >> 8;
                      b+=lpPixel[(j+l)+(i+k)*iWidth] & 0x000000ff;

                      v++;

                  }

          r/=v;
          g/=v;
          b/=v;

          lpWrk1[j+i*iWidth]=(r << 16)+(g << 8)+b;

      }

 次に元画像とぼかし画像とのピクセル毎の成分差dr, dg, dbを求めます。そして、この成分差を表示用ビットマップのピクセル列に書き込んでいきましょう。今回は、成分差の値の変化を見るために各成分を128+成分差×2、という形にしてみました。

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

          r1=(lpPixel[j+i*iWidth] & 0x00ff0000) >> 16;
          g1=(lpPixel[j+i*iWidth] & 0x0000ff00) >> 8;
          b1=lpPixel[j+i*iWidth] & 0x000000ff;

          r2=(lpWrk1[j+i*iWidth] & 0x00ff0000) >> 16;
          g2=(lpWrk1[j+i*iWidth] & 0x0000ff00) >> 8;
          b2=lpWrk1[j+i*iWidth] & 0x000000ff;

          dr=r1-r2;
          dg=g1-g2;
          db=b1-b2;

          r=128+dr*2;
          g=128+dg*2;
          b=128+db*2;

          if (r<0)
              r=0;
          if (r>255)
              r=255;

          if (g<0)
              g=0;
          if (g>255)
              g=255;

          if (b<0)
              b=0;
          if (b>255)
              b=255;

          lpPixel[j+i*iWidth]=(r << 16)+(g << 8)+b;

      }

プログラム

 適当な24ビットフルカラーBMPをドロップしたら、実行ボタンでぼかし画像との差分を表示させてみましょう。輪郭や変化の激しい部分が浮き出てくる様子がわかりますね。線画調への変換にも応用できるかもしれません。

 ピクセル色成分の平均化を行う範囲の大きさは変数iRangeで設定しているので、この値をいじってみるのもおもしろいでしょう。


創作プログラミングの街