#スプラトゥーン動画解析してみた
スプラトゥーンの観戦動画から、プレイヤーの位置情報を解析してみました。
元ネタは、北海道甲子園(?)のイカ自慢コンテストで有名チームの解析を行ってるかたがいて、興味があったので自分もやってみました。
##解析結果
INPUTは観戦視点の動画。
OUTPUTは各プレイヤーの位置情報の分布です。
例えばある試合の4人の位置情報は以下でした。
シューターのほうがステージをまんべんなく動いてることが分かりますねー。
##解析方法
こんな感じです
①観戦動画を撮る(地味にハードル高い。説明は割愛)
②動画を白黒変換
③白黒変換された動画から、各プレイヤー名の画像を抜き出す
④openCVのパターンマッチングで位置情報取得
⑤④で取得した位置情報の周辺も適当に重みづけして位置情報を画像で出力
⑥ステージ画像を⑤の画像を合成
##解析方法の詳細
①観戦動画を撮る
キャプチャーボード買いました。
配信者探してプラべ混ぜてもらいました。
②動画を白黒変換
グレースケールに変換して、220を閾値にして白黒変換しました。
ソース雰囲気だけ書くと以下。もっと便利な方法ある気もするけど。
# 閾値の設定
threshold = 220
fmt = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # ファイル形式(ここではmp4)
#グレースケールの場合は、ライターの最後の引数をFalseにする(カラーの場合はTrueか省略)
out = cv2.VideoWriter('./video/g_area_1_gray_2.mp4', fmt, frame_rate, (width,height),False) # ライター作成
while(cap.isOpened()):
ret, frame = cap.read()
if ret==True:
#グレースケール化
gray_cv = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
# 二値化(閾値thresholdを超えた画素を255にする。)
ret, img_thresh = cv2.threshold(gray_cv, threshold, 255, cv2.THRESH_BINARY)
# write the flipped frame
out.write(img_thresh) #output.aviにframe毎書込み
cv2.imshow('gray_cv',img_thresh) #グレースケールframeを表示
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
③白黒変換された動画から、各プレイヤー名の画像を抜き出す
ここは手作業。。。
一応画像切り取り用のソースは以下。
import cv2
playerImg = cv2.imread('./XXX.jpg')
playerImg = playerImg[0:30, 80:150]#ここで切り取る箇所指定
cv2.imwrite('./YYY.jpg', playerImg)
cv2.imshow('playerImg',playerImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
④openCVのパターンマッチングで位置情報取得
openCVでパターンマッチングする。
1フレーム毎に位置情報がタプルで返却されるので、リストに保持。
マッチングにはいくつかアルゴリズムがあるみたいで、どれがいいかは良く分からん。比較するのもめんどくさいから適当に選んだ。
ソースは雰囲気以下。
###読み込む動画(②で白黒変換した動画)###
cap = cv2.VideoCapture('./grayMovie.mp4')
###テンプレート画像(#③で切り取ったプレイヤー名の画像)###
template = cv2.imread('./XXX.jpg',0)
###パターンマッチングのアルゴリズム###
method = cv2.TM_CCOEFF
#位置情報のリスト
player1_locations = []
###パターンマッチング###
while(cap.isOpened()):
ret, frame = cap.read()
if ret==True:
# Apply template Matching
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(frame,template,method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
center = ((top_left[0]+bottom_right[0])//2,(top_left[1]+bottom_right[1])//2)
player1_locations.append(center)
⑤④で取得した位置情報の周辺も適当に重みづけして位置情報を画像で出力
取得した位置情報だけを画像にマッピングするとなんとも微妙な見た目になるので、”映え”るためにしました。
作成された2次元配列で数値が大きい場所ほど、長く居座ってることになります。
ソース割愛しますが、数値が大きいほど色を濃くして、プレイヤーの位置情報を画像にします。
#player1のmap上の統計情報を2次元配列で定義
player1_location_statistics_around = np.array([[0] * (width) for i in range(height)], dtype=float)
#player1のmap上の統計情報を計算
for x,y in player1_locations:
x=int(x)
y=int(y)
player1_location_statistics_around[y:y+50,x-50:x+50] += 1
player1_location_statistics_around[y:y+30,x-30:x+30] += 1
player1_location_statistics_around[y:y+20,x-20:x+20] += 2
player1_location_statistics_around[y:y+10,x-10:x+10] += 3
player1_location_statistics_around[y:y+5,x-5:x+5] += 5
⑥ステージ画像を⑤の画像を合成
ステージの画像と⑤のプレイヤーの位置情報の画像を合成して完成🎊
import cv2
stageImg = cv2.imread('./stage_pic/hujiSport.jpg')
player1LocationImg = cv2.imread('./XXX.png')
dst = cv2.addWeighted(stageImg, 1.0, player1LocationImg, 1.0, 0)
cv2.imwrite('./result.jpg', dst)
cv2.imshow('location',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
#終わりに
スプラトゥーン好きなので、楽しく実装できました。
誤認識してしまうところもあって完璧ではないですが、結構お手軽に可視化できてopenCVのパターンマッチングすげーってなりましね。
もっといい実装(精度高いとか、画像映えするアイデアとか)あったら教えてほしいです。
読んで頂いてありがとうございました。