# 系统架构和流程图

## 🏗️ 整体架构

```
SmartMinimapStitcher
├── 初始化组件
│   ├── 画布管理 (canvas, canvas_x, canvas_y)
│   ├── 历史记录 (mean_diff_history, movement_history)
│   ├── 状态机 (state, state_confidence, no_move_frames)
│   ├── Kalman滤波 (smooth_dx, smooth_dy, smooth_response)
│   └── 特征检测 (orb, bf_matcher)
│
├── 主循环 run()
│   ├── 截取新帧 capture_minimap()
│   ├── 检测移动 detect_movement() ← 核心检测逻辑
│   │   ├─ 第一层：计算mean_diff + 历史记录
│   │   ├─ 第二层：计算自适应阈值
│   │   ├─ 第三层：相位相关检测
│   │   ├─ 第四层：Kalman滤波平滑
│   │   ├─ 第五层：多条件AND验证
│   │   └─ 第六层：状态机转移
│   │
│   └─ if has_moved:
│       └── 拼接处理 stitch() ← 核心拼接逻辑
│           ├─ if confidence > 0.85: 增量拼接
│           │  ├── 提取新增区域
│           │  └── 渐变融合 _blend_region()
│           └─ else: 特征匹配对齐
│              ├── ORB特征检测
│              ├── 特征匹配
│              ├── RANSAC估计
│              └── 校正拼接 _simple_stitch()
│
└── 辅助方法
    ├── _get_response_threshold() - 状态相关阈值
    ├── _blend_region() - 渐变融合
    └── _simple_stitch() - 通用拼接
```

---

## 📊 detect_movement() 详细流程

```
输入: frame_prev, frame_curr
│
├─ [第一层] 基础计算
│  ├─ gray1 = cvtColor(frame_prev, BGR2GRAY)
│  ├─ gray2 = cvtColor(frame_curr, BGR2GRAY)
│  ├─ diff = absdiff(gray1, gray2)
│  ├─ mean_diff = mean(diff)
│  └─ mean_diff_history.append(mean_diff)  ← 记录历史
│
├─ [第二层] 自适应阈值计算
│  ├─ if len(history) >= 10:
│  │  ├─ baseline = mean(last_30_frames)
│  │  ├─ std_dev = std(last_30_frames)
│  │  ├─ if state == "IDLE":
│  │  │  └─ threshold = baseline + 1.5 * std_dev  [严格]
│  │  ├─ elif state == "MOVING":
│  │  │  └─ threshold = baseline + 0.8 * std_dev  [宽松]
│  │  └─ elif state == "STOPPED":
│  │     └─ threshold = baseline + 1.2 * std_dev  [中等]
│  │
│  └─ else:
│     └─ threshold = fixed_value (3.0)
│
├─ [快速检查] 极端情况
│  ├─ if mean_diff < threshold * 0.5:
│  │  ├─ no_move_frames++
│  │  └─ return (False, 0, 0, 0, state)
│  │
│  └─ else: 继续处理
│
├─ [第三层] 相位相关检测
│  ├─ shift, response = phaseCorrelate(gray1, gray2)
│  ├─ dx, dy = shift[0], shift[1]  [原始位移]
│  └─ exception: return (False, 0, 0, 0, state)
│
├─ [第四层] Kalman滤波平滑
│  ├─ smooth_dx = 0.7 * prev_dx + 0.3 * dx
│  ├─ smooth_dy = 0.7 * prev_dy + 0.3 * dy
│  ├─ smooth_response = 0.6 * prev_response + 0.4 * response
│  └─ [结果] 消除0.5像素抖动，保留趋势
│
├─ [第五层] 多条件AND验证
│  ├─ condition1: mean_diff > adaptive_threshold
│  ├─ condition2: abs(smooth_dx) >= 0.5 OR abs(smooth_dy) >= 0.5
│  ├─ condition3: smooth_response > _get_response_threshold()
│  ├─ condition4: abs(dx) <= 100 AND abs(dy) <= 100  [过滤异常]
│  │
│  └─ has_moved = ALL(conditions)  [全部满足才认定]
│
├─ [第六层] 状态机转移
│  ├─ if has_moved:
│  │  ├─ if state == "IDLE":
│  │  │  ├─ state = "MOVING"
│  │  │  ├─ state_confidence = 0.5
│  │  │  └─ no_move_frames = 0
│  │  │
│  │  ├─ elif state == "MOVING":
│  │  │  ├─ state_confidence = min(1.0, state_confidence + 0.1)
│  │  │  └─ no_move_frames = 0
│  │  │
│  │  └─ elif state == "STOPPED":
│  │     ├─ state = "MOVING"
│  │     ├─ state_confidence = 0.5
│  │     └─ no_move_frames = 0
│  │
│  └─ else:  [无移动被检测]
│     ├─ no_move_frames++
│     ├─ if state == "MOVING" AND no_move_frames >= 3:
│     │  └─ state = "STOPPED"
│     │
│     ├─ if state == "STOPPED" AND no_move_frames >= 5:
│     │  └─ state = "IDLE"
│     │
│     └─ smooth_dx = 0, smooth_dy = 0
│
└─ 返回结果
   ├─ return (has_moved, smooth_dx, smooth_dy, smooth_response, state)
   └─ save self.smooth_dx/dy/response for next iteration
```

