### 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) ![image](https://hackmd.io/_uploads/r1cWOBtEbe.png) ![image](https://hackmd.io/_uploads/SJz3tHtVZg.png) 每一列 = 影片的一個 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 ![image](https://hackmd.io/_uploads/BJYw5SFVWl.png) 但這次抓取並不是完全正確的,還需要人工檢查,因為模型不夠好,會受到背景或第二顆球的影響,揮拍抓不準確(仍須修正模型) #### 程式邏輯 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 ![image](https://hackmd.io/_uploads/rJyNnIYVbl.png) #### 執行 觀察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 ![image](https://hackmd.io/_uploads/H1PihLF4bl.png) #### 程式邏輯 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](https://hackmd.io/_uploads/rJ9EgvYEbe.png) C0045_max_speed_stroke_id.png ![C0045_median_speed_stroke_id](https://hackmd.io/_uploads/B18IevFEZx.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 ```