第 3 章 画像処理入門 1~ アルゴリズム入門 ~ サンプルプログラムのダウンロード (msdnaa_algorithm03.exe、102 KB)
3.1 はじめに 近年の情報技術の発達に伴い、コンピュータによる画像処理技術も大きく進歩しました。画像処理技術は、医療、工業、そしてマスメディアなど様々な分野で活用されています。例えば、医療におけるレントゲン写真の解析、工業における部品の自動認識、そしてセキュリティ分野における顔認証など、様々な分野で利用されています。また、デジタルカメラなどの発達と普及を受け、デジタル画像はより身近なものになっています。そのため、画像処理の重要性は、増加の一途を辿っています。 代表的な画像処理のアルゴリズムには、画像の色情報の変換やオブジェクト抽出などがあります。このようなアルゴリズムは、画像処理を利用したシステムで広く用いられていますが、プログラムで実現するために複雑なコードを記述する必要があり、開発者にとって大きな負担となっています。しかし、.NET Framework には、画像に対してピクセル単位の処理を行うためのクラスやメソッドが用意されているため、Visual C# では、2 値化処理やフィルタ処理などの画像処理プログラムを非常に簡単なコードで記述できます。 本章では、Visual C# を利用して、画像処理の中でも閾値処理とラベリング処理による領域抽出やフィルタを用いたエッジ検出処理について解説します。 3.2 閾値処理 画像処理には、画像から特定の領域のみを取り出す技術があります。これは、例えば、写真の風景の中から特定の人物やものを取り出すことや、監視カメラの映像中から侵入者を見つけ出すために利用する技術です。テレビ番組などの映像の編集でよく利用される手法としては、背景を特定の色で撮影し、その色を取り除くことで特定の領域を取り出すものがあります。しかし、通常の画像において、背景が一色であるとは限りません。そこで、閾値処理などにより特定の領域を抽出する必要があります。本節では、この閾値処理について解説します。閾値処理の例を図 1 に示します。 .jpg)
図 1 閾値処理の例 3.2.1 閾値処理とは閾値処理とは、画像に対して 2 値化を行うことです。2 値化とは、各画素の明るさを一定の基準値により、黒色と白色の 2 つの値に変換する処理のことです。また、一定の基準値のことを閾値と言います。通常、画像の各画素は、0~255 の RGB 値を持っています。この RGB 値の平均値が各画素における明るさになります。基準とする閾値の値によって、閾値処理後の画像は異なります。画素 (x, y) の明るさ f(x, y) に対する閾値 T での閾値処理の式を次に示します。 .gif)
3.2.2 閾値処理プログラム 本プログラムでは、画像に閾値処理を行います。まず、Bitmap クラスを利用して、画素の明るさを算出します。次に、画素の明るさが指定された閾値以下なら白色を画素に設定します。そして、それ以外の場合は、画素に黒色を設定します。 private void FormationOfTwoValues(int iThresh)
{
int i, j, iAverage; // 変数の宣言 iAverage : RGB 値の平均値
Bitmap bBitmap = new Bitmap(pictureBox1.Image); // 変数の宣言
Color cColor; // 変数の宣言
// 画像の 2 値化の実行
for(i = 0; i < pictureBox1.Image.Width; i++)
for(j = 0; j < pictureBox1.Image.Height; j++)
{
cColor = bBitmap.GetPixel(i, j); // ピクセルの色の取得
iAverage = GetColorAverage(cColor); // RGB の平均値の取得
// RGB 平均値が閾値以下の場合
if(iAverage <= iThresh)
bBitmap.SetPixel(i, j, Color. White); // 白色に設定
// RGB 平均値が閾値より大きい場合
else
bBitmap.SetPixel(i, j, Color. Black); // 黒色に設定
}
pictureBox1.Image = bBitmap; // 結果の表示
}本プログラムの実行結果を図 2 に示します。 .gif)
図 2 実行結果 3.3 ラベリング処理 デジタル画像は、アナログ画像と異なり画素単位で独立して色情報を表現しています。そのため、前節で解説した閾値処理を行うだけでは、画像内のオブジェクトの認識や抽出などの処理を行うことはできません。このような処理を行うには、画素単位で独立した色情報に属性を付加する必要があります。画像処理では、属性を付加する手法の1つに、各画素にグループ属性を付加するラベリング処理があります。ラベリング処理は、画像内に複数のオブジェクトが存在する場合、対象とする領域を識別する手法として非常に有効です。本節では、このラベリング処理について解説します。ラベリング処理のイメージ図を図 3 に示します。 .gif)
図 3 ラベリング処理のイメージ図 3.3.1 ラベリング処理とはラベリング処理を行った 2 値化画像では、各画素にラベル (番号) を属性として付加することにより、特定の領域を抽出できます。ラベリング処理とは、連結している画素に同じラベルを付加することで複数の領域をグループとして分類することです。ラベリング処理の手法には、様々な種類がありますが、代表的な手法である 4 近傍によるラベリング処理のステップを次に解説します。 まず、画像上でラベルが付加されていない画素 (x, y) を見つけ、新しいラベルを付加します。次に、画素 (x, y) に 4 方向に連結している画素に同じラベルを付加します。最後に、同じラベルを付加した画素と 4 方向に連結している画素に対して、同じラベルを付加します。画像内にラベルを付加する画素がある限り、この操作を繰り返します。画像内のラベル付けする画素を検索することを走査と言います。ラベリング処理の流れを図 4 に示します。 .gif)
図 4 ラベリング処理の流れ 3.3.2 ラベリング処理によるオブジェクト抽出処理プログラム 本プログラムでは、画像にラベリング処理を行います。処理の流れとしては、まず、指定した閾値を基準として画像の閾値処理を行います。次に、全画素に対してラベリング処理を行います。最後に、ラベリング処理の結果を視覚的に分かるように、走査ラベルにより指定されたラベルに該当する領域を白色で抜き出しています。 private void Labeling (int iSetLabel)
{
(中略)
// 画像の色情報の取得
for(i = 0; i < pictureBox1.Image.Width; i++)
for(j = 0; j < pictureBox1.Image.Height; j++)
{
cColor = bBitmap.GetPixel(i, j); // ピクセルの色の取得
iArrayValue[i, j] = cColor.R; // ピクセルの色の設定
}
// ラベリング
for(i = 0; i < pictureBox1.Image.Width; i++)
for(j = 0; j < pictureBox1.Image.Height; j++)
// 白色の場合
if(iArrayValue[i, j] == Color.White.B)
{
// ラベルの設定
SetLabel(ref iArrayValue, i, j, iLabel);
iLabel++; // 設定ラベルの加算
}
// ラベル値による画像の抜き出し
for(i = 0; i < pictureBox1.Image.Width; i++)
for(j = 0;j < pictureBox1.Image.Height; j++)
// 設定したラベルの場合
if(iArrayValue[i, j] == iSetLabel)
bBitmap.SetPixel(i, j, Color.White); // 白色に設定
// 設定したラベル以外の場合
else
bBitmap.SetPixel(i, j, Color.Black); // 黒色に設定
pictureBox1.Image = bBitmap; // 変更結果の設定
}
private void SetLabel(ref int[,] iArray, int iStartX, int iStartY, int iLabel)
{
// 変数の宣言 iCount : 設定ピクセル数 im, ip, jm, jp : 走査基準位置の上下左右位置
int i, j, iCount, im, ip, jm, jp ;
iArray[iStartX, iStartY] = iLabel; // 開始位置のラベルの設定
// ラベルの設定処理
do
{
iCount = 0; // 初期化
// ラベルの設定
for(i = 0; i < pictureBox1.Image.Width; i++ )
for(j = 0; j < pictureBox1.Image.Height; j++ )
// スタート位置を基準としたラベルの設定
if(iArray[i, j] == iLabel)
{
im = i - 1; // im値の設定
ip = i + 1; // ip値の設定
jm = j - 1; // jm値の設定
jp = j + 1; // ip値の設定
(中略)
// [i, jp]のピクセルの色が白色の場合
if(iArray[i, jp] == Color. White.B)
{
// ラベルの設定
iArray[i, jp] = iLabel;
iCount++; // 設定数のカウント
}
// [im, j]のピクセルの色が白色の場合
if(iArray[im, j] == Color. White.B)
{
// ラベルの設定
iArray[im, j] = iLabel;
iCount++; // 設定数のカウント
}
// [im, jm]のピクセルの色が白色の場合
if(iArray[im, jm] == Color. White.B)
{
// ラベルの設定
iArray[im, jm] = iLabel;
iCount++; // 設定数のカウント
}
// [i, jm]のピクセルの色が白色の場合
if(iArray[i, jm] == Color. White.B)
{
// ラベルの設定
iArray[i, jm] = iLabel;
iCount++; // 設定数のカウント
}
// [ip, jm]のピクセルの色が白色の場合
if(iArray[ip, jm] == Color. White.B)
{
// ラベルの設定
iArray[ip, jm] = iLabel;
iCount++; // 設定数のカウント
}
// [ip, jp]のピクセルの色が白色の場合
if(iArray[ip, jp] == Color. White.B)
{
// ラベルの設定
iArray[ip, jp] = iLabel;
iCount++; // 設定数のカウント
}
}
}
while(iCount != 0); // 設定数が0の場合ループ終了
} 本プログラムでは、前処理として閾値処理を行うため、画像の濃淡に応じて適切な閾値を指定する必要があります。また、抽出する画素のグループは、走査ラベルにより指定できます。走査ラベルに 1 を指定すると最初にラベリング処理されたグループを抽出できます。図 5 の実行結果の場合は、"M" は 1、"S" は 2、そして "D" は 3・・・というラベルでグループ化されています。 .gif)
図 5 実行結果 3.4 エッジ画像処理 人は、外形を表す輪郭をもとに物を見て理解しています。そのため、人間の物体認識において、輪郭は、非常に重要な要素と言えます。画像処理においても輪郭は、オブジェクトを識別するための重要な要素です。画像内のオブジェクトの輪郭を抽出することにより、特定のオブジェクトの抜き出し、2 つの画像間の対応点算出、そして、さらに複雑な画像認識のための前処理などの様々な処理を行います。そのため、画像内のオブジェクトの輪郭を抽出するエッジ検出は、画像処理において多く利用されています。そこで、本節では、エッジ検出について解説します。エッジ処理の例を図 6 に示します。 .gif)
図 6 エッジ処理の例 3.4.1 エッジ検出とは 画像内のすべての画素は、色情報を持っています。その画像内の明るさが急激に変化する場所がオブジェクトの領域の境界、つまり輪郭となります。エッジ検出とは、画像の明るさの変化によりオブジェクトの輪郭を算出することです。明るさの変化値は、微分演算を利用することで算出できます。微分には、グラディエント (1 次微分) とラプラシアン (2 次微分) があります。 しかし、デジタル画像は連続ではないため、厳密には微分演算は行えません。そこで、隣接する画素の微分値の近似値を差分により算出する必要があります。隣接する画素の差分は、微分パラメータを用いて画素に重み付けをすることにより算出できます。この差分値が隣接する画素の微分値の近似値となります。 (1) グラディエント (1 次微分)画像上の座標 (x, y) における明るさの勾配は、グラディエントにより算出できます。また、この微分値は、ベクトル (⊿x, ⊿y) として表現できます。微分と強さを算出する式を次に示します。 【微分】
.gif)
【強さ】
.gif)
グラディエントに用いるオペレータを表 1 に示します。 表 1 グラディエントに用いるオペレータ | オペレータ名 | 通常の差分 | Roberts オペレータ | Sobel オペレータ | | ⊿x を算出するオペレータ | 0 0 0 0 1 -1 0 0 0 | 0 0 0 0 1 0 0 0 -1 | -1 0 1 -2 0 2 -1 0 1 | | ⊿y を算出するオペレータ | 0 0 0 0 1 0 0 -1 0 | 0 0 0 0 0 1 0 -1 0 | -1 -2 1 0 0 0 1 2 1 |
(2) ラプラシアン (2 次微分) ラプラシアンは、グラディエントをさらに微分したものです。単純に、画像上の画素 (x, y) における明るさの差分⊿ (x, y) のみを表します。ラプラシアンの式を次に示します。 .gif)
ラプラシアンに用いるオペレータを表 2 に示します。 表 2 ラプラシアンに用いるオペレータ | オペレータ名 | ラプラシアン 1 | ラプラシアン 2 | ラプラシアン3 | | ⊿(x,y) を算出するオペレータ | 0 -1 0 -1 4 -1 0 -1 0 | -1 -1 -1 -1 8 -1 -1 -1 -1 | 1 -2 1 -2 4 -2 1 -2 1 |
3.4.2 エッジ検出処理プログラム 本プログラムでは、エッジ検出に代表的なラプラシアン 2 を用いた2 次微分を行っています。まず、画素の明るさを取得しやすくするために、画像をモノクロ画像に変換します。次に、ラプラシアン 2 により、画素の明るさの差分を算出し、指定された出力エッジ画像のレベルに応じて、その結果を画素に設定します。ラプラシアン 2 によるエッジ検出では、出力エッジ画像のレベルを 4、5 くらいに設定すると輪郭を鮮明に表示できます。しかし、使用する微分オペレータによって、適切なレベルの設定値は異なります。そのため、使用する微分オペレータに応じて適切な出力エッジ画像のレベルを指定する必要があります。 private void GrayImage ()
{
(中略)
// 画像のグレースケールへの変換
for(i = 0; i < pictureBox1.Image.Width; i++)
for(j = 0; j < pictureBox1.Image.Height; j++)
{
cColor = bBitmap.GetPixel(i, j); // ピクセルの色の取得
iRed = cColor.R; // Red 値の設定
iBlue = cColor.B; // Blue 値の設定
iGreen = cColor.G; // Green 値の設定
iAverage = (iRed + iBlue + iGreen) / 3;// RGB の平均値を算出
// iAverage 値による色の設定
cSetColor = Color.FromArgb(iAverage, iAverage, iAverage);
bBitmap.SetPixel(i, j, cSetColor); // ピクセルの色の設定
}
pictureBox1.Image = bBitmap; // 変更結果の設定
}
private void Laplacian(int iAmp)
{
int i, j, iColorValue; // 変数の宣言 iColorValue : RGB平均値
// 変数の宣言 iFilter : ラプラシアンフィルタ
int[] iFilter = new int[]{-1, -1, -1, -1, 8, -1, -1, -1, -1};
// 変数の宣言 iArrayValue : ピクセル色情報の配列
int[,] iArrayValue = new int[pictureBox1.Image.Width, pictureBox1.Image.Height];
Color[] cArrayColor = new Color[9]; // 変数の宣言 cArrayColor : 色情報の配列
Color cSetColor; // 変数の宣言 cSetColor : 設定する色
Bitmap bBitmap = new Bitmap(pictureBox1.Image); // 変数の宣言
// 画像に対するフィルタ処理
for(i = 1; i < pictureBox1.Image.Width - 1; i++)
for(j = 1; j < pictureBox1.Image.Height - 1; j++)
{
(中略)
// フィルタ処理
iColorValue =
iFilter[0]*cArrayColor[0].R + iFilter[1]*cArrayColor[1].R +
iFilter[2]*cArrayColor[2].R + iFilter[3]*cArrayColor[3].R +
iFilter[4]*cArrayColor[4].R + iFilter[5]*cArrayColor[5].R +
iFilter[6]*cArrayColor[6].R + iFilter[7]*cArrayColor[7].R +
iFilter[8]*cArrayColor[8].R;
iColorValue = iAmp*iColorValue; // 出力レベルの設定
// iColorValue が負の場合
if(iColorValue < 0)
iColorValue = -iColorValue; // 正の値に変換
// iColorValueが255より大きい場合
if(iColorValue > 255)
iColorValue = 255; // iColorValue を 255 に設定
iArrayValue[i, j] = iColorValue;// iColorValue の設定
}
// フィルタ処理の結果の出力
for(i = 1; i < pictureBox1.Image.Width - 1; i++)
for(j = 1; j < pictureBox1.Image.Height - 1; j++)
{
// iArrayValue 値による色の設定
cSetColor = Color.FromArgb(iArrayValue[i, j],
iArrayValue[i, j],iArrayValue[i, j]);
// ピクセルの色の設定
bBitmap.SetPixel(i, j, cSetColor);
}
pictureBox1.Image = bBitmap; // 変更結果の設定
}本プログラムの実行結果を図 7 に示します。 .gif)
図 7 実行結果 3.5 画像のピクセル処理とファイル処理 画像処理において、画像のピクセル単位での処理や表示と保存のためのインターフェースは、必要不可欠です。Visual C# では、.NET Framework に用意されたクラスを利用することにより、画像のピクセル単位での処理や表示と保存を行うことができます。本節では、まず、Bitmap クラスについて解説し、次に、OpenFileDialog クラスと SaveFileDialog クラスを利用した画像の表示と保存について解説します。 3.5.1 Bitmap クラス 画像処理を行う場合は、画像のピクセル形式や色情報の変更などの処理をピクセル単位で行う必要があります。Visual C# では、Bitmap クラスを利用することにより、画像に対してピクセル単位で様々な処理を行うことができます。Bitmap クラスは、System.Drawing.Bitmap 名前空間に存在します。Bitmap インスタンスを生成する構文を次に示します。 | Bitmap オブジェクト名 = new Bitmap (画像イメージ); |
Bitmap クラスの主なプロパティを表 3 に示します。 表 3 Bitmap クラスの主なプロパティ | プロパティ名 | 型 | 説明 | | Height | int | Image オブジェクトの高さ | | Width | int | Image オブジェクトの幅 | | PixelFormat | PixelFormat | Image オブジェクトのピクセル形式を取得 | | Size | Size | Image オブジェクトの幅と高さを取得 |
Bitmap クラスの主なメソッドを表 4 に示します。 表 4 Bitmap クラスの主なメソッド | メソッド名 | 型 | 説明 | | GetPixel | Color | Bitmap の指定したピクセルの色を取得 | | SetPixel | void | Bitmap オブジェクトの指定のピクセル色を設定 |
(1) OpenFileDialog クラスVisual C# では、画像を表示する場合は、PictureBox コントロールを利用するのが一般的です。実際に PictureBox コントロールにユーザが指定した画像を表示するには、そのファイル名を引き渡す必要があります。OpenFileDialog クラスを利用することにより、PictureBox コントロールにユーザが指定したファイル名を引き渡すことができます。OpenFileDialog インスタンスを生成する構文を次に示します。 OpenFileDialog オブジェクト名 = new OpenFileDialog (); OpenFileDialog クラスの主なプロパティを表 5 に示します。 表 5 OpenFileDialog クラスの主なプロパティ | プロパティ名 | 型 | 説明 | | Title | 文字列 | ダイアログに表示するタイトルを取得または設定 | | DefaultExt | 文字列 | 標準の拡張子を取得または設定 | | FileName | 文字列 | 選択されたファイル名を含む文字列を取得または設定 | | Filter | 文字列 | ダイアログで表示するファイルの種類を取得または設定 |
OpenFileDialog クラスの主なメソッドを表 6 に示します。 表 6 OpenFileDialog クラスの主なメソッド | メソッド名 | 説明 | | ShowDialog | ダイアログボックスの表示 | | Reset | プロパティ値の初期化 |
(2) SaveFileDialog クラスPictureBox コントロールを利用して表示されている画像を保存する場合は、保存先のファイル名を引き渡す必要があります。Visual C# では、SaveFileDialog クラスを利用することにより、PictureBox コントロールにユーザが指定したファイル名を引き渡すことができます。SaveFileDialog インスタンスを生成する構文を次に示します。 | SaveFileDialog オブジェクト名 = new SaveFileDialog (); |
SaveFileDialog クラスには、OpenFileDialog クラスと同様のプロパティとメソッドが用意されていますが、OpenFileDialog クラスと異なるプロパティもあります。OpenFileDialog クラスと異なるプロパティを表 7 に示します。 表 7 OpenFileDialog クラスと異なるプロパティ | プロパティ名 | 型 | 説明 | | CreatePrompt | TRUE | 存在しないファイルをユーザが指定した場合、新しいファイルを作成するかの確認メッセージを表示 | | FALSE | 確認メッセージの非表示 | | OverwritePrompt | TRUE | 既存のファイルをユーザが指定した場合、上書きの確認メッセージの表示 | | FALSE | 確認メッセージの非表示 |
3.6 まとめ本章では、Visual C# による画像処理の代表的な手法である閾値処理、ラベリング処理、そしてエッジ検出について解説しました。Visual C# には、ピクセル単位での処理を扱う便利なクラスやメソッドが用意されているため、複雑な画像処理を扱うシステムも非常に高い効率で作成することができます。次回の連載では、さらに一歩踏み込んで、画像の色情報の調整や幾何変換処理について解説します。
アルゴリズム入門 本連載は、テキスト処理、グラフィック処理、画像処理、知識情報処理などのアルゴリズムを Visual C# を用いて解説します。
著者略歴田中 成典 (たなか しげのり)| 1986 年 | 関西大学工学部土木工学科卒業 | | 1988 年 | 関西大学大学院工学研究科 土木工学専攻博士課程前期課程修了 | | 1996 年 | 博士 (工学) 授与,関西大学 | | 1997 年 | 関西大学総合情報学部助教授 (現在に至る) | | 主な著書: | やさしい C のはじめかた,オーム社,1993 年 | | | 建設技術者のための知識情報処理の実践,関西大学出版部,1999 年 | | | DirectX8,工学社,2001 年 | | | ステップアップ XML,工学社,2002 年 | | | Linux アプリケーション入門,森北出版,2002年 ほか |
中山 浩太郎 (なかやま こうたろう)| 2001 年 3 月 | 関西大学総合情報学部総合情報学科卒業 | | 2003 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 | | 2003 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) | | 主な著書: | Web 工房シリーズ Perl の達人,森北出版,1999 年 | | | 決定版 Visual Basic,共立出版,2000年 | | | DirectX8,工学社,2001 年 | | | Linux アプリケーション入門,森北出版,2002 年 | | | ステップアップ Visual C# .NET 入門,工学社,2002 年 ほか |
中村 健二 (なかむら けんじ)| 2000 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) | | 主な著書: | DirectX8 & VC++ 3D の基礎とゲームの作り方,工学社,2002年 |
北川 悦司 (きたがわ えつじ)| 2000 年 3 月 | 関西大学総合情報学部総合情報学科卒業 | | 2002 年 3 月 | 関西大学大学院総合情報学研究科 博士課程前期課程修了 | | 2002 年 4 月 | 関西大学大学院総合情報学研究科 博士課程後期課程入学 (現在に至る) | | 主な著書: | Web 工房シリーズ Java の達人,森北出版,1999 年 | | | デジカメ活用によるデジタル写真測量入門,森北出版,2000 年 | | | ステップアップ XML 活用法,工学社,2002 年 |
上山 智士 (うえやま さとし)| 2002 年 4 月 | 関西大学総合情報学部総合情報学科入学 (現在に至る) |
杉町 敏之 (すぎまち としゆき)| 2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 | | 2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) | | 主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |
野中 一希 (のなか かずき)| 2003 年 3 月 | 関西大学総合情報学部総合情報学科卒業 | | 2003 年 4 月 | 関西大学大学院総合情報学研究科入学 (現在に至る) | | 主な著書: | ステップアップ Visual C# .NET 入門,工学社,2002 年 |
ページのトップへ
| |