スランプグラフから差枚数を自動算出するツール作成(前編)
今回はスランプグラフから差枚数を自動算出するツールをPythonで作りたいと思います。
スランプグラフから差枚数を算出するには以下の2ステップが必要となるので、前編・後編の2部構成で説明します。
1. グラフの位置を求める
2. スランプグラフから差枚数を求める
今回は、下図のような画像から差枚数を読み取ってみます。
1. グラフの位置を求める
はじめに
先ほどの画像では、1枚の画像の中に3枚のスランプグラフが含まれています。
この場合、まずは画像の中にある3枚分のスランプグラフの場所を求める必要があります。
方法は様々なものがあると思いますが、今回は「テンプレートマッチング」を用いて、スランプグラフの場所を見つけていきます。
テンプレートマッチングは下図のような技術で、あらかじめ作成したテンプレート画像が、調べたい画像(対象画像)の中のどこにあるのかを探してくれます。
テンプレートマッチングのアルゴリズムも様々なものがありますが、簡単な例としては引き算で探す方法があげられます。
例えば●、▲、■から、■を引き算すると以下のような結果になります。
つまり、引き算をしたときに、何もなくなるところが今回探したい場所になります。
これを対象画像からテンプレート画像をちょっとずつずらしながら引き算していくことで、テンプレート画像と同じ画像がある位置を知ることができます。
Pythonでのコーディング
では、実際にPythonでグラフの位置を探してみましょう。
まずは、テンプレート画像とグラフを探したい画像を準備します。
今回は、以下のような画像を準備しました。
※今回の方法の注意点として、テンプレート画像のグラフの大きさと、グラフを探したい画像のグラフの大きさを揃える必要があります。
※グラフの大きさが違うと、引き算した時に差が出るので、正しい位置が求められません。
テンプレートマッチングについて長々と説明しましたが、Pythonでのコードは↓の二行だけです。
このコードを実行することで、先ほどの例でいうところの「ちょっとずつずらしながら引き算した結果」を出力してくれます。
※しかも実際にはもっと賢いことをやってくれてます。ありがとうPython!!
import cv2 res = cv2.matchTemplate(グラフを探したい画像,テンプレート画像,cv2.TM_CCOEFF_NORMED)
↑のコードを実行するために、必要な情報を入力していきます。
画像の読み込み
まずは、画像の読み込みから。
カラー画像でもマッチング可能ですが、今回は多少色が違っても同じグラフとして認識したいので、グレースケールに変換しておきます。
# テンプレート画像の場所 Template_path = "D:\Test\Template.bmp" # グラフを探したい画像の場所 Target_path = "D:\Test\Target.bmp" # テンプレート画像の読み込み template = cv2.imread(Template_path) template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY) # グラフを探したい画像の読み込み img_rgb = cv2.imread(Target_path) img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
テンプレートマッチングの実行
画像の読み込みが完了したら、早速テンプレートマッチングを実行します。
# テンプレートマッチング実行 res = cv2.matchTemplate(img_gray,template_gray,cv2.TM_CCOEFF_NORMED)
テンプレートマッチングの結果の精製
「res」にテンプレートマッチングの結果が出力されているので、この結果を元に欲しい情報を入手していきます。
まずは類似度でフィルタすることが一般的です。
類似度は0~1の数値で求められており、0が全く違う画像、1が全く同じ画像です。
感覚的には0.7以上であれば問題ないと思います。
↓のコードでは、「loc」にテンプレートマッチングで類似度0.7以上の座標が出力されています。
# 閾値の設定。threshold=1だと全く同じ画像だけ認識する threshold = 0.7 # 閾値以下のポイントを削除 loc = np.where( res >= threshold)
一旦、どんなデータが得られたか確認してみましょう。
「loc」は配列形式なので、for文で中身を1個ずつ見ていきます。
# テンプレートマッチングで見つかった位置の表示 for pt in zip(*loc[::]): print("x, y = ", pt[1], ", ", pt[0])
結果は↓のようになりました。
大きく3か所の座標が得られていますが、それぞれ数pixずれた位置の結果も出力されています。
数pixずれた位置の結果は必要ないので、もうちょっと結果を精製していきます。
x, y = 91 , 223 x, y = 92 , 223 x, y = 93 , 223 x, y = 732 , 223 x, y = 733 , 223 x, y = 92 , 572 x, y = 93 , 572 x, y = 92 , 573
類似度を微調整しても良いのですが、せっかくなので違うやり方で不要点を削除します。
今回不要なデータは数pixずれただけの位置なので、座標同士を比較して距離が近ければ削除する方法が使えそうです。
つまり、X座標が近くて、なおかつY座標も近ければ不要な座標ですね。
式であらわすと、以下のようになります。
座標AのX座標 - 座標BのX座標 < 数pix & 座標AのY座標 - 座標BのY座標 < 数pix
for文で「loc」の中身を見ながら不要なデータがある位置を「index」という配列に記録していきます。
記録した不要データの位置をテンプレートマッチングの結果を入れた「loc」という配列から削除すればOKです。
# 座標リストで近すぎる座標を削除 # Mergin[pix]の値より近い座標は削除する Mergin = 10 index = [] for i in range(len(loc[1][:])): count = 0 for j in range(len(loc[1][:])): # Mergin[pix]の値より近い座標の番号をメモする if abs(loc[1][:][i] - loc[1][:][j]) < Mergin and abs(loc[0][:][i] - loc[0][:][j]) < Mergin: if count > 0: index.append(j) count = count + 1 # Mergin[pix]の値より近い座標の番号を削除する loc = np.delete(loc, index, axis=1)
テンプレートマッチングの結果確認
不要データが無事に削除できたか確認してみます。
せっかくなので、グラフを探したい画像内で見つかった位置を赤枠で囲ってみましょう。
# テンプレートの画像サイズ確認 w, h = template_gray.shape[::-1] # テンプレートマッチングで見つかった位置の表示(精製後) for pt in zip(*loc[::]): print("x, y = ", pt[1], ", ", pt[0]) cv2.rectangle(img_rgb, (pt[1], pt[0]), (pt[1] + w, pt[0] + h), (0,0,255), 10) cv2.imwrite("D:\Test\Template_Result.bmp",img_rgb) cv2.imshow("Matching Location", img_rgb) cv2.waitKey(0) cv2.destroyAllWindows()
出力結果はこちら。
座標が3つだけ(=グラフの数)だけ出力されており、画像の赤枠を見る限りグラフの位置も正しく認識できています。
x, y = 91 , 223 x, y = 732 , 223 x, y = 92 , 572
ここまでのコード
スランプグラフの位置を求めるまでのコードはこちら。
# coding: UTF-8 import cv2 import numpy as np # テンプレート画像の場所 Template_path = "D:\Test\Template.bmp" # グラフを探したい画像の場所 Target_path = "D:\Test\Target.bmp" # テンプレート画像の読み込み template = cv2.imread(Template_path) template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY) # グラフを探したい画像の読み込み img_rgb = cv2.imread(Target_path) img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) # テンプレートマッチング実行 res = cv2.matchTemplate(img_gray,template_gray,cv2.TM_CCOEFF_NORMED) # 閾値の設定。threshold=1だと全く同じ画像だけ認識する threshold = 0.7 # 閾値以下のポイントを削除 loc = np.where( res >= threshold) # # テンプレートマッチングで見つかった位置の表示 # for pt in zip(*loc[::]): # print("x, y = ", pt[1], ", ", pt[0]) # print("----------------------------") # 座標リストで近すぎる座標を削除 # Mergin[pix]の値より近い座標は削除する Mergin = 10 index = [] for i in range(len(loc[1][:])): count = 0 for j in range(len(loc[1][:])): # Mergin[pix]の値より近い座標の番号をメモする if abs(loc[1][:][i] - loc[1][:][j]) < Mergin and abs(loc[0][:][i] - loc[0][:][j]) < Mergin: if count > 0: index.append(j) count = count + 1 # Mergin[pix]の値より近い座標の番号を削除する loc = np.delete(loc, index, axis=1) # テンプレートの画像サイズ確認 w, h = template_gray.shape[::-1] # テンプレートマッチングで見つかった位置の表示(精製後) for pt in zip(*loc[::]): print("x, y = ", pt[1], ", ", pt[0]) cv2.rectangle(img_rgb, (pt[1], pt[0]), (pt[1] + w, pt[0] + h), (0,0,255), 10) cv2.imwrite("D:\Test\Template_Result.bmp",img_rgb) cv2.imshow("Matching Location", img_rgb) cv2.waitKey(0) cv2.destroyAllWindows()
後編では、求めたスランプグラフの位置から差枚数を計算していきます。
にほんブログ村
コメント
コメントを投稿