ラズパイ4で数字を画像認識する(open CV テンプレートマッチング)

テンプレートマッチングについて

 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でテンプレートマッチングを試みました。
 下図はエクセルシートを直接キャプチャーしたものです。

エクセル上の数字認識(倍率:100%,MSゴシック,フォントサイズ:80)

② テンプレート画像作成

 下の写真は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]))
    

 

まとめ

 結果が出るのに少し時間がかかりますが、私個人は許容範囲内です。機会があれば、実際にデジタル表示器で試してみたいと思います。ラズパイはこの様な処理を連続的にできるのか、という疑問を感じました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です