---

## 📊 stitch() 详细流程

```
输入: minimap(新帧), last_minimap(旧帧), dx, dy, confidence
│
├─ [路径1] 高置信度 (confidence > 0.85) - 增量拼接
│  │
│  ├─ 【计算水平新增区域】
│  │  ├─ if dx > 0 (向右移动):
│  │  │  ├─ 从小地图左边复制新内容到画布左边
│  │  │  ├─ new_left = 0
│  │  │  ├─ new_right = min(|dx|, w)
│  │  │  └─ canvas_left = canvas_x
│  │  │
│  │  ├─ elif dx < 0 (向左移动):
│  │  │  ├─ 从小地图右边复制新内容到画布右边
│  │  │  ├─ new_left = max(0, w + dx)
│  │  │  ├─ new_right = w
│  │  │  └─ canvas_left = canvas_x + w + dx
│  │  │
│  │  └─ else (无水平移动):
│  │     ├─ new_left = 0
│  │     ├─ new_right = w
│  │     └─ canvas_left = canvas_x
│  │
│  ├─ 【计算竖直新增区域】 (类似逻辑)
│  │  ├─ if dy > 0: 从上边复制
│  │  ├─ elif dy < 0: 从下边复制
│  │  └─ else: 全部复制
│  │
│  ├─ 【提取新增区域】
│  │  ├─ new_region = minimap[new_top:new_bottom, new_left:new_right]
│  │  ├─ canvas_h = new_bottom - new_top
│  │  └─ canvas_w = new_right - new_left
│  │
│  ├─ 【边界检查】
│  │  ├─ if all canvas coordinates within bounds:
│  │  │
│  │  ├─ 【融合处理】
│  │  │  ├─ if (dx != 0 OR dy != 0) AND confidence > 0.75:
│  │  │  │  └─ blend_region = _blend_region()
│  │  │  │     ├─ 计算距离边界的权重
│  │  │  │     ├─ weight_x/y = 高斯分布 (边界低, 内部高)
│  │  │  │     ├─ 根据confidence调整权重强度
│  │  │  │     └─ blended = new * weight + old * (1-weight)
│  │  │  │
│  │  │  └─ canvas[canvas_top:bottom, canvas_left:right] = blended
│  │  │
│  │  └─ else: 直接覆盖
│  │     └─ canvas[canvas_top:bottom, canvas_left:right] = new_region
│  │
│  └─ return True
│
├─ [路径2] 低置信度 (confidence ≤ 0.85) - 特征匹配对齐
│  │
│  ├─ 【ORB特征检测】
│  │  ├─ kp1, des1 = orb.detectAndCompute(last_minimap)
│  │  ├─ kp2, des2 = orb.detectAndCompute(minimap)
│  │  │
│  │  ├─ if features not enough (< 4 keypoints):
│  │  │  └─ return _simple_stitch(minimap, dx, dy)
│  │  │
│  │  └─ else: 继续匹配
│  │
│  ├─ 【特征匹配】
│  │  ├─ matches = bf_matcher.knnMatch(des1, des2, k=2)
│  │  ├─ for each match_pair in matches:
│  │  │  ├─ if m.distance < 0.75 * n.distance:  [Lowe's ratio test]
│  │  │  │  └─ good_matches.append(m)
│  │  │  │
│  │  │  └─ else: 过滤掉不可靠的匹配
│  │  │
│  │  └─ if good_matches < 4:
│  │     └─ return _simple_stitch(minimap, dx, dy)
│  │
│  ├─ 【RANSAC仿射变换估计】
│  │  ├─ pts1 = 旧帧特征点坐标
│  │  ├─ pts2 = 新帧特征点坐标
│  │  ├─ matrix, mask = estimateAffinePartial2D(pts1, pts2, RANSAC)
│  │  │
│  │  ├─ if matrix is None:
│  │  │  └─ return _simple_stitch(minimap, dx, dy)
│  │  │
│  │  └─ else: 计算校正位移
│  │     ├─ corrected_dx = matrix[0, 2]
│  │     ├─ corrected_dy = matrix[1, 2]
│  │     └─ return _simple_stitch(minimap, corrected_dx, corrected_dy)
│  │
│  └─ exception: return _simple_stitch(minimap, dx, dy)
│
└─ return success_flag
```

