Python
OpenCV
matplotlib
Jupyter

画像認識で硬貨を識別したい その1 グレイスケール変換して二値化して背景を排除

More than 1 year has passed since last update.


目的

画像処理などの勉強として、日本のコインの映像を撮って、写っているコインの金額の総計を求めるようなアルゴリズムを書いてみたい


今回やったこと

第1段階として、グレイスケール値を使って背景を排除(二値化)することを目指す


使用する画像

ぐちゃっとコインが置いてある状況だと、データがぐちゃぐちゃして分析しづらそうだったので、処理の検討用に、きれいに並んでいる写真を撮りました。

coin_series_b.png


環境


  • python 3.6.1

  • opencv 3.4.1+contrib


  • jupyter notebook上で処理を組んでいます。


モジュールのインポート

import numpy as np

import cv2
import matplotlib.pyplot as plt

%matplotlib inline

numpyとopenCV、表示用にmatplotlibからpyplotをインポート


画像の読み込み&とりあえず表示

img_BGR = cv2.imread("coin_series_b.png")

img_RGB = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB)
plt.imshow(img_RGB)

とすると、以下のような元画像が表示されます。

image.png

上記の2行目で、cv2.cvtColorで色変換していますが

OpenCVはBGRで画像データ読込しているのに対し、

MatplotlibはRGBで表示するため、変換をかけています。

この変換がないと、変な色で表示されます。


グレイスケールに変換して表示

img_gray = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2GRAY)

plt.imshow(img_gray)
plt.colorbar()

1行目で色変換してます。つまり、ピクセルごとの明るさを、0~255の値に変換しています。

2行目でグレイスケール画像を表示、3行目でカラーバーをつけています。

すると、こうなります。

白い背景部分は黄色っぽく、硬貨のある部分は青っぽく表示されています。

この、黄色い部分を消せば、硬貨部分だけが取り出せる、はず。

image.png

右下部分、撮影時に手か何かの影になった部分が低めになっているのがちょっと気になりますが・・・。


ヒストグラム化してみる

さて、このままだと画像を見ても、どういう数値が入っているのかよくわからず、処理が組めません。

様子を見るために、グレイスケールの輝度の分布をヒストグラムにして表示してみます。

plt.hist(img_gray.flatten())

image.png

img_gray.flatten() というのは、二次元のアレイになっている画像データ(img_gray)を、一次元の長い数値のアレイに変換する(flatten()、ひらたくする)、ということです。

plt.hist()で、グレイスケール画面全体の画素値データの塊を、1つのデータとして扱うために、このようにしています。

というか、plt.hist()じたいが、一次元の配列しか受け取ってくれないぽい。

さて、このヒストグラムだとなんとなく、いまいち粗い、ので、細かくします。



plt.hist(img_gray.flatten(),bins = np.arange(0,255,8))



bins 以下の内容を追加したことになります。

ヒストグラムの段階の設定が自動設定だったところを、0から8区切りで255までの配列で指定しています。

結果、こうなります。

image.png

200前後の凹んだ部分より上が背景の白部分で、それより下が硬貨部分、なのかな。


グレイスケール値で二値化してみる

さて、背景を完全に排除したいので、200よりちょっと下の180を閾値として、二値化してみます。

ret, th_img1 = cv2.threshold(img_gray,180,255,cv2.THRESH_BINARY_INV)

plt.imshow(th_img1)

cv2.thresholdの引数は、入力画像、閾値、二値化後の値、二値化の方法です。

二値化の方法でcv2.THRESH_BINARY_INVとあるのは、今回、グレイスケール画像中で値が低い方の部分(硬貨部分)を255に、背景を0にする二値化を行うためです。

INVつまり、inverseで、逆の二値化を行う、ということですね。

さて、二値化の結果は以下の通りになりました。

image.png

やはり、右下の影の部分がちょっと入り込んでますねー。

それ以外はまあまあ良いのではないでしょうか。硬貨の形状はうまく取れています。

影の部分を取るには、閾値を下げれば良い(もっと暗いところだけ硬貨とする)のですけれど、影が消えるところまで(閾値150)下げると、こうなります。

ret, th_img1 = cv2.threshold(img_gray,150,255,cv2.THRESH_BINARY_INV)

plt.imshow(th_img1)

image.png

影は消えるけれども、硬貨部分も結構消えています。

輪郭は取れるから、使えなくはなさそうだけれども・・・。


まとめ

グレイスケールだと、影の映り込みとかでうまく硬貨部分を抽出できないみたいです。

もしかしたら、背景が白でなく、黒い紙だったら、うまくいくのかもですが・・・。

 

次回、グレイスケール以外の方法も試してみます。