### Speed 分析整體流程總覽
整個分析流程如下:
```
原始逐 frame CSV (由velocity_rec4.py生成的)
↓
get_max_speed.py
→ 每次揮拍的 max_speed
↓
tools/label_tool.py
→ 肉眼逐frame檢查每次揮拍有沒有抓取正確
↓
add_median_speed.py
→ 加入 median / mean 統計列
↓
draw_plot.py
→ 繪製速度變化圖
```
**後續以player 48為例子**
### 輸入資料(逐 frame 的 CSV)


每一列 = 影片的一個 frame,欄位意義如下:
* frame_num:第幾個 frame
* visible:這個 frame 有沒有偵測到球
1 = 有偵測到球(x, y 才會是正常座標)
0 = 沒偵測到球(通常 x, y 會是 0)
* x, y:球在畫面上的座標
visible=0 時常見 x=y=0(表示「沒有有效座標」)
* in_zone:球是否落在你定義的 ROI / 區域內
TRUE 表示「在有效分析區」
* speed_km_hr:速度(km/hr)
通常是用連續 frame 的座標差計算出來的
* valid_speed:這個速度值能不能用
TRUE 表示速度計算是合理的
* on_table:球是否在球桌上
我們統計速度只用到 **frame_num、x、y、in_zone**,四個指標
### get_max_speed.py
#### 功能說明:
從逐 frame 的速度資料中,計算「每一次揮拍的最大速度」
#### 輸入
原始逐 frame 的 CSV
-> C0045_predict_speed.csv
#### 輸出
C0045_right_strokes_segments.csv

但這次抓取並不是完全正確的,還需要人工檢查,因為模型不夠好,會受到背景或第二顆球的影響,揮拍抓不準確(仍須修正模型)
#### 程式邏輯
1. 先從 frame-level 資料中,找出「球在 zone 內、且往右移動」的 frame
* 利用相鄰 frame 的 x 座標變化判斷方向
* 只保留 in_zone == True 且 x 有往右增加 的資料
2. 將連續、時間上接近的 frame 視為同一次揮拍(stroke)
* 連續符合條件的 frame 會被分到同一組
* 若中間中斷或 frame 間隔過大,則視為不同次揮拍
* 過短的片段會被捨棄
3. 針對每一次揮拍分別處理
* 每一組只包含該次揮拍中、符合條件的 frame
4. 從該次揮拍的 frame 中,取得速度最大值
* 使用該次揮拍內的 speed_km_hr,取出max speed
5. 記錄該次揮拍的基本資訊
* 揮拍起始 frame(start_frame)
* 揮拍結束 frame(end_frame)
* 該次揮拍的最大速度(max_speed)
6. 彙整所有揮拍結果並輸出
* 每一次揮拍對應輸出一列資料
* 最後附加一列整場的平均最大速度
#### 使用
```
parser = argparse.ArgumentParser()
parser.add_argument("--csv", required=True, help="frame-level csv")
parser.add_argument("--output", required=True, help="output folder")
parser.add_argument("--min_dx", type=float, default=0.0)
parser.add_argument("--max_frame_gap", type=int, default=1)
parser.add_argument("--min_run_len", type=int, default=2)
parser.add_argument("--use_valid_speed_only", action="store_true")
args = parser.parse_args()
```
* --csv:輸入的 frame-level CSV
* --output:輸出資料夾
* --min_dx:判斷「往右」的門檻
程式用 dx > min_dx
預設 0.0:只要 x 比上一個 frame 大,就算往右
* --max_frame_gap:兩個 frame 相隔多少以內,才算同一段 stroke
預設 1:表示要幾乎連續 frame 才會接在一起
* --min_run_len:一段 stroke 至少要幾個 frame 才算(太短丟掉)
* --use_valid_speed_only:如果你有 valid_speed 欄位,可以只拿 valid_speed==True 的速度來算 max
#### 執行
```python get_max_speed.py --csv 048/C0045_predict_speed.csv --output 048```
### tools/label_tool.py
因為模型的限制,仍會有一些干擾球導致抓不到準確的球,會沒辦法切分揮拍與球速。我們主要是依照每個揮拍進行球速紀錄,所以需要人工對影片
#### 功能說明
本來是用來標記資料的,但我們利用它可以逐frame觀察的效果來進行人工檢查
**操作**
| 功能 | 鍵 |
| -------- | --- |
| 下一 frame | `n` |
| 上一 frame | `p` |
| 跳到第一幀 | `z` |
| 跳到最後一幀 | `x` |
| 快轉 36 幀 | `f` |
| 倒轉 36 幀 | `b` |
| 離開並存檔 | `q` |
#### 輸入
mp4檔案
#### 結果
C0045_right_strokes_segments.csv