---

## 🔄 状态机转移图

```
                     ┌──────────────────────────────────────┐
                     │  初始化: state = "IDLE"              │
                     │  初始化: no_move_frames = 0           │
                     │  初始化: state_confidence = 0         │
                     └────────────┬───────────────────────────┘
                                  │
                    ┌─────────────▼──────────────┐
                    │      📍 IDLE 状态          │
                    ├────────────────────────────┤
                    │ 特点：严格要求             │
                    │ response > 0.80            │
                    │ mean_diff +1.5*std_dev    │
                    │ 用途：防止静止时误触       │
                    └─────────────┬──────────────┘
                                  │
                                  │ ✅ 检测到真实移动
                                  │ (4个条件全满足)
                                  │
                    ┌─────────────▼──────────────┐
                    │   🔄 MOVING 状态           │
                    ├────────────────────────────┤
                    │ 特点：宽松要求             │
                    │ response > 0.60            │
                    │ mean_diff +0.8*std_dev    │
                    │ 用途：快速响应移动         │
                    │                            │
                    │ state_confidence↑ 每移动   │
                    │ no_move_frames = 0         │
                    └──┬──────────────────────┬──┘
                       │                      │
                       │ ❌ 无移动3帧+       │ ✅ 有移动
                       │                      │
            ┌──────────▼────────────┐        │
            │  ⏸️ STOPPED 状态     │◀───────┘
            ├──────────────────────┤
            │ 特点：平衡要求       │
            │ response > 0.70      │
            │ mean_diff +1.2*std..│
            │ 用途：验证是否停止   │
            │                      │
            │ no_move_frames=3..  │
            └──────────┬───────┬───┘
                       │       │
                       │       │ ✅ 有移动
                       │       │
        ❌ 无移动5帧+  │       └─────→ MOVING
                       │
            ┌──────────▼──────────┐
            │   👈 回到 IDLE       │
            │   no_move_frames=0  │
            └─────────────────────┘


【状态特征对比表】

状态    │ response阈值 │ mean_diff倍数 │ 转移条件        │ 用途
────────┼─────────────┼──────────────┼────────────────┼──────────────
IDLE    │ 0.80        │ 1.5x std     │ 移动→MOVING    │ 防止误触
MOVING  │ 0.60        │ 0.8x std     │ 3帧无移→STOPPED│ 快速响应
STOPPED │ 0.70        │ 1.2x std     │ 5帧无移→IDLE   │ 验证停止
        │             │              │ 有移→MOVING    │
```

