ゆるゆるプログラミング

・エッジ(境界)検出

エッジ検出とは、隣合った画素の明るさの差が指定した値(閾値)が大きいときにエッジとして判定する処理のことです。この処理によって画像中の物体の輪郭だけを抽出することができます。

元の画像元の画像      エッジ検出画像エッジ検出

ここで紹介するエッジ検出方法は、隣合った画素の明るさの差が指定した値(閾値)より大きいときにエッジとして判定する処理です。この処理によって画像中の物体の輪郭だけを抽出することができます。

ピクセル座標(x,y)と右の座標(x+1,y)、下の座標(x,y+1)の明るさの差を計算し、その差が閾値より大きい時に(x,y)をエッジとして検出します。明るさの差は、RGBそれぞれの差の絶対値で計算し、RGBの差のいずれかが閾値を超えたらエッジとして判定します。エッジとして検出された座標の色は黒、検出されなかった色は白で表現します。

Edge01.java ← クリックしてダウンロードページに移動
001:    import java.awt.image.BufferedImage;
002:    import java.io.File;
003:    import javax.imageio.ImageIO;
004:    import java.io.IOException;
005:    
006:    public class Edge01 {
007:    	public static void main( String[] args ) {
008:    		// 結果格納フラグ
009:    		boolean result;
010:    		// 差の閾値
011:    		int gap;
012:    		// ファイル名
013:    		String inname, outname;
014:    		// 画像格納クラス
015:    		BufferedImage img = null;
016:    		BufferedImage newimg = null;
017:    
018:    		// 入力した引数が3つ以上かを調べる
019:    		if ( 3 > args.length ) {
020:    			// 入力した引数が3つ未満の場合、使用方法を表示する
021:    			System.out.println( "Edge01 [入力JPEG名] [出力PNG名] [差の閾値]" );
022:    			return;
023:    		}
024:    
025:    		// 入力JPEG名をinnameに代入(拡張子".jpg"省略なし)
026:    		inname  = args[ 0 ];
027:    		// 出力PNG名をoutnameに代入(拡張子".png"省略なし)
028:    		outname = args[ 1 ];
029:    
030:    		// 引数を変換し、差の閾(しきい)値に代入
031:    		try {
032:    			gap = Integer.valueOf( args[ 2 ] );
033:    			if ( 1 > gap) {
034:    				System.out.println( "差の閾値に1以上を指定してください" );
035:    				return;
036:    			}
037:    		}
038:    		catch( NumberFormatException ne )
039:    		{
040:    			System.out.println( "引数が不正です" );
041:    			return;
042:    		}
043:    
044:    		// 入力画像の読み込み
045:    		try {
046:    			// inname(入力JPEG)を読み込んでimgにセット
047:    			img = ImageIO.read( new File( inname ) );
048:    		} catch (Exception e) {
049:    			// inname(入力JPEG)の読み込みに失敗したときの処理
050:    			 e.printStackTrace();
051:    			return;
052:    		}
053:    
054:    		// 画像の色の持ち方をチェック
055:    		if ( BufferedImage.TYPE_3BYTE_BGR != img.getType() )
056:    		{
057:    			System.out.println( "対応していないカラーモデルです!("
058:    									 + inname +")" );
059:    			return;
060:    		}
061:    
062:    		// 変数を宣言
063:    		int x, y;		// ピクセル座標
064:    		int width, height;	// 画像サイズ
065:    		int color;		// 色
066:    		int newcolor;		// 境界色
067:    		int r, g, b;		// (x,y)の色
068:    		int r_r, g_r, b_r;	// (x,y)の右(x+1,y)の色
069:    		int r_d, g_d, b_d;	// (x,y)の下(x,y+1)の色
070:    		int r_a, g_a, b_a;	// 境界判定した色(黒or白)
071:    
072:    		// 画像サイズの取得
073:    		width = img.getWidth();
074:    		height= img.getHeight();
075:    
076:    		// 新しい画像を作成
077:    		// 24ビットカラーの画像を作成
078:    		try {
079:    			newimg = new BufferedImage( width, height,
080:    						 BufferedImage.TYPE_INT_RGB );
081:    		} catch ( Exception e ) {
082:    			// 画像作成に失敗したときの処理
083:    			e.printStackTrace();
084:    			return;
085:    		}
086:    
087:    		// エッジ(境界)抽出
088:    		for ( y = 0; y < height; ++ y ) {
089:    			for ( x = 0; x < width; ++ x ) {
090:    				// 境界色の初期値は白色
091:    				r_a = 255;
092:    				g_a = 255;
093:    				b_a = 255;
094:    
095:    				if ( ( ( width - 1 ) != x ) && ( ( height - 1 ) != y ) ) {
096:    					// (x,y)の色を取得し、rgbに分解
097:    					color = img.getRGB( x, y );
098:    					r = ( color >> 16 ) & 0xff;
099:    					g = ( color >> 8 ) & 0xff;
100:    					b = color & 0xff;
101:    
102:    					// (x+1,y)の色を取得し、rgbに分解
103:    					color = img.getRGB( x + 1, y );
104:    					r_r = ( color >> 16 ) & 0xff;
105:    					g_r = ( color >> 8 ) & 0xff;
106:    					b_r = color & 0xff;
107:    
108:    					// 右側の色との差が閾値より大きければ黒色
109:    					if ( ( gap < Math.abs( r - r_r ) )
110:    						 || ( gap < Math.abs( g - g_r ) )
111:    						 	|| ( gap < Math.abs( b - b_r ) ) ) {
112:    						// 黒色に決定
113:    						r_a = 0;
114:    						g_a = 0;
115:    						b_a = 0;
116:    					}
117:    					else {
118:    						// (x,y+1)の色を取得し、rgbに分解
119:    						color = img.getRGB( x, y + 1 );
120:    						r_d = ( color >> 16 ) & 0xff;
121:    						g_d = ( color >> 8 ) & 0xff;
122:    						b_d = color & 0xff;
123:    
124:    						// 下側の色との差が閾値より大きければ黒色
125:    						if ( ( gap < Math.abs( r - r_d ) )
126:    							 || ( gap < Math.abs( g - g_d ) )
127:    							 	|| ( gap < Math.abs( b - b_d ) ) ) {
128:    							// 黒色に決定
129:    							r_a = 0;
130:    							g_a = 0;
131:    							b_a = 0;
132:    						}
133:    					}
134:    				}
135:    
136:    				// r,g,bの色を合成
137:    				newcolor = ( r_a << 16 ) + ( g_a << 8 ) + b_a;
138:    
139:    				// 合成した色を(x,y)に設定
140:    				newimg.setRGB( x, y, newcolor );
141:    			}
142:    		}
143:    
144:    		try {
145:    			// newimgをoutname(出力PNG)に保存
146:    			result = ImageIO.write( newimg, "PNG", new File( outname ) );
147:    		} catch ( Exception e ) {
148:    			// outname(出力JPEG)の保存に失敗したときの処理
149:    			e.printStackTrace();
150:    			return;
151:    		}
152:    
153:    		// 正常に終了
154:    		System.out.println( "正常に終了しました" );
155:    	}
156:    }

