v9.5: 實作標籤完全不重疊算法

- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數
- 修改泳道選擇算法,優先選擇無標籤重疊的泳道
- 兩階段搜尋:優先側別無可用泳道則嘗試另一側
- 增強日誌輸出,顯示標籤範圍和詳細衝突分數

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-11-06 11:35:29 +08:00
commit 2d37d23bcf
83 changed files with 22971 additions and 0 deletions

322
IMPROVEMENTS_v5.md Normal file
View File

@@ -0,0 +1,322 @@
# 時間軸標籤避碰改進v5.0 - 真正的碰撞預防系統
## 新增改進v5.0
### 核心概念:從靜態分層到動態碰撞檢測
**v4.1 的問題**
- ❌ 只是根據層級靜態計算路徑高度
- ❌ 沒有真正檢測線條之間的碰撞
- ❌ 沒有檢測線條與文字框的碰撞
- ❌ 仍然會出現嚴重的重疊
**v5.0 的解決方案**
-**真正的碰撞檢測算法**
-**動態路徑優化**
-**20個候選高度選擇最佳路徑**
-**實時追蹤已繪製的線條和文字框**
---
## 技術實現
### 1. **碰撞檢測算法**
#### 線段與線段碰撞檢測
```python
def check_collision(x_start_sec, x_end_sec, y_height, margin=0.05):
collision_score = 0
# 檢查與已繪製線段的碰撞
for seg_start, seg_end, seg_y in drawn_horizontal_segments:
# Y 座標是否接近(在 margin 範圍內)
if abs(y_height - seg_y) < margin:
# X 範圍是否重疊
if not (x_end_sec < seg_start or x_start_sec > seg_end):
overlap = min(x_end_sec, seg_end) - max(x_start_sec, seg_start)
collision_score += overlap / (x_end_sec - x_start_sec + 1)
return collision_score
```
**邏輯**
- 檢查新線段的水平部分是否與已有線段在同一高度±5%範圍內)
- 計算 X 軸重疊的比例
- 重疊越多,碰撞分數越高
#### 線段與文字框碰撞檢測
```python
# 檢查與文字框的碰撞
for box_x, box_y, box_w, box_h in text_boxes:
# Y 座標是否在文字框範圍內
if abs(y_height - box_y) < box_h / 2 + margin:
# X 範圍是否穿過文字框
box_left = box_x - box_w / 2
box_right = box_x + box_w / 2
if not (x_end_sec < box_left or x_start_sec > box_right):
overlap = min(x_end_sec, box_right) - max(x_start_sec, box_left)
collision_score += overlap / (x_end_sec - x_start_sec + 1) * 2 # 權重 x2
```
**邏輯**
- 檢查線段是否穿過文字框的垂直範圍
- 計算與文字框的 X 軸重疊
- 文字框碰撞的權重是線段碰撞的2倍更嚴重
### 2. **最佳路徑選擇**
```python
def find_best_path_height(event_x_sec, label_x_sec, label_y, layer):
is_upper = label_y > 0
# 生成20個候選高度
candidates = []
if is_upper:
# 上方:從 20% 到 90% (每次增加 3.5%)
for i in range(20):
ratio = 0.20 + (i * 0.035)
candidates.append(ratio)
else:
# 下方:從 90% 到 20% (每次減少 3.5%)
for i in range(20):
ratio = 0.90 - (i * 0.035)
candidates.append(ratio)
# 計算每個高度的碰撞分數
best_ratio = candidates[layer % len(candidates)] # 默認值
min_collision = float('inf')
x_start = min(event_x_sec, label_x_sec)
x_end = max(event_x_sec, label_x_sec)
for ratio in candidates:
test_y = label_y * ratio
score = check_collision(x_start, x_end, test_y)
if score < min_collision:
min_collision = score
best_ratio = ratio
return best_ratio
```
**邏輯**
1. 根據標籤位置(上方/下方生成20個候選高度
2. 對每個候選高度計算碰撞分數
3. 選擇碰撞分數最低的高度
4. 如果所有高度碰撞分數相同都是0使用層級對應的默認高度
### 3. **實時追蹤系統**
```python
# 初始化追蹤列表
drawn_horizontal_segments = [] # [(x_start, x_end, y), ...]
text_boxes = [] # [(x_center, y_center, width, height), ...]
# 繪製後記錄
if not is_directly_above:
drawn_horizontal_segments.append((x_start_sec, x_end_sec, mid_y))
text_boxes.append((label_x_sec, label_y, label_width_sec, label_height))
```
**效果**
- 每繪製一條線段,立即記錄其位置
- 每繪製一個文字框,立即記錄其範圍
- 後續線條會避開已記錄的所有障礙物
---
## 效果對比
### v4.1(靜態分層)
```python
# 只根據層級計算高度
if is_upper_side:
base_ratio = 0.25
layer_offset = layer_group * 0.06
mid_y_ratio = base_ratio + layer_offset
問題無法知道這個高度是否會碰撞
```
### v5.0(動態碰撞檢測)
```python
# 測試20個候選高度
for ratio in candidates:
test_y = label_y * ratio
score = check_collision(x_start, x_end, test_y)
if score < min_collision:
best_ratio = ratio
優勢保證選擇碰撞最少的路徑
```
---
## 性能分析
### 時間複雜度
- **單條線路徑選擇**O(候選數 × (已繪線段數 + 文字框數))
- **全部線條**O(事件數 × 候選數 × 事件數) = O(20n²)
- **實際情況**:因為是按順序繪製,平均複雜度約為 O(10n²)
### 空間複雜度
- **線段追蹤**O(事件數)
- **文字框追蹤**O(事件數)
- **總計**O(事件數)
### 性能表現
- 10 個事件:~2000 次碰撞檢測
- 50 個事件:~50000 次碰撞檢測
- 100 個事件:~200000 次碰撞檢測
**優化空間**
- 可以使用空間索引R-tree降低到 O(n log n)
- 可以減少候選數量從20降到10
- 可以使用啟發式策略減少檢測次數
---
## 參數配置
```python
# 碰撞檢測參數
margin = 0.05 # Y 軸碰撞容忍度5%
text_box_weight = 2.0 # 文字框碰撞權重x2
# 候選高度參數
candidates_count = 20 # 候選高度數量
upper_range = (0.20, 0.90) # 上方高度範圍 20%-90%
lower_range = (0.90, 0.20) # 下方高度範圍 90%-20%
step = 0.035 # 每次增減 3.5%
# 文字框估算參數
label_width_ratio = 0.15 # 文字框寬度 = 15% 時間軸
label_height = 0.3 # 文字框高度 = 0.3 單位
```
---
## 調整建議
### 如果仍有碰撞
1. **增加候選高度數量**
```python
for i in range(30): # 從 20 增加到 30
ratio = 0.20 + (i * 0.024) # 調整步長
```
2. **增加碰撞容忍度**
```python
margin = 0.08 # 從 0.05 增加到 0.08
```
3. **增加文字框尺寸估算**
```python
label_width_sec = time_range_seconds * 0.18 # 從 0.15 增加到 0.18
label_height = 0.4 # 從 0.3 增加到 0.4
```
### 如果性能太慢
1. **減少候選數量**
```python
for i in range(10): # 從 20 減少到 10
```
2. **使用啟發式優先級**
```python
# 優先測試層級對應的高度附近的候選
priority_candidates = [
candidates[layer % len(candidates)], # 優先級1層級對應
candidates[(layer-1) % len(candidates)], # 優先級2相鄰
candidates[(layer+1) % len(candidates)], # 優先級3相鄰
# ... 然後測試其他候選
]
```
---
## 視覺效果
### 碰撞檢測過程示意
```
測試候選高度 ratio=0.20 (20%):
████████████ 線段1 (已存在)
────────────────── 測試線段 ← 碰撞! score=0.8
測試候選高度 ratio=0.35 (35%):
────────────────── 測試線段 ← 無碰撞! score=0.0 ✓
████████████ 線段1 (已存在)
選擇 ratio=0.35,碰撞分數最低
```
### 文字框避讓示意
```
┌──────────┐
│ 文字框A │ (已存在)
└──────────┘
────────── 測試路徑1 ← 穿過文字框! score=1.5
─────────── 測試路徑2 ← 避開文字框! score=0.0 ✓
時間軸
```
---
## 版本改進總結
| 版本 | 方法 | 線條交錯 | 線條穿框 | 性能 |
|------|------|----------|----------|------|
| v3.2 | 增加間距 | ❌ 嚴重 | ❌ 嚴重 | ⚡ 快 |
| v4.0 | 層級偏移 | ⚠️ 存在 | ⚠️ 偶爾 | ⚡ 快 |
| v4.1 | 鏡像分布 | ⚠️ 仍有 | ⚠️ 仍有 | ⚡ 快 |
| **v5.0** | **碰撞檢測** | **✅ 最小** | **✅ 極少** | **⚡ 中等** |
---
## 未來改進方向
### 1. **空間索引優化**
使用 R-tree 或 KD-tree 加速碰撞檢測:
- 當前O(n) 檢測每個障礙物
- 優化後O(log n) 查詢相關障礙物
### 2. **貝茲曲線**
使用平滑曲線代替直角折線:
- 更自然的視覺效果
- 更容易避開障礙物
### 3. **A* 路徑規劃**
使用圖搜索算法找到最優路徑:
- 可以繞過複雜的障礙物布局
- 保證找到全局最優解
### 4. **分組優化**
對事件進行分組,組內使用相似的路徑高度:
- 減少視覺混亂
- 突出事件的邏輯關係
---
**版本**: v5.0
**更新日期**: 2025-11-05
**作者**: Claude AI
## 關鍵突破
從**靜態規則**到**動態智能**
- v1-v4根據規則計算路徑 → 希望不會碰撞
- **v5**:測試所有可能路徑 → **保證選擇最佳路徑**
這是從**被動避讓**到**主動檢測**的質的飛躍! 🚀