---

## 🌊 Kalman滤波的效果

```
【原始位移信号 (带抖动)】
位移值: [12.1, 12.3, 11.9, 12.2, 12.0, 12.4, 12.1]
问题：频繁上下跳动0.2-0.4像素


【Kalman滤波后 (平滑)】
smooth = 0.7 * prev + 0.3 * new

计算过程：
  Frame 0: smooth = 12.1 (初始)
  Frame 1: smooth = 0.7*12.1 + 0.3*12.3 = 12.13  ← 平滑
  Frame 2: smooth = 0.7*12.13 + 0.3*11.9 = 12.05 ← 平滑
  Frame 3: smooth = 0.7*12.05 + 0.3*12.2 = 12.10 ← 平滑
  Frame 4: smooth = 0.7*12.10 + 0.3*12.0 = 12.07 ← 平滑

平滑值: [12.1, 12.13, 12.05, 12.10, 12.07, 12.15, 12.10]
特点：抖动完全消除，核心趋势保留


【权重调整的影响】
权重比例        │ 特性
────────────────┼──────────────────────────
0.8 * prev      │ 过度平滑，反应慢
0.7 * prev      │ [默认] 最优平衡
0.6 * prev      │ 响应更快，但抖动仍存
0.5 * prev      │ 基本无平滑，接近原始值
```

---

## 📈 自适应阈值计算

```
【原理】
adaptive_threshold = baseline + k × std_dev

baseline = 最近30帧mean_diff的平均值  (光照基准)
std_dev = 最近30帧mean_diff的标准差   (环境噪声)
k = 状态相关系数                       (根据state调整)


【计算示例】

假设最近30帧的mean_diff值：
[2.1, 2.3, 2.2, 2.4, 2.1, 2.5, 2.2, 2.3, 2.4, 2.2,
 2.3, 2.1, 2.4, 2.2, 2.3, 2.1, 2.2, 2.3, 2.4, 2.1,
 2.2, 2.3, 2.5, 2.2, 2.3, 2.1, 2.4, 2.2, 2.3, 2.2]

baseline = mean(...) = 2.25
std_dev = std(...) = 0.12


【不同状态下的阈值】

状态      │ 公式                    │ 计算结果  │ 说明
──────────┼────────────────────────┼──────────┼─────────────────
IDLE      │ 2.25 + 1.5 × 0.12     │ 2.43     │ 严格，3倍std以上才算
MOVING    │ 2.25 + 0.8 × 0.12     │ 2.35     │ 宽松，1.5倍std以上
STOPPED   │ 2.25 + 1.2 × 0.12     │ 2.39     │ 平衡，2.5倍std以上


【优势】

对比硬编码阈值(固定3.0):
  ├─ 高光照变化时：baseline上升，阈值跟随上升
  ├─ 低光照变化时：baseline下降，阈值跟随下降
  ├─ UI闪动多时：std_dev上升，IDLE时阈值上升防止误触
  └─ UI闪动少时：std_dev下降，MOVING时阈值下降便于检测

结果：完全自适应环境，无需手动调整
```

---

## 🎯 渐变融合的权重分布

