【OpenCVを使ったスポーツ画像解析2(モーショントラッキング)】フェデラー選手の移動軌跡をグラフ化してみました
公開日:
最終更新日:2018/07/27
2018/07/27
先回、【OpenCVを使ったスポーツ画像解析1(モーショントラッキング)】テニス選手の移動量や軌跡をデータ化する
にて、選手(フェデラー)の動きをトラッキングする様子を動画で説明しました。
バウンディングボックスで選手をトラッキングしていますが、その重心の移動を軌跡データとして描画した動画が↓になります。
トラッキングデータである移動軌跡をグラフ上にプロットしてみます。
赤丸の位置をスタート位置として、そのあとの移動軌跡が描けていることがわかります。
グラフの軸の単位はpixelになっており、これをmに変換する必要があるのと、遠近補正を施す必要があります。
斜め上から撮影した動画になっていますが、遠近補正で真上から見た画像に変換します。
遠近補正にはホモグラフィ変換という変換法を使用しました。
opencvでは、findHomographyという関数を使用することで、台形から長方形へと遠近補正することができます。
pixelからmへの単位変換は、テニスコートのサイズは、縦23.8m 横 8.23mとなるので、縦2380 横823の長方形に変換しました。
軌跡が描けたので、次は移動距離を計算したいと思います。
見やすくするために、移動軌跡を拡大します。
移動距離は点間距離の合計値を総移動距離としています。
この場合、総移動距離は1989=19.9mとなりました。
動画を見ればわかりますが、このポイントでは19.9mも動いていませんよね。
これは、
・サーブを打っているときの移動量が大きくでてしまう(フォームモーションの影響?)
・0.03s(30fps)毎にデータ取得しているので、細かいノイズがのってしまう
の2点の影響で、実際の移動量よりも大きい移動量が算出されていると考えられます。
なので、今回は、
・サーブの時間帯(0~80フレーム)は省く
・0.3s(3fps)でデータ取得と間隔を大きくする
ことで、実際の移動距離に近づける作業をしました。
サーブの時間帯(0~80フレーム)を省いた軌跡データをみてみましょう。
このとき総移動距離は、10.6mとなります。
そして、0.3s毎にデータ取得とデータ取得間隔を少し大きくすると↓のようになります。
このとき総移動距離は、5.8mとなり、細かいノイズが減ったことでそこそこいい数字になったかと思います。
まだ改善の余地はあるかと思いますが、このポイントだけで条件を決めるわけにはいかないので、いろんなポイントパターンをみながら改善していきたいと思います。
スポーツ画像解析リンク
・【OpenCVを使ったスポーツ画像解析1】テニス選手の移動量や軌跡をデータ化する
・【OpenCVを使ったスポーツ画像解析2】フェデラー選手の移動軌跡をグラフ化してみました
・【OpenCVを使ったスポーツ画像解析3】選手やテニスボールの移動を自動で検出して移動軌跡をトラッキング
・【OpenCVを使ったスポーツ画像解析4】テニスコートのラインの自動検出
・【OpenCVを使ったスポーツ画像解析5】画像からテニス選手の位置を検出する
・【OpenCVを使ったスポーツ画像解析6】深層学習(ディープラーニング)を用いてテニス選手とボールをトラッキング
■環境
Windows10
Python3.5
OpenCV3.2+contrib
import cv2 import sys import numpy as np #トラッキングタイプでMILを選択 tracker = cv2.Tracker_create("MIL") #videoファイルを読み込む video = cv2.VideoCapture("aus-tennis.mp4") # ファイルがオープンできない場合の処理. if not video.isOpened(): print ("Could not open video") sys.exit() # 最初のフレームを読み込む ok, frame = video.read() if not ok: print ('Cannot read video file') sys.exit() # バウンディングボックスの最初の位置とサイズを設定 c,r,w,h = 320,210,70,90 bbox = (c,r,w,h) roi = frame[r:r+h, c:c+w] hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv_roi, np.array((0., 30.,32.)), np.array((180.,255.,255.))) roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180]) cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX) term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 80, 1) # 動画の読み込みと動画情報の取得 #movie = cv2.VideoCapture(target) fps = video.get(cv2.CAP_PROP_FPS) height = video.get(cv2.CAP_PROP_FRAME_HEIGHT) width = video.get(cv2.CAP_PROP_FRAME_WIDTH) # 形式はMP4Vを指定 fourcc = cv2.VideoWriter_fourcc(*'MJPG') result = "result/test_output.m4v" # 出力先のファイルを開く out = cv2.VideoWriter('output.avi',fourcc,20.0, (int(width), int(height))) # バウンディングボックスをフレームに設定 ok = tracker.init(frame, bbox) #モーション情報格納用の配列 motion=[] while True: # フレームを読み込む ok, frame = video.read() if not ok: break hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) dst = cv2.calcBackProject([hsv], [0], roi_hist, [0,180], 1) # フレームを更新 ok, bbox = tracker.update(frame) # バウンディングボックスを描画 if ok: #バウンディングボックスの重心位置を計算 g=(int(bbox[0])+int(0.5*bbox[2]), int(bbox[1])+int(0.5*bbox[3])) motion.append(g) #バウンディングボックスを描画 p1 = (int(bbox[0]), int(bbox[1])) p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3])) cv2.rectangle(frame, p1, p2, (0,0,255)) #軌跡データを描画 for i in range(len(motion)-1): cv2.line(frame,motion[i],motion[i+1],(255,0,0),1) # フレームを画面表示 cv2.imshow("Tracking", frame) #読み込んだフレームを書き込み out.write(frame) # ESCを押したら中止 k = cv2.waitKey(1) & 0xff if k == 27 : break video.release() out.release() cv2.destroyAllWindows()
#ホモグラフィ変換 import cv2 import numpy as np p1,p2,p3,p4=[229, 90],[147, 280],[508, 280],[429, 90]#補正前(台形)4点 c1,c2,c3,c4=[0, 0],[0, 2378],[823, 2378],[823, 0]#補正後(正方形)4点 src_pts = np.float32([p1,p2,p3,p4]).reshape(-1,1,2) dst_pts = np.float32([c1,c2,c3,c4]).reshape(-1,1,2) M,mask=cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
import matplotlib.pyplot as plt #モーションデータ整理 m=[] for i in range(166): m.append([motion[i][0],motion[i][1]]) xc=[] yc=[] xc_es=[] yc_es=[] for i in range(166): xc.append(dst[i][0][0]) yc.append(dst[i][0][1]) if(i>80):#サーブのデータは含まない xc_es.append(dst[i][0][0]) yc_es.append(dst[i][0][1]) data=[[0, 0],[0, 2378],[823, 2378],[823, 0],[0, 0]] array2=np.array(data) #間引きデータ xd=[] yd=[] xd_es=[] yd_es=[] for i in range(len(xc)-1): if(i%10==0): xd.append(xc[i]) yd.append(yc[i]) for i in range(len(xc_es)-1): if(i%10==0): xd_es.append(xc_es[i]) yd_es.append(yc_es[i]) #グラフプロット plt.scatter(xc[0],yc[0],c='red',s=30) plt.plot(xc,yc) plt.plot(array2[:,0],array2[:,1]) plt.xlim(-10,1000) plt.ylim(2500,-10) plt.show() #総移動距離計算 s=0 for i in range(len(xc)-1):#len(xc)-1 s+=math.sqrt((xc[i+1]-xc[i])**2+(yc[i+1]-yc[i])**2) s_es=0 for i in range(len(xc_es)-1):#len(xc)-1 s_es+=math.sqrt((xc_es[i+1]-xc_es[i])**2+(yc_es[i+1]-yc_es[i])**2) print(s) print(s_es)
[:en]先回、【OpenCVを使ったスポーツ画像解析1(モーショントラッキング)】テニス選手の移動量や軌跡をデータ化する
にて、選手(フェデラー)の動きをトラッキングする様子を動画で説明しました。
バウンディングボックスで選手をトラッキングしていますが、その重心の移動を軌跡データとして描画した動画が↓になります。
トラッキングデータである移動軌跡をグラフ上にプロットしてみます。
赤丸の位置をスタート位置として、そのあとの移動軌跡が描けていることがわかります。
グラフの軸の単位はpixelになっており、これをmに変換する必要があるのと、遠近補正を施す必要があります。
斜め上から撮影した動画になっていますが、遠近補正で真上から見た画像に変換します。
遠近補正にはホモグラフィ変換という変換法を使用しました。
opencvでは、findHomographyという関数を使用することで、台形から長方形へと遠近補正することができます。
pixelからmへの単位変換は、テニスコートのサイズは、縦23.8m 横 8.23mとなるので、縦2380 横823の長方形に変換しました。
軌跡が描けたので、次は移動距離を計算したいと思います。
見やすくするために、移動軌跡を拡大します。
移動距離は点間距離の合計値を総移動距離としています。
この場合、総移動距離は1989=19.9mとなりました。
動画を見ればわかりますが、このポイントでは19.9mも動いていませんよね。
これは、
・サーブを打っているときの移動量が大きくでてしまう(フォームモーションの影響?)
・0.03s(30fps)毎にデータ取得しているので、細かいノイズがのってしまう
の2点の影響で、実際の移動量よりも大きい移動量が算出されていると考えられます。
なので、今回は、
・サーブの時間帯(0~80フレーム)は省く
・0.3s(3fps)でデータ取得と間隔を大きくする
ことで、実際の移動距離に近づける作業をしました。
サーブの時間帯(0~80フレーム)を省いた軌跡データをみてみましょう。
このとき総移動距離は、10.6mとなります。
そして、0.3s毎にデータ取得とデータ取得間隔を少し大きくすると↓のようになります。
このとき総移動距離は、5.8mとなり、細かいノイズが減ったことでそこそこいい数字になったかと思います。
まだ改善の余地はあるかと思いますが、このポイントだけで条件を決めるわけにはいかないので、いろんなポイントパターンをみながら改善していきたいと思います。
スポーツ画像解析リンク
・【OpenCVを使ったスポーツ画像解析1】テニス選手の移動量や軌跡をデータ化する
・【OpenCVを使ったスポーツ画像解析2】フェデラー選手の移動軌跡をグラフ化してみました
・【OpenCVを使ったスポーツ画像解析3】選手やテニスボールの移動を自動で検出して移動軌跡をトラッキング
・【OpenCVを使ったスポーツ画像解析4】テニスコートのラインの自動検出
・【OpenCVを使ったスポーツ画像解析5】画像からテニス選手の位置を検出する
■環境
Windows10
Python3.5
OpenCV3.2+contrib
import cv2 import sys import numpy as np #トラッキングタイプでMILを選択 tracker = cv2.Tracker_create("MIL") #videoファイルを読み込む video = cv2.VideoCapture("aus-tennis.mp4") # ファイルがオープンできない場合の処理. if not video.isOpened(): print ("Could not open video") sys.exit() # 最初のフレームを読み込む ok, frame = video.read() if not ok: print ('Cannot read video file') sys.exit() # バウンディングボックスの最初の位置とサイズを設定 c,r,w,h = 320,210,70,90 bbox = (c,r,w,h) roi = frame[r:r+h, c:c+w] hsv_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv_roi, np.array((0., 30.,32.)), np.array((180.,255.,255.))) roi_hist = cv2.calcHist([hsv_roi], [0], mask, [180], [0, 180]) cv2.normalize(roi_hist, roi_hist, 0, 255, cv2.NORM_MINMAX) term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 80, 1) # 動画の読み込みと動画情報の取得 #movie = cv2.VideoCapture(target) fps = video.get(cv2.CAP_PROP_FPS) height = video.get(cv2.CAP_PROP_FRAME_HEIGHT) width = video.get(cv2.CAP_PROP_FRAME_WIDTH) # 形式はMP4Vを指定 fourcc = cv2.VideoWriter_fourcc(*'MJPG') result = "result/test_output.m4v" # 出力先のファイルを開く out = cv2.VideoWriter('output.avi',fourcc,20.0, (int(width), int(height))) # バウンディングボックスをフレームに設定 ok = tracker.init(frame, bbox) #モーション情報格納用の配列 motion=[] while True: # フレームを読み込む ok, frame = video.read() if not ok: break hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) dst = cv2.calcBackProject([hsv], [0], roi_hist, [0,180], 1) # フレームを更新 ok, bbox = tracker.update(frame) # バウンディングボックスを描画 if ok: #バウンディングボックスの重心位置を計算 g=(int(bbox[0])+int(0.5*bbox[2]), int(bbox[1])+int(0.5*bbox[3])) motion.append(g) #バウンディングボックスを描画 p1 = (int(bbox[0]), int(bbox[1])) p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3])) cv2.rectangle(frame, p1, p2, (0,0,255)) #軌跡データを描画 for i in range(len(motion)-1): cv2.line(frame,motion[i],motion[i+1],(255,0,0),1) # フレームを画面表示 cv2.imshow("Tracking", frame) #読み込んだフレームを書き込み out.write(frame) # ESCを押したら中止 k = cv2.waitKey(1) & 0xff if k == 27 : break video.release() out.release() cv2.destroyAllWindows()
#ホモグラフィ変換 import cv2 import numpy as np p1,p2,p3,p4=[229, 90],[147, 280],[508, 280],[429, 90]#補正前(台形)4点 c1,c2,c3,c4=[0, 0],[0, 2378],[823, 2378],[823, 0]#補正後(正方形)4点 src_pts = np.float32([p1,p2,p3,p4]).reshape(-1,1,2) dst_pts = np.float32([c1,c2,c3,c4]).reshape(-1,1,2) M,mask=cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
import matplotlib.pyplot as plt #モーションデータ整理 m=[] for i in range(166): m.append([motion[i][0],motion[i][1]]) xc=[] yc=[] xc_es=[] yc_es=[] for i in range(166): xc.append(dst[i][0][0]) yc.append(dst[i][0][1]) if(i>80):#サーブのデータは含まない xc_es.append(dst[i][0][0]) yc_es.append(dst[i][0][1]) data=[[0, 0],[0, 2378],[823, 2378],[823, 0],[0, 0]] array2=np.array(data) #間引きデータ xd=[] yd=[] xd_es=[] yd_es=[] for i in range(len(xc)-1): if(i%10==0): xd.append(xc[i]) yd.append(yc[i]) for i in range(len(xc_es)-1): if(i%10==0): xd_es.append(xc_es[i]) yd_es.append(yc_es[i]) #グラフプロット plt.scatter(xc[0],yc[0],c='red',s=30) plt.plot(xc,yc) plt.plot(array2[:,0],array2[:,1]) plt.xlim(-10,1000) plt.ylim(2500,-10) plt.show() #総移動距離計算 s=0 for i in range(len(xc)-1):#len(xc)-1 s+=math.sqrt((xc[i+1]-xc[i])**2+(yc[i+1]-yc[i])**2) s_es=0 for i in range(len(xc_es)-1):#len(xc)-1 s_es+=math.sqrt((xc_es[i+1]-xc_es[i])**2+(yc_es[i+1]-yc_es[i])**2) print(s) print(s_es)