647 lines
27 KiB
Markdown
647 lines
27 KiB
Markdown
|
|
# algorithms.py 代码审查报告
|
|||
|
|
|
|||
|
|
> 审查日期: 2026-04-02
|
|||
|
|
> 审查文件: algorithms.py (1733行)
|
|||
|
|
> 审查范围: LeavePostAlgorithm, IntrusionAlgorithm, IllegalParkingAlgorithm, VehicleCongestionAlgorithm, AlgorithmManager
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 功能基线清单
|
|||
|
|
|
|||
|
|
### 1.1 LeavePostAlgorithm (离岗检测)
|
|||
|
|
|
|||
|
|
**状态定义 (7个):**
|
|||
|
|
|
|||
|
|
| 状态 | 常量 | 含义 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| INIT | `STATE_INIT` | 初始化,等待检测到人 |
|
|||
|
|
| CONFIRMING_ON_DUTY | `STATE_CONFIRMING_ON_DUTY` | 上岗确认中(需持续检测到人) |
|
|||
|
|
| ON_DUTY | `STATE_ON_DUTY` | 已确认在岗 |
|
|||
|
|
| CONFIRMING_OFF_DUTY | `STATE_CONFIRMING_OFF_DUTY` | 离岗确认中(持续未检测到人) |
|
|||
|
|
| OFF_DUTY_COUNTDOWN | `STATE_OFF_DUTY_COUNTDOWN` | 离岗倒计时(确认离岗后等待告警) |
|
|||
|
|
| ALARMED | `STATE_ALARMED` | 已告警(等待回岗) |
|
|||
|
|
| NON_WORK_TIME | `STATE_NON_WORK_TIME` | 非工作时间 |
|
|||
|
|
|
|||
|
|
**状态转换矩阵:**
|
|||
|
|
|
|||
|
|
| 从 \ 到 | INIT | CONFIRMING_ON_DUTY | ON_DUTY | CONFIRMING_OFF_DUTY | OFF_DUTY_COUNTDOWN | ALARMED | NON_WORK_TIME |
|
|||
|
|
|---------|------|--------------------|---------|--------------------|-------------------|---------|--------------|
|
|||
|
|
| INIT | - | roi_has_person==True | - | - | - | - | not in_working_hours |
|
|||
|
|
| CONFIRMING_ON_DUTY | detection_ratio==0 | - | elapsed>=confirm_on_duty_sec AND ratio>=0.6 | - | - | - | not in_working_hours |
|
|||
|
|
| ON_DUTY | - | - | - | detection_ratio<0.2 | - | - | not in_working_hours |
|
|||
|
|
| CONFIRMING_OFF_DUTY | - | - | detection_ratio>=0.5 | - | elapsed>=confirm_off_duty_sec AND ratio<0.2 | - | not in_working_hours |
|
|||
|
|
| OFF_DUTY_COUNTDOWN | - | - | roi_has_person==True | - | - | elapsed>=leave_countdown_sec AND cooldown ok | not in_working_hours |
|
|||
|
|
| ALARMED | - | roi_has_person==True | - | - | - | - | not in_working_hours |
|
|||
|
|
| NON_WORK_TIME | in_working_hours | - | - | - | - | - | - |
|
|||
|
|
|
|||
|
|
**关键行为:**
|
|||
|
|
- 使用滑动窗口(10秒)平滑检测结果,计算 detection_ratio
|
|||
|
|
- ALARMED -> CONFIRMING_ON_DUTY 复用上岗确认状态(不是独立的回岗确认状态)
|
|||
|
|
- 进入 ON_DUTY 状态时,若存在 `_last_alarm_id`,自动发送 `alarm_resolve` 事件
|
|||
|
|
- 进入 NON_WORK_TIME 时,若有未结束告警,发送 resolve_type="non_work_time" 的 resolve 事件
|
|||
|
|
- 冷却期检查使用 `cooldown_key = f"{camera_id}_{roi_id}"`
|
|||
|
|
- `_last_alarm_id` 由外部 main.py 通过 `set_last_alarm_id()` 回填
|
|||
|
|
- `_leave_start_time` 在进入 OFF_DUTY_COUNTDOWN 时记录(值等于 state_start_time)
|
|||
|
|
|
|||
|
|
**构造函数参数:**
|
|||
|
|
- `confirm_on_duty_sec`: int = 10 (上岗确认窗口)
|
|||
|
|
- `confirm_off_duty_sec`: int = 30 (离岗确认窗口)
|
|||
|
|
- `confirm_return_sec`: int = 10 (回岗确认窗口 -- 注意: 代码中实际未使用此参数)
|
|||
|
|
- `leave_countdown_sec`: int = 300 (离岗倒计时)
|
|||
|
|
- `cooldown_sec`: int = 600 (告警冷却期)
|
|||
|
|
- `working_hours`: Optional[List[Dict]] = None
|
|||
|
|
- `target_class`: Optional[str] = "person"
|
|||
|
|
- `alarm_level`: Optional[int] = None (默认2)
|
|||
|
|
- `confirm_leave_sec`: Optional[int] = None (向后兼容旧参数名)
|
|||
|
|
|
|||
|
|
### 1.2 IntrusionAlgorithm (周界入侵)
|
|||
|
|
|
|||
|
|
**状态定义 (4个):**
|
|||
|
|
|
|||
|
|
| 状态 | 常量 | 含义 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| IDLE | `STATE_IDLE` | 空闲,无入侵 |
|
|||
|
|
| CONFIRMING_INTRUSION | `STATE_CONFIRMING_INTRUSION` | 入侵确认中 |
|
|||
|
|
| ALARMED | `STATE_ALARMED` | 已告警(等待入侵消失) |
|
|||
|
|
| CONFIRMING_CLEAR | `STATE_CONFIRMING_CLEAR` | 入侵消失确认中 |
|
|||
|
|
|
|||
|
|
**状态转换矩阵:**
|
|||
|
|
|
|||
|
|
| 从 \ 到 | IDLE | CONFIRMING_INTRUSION | ALARMED | CONFIRMING_CLEAR |
|
|||
|
|
|---------|------|---------------------|---------|-----------------|
|
|||
|
|
| IDLE | - | roi_has_person==True | - | - |
|
|||
|
|
| CONFIRMING_INTRUSION | not roi_has_person OR cooldown内 OR state_start_time==None | - | elapsed>=confirm_intrusion_seconds AND cooldown ok | - |
|
|||
|
|
| ALARMED | - | - | - | not roi_has_person |
|
|||
|
|
| CONFIRMING_CLEAR | elapsed>=confirm_clear_seconds AND no person | - | person_elapsed>=confirm_intrusion_seconds (持续有人) OR state_start_time==None | - |
|
|||
|
|
|
|||
|
|
**关键行为:**
|
|||
|
|
- 不使用滑动窗口,直接使用当前帧的 `roi_has_person` 判断
|
|||
|
|
- CONFIRMING_CLEAR 有子状态追踪: `_person_detected_in_clear_time` 用于判断短暂有人 vs 持续有人
|
|||
|
|
- 冷却期内入侵确认直接回到 IDLE(不触发告警)
|
|||
|
|
- 包含防御性编程: state_start_time==None 时重置到 IDLE
|
|||
|
|
- `_check_target_class` 允许 target_class 为 None(匹配所有类别)
|
|||
|
|
|
|||
|
|
**构造函数参数:**
|
|||
|
|
- `cooldown_seconds`: int = 300
|
|||
|
|
- `confirm_seconds`: int = 5 (向后兼容)
|
|||
|
|
- `confirm_intrusion_seconds`: Optional[int] = None (默认使用 confirm_seconds)
|
|||
|
|
- `confirm_clear_seconds`: Optional[int] = None (默认180)
|
|||
|
|
- `target_class`: Optional[str] = None
|
|||
|
|
- `alarm_level`: Optional[int] = None (默认1)
|
|||
|
|
|
|||
|
|
### 1.3 IllegalParkingAlgorithm (车辆违停)
|
|||
|
|
|
|||
|
|
**状态定义 (5个):**
|
|||
|
|
|
|||
|
|
| 状态 | 常量 | 含义 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| IDLE | `STATE_IDLE` | 空闲 |
|
|||
|
|
| CONFIRMING_VEHICLE | `STATE_CONFIRMING_VEHICLE` | 车辆确认中 |
|
|||
|
|
| PARKED_COUNTDOWN | `STATE_PARKED_COUNTDOWN` | 违停倒计时 |
|
|||
|
|
| ALARMED | `STATE_ALARMED` | 已告警 |
|
|||
|
|
| CONFIRMING_CLEAR | `STATE_CONFIRMING_CLEAR` | 消失确认中 |
|
|||
|
|
|
|||
|
|
**状态转换矩阵:**
|
|||
|
|
|
|||
|
|
| 从 \ 到 | IDLE | CONFIRMING_VEHICLE | PARKED_COUNTDOWN | ALARMED | CONFIRMING_CLEAR |
|
|||
|
|
|---------|------|-------------------|-----------------|---------|-----------------|
|
|||
|
|
| IDLE | - | roi_has_vehicle | - | - | - |
|
|||
|
|
| CONFIRMING_VEHICLE | ratio<0.3 OR state_start_time==None | - | elapsed>=confirm_vehicle_sec AND ratio>=0.6 | - | - |
|
|||
|
|
| PARKED_COUNTDOWN | ratio<0.2 (车离开) OR cooldown内 OR state_start_time==None | - | - | elapsed>=parking_countdown_sec AND cooldown ok | - |
|
|||
|
|
| ALARMED | - | - | - | - | ratio<0.15 |
|
|||
|
|
| CONFIRMING_CLEAR | elapsed>=confirm_clear_sec AND ratio<0.2 OR state_start_time==None | - | - | ratio>=0.5 | - |
|
|||
|
|
|
|||
|
|
**关键行为:**
|
|||
|
|
- 使用滑动窗口(WINDOW_SIZE_SEC=10秒)
|
|||
|
|
- 支持多类车辆: target_classes 默认 ["car", "truck", "bus", "motorcycle"]
|
|||
|
|
- 告警字段包含 `confidence` 和 `duration_minutes`
|
|||
|
|
- CONFIRMING_CLEAR -> IDLE 时清除 alert_cooldowns(新车违停可正常告警)
|
|||
|
|
- ALARMED 进入 CONFIRMING_CLEAR 的阈值(0.15)比其他算法更严格
|
|||
|
|
|
|||
|
|
**构造函数参数:**
|
|||
|
|
- `confirm_vehicle_sec`: int = 15
|
|||
|
|
- `parking_countdown_sec`: int = 300
|
|||
|
|
- `confirm_clear_sec`: int = 120
|
|||
|
|
- `cooldown_sec`: int = 1800
|
|||
|
|
- `target_classes`: Optional[List[str]] = None (默认 ["car", "truck", "bus", "motorcycle"])
|
|||
|
|
- `alarm_level`: Optional[int] = None (默认1)
|
|||
|
|
|
|||
|
|
### 1.4 VehicleCongestionAlgorithm (车辆拥堵)
|
|||
|
|
|
|||
|
|
**状态定义 (4个):**
|
|||
|
|
|
|||
|
|
| 状态 | 常量 | 含义 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| NORMAL | `STATE_NORMAL` | 正常 |
|
|||
|
|
| CONFIRMING_CONGESTION | `STATE_CONFIRMING_CONGESTION` | 拥堵确认中 |
|
|||
|
|
| CONGESTED | `STATE_CONGESTED` | 拥堵中 |
|
|||
|
|
| CONFIRMING_CLEAR | `STATE_CONFIRMING_CLEAR` | 消散确认中 |
|
|||
|
|
|
|||
|
|
**状态转换矩阵:**
|
|||
|
|
|
|||
|
|
| 从 \ 到 | NORMAL | CONFIRMING_CONGESTION | CONGESTED | CONFIRMING_CLEAR |
|
|||
|
|
|---------|--------|----------------------|-----------|-----------------|
|
|||
|
|
| NORMAL | - | avg_count >= count_threshold | - | - |
|
|||
|
|
| CONFIRMING_CONGESTION | avg_count < count_threshold OR cooldown内 OR state_start_time==None | - | elapsed >= confirm_congestion_sec AND cooldown ok | - |
|
|||
|
|
| CONGESTED | - | - | - | avg_count < count_threshold * 0.5 |
|
|||
|
|
| CONFIRMING_CLEAR | elapsed >= confirm_clear_sec OR state_start_time==None | - | avg_count >= count_threshold | - |
|
|||
|
|
|
|||
|
|
**关键行为:**
|
|||
|
|
- 使用滑动窗口(WINDOW_SIZE_SEC=10秒)存储车辆计数,取平均值判断
|
|||
|
|
- 消散需车辆数降到阈值的 **50%** 以下才开始确认(避免抖动)
|
|||
|
|
- CONFIRMING_CLEAR -> NORMAL 时清除 alert_cooldowns
|
|||
|
|
- 告警字段包含 `vehicle_count` 和 `confidence`
|
|||
|
|
|
|||
|
|
**构造函数参数:**
|
|||
|
|
- `count_threshold`: int = 5
|
|||
|
|
- `confirm_congestion_sec`: int = 60
|
|||
|
|
- `confirm_clear_sec`: int = 180
|
|||
|
|
- `cooldown_sec`: int = 1800
|
|||
|
|
- `target_classes`: Optional[List[str]] = None (默认 ["car", "truck", "bus", "motorcycle"])
|
|||
|
|
- `alarm_level`: Optional[int] = None (默认2)
|
|||
|
|
|
|||
|
|
### 1.5 AlgorithmManager
|
|||
|
|
|
|||
|
|
**数据结构:**
|
|||
|
|
```
|
|||
|
|
self.algorithms: Dict[str, Dict[str, Dict[str, Algorithm]]]
|
|||
|
|
结构: { roi_id: { "{roi_id}_{bind_id}": { algo_type: algo_instance } } }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**公开方法:**
|
|||
|
|
- `start_config_subscription()` - 启动 Redis 配置订阅
|
|||
|
|
- `stop_config_subscription()` - 停止配置订阅
|
|||
|
|
- `load_bind_from_redis(bind_id)` - 从 Redis 加载单个绑定配置
|
|||
|
|
- `reload_bind_algorithm(bind_id)` - 重载单个绑定
|
|||
|
|
- `reload_algorithm(roi_id)` - 重载单个 ROI 的所有算法
|
|||
|
|
- `update_algorithm_params(roi_id, bind_id, bind_config)` - 仅更新参数,保留状态
|
|||
|
|
- `reload_all_algorithms(preserve_state=True)` - 重载全部算法
|
|||
|
|
- `register_algorithm(roi_id, bind_id, algorithm_type, params)` - 注册算法(带缓存)
|
|||
|
|
- `process(roi_id, bind_id, camera_id, algorithm_type, tracks, current_time)` - 处理检测结果
|
|||
|
|
- `update_roi_params(roi_id, bind_id, algorithm_type, params)` - 更新参数
|
|||
|
|
- `reset_algorithm(roi_id, bind_id=None)` - 重置算法状态
|
|||
|
|
- `reset_all()` - 重置所有算法
|
|||
|
|
- `remove_roi(roi_id)` - 移除 ROI
|
|||
|
|
- `remove_bind(roi_id, bind_id)` - 移除绑定
|
|||
|
|
- `get_status(roi_id)` - 获取状态
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 接口契约清单
|
|||
|
|
|
|||
|
|
### 2.1 process() 方法统一签名
|
|||
|
|
|
|||
|
|
所有四个算法的 `process()` 方法具有相同签名:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def process(
|
|||
|
|
self,
|
|||
|
|
roi_id: str,
|
|||
|
|
camera_id: str,
|
|||
|
|
tracks: List[Dict],
|
|||
|
|
current_time: Optional[datetime] = None,
|
|||
|
|
) -> List[Dict]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**tracks 输入格式 (每个元素):**
|
|||
|
|
```python
|
|||
|
|
{
|
|||
|
|
"track_id": str, # 跟踪ID
|
|||
|
|
"class": str, # 检测类别 ("person", "car", "truck", ...)
|
|||
|
|
"confidence": float, # 置信度
|
|||
|
|
"bbox": List[float], # 边界框 [x1, y1, x2, y2]
|
|||
|
|
"matched_rois": [ # 匹配的ROI列表
|
|||
|
|
{"roi_id": str}
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 告警输出格式
|
|||
|
|
|
|||
|
|
#### LeavePostAlgorithm 告警:
|
|||
|
|
```python
|
|||
|
|
{
|
|||
|
|
"track_id": str, # 等于 roi_id
|
|||
|
|
"camera_id": str,
|
|||
|
|
"bbox": List[float], # 可能为空 []
|
|||
|
|
"alert_type": "leave_post",
|
|||
|
|
"alarm_level": int, # 默认 2
|
|||
|
|
"message": "人员离岗告警",
|
|||
|
|
"first_frame_time": str, # 格式: '%Y-%m-%d %H:%M:%S'
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
注意: leave_post 告警使用 `track_id` 而非 `roi_id` 字段名(与其他算法不同)。
|
|||
|
|
|
|||
|
|
#### IntrusionAlgorithm 告警:
|
|||
|
|
```python
|
|||
|
|
{
|
|||
|
|
"roi_id": str,
|
|||
|
|
"camera_id": str,
|
|||
|
|
"bbox": List[float],
|
|||
|
|
"alert_type": "intrusion",
|
|||
|
|
"alarm_level": int, # 默认 1
|
|||
|
|
"message": "检测到周界入侵",
|
|||
|
|
"first_frame_time": str, # 格式: '%Y-%m-%d %H:%M:%S'
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### IllegalParkingAlgorithm 告警:
|
|||
|
|
```python
|
|||
|
|
{
|
|||
|
|
"roi_id": str,
|
|||
|
|
"camera_id": str,
|
|||
|
|
"bbox": List[float],
|
|||
|
|
"alert_type": "illegal_parking",
|
|||
|
|
"alarm_level": int, # 默认 1
|
|||
|
|
"confidence": float,
|
|||
|
|
"message": str, # 动态生成,包含停留分钟数
|
|||
|
|
"first_frame_time": str, # 格式: '%Y-%m-%d %H:%M:%S',可能为 None
|
|||
|
|
"duration_minutes": float,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### VehicleCongestionAlgorithm 告警:
|
|||
|
|
```python
|
|||
|
|
{
|
|||
|
|
"roi_id": str,
|
|||
|
|
"camera_id": str,
|
|||
|
|
"bbox": List[float],
|
|||
|
|
"alert_type": "vehicle_congestion",
|
|||
|
|
"alarm_level": int, # 默认 2
|
|||
|
|
"confidence": float,
|
|||
|
|
"message": str, # 动态生成,包含平均车辆数和持续秒数
|
|||
|
|
"first_frame_time": str, # 格式: '%Y-%m-%d %H:%M:%S',可能为 None
|
|||
|
|
"vehicle_count": int,
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### alarm_resolve 事件 (所有算法统一格式):
|
|||
|
|
```python
|
|||
|
|
{
|
|||
|
|
"alert_type": "alarm_resolve",
|
|||
|
|
"resolve_alarm_id": str,
|
|||
|
|
"duration_ms": int,
|
|||
|
|
"last_frame_time": str, # 格式: '%Y-%m-%d %H:%M:%S'
|
|||
|
|
"resolve_type": str, # "person_returned" | "non_work_time" | "intrusion_cleared" | "vehicle_left" | "congestion_cleared"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 AlgorithmManager.process() 签名
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def process(
|
|||
|
|
self,
|
|||
|
|
roi_id: str,
|
|||
|
|
bind_id: str,
|
|||
|
|
camera_id: str,
|
|||
|
|
algorithm_type: str,
|
|||
|
|
tracks: List[Dict],
|
|||
|
|
current_time: Optional[datetime] = None,
|
|||
|
|
) -> List[Dict]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 AlgorithmManager.register_algorithm() 签名
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
def register_algorithm(
|
|||
|
|
self,
|
|||
|
|
roi_id: str,
|
|||
|
|
bind_id: str,
|
|||
|
|
algorithm_type: str, # "leave_post" | "intrusion" | "illegal_parking" | "vehicle_congestion"
|
|||
|
|
params: Optional[Dict[str, Any]] = None,
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 已发现的潜在问题
|
|||
|
|
|
|||
|
|
### 3.1 Critical (必须修复)
|
|||
|
|
|
|||
|
|
**[C1] LeavePostAlgorithm: `confirm_return_sec` 参数声明但从未使用**
|
|||
|
|
- 位置: 第53行声明,但状态机中 ALARMED -> CONFIRMING_ON_DUTY -> ON_DUTY 的转换直接复用 `confirm_on_duty_sec`
|
|||
|
|
- 影响: 使用者设置 `confirm_return_sec` 以为可以独立控制回岗确认时长,但实际无效
|
|||
|
|
- 建议: 文档中声明此参数复用 `confirm_on_duty_sec`,或实现独立的回岗确认逻辑
|
|||
|
|
|
|||
|
|
**[C2] LeavePostAlgorithm: resolve 事件的 duration_ms 计算依赖 `_leave_start_time`,但该值可能为 None**
|
|||
|
|
- 位置: 第325行 `duration_ms = int((current_time - self._leave_start_time).total_seconds() * 1000)`
|
|||
|
|
- 场景: 如果算法在 OFF_DUTY_COUNTDOWN 之前(即 _leave_start_time 赋值之前)因某种异常跳到 ON_DUTY 且 _last_alarm_id 非空,会抛出 TypeError
|
|||
|
|
- 风险: 低概率但会导致该帧整个 process 调用抛异常
|
|||
|
|
|
|||
|
|
**[C3] LeavePostAlgorithm: `_leave_start_time` 赋值时机问题**
|
|||
|
|
- 位置: 第277行 `self._leave_start_time = self.state_start_time`
|
|||
|
|
- `state_start_time` 此时是 CONFIRMING_OFF_DUTY 的开始时间(非 OFF_DUTY_COUNTDOWN 的开始时间,因为 state_start_time 在下一行第276行才被更新为 current_time)
|
|||
|
|
- 实际效果: `_leave_start_time` 记录的是**离岗确认开始时间**,不是**倒计时开始时间**
|
|||
|
|
- 审查结论: 这是有意设计,离开时间应该从人离开被确认开始计算,但代码注释"记录离开时间"可能造成误解
|
|||
|
|
|
|||
|
|
### 3.2 Important (应该修复)
|
|||
|
|
|
|||
|
|
**[I1] LeavePostAlgorithm 告警字典使用 `track_id` 而非 `roi_id`**
|
|||
|
|
- 位置: 第299行 `"track_id": roi_id`
|
|||
|
|
- 其他三个算法统一使用 `"roi_id": roi_id`
|
|||
|
|
- 影响: main.py 中的 `_handle_detections` 不直接使用此字段(它有自己的 roi_id),所以不影响功能,但接口不一致
|
|||
|
|
|
|||
|
|
**[I2] AlgorithmManager 缺乏线程安全**
|
|||
|
|
- `process()` 方法未加锁(第1637-1651行),而 `register_algorithm()` 也未加锁
|
|||
|
|
- `_update_lock` 仅在 `load_bind_from_redis` 和 `reload_all_algorithms` 中使用
|
|||
|
|
- 风险: 如果 config_update_worker 线程调用 `reload_all_algorithms` 同时主线程调用 `process`,可能读到不一致的 `self.algorithms` 字典
|
|||
|
|
- 缓解: Python GIL 在字典读操作上提供了一定程度的原子性保护,实际崩溃概率很低
|
|||
|
|
|
|||
|
|
**[I3] AlgorithmManager.default_params 中 illegal_parking 的 confirm_clear_sec 默认值(30) 与 IllegalParkingAlgorithm 构造函数默认值(120) 不一致**
|
|||
|
|
- 位置: 第1233行 vs 第751行
|
|||
|
|
- 影响: 通过 AlgorithmManager 创建的 illegal_parking 算法 confirm_clear_sec 为 30,直接创建为 120
|
|||
|
|
|
|||
|
|
**[I4] AlgorithmManager.default_params 中 vehicle_congestion 的 count_threshold 默认值(3) 与 VehicleCongestionAlgorithm 构造函数默认值(5) 不一致**
|
|||
|
|
- 位置: 第1237行 vs 第1004行
|
|||
|
|
- 影响: 通过 AlgorithmManager 创建的算法阈值为 3,直接创建为 5
|
|||
|
|
|
|||
|
|
**[I5] `update_algorithm_params` 仅支持 leave_post 和 intrusion**
|
|||
|
|
- 位置: 第1461-1496行
|
|||
|
|
- 缺少 illegal_parking 和 vehicle_congestion 的参数更新逻辑(第1494行注释 "其他算法类型可以在此添加")
|
|||
|
|
- 影响: `reload_all_algorithms(preserve_state=True)` 对 illegal_parking/vehicle_congestion 会回退到 `load_bind_from_redis`,会重置算法状态
|
|||
|
|
|
|||
|
|
**[I6] `load_bind_from_redis` 仅支持 leave_post 和 intrusion**
|
|||
|
|
- 位置: 第1322-1403行
|
|||
|
|
- 缺少 illegal_parking 和 vehicle_congestion 的 Redis 加载逻辑
|
|||
|
|
- 影响: 从 Redis 热更新配置时,这两种算法无法被加载
|
|||
|
|
|
|||
|
|
**[I7] IntrusionAlgorithm: `_get_latest_bbox` 不检查 target_class**
|
|||
|
|
- 位置: 第456-460行
|
|||
|
|
- 与 LeavePostAlgorithm 不同(第139-142行会检查 target_class)
|
|||
|
|
- 影响: 可能返回非目标类别的 bbox
|
|||
|
|
|
|||
|
|
**[I8] `_is_in_working_hours` 不支持跨午夜时间段**
|
|||
|
|
- 位置: 第112行 `if start_minutes <= current_minutes < end_minutes`
|
|||
|
|
- 如果 working_hours 配置为 `{"start": "22:00", "end": "06:00"}`,则无法正确判断
|
|||
|
|
- 影响: 夜班场景可能不工作
|
|||
|
|
|
|||
|
|
### 3.3 Suggestions (建议改进)
|
|||
|
|
|
|||
|
|
**[S1] 滑动窗口的 window_size_sec 硬编码为 10 秒**
|
|||
|
|
- LeavePostAlgorithm 第80行: `self.window_size_sec = 10`
|
|||
|
|
- IllegalParkingAlgorithm 第744行: `WINDOW_SIZE_SEC = 10`
|
|||
|
|
- VehicleCongestionAlgorithm 第1000行: `WINDOW_SIZE_SEC = 10`
|
|||
|
|
- 建议: 提取为可配置参数
|
|||
|
|
|
|||
|
|
**[S2] LeavePostAlgorithm._update_detection_window 与 IllegalParkingAlgorithm._update_window 实现逻辑相同但代码重复**
|
|||
|
|
- 可提取为基类方法或工具函数
|
|||
|
|
|
|||
|
|
**[S3] LeavePostAlgorithm.get_state() 使用 datetime.now() 而非参数传入的 current_time**
|
|||
|
|
- 位置: 第367-372行
|
|||
|
|
- 在测试场景中会导致状态信息不准确(不影响核心逻辑,仅影响监控展示)
|
|||
|
|
|
|||
|
|
**[S4] AlgorithmManager.get_status() 中 leave_post 分支访问 `alarm_sent` 属性**
|
|||
|
|
- 位置: 第1724行
|
|||
|
|
- LeavePostAlgorithm 实际上没有 `alarm_sent` 属性,getattr 返回 False
|
|||
|
|
- 这是旧版本残留代码
|
|||
|
|
|
|||
|
|
**[S5] AlgorithmManager.remove_roi() 中 bind_id 解析逻辑脆弱**
|
|||
|
|
- 位置: 第1703行 `key.split("_")[-1]`
|
|||
|
|
- 如果 bind_id 本身包含下划线,解析会出错
|
|||
|
|
- key 格式为 `"{roi_id}_{bind_id}"`,应该用 `key[len(roi_id)+1:]` 提取 bind_id
|
|||
|
|
|
|||
|
|
**[S6] `_is_in_working_hours` 中的 bare `except:` (第99行, 第124行)**
|
|||
|
|
- 应该至少 except Exception 或更具体的异常类型
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 测试覆盖分析
|
|||
|
|
|
|||
|
|
### 4.1 test_leave_post_full_workflow.py 覆盖的场景
|
|||
|
|
|
|||
|
|
| 场景 | 状态路径 | 覆盖 |
|
|||
|
|
|------|----------|------|
|
|||
|
|
| 上岗确认成功 | INIT -> CONFIRMING_ON_DUTY -> ON_DUTY | YES |
|
|||
|
|
| 离岗确认 | ON_DUTY -> CONFIRMING_OFF_DUTY -> OFF_DUTY_COUNTDOWN | YES |
|
|||
|
|
| 倒计时触发告警 | OFF_DUTY_COUNTDOWN -> ALARMED | YES |
|
|||
|
|
| 回岗 resolve | ALARMED -> CONFIRMING_ON_DUTY -> ON_DUTY (+ resolve) | YES |
|
|||
|
|
| 告警字段验证 | 无 duration_minutes, 有 first_frame_time | YES |
|
|||
|
|
| resolve 字段验证 | duration_ms, resolve_alarm_id, resolve_type | YES |
|
|||
|
|
| set_last_alarm_id 回填 | - | YES |
|
|||
|
|
|
|||
|
|
### 4.2 test_vehicle_algorithms.py 覆盖的场景
|
|||
|
|
|
|||
|
|
**IllegalParkingAlgorithm:**
|
|||
|
|
| 场景 | 覆盖 |
|
|||
|
|
|------|------|
|
|||
|
|
| 完整生命周期 IDLE->CONFIRMING->COUNTDOWN->ALARMED->CLEAR->IDLE | YES |
|
|||
|
|
| 车辆短暂路过不触发 | YES |
|
|||
|
|
| 多类车辆检测 (truck, bus) | YES |
|
|||
|
|
| person 不触发违停 | YES |
|
|||
|
|
| 冷却期内不重复告警 | YES |
|
|||
|
|
| resolve 事件发送 | YES |
|
|||
|
|
|
|||
|
|
**VehicleCongestionAlgorithm:**
|
|||
|
|
| 场景 | 覆盖 |
|
|||
|
|
|------|------|
|
|||
|
|
| 完整生命周期 NORMAL->CONFIRMING->CONGESTED->CLEAR->NORMAL | YES |
|
|||
|
|
| 少于阈值不触发 | YES |
|
|||
|
|
| 短暂拥堵不触发 | YES |
|
|||
|
|
| resolve 事件发送 | YES |
|
|||
|
|
|
|||
|
|
**AlgorithmManager:**
|
|||
|
|
| 场景 | 覆盖 |
|
|||
|
|
|------|------|
|
|||
|
|
| 注册 illegal_parking | YES |
|
|||
|
|
| 注册 vehicle_congestion | YES |
|
|||
|
|
| process 调用 | YES |
|
|||
|
|
| get_status 调用 | YES |
|
|||
|
|
| 重复注册走缓存 | YES |
|
|||
|
|
| reset_algorithm | YES |
|
|||
|
|
|
|||
|
|
### 4.3 测试覆盖缺口
|
|||
|
|
|
|||
|
|
**LeavePostAlgorithm 未覆盖:**
|
|||
|
|
- [ ] CONFIRMING_ON_DUTY -> INIT (人消失)
|
|||
|
|
- [ ] CONFIRMING_OFF_DUTY -> ON_DUTY (人回来,ratio>=0.5)
|
|||
|
|
- [ ] OFF_DUTY_COUNTDOWN -> ON_DUTY (倒计时期间回来)
|
|||
|
|
- [ ] 非工作时间自动 resolve
|
|||
|
|
- [ ] NON_WORK_TIME -> INIT (工作时间恢复)
|
|||
|
|
- [ ] 冷却期内不重复告警
|
|||
|
|
- [ ] 空 tracks 输入
|
|||
|
|
- [ ] working_hours 配置解析(字符串格式)
|
|||
|
|
|
|||
|
|
**IntrusionAlgorithm 完全未测试:**
|
|||
|
|
- [ ] 完整生命周期 IDLE->CONFIRMING->ALARMED->CLEAR->IDLE
|
|||
|
|
- [ ] 入侵确认中人消失
|
|||
|
|
- [ ] CONFIRMING_CLEAR 中短暂有人 vs 持续有人
|
|||
|
|
- [ ] 冷却期
|
|||
|
|
- [ ] resolve 事件
|
|||
|
|
- [ ] target_class=None 匹配所有类别
|
|||
|
|
- [ ] state_start_time==None 的防御性代码分支
|
|||
|
|
|
|||
|
|
**IllegalParkingAlgorithm 未覆盖:**
|
|||
|
|
- [ ] state_start_time==None 的防御性代码分支 (CONFIRMING_VEHICLE, PARKED_COUNTDOWN, CONFIRMING_CLEAR)
|
|||
|
|
- [ ] CONFIRMING_CLEAR -> ALARMED (车辆又出现, ratio>=0.5)
|
|||
|
|
- [ ] ALARMED 状态下 ratio 在 0.15-0.5 之间(维持 ALARMED)
|
|||
|
|
|
|||
|
|
**VehicleCongestionAlgorithm 未覆盖:**
|
|||
|
|
- [ ] state_start_time==None 的防御性代码分支
|
|||
|
|
- [ ] CONFIRMING_CLEAR -> CONGESTED (又拥堵了)
|
|||
|
|
- [ ] 消散阈值 0.5*count_threshold 的边界值
|
|||
|
|
- [ ] 冷却期测试
|
|||
|
|
|
|||
|
|
**AlgorithmManager 未覆盖:**
|
|||
|
|
- [ ] start_config_subscription / stop_config_subscription
|
|||
|
|
- [ ] load_bind_from_redis
|
|||
|
|
- [ ] reload_all_algorithms (含孤立实例清理)
|
|||
|
|
- [ ] update_algorithm_params
|
|||
|
|
- [ ] remove_roi / remove_bind
|
|||
|
|
- [ ] 并发调用安全性
|
|||
|
|
- [ ] register_algorithm 的 leave_post 和 intrusion 类型
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 优化安全边界
|
|||
|
|
|
|||
|
|
### 5.1 不可修改区域 (功能合约)
|
|||
|
|
|
|||
|
|
以下代码是外部依赖的契约,修改会破坏 main.py 或其他模块:
|
|||
|
|
|
|||
|
|
1. **所有算法的 `process()` 方法签名** -- main.py 的 `_handle_detections` 直接调用
|
|||
|
|
2. **告警字典的字段名和类型** -- main.py 的 `_handle_detections` 依赖 `alert_type`, `alarm_level`, `confidence`, `bbox`, `message`, `first_frame_time`, `duration_minutes`, `vehicle_count`
|
|||
|
|
3. **`alarm_resolve` 事件格式** -- main.py 的 resolve 逻辑依赖 `resolve_alarm_id`, `duration_ms`, `last_frame_time`, `resolve_type`
|
|||
|
|
4. **`set_last_alarm_id(alarm_id)` 方法** -- main.py 回填 alarm_id
|
|||
|
|
5. **`reset()` 方法** -- AlgorithmManager 调用
|
|||
|
|
6. **`get_state()` 方法** -- AlgorithmManager.get_status() 调用
|
|||
|
|
7. **AlgorithmManager.process() 签名和返回值** -- main.py 直接调用
|
|||
|
|
8. **AlgorithmManager.register_algorithm() 签名** -- main.py 直接调用
|
|||
|
|
9. **AlgorithmManager.algorithms 的三层字典结构** -- main.py 直接访问内部实例来获取 `_leave_start_time` 等属性 (第905-911行)
|
|||
|
|
|
|||
|
|
### 5.2 可安全优化区域
|
|||
|
|
|
|||
|
|
以下代码修改不会影响外部行为:
|
|||
|
|
|
|||
|
|
1. **滑动窗口实现** -- `_update_detection_window`, `_update_window`, `_update_count_window` 的内部实现可以优化,只要 `_get_detection_ratio()`, `_get_window_ratio()`, `_get_avg_count()` 的语义不变
|
|||
|
|
2. **`_check_detection_in_roi` / `_check_target_class` / `_check_target_classes`** -- 内部实现可优化,接口不变即可
|
|||
|
|
3. **`_get_latest_bbox` / `_get_max_confidence`** -- 辅助方法,内部实现可优化
|
|||
|
|
4. **`_is_in_working_hours` / `_parse_time_to_minutes`** -- 内部实现可优化(建议修复跨午夜问题)
|
|||
|
|
5. **AlgorithmManager 的 Redis 相关方法** -- `load_bind_from_redis`, `reload_*`, `_config_update_worker` 可以修改,不影响算法核心逻辑
|
|||
|
|
6. **日志输出** -- 所有 logger.* 调用可以调整
|
|||
|
|
7. **`default_params` 字典** -- 可以修正默认值不一致的问题
|
|||
|
|
|
|||
|
|
### 5.3 高风险修改区域 (需要完整回归测试)
|
|||
|
|
|
|||
|
|
1. **状态转换条件 (ratio 阈值)** -- 任何 detection_ratio, window_ratio 的阈值变更都可能影响告警灵敏度
|
|||
|
|
- LeavePostAlgorithm: 0.6 (上岗), 0.2 (离岗开始), 0.5 (离岗恢复), 0.2 (离岗确认)
|
|||
|
|
- IllegalParkingAlgorithm: 0.3 (放弃确认), 0.6 (确认有车), 0.2 (车离开), 0.15 (开始消失确认), 0.5 (车又来), 0.2 (消失确认)
|
|||
|
|
- VehicleCongestionAlgorithm: count_threshold (开始确认), 0.5*count_threshold (开始消散)
|
|||
|
|
|
|||
|
|
2. **时间比较逻辑** -- `elapsed >= xxx_sec` 的方向(大于等于 vs 大于)
|
|||
|
|
3. **冷却期检查** -- `cooldown_key` 的构造方式和比较逻辑
|
|||
|
|
4. **resolve 事件触发逻辑** -- LeavePostAlgorithm 的 "进入 ON_DUTY 且 _last_alarm_id 存在" 的检查
|
|||
|
|
|
|||
|
|
### 5.4 重复代码可提取区域
|
|||
|
|
|
|||
|
|
以下方法在多个算法中重复实现,可以提取为基类或 mixin:
|
|||
|
|
|
|||
|
|
| 方法 | 出现在 | 可提取 |
|
|||
|
|
|------|--------|--------|
|
|||
|
|
| `_check_detection_in_roi` | 全部4个 | YES |
|
|||
|
|
| `_check_target_class` | LeavePost, Intrusion | YES |
|
|||
|
|
| `_check_target_classes` | IllegalParking, VehicleCongestion | YES |
|
|||
|
|
| `_get_latest_bbox` | 全部4个 | YES (注意 Intrusion 不检查 target_class) |
|
|||
|
|
| `_get_max_confidence` | IllegalParking, VehicleCongestion | YES |
|
|||
|
|
| `set_last_alarm_id` | 全部4个 | YES |
|
|||
|
|
| 滑动窗口逻辑 | LeavePost, IllegalParking, VehicleCongestion | YES |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. config_models.py 与 algorithms.py 的一致性
|
|||
|
|
|
|||
|
|
### 6.1 AlgorithmType 枚举缺失
|
|||
|
|
|
|||
|
|
`config_models.py` 中的 `AlgorithmType` 枚举:
|
|||
|
|
```python
|
|||
|
|
LEAVE_POST = "leave_post"
|
|||
|
|
INTRUSION = "intrusion"
|
|||
|
|
CROWD_DETECTION = "crowd_detection" # 已在 algorithms.py 中注释掉
|
|||
|
|
FACE_RECOGNITION = "face_recognition" # algorithms.py 中不存在
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
缺失:
|
|||
|
|
- `ILLEGAL_PARKING = "illegal_parking"` -- algorithms.py 已实现但枚举未添加
|
|||
|
|
- `VEHICLE_CONGESTION = "vehicle_congestion"` -- algorithms.py 已实现但枚举未添加
|
|||
|
|
|
|||
|
|
### 6.2 ROIInfo 默认值
|
|||
|
|
|
|||
|
|
`config_models.py` 的 `ROIInfo.confirm_leave_sec` 默认值为 **10**,而 `AlgorithmManager.default_params["leave_post"]["confirm_leave_sec"]` 为 **30**,`LeavePostAlgorithm.confirm_off_duty_sec` 默认为 **30**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. main.py 集成要点
|
|||
|
|
|
|||
|
|
### 7.1 main.py 对 algorithms.py 的依赖
|
|||
|
|
|
|||
|
|
1. **直接访问算法内部属性** (第905-911行):
|
|||
|
|
```python
|
|||
|
|
for attr in ('_leave_start_time', '_parking_start_time', '_congestion_start_time', '_intrusion_start_time'):
|
|||
|
|
val = getattr(algo, attr, None)
|
|||
|
|
```
|
|||
|
|
这是紧耦合,如果内部属性名变更会导致 first_frame_time 丢失。
|
|||
|
|
|
|||
|
|
2. **alarm_id 回填** (第943-945行):
|
|||
|
|
```python
|
|||
|
|
algo.set_last_alarm_id(alarm_info.alarm_id)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **两层去重机制**:
|
|||
|
|
- ROI级别: `_active_alarms[f"{roi_id}_{alert_type}"]`
|
|||
|
|
- 摄像头级别: `_camera_alert_cooldown[f"{camera_id}_{alert_type}"]` (30秒冷却)
|
|||
|
|
|
|||
|
|
4. **duration_ms 在 ext_data 中的计算** (第925行):
|
|||
|
|
```python
|
|||
|
|
"duration_ms": int(alert.get("duration_minutes", 0) * 60 * 1000) if alert.get("duration_minutes") else None,
|
|||
|
|
```
|
|||
|
|
仅 IllegalParkingAlgorithm 的告警包含 `duration_minutes`,其他算法的 ext_data.duration_ms 为 None。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附录: 状态机可视化
|
|||
|
|
|
|||
|
|
### LeavePostAlgorithm
|
|||
|
|
```
|
|||
|
|
+--> NON_WORK_TIME --+
|
|||
|
|
| (any state) | (in_working_hours)
|
|||
|
|
| v
|
|||
|
|
INIT --+--> CONFIRMING_ON_DUTY ---> ON_DUTY ---> CONFIRMING_OFF_DUTY
|
|||
|
|
^ | ^ |
|
|||
|
|
+----------+ | v
|
|||
|
|
(ratio==0) | OFF_DUTY_COUNTDOWN
|
|||
|
|
| | |
|
|||
|
|
| (roi_has_person) | v
|
|||
|
|
| +---------------+ ALARMED
|
|||
|
|
| | |
|
|||
|
|
+-------+ (roi_has_person) |
|
|||
|
|
ON_DUTY <-- (if _last_alarm_id, send resolve)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### IntrusionAlgorithm
|
|||
|
|
```
|
|||
|
|
IDLE ---> CONFIRMING_INTRUSION ---> ALARMED ---> CONFIRMING_CLEAR ---> IDLE
|
|||
|
|
^ | | |
|
|||
|
|
+------------+ +--------+
|
|||
|
|
(person gone) (person back >= confirm_intrusion_sec)
|
|||
|
|
-> ALARMED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### IllegalParkingAlgorithm
|
|||
|
|
```
|
|||
|
|
IDLE --> CONFIRMING_VEHICLE --> PARKED_COUNTDOWN --> ALARMED --> CONFIRMING_CLEAR --> IDLE
|
|||
|
|
^ | | |
|
|||
|
|
+----------+ | v
|
|||
|
|
(ratio<0.3) (ratio<0.2) | ALARMED
|
|||
|
|
^ | | (ratio>=0.5)
|
|||
|
|
+--------------------------+ v
|
|||
|
|
ALARMED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### VehicleCongestionAlgorithm
|
|||
|
|
```
|
|||
|
|
NORMAL --> CONFIRMING_CONGESTION --> CONGESTED --> CONFIRMING_CLEAR --> NORMAL
|
|||
|
|
^ | |
|
|||
|
|
+--------------+ v
|
|||
|
|
(avg<threshold) CONGESTED
|
|||
|
|
(avg>=threshold)
|
|||
|
|
```
|