2024.08.01
空間フィルタリング(spatial filtering)
はじめに
空間フィルタリングの説明
それでは、空間フィルタリングについて詳しく説明していきます。
空間フィルタリング処理は、1つの画素に注目して濃淡を変換します。その変換対象の画素を注目画素といい、濃淡を変換するために注目画素の周辺の画素、近傍画素を使用します。
下の図は、注目画素を表したもので、赤い枠で囲んでいます。
以下は、注目画素を中心とした3x3画素を使用した近傍画素を表したもので、青い枠で囲んでいます。この注目画素と近傍画素を組み合わせたものをカーネルといいます。また、オペレータ、マスクとも呼びます。
近傍画素が3x3画素の場合、以下のように3x3の空間フィルタを設定します。kは係数です。
注目画素のR値 = 235 x k00 + 226 x k01 + 231 x k02 + 240 x k10 + 198 x k11 + 200 x k12 + 237 x k20 + 255 x k21 + 228 x k22 注目画素のG値 = 226 x k00 + 221 x k01 + 222 x k02 + 186 x k10 + 147 x k11 + 137 x k12 + 143 x k20 + 183 x k21 + 133 x k22 注目画素のB値 = 161 x k00 + 140 x k01 + 131 x k02 + 126 x k10 + 82 x k11 + 66 x k12 + 91 x k20 + 131 x k21 + 75 x k22
注目画素のR値 = 235 x R00 + 226 x R01 + 231 x R02 + 240 x R10 + 198 x R11 + 200 x R12 + 237 x R20 + 255 x R21 + 228 x R22 注目画素のG値 = 226 x G00 + 221 x G01 + 222 x G02 + 186 x G10 + 147 x G11 + 137 x G12 + 143 x G20 + 183 x G21 + 133 x G22 注目画素のB値 = 161 x B00 + 140 x B01 + 131 x B02 + 126 x B10 + 82 x B11 + 66 x B12 + 91 x B20 + 131 x B21 + 75 x B22
下の図は、注目が画素の位置が画像の左上(0,0)にある例です。この場合、3x3の空間フィルタ内の全ての画素のRGB値を取得することができません。
このように、注目画素の位置が上辺/下辺/左辺/右辺にある場合は、空間フィルタ内の一部のRGB値を取得することができません。
では、どのように処理するのか。
空間フィルタの種類
ぼかし系のフィルタ
ノイズを軽減したり、スムージング(平滑化)を目的に使われます。
ここでは、平均化フィルタ、Gaussian(ガウシアン)フィルタを紹介します。
エッジ抽出系のフィルタ
色の境界を検出するためのもの、物や形状の輪郭を検出するのに使われます。
ここでは、Sobel(ソーベル)フィルタ、微分フィルタ、Laplacian(ラプラシアン)フィルタ、Prewitt(プレヴィット)フィルタを紹介します。
Javaソースコード
元の画像ファイル名と、出力する画像ファイル名と、3x3の空間フィルタの値を渡して、空間フィルタリングを行うソースコード例です。
空間フィルタの9つの値は、k00 k01 k02 k10 k11 k12 k20 k21 k22の順番で渡します。
このソースコードは、画像を操作するMyBufferedImage0クラスを使用しているので、以下に記載のソースコードが必要です。
「MyBufferedImage0.java」を取得してコンパイルしてください。
MySpatialFilter3.java
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
import java.awt.image.BufferedImage; public class MySpatialFilter3 { // 空間フィルタリング(3x3)を行い、注目画素の色を計算するメソッド private static int getColor( MyBufferedImage0 img, int x, int y, double[] filter ) { // 変数の宣言 double sum_r, sum_g, sum_b; double f; // 注目画素の色をdefcolorに代入 // 座標(x,y)が画像外の場合、colorに0xffffffff(白色)が代入される。 int defcolor = img.getRGB( x, y, 0xffffffff ); // 注目画素の色を計算 sum_r = sum_g = sum_b = 0.0; for ( int dy = -1; dy <= 1; dy++ ) { for ( int dx = -1; dx <= 1; dx++ ) { // 空間フィルタの値を取得 f = filter[ ( dx + 1 ) + ( dy + 1 ) * 3 ]; // 色を取得 // 座標(x+dx,y+dy)が画像外の場合、colorにdefcolorが代入される int color = img.getRGB( x + dx, y + dy, defcolor ); // 色をr,g,bに分解 int r = ( color >> 16 ) & 0xff; int g = ( color >> 8 ) & 0xff; int b = color & 0xff; // RGB値に空間フィルタの値を掛けて足す sum_r += (double)r * f; sum_g += (double)g * f; sum_b += (double)b * f; } } // 計算したRGBの値をそれぞれ0~255の値に収める if ( 0.0 > sum_r ) sum_r = 0.0; if ( 255.0 < sum_r ) sum_r = 255.0; if ( 0.0 > sum_g ) sum_g = 0.0; if ( 255.0 < sum_g ) sum_g = 255.0; if ( 0.0 > sum_b ) sum_b = 0.0; if ( 255.0 < sum_b ) sum_b = 255.0; // RGBの値を合成して戻す return ( (int)sum_r << 16 ) + ( (int)sum_g << 8 ) + (int)sum_b; } // メインメソッド public static void main(String[] args) { // ファイル名 String inname, outname; // 空間フィルタ double filter[] = new double[ 9 ]; // 入力した引数が11以上かを調べる if ( 11 > args.length ) { // 入力した引数が11未満の場合、使用方法を表示する System.out.println( "MySpatialFilter3 [入力画像名] [出力JPG名] k00 k01 k02 k10 k11 k12 k20 k21 k22" ); return; } // 入力画像名をinnameに代入(拡張子省略なし) inname = args[ 0 ]; // 出力JPG名をoutnameに代入(拡張子".jpg"省略なし) outname = args[ 1 ]; // 空間フィルタの設定 for ( int i = 0; i < 9; ++ i ) { filter[ i ] = Double.parseDouble( args[ i + 2 ] ); } // 画像操作クラスを作成(元画像格納用) MyBufferedImage0 img_src = new MyBufferedImage0(); // 画像の読み込み if ( !img_src.read( inname ) ) { System.out.println( "読み込みに失敗しました!(" + inname +")" ); return; } // 読み込んだ画像のサイズ int w = img_src.getWidth(); int h = img_src.getHeight(); // 画像操作クラスを作成(変換後画像格納用) MyBufferedImage0 img_des = new MyBufferedImage0(); if ( false == img_des.create( w, h, BufferedImage.TYPE_INT_RGB ) ) { System.out.println( "画像の作成に失敗しました!" ); return; } // 空間フィルタリング for ( int y = 0; y < h; y++ ) { for ( int x = 0; x < w; x++ ) { // 色を取得 int color = getColor( img_src, x, y, filter ); int r = MyBufferedImage0.getR( color ); int g = MyBufferedImage0.getG( color ); int b = MyBufferedImage0.getB( color ); // 変換した色を設定 img_des.setRGB( x, y, r, g, b ); } } // JPG画像の保存 if ( !img_des.writeJPG( outname ) ) { System.out.println( "保存に失敗しました!(" + outname +")" ); return; } // 正常に終了 System.out.println( "正常に終了しました" ); } }
コンパイル ソースコードが「ANSI」の場合
C:\talavax\javasample>javac -encoding sjis MySpatialFilter3.java
コンパイル ソースコードが「UTF-8」の場合
C:\talavax\javasample>javac MySpatialFilter3.java
以下の例は、元画像に対して空間フィルタリング処理を行った結果です。
実行例 - 平均化フィルタ
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_box.jpg 0.11111 0.11111 0.11111 0.11111 0.11111 0.11111 0.11111 0.11111 0.11111
フィルタの値の1/9は、0.11111で渡しています。
実行例 - ガウシアンフィルタ
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_gauss.jpg 0.0625 0.125 0.0625 0.125 0.25 0.125 0.0625 0.125 0.0625
実行例 - ソーベルフィルタ(横方向)
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_sobel-x.jpg -1 0 1 -2 0 2 -1 0 1
実行例 - ソーベルフィルタ(縦方向)
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_sobel-y.jpg -1 -2 -1 0 0 0 1 2 1
実行例 - 微分フィルタ(横方向)
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_difference-x.jpg 0 0 0 -0.5 0 0.5 0 0 0
実行例 - 微分フィルタ(縦方向)
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_difference-y.jpg 0 -0.5 0 0 0 0 0 0.5 0
実行例 - ラプラシアンフィルタ(4方向)
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_laplacian-4.jpg 0 1 0 1 -4 1 0 1 0
実行例 - ラプラシアンフィルタ(8方向)
java MySpatialFilter3 sampleimage001_400x320.jpg spatialfilter_laplacian-8.jpg 1 1 1 1 -8 1 1 1 1
Javaソースコードの解説
001
import java.awt.image.BufferedImage;
Javaのクラスライブラリの中から「java.awt.image.BufferedImage」というパッケージにあるクラスを、このプログラム内で使うために記述します。
この記述により、BufferedImageクラスが利用できるようになります。
003
public class MySpatialFilter3 {
クラス名を、MySpatialFilter3としています。
004 005
// 空間フィルタリング(3x3)を行い、注目画素の色を計算するメソッド private static int getColor( MyBufferedImage0 img, int x, int y, double[] filter )
空間フィルタリングを行うメソッドgetColorです。
画像を格納するオブジェクトimg、注目画素の座標(x,y)、空間フィルタの値を格納したdouble型の配列filterを渡して、計算後の注目画素の色を戻します。
007 008 009
// 変数の宣言 double sum_r, sum_g, sum_b; double f;
011 012 013
// 注目画素の色をdefcolorに代入 // 座標(x,y)が画像外の場合、colorに0xffffffff(白色)が代入される。 int defcolor = img.getRGB( x, y, 0xffffffff );
注目画素(x,y)の色を取得して、int型の変数defcolorに代入しています。defcolorは、RGBの値を合成したものです。
ここでは、座標(x,y)が画像の外側になることはないですが、外側の場合はgetRGBメソッドの3番目の引数0xffffffff(白色)がdefcolorに代入されます。
015 016
// 注目画素の色を計算
sum_r = sum_g = sum_b = 0.0;
ここから注目画素(x,y)のRGB値の計算です。
計算結果は、sum_rとsum_gとsum_bに格納されます。ここでは、初期値0.0を代入しています。
017 018
for ( int dy = -1; dy <= 1; dy++ ) { for ( int dx = -1; dx <= 1; dx++ ) {
019 020
// 空間フィルタの値を取得
f = filter[ ( dx + 1 ) + ( dy + 1 ) * 3 ];
座標(x+dx,y+dy)に対応した空間フィルタの値をdouble型の変数fに代入しています。
空間フィルタの値を格納しているfilterは1次元配列なので、dxとdyの値から配列の添え字を以下の式で計算しています。
空間フィルタ配列(3x3)の添え字 = ( dx + 1 ) + ( dy + 1 ) x 3
( -1 + 1 ) + ( -1 + 1 ) x 3 = 0
( 1 + 1 ) + ( 1 + 1 ) x 3 = 8
で計算することができます。
空間フィルタ配列(5x5)の添え字 = ( dx + 2 ) + ( dy + 2 ) x 5
で計算することができます。
022 023 024
// 色を取得 // 座標(x+dx,y+dy)が画像外の場合、colorにdefcolorが代入される int color = img.getRGB( x + dx, y + dy, defcolor );
座標(x+dx,y+dy)の色を取得してcolorに代入しています。(x+dx,y+dy)が画像の外側の場合はgetRGBメソッドの3番目の引数defcolorがcolorに代入されます。
defcolorは、座標(x,y)に色です。
026 027 028 029
// 色をr,g,bに分解 int r = ( color >> 16 ) & 0xff; int g = ( color >> 8 ) & 0xff; int b = color & 0xff;
色の分解方法の詳細はこちらを参照してください。
031 032 033 034
// RGB値に空間フィルタの値を掛けて足す sum_r += (double)r * f; sum_g += (double)g * f; sum_b += (double)b * f;
038 039 040 041 042 043 044
// 計算したRGBの値をそれぞれ0~255の値に収める
if ( 0.0 > sum_r ) sum_r = 0.0;
if ( 255.0 < sum_r ) sum_r = 255.0;
if ( 0.0 > sum_g ) sum_g = 0.0;
if ( 255.0 < sum_g ) sum_g = 255.0;
if ( 0.0 > sum_b ) sum_b = 0.0;
if ( 255.0 < sum_b ) sum_b = 255.0;
積和計算で得られたsum_r、sum_g、sum_bの値を0.0から255.0の間に収めています。
0.0未満の値は0.0、255.0を超える値は255.0にしています。
046 047
// RGBの値を合成して戻す return ( (int)sum_r << 16 ) + ( (int)sum_g << 8 ) + (int)sum_b;
051 052
// メインメソッド public static void main(String[] args) {
このmainメソッドからプログラムを実行します。
053 054 055 056 057
// ファイル名 String inname, outname; // 空間フィルタ double filter[] = new double[ 9 ];
059 060 061 062 063 064
// 入力した引数が11以上かを調べる if ( 11 > args.length ) { // 入力した引数が11未満の場合、使用方法を表示する System.out.println( "MySpatialFilter3 [入力画像名] [出力JPG名] k00 k01 k02 k10 k11 k12 k20 k21 k22" ); return; }
066 067 068 069 070 071 072 073 074
// 入力画像名をinnameに代入(拡張子省略なし) inname = args[ 0 ]; // 出力JPG名をoutnameに代入(拡張子".jpg"省略なし) outname = args[ 1 ]; // 空間フィルタの設定 for ( int i = 0; i < 9; ++ i ) { filter[ i ] = Double.parseDouble( args[ i + 2 ] ); }
076 077
// 画像操作クラスを作成(元画像格納用)
MyBufferedImage0 img_src = new MyBufferedImage0();
画像を操作するMyBufferedImage0クラスの実体img_srcを作成しています。
079 080 081 082 083
// 画像の読み込み if ( !img_src.read( inname ) ) { System.out.println( "読み込みに失敗しました!(" + inname +")" ); return; }
MyBufferedImage0.readメソッドで、画像ファイルinnameをimg_srcに読み込んでいます。
MyBufferedImage0.readメソッド
boolean read( String imgname )
・指定した画像ファイルを読み込みます。 パラメータ imgname : フルパスの画像ファイル名(拡張子は省略不可) 戻り値 true : 画像の読み込みに成功 false : 画像の読み込みに失敗
085 086 087
// 読み込んだ画像のサイズ int w = img_src.getWidth(); int h = img_src.getHeight();
MyBufferedImage0.getWidthメソッド
public int getWidth()
・画像の横のピクセルサイズを返します。 パラメータ なし 戻り値 画像の横ピクセル数
MyBufferedImage0.getHeightメソッド
public int getHeight()
・画像の縦のピクセルサイズを返します。 パラメータ なし 戻り値 画像の縦ピクセル数
089 090
// 画像操作クラスを作成(変換後画像格納用)
MyBufferedImage0 img_des = new MyBufferedImage0();
画像を操作するMyBufferedImage0クラスの実体img_desを作成しています。
091 092 093 094
if ( false == img_des.create( w, h, BufferedImage.TYPE_INT_RGB ) ) { System.out.println( "画像の作成に失敗しました!" ); return; }
MyBufferedImage0.createメソッドで、幅w、高さh、24ビットカラー(BufferedImage.TYPE_INT_RGB)の画像img_desを作成しています。
MyBufferedImage0.createメソッド
boolean create( int width, int height, int type )
・指定したサイズ、種類の画像を作成します。 パラメータ width : 作成する画像の横のピクセル数 height : 作成する画像の縦のピクセル数 type : 作成するイメージの形式 指定例) BufferedImage.TYPE_INT_RGB 戻り値 true : 画像の作成に成功 false : 画像の作成に失敗
096 097 098
// 空間フィルタリング for ( int y = 0; y < h; y++ ) { for ( int x = 0; x < w; x++ ) {
099 100
// 色を取得 int color = getColor( img_src, x, y, filter );
101 102 103
int r = MyBufferedImage0.getR( color ); int g = MyBufferedImage0.getG( color ); int b = MyBufferedImage0.getB( color );
変数colorの赤成分r、緑成分g、青成分bに分解しています。
MyBufferedImage0.getRメソッド
static int getR( int color )
・RGBを合成した色からR成分(赤の輝度)だけを抽出します。 パラメータ color : RGBを合成した色 戻り値 赤の輝度
MyBufferedImage0.getGメソッド
static int getG( int color )
・RGBを合成した色からG成分(緑の輝度)だけを抽出します。 パラメータ color : RGBを合成した色 戻り値 緑の輝度
MyBufferedImage0.getBメソッド
static int getB( int color )
・RGBを合成した色からB成分(青の輝度)だけを抽出します。 パラメータ color : RGBを合成した色 戻り値 青の輝度
105 106
// 変換した色を設定
img_des.setRGB( x, y, r, g, b );
赤成分r、緑成分g、青成分bをimg_desの(x,y)に設定しています。
MyBufferedImage0.setRGBメソッド
boolean setRGB( int x, int y, int color )
・指定した座標の色を設定します。 パラメータ x : ピクセルのx座標 y : ピクセルのy座標 color : 設定するRGBを合成した色 戻り値 (x,y)が画像の範囲内の場合、true (x,y)が画像の範囲外の場合、false
boolean setRGB( int x, int y, int r, int g, int b )
・指定した座標の色を設定します。 パラメータ x : ピクセルのx座標 y : ピクセルのy座標 r : 赤の輝度 g : 緑の輝度 b : 青の輝度 戻り値 (x,y)が画像の範囲内の場合、true (x,y)が画像の範囲外の場合、false 詳細 r、g、bは自動で0~255の範囲に変換されます。
110 111 112 113 114
// JPG画像の保存 if ( !img_des.writeJPG( outname ) ) { System.out.println( "保存に失敗しました!(" + outname +")" ); return; }
img_desをファイル名outnameで保存しています。
MyBufferedImage0.writeJPGメソッドを使用してJPEG形式で保存しています。
MyBufferedImage0.writeJPGメソッド
boolean writeJPG( String imgname )
・画像をJPEG形式のファイルで保存します。 パラメータ imgname : フルパスのJPEG画像ファイル名 戻り値 true : 画像の読み込みに成功 false : 画像の読み込みに失敗
116 117
// 正常に終了 System.out.println( "正常に終了しました" );
全ての処理が正常終了すると、ここまで処理が実行されます。
以上です。
関連コンテンツ
一般に使われている画像フォーマットには、いろいろな種類があります。画像フォーマットBMP、JPEG、PNG、GIF、TIFFの特徴を知ってますか?