ゆるゆるプログラミング

・ぼかし処理

ぼかし処理とは、画像全体ををぼやけた感じにする処理です。この処理は、ノイズを目立たせなくしたり、画像を合成したときの接続部の違和感をなくしたりする処理の1つです。

元の画像元の画像      ぼかし処理後の画像ぼかし処理後の画像

画像のぼかし方は、注目する画素と周りの8ピクセルの色の平均値を計算し、その色を注目画素の位置に戻すことで画像をぼかします。下図が具体的な例です。

ぼかし処理例ぼかし処理例  

3x3の真ん中を注目画素としたときに、その周りのRGBの値の平均値をそれぞれ求めています。

上の例では、Rの平均は(255+255+255+255+255+255+255+255+255)÷9=255、Gの平均は(255+0+0+255+255+0+0+0+0)÷9=85、Bの平均は(255+0+0+255+255+0+0+0+0)÷9=85となります。

これを、Javaソースコードにすると以下のようになります。

SoftFocus.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 SoftFocus {
007:    	public static void main( String[] args ) {
008:    		// 結果格納フラグ
009:    		boolean result;
010:    		// ファイル名
011:    		String inname, outname;
012:    		// 画像格納クラス
013:    		BufferedImage img = null;
014:    
015:    		// 入力した引数が2つ以上かを調べる
016:    		if ( 2 > args.length ) {
017:    			// 入力した引数が2つ未満の場合、使用方法を表示する
018:    			System.out.println( "SoftFocus [入力JPEG名]  [出力JPEG名]" );
019:    			return;
020:    		}
021:    
022:    		// 入力JPEG名をinnameに代入(拡張子".jpg"省略なし)
023:    		inname  = args[ 0 ];
024:    		// 出力JPEG名をoutnameに代入(拡張子".jpg"省略なし)
025:    		outname = args[ 1 ];
026:    
027:    		try {
028:    			// inname(入力JPEG)を読み込んでimgにセット
029:    			img = ImageIO.read( new File( inname ) );
030:    		} catch (Exception e) {
031:    			// inname(入力JPEG)の読み込みに失敗したときの処理
032:    			 e.printStackTrace();
033:    			return;
034:    		}
035:    
036:    		// 画像の色の持ち方をチェック
037:    		if ( BufferedImage.TYPE_3BYTE_BGR != img.getType() )
038:    		{
039:    			System.out.println( "対応していないカラーモデルです!("
040:    									 + inname +")" );
041:    			return;
042:    		}
043:    
044:    		// ぼかし処理
045:    		int x, y, dx, dy;
046:    		int width, height;
047:    		int color, r, g, b;
048:    		int sumr, sumg, sumb;
049:    		int p;
050:    		int newcolor;
051:    		BufferedImage newimg = null;
052:    
053:    		// 画像サイズの取得
054:    		width = img.getWidth();
055:    		height= img.getHeight();
056:    
057:    		// 新しい画像を作成
058:    		// 元の画像と同じ状態の24ビットカラー画像を作成
059:    		try {
060:    			newimg = new BufferedImage( width, height, img.getType() );
061:    		} catch ( Exception e ) {
062:    			// 画像作成に失敗したときの処理
063:    			e.printStackTrace();
064:    			return;
065:    		}
066:    
067:    		// ぼかし処理
068:    		for ( y = 0; y < height; ++ y ) {
069:    			for ( x = 0; x < width; ++ x ) {
070:    				if ( ( 0 == x ) || ( 0 == y ) ||
071:    					( x == ( width - 1 ) ) || ( y == ( height - 1 ) ) ) {
072:    					// (x,y)が画像の外枠の場合、元の画像の色を取得
073:    					newcolor = img.getRGB( x, y );
074:    				}
075:    				else {
076:    					// 9画素の平均値を計算
077:    					sumr = 0;		// R値の合計
078:    					sumg = 0;	// G値の合計
079:    					sumb = 0;	// B値の合計
080:    					for ( dy = -1; dy <= 1; ++ dy ) {
081:    						for ( dx = -1; dx <= 1; ++ dx ) {
082:    							// (x,y)の色を取得
083:    							color = img.getRGB( x + dx, y + dy );
084:    
085:    							// 色をr,g,bに分解
086:    							r = ( color >> 16 ) & 0xff;
087:    							g = ( color >> 8 ) & 0xff;
088:    							b = color & 0xff;
089:    						
090:    							//
091:    							sumr += r;
092:    							sumg += g;
093:    							sumb += b;
094:    						}
095:    					}
096:    					sumr /= 9;	// R値の平均
097:    					sumg /= 9;	// G値の平均
098:    					sumb /= 9;	// B値の平均
099:    
100:    					// r,g,bの色を合成
101:    					newcolor = (sumr << 16 ) + ( sumg << 8 ) + sumb;
102:    				}
103:    
104:    				// 新しい色を(x,y)に設定
105:    				newimg.setRGB( x, y, newcolor );
106:    			}
107:    		}
108:    
109:    		try {
110:    			// imgをoutname(出力JPEG)に保存
111:    			result = ImageIO.write( newimg, "jpeg", new File( outname ) );
112:    		} catch ( Exception e ) {
113:    			// outname(出力JPEG)の保存に失敗したときの処理
114:    			e.printStackTrace();
115:    			return;
116:    		}
117:    
118:    		// 正常に終了
119:    		System.out.println( "正常に終了しました" );
120:    	}
121:    }

