テンプレートマッチングについて
OpenCVのテンプレートマッチングという機能を使って、数字認識を行います。先回の投稿で顔認識にトライしましたので、分類器自作も検討していますが、テンプレートマッチングについて知り、限定された条件下であれば文字認識等にも応用できると考え、今回 試してみることにしました。
まず、テンプレートマッチングについて整理します。テンプレートマッチングは、下図の①元画像から、②テンプレート画像を検索し、③検索結果の様に場所等を特定するロジックです。
①元画像

②テンプレート画像

③検索結果(赤枠部がテンプレート画像)

上記処理のプログラムを示します。
import cv2
try:
i_orgn=cv2.imread('flower_all.jpg')
i_tmpl=cv2.imread('flower_part.jpg')
if i_orgn is None or i_tmpl is None:
print('ファイルを読み込めません')
import sys
sys.exit()
# リサイズ
i_orgn = cv2.resize(i_orgn, (int(i_orgn.shape[1]*0.4), int(i_orgn.shape[0]*0.4)))
i_tmpl = cv2.resize(i_tmpl, (int(i_tmpl.shape[1]*0.4), int(i_tmpl.shape[0]*0.4)))
result= cv2.matchTemplate(i_orgn,i_tmpl,cv2.TM_CCOEFF_NORMED)
mmr=cv2.minMaxLoc(result)
pos=mmr[3]
dst=i_orgn.copy()
cv2.rectangle(dst,pos,(pos[0]+i_tmpl.shape[1],pos[1]+i_tmpl.shape[0]),(0,0,255),2)
cv2.imshow('dst',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
except:
import sys
print("Error:",sys.exc_info()[0])
print(sys.exc_info()[1])
import traceback
print(traceback.format_tb(sys.exc_info()[2]))
数字認識(テンプレートマッチング)
① 認識対象
計測器等の現在値を記録する場合、パソコン等との通信インタフェースがあれば、電子的データ取得が可能ですが、インタフェースが無い場合、目で見て手書きすることになります。今回は下記の様なデジタル表示値を画像処理で文字データ等に変換し、電子的に記録することを想定しています。

但し、実際には上図の様に表示する表示器は持っていないので、容易に準備できるエクセルのシート上に1セル1文字中央配置表示し、ラズパイ4に接続するUSBカメラで撮像後、python + openCVでテンプレートマッチングを試みました。
下図はエクセルシートを直接キャプチャーしたものです。

② テンプレート画像作成
下の写真はUSBカメラで撮影したそのままの状態です。

下の図は撮影画像を二値化したものです。実際評価していませんが、距離や角度による大きさ・歪の影響等は除去できませんが、明るさ等の影響は取り除くことが出来そうなので、二値化処理後、テンプレートマッチングすることにしました。

二値化画像を切り抜いて、下図の様に数字の0~9に対応するテンプレート画像を作成します。例えば、“template8.jpg”ファイルは数字の “8” に対応しています。

テンプレートを作成する時に使ったプログラムです。
上段{0,1,2,3,4},下段{5,6,7,8,9} の順になっている前提で画像を切り抜き、保存します。X_S, WDT, Y_S, HGT, X_D, Y_D等の切り抜き位置,サイズのパターンを指定する変数は実際の撮影画像にあわせて調整が必要です。
#テンプレート作成
import sys
import cv2
import math
capture = cv2.VideoCapture(0)
try:
if capture.isOpened() is False:
print("カメラを開けませんでした。")
sys.exit()
X_S=90
WDT=58
Y_S=119
HGT=88
X_D=98
Y_D=143
while True:
ret, frame = capture.read()
ret, frame = cv2.threshold(frame , 60, 255 , cv2.THRESH_BINARY)
cv2.imwrite("img_org.jpg",frame)
if cv2.waitKey(1) & 0xFF==ord('q'):
break
for i in range(10):
T_XS = X_S + (i-5*math.floor(i/5)) * X_D
T_YS = Y_S + Y_D * math.floor(i/5)
T_XE = T_XS + WDT
T_YE = T_YS + HGT
img_crop=frame[T_YS:T_YE,T_XS:T_XE]
cv2.imwrite("template"+str(i)+".jpg",img_crop)
cv2.rectangle(frame,(T_XS,T_YS),(T_XE,T_YE),(255, 255, 0),thickness=1)
cv2.imshow('frame',frame)
capture.release()
cv2.destroyAllWindows()
except:
print("Error:",sys.exc_info()[0])
print(sys.exc_info()[1])
import traceback
print(traceback.format_tb(sys.exc_info()[2]))
③ テンプレートマッチング
次の動画は実際に文字列変換している様子です。
画面左側モニターのエクセルシート上の数字をラズパイ4に接続する手前USBカメラで撮影し、ラズパイ側でpython+OpenCVテンプレートマッチング機能を使って、文字列変換後、右側モニターに結果表示しています。赤字部分が変換した文字列をテキスト表示したものです。
処理の流れを概略説明します。下図の結果表示例に示す通り、10箇所ある表示位置(水色の枠内)を1ヶ所づつ、“0”~“9” に対応する全テンプレート画像のテンプレートマッチングを行い ます。一致レベルを示すスコアを取得できるので、最もスコア値が大きいテンプレートの数字をその位置の数字としています。
(赤字部分は変換文字列(結果)をテキスト表示)

ラズパイ側 python プログラムは次の通りです。
import sys
import cv2
import math
import time
try:
capture=cv2.VideoCapture(0)
if capture.isOpened() is False:
print("カメラを開けませんでした。")
sys.exit()
X_S=65
WDT=98
Y_S=85
HGT=145
X_D=100
Y_D=148
while(True):
ret,frame=capture.read()
ret,frame = cv2.threshold(frame , 60, 255 , cv2.THRESH_BINARY)
getVal=["*","*","*","*","*","*","*","*","*","*"]
for i in range(10):
T_XS = X_S + (i-5*math.floor(i/5)) * X_D
T_YS = Y_S + Y_D * math.floor(i/5)
T_XE = T_XS + WDT
T_YE = T_YS + HGT
cv2.rectangle(frame,(T_XS,T_YS),(T_XE,T_YE),(255, 255, 0),thickness=1)
img_crop = frame[T_YS:T_YE,T_XS:T_XE]
maxVal_All = 0.7
num_dsp = -1
#0〜9のテンプレートと照合
for j in range(10):
i_tmpl=cv2.imread("template"+str(j)+".jpg")
result= cv2.matchTemplate(img_crop,i_tmpl,cv2.TM_CCOEFF_NORMED)
mmr=cv2.minMaxLoc(result)
maxVal=mmr[1]
if maxVal > maxVal_All:
num_dsp = j
maxVal_All = maxVal
if num_dsp != -1:
getVal[i] = str(num_dsp)
#print("Position "+str(i)+" : "+str(num_dsp))
#print(getVal)
cv2.putText(frame,
getVal[0]+getVal[1]+getVal[2]+getVal[3]+getVal[4],
(X_S, Y_S-10), cv2.FONT_HERSHEY_SIMPLEX,2,(0,0,255),2,cv2.LINE_AA)
cv2.putText(frame,
getVal[5]+getVal[6]+getVal[7]+getVal[8]+getVal[9],
(X_S, Y_S+HGT+Y_D+50), cv2.FONT_HERSHEY_SIMPLEX,2,(0,0,255),2,cv2.LINE_AA)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF==ord('q'):
break
#break
#time.sleep(0.1)
capture.release()
cv2.destroyAllWindows()
except:
print("Error:",sys.exc_info()[0])
print(sys.exc_info()[1])
import traceback
print(traceback.format_tb(sys.exc_info()[2]))
まとめ
結果が出るのに少し時間がかかりますが、私個人は許容範囲内です。機会があれば、実際にデジタル表示器で試してみたいと思います。ラズパイはこの様な処理を連続的にできるのか、という疑問を感じました。