Edge01を3つの値で実行

java Edge01 gundam-001.jpg edge01.png 13

1つ目の引数で渡したJPEGファイルエッジを検出し、2つ目の引数で指定したPNGファイル名で保存します。この場合、3つ目の引数は閾値です。

実行結果

・元の画像(gundam-001.jpg)

元画像

・変換後の画像(edge01.png)

エッジ検出画像

それでは、ここからエッジ検出ソースコードを解説していきます。

001:    import java.awt.image.BufferedImage;
002:    import java.io.File;
003:    import javax.imageio.ImageIO;
004:    import java.io.IOException;

Javaのクラスライブラリの中から「java.awt.image.BufferedImage」と「java.io.File」と「javax.imageio.ImageIO」と「java.io.IOException」というパッケージにあるクラスを、このプログラム内で使うために記述します。この記述により、BufferedImageクラスImageIOクラスが利用できるようになります。

006:    public class Edge01 {

クラス名を、Edge01としています。

007:    	public static void main( String[] args ) {

このmainメソッドからプログラムを実行します。

018:    		// 入力した引数が3つ以上かを調べる
019:    		if ( 3 > args.length ) {
020:    			// 入力した引数が3つ未満の場合、使用方法を表示する
021:    			System.out.println( "Edge01 [入力JPEG名] [出力PNG名] [差の閾値]" );
022:    			return;
023:    		}

3つ以上の引数が与えられたかをチェックし、3つ未満の場合に、使い方のメッセージを表示し、returnによってmainメソッドを抜けています。

025:    		// 入力JPEG名をinnameに代入(拡張子".jpg"省略なし)
026:    		inname  = args[ 0 ];
027:    		// 出力PNG名をoutnameに代入(拡張子".png"省略なし)
028:    		outname = args[ 1 ];

与えられた引数を、入力JPG名の変数(inname)と出力PNG名の変数(outname)代入しています。

030:    		// 引数を変換し、差の閾(しきい)値に代入
031:    		try {
032:    			gap = Integer.valueOf( args[ 2 ] );
033:    			if ( 1 > gap) {
034:    				System.out.println( "差の閾値に1以上を指定してください" );
035:    				return;
036:    			}
037:    		}
038:    		catch( NumberFormatException ne )
039:    		{
040:    			System.out.println( "引数が不正です" );
041:    			return;
042:    		}

与えられた引数を色の差の閾値を格納する変数gapに代入しています。

044:    		// 入力画像の読み込み
045:    		try {
046:    			// inname(入力JPEG)を読み込んでimgにセット
047:    			img = ImageIO.read( new File( inname ) );
048:    		} catch (Exception e) {
049:    			// inname(入力JPEG)の読み込みに失敗したときの処理
050:    			 e.printStackTrace();
051:    			return;
052:    		}

入力JPEG名の変数(inname)を読み込んで、BufferedImageクラスのimgに格納しています。この処理には、ImageIOクラスreadメソッドを使います。

ImageIO.readメソッド

public static BufferedImage read( File input ) throws IOException
■Fileオブジェクトを復元した結果をBufferedImageに格納します。

  パラメータ input : Fileオブジェクト

  戻り値     inputを復元したBufferedImageaを返します。

try { ~ } catchは、失敗する可能性がある処理を波括弧で囲み、その処理に失敗したときにcatch { ~ }の波括弧で囲まれた処理を実行するということです。この場合は、JPEGファイル名が不正であったり、存在していなかったり、フォーマットが違っているなどが原因で処理が失敗する可能性があります。処理が失敗するとreturnによってmainメソッドを抜けるようにしています。

054:    		// 画像の色の持ち方をチェック
055:    		if ( BufferedImage.TYPE_3BYTE_BGR != img.getType() )
056:    		{
057:    			System.out.println( "対応していないカラーモデルです!("
058:    									 + inname +")" );
059:    			return;
060:    		}

BufferedImageクラスgetTypeメソッドで画像のイメージ型を取得しています。

BufferedImage.getTypeメソッド

public static int getType()
■イメージ型を返します。
  パラメータ なし

  戻り値     BufferedImage のイメージ型を返します。

062:    		// 変数を宣言
063:    		int x, y;		// ピクセル座標
064:    		int width, height;	// 画像サイズ
065:    		int color;		// 色
066:    		int newcolor;		// 境界色
067:    		int r, g, b;		// (x,y)の色
068:    		int r_r, g_r, b_r;	// (x,y)の右(x+1,y)の色
069:    		int r_d, g_d, b_d;	// (x,y)の下(x,y+1)の色
070:    		int r_a, g_a, b_a;	// 境界判定した色(黒or白)

エッジ検出処理で使う変数を宣言しています。

072:    		// 画像サイズの取得
073:    		width = img.getWidth();
074:    		height= img.getHeight();

widthに読み込んだJPEG画像の画像の幅(ピクセル)、heightに画像の高さ(ピクセル)を代入しています。

076:    		// 新しい画像を作成
077:    		// 24ビットカラーの画像を作成
078:    		try {
079:    			newimg = new BufferedImage( width, height,
080:    						 BufferedImage.TYPE_INT_RGB );
081:    		} catch ( Exception e ) {
082:    			// 画像作成に失敗したときの処理
083:    			e.printStackTrace();
084:    			return;
085:    		}

BufferedImageクラスコンストラクタで、新しいBufferedImageを構築しています。

BufferedImageコンストラクタ

BufferedImage( int width, int height, int imageType )
■新しい BufferedImage を構築します。
  パラメータ width     : 構築する画像の横ピクセル
        height    : 構築する画像の縦ピクセル
        imageType : 構築する画像のイメージ形式

imgTypeで指定しているTYPE_INT_RGBは、整数ピクセルにパックされた 8 ビット RGB 色成分によるイメージを表します。これを指定することで24ビットの画像を作成できます。

087:    		// エッジ(境界)抽出
088:    		for ( y = 0; y < height; ++ y ) {
089:    			for ( x = 0; x < width; ++ x ) {

画像の全ての座標(x,y)を

090:    				// 境界色の初期値は白色
091:    				r_a = 255;
092:    				g_a = 255;
093:    				b_a = 255;

境界色の初期値として白色(RGBの値を全てを255)を代入しておきます。

095:    				if ( ( ( width - 1 ) != x ) && ( ( height - 1 ) != y ) ) {

xが画像の右端、yが画像の下端のときは、エッジ検出処理を行わないようにしています。この処理では、処理対象の画素の座標(x,y)の右と下の座標の画素の色の差で境界かどうか判定しています。よって、色情報が取得できないな右端と下端の座標が処理対象外としています。

096:    					// (x,y)の色を取得し、rgbに分解
097:    					color = img.getRGB( x, y );
098:    					r = ( color >> 16 ) & 0xff;
099:    					g = ( color >> 8 ) & 0xff;
100:    					b = color & 0xff;

ピクセル座標(x,y)の色を取得し、変数r、b、gに代入しています。

102:    					// (x+1,y)の色を取得し、rgbに分解
103:    					color = img.getRGB( x + 1, y );
104:    					r_r = ( color >> 16 ) & 0xff;
105:    					g_r = ( color >> 8 ) & 0xff;
106:    					b_r = color & 0xff;

ピクセル座標(x,y)の右隣の座標(x+1)の色を取得し、変数r_r、b_r、g_rに代入しています。

108:    					// 右側の色との差が閾値より大きければ黒色
109:    					if ( ( gap < Math.abs( r - r_r ) )
110:    						 || ( gap < Math.abs( g - g_r ) )
111:    						 	|| ( gap < Math.abs( b - b_r ) ) ) {
112:    						// 黒色に決定
113:    						r_a = 0;
114:    						g_a = 0;
115:    						b_a = 0;
116:    					}

ピクセル座標(x,y)の色と(x+1)の色の差を絶対値で計算し、RGBのどれか1つでも閾値gapを超えたら、境界色を黒色(RGBの値を全てを0)とします。

117:    					else {
118:    						// (x,y+1)の色を取得し、rgbに分解
119:    						color = img.getRGB( x, y + 1 );
120:    						r_d = ( color >> 16 ) & 0xff;
121:    						g_d = ( color >> 8 ) & 0xff;
122:    						b_d = color & 0xff;
123:    
124:    						// 下側の色との差が閾値より大きければ黒色
125:    						if ( ( gap < Math.abs( r - r_d ) )
126:    							 || ( gap < Math.abs( g - g_d ) )
127:    							 	|| ( gap < Math.abs( b - b_d ) ) ) {
128:    							// 黒色に決定
129:    							r_a = 0;
130:    							g_a = 0;
131:    							b_a = 0;
132:    						}
133:    					}

右隣との色の差が閾値gap以下だった場合、下隣のピクセル座標(x,y+1)の色の差の絶対値を計算して、境界かどうかを判定します。

136:    				// r,g,bの色を合成
137:    				newcolor = ( r_a << 16 ) + ( g_a << 8 ) + b_a;

境界色r_a、b_a、g_aをビット処理で合成し、変数newcolorに代入しています。

139:    				// 合成した色を(x,y)に設定
140:    				newimg.setRGB( x, y, newcolor );

境界色(newcolor)をnewimgの(x,y)に代入しています。

144:    		try {
145:    			// newimgをoutname(出力PNG)に保存
146:    			result = ImageIO.write( newimg, "PNG", new File( outname ) );
147:    		} catch ( Exception e ) {
148:    			// outname(出力JPEG)の保存に失敗したときの処理
149:    			e.printStackTrace();
150:    			return;
151:    		}

BufferedImageクラスのnewimgのメモリ内のデータを、出力PNG名の変数(outname)に格納されているファイル名で保存します。この場合は、PNGファイル名が不正であったり、保存先のHDDなどが存在していなかったり、空き容量が少ないなどが原因で処理が失敗する可能性があります。

ImageIO.writeメソッド

public static boolean write( RenderedImage im, String formatName, File output ) throws IOException
■BufferedImageを画像ファイルに保存します。

  パラメータ RenderedImage : 保存するRenderedImage
                  formatName     : 画像ファイルのフォーマット(png/jpeg/bmp/gifなど)
                  output             : Fileオブジェクト

  戻り値     保存に成功するとtrue、失敗するとfalseを返します。

153:    		// 正常に終了
154:    		System.out.println( "正常に終了しました" );

全ての処理が正常終了すると、ここまで処理が実行されます。

■関連コンテンツ

光と色の3原色 光と色の3原色の考え方を解説

■新着情報

2019.09.13 長さの単位変換 1マイル、1フィートは何m?
2019.09.06 クイックソート 高速に配列に並び替える方法
2019.09.05 中央値(メディアン) 配列に格納されている値の中央値を求める
2019.09.05 最頻値 配列から出現回数が最も多い値の取得
2019.09.03 配列値の反転 配列の反転処理
2019.08.05 トランプの操作 トランプを操作するクラス

■広告

法人向けのETC専用カード

~約8,000名の受講生と80社以上の導入実績~ 企業向けプログラミング研修ならCodeCamp

日本最大級ショッピングサイト!お買い物なら楽天市場

Topへ