OpenCV

[OpenCV] 画像の二値化処理を初心者にも分かりやすく徹底解説!

本記事では、機械学習などでよく利用される二値化処理に焦点をあてて、様々な手法を分かりやすく解説していきたいと思います!

二値化処理とは

画像処理の一種で、画像の色調を白黒へと変換する処理のことです。

二値化処理は、画像のノイズ削減濃淡を強調させるために用いられることが多いです。

近年注目を集めてきている機械学習やディープラーニング等で行われる画像識別特徴点抽出などでは、二値化像からデータを取得しています。

そのため二値化処理はなくてはならない画像処理手法の1つです!
では分かりやすく解説していきます。

準備:画像の読み込みと書き込み

画像を読み込む方法

OpenCVを用いて画像を読み込む際には、cv2.imread()を使用します。引数には読み込みたい画像の保存先URLを入力しておきます。

インスタンス名 = cv2.imread ( ” C:¥image.jpg " ) #この場合はCドライブ内のimage.jpgという画像を指定

すらいむ管理人

こうすることで、指定したインスタンスに画像情報を持たせることができます!

画像を保存したい場合

OpenCVを用いて画像処理を行った後の画像データを保存したい場合には、cv2.imwrite()を利用します!

画像処理を行った次の行に挿入することで画像を保存することができます。書き方は以下の通りです。第一引数(adress)には保存先URL、第二引数に保存したい画像データを指定します。

cv2.imwrite("address", 画像名)

画像が読み込めない場合にプログラムを終了させる方法

画像が読み込めない場合に、プログラムのRUNを終了させたい場合には以下のコードをプログラムに追加します。

ファイル読み込みを行う行の下に挿入してみて下さい!

    if image is None:
        print("ファイルが見つかりません")
        import sys
        sys.exit()

グレイスケールで表示させる方法

グレイスケールとは、画像を白と黒の濃淡のみで表示させる手法です。
画像をグレイスケールに変更する場合は、cv2.cvtColorメソッドにあるcv2.COLOR_RGB2GRAY関数を利用します。

第一引数には、グレイスケールに変更したい画像を指定します。
第二引数には、関数を指定します。

cv2.cvtColor ( image , cv2.COLOR_RGB2GRAY )

では実際に画像をグレースケールに変更してみしょう!
なお、”\” は “半角\” を示しています。

import cv2

def main():
    image=cv2.imread("C:\Imagefile\cat-image.jpg")
#グレイスケールへ変換
    gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
    cv2.imwrite("C:\Imagefile\cat-gray.jpg",gray)
    cv2.imshow("gray1",gray)

    cv2.waitKey()
    cv2.destroyAllWindows()
if __name__=="__main__":
    main()

(実行結果)

thresh-image1

このように画像の色がグレイスケールに変更されました!

画像が自動で白黒に変更されるので非常に便利ですね!次は閾値処理という手法を紹介したいと思います!

すらいむ管理人

閾値処理は画像を白黒へ二値化するための処理です!

閾値(しきい値)処理で二値化してみよう

二値化処理とは

二値化処理とは、画像を以下のような白黒画像に変換する手法です。白黒に変換する方法は、グレイスケール画像の諧調(輝度)を閾値を指定して黒(0)白(255)かで表す画像処理手法です。(下図は閾値を130に指定)

thresh-image2
thresh-image3

グレイスケール画像とは?

グレイスケール画像は、原画像の色分布をもとにして白黒の輝度(0~255の計256段階)で表示した画像のこと

閾値(しきい値)とは?

二値化画像を作成する場合には、0~255の計256段階の諧調で表示されたグレイスケール画像の、どの値を境界として白黒に分けるかを指定する。この時指定した値を閾値(しきい値)という

二値化の基本的な仕組み

閾値画像処理をする上で、二値化の仕組みを知っておくことは非常に大切です。

すらいむ管理人

今回は、二値化手法の基本的な仕組みを分かりやすく解説していきたいと思います!

まず、グラフの下に表示されたようなグレースケールで表される画像があるとします。すると、この画像の輝度は下図に示したグラフのように表現できます。

thresh-process

この画像に対して、閾値を130に設定して二値化をしてみます。閾値を130に設定すると、輝度が130よりも高いエリアは白で表示され、130よりも低いエリアは黒に表示されて二値化されます。

thresh-process2

このようなシステムで二値化を行うため、画像全体の輝度が平滑である必要があります。輝度に偏りがある場合には、正確に二値化を行うことができないため、前処理として輝度平滑化処理を施す必要があります。

まず初めに、画像上のノイズを低減させるためにフィルタ処理(ぼかし)をかけます。

その後、特殊な環境でない限り、通常は少なからず輝度値に偏りがあるため輝度平滑化処理を行うか、後半部分で説明する適応的二値化処理を行います。

おすすめ書籍

OpenCVによる二値化手法一覧

ここからは実際に画像を白と黒へ二値化する方法について解説していきます。

OpenCVにおいて、二値化をするために用いられる手法は以下の通りとなっています。次項でそれぞれ詳しく解説していきます!

cv2.THRESH_BINARY

まずは、一般的な二値化関数であるcv2.THRESH_BINARY関数を見てみましょう!

プログラムと実行結果は以下の通りです。

import cv2

def main():
    image=cv2.imread("C:\Imagefile\cat-image.jpg")

    gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
    ret,thresh=cv2.threshold(gray,130,255,cv2.THRESH_BINARY)
    #cv2.imwrite("C:\Imagefile\cat-gray.jpg",thresh)
    cv2.imshow("gray1",thresh)

    cv2.waitKey()
    cv2.destroyAllWindows()
if __name__=="__main__":
    main()

(実行結果)

thresh-image4

このような形で二値化画像が表示されました!
実際のcv2.THRESH_BINARY関数は以下のように書かれており、引数にはそれぞれ以下を指定します。

第一引数
二値化させたいグレースケール画像

第二引数
閾値を指定

第三引数
0~255段階の諧調の内で、上限値を指定

cv2.threshold(gray,130,255,cv2.THRESH_BINARY)

では実際に第二引数第三引数を変化させた場合の二値化像の変化を見てみましょう!

thresh-image5
第2引数のみを変更した結果
すらいむ管理人

第2引数(閾値)の値を小さくすると、白だと判定される領域が広くなっていますね!

thresh-image6
第3引数のみを変更した結果
すらいむ管理人

第3引数の値を小さくすると、白と判別されたエリアの色が暗くなっていますね!

cv2.THRESH_BINARY_INV

cv2.THRESH_BINARY_INV関数は、cv2.THRESH_BINARY関数で判定した、白と黒の領域を反転させて表示させる手法です!

cv2.thresholdメソッド内の第4引数に、cv2.THRESH_BINARY_INVを指定して利用します。実行結果は以下のようになります。

ret,thresh=cv2.threshold(gray,130,255,cv2.THRESH_BINARY_INV)

thresh-image7
第2引数のみを変更した結果
thresh-image8
第2引数のみを変更した結果

cv2.THRESH_TRUNC

cv2.THRESH_TRUNCのTRUNCは、Truncationのことで閾値を切り捨てるということを意味します。

そのため第2引数で指定した閾値以上の領域は閾値で指定した値がそのまま適用されます
そして最も重要なのは、閾値で指定した値よりも下の領域は二値化がされず、そのままグレースケールで表示されるということです!

使い方は、cv2.thresholdメソッド内の第4引数に、cv2.THRESH_BINARY_INVを指定して利用します。実行結果は以下のようになります。

ret,thresh=cv2.threshold(gray,200,200,cv2.THRESH_TRUNC)

第一引数
二値化させたいグレースケール画像

第二引数
閾値を指定

第三引数
値を変えても変化が無かったので今回は特に値を変更させる必要はなさそうです。

thresh-image9
第2引数のみを変更した結果
すらいむ管理人

画像が明るすぎるけど、グレースケールはそのままで表示させたいという場合に使えますね!

cv2.THRESH_TOZERO

cv2.THRESH_TOZERO関数は、cv2.THRESH_TRUNCの逆のことをしている関数です!

つまり第2引数で指定した閾値よりも輝度値が下の領域には、閾値で指定した値を適用します。そして、閾値で指定した輝度値よりも上の領域は、グレースケールのままで表示させるというものです。

使い方は、cv2.thresholdメソッド内の第4引数に、cv2.THRESH_TOZEROを指定して利用します。実行結果は以下のようになります。

ret,thresh=cv2.threshold(gray,130,10,cv2.THRESH_TOZERO)

第一引数
二値化させたいグレースケール画像

第二引数
閾値を指定

第三引数
値を変えても変化が無かったので今回は特に値を変更させる必要はなさそうです。

thresh-image10
第2引数のみを変更した結果

cv2.THRESH_TOZERO_INV

cv2.THRESH_TOZERO_INV関数は、cv2.THRESH_TOZERO関数で判定した、白と黒の領域を反転させて表示させる手法です!

cv2.thresholdメソッド内の第4引数に、cv2.THRESH_TOZERO_INVを指定して利用します。実行結果は以下のようになります。

ret,thresh=cv2.threshold(gray,130,255,cv2.THRESH_TOZERO_INV)

thresh-image11
第二引数のみを変更した結果

【余談】他にもプログラミングに役立つガジェットなども紹介していますので是非参考にしてみてください~!

適応的閾値処理(adaptive threshold)

適応的二値化とは

二値化処理には、閾値処理の他に適応的二値化処理(adaptive threshold)という手法があります。

適応的二値化とは、画像全体に対して一定の閾値を用いて二値化するのではなく、画素ごとで適切な閾値を用いて二値化する手法のことです。

このような仕組みで二値化を行うため、輝度を平滑化しなくても正確に二値化を行うことができます

そしてこの適応的二値化で扱える関数のうち、今回紹介するのは以下の2つです。

OTSU

OTSUは、大津の二値化と呼ばれており、自動的に閾値を設定していく適応的閾値処理の一つです。このOTSUを用いる場合には、プログラムを以下のように書きます。

import cv2

def main():
    image=cv2.imread("C:\Imagefile\cat-image.jpg")

    gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
    ret,thresh = cv2.threshold(gray,0,255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    #cv2.imwrite("C:\Imagefile\cat-gray.jpg",thresh)
    cv2.imshow("gray1",thresh)

    cv2.waitKey()
    cv2.destroyAllWindows()
if __name__=="__main__":
    main()

thresh-image12
第3引数のみを変更した結果

第2引数は変更しても変化がなかったので特に指定する必要はないと思います。

cv2.ADAPTIVE_THRESH_MEAN_C

cv2.ADAPTIVE_THRESH_MEAN_Cを用いて適応的二値化を行った結果を以下に示します。

 thresh = cv2.adaptiveThreshold ( gray , 160 , cv2.ADAPTIVE_THRESH_MEAN_C , cv2.THRESH_BINARY , 7 , 8 )

第一引数
二値化させたいグレースケール画像

第二引数
条件(第三引数)を満たすピクセルに割り当てられる輝度値

第三引数
適応的二値化アルゴリズムの指定。
“cv2.ADAPTIVE_THRESH_MEAN_C” あるいは ”cv2.ADAPTIVE_THRESH_GAUSSIAN_C”

第四引数
閾値処理の種類を指定。
“cv2.THRESH_BINARY” あるいは “cv2.THRESH_BINARY_INV”

第五引数
どれくらいの画素数を一つの領域とするか(近傍領域)の指定。
奇数のみで指定(通常3,5,7などが用いられる)。偶数を入力するとエラーがでる

第六引数
平均または加重平均から減算される定数

import cv2

def main():
    image=cv2.imread("C:\Imagefile\cat-image.jpg")

    gray=cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
    thresh=cv2.adaptiveThreshold(gray,160,cv2.ADAPTIVE_THRESH_MEAN_C,
                                     cv2.THRESH_BINARY,7,8)
    cv2.imshow("gray1",thresh)

    cv2.waitKey()
    cv2.destroyAllWindows()
if __name__=="__main__":
    main()

(実行結果)

thresh-image13
第2引数のみを変更した結果
すらいむ管理人

適応的二値化後の輝度値の上限(白で表される部分)の色味が変わっていますね!

thresh-image14
第5引数のみを変更した結果
すらいむ管理人

第5引数の値を変化させると、黒で表される領域のサイズが変わっていますね!引数の値が小さい方が一つ一つの領域が細かくなっています!

cv2.ADAPTIVE_THRESH_GAUSSIAN_C

cv2.ADAPTIVE_THRESH_GAUSSIAN_Cを用いて適応的二値化を行った結果を以下に示します。

 thresh = cv2.adaptiveThreshold ( gray , 160 , cv2.ADAPTIVE_THRESH_GAUSSIAN_C , cv2.THRESH_BINARY , 7 , 8 )

(実行結果)

thresh-image15
第2引数のみを変更した結果
すらいむ管理人

先ほどと同様に、適応的二値化後の輝度値の上限(白で表される部分)の色味が変わっていますね!

どの二値化手法が最も優れているのか

最後に、どの二値化手法を用いればよいのかお話していきたいと思います!

結論からお話ししますと、、、、、、

状況に応じて適したものを選ぶしかない!です

二値化画像の用途や、撮影機器、機械学習にかける場合には、そのAIなどの外部要因によって優れている二値化手法は変わってきます

そのため画像を二値化して、何をするか明確になったときに色々と試してみて精度がもっともよいと思われる二値化手法を用いるのが良いと思います。

すらいむ管理人

余談ではありますが、ボクは二値化処理にOTSUを用いることが多いです!

以上となります。最後まで見ていただきありがとうございました!
色(RGB値)の変換方法Python学習に役立つおすすめ書籍も紹介していますので是非参考にしてみて下さい!

プログラミング学習に役立つガジェット厳選4選!筆者が実際に使ってみて有用だと感じた、プログラミングに役立つガジェットを紹介しています!プログラミングされている方には是非とも読んでみてほしい記事です!...