pythonを使って画像の外れ値探しと修正処理

取得した画像に望まれないそして理由のはっきりしている例外が含まれていることはよくある。 そんな時は見た目的にも解析的にも修正できるなら修正しておきたいところ。
理由がわからない場合はそれが事象かもしれないので、修正してしまうのは問題だけど。

この投稿では二次元画像にはっきりと区別できる望まれない何かしらが写り込んでしまった場合の修正の一例について書いてみたい。

画像の取得

raw画像の取得について、以前の投稿で書いたので詳細に興味がある方は参照していただきたい。

リンク: pythonでraw形式の画像を読み込んで処理する方法

tiffやjpegなどだったらもっとシンプルに読み込めるのじゃないかな。
いずれにしろPILなどを用いて画像を読み込み、それを処理のためにnumpyのndarrayに突っ込んでおくのが基本方針だろう。

画像の中から例外値の取得

X線画像

ここではX線の画像に、X線を止める物質が混入したという例で。 まあビームストップなんだけど。
画像は360x1500ピクセルの画像で、上記したようにnumpyのndarrayにピクセルごとに入れてある(上の写真はトリミングしてるので縦800ピクセルになっているけれど)。
ndarrayのshapeがそのまま(1500, 360)になるのでどう処理していけば良いかは直感的にわかりやすい。

この例の場合はビームストップの周りはピクセル強度があからさまに落ちるので(画像の縦に走る黒い線)、強度の低いデータ場所を抜き出すというのが最初の方針になる。
単純に弱いのを抜き出すのでよければ、適当に決めた閾値よりも小さい値を一気に抜き出せば良いので簡単。

ただこの例の場合だと画像の下に行くほど比例的に強度が落ちて行くため、その方法だと画像下部が大量にマスクされてしまう。 それはあまりよろしくない。 いや実はこの画像の例ではよろしかったんだけど、よろしくないことにしておく。

さてじゃあどうするか。 とりあえず閾値を横方向の1データセットごとにセットすることにした(画像を横方向にスライスして最小値を拾う)。
savgol_filterでスムージングフィルターをかけているのは、横方向スライスした時に極端な外れ値があった場合に取り除けるように。 だけど私の画像は割と平坦なのでほとんど働いていなかった。
ちなみにビームストップの強度を拾わないように画像左半分[:,0:180]の範囲から最小値を拾っている。

#最初に閾値を1行あたりに決めるためのarrayを作成
threshold = np.linspace(0,1499,1500)

#0列から180列まででデータ処理をする。 polar_dataが画像を格納したndarray。
polar_data_smo= polar_data[:,0:180]

#1500行について1行ずつスムージングフィルターをかける
for i in range(1500):
  polar_data_smo[i] = savgol_filter(polar_data[i,0:180],11,4)

#閾値に1行ごとの最小値を格納していく
  threshold = np.nanmin(polar_data_smo,axis=1)

というわけでこれで閾値が取得できた。
実用的にはthresholdから多少値を引いてあげて余裕をもたせても良いかもしれない。 拾いたい値がどの程度通常の値と離れているか次第。

例外値をどうするか

均一な画像の場合

私の例はそうではないのだけど、均一なデータの場合はNANに値を変えてしまうのが楽。 均一データは平均値で物が扱えるので、平均を取るときにnanmeanなどを使えば良い。

arrayの中である値より小さな値を探す処理はnumpyのwhereが便利だと思う。

polar_data = msk_small(img2,origin,polar_data,threshold)

#閾値以下の値をNoneに変える関数
def msk_small(img2,origin,polar_data,threshold):

#1行ずつ閾値より小さな値を探してNoneを代入していく
 for i in range(1500):

#whereを使って閾値より小さな値を探す
  polar_data[i,np.where(polar_data[i,0:360]<threshold[i])] = None

#データをPILのImageで画像に出力してうまく処理できているか確認
  img = Image.fromarray(polar_data)
  img.save("test.tif")
  return polar_data

不均一だけど対称的な画像の場合

私の例の画像はこの例。
対称的だから例外値のところを使わなければ良いのだけど、ちょっと例外値の場所の周りも使いたい事情がある場合。
とりあえず最小値をthresholdに取得してあるので、それを流用して最小値で埋めてみる。 見た目的には多少マシになる。

 polar_data[i,np.where(polar_data[i,0:360]<threshold[i])] = threshold[i]

というわけでできあがった図は下のような感じ。 左側が修正後の画像。
ぱっと見はそんなに悪くはないような気もするけれど。 並べてみればまだまだはっきりとわかる。
まあ絵としては別にいいけれどデータ解析には使えない。

 

ついでにこれだとビームストップの下に特徴(ピーク)があるような場所は全然ダメ。
やっぱり適当に対称の場所をフィットしてそれを強度を合わせるように持ってきた方が良いのか。 しかしあんまりごちゃごちゃ処理するのもこういう画像処理としてはやりすぎかな。

ビームストップの位置を変えて2回データを取るなど、外れ値を作らない努力をする方が幸せかもしれない。

まあこういう処理でうまくいくような画像もきっとあるのだろう。 きっと。

不均一で何の規則もないような画像

というのもちょっと考えてみたけれど試していない。 そもそもこういう場合だと、例外値の取得がまずもって難しそうだし。 ちょうど良い画像の例もみつからなかったので諦めた。

というわけで何やら失敗談の投稿になってしまった。 せっかく書いたので投稿はしておくが後でもう少し取り組むチャンスがあれば修正・加筆するかもしれない。

何かこういう画像処理が簡単にできるパッケージ・ライブラリなどをご存知の人がいたら教えていただけるとありがたい。

関連記事

pythonのまとめ

D