2006. 9.23.
石立 喬
Visual C++ 2005 Express Edition を用いた易しい画像処理(5)
――3×3画素のオペレータを用いた画像の平滑化、鮮鋭化、線画化――
「易しい画像処理(4)」までで紹介した、ピクセル毎の単独処理とは別に、近傍ピクセル値を含めて演算を行う画像処理がある。この代表的なものに、コンボリューションと呼ぶ処理がある。 コンボリューションは、原画像の平滑化(ノイズを除去したり、軟調化したりする)、鮮鋭化(ボケた画像をシャープにする)、エッジ検出(線画のように輪郭を抽出する)などに広く用いられる。
コンボリューション(Convolution)
コンボリューションは、「畳み込み」と訳され、図1のように、原画像とカーネル(Kernel、核心部)またはオペレータ(Operator、演算子)の間で積和演算を行うことである。Java2
Standard Edition 6.0 には、ConvolveOpクラスがあり、制約はあるものの、使いやすいメソッドがある。.NET
Framework 2.0 には、このようなクラスが用意されていないので、コンボリューション用のメソッドを自作した。
図1 コンボリューションの方法
オペレータには、用途に応じて色々なマトリックスが用いられる。図2は、その一例で、他にも縦方向、横方向専用のエッジ検出などがあり、ガウスぼかしには、σに応じてさらに大きいマトリックスも用いられる。
図2 オペレータの一例
プログラムの概要
プログラムは、次の部分からなっている。
◎Form1起動時に実行するもの
1) 原画像を読み込む。
2) 原画像から二次元のピクセルデータ(Color構造体の)を取得する。
3) 周辺処理のために、原画像の二次元ピクセルデータを拡張(四辺を外側にコピー)する。
4) 平滑化オペレータでコンボリューションする。
5) 鮮鋭化オペレータでコンボリューションする。
6) エッジ検出オペレータでコンボリューションする。
7) 上記のエッジ検出結果に対して、R、G、B各成分のいずれかが閾値(ここでは20を使用)を超える場合は濃度dを0(黒)に、それ以外は255(白)に設定する。
◎Form1が画面に表示される度に実行するもの
原画像、平滑化画像、エッジ検出画像を描画する。
プログラム
private:
/// <summary>
/// 必要なデザイナ変数です。
/// </summary>
System::ComponentModel::Container ^components;
static int WIDTH=320,HEIGHT=240;
Bitmap^ bmap_src;
Bitmap^ bmap_blur;
Bitmap^ bmap_sharp;
Bitmap^ bmap_edge;
private: System::Void Form1_Load(System::Object^ sender, System::EventArgs^ e)
{
int i,j;
Color color;
int r,g,b,d;
bmap_blur=gcnew Bitmap(WIDTH,HEIGHT);
bmap_sharp=gcnew Bitmap(WIDTH,HEIGHT);
bmap_edge=gcnew Bitmap(WIDTH,HEIGHT);
array<Color,2>^ pdata=gcnew array<Color,2>(WIDTH,HEIGHT); //原画像のピクセルデータ
array<Color,2>^ pdata1=gcnew array<Color,2>(WIDTH+2,HEIGHT+2); //拡張画像のピクセルデータ
array<double,2>^ operator1={{0.11,0.11,0.11}, //平滑化
{0.11,0.12,0.11},
{0.11,0.11,0.11}};
array<double,2>^ operator2={{ 0.0,-0.3, 0.0}, //鮮鋭化
{-0.3,
2.2,-0.3},
{
0.0,-0.3, 0.0}};
array<double,2>^ operator3={{ 0.0,-1.0, 0.0}, //エッジ検出
{-1.0,
4.0,-1.0},
{
0.0,-1.0, 0.0}};
//画像ファイルを読み込んで原画像とする
bmap_src=gcnew Bitmap("A:/sample.jpg");
//Bitmapを二次元PixelDataにする
for(j=0;j<HEIGHT;j++)
for(i=0;i<WIDTH;i++)
pdata[i,j]=bmap_src->GetPixel(i,j); //二次元配列の添え字の書き方に注意!!
//二次元PixelDataを拡張する
//中心部
for(j=0;j<HEIGHT;j++)
for(i=0;i<WIDTH;i++)
pdata1[i+1,j+1]=pdata[i,j];
//上辺と下辺
for(i=0;i<WIDTH+2;i++){
pdata1[i,0]=pdata1[i,1];
pdata1[i,HEIGHT+1]=pdata1[i,HEIGHT];
}
//左辺と右辺
for(j=0;j<HEIGHT+2;j++){
pdata1[0,j]=pdata1[1,j];
pdata1[WIDTH+1,j]=pdata1[WIDTH,j];
}
//拡張PixelDataとKernelでコンボリューションする
bmap_blur=makeConvolution(pdata1,operator1); //平滑化
bmap_sharp=makeConvolution(pdata1,operator2); //鮮鋭化
bmap_edge=makeConvolution(pdata1,operator3); //エッジ検出
//エッジ検出画像を閾値を用いて反転
for(j=0;j<HEIGHT;j++)
for(i=0;i<WIDTH;i++){
color=bmap_edge->GetPixel(i,j);
r=color.R;
g=color.G;
b=color.B;
if(r>20 || g>20 || b>20) d=0;
else d=255;
bmap_edge->SetPixel(i,j,Color::FromArgb(d,d,d));
}
}
private: System::Void Form1_Paint(System::Object^ sender,
System::Windows::Forms::PaintEventArgs^
e) {
Graphics^ gr=e->Graphics;
int X0=10,Y0=10;
int X1=340,Y1=260;
//原画像を描画する
gr->DrawImage(bmap_src,X0,Y0,WIDTH,HEIGHT);
//加工画像を描画する
gr->DrawImage(bmap_blur,X1,Y0); //平滑化
gr->DrawImage(bmap_sharp,X0,Y1); //鮮鋭化
gr->DrawImage(bmap_edge,X1,Y1); //エッジ検出
}
private: Bitmap^ makeConvolution(array<Color,2>^ pdata1,array<double,2>^ opr){
int i,j,m,n;
Color color;
int r,g,b;
double r1,g1,b1;
Bitmap^ bmap=gcnew Bitmap(WIDTH,HEIGHT);
//拡張PixelDataとKernelでコンボリューションする
for(j=0;j<HEIGHT;j++)
for(i=0;i<WIDTH;i++){
r1=0.0;g1=0.0;b1=0.0;
for(n=-1;n<=1;n++)
for(m=-1;m<=1;m++){
color=pdata1[i+m+1,j+n+1];
r1+=color.R*opr[m+1,n+1];
g1+=color.G*opr[m+1,n+1];
b1+=color.B*opr[m+1,n+1];
}
r=(int)r1;
if(r<0) r=0;
else if(r>255) r=255;
g=(int)g1;
if(g<0) g=0;
else if(g>255) g=255;
b=(int)b1;
if(b<0) b=0;
else if(b>255) b=255;
bmap->SetPixel(i,j,Color::FromArgb(r,g,b));
}
return bmap;
}
得られた結果
図3の左上は原画像で、意図的にソフトな画像を使用している。右上は原画像を平滑化した画像で、さらにソフトになっている。左下は、鮮鋭化した画像で、原画像がソフトにもかかわらず、十分シャープに見える。右下は、原画像のエッジを検出し、一定量以上の場所を黒く表示している。簡単なエッジ検出オペレータを使用したので、十分満足な画像が得られたとは言えない。
図3 得られた結果