SoftFocusを実行

C:\talavax\javasample>java SoftFocus noise2.jpg softFocus.jpg

1つ目の引数で元のJPEGファイル名、2つ目の引数でぼかし処理をしたJPEG画像ファイル名を指定します。

実行結果

・元の画像(noise2.jpg)

元画像

・ぼかし処理をした画像(SoftFocus.jpg)

ぼかし処理をした画像ぼかし処理をした画像

画像に中にある白いノイズが、周りの色に少し溶け込んでいることが分かります。

ここからは、このJavaソースコードを上から順番に解説していきます。

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」と「java.awt.image.RasterFormatException」というパッケージにあるクラスを、このプログラム内で使うために記述します。 この記述により、ImageIOクラスBufferedImageクラスRasterFormatExceptionが利用できるようになります。

006:    public class SoftFocus {

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

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

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

008:    		// 結果格納フラグ
009:    		boolean result;
010:    		// ファイル名
011:    		String inname, outname;
012:    		// 画像格納クラス
013:    		BufferedImage img = null;

このプログラムで使う変数を宣言しています。

015:    		// 入力した引数が2つ以上かを調べる
016:    		if ( 2 > args.length ) {
017:    			// 入力した引数が2つ未満の場合、使用方法を表示する
018:    			System.out.println( "SoftFocus [入力JPEG名]  [出力JPEG名]" );
019:    			return;
020:    		}

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

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

与えられた引数をそれぞれ、入力JPEGファイル名、出力JPEGファイル名、ノイズの割合を格納する変数に代入しています。

027:    		try {
028:    			// inname(入力JPEG)を読み込んでimgにセット
029:    			img = ImageIO.read( new File( inname ) );
030:    		} catch (Exception e) {
031:    			// inname(入力JPEG)の読み込みに失敗したときの処理
032:    			 e.printStackTrace();
033:    			return;
034:    		}

入力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メソッドを抜けるようにしています。

036:    		// 画像の色の持ち方をチェック
037:    		if ( BufferedImage.TYPE_3BYTE_BGR != img.getType() )
038:    		{
039:    			System.out.println( "対応していないカラーモデルです!("
040:    									 + inname +")" );
041:    			return;
042:    		}

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

BufferedImage.getTypeメソッド

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

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

044:    		// ぼかし処理
045:    		int x, y, dx, dy;
046:    		int width, height;
047:    		int color, r, g, b;
048:    		int sumr, sumg, sumb;
049:    		int p;
050:    		int newcolor;
051:    		BufferedImage newimg = null;

ぼかし処理で使う変数を宣言しています。

053:    		// 画像サイズの取得
054:    		width = img.getWidth();
055:    		height= img.getHeight();

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

057:    		// 新しい画像を作成
058:    		// 元の画像と同じ状態の24ビットカラー画像を作成
059:    		try {
060:    			newimg = new BufferedImage( width, height, img.getType() );
061:    		} catch ( Exception e ) {
062:    			// 画像作成に失敗したときの処理
063:    			e.printStackTrace();
064:    			return;
065:    		}

BufferedImageクラスコンストラクタで、もとの画像と同じ大きさとイメージ形式のBufferedImageを構築しています。

BufferedImageコンストラクタ

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

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

067:    		// ぼかし処理
068:    		for ( y = 0; y < height; ++ y ) {
069:    			for ( x = 0; x < width; ++ x ) {

画像の中の全てのピクセルの座標を参照するループをつくっています。具体的には、変数yを0~height-1、変数xを0~width-1に変化させています。

070:    				if ( ( 0 == x ) || ( 0 == y ) ||
071:    					( x == ( width - 1 ) ) || ( y == ( height - 1 ) ) ) {
072:    					// (x,y)が画像の外枠の場合、元の画像の色を取得
073:    					newcolor = img.getRGB( x, y );
074:    				}

(x,y)が画像の縁の場合、ぼかし処理をせずに元の画像の色をそのまま採用しています。x=0またはy=0またはx=width-1またはy=height-1の条件を満たしているとき画像の縁と判定しています。

この判定を入れている理由は、縁にある画像を注目画素にしたときに、その周りの8画素の一部が元画像の外に存在するため、8個すべての色情報が取れないからです。ここで紹介する方法は処理を簡単にするために、このような手法を使っています。

076:    					// 9画素の平均値を計算
077:    					sumr = 0;		// R値の合計
078:    					sumg = 0;	// G値の合計
079:    					sumb = 0;	// B値の合計
080:    					for ( dy = -1; dy <= 1; ++ dy ) {
081:    						for ( dx = -1; dx <= 1; ++ dx ) {
082:    							// (x,y)の色を取得
083:    							color = img.getRGB( x + dx, y + dy );
084:    
085:    							// 色をr,g,bに分解
086:    							r = ( color >> 16 ) & 0xff;
087:    							g = ( color >> 8 ) & 0xff;
088:    							b = color & 0xff;
089:    						
090:    							//
091:    							sumr += r;
092:    							sumg += g;
093:    							sumb += b;
094:    						}
095:    					}
096:    					sumr /= 9;	// R値の平均
097:    					sumg /= 9;	// G値の平均
098:    					sumb /= 9;	// B値の平均

注目画素の座標(x,y)とその周りの8画像のRGBのそれぞれの輝度を合計し、RGBそれぞれの値を9で割り平均値を計算しています。

100:    					// r,g,bの色を合成
101:    					newcolor = (sumr << 16 ) + ( sumg << 8 ) + sumb;

RGBの平均値を合成し、newcolorに格納しています。

104:    				// 新しい色を(x,y)に設定
105:    				newimg.setRGB( x, y, newcolor );

色(変数newcolor)を新しく作ったnewimgの(x,y)に代入しています。これにより、元画像(img)からRGBの平均値を計算することができます。imgを変更すると平均値を求めた値で、さらに平均値を計算することになり狙いどおりの画像に変換できません。

109:    		try {
110:    			// imgをoutname(出力JPEG)に保存
111:    			result = ImageIO.write( newimg, "jpeg", new File( outname ) );
112:    		} catch ( Exception e ) {
113:    			// outname(出力JPEG)の保存に失敗したときの処理
114:    			e.printStackTrace();
115:    			return;
116:    		}

BufferedImageクラスのnewimgのメモリ内のデータを、出力JPEG名の変数(outname)に格納されているファイル名で保存します。この場合は、JPEGファイル名が不正であったり、保存先の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を返します。

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

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

■関連コンテンツ

光と色の3原色 光と色の3原色の考え方を解説
画像の色 画像の色について解説
画像ファイル形式 画像ファイル形式について解説
for文 繰り返し処理に使用するfor文について解説
コマンドライン引数 外部からの値を受け取る方法について解説
ノイズ画像 乱数を使ってノイズ画像を作成する方法について解説

■新着情報

2017.11.17 N値化 カラー画像をN値化する方法について解説
2017.11.16 最も近い値の取得 指定値に最も近い配列の値を取得する方法を解説
2017.10.02 アルファ値(透過) アルファ値(透過)について

■広告

法人向けのETC専用カード

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

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

Topへ