Pythonでテニスの動画解析ツールを自作してみた

#テニスの動画解析ツールでできること

動画解析ツールでは以下のことをできるようにしています。

* 動画をシーン毎に分割
* スコアを記録(15-15 30-15など)
* エース、ストロークウィナー、ストロークエラーなどのポイントの種類を分類して記録
* サーブの着地点座標をマウス操作で記録


テニスのスコアを記録するツールは多くあると思うのですが、サーブの着地点を記録するようなツールはないため、つくってみたら面白そうだなと思い、やってみた次第です。

[github.com/taikoma/TennisVideoAnalysis](https://github.com/taikoma/TennisVideoAnalysis)

#操作画面やデータ分析結果

操作画面、データ分析結果（サーブの着地点の記録、ポイント種別のグラフ化）は↓のようになります。
（錦織とデルポトロが対戦した試合を分析した結果です。詳しくは[自作したテニスのビデオ分析ツールで錦織・デルポトロ戦を分析してみました](http://datatennis.net/archives/4377/)をご覧になってください。）
##操作画面
![Image.png](https://qiita-image-store.s3.amazonaws.com/0/45007/4128f123-b236-63d3-b2e5-96b447d69fd5.png)

ボタンを左下に配置し、ボタンをクリックするとスコアやポイント内容が右側のリストに記録されていきます。
（ボタンの大きさが不均一だったりUIはまだ全く洗練されていませんが）
##ポイント種類の比較（データ分析結果）
![Image1.png](https://qiita-image-store.s3.amazonaws.com/0/45007/96d49867-f481-6100-3639-a6717e3b6ff0.png)
とったポイントの内容を積み上げグラフで比較してます。
デルポトロの方がサービスエースが多かったり、相手のストロークエラーで多くポイントをとれていることがわかります。

##サーブの着地点（データ分析結果）
![Image5.png](https://qiita-image-store.s3.amazonaws.com/0/45007/f044a039-4c3d-70a2-5813-d6699135ec11.png)

こちらはサーブの着地点をプロットしたものです。
上側がデルポトロサーブの着地点で、下側が錦織のサーブ着地点なのですが、デルポトロの方がサーブコースで、ライン際の厳しい箇所に打てていることがわかります。ポイントをとれたら青色、ポイントをとれなかったら赤色にしており、コースが鋭いほうがポイントをとれている傾向はわかるかと思います。

#お願いしたいこと&アドバイス募集中
なんとか動くコードにしていますが、操作性が悪かったり、メモリ管理がうまくできていなく動画も分割してとりこまなければいけないなど、改善点を多く残しています。
「こうした方がいいよ」というアドバイスがありましたら、是非コメントいただくか、datatennisnet@gmail.comまでご連絡ください。

特に悩んでいるのが、
* 動画の全フレームを配列に落とし込んでから動画解析するようにしていてメモリの管理が微妙
* こういう動画解析ツールつくるのはPythonだと微妙でC++とかのようがよい？
* GUIツールキットでTkinterを使用しているが、ほかにいいのある？
などコードの書き方や技術やツールの選択を間違えていないか、もっとよい方法がないか、などに悩んでおります。

ご連絡お待ちしております。

Q&Aサイトでも質問してます。
[【Python】Aviutlみたいに容量の大きい動画でも、スライダー操作でサクサク画像を表示できるようにしたい](https://teratail.com/questions/140012)

#①前処理が必要で、まずは動画を読み込んでフレーム毎に配列に格納させます
OpenCVのVideoCaptureを使って動画を読み込み、360×640のサイズにリサイズして全フレームを配列に格納しています。
start=88000、end=98000としていて、88000～99000の範囲でフレームを取り込んでいます。
これが微妙なところで、すべてのフレームを配列に格納しようとするとメモリが足りなくなるので、動画の範囲を絞って配列に格納してます。


```python
# 動画ファイル(avi mp4)を読み込んで、各フレームをArrayファイルに格納する
import cv2
import sys
import numpy as np

w, h = 360, 640

start = 88000  # 38000
end = 98000  # 49000

imageArray = []
fileName = 'test.avi'
video = cv2.VideoCapture(fileName)
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()

for i in range(0, end):
    ok, frame = video.read()
    if(start <= i):
        if(ok):
            img_resize = cv2.resize(frame, (h, w))
            imageArray.append(img_resize)

video.release()
cv2.destroyAllWindows()
```

#②取り込んだ画像データをコマ送りしたりスコア記録したり画面操作できるようにする
①で取り込んだ画像データを表示し、コマ送り、フレーム分割などしながら、スコア記録やサーブ着地点を記録できるようにしています。

PythonのGUIツールキットであるTkinterを用いて操作画面を構築しました。

GUIで構築した機能は↓です。
* ラジオボタンで最初のゲームのサーバーを設定
* スライドバーやコマ送りボタンで確認したいフレームに移動
* 「終了フレーム」ボタンをクリックすると、フレームを分割
* 右の画面のリストで行を選択するとそのポイントのフレームが画面に表示
* ポイントをとった側を選択して、ポイントの種類（サービスエースとか）をクリック
* サーブの着地点は、シングルスコートの4隅をクリックしてコートラインを形成し、サーブの着地点をマウスクリックするとXY座標が記録される


```python
import numpy as np
import cv2
import tkinter
from PIL import Image, ImageTk
from tkinter import ttk
import sqlite3
import pandas as pd
import tkinter.messagebox

def readImage():

    return imageArray[int(myval.get() - rangeStart.get())]


def mouseclicked(event):  # mouseevent 着弾点をマウスでクリック
    if((arrayContactServe[number.get()][0] > 0) and(arrayContactServe[number.get()][1] > 0)):
        msg = tkinter.messagebox.askyesno('serve', 'サーブ座標データを上書きしますか？')
        if msg == 1:  # true
            arrayContactServe[number.get()] = [0, 0]
    else:
        if(mode.get() == 0):
            gimg = readImage()
            pts, dilation = calcCourtPoints(gimg)
            img_copy = np.copy(gimg)
            cv2.polylines(img_copy, [pts], True, (0, 255, 0), 2)
            h, w = img_copy.shape[0], img_copy.shape[1]
            cv2.line(img_copy, (event.x - 2, 0),
                     (event.x - 2, h - 1), (255, 0, 0))
            cv2.line(img_copy, (0, event.y - 2),
                     (w - 1, event.y - 2), (255, 0, 0))
            image = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
            im = Image.fromarray(image)
            imgtk = ImageTk.PhotoImage(image=im)
            panel.configure(image=imgtk)
            panel.image = imgtk
        elif(mode.get() == 1):
            gimg = readImage()
            img_copy = np.copy(gimg)
            cv2.circle(img_copy, (event.x - 2, event.y - 2),
                       2, (0, 255, 0), -1)
            arrayPointXY[pointXYNum.get()][0] = event.x - 2
            arrayPointXY[pointXYNum.get()][1] = event.y - 2

            arrayCourt[pointXYNum.get()][number.get()][0] = event.x - \
                2  # arrayCourt[0]
            arrayCourt[pointXYNum.get()][number.get()][1] = event.y - 2

            pointXYNum.set(pointXYNum.get() + 1)

            if(pointXYNum.get() < 4):
                image = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
                im = Image.fromarray(image)
                imgtk = ImageTk.PhotoImage(image=im)
                panel.configure(image=imgtk)
                panel.image = imgtk
            else:
                pts = np.array([arrayPointXY[0], arrayPointXY[1],
                                arrayPointXY[2], arrayPointXY[3]], dtype=int)
                # print(pts)
                cv2.polylines(img_copy, [pts], True, (0, 255, 0), 2)
                image = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
                im = Image.fromarray(image)
                imgtk = ImageTk.PhotoImage(image=im)
                panel.configure(image=imgtk)
                panel.image = imgtk

                pointXYNum.set(0)
                mode.set(2)
        elif(mode.get() == 2):
            gimg = readImage()
            pts = np.array([arrayPointXY[0], arrayPointXY[1],
                            arrayPointXY[2], arrayPointXY[3]], dtype=int)
            img_copy = np.copy(gimg)
            cv2.polylines(img_copy, [pts], True, (0, 255, 0), 2)
            h, w = img_copy.shape[0], img_copy.shape[1]
            cv2.line(img_copy, (event.x - 2, 0),
                     (event.x - 2, h - 1), (255, 0, 0))
            cv2.line(img_copy, (0, event.y - 2),
                     (w - 1, event.y - 2), (255, 0, 0))
            image = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
            im = Image.fromarray(image)
            imgtk = ImageTk.PhotoImage(image=im)
            panel.configure(image=imgtk)
            panel.image = imgtk
            mode.set(1)
            arrayContactServe[number.get()] = [event.x - 2, event.y - 2]
            setTree()


def setCourt(event):
    mode.set(1)
    pointXYNum.set(0)


def imageshow():  # 画像描画
    gimg = readImage()
    img_copy = np.copy(gimg)
    image_change = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
    im = Image.fromarray(image_change)
    imgtk = ImageTk.PhotoImage(image=im)
    panel.configure(image=imgtk)
    panel.image = imgtk


def value_changed(*args):  # scaleの値が変化したとき
    if(myval.get() >= rangeStart.get() and myval.get() <= rangeEnd.get()):
        if(myval.get() > arrayFrameEnd[number.get()]):
            number.set(number.get() + 1)
        elif(myval.get() < arrayFrameStart[number.get()]):
            number.set(number.get() - 1)
        tree.selection_set(tree.get_children()[number.get()])
        imageshow()


def getValue(event):
    thresh = EditBox.get()
    imageshow()


def select(event):
    curItem = tree.focus()
    myval.set(int(tree.item(curItem)["values"][1]))
    normalPatternButton()


def button_end(event):
    end = arrayFrameEnd[number.get()]  # 次のフレームに行く前に終了フレームを一時記憶
    arrayFrameEnd[number.get()] = int(myval.get() - 1)  # 終了フレーム
    normalPatternButton()
    if(faultFlug.get() == 1):
        Button_fault["text"] = "ダブルフォルト"
    else:
        Button_fault["text"] = "フォルト"

    number.set(number.get() + 1)  # 次のシーン
    arrayFrameStart.insert(number.get(), int(myval.get()))  # 開始フレーム
    arrayFrameEnd.insert(number.get(), end)
    arrayPointPattern.insert(number.get(), "")  # パターン
    arrayPointWinner.insert(number.get(), "")  # ポイント勝者+
    pointWin[0].insert(number.get(), 2)
    pointWin[1].insert(number.get(), 2)
    arraySet.insert(number.get(), "")  # スコア
    arrayGame.insert(number.get(), "")  # スコア
    arrayScore.insert(number.get(), "")  # スコア
    arrayScoreResult.insert(number.get(), "")  # スコア
    arrayFirstSecond.insert(number.get(), 0)  # 1st2nd
    arrayServer.insert(number.get(), "")  # サーバー
    arrayForeBack.insert(number.get(), "")  # フォアバック
    # arrayCourt.insert(number.get(),[[0,0],[0,0],[0,0],[0,0]])
    arrayCourt[0].insert(number.get(), [0, 0])
    arrayCourt[1].insert(number.get(), [0, 0])
    arrayCourt[2].insert(number.get(), [0, 0])
    arrayCourt[3].insert(number.get(), [0, 0])
    arrayContactServe.insert(number.get(), [0, 0])
    setTree()


def button_save(event):
    msg = tkinter.messagebox.askyesno('save', 'データを保存しますか？')
    if msg == 1:  # true
        saveDatabase()


def button_load(event):
    msg = tkinter.messagebox.askyesno('save', 'データを読み込みますか？')
    if msg == 1:  # true
        loadDatabase()
        setTree()
        curItem = tree.get_children()[number.get()]
        myval.set(int(tree.item(curItem)["values"][1]))


def reset():
    faultFlug.set(0)


def change_state():
    if (winner.get() == (firstServer.get() + totalGame.get() + 1) % 2):
        Button1.configure(state="normal")
        Button4.configure(state="normal")
    elif (winner.get() != (firstServer.get() + totalGame.get() + 1) % 2):
        Button1.configure(state="disabled")
        Button4.configure(state="disabled")


def right(event):
    myval.set(myval.get() + 1)


def left(event):
    myval.set(myval.get() - 1)
def play(event):
    print("play button in development")
    #video = cv2.VideoCapture("nishikori-ber.mp4")
    #ok, frame = video.read()
    # 動画の読み込みと動画情報の取得
    #fps    = video.get(cv2.CAP_PROP_FPS)
    #height = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
    #width  = video.get(cv2.CAP_PROP_FRAME_WIDTH)

    #num=0
    #while True:
        #myval.set(myval.get()+1)
        #gimg=imageArray[int(myval.get()-rangeStart.get())]
        
        #img_copy=np.copy(gimg)
        #image_change = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
        
        
        #im = Image.fromarray(image_change)
        #imgtk = ImageTk.PhotoImage(image=im)
        
        #panel.image=imgtk
        #panel.configure(image=imgtk)

def buttonFault_clicked(event):
    if(faultFlug.get() == 0):
        faultFlug.set(1)
        arrayFirstSecond[number.get()] = 1
        arrayPointPattern[number.get()] = patternString[6]
        arrayPointWinner.insert(number.get(), "")  # ポイント勝者
        pointWin[0].insert(number.get(), 2)
        pointWin[1].insert(number.get(), 2)

    elif(faultFlug.get() == 1):
        faultFlug.set(0)
        arrayFirstSecond[number.get()] = 2
        arrayPointPattern[number.get()] = patternString[7]
        disabledPatternButton()
        pointWin[(firstServer.get() + totalGame.get()) % 2][number.get()] = 0
        pointWin[(firstServer.get() + totalGame.get() + 1) %
                 2][number.get()] = 1
        arrayPointWinner[number.get()] = playerName[(
            firstServer.get() + totalGame.get() + 1) % 2]
        calcScore()  # arrayScoreにスコアを格納
    arrayServer[number.get()] = playerName[(
        firstServer.get() + totalGame.get()) % 2]
    setTree()
    disabledPatternButton()


def disabledPatternButton():
    Button1.configure(state="disabled")
    Button2.configure(state="disabled")
    Button3.configure(state="disabled")
    Button4.configure(state="disabled")
    Button5.configure(state="disabled")
    Button6.configure(state="disabled")
    Button_fault.configure(state="disabled")


def normalPatternButton():
    Button1.configure(state="normal")
    Button2.configure(state="normal")
    Button3.configure(state="normal")
    Button4.configure(state="normal")
    Button5.configure(state="normal")
    Button6.configure(state="normal")
    Button_fault.configure(state="normal")


def button1_clicked(event):  # Ace
    setPattern(0)


def button2_clicked(event):  # STW
    setPattern(1)


def button3_clicked(event):  # VlW
    setPattern(2)


def button4_clicked(event):  # RtE
    setPattern(3)


def button5_clicked(event):  # StE
    setPattern(4)


def button6_clicked(event):  # VlE
    setPattern(5)


def button_forward10(event):
    myval.set(myval.get() + 10)


def button_backward10(event):
    myval.set(myval.get() - 10)


def button_forward1(event):
    myval.set(myval.get() + 1)


def button_backward1(event):
    myval.set(myval.get() - 1)


def button_forward100(event):
    myval.set(myval.get() + 100)


def button_backward100(event):
    myval.set(myval.get() - 100)


def setPattern(pattern):
    if(arrayPointPattern[number.get()] == ""):
        setPattern2(pattern)
    else:
        msg = tkinter.messagebox.askyesno('data', 'データを上書きしますか？')
        if msg == 1:
            setPattern2(pattern)


def setPattern2(pattern):
    disabledPatternButton()
    pointWin[winner.get()][number.get()] = 1
    pointWin[(winner.get() + 1) % 2][number.get()] = 0
    calcScore()  # arrayScoreにスコアを格納
    arrayPointWinner[number.get()] = playerName[winner.get()]  # 勝者の名前を格納
    arrayPointPattern[number.get()] = patternString[pattern]  # パターンを格納
    if(faultFlug.get() == 1):  # 前のポイントでフォルトをしていた場合
        arrayFirstSecond[number.get()] = 2
    elif(faultFlug.get() == 0):
        arrayFirstSecond[number.get()] = 1
    setTree()
    faultFlug.set(0)  # ポイント後は必ずフォルトを0にリセット


def setTree():
    for i, t in enumerate(tree.get_children()):
        tree.delete(t)
    for i in range(len(arrayFrameStart)):
        tree.insert("", i, values=(i, arrayFrameStart[i], arrayFrameEnd[i], arraySet[i], arrayGame[i], arrayScore[i],
                                   arrayScoreResult[i], firstSecondString[arrayFirstSecond[i]
                                                                          ], arrayServer[i],
                                   arrayPointWinner[i], arrayPointPattern[i], "", arrayContactServe[i][0], arrayContactServe[i][1]))
    tree.selection_set(tree.get_children()[number.get()])


def calcScore():
    p = []
    p.append(0)
    p.append(0)
    g = []
    g.append(0)
    g.append(0)
    s = []
    s.append(0)
    s.append(0)
    scoreA = ""
    scoreB = ""
    nextScore = "0-0"
    nextGame = "0-0"
    nextSet = "0-0"
    totalGame.set(0)
    for i in range(len(pointWin[0])):  # ポイント間も含め全ポイントを計算する　
        if(pointWin[0][i] == 2):  # ポイント間で、winデータなし
            arrayScore[i] = ""
            arrayScoreResult[i] = ""
            arrayGame[i] = ""
            arraySet[i] = ""
        else:
            arrayScore[i] = nextScore
            arrayGame[i] = nextGame
            arraySet[i] = nextSet
            if(pointWin[0][i] == 1):
                p[0] += 1
            if(pointWin[1][i] == 1):
                p[1] += 1
            scoreA, scoreB, p[0], p[1], g[0], g[1], s[0], s[1] = convertScore(
                p[0], p[1], g[0], g[1], s[0], s[1])
            nextScore = scoreA + "-" + scoreB
            nextGame = str(g[0]) + "-" + str(g[1])
            nextSet = str(s[0]) + "-" + str(s[1])
            if(arrayPointPattern[i] == patternString[6]):
                arrayScoreResult[i] = ""
            else:
                arrayScoreResult[i] = nextScore
    if((p[0] + p[1]) != 0):
        arrayServer[number.get()] = playerName[(
            firstServer.get() + g[0] + g[1]) % 2]
    else:
        arrayServer[number.get()] = playerName[(
            firstServer.get() + g[0] + g[1] + 1) % 2]


def convertScore(gamePointA, gamePointB, gameA, gameB, setA, setB):
    if((gameA == 6) and (gameB == 6)):
        if(gamePointA > 5 and gamePointB > 5):
            if((gamePointA - gamePointB) > 1):
                scoreA = "0"
                scoreB = "0"
                gamePointA = 0
                gamePointB = 0
                gameA += 1
                totalGame.set(totalGame.get() + 1)
            elif((gamePointB - gamePointA) > 1):
                scoreA = "0"
                scoreB = "0"
                gamePointA = 0
                gamePointB = 0
                gameB += 1
                totalGame.set(totalGame.get() + 1)
            else:
                scoreA = str(gamePointA)
                scoreB = str(gamePointB)

        else:
            scoreA = str(gamePointA)
            scoreB = str(gamePointB)

    else:
        if(gamePointA > 2 and gamePointB > 2):  # 40-40以降
            if((gamePointA - gamePointB) > 1):
                scoreA = "0"
                scoreB = "0"
                gamePointA = 0
                gamePointB = 0
                gameA += 1
                totalGame.set(totalGame.get() + 1)
            elif((gamePointB - gamePointA) > 1):
                scoreA = "0"
                scoreB = "0"
                gamePointA = 0
                gamePointB = 0
                gameB += 1
                totalGame.set(totalGame.get() + 1)
            elif((gamePointA - gamePointB) == 1):
                scoreA = "Ad"
                scoreB = "40"
            elif((gamePointB - gamePointA) == 1):
                scoreA = "40"
                scoreB = "Ad"
            else:
                scoreA = "40"
                scoreB = "40"
        else:
            if(gamePointA == 0):
                scoreA = "0"
            if(gamePointB == 0):
                scoreB = "0"
            if(gamePointA == 1):
                scoreA = "15"
            if(gamePointB == 1):
                scoreB = "15"
            if(gamePointA == 2):
                scoreA = "30"
            if(gamePointB == 2):
                scoreB = "30"
            if(gamePointA == 3):
                scoreA = "40"
            if(gamePointB == 3):
                scoreB = "40"
            if(gamePointA > 3 and gamePointB < 3):
                scoreA = "0"
                scoreB = "0"
                gamePointA = 0
                gamePointB = 0
                gameA += 1
                totalGame.set(totalGame.get() + 1)
            elif(gamePointB > 3 and gamePointA < 3):
                scoreA = "0"
                scoreB = "0"
                gamePointA = 0
                gamePointB = 0
                gameB += 1
                totalGame.set(totalGame.get() + 1)

    gameA, gameB, setA, setB = convertSet(gameA, gameB, setA, setB)

    return scoreA, scoreB, gamePointA, gamePointB, gameA, gameB, setA, setB


def convertSet(gameA, gameB, setA, setB):
    if(gameA > 5 and gameB < 5):
        setA += 1
        gameA = 0
        gameB = 0
    elif(gameB > 5 and gameA < 5):
        setB += 1
        gameA = 0
        gameB = 0
    elif(gameA > 5 and gameB > 5):
        if(gameA == 7):
            setA += 1
            gameA = 0
            gameB = 0
        elif(gameB == 7):
            setB += 1
            gameA = 0
            gameB = 0

    return gameA, gameB, setA, setB


def saveDatabase():
    print("saveDatabase")
    dbname = 'tennis01.db'
    conn = sqlite3.connect(dbname)
    c = conn.cursor()

    df = pd.DataFrame({'StartFrame': arrayFrameStart, 'EndFrame': arrayFrameEnd, 'Set': arraySet, 'Game': arrayGame,
                       'Score': arrayScore, 'ScoreResult': arrayScoreResult, 'FirstSecond': arrayFirstSecond, 'Server': arrayServer,
                       'PointWinner': arrayPointWinner, 'PointWinA': pointWin[0], 'PointWinB': pointWin[1],
                       'PointPattern': arrayPointPattern, 'ForeBack': arrayForeBack,
                       'ContactServeX': list(np.array(arrayContactServe)[:, 0]), 'ContactServeY': list(np.array(arrayContactServe)[:, 1]),
                       'Court1X': list(np.array(arrayCourt)[0][:, 0]), 'Court1Y': list(np.array(arrayCourt)[0][:, 1]),
                       'Court2X': list(np.array(arrayCourt)[1][:, 0]), 'Court2Y': list(np.array(arrayCourt)[1][:, 1]),
                       'Court3X': list(np.array(arrayCourt)[2][:, 0]), 'Court3Y': list(np.array(arrayCourt)[2][:, 1]),
                       'Court4X': list(np.array(arrayCourt)[3][:, 0]), 'Court4Y': list(np.array(arrayCourt)[3][:, 1])
                       })
    df_basic = pd.DataFrame({'playerA': playerA.get(), 'playerB': playerB.get(), 'number': number.get(), 'totalGame': totalGame.get(),
                             'faultFlug': faultFlug.get()}, index=[0])
    df.to_sql("score", conn, if_exists="replace")
    df_basic.to_sql("match", conn, if_exists="replace")
    conn.close()


def loadDatabase():
    arrayFrameStart.clear()
    arrayFrameEnd.clear()
    arraySet.clear()
    arrayGame.clear()
    arrayScore.clear()
    arrayScoreResult.clear()
    arrayFirstSecond.clear()
    arrayServer.clear()
    arrayPointWinner.clear()
    arrayPointPattern.clear()
    arrayForeBack.clear()
    pointWin[0].clear()
    pointWin[1].clear()
    arrayContactServe.clear()
    arrayCourt.clear()

    dbname = 'tennis01.db'
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    df = pd.read_sql("select * from score", conn)
    arrayFrameStart.extend(df['StartFrame'].values.tolist())
    arrayFrameEnd.extend(df['EndFrame'].values.tolist())
    arraySet.extend(df['Set'].values.tolist())
    arrayGame.extend(df['Game'].values.tolist())
    arrayScore.extend(df['Score'].values.tolist())
    arrayScoreResult.extend(df['ScoreResult'].values.tolist())
    arrayFirstSecond.extend(df['FirstSecond'].values.tolist())
    arrayServer.extend(df['Server'].values.tolist())
    arrayPointWinner.extend(df['PointWinner'].values.tolist())
    arrayPointPattern.extend(df['PointPattern'].values.tolist())
    arrayForeBack.extend(df['ForeBack'].values.tolist())

    pointWin[0].extend(df['PointWinA'].values.tolist())
    pointWin[1].extend(df['PointWinB'].values.tolist())

    for i in range(len(df['ContactServeX'].values.tolist())):
        arrayContactServe.append([df['ContactServeX'].values.tolist()[
                                 i], df['ContactServeY'].values.tolist()[i]])
    arrayCourt.append([])
    arrayCourt.append([])
    arrayCourt.append([])
    arrayCourt.append([])
    for i in range(len(df['Court1X'].values.tolist())):
        arrayCourt[0].insert(i, [df['Court1X'].values.tolist()[
                             i], df['Court1Y'].values.tolist()[i]])
        arrayCourt[1].insert(i, [df['Court2X'].values.tolist()[
                             i], df['Court2Y'].values.tolist()[i]])
        arrayCourt[2].insert(i, [df['Court3X'].values.tolist()[
                             i], df['Court3Y'].values.tolist()[i]])
        arrayCourt[3].insert(i, [df['Court4X'].values.tolist()[
                             i], df['Court4Y'].values.tolist()[i]])

    df_basic = pd.read_sql("select * from match", conn)
    playerA.set(df_basic['playerA'].values)
    playerB.set(df_basic['playerB'].values)

    number.set(len(df) - 1)
    totalGame.set(df_basic['totalGame'].values[0])
    faultFlug.set(df_basic['faultFlug'].values[0])
    conn.close()


root = tkinter.Tk()
root.title("TennisTracking")
root.bind("<Right>", right)
root.bind("<Left>", left)

#### 変数格納 ####
rangeStart = tkinter.IntVar()
rangeEnd = tkinter.IntVar()
rangeStart.set(88000)
rangeEnd.set(98000)

playerA = tkinter.StringVar()
playerA.set("錦織")
playerB = tkinter.StringVar()
playerB.set("デルポトロ")
playerName = [playerA.get(), playerB.get()]
patternString = ["サービスエース", "ストロークウィナー", "ボレーウィナー",
                 "リターンエラー", "ストロークエラー", "ボレーエラー", "フォルト", "ダブルフォルト"]
firstSecondString = ["", "1st", "2nd"]

pointXYNum = tkinter.IntVar()
pointXYNum.set(0)
arrayPointXY = []  # コートのXY座標
arrayPointXY.append([0, 0])
arrayPointXY.append([0, 0])
arrayPointXY.append([0, 0])
arrayPointXY.append([0, 0])
arrayCourt = [[], [], [], []]
arrayCourt[0].append([0, 0])
arrayCourt[1].append([0, 0])
arrayCourt[2].append([0, 0])
arrayCourt[3].append([0, 0])
arrayContactServe = []
arrayContactServe.append([0, 0])
print(arrayCourt)

maxFrame = 185430

# Array系
arrayFrameStart = []  # 開始フレーム
arrayFrameStart.append(0)
arrayFrameEnd = []  # 終了フレーム
# arrayFrameEnd.append(len(imageArray)-1)
arrayFrameEnd.append(maxFrame - 1)
arraySet = []  # セット
arraySet.append("")
arrayGame = []  # ゲーム
arrayGame.append("")
arrayScore = []  # スコア
arrayScore.append("")
arrayScoreResult = []  # スコア結果
arrayScoreResult.append("")
arrayServer = []  # サーバー
arrayServer.append("")
pointWin = []
pointA = []
pointB = []
pointA.append(0)
pointB.append(0)
pointWin.append(pointA)  # pointA 勝ったら1を格納
pointWin.append(pointB)  # pointB 勝ったら1を格納
arrayPointWinner = []  # ウィナーの名前
arrayPointWinner.append("")
arrayPointPattern = []  # ポイントパターン
arrayPointPattern.append("")
arrayFirstSecond = []  # 最初のゲームのサーバー
arrayFirstSecond.append(0)
arrayForeBack = []  # サーバー
arrayForeBack.append("")

# 変数
number = tkinter.IntVar()
number.set(0)
totalGame = tkinter.IntVar()
totalGame.set(0)
faultFlug = tkinter.IntVar()
faultFlug.set(0)
mode = tkinter.IntVar()
mode.set(1)


pw = tkinter.PanedWindow(root, orient='horizontal')
pw.pack(expand=True)
pw1 = tkinter.PanedWindow(pw, orient='vertical')
pw.add(pw1)

##### opencv 画像描画 ####
gimg = imageArray[0]
image_change = cv2.cvtColor(gimg, cv2.COLOR_BGR2RGB)
im = Image.fromarray(image_change)
imgtk = ImageTk.PhotoImage(image=im)
panel = tkinter.Label(root, image=imgtk)
panel.bind("<Button-1>", mouseclicked)
pw1.add(panel, padx=10, pady=10)

##### Scale ####
# maxFrame=len(imageArray)

wname = "img"
myval = tkinter.DoubleVar()
myval.trace("w", value_changed)
sc = tkinter.Scale(variable=myval, orient='horizontal',
                   length=gimg.shape[1], from_=0, to=(maxFrame - 1))
pw1.add(sc, padx=10)

##### Button ####
pw1_4 = tkinter.PanedWindow(pw1, orient='horizontal')
pw1.add(pw1_4)

Button_backward100 = tkinter.Button(text=u'100←', width=10)
Button_backward100.bind("<Button-1>", button_backward100)
pw1_4.add(Button_backward100)

Button_backward10 = tkinter.Button(text=u'10←', width=10)
Button_backward10.bind("<Button-1>", button_backward10)
pw1_4.add(Button_backward10)

Button_backward1 = tkinter.Button(text=u'1←', width=10)
Button_backward1.bind("<Button-1>", button_backward1)
pw1_4.add(Button_backward1)


Button_play = tkinter.Button(text=u'play', width=10)
Button_play.bind("<Button-1>", play)
pw1_4.add(Button_play)


Button_forward1 = tkinter.Button(text=u'→1', width=10)
Button_forward1.bind("<Button-1>", button_forward1)
pw1_4.add(Button_forward1)

Button_forward10 = tkinter.Button(text=u'→10', width=10)
Button_forward10.bind("<Button-1>", button_forward10)
pw1_4.add(Button_forward10)

Button_forward100 = tkinter.Button(text=u'→100', width=10)
Button_forward100.bind("<Button-1>", button_forward100)
pw1_4.add(Button_forward100)

##### EditText ####
pw1_2 = tkinter.PanedWindow(pw1, orient='horizontal')
pw1.add(pw1_2)

thresh = tkinter.StringVar()
thresh.set("140")
EditBox = tkinter.Entry(root, textvariable=thresh)
pw1_2.add(EditBox)

Button_thresh = tkinter.Button(text=u'2値化閾値変更', width=10)
Button_thresh.bind("<Button-1>", getValue)
pw1_2.add(Button_thresh)

Button_court = tkinter.Button(text=u'コートライン作成', width=10)
Button_court.bind("<Button-1>", setCourt)
pw1_2.add(Button_court)

Button_reset = tkinter.Button(text=u'リセット', width=10)
Button_reset.bind("<Button-1>", reset)
pw1_2.add(Button_reset)

pw1_3 = tkinter.PanedWindow(pw1, orient='horizontal')
pw1.add(pw1_3)

pw1_3_1 = tkinter.PanedWindow(pw1, orient='vertical')
pw1_3.add(pw1_3_1)
winner = tkinter.IntVar()
winner.set(0)
radio1 = tkinter.Radiobutton(
    text=playerA.get(), variable=winner, value=0, command=change_state)
pw1_3_1.add(radio1)
radio2 = tkinter.Radiobutton(
    text=playerB.get(), variable=winner, value=1, command=change_state)
pw1_3_1.add(radio2)

label1 = tkinter.Label(text=u'のポイント')
pw1_3.add(label1)
Button_fault = tkinter.Button(text=u'フォルト', width=10)
Button_fault.bind("<Button-1>", buttonFault_clicked)
pw1_3.add(Button_fault)
Button_end = tkinter.Button(text=u'終了フレーム', width=10)
Button_end.bind("<Button-1>", button_end)
pw1_3.add(Button_end)

Button_save = tkinter.Button(text=u'データ保存', width=10)
Button_save.bind("<Button-1>", button_save)
pw1_3.add(Button_save)

Button_load = tkinter.Button(text=u'データ読込', width=10)
Button_load.bind("<Button-1>", button_load)
pw1_3.add(Button_load)

# label1=tkinter.Label(text=u'1stServer')
# pw1_3.add(label1)
pw1_3_2 = tkinter.PanedWindow(pw1, orient='vertical')
pw1_3.add(pw1_3_2)
firstServer = tkinter.IntVar()
firstServer.set(0)
radio3 = tkinter.Radiobutton(
    text=playerA.get(), variable=firstServer, value=0, command=change_state)
pw1_3_2.add(radio3)
radio4 = tkinter.Radiobutton(
    text=playerB.get(), variable=firstServer, value=1, command=change_state)
pw1_3_2.add(radio4)

pw1_4 = tkinter.PanedWindow(pw1, orient='horizontal')
pw1.add(pw1_4)
pw1_4_1 = tkinter.PanedWindow(pw1_4, orient='vertical')
pw1_4.add(pw1_4_1)
pw1_4_2 = tkinter.PanedWindow(pw1_4, orient='vertical')
pw1_4.add(pw1_4_2)
pw1_4_3 = tkinter.PanedWindow(pw1_4, orient='vertical')
pw1_4.add(pw1_4_3)

##### Button1 ####

Button1 = tkinter.Button(text=u'サービスエース(1)', width=20)
Button1.bind("<Button-1>", button1_clicked)
pw1_4_1.add(Button1)
Button4 = tkinter.Button(text=u'リターンエラー(4)', width=20)
Button4.bind("<Button-1>", button4_clicked)
pw1_4_1.add(Button4)

Button2 = tkinter.Button(text=u'ストロークウィナー(2)', width=20)
Button2.bind("<Button-1>", button2_clicked)
pw1_4_2.add(Button2)
Button5 = tkinter.Button(text=u'ストロークエラー(5)', width=20)
Button5.bind("<Button-1>", button5_clicked)
pw1_4_2.add(Button5)

Button3 = tkinter.Button(text=u'ボレーウィナー(3)', width=20)
Button3.bind("<Button-1>", button3_clicked)
pw1_4_3.add(Button3)
Button6 = tkinter.Button(text=u'ボレーエラー(6)', width=20)
Button6.bind("<Button-1>", button6_clicked)
pw1_4_3.add(Button6)

tree = ttk.Treeview(root, selectmode="browse")
tree["columns"] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
tree["show"] = "headings"
tree.column(1, width=40)
tree.column(2, width=75)
tree.column(3, width=75)
tree.column(4, width=85)
tree.column(5, width=75)
tree.column(6, width=75)
tree.column(7, width=75)
tree.column(8, width=75)
tree.column(9, width=75)
tree.column(10, width=75)
tree.column(11, width=75)
tree.column(12, width=75)
tree.column(13, width=40)
tree.column(14, width=40)
tree.heading(1, text="No")
tree.heading(2, text="開始フレーム")
tree.heading(3, text="終了フレーム")
tree.heading(4, text="セット")
tree.heading(5, text="ゲーム")
tree.heading(6, text="スコア")
tree.heading(7, text="スコア結果")
tree.heading(8, text="1st2nd")
tree.heading(9, text="サーバー")
tree.heading(10, text="ポイント勝者")
tree.heading(11, text="パターン")
tree.heading(12, text="フォアバック")
tree.heading(13, text="X")
tree.heading(14, text="Y")
setTree()

pw2 = tkinter.PanedWindow(pw, orient='vertical')
pw.add(pw2)
pw2.add(tree)
tree.bind('<ButtonRelease-1>', select)  # Double-1
tree.selection_set(tree.get_children()[0])
root.mainloop()

```