ハーフトーンを削除する方法を考える

python コンピュータ
python

スキャンした書籍の画像を、大き目のPCモニターに合わせてWaifu2xで拡大処理をしています。
印刷物などで点描で濃淡を表現する方法があり、ハーフトーンというらしいのですが、そのままWaifu2xで拡大すると、点が格子状の模様となって現れます。点を削除しベタ塗状態にすることが出来ないか考えてみます。

おおまかな方法としては、ぼかし処理をすることで独立した点同志を結合します。次にぼかし処理でボケてしまった輪郭などの境界部を、なんらかな方法でメリハリをつけてあげます。

1.縮小、拡大処理をする。
点同志を結合させる方法として縮小処理の補完機能を使います。その後縮小された画像を拡大することで元の大きさに戻します。
補完方法によって結果が異なります。
デメリットとして縮小することで画像の情報量が大幅に減少するため、ディティールが損失の大きい点です。
また、拡大率で調整するため、細かな調整は不向きです。

2.ガウシアンフィルタとアンシャープマスキングフィルタ
ぼかし処理にガウシアンフィルタを使い、シャープ処理にアンシャープマスキングフィルタを使います。
効果的ですが、ガウシアンフィルタを強くかけると、境界部が失われます。また、アンシャープマスキングフィルタで境界が不自然に補強される場合があります。
ガウシアンフィルタの強弱とアンシャープマスキングフィルタの強弱で調整。1.の拡大縮小よりは細かな調整が利きます。

3.バイラテラルフィルタとラプラシアンフィルタ
ぼかし処理にバイラテラルフィルタを使います。バイラテラルフィルタは境界部を残しそれ以外をぼかす処理をしてくれます。
理想的なフィルタですが、強くフィルタをかけすぎると境界部もボケてきます。
あと、パラメータを強めで1回で処理する場合と、パラメータを弱めで複数実行する場合で結果が異なります。

また、OpenCVにはfastNlMeansDenoisingという強力なノイズ除去フィルタがあります。強力過ぎてディテールをつぶしてしまう場合があるので、フィルターの最終段階で仕上げに使うと良さそうです。

次にラプラシアンフィルタで輪郭を抽出し、その輪郭部分を補強してあげることで境界部にメリハリをつけます。
ラプラシアンフィルタをかけた画像は、境界部が明るい白に近づき、それ以外が暗い黒になります。
PythonとNumpyの話になりますが、ぼかし処理を行った画像からラプラシアンフィルタで抽出した輪郭画像を減算すると、境界部が補強されます。文字通り数値を引き算するような書式で表現できる点はNumpyは良くできているなと感じました。
アンシャープフィルタマスクほど強力では無いですが、かなり自然な仕上がりになります。
(追記、調べてみるとこの手順は平滑化(バイラテラルフィルタ)した画像にラプラシアンフィルタを演算しているあたり、はからずもアンシャープマスキングを手動でやっているような代物になっています。)

この補強で満足できない場合はさらにアンシャープフィルタマスクを施します。その場合アンシャープフィルタマスクでノイズも補強されて画像が荒れるようであれば、さらにfastNlMeansDenoisingでノイズを消してあげます。

輪郭抽出のフィルタはラプラシアンフィルタ以外にも複数存在しますので、試しても見るのも良いと思います。

フィルターを多数かけることで理想の画像に近づけることは出来ますが、代わりに元画像の情報は失われます。何を残し何を消すか、何をぼかし何を強調するか、この調整が楽しいと感じるか、終わりの無い作業と感じるかは人それぞれです。

実装はPython+Numpy+OpenCVの組み合わせで試行錯誤していますが、フィルタの組み合わせを調整することで点が消え、ベタ塗に変換される結果を見ると不思議な感じがします。不可逆な処理だと思いますので、完璧に理想通りにはならないとは思いますが、うまく調整できた場合は、苦労にみあった達成感を感じることが出来ると思います。

追記

最近はバイラテラルフィルタの代わりに以下のカーネルを引数にfilter2Dを数回実行します。

kernel = np.array([0,2/16,0],[2/16,8/16,2/16],[0,2/16,0])

バイラテラルフィルタと比べて輪郭はボケますが、格子状のパターンをいい感じに壊してくれます。

バイラテラルフィルタはパラメータを調整すると輪郭を残したまま、ぼかし処理をすることが出来ますが、輪郭を意識しすぎるとぼかしが甘く、輪郭と同色の点が残ったり、濃淡の全くないベタ塗状態になったりします。ぼかす範囲を大きくとるか、処理を数回重ねることで対処できますが、今度は輪郭がボケてきます。理想的なフィルタだと思うのですが、自分には扱いきれない感じです。

上記フィルタで、荒れた感じになるので、次にfastNlMeansDenoisingフィルタを適度に施すことでベタ塗りになります。

この段階で輪郭がボケてくるのでアンシャープマスキングフィルタで輪郭を強調してあげます。

k = 2.0
kernel = np.array([-k/9,-k/9,-k/9],[-k/9,1+8*k/9,-k/9],[-k/9,-k/9,-k/9])

これまでは、ラプラシアンフィルタで輪郭を抽出して強調していたのですが、綺麗に輪郭を抽出できる画像であれば、いい感じに効果を発揮してくれますが、輪郭以外が残ってしまうと、それがノイズとして強調されしまうため、扱いが難しいので止めました。
その点アンシャープマスキングフィルタは、kの値を調整することで強調度合いを調整出来ますので、対象画像ごとに設定をつめることで理想の画像に近づけることが出来ます。

コメント