监控目标检测项目:时间戳、电梯楼层与 ArcDetect 封装
编写时间:2023-07
这个项目的场景很具体:监控视频里检测时间戳和电梯楼层显示区域。输入是 YUV 帧,输出是数字所在位置的 ROI 框,框大小不要求非常精确,但必须稳定、可部署。
任务定义
| 项目 | 内容 |
|---|---|
| 目标 | 时间戳、电梯楼层显示器、监控文字区域 |
| 输入 | YUV 格式视频帧 |
| 输出 | ROI 坐标框 |
| 分辨率 | 1920x1080、1280x720、704x576、640x360 |
| 端侧 | Jetson NX / RK3588 |
为什么不直接做 OCR
最早想过门牌号、仪表盘、文本检测网络,甚至可以考虑 OCR。但真实需求只是定位数字区域,不需要识别具体内容。这样目标检测更简单,部署也更稳。
任务判断是:时间戳相对容易,难点在电梯楼层显示器。电梯显示器字体、亮度、反光、拍摄角度都不稳定。如果数字检测不稳,可以先检测整个显示器框。
数据集生成思路
原始监控视频自带时间戳,所以需要先裁掉原有时间戳,再生成合成样本。这个流程比单纯标注更划算。
生成时考虑这些扰动:
| 扰动 | 示例 |
|---|---|
| 格式 | 年月日、英文、斜杠、横杠 |
| 颜色 | 黑、白、绿、红、灰、蓝、黄 |
| 尺寸 | 随机大小 |
| 位置 | 四周概率更大,避免 padding 区域 |
| 背景 | 半透明黑底、不透明黑底、无底 |
| 文本 | 地名、楼栋、camera 标识等干扰项 |
模型迭代记录
2023-06-21 到 2023-06-29 的过程可以浓缩成几步:先尝试数字相关数据集和文本检测网络,再转向 YOLOv8 自建数据集;当 1280 输入仍然框不全时,判断为模型容量不足;最后 YOLOv8m 在 640 尺寸上效果已经比较好,偶发错判可以通过当前帧最高置信度或类别规则修正。
工程封装
ArcDetect_Timestamps 的核心不是训练,而是把 demo 改成模块:初始化、YUV 处理、推理、ROI 输出都要独立。这样上层业务只需要给一帧数据和宽高,拿回结构体。
接口设计
对外只暴露三个方法,抹平 RK 和 Jetson 两套推理后端的差异:
class ArcDetect {
virtual void init(model_path, width, height) = 0;
virtual void process(yuv_frame, width, height, frame_roi) = 0;
virtual void free() = 0;
static ArcDetect* create(); // 工厂方法,按平台返回 RK 或 TRT 实现
};create() 通过条件编译(#ifdef PLATFORM_RK / PLATFORM_JETSON)自动选择后端,调用方不需要知道当前跑在哪块板子上。
数据结构
ROI 输出是一个简单的 C 结构体数组,方便上层直接取用:
typedef struct roi_info {
int left, top, right, bottom; // 像素坐标
int roi_type; // 0: 时间戳 1: 标签
int roi_id; // ROI 序号
} ROI_Info;
typedef struct _frame_roi {
int nNumRoi; // 本帧检测到的 ROI 数量
ROI_Info* pRoi; // 动态数组
} Frame_ROI;调用链路
上层业务的典型调用模式非常短:
设计要点
- 平台无关:工厂方法 + 纯虚接口,切换板子只需重新编译,调用代码一行不改。
- YUV 直入:不做格式转换暴露给调用方,内部统一处理 YUV→RGB,减少上层的心智负担。
- 单帧无状态:
process()不保留帧间状态,每次调用独立,天然适合视频流的逐帧处理。 - 生命周期明确:
init → process → free三步,资源分配和释放完全对称。
工程边界
测试样例路径、设备信息和内网定位只作为调试上下文,不参与算法与工程复盘。文章主体只保留任务方法、数据生成和接口封装。