概要
先回、ラズパイ4に pypylon をインストールし、Basler社カメラを使える様にしましたが、今回は近所の電気屋さんで比較的リーズナブルに購入できるwebカメラを使ってみます。
また、OpenCVのハフ(Hough)変換という処理を使って、水平方向のスマホ時計の位置・傾きを検出し、秒針を読み取る検討をしました。奥行方向移動は今回未検討です。
リンク
結果
次動画の通り 左上に秒針読取結果を水色表示しています。途中で時計を水平移動しますが、位置・傾きを補正し結果表示します。細くはいろいろ課題がありますが、とりあえず動いてくれました。
リンク
処理フロー
処理フローは次の通りです。
① ハフ変換(円)で時計部と中心座標を検出(黄緑色)
② ハフ変換(直線)で秒針(ピンク)検出
検出直線で円中心に最も近いものを秒針と判定
③ 秒針の始点を円中心に近い側として傾き計算
④ ハフ変換(直線)でスマホ上端(水色)検出
円中心からの距離と傾き(10度以内)で判定
⑤ 秒針傾きとスマホ傾きから、経過時間(秒)計算
OpenCVのサンプルプログラムでは、ハフ変換(直線)前にCanny法でエッジ検出しているので同じ様にしています。参考までに処理結果は次の様になりました。
プログラム
今回のテストしたプログラムです。
import cv2
import math
import numpy as np
import time
x3 = 0 # 秒針中心座標初期化(x)
y3 = 0 # 秒針中心座標初期化(y)
sec_trs=0 # 経過時間
ang_pin=0 # 秒針角度
ang_hsi=0 # 補正角度
pic_cnt=0 # 画像保存枚数
capture = cv2.VideoCapture(0)
if capture.isOpened() is False:
raise IOError
# ループ処理
while(True):
try:
ret, frame = capture.read()
if ret is False:
raise IOError
img_gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) # グレイ変換
edges = cv2.Canny(img_gray,10,220,apertureSize=3) # エッジ検出
# ハフ変換で時計部円検出 (半径等条件設定により、時計部のみ検出する様にしている)
circles=cv2.HoughCircles(img_gray,cv2.HOUGH_GRADIENT,1,30,param1=70,param2=30,minRadius=80,maxRadius=110)
cnt_cntr=0
if circles is not None:
for cx,cy,r in circles.squeeze(axis=0).astype(int):
cv2.circle(frame,(cx,cy),r,(0,255,0),2)
cv2.circle(frame,(cx,cy),1,(0,0,255),3)
x3_tmp = cx
y3_tmp = cy
cnt_cntr = cnt_cntr + 1
if cnt_cntr==1:
x3 = x3_tmp
y3 = y3_tmp
if x3 > 0 and y3 > 0 :
# ハフ変換で直線抽出
lines = cv2.HoughLinesP(edges, rho=1, theta=np.pi/180, threshold=90,minLineLength=20,maxLineGap=5)
ang_hsi =0
if lines is not None:
for line in lines:
x1 , y1 , x2 , y2 = line[0] # 検出直線始点・終点座標取得
# 円中心と検出した直線の距離計算
u = np.array([ x2-x1 , y2-y1 ])
v = np.array([ x3-x1 , y3-y1 ])
D_CTR = math.floor(abs(np.cross(u,v) / np.linalg.norm(u)))
D_P1 = math.sqrt((x1-x3)**2+(y1-y3)**2) # 円中心と直線始点の距離計算
D_P2 = math.sqrt((x2-x3)**2+(y2-y3)**2) # 円中心と直線終点の距離計算
# 距離が短い点を始点として角度計算(ラジアン)
if D_P1 > D_P2 and D_CTR < 10:
rslt_rad = math.atan2( y1-y2 , x1-x2 )
else :
rslt_rad = math.atan2( y2-y1 , x2-x1 )
rslt_ang = math.degrees(rslt_rad) # ラジアンから角度変換
# 0秒=0度として、角度変換
if rslt_ang > -90 :
rslt_ang_360 = rslt_ang + 90
else :
rslt_ang_360 = rslt_ang + 450
dsp_ang = str(math.floor(rslt_ang_360))
if D_CTR < 10:
# 円中心からの距離が10以下 → 秒針と判定
cv2.putText(frame,dsp_ang,(x2,y2),cv2.FONT_HERSHEY_PLAIN,2,(255,0,255),1,cv2.LINE_AA)
cv2.line(frame,(x1,y1),(x2,y2),(255,0,255),3) # 秒針描画(ピンク)
ang_pin = rslt_ang_360
else:
# 秒針以外に検出した直線
if rslt_ang < 15 and rslt_ang > -15 and D_CTR > 150 and D_CTR < 220 and y1 < y3 and y2 < y3 :
# スクリーン上側の水平方向直線(傾き10度以内 + 他条件)
cv2.putText(frame,str(math.floor(rslt_ang))+",D" + str(D_CTR),(x1,y1+30),cv2.FONT_HERSHEY_PLAIN,1,(255,125,0),1,cv2.LINE_AA)
cv2.line(frame,(x1,y1),(x2,y2),(255,255,0),3) # スクリーン上側水平方向直線(水色)
ang_hsi = rslt_ang # 補正角度記憶
else:
# その他の検出した直線
#cv2.putText(frame,str(math.floor(rslt_ang))+",D" + str(D_CTR),(x1,y1-10),cv2.FONT_HERSHEY_PLAIN,1,(0,0,255),1,cv2.LINE_AA)
cv2.line(frame,(x1,y1),(x2,y2),(0,0,255),2) # 赤色直線
except KeyboardInterrupt:
cv2.putText(frame,"Error",(25,25),cv2.FONT_HERSHEY_PLAIN,2,(0,0,255),1,cv2.LINE_AA)
# 角度から秒に変換(秒針傾きと補正角度考慮)
sec_trs = math.floor( 60 * (ang_pin - ang_hsi) / 360 )
if sec_trs<=0:
sec_trs = 60+sec_trs
if sec_trs>=60:
sec_trs = sec_trs-60
# 経過時間・時計部中心座標等、表示追加
cv2.putText(frame,"TIME : "+str(sec_trs)+"sec",(15,45),cv2.FONT_HERSHEY_PLAIN,3,(255,255,0),1,cv2.LINE_AA)
cv2.putText(frame,"( Center Of Circle : "+str(x3)+" , "+str(y3) + " )",(350,25),cv2.FONT_HERSHEY_PLAIN,1,(0,255,255),1,cv2.LINE_AA)
edges_rgb=cv2.cvtColor(edges,cv2.COLOR_GRAY2RGB) # 画像合成用に変換
cv2.putText(edges_rgb,"Canny Edge Detection",(15,45),cv2.FONT_HERSHEY_PLAIN,3,(0,0,255),1,cv2.LINE_AA)
img_mrg=cv2.hconcat([edges_rgb,frame]) # 画像合成
cv2.imshow( 'img_mrg' , img_mrg )
cv2.imshow('frame',frame)
#cv2.imshow('edges',edges_rgb)
time.sleep(0.09)
# ESCで処理中断
k = cv2.waitKey(1)
if k==27:
break
capture.release()
cv2.destroyAllWindows()
まとめ
照明・背景色・移動範囲等をある程度限定していますが、時計移動に追従して、秒針を解析することが出来ました。ただ、連続して動かすと、誤認識等もあるので今後の課題として認識しておきます。