```
【无融合 (直接覆盖)】
├─ 新帧像素 100%
├─ 旧帧像素 0%
└─ 问题：边界色差明显 ❌


【高斯权重融合】

水平方向的权重分布（dx>0向右移动）：
  边界        中间        边界
  ├────────────────────────┤
  │ 0.2  0.4  0.6  0.8  1.0│  ← 距离右边界越远，权重越高
  │ 新帧  20%  40%  60%  80%│100% (新帧)
  │ 旧帧  80%  60%  40%  20%│0%  (旧帧)


竖直方向类似：
  ├────────┤ (if dy>0)
  │ 0.2    │
  │ 0.4    │
  │ 0.6    │
  │ 0.8    │
  │ 1.0    │ ← 距离上边界越远，权重越高


【二维高斯权重】(dx && dy都不为0)

           │ 左边界  中间  右边界
    ───────┼─────────────────
    上边界 │ 0.04  0.20  0.16
    中间   │ 0.20  1.00  0.20
    下边界 │ 0.16  0.20  0.04

特点：
  ├─ 四个角落权重最低（0.04-0.16）
  ├─ 中心权重最高（1.0）
  └─ 边界平滑过渡，无突兀色差线


【confidence的影响】

weight_final = (weight_map) × confidence + (1 - confidence) × 0.5

confidence=0.95（高）：
  → weight_final ≈ weight_map   (保持高斯分布特性)

confidence=0.70（中）：
  → weight_final ≈ 0.5~weight_map (融合比例减弱)

confidence=0.50（低）：
  → weight_final ≈ 0.5 (双方各50%)


【实际视觉效果】

拼接处竖直缝线（概念图）：

无融合:
  旧帧    │新帧
  XXXXXX │YYYYYY  ← 色差线明显
  
渐变融合:
  旧帧    │融合过渡│新帧
  XXXXXX △△YYYYYY  ← 平滑过渡
```

---

## ⚙️ ORB特征匹配流程

```
【输入】
├─ 旧帧 (last_minimap)
└─ 新帧 (minimap)


【第一步】ORB特征检测
├─ kp1, des1 = orb.detectAndCompute(last_minimap)
│  ├─ 检测500个关键点 (nfeatures=500)
│  └─ 计算每个关键点的描述符
│
├─ kp2, des2 = orb.detectAndCompute(minimap)
│  ├─ 检测500个关键点
│  └─ 计算每个关键点的描述符
│
└─ 特征不足检查
   ├─ if len(kp1) < 4 or len(kp2) < 4:
   │  └─ return _simple_stitch()  ✗ 特征太少，放弃特征匹配
   └─ else: 继续


【第二步】特征匹配 (BF Matcher)
├─ matches = bf_matcher.knnMatch(des1, des2, k=2)
│  └─ 为每个旧帧特征找到最相似的2个新帧特征
│
├─ Lowe's Ratio Test (过滤)
│  for m, n in matches:
│     if m.distance < 0.75 * n.distance:
│        good_matches.append(m)
│
│  说明：
│  └─ 最佳匹配距离 < 0.75 × 次佳匹配距离
│     说明特征明确对应，值得信任
│
└─ 匹配质量检查
   ├─ if len(good_matches) < 4:
   │  └─ return _simple_stitch()  ✗ 匹配点太少
   └─ else: 继续


【第三步】RANSAC仿射变换估计
├─ 提取匹配点对的坐标
│  ├─ pts1 = [kp1[m.queryIdx].pt for m in good_matches]  ← 旧帧坐标
│  └─ pts2 = [kp2[m.trainIdx].pt for m in good_matches]  ← 新帧坐标
│
├─ RANSAC算法
│  ├─ 随机选择3对点，拟合仿射变换矩阵
│  ├─ 用该矩阵检验其他点
│  ├─ 计数有多少点符合这个矩阵
│  ├─ 重复多次，选择支持点最多的矩阵
│  └─ [结果] 消除异常匹配点的影响
│
├─ matrix, mask = estimateAffinePartial2D(pts1, pts2, RANSAC)
│  ├─ matrix: 仿射变换矩阵 [[a, b, tx], [c, d, ty]]
│  ├─ mask: 哪些点被认为是内点 (vs 外点)
│  └─ 如果没有足够的共识，返回None
│
└─ 校正位移
   ├─ if matrix is None:
   │  └─ return _simple_stitch()  ✗ RANSAC失败
   └─ else:
      ├─ corrected_dx = matrix[0, 2]  ← 从仿射矩阵提取dx
      ├─ corrected_dy = matrix[1, 2]  ← 从仿射矩阵提取dy
      └─ return _simple_stitch(minimap, corrected_dx, corrected_dy)


【输出】
└─ 使用校正后的位移进行拼接，精度更高 ✓


【例子】

旧帧特征: [(100, 200), (150, 250), (200, 300), ...]
新帧对应特征: [(112, 195), (162, 245), (212, 295), ...]

位移估计:
  dx ≈ (112-100 + 162-150 + ...) / n ≈ 12.3
  dy ≈ (195-200 + 245-250 + ...) / n ≈ -5.1

RANSAC的作用:
  ├─ 如果某个匹配明显偏离趋势（比如实际是(205, 290)）
  ├─ 它会被标记为外点 (outlier)
  └─ 计算时被忽略，不影响最终结果
```

---

## 📝 关键数据结构

```python
# 历史数据（用于自适应计算）
mean_diff_history: deque[float]
  ├─ 最大长度：30帧
  ├─ 用途：计算自适应阈值的baseline和std_dev
  └─ 更新频率：每帧一次

movement_history: deque[tuple(float, float, float)]
  ├─ 元素：(smooth_dx, smooth_dy, smooth_response)
  ├─ 最大长度：5帧
  ├─ 用途：debug和未来的自学习优化
  └─ 更新频率：仅在has_moved时更新


# Kalman滤波状态
smooth_dx: float          # 平滑后的x方向位移
smooth_dy: float          # 平滑后的y方向位移
smooth_response: float    # 平滑后的置信度

update_rule: new = 0.7 * prev + 0.3 * current


# 状态机状态
state: str                # "IDLE" | "MOVING" | "STOPPED"
state_confidence: float   # [0, 1] 状态确认度
no_move_frames: int       # 连续无移动的帧数

转移规则见状态机转移图


# 画布管理
canvas: ndarray[H, W, 3]  # 3000×3000 RGB图像
canvas_x: int             # 当前小地图在画布中的x坐标
canvas_y: int             # 当前小地图在画布中的y坐标


# 特征检测器
orb: ORB_create()         # ORB特征检测器 (500特征点)
bf_matcher: BFMatcher()   # 暴力特征匹配器 (HAMMING距离)
```

---

## 🔗 函数调用关系

```
main()
├─ SmartMinimapStitcher.__init__()
│  ├─ 创建Jy_screen()并绑定窗口
│  ├─ 初始化各种状态变量
│  └─ 创建ORB和BFMatcher
│
└─ stitcher.run()  ← 主循环
   ├─ [初始化] capture_minimap() → first_frame
   │
   └─ while True:  ← 主循环
      ├─ capture_minimap() → minimap_new
      │
      ├─ detect_movement(last_minimap, minimap_new)
      │  ├─ 计算mean_diff
      │  ├─ 计算自适应阈值
      │  ├─ 相位相关
      │  ├─ Kalman滤波
      │  ├─ 多条件验证
      │  └─ 状态机转移
      │  └─ return (has_moved, dx, dy, response, state)
      │
      ├─ if has_moved:
      │  ├─ stitch(minimap_new, last_minimap, dx, dy, response)
      │  │  ├─ if confidence > 0.85:  [路径1 增量拼接]
      │  │  │  ├─ 计算新增区域坐标
      │  │  │  ├─ if 需要融合:
      │  │  │  │  └─ _blend_region()
      │  │  │  │     ├─ 计算高斯权重
      │  │  │  │     ├─ 融合像素
      │  │  │  │     └─ return blended_region
      │  │  │  │
      │  │  │  └─ 复制到画布
      │  │  │
      │  │  └─ else:  [路径2 特征匹配]
      │  │     ├─ orb.detectAndCompute() x2
      │  │     ├─ bf_matcher.knnMatch()
      │  │     ├─ Lowe's ratio test
      │  │     ├─ estimateAffinePartial2D()
      │  │     ├─ _simple_stitch(corrected_dx, dy)
      │  │     │  ├─ 更新canvas_x/y
      │  │     │  └─ 复制到画布
      │  │     │
      │  │     └─ return success
      │  │
      │  └─ update last_minimap
      │
      ├─ cv2.imshow() → display
      ├─ cv2.waitKey() → handle input
      └─ time.sleep(0.05)
   
   ├─ 保存 final_smart_stitch.png
   ├─ 裁剪并保存 final_smart_stitch_cropped.png
   └─ 打印统计信息
```