#### 執行
觀察frame的用法
```python label_tool.py your_video.mp4```
label的用法
```python label_tool.py your_video.mp4 --csv_dir dataset/match/labels```
### add_median_speed.py
還只是簡略的找出每個stroke的中位數而已,誤差非常大
#### 功能說明
依照每個stroke,紀錄stroke的median speed
#### 輸入
1. C0045_predict_speed.csv
-> 原始逐 frame 的 CSV
2. C0045_right_strokes_segments.csv
-> 經過人工檢查後的的 max speed CSV
#### 輸出
C0045_right_strokes_segments_with_median.csv

#### 程式邏輯
1. 讀兩份 CSV
* max_csv:每次揮拍一列的結果(有 start_frame / end_frame / stroke_id / max_speed)
* all_csv:每個 frame 一列的原始資料(有 frame_num / speed_km_hr)
2. 只處理「真正的揮拍列」
* 用 stroke_id 判斷是不是數字
* stroke_id 是數字 → 代表是正常揮拍(要算)
* stroke_id 不是數字(像 mean_max_speed)→ 當作統計列,不拿來算單次揮拍
3. 逐個揮拍計算 median_speed
* 對每一個揮拍:
1. 讀出這拍的 start_frame / end_frame
2. 去 all_csv 裡抓出 frame_num 在範圍內的資料
3. 只取 speed_km_hr != 0 的速度(0 代表不要算)
4. 計算中位數 median_speed
* 沒速度 → 留空(NaN)
* 只有 1 筆 → median 就是那個值
* 多筆 → 用 median
4. 算整體的 mean_median_speed(統計列)
* 把所有「正常揮拍」算出的 median_speed 取平均(忽略空值)
* 新增一列:
stroke_id = "mean_median_speed"
median_speed = 平均值
5. 輸出 CSV
* 如果沒指定輸出檔名:預設輸出成 原檔名_with_median.csv
#### 執行
```
python ver4\add_median_speed.py 048\C0045_right_strokes_segments.csv 048\C0045_predict_speed.csv
```
### draw_plot.py
#### 輸入
C0045_right_strokes_segments_with_median.csv
-> 完成所有分析的csv
#### 輸出

C0045_max_speed_stroke_id.png

C0045_median_speed_stroke_id.png
#### 程式邏輯
1. 讀取最終 CSV
* CSV 中同時包含 max_speed 與 median_speed
2. 只保留真正的揮拍資料
* 排除 stroke_id 不是數字的統計列(例如 mean)
3. 分別畫兩張圖
* 第 1 張:stroke_id vs max_speed
* 第 2 張:stroke_id vs median_speed
4. 設定座標軸並輸出圖片
* X 軸:每一拍的 stroke_id(全部顯示)
* Y 軸:對應的速度數值
* 各自存成一張 png 圖檔
#### 使用
```
video_name = 'C0045'
path = r"C:\Users\amych\OneDrive\桌面\個人\實驗室\TrackNetV2-pytorch\048\\"
csv_path = f"{path}{video_name}_right_strokes_segments_with_median.csv"
```
在程式碼中調整要使用的**影片(vedio_name)** 與 **路徑(path)**
#### 執行
```
python draw_plot.py
```