スキャン画像のモアレ除去のための、ぼかしとアンシャープマスク処理

CDのジャケット等の印刷物をスキャナでスキャンすると、

以下の画像の様にモアレが発生します。

(本画像はCQ出版「定本 トランジスタ回路の設計」表紙画像のスキャン画像の一部です。

ダウンロードして拡大するとモアレがよりよく見えます。)

moire50.png

これは、印刷する際に、あるdpiで印刷していて、

それを異なるdpiで読み取るために、ズレが周期的に発生して

干渉縞ができるためです。

ふつう、スキャナにはこれを除去するモアレ除去という

機能が付いているのですが、安物の機種にはついていなかったり、

ついていてもONOFFのみなど、調整ができなかったりします。

これを調整できるようにしようかと思い、javaでプログラムを作成しました。

AdobeのHPを見ると、『画像に「ぼかし(ガウス)」を加えてから

「アンシャープマスク」で引き締める 2 つのフィルタを適用する

方法が効果的です。』とあります。

スキャン画像のモアレの解消方法

https://helpx.adobe.com/jp/x-productkb/multi/216043.html

おそらく普通のスキャナにも、こういった処理がついていて、

パラメータが固定値になっているのではないでしょうか。

というわけで、Javaで「ぼかし」と「アンシャープマスク」の

2つを実装してみます。

こういった処理にはJava 2D APIを使用するのが良いようです。

コンボリューションを用いた画像の平滑化、鮮鋭化とエッジ検出

http://codezine.jp/article/detail/129

ここでいう「平滑化」が「ぼかし」で「鮮鋭化」が

「アンシャープマスク」ですね。オペレータを作って

Java 2D APIで畳み込みをすればこれらの処理が実現できます。

この例では3×3のオペレータの例しか載っていませんが、

実際は記述されている通り7×7や15×15くらいのサイズが必要ですので、

オペレータを作成する処理が別途必要です。

ここでは、この記事に書いてあるように、ガウス分布(正規分布)を

使って、範囲を分散σ^2で調整できるようなオペレータを作成します。

また、オペレータのサイズも自由に変更できるようにします。

サイズとσ値は調整が必要です。とりあえず、サイズ15、σ2で

ぼかしとアンシャープマスクをかけると、記事冒頭の画像が以下のようになります。

moireremoved50.png

ややボケた感じが残っていますが、最初に目立っていた周期的な

モアレノイズはかなり低減できていると思います。

あとはパラメータ調整と、別のシャープ化処理とかを実装すれば、

そこそこ実用的になるのではと思います。

以下、今回作成したサンプルコードです。

一部に、私のオリジナルのライブラリ関数が使われてるので注意してください。

/**

* 正規分布をつかってオペレータを作る

*/

public static Matrix gausianOperator( int size, double sigma )

{

//サイズが奇数でなければエラー

if( size%2 == 0 ) return null;

//オペレータをつくる

Matrix operator = new Matrix(size, size);

double distance;

int center = size/2;

//各マスに正規分布の値を入れる

for(int r=0; r<size; r++){

for(int c=0; c<size; c++){

distance = Math.hypot(r-center, c-center);//中央からの距離を計算

operator.set(r, c, Numeric.normalDistribution(distance, 0, sigma)); //正規分布の値をセット

}

}

//全部の値の合計が1になるように調整

operator = operator.multiply(1/operator.sum());

return operator;

}

/**

* BufferedImageの画像を畳み込みする

*/

public static BufferedImage convolution( BufferedImage image, Matrix operator,int type)

{

//オペレータをfloat[]に変換する準備をする

Vector temp = new Vector(0);

int size = operator.length();

for(int i=0; i<size; i++) temp = temp.add(operator.getRow(i));

//畳み込む

Kernel kernel=new Kernel(size,size,Cast.doubleToFloat(temp.get()));

ConvolveOp convolveOp;

if(type==1)

convolveOp=new ConvolveOp(kernel,ConvolveOp.EDGE_ZERO_FILL,null);

else

convolveOp=new ConvolveOp(kernel,ConvolveOp.EDGE_NO_OP,null);

BufferedImage output=convolveOp.filter(image,null);

return output;

}

/**

* 画像を平滑化する<br>

*/

public static BufferedImage smooth( BufferedImage image, int size, double sigma )

{

//オペレータを作成する

Matrix operator = gausianOperator( size, sigma );

//オペレータを使って畳み込みする

return convolution(image, operator, 0);

}

/**

* 画像を鮮鋭化する<br>

*/

public static BufferedImage sharpen( BufferedImage image, int size, double sigma )

{

//オペレータを作成する

Matrix operator = gausianOperator( size, sigma );

//オペレータをアンシャープマスクにする

operator = operator.multiply(-1);

operator.set(size/2, size/2, (2-operator.get(size/2, size/2)*(-1)) );

//オペレータを使って畳み込みする

return convolution(image, operator, 0);

}

public static void main(String[] args)

{

BufferedImage target = ImageUtility.read("test.csv");

BufferedImage smoothed = ImageProcessing.smooth(target, 15, 2);

BufferedImage shaped = ImageProcessing.sharpen(smoothed,15, 2);

ImageUtility.save(shaped , "test2.csv");

}