PythonとOPENCVを使って、秒針を読む(1)

概要

 Windows10 パソコン上で、PYTHONとOPENCVを使って秒針を読むプログラムを検討しました。簡易検証なので、手軽さからスマホアプリのストップウォッチの秒針を読み取ることにしました。
 下写真の右側スマホを左側カメラで撮像した画像をパソコンに取り込んで秒針の値を解析します。

【構成】スマホ(右)のストップウォッチ秒針をカメラ(左)撮像

秒針を解析するロジック

 解析ロジック概要について紹介します。
 次の写真左半分は撮像した画像のスマホ部分のみをトリミングし表示しています。赤色外周矩形枠はスクリーンの外周部分をプログラム内で定義し、実画像上で一致しているか確認する為の表示です。この赤枠部分を実測値の縮尺になる様に射影変換し、時計部分のみ切り出したものが右側の図です。
 右側下半分は時計部をそのまま表示し、プログラムで定義した時計中心の一致と射影変換が正しく行われていることを確認する目的で赤い中心円と時計の外周円を表示しています。  
 秒針の回転中心がずれていたり、赤い外周円と時計の目盛のギャップに偏りがある場合はプログラムの定義を修正します。

【秒針解析状況】

 上写真の右上半分の写真が実際の解析状況を示しています。
 撮影画像を2値化後、秒針回転軸を中心に1度づつ回転させ、図の赤い四角枠と秒針が重なった時の回転角度から経過時間を算出しています。赤い四角枠の処理領域側を回転させる方法が解らなかったので、画像側を回転させることにしました。重なり検出は画像の白ピクセル数を計測する関数(countNonZero)を使用しています。
 解析した経過時間は、写真右半分の時計の少し下部分に表示しています。(写真では「52sec」と表示)

秒針解析状況動画

 秒速解析状況動画です。2倍速再生しています。

プログラム(python+OpenCV)

from pypylon import pylon
import cv2
import numpy as np
import time

# conecting to the first available camera
camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice())

# Grabing Continusely (video) with minimal delay
camera.StartGrabbing(pylon.GrabStrategy_LatestImageOnly) 
converter = pylon.ImageFormatConverter()

# converting to opencv bgr format
converter.OutputPixelFormat = pylon.PixelType_BGR8packed
converter.OutputBitAlignment = pylon.OutputBitAlignment_MsbAligned

P_LU=[120,108]              # [左上] 射影変換[X,Y]
P_RU=[918,91]               # [右上] 射影変換[X,Y]
P_RD=[933,1827]             # [右下]  射影変換[X,Y]
P_LD=[133,1830]             # [左下]  射影変換[X,Y]

#携帯サイズ 73.4 X 155.2
dst_w=721                   # WINDOW幅
dst_h=1552                  # WINDOW高さ 

cnt_w=round(dst_w/2)-4      # 時計中心(X)
cnt_h=457                   # 時計中心(Y)
cnt_r=280                   # 切り取り領域

scale = 1.0                 #スケールを指定
ctr_rot = (cnt_r, cnt_r)    #回転中心

threshold = 140             # 二値化閾値
angle = 0
pixel_thrsh = 100

while camera.IsGrabbing():
    grabResult = camera.RetrieveResult(5000, pylon.TimeoutHandling_ThrowException)

    if grabResult.GrabSucceeded():
        # Access the image data
        image = converter.Convert(grabResult)
        img = image.GetArray()

        # 画像の大きさを取得
        height, width, channels = img.shape[:3]

        # 対象サイズ程度にクロップ
        img1 = img[5 : 1905, round(width/2) - 550 : round(width/2) + 550 ]
        hgt1, wdt1, chnls1 = img1.shape[:3]

        pts = np.array([P_LU,P_RU,P_RD,P_LD])
        pts = pts.reshape((-1,1,2))
        cv2.polylines(img1,[pts],True,(0,0,255),2)

        pts_src = np.float32([ P_LU , P_RU , P_LD , P_RD ])
        pts_dst = np.float32([[0,0],[dst_w,0],[0,dst_h],[dst_w,dst_h]])

        # 射影変換
        M = cv2.getPerspectiveTransform( pts_src , pts_dst )
        img_trn = cv2.warpPerspective( img1 , M , ( dst_w , dst_h ))

        img_wch = img_trn[ cnt_h - cnt_r : cnt_h + cnt_r , cnt_w - cnt_r : cnt_w + cnt_r ]
        img_gray = cv2.cvtColor(img_wch, cv2.COLOR_BGR2GRAY)
        ret,img_thrsh = cv2.threshold(img_gray , threshold , 255 , cv2.THRESH_BINARY)

        # 経過時間検出処理
        for num in range(360):            
            angle = angle + 1                                               # 回転角を指定

            if angle > 359 :
                angle = 0
            
            trans = cv2.getRotationMatrix2D(ctr_rot, angle , scale)         # getRotationMatrix2D関数を使用
            img_rot = cv2.warpAffine(img_thrsh, trans, (2*cnt_r,2*cnt_r))   # アフィン変換

            img_chk = img_rot[cnt_r-165:cnt_r-115,cnt_r-1:cnt_r+4]
            whole_area=img_chk.size
            whitePixels = cv2.countNonZero(img_chk)
            blackPixels = whole_area - whitePixels

            if blackPixels > pixel_thrsh:
                img_rot_rgb = cv2.cvtColor(img_rot,cv2.COLOR_GRAY2RGB)      # 回転画像をRGB変換
                cv2.rectangle(img_rot_rgb, (cnt_r-8, cnt_r-165), (cnt_r+8, cnt_r-115), (0, 0, 255), thickness=2)
               
                sec_trs= round( 60 * angle / 360)                           # 角度を秒変換
                cv2.circle(img_wch, (cnt_r, cnt_r), 12, (0, 0, 255), thickness=2)
                cv2.circle(img_wch, (cnt_r, cnt_r), 268, (0, 0, 255), thickness=2)
                cv2.putText(img_wch, "" + str(blackPixels)+"pixels", (0, 30), cv2.FONT_HERSHEY_PLAIN, 2,(0, 0, 255), 2, cv2.LINE_AA)

                img_mrg1 = cv2.vconcat([img_rot_rgb, img_wch])              # 2画像を縦連結
                hgt2, wdt2, chnls2 = img_mrg1.shape[:3]

                img1_resize = cv2.resize(img1, (int(1100*1120/1900),1120))

                img_mrg2 = cv2.hconcat([img1_resize, img_mrg1])              # 2画像を縦連結
                cv2.putText(img_mrg2, "" + str(sec_trs)+"sec", (250, 700), cv2.FONT_HERSHEY_PLAIN, 5,(0, 0, 255), 3, cv2.LINE_AA)
                
                cv2.namedWindow('img_mrg2', cv2.WINDOW_NORMAL)
                cv2.imshow('img_mrg2', img_mrg2)


                time.sleep(0.1)
                break
            
        
        k = cv2.waitKey(1)
        if k == 27:
            break
    grabResult.Release()
    
# Releasing the resource    
camera.StopGrabbing()

cv2.destroyAllWindows()

まとめ

 久しぶりの投稿です。
 サイトのPHP・WordPress・他のアップデートがうまく出来ず、挫折しかけましたが、プラグインを解除(停止?)後アップデートするのが良いようです。
 私の場合、後から全て削除したので、完全には戻りませんでしたが何とか閲覧できるレベルには戻った様です。
 また、少しづつでも続けていきたいと思います。


 

コメントを残す

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