4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pythonで大津の二値化がオーバーフローする問題と対策

Last updated at Posted at 2020-03-15

#はじめに
 大津の二値化とは、画像を二値化する際の閾値を自動で決定してくれるアルゴリズムです。既にOpenCV等のライブラリで実装されていますが、Pythonでの実装を見つけたので触ってみました。タイトルには問題とありますが、おそらく私の実行環境が悪かったために発生したものだと思います。

#コード
コード自体は私が書いたものではなく、他の人が描いたブログ[1]からの引用です。

ots.py
#大津の二値化(引数のgrayはグレースケール画像)
def threshold_otsu(gray, min_value=0, max_value=255):

    # ヒストグラムの算出
    hist = [np.sum(gray == i) for i in range(256)]

    s_max = (0,-10)

    for th in range(256):
        
        # クラス1とクラス2の画素数を計算
        n1 = sum(hist[:th])
        n2 = sum(hist[th:])
        
        # クラス1とクラス2の画素値の平均を計算
        if n1 == 0 : mu1 = 0
        else : mu1 = sum([i * hist[i] for i in range(0,th)]) / n1   
        if n2 == 0 : mu2 = 0
        else : mu2 = sum([i * hist[i] for i in range(th, 256)]) / n2

        # クラス間分散の分子を計算
        s = n1 * n2 * (mu1 - mu2) ** 2

        # クラス間分散の分子が最大のとき、クラス間分散の分子と閾値を記録
        if s > s_max[1]:
            s_max = (th, s)
    
    # クラス間分散が最大のときの閾値を取得
    t = s_max[0]

    # 算出した閾値で二値化処理
    gray[gray < t] = min_value
    gray[gray >= t] = max_value

    return gray

#問題

下の様な写真に対して二極化を行ったときに、オーバーフローの警告が出ました。

img5.jpg

RuntimeWarning: overflow encountered in long_scalars
  s = n1 * n2 * ((mu1 - mu2) ** 2)

 その際、閾値は137となり、OpenCVで大津の二値化を行った際に得られる78から離れてしまいます。

#発生原因
 n1とn2に対してtype(n1)、type(n2)を実行すると、<class 'numpy.int32'>という結果になりました。そのため、計算結果がint32型の範囲(-2147483648 〜 2147483647)を超えていることが原因になっていると考えられます[2]

#対策
 2つの対策を考えました。1つ目は、n1とn2の型をfloat型にすることです。下記の様に1行追加することでhistの型が変わり、結果としてn1とn2の型も変わります。

# ヒストグラムの算出
hist = [np.sum(gray == i) for i in range(256)]
hist = np.array(hist, dtype=np.float64)

 2つ目は、logを用いることです。sは値そのものよりも相対的な大きさが重要になるため、sの両辺にlogを取り、右辺を分解します(logを取った結果はfloat型になります)。中身が1とマイナスの場合を除き、logをとっても順番は変わりません。

import math
...

# クラス間分散の分子を計算
#s = float(n1 * n2 * ((mu1 - mu2) ** 2))
        
if n1 == 0 or n2 == 0:
    continue
if mu2 >= mu1:
    s = math.log(n1)+math.log(n2)+2*math.log(mu2-mu1)
elif mu1 < mu2:
    s = math.log(n1)+math.log(n2)+2*math.log(mu1-mu2)
...

 無事に解決できました。下の写真は、プログラムを実行した結果です。
otsu.jpg

 また、閾値の値も79(OpenCVは閾値以下、このプログラムは閾値未満で二値化している)となり、プログラミングが正常に動くことも確認できました。
 最後に、一番目と二番目では、圧倒的に前者の方が簡単ですが、面白そうだったので後者も載せました。

#おわりに
 型がずれてしまったのが問題発生の要因だと考えられます。Pythonで画像処理を行う際はよく型に苦しめられるので、良い勉強になりました。

 最後までご覧いただきありがとうございました。感想・ご指摘等ありましたら宜しくお願いします。

#参考URL
[1]https://algorithm.joho.info/programming/python/opencv-otsu-thresholding-py/
[2]https://jakevdp.github.io/PythonDataScienceHandbook/02.01-understanding-data-types.html

4
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?