返回列表
2025年 2025-06 —— 2026-03 · 59 分钟

智能厂房人数统计与超员报警系统

基于单目视觉(YOLO + ByteTrack)的多路 RTSP 人数统计与超员报警系统,覆盖跨线差分计数、区域计数、房间多相机融合与 MQTT 上报

智能厂房人数统计与超员报警系统 — 技术文档

文档性质:面向实现方法的功能逻辑说明 覆盖范围:algorithms/ 算法模块、people_counter_W.py 计数引擎、main_z_chuangkou_image.py 主程序


一、总体技术方案

系统基于 单目视觉人数统计算法体系,通过多路 RTSP 网络摄像头采集画面,使用 YOLO 完成行人检测、ByteTrack 完成多目标跟踪,再结合”跨线计数(出入差分)“与”区域计数(逐帧存在人数)“两种统计手段,计算每个房间的实时人数。全程不依赖双摄像头/深度相机,人数与位置均靠单目相机像素坐标 → 世界坐标转换得到。

关键技术点:

环节采用方法作用
行人检测YOLO 模型(model.track,仅 person 类)输出检测框与跟踪 ID
目标跟踪ByteTrack(bytetrack.yaml,persist 跨帧保持 ID)同一人跨帧 ID 稳定,是过线计数的前提
像素→世界坐标单目测距:宽度估计法(相似三角形)或射线投影法(光心射线与地平面求交)得到人脚部世界坐标(米),供去重、补点、位置上报
人数统计跨线差分计数 / 区域逐帧计数 / 20 帧众数平滑见第三章算法详解
房间汇总增量式跟随(较大值更新 + 增量叠加)+ 检测点选取与随机补点多相机融合成房间总人数
上报HTTP 图片服务 + MQTT JSON 上报前端看板展示人数、照片、报警

二、系统分层与数据流

┌─────────────────────────────────────────────────────────────┐
│  main_z_chuangkou_image.py(UI / 数据组装 / 上报)          │
│   PyQt5 MainWindow ── PeopleCounterThread(主循环)            │
│        ├─ DataProcessor(0.1s 组装数据 + 超员判定 + 拍照)    │
│        ├─ MQTTPublisher(3s 发布 room / 订阅 model_room)     │
│        └─ HTTPServerManager(8080 图片静态服务)              │
└───────────────────────────┬─────────────────────────────────┘
                            │ 调用
┌───────────────────────────▼─────────────────────────────────┐
│  people_counter_W.py(计数引擎 RoomPeopleCounter)            │
│   每相机:CameraCaptureThread(取流) ─ CameraInferenceManager   │
│           (YOLO.track → 线程池异步 → 算法.process/draw)       │
│   每房间:按算法分发到 room_statistics 的统计器汇总人数         │
└───────────────────────────┬─────────────────────────────────┘
                            │ process
┌───────────────────────────▼─────────────────────────────────┐
│  algorithms/(算法工厂按配置 type 动态加载)                   │
│   yolo_detection │ coordinate_detection │ cross_line_detection│
│   cross_line_region_detection │ region_counter                │
│   (底层算法) ──► room_statistics(房间级汇总统计器)          │
└─────────────────────────────────────────────────────────────┘

数据流单向:RTSP 帧 → 跳帧存储 → YOLO 检测跟踪 → 坐标转换 → 过线/区域判定 → 房间统计器融合 → 全厂汇总/内外比对 → 超员判定 → MQTT 发布。


三、算法模块详解(algorithms/)

3.1 世界坐标转换(coordinate_detection.py)

解决的问题:单目相机无法直接测距,需要利用相机标定参数把像素坐标映射为真实房间坐标(米)。

用到的参数与方法

  • 相机内参矩阵 K、畸变系数 D、去畸变后新内参 new_K、相机到世界变换矩阵 camera2world(R、t)。
  • 像素点先经 cv2.undistortPoints 做畸变校正,消除广角镜头桶形畸变造成的定位偏移。

两种测距模型(按配置 method 选择)

  1. 宽度估计法(width):假设行人肩宽(real_width_m,默认 0.5 m)在世界中恒定,检测框的水平像素宽度与真实宽度成正比,利用小孔成像相似三角形 depth = 真实宽度 × 焦距 / 像素宽度 求出深度,再把相机系下的脚部点经 camera2world 变换得到世界坐标。优点:实现简单、无地面假设。
  2. 射线投影法(ray):以检测框底部中心(脚部)或框中心为起点,经反投影得到相机坐标系下的射线方向,旋转到世界系后与世界平面(地面 z=0,或按 target_height 指定高度)求交点,交点即人脚在世界系的位置。优点:符合透视几何,适合俯视/斜视安装,需知道相机离地高度。

输出检测点结构{class, conf, xyz_m(世界xyz米), xy(平面x,y), pixel_center, method, on_ground},供后续统计与补点使用。

Coordinate_detection 包装类:逐帧对每个检测框做上述转换,并对世界坐标做边界修正——超出房间长宽(room_length/room_width)的坐标直接截断到边界(此文件为截断式;跨线类算法文件中为”随机拉回”式,见 3.2)。不产生过线计数,返回检测点列表。

3.2 跨线检测算法(cross_line_detection.py,类 Cross_line_detection

思想:在画面中定义一条(或多条)虚拟”检测线”,把线两侧定义为 side1/side2。行人跟踪 ID 连续的两帧连线若与检测线相交,且跨越前后所在侧发生变化,则判定为一次有效”过线”,按方向给计数 +1/-1。这种方式记录的是进入与离开的净流量(室内人数增量)。

核心状态(按相机实例持有)

  • line_counters:每条线的累计净出入数,全房间人数 = 各线计数之和(通常单线)。
  • track_history:每个 track_id 记录”上一帧脚部位置 (x,y)、每一条线的历史所在侧、最后更新时间”,并限制最多保留 10 个 ID 防内存增长。
  • crossed_tracks:记录 (track_id, line_idx),防止同一目标在同一帧内跨越多条线被重复计数。

单帧处理流程(process)

  1. 取坐标转换器,清空上帧世界坐标结果,清空 crossed_tracks,区域计数归零。
  2. 对每个检测框:算底边中心(脚部像素点)→ 转世界坐标 → 读跟踪 ID。
  3. 若该 ID 首次出现则写入 track_history(首帧不计数,只建基线,避免误判)。
  4. 过线判定:用上一帧脚点与本帧脚点连成线段,与每条检测线做线段相交判断(向量叉积符号 + 共线端点特判,即跨立实验);相交后再用叉积符号确定目标当前在线的哪一侧,与历史侧比对:side2→side1 记 +1(进入),side1→side2 记 -1(离开)。只有两侧发生翻转且该 (track_id, line) 本帧未计数过才生效。
  5. 区域过滤:使用 shapely 多边形 contains(Point(脚点)) 判断人是否在配置的检测区域内,仅区域内的检测才计入输出,区域外的不计(用于把检测限制在门口/指定通道)。
  6. 边界随机修正:世界坐标越出房间边界时,向房间内随机偏移 0.2~1.0 m 拉回,避免点贴墙造成位置上报异常。
  7. 随机检测点生成:由于”区域计数”只统计多边形内的人,而跨线差分可能给出比当帧可见人数更大的值,算法以房间配置的 random_x/random_y 为中心生成 3×3 网格(间距 0.5 m) 的 9 个随机补充点(track_id 从 101 起),供房间统计器在检测点不足时”凑足人数”,保证 UI/上报的可见点数量与人数一致。
  8. 输出:{detections, random_detections, room_total_count(过线净人数), reset_triggered, room_id, camera_id}

重置机制(reset):重置相机计数时,把所有过线计数器清零、清空 track 与 crossed 记录,并置 reset_triggered=Truereset_signal_counter=10,让后续连续 10 帧返回重置信号;房间统计器收到该信号会把”上一帧过线人数基线”同步为当前值、增量置 0,避免清零瞬间产生虚假的大幅差分。

3.3 跨线 + 区域检测算法(cross_line_region_detection.py,类 Cross_line_region_detection

在 3.2 全部逻辑的基础上,增加三点改进:

  1. 重叠框 NMS:处理前先对检测结果做自定义 NMS(按置信度排序,IoU>0 且”并集/较大框面积比 < 1.25”判为重叠并抑制较小框),减少同一个人被重复检测导致的多余轨迹/错误计数。
  2. 区域人数输出:每帧统计落在区域内目标的去重集合大小 region_counters(按 track_id 去重),作为”当前区域可见人数”一并输出,供房间统计器与过线人数交叉验证。
  3. 零人超时自复位:当 region_counters == 0 持续 30 秒(记录 region_zero_start_time)时,判定房间无人,自动清零所有过线计数、清空跟踪记录,并置 reset_triggered=Truereset_signal_counter=20(连续 20 帧返回重置信号)。这解决”有人一直坐着不动 → 跟踪丢失 → 过线计数长期不变甚至为负”的漂移问题,确保清场后计数归零。
  4. 其余过线判定、随机点、边界修正同 3.2。

3.4 纯区域计数算法(region_counter.py,类 Region_counter

适用:摄像头正对无需区分进出方向的区域(如车间内部),只统计”画面里此刻有几个人”。

  • 仅保留”区域”配置(不解析检测线)。
  • 逐帧对每个检测框做多边形包含判断(shapely contains 脚点),区域内目标 ID 写入集合,区域人数 = 集合大小(每帧独立统计,无跨帧累计、无连续帧确认逻辑,也未启用随机补点)。
  • 输出 {detections, region_counters(去重区域人数), random_detections:{}},不含过线值与重置信号。
  • 缺点:被遮挡/短暂离场的人会被漏计,因此房间统计层通常搭配众数平滑使用(见 3.6 的 RegionCounter)。

3.5 纯 YOLO 检测算法(yolo_detection.py,类 Yolo_detection

定位:早期/演示用算法,不做区域与过线过滤,只做”检测 + 轨迹平滑可视化”:

  • 每目标保留最近 10 个中心点做移动平均平滑,抑制抖动;同时保留最近 30 点用于绘制渐变轨迹线 + 方向箭头
  • 人数 = 当前帧全部检测框数(不区分是否在区域内)。
  • 边界修正为截断式。输出不含 room_total_count / random_detections(置 0 或空)。

3.6 房间统计汇总(room_statistics.py)——多相机融合核心

这是算法层到”房间人数”的关键一层。单个相机的算法输出只是一路画面结果,一个房间通常有多台相机(各守一个门口/区域),需要一套房间级统计器把多路结果融合成该房间当前总人数,并返回一组用于展示/上报的”检测点”。

系统内三个统计器与三种房间算法一一对应:

(a) Cross_Line_DetectionCounter —— 过线差分式房间统计

  • 维护两级状态:房间状态 {total_people};每相机状态 {last_cross_line_people, delta_cross}
  • 逐相机计算增量delta = 本帧过线人数 − 上帧过线人数(进入为 +,离开为 −)。若该相机算法返回 reset_triggered 信号(底层刚被清零),则把基线 last 直接同步为当前值、delta 置 0,屏蔽清零假增量
  • 总人数更新策略(房间级):
    • 先取”各相机过线人数之和”作为当前测量值;
    • 若测量值 > 当前总人数 → 直接用较大值覆盖(向上跟随,防止漏检导致的低估);
    • 否则总人数 += 各相机 delta 之和(差分维持,人员离场时靠负增量下降);
    • 结果钳制不小于 0。
  • 检测点选取与随机补点:将需要的人数与真实检测点(含各相机)匹配——按世界 xy 距离 > 0.3 m 去重后逐个选取;点数不足时用各相机生成的随机网格点补足,直到与总人数一致,使上报的可见点数量等于房间人数。

(b) Cross_Line_Region_DetectionCounter —— 过线+区域融合式房间统计

在 (a) 的相机增量计算基础上增加区域交叉验证与超时归零

  • 额外累计各相机 region_counters(区域可见人数)之和;
  • 只要区域人数或过线人数 > 0,就认为”房内有人”,清零零人计时;全部为 0 时开始计时,连续为 0 满 60 秒则将房间总人数强制归 0(防累计误差残留);
  • 总人数更新:区域人数和作为”较大值跟随”的依据,否则叠加过线增量(同 (a))。

(c) RegionCounter —— 众数平滑式房间统计(用于纯区域算法)

  • 每个房间维护一个最近 20 帧的滑动窗口(deque,存每帧的区域人数与当时的检测点)。
  • 对窗口内 20 个区域人数值取众数(出现最多的值;多个众数并列时取最小值)作为输出人数。
  • 检测点则从窗口内由新到旧找到第一个”人数 = 众数”的那一帧,取该帧检测点作为输出,保证人数与点集合自洽。
  • 目的:区域计数容易因遮挡/抖动单帧突变,取众数可平滑毛刺、稳定输出。

兜底方法 _default_count_people:当数据中取不到 room_id 时,直接用真实检测点数作为人数,不足 10 人时用随机点补到 10。

小结:三种统计思路对比

统计器数据来源上升方式下降方式抗漂移手段
Cross_Line_DetectionCounter相机过线净人数较大值覆盖过线负增量重置信号屏蔽假增量
Cross_Line_Region_DetectionCounter过线 + 区域人数区域较大值覆盖过线负增量全零 60s 归零
RegionCounter区域可见人数20 帧众数众数自然下降众数平滑

3.7 历史脚本(tongjibeifen.py)

早期备份版本:房间总人数 = 各相机 room_total_count(过线净人数)之和,负数钳 0,直接作为最终人数;无增量/重置/超时等容错,已不再使用。


四、计数引擎 people_counter_W.py

4.1 配置加载(ConfigLoader)

读取 JSON 配置文件,校验必须包含 rooms(房间→相机树)与 model_config(模型权重等)。test.json 为实际加载文件。

4.2 帧缓冲(FrameBuffer)

线程安全环形双缓冲(容量 2):采集线程 add_frame 覆盖写,消费方 get_latest_frame 只取最新一帧并标记已消费。避免推理积压导致画面越来越”旧”。

4.3 算法工厂(AlgorithmFactory)

按房间配置的算法 type 字符串,用 importlib 动态加载 algorithms.<type> 模块中同名大写类并实例化(如 cross_line_detectionCross_line_detection)。新增算法只需在 algorithms 下加文件并在配置里写 type,无需改引擎代码。

4.4 相机采集线程(CameraCaptureThread)

  • 每相机一个线程,cv2.VideoCapture(FFMPEG 后端)拉取 RTSP 流,设置小缓冲、读/连接超时与硬件加速。
  • 断线自愈:读帧失败连续多次即释放句柄,按 5 秒间隔自动重连。
  • 跳帧降载:每 2 帧只存 1 帧(约降到 12.5 fps 入库),有效降低后端推理与带宽压力;统计实际存储帧率供日志监控。

4.5 相机推理管理器(CameraInferenceManager)——每相机一管推理流水线

  • 每相机独立加载一个 YOLO 模型实例,对最新帧执行 model.track(classes=[0], conf=0.6, iou=0.6, persist=True, tracker="bytetrack.yaml"),一次调用同时完成检测与跟踪。
  • 两级线程池异步流水线(共享引擎级线程池,避免每帧同步阻塞主线程):
    1. tracking_executor 提交跟踪;
    2. 跟踪完成回调 _tracking_done_callback 再把结果提交到 processing_executor 执行算法 process()draw()
    3. 结果写回 algorithm_detections / random_detections / algorithm_data(含 room_total_count、region_counters、reset_triggered 等)与显示缓冲。
  • 单帧串行锁:每相机用 processing 标志 + 锁保证同一时刻只处理一帧,天然削峰防积压。
  • 提供查询接口给上层(get_detections / get_random_detections / get_algorithm_data / get_display_frame),并支持 reset_algorithm_state()(调用算法的 reset,用于系统级清零)。
  • 停止时释放模型、清缓冲。

4.6 显示管理(DisplayManager)

以约 30 fps 频率轮询各相机的显示缓冲,用 cv2 窗口展示(每相机独立窗口,标题 房间/相机);无帧时显示”WAITING FOR FRAME”占位并复用缓存帧。

4.7 房间人数总控(RoomPeopleCounter)

  • 模型预热:启动时用 3 次纯黑 dummy 帧跑一遍 track,完成 CUDA 图/算子预热并清显存,避免首帧卡顿。
  • 线程池规模:跟踪池 = 2×CPU 核数,处理池与房间计算池 = CPU 核数。
  • 相机初始化:遍历配置 rooms,为每个房间的每台相机创建采集线程 + 推理管理器(算法类型来自房间配置)。
  • 房间统计入口 calculate_room_occupancy:将各房间提交到房间线程池并行计算_process_room_occupancy),各房间内部先收集所有相机的算法数据,再按房间算法类型分发到 3.6 中对应的统计器,得到 (房间人数, 检测点集合, 相机数据);主线程对每个房间计算与上一轮的人数差分 change_count/change_status(进 +1 / 出 0),汇总成整体 JSON。
  • 内外人数比对校正 people_comparerooms.total(全厂汇总房间,代码称”外部”)与各普通房间之和(“内部”)分别统计;若内部 > 外部,说明某房间多算了人,则按三重排序(track_id 降序 → camera 编号降序 → room 编号降序)优先删除”新出现的高 ID 目标”,直到内部人数 = 外部人数,并把总人数修正为外部值。防止跟踪碎片导致虚增。
  • 全系统零人重置 _check_and_reset_if_needed:当内部总人数持续为 0 达阈值(默认 3600 秒)时调用 reset_system_counters(),对所有相机的算法、上一帧人数基线与三个统计器统一清零,保证次日/长时间空场后计数不残留。

五、主程序 main_z_chuangkou_image1.py

5.1 运行线程(PeopleCounterThread,QThread)

  • 启动顺序固定:HTTP 图片服务 → 计数引擎 RoomPeopleCounter(含取流与推理线程)→ DataProcessor → MQTTPublisher,保证图片可访问后再开始拍照与上报。
  • 主循环每 10 ms 遍历所有相机的推理管理器执行 update()(触发跟踪-处理流水线);画面以 0.1 s 为间隔通过 Qt 信号 frame_updated 推给 UI,避免信号队列积压。
  • safe_cleanup 反向安全停止:先停 MQTT → DataProcessor → RoomPeopleCounter → HTTP 服务,最后清 GPU 显存;配合 cleanup_complete 事件保证线程完全退出。

5.2 数据处理线程(DataProcessor._process_data_loop,每 0.1 s)

  • counter.calculate_room_occupancy() + people_compare() 取房间数据;随后做每日清理检查(按 90 天滚动删除日期目录与入口照片)。
  • 超员判定:总人数 > 容量上限(来自配置文件或 MQTT 动态修改,线程安全读写)后开始计时,连续超限 ≥ 1 秒才置 factory_error_flag=true(报警带 1 秒去抖);人数回落即解除并记录恢复时刻。
  • 位置数据组包:把每个检测点映射为 user_id = G0519_{room}_{camera}_usr{track_id},携带当前世界坐标 (x,y)(保留 2 位小数)与上一次坐标(remain,用于前端画移动轨迹/区分静止人),实现”逐人逐点上报”。
  • 拍照标志置位:超限期间按 capture_interval(0.1 s)周期置 need_capture(由发布线程消费);同时缓存各路相机最新帧到 camera_frame_cache(拍照时优先取缓存,避免阻塞推理线程)。
  • 组装 full_data(timestamp / workshop / capacity / user_count / error_flag / rooms 明细 / capture_ready / entrance_image_urls),写入加锁共享的最新数据区。

5.3 拍照取证系统

  • 普通房间拍照 capture_images:取最新数据找出 user_count>0 的房间,逐个房间调用 capture_room_all_cameras——对房间内每台相机,帧来源优先级:帧缓存 → 推理管理器显示缓冲 → 重试接口(最多 3 次);成功后经 generate_image 写入 images/YYYYMMDD/room_cam_时间戳.jpg 并生成可访问 URL,存入该房间 room_last_image_urls 供 MQTT 挂载。capture_ready 标志标记本次拍照动作完成。
  • 入口摄像头拍照 capture_entrance_cameras:对象为配置 rooms.total.cameras(门口机),帧同样优先缓存、其次重试;图片写入独立目录 images/entrance_cameras/total_cam_时间戳.jpg,URL 挂入 entrance_last_image_urls
  • 触发策略(数量变化触发,非周期连拍)_process_data_loop 检测到总人数与上轮不同,且处于”超员相关状态”(当前超员,或刚从超员恢复且在 10 秒恢复窗口内)时置 entrance_need_capture;由发布线程消费执行拍照。正常进出(0→1、1→2)不拍,避免磁盘膨胀。

5.4 MQTT 发布器(MQTTPublisher)

  • 每 3 秒向话题 room 发布 JSON(发布前先消费两个拍照标志:超员周期拍照与入口人数变化拍照,拍完重取一次最新数据,保证照片 URL 随当次上报)。
  • 订阅 model_room:收到 {"value": N} 即在线更新容量上限,无需重启。
  • 发布后记录每个 user_id 的当前坐标作为下次的 remain。

5.5 HTTP 图片服务(HTTPServerManager)

基于 ThreadingHTTPServer + SimpleHTTPRequestHandler 提供图片静态访问,根目录指向 images/;端口 8080 起自动探测空闲端口(绑定成功即用),URL 形如 http://IP:PORT/YYYYMMDD/xxx.jpg/entrance_cameras/xxx.jpg

5.6 PyQt5 界面(MainWindow / RoomWidget / CameraDisplayWindow)

  • 主窗口含启动/停止按钮、状态栏、各房间人数卡片(RoomWidget 展示房间名/人数/容量/超限红色告警 UI)、相机画面缩略与弹窗(CameraDisplayWindow 每 0.1 s 收 frame_updated 信号刷新)。
  • 支持”自动模式”:启动后不经点击直接进入运行;通过定时器每秒监控线程状态,异常时提示并在主线程中安全重启。

六、关键运行参数

参数位置含义
capacity配置文件顶层1全厂容量上限(超员报警阈值)
over_capacity_durationDataProcessor1 s连续超限去抖时长
capture_intervalDataProcessor0.1 s超员期间普通房间拍照最小间隔
entrance_recover_send_windowDataProcessor10 s恢复后入口照片仍下发窗口
publish_intervalMQTTPublisher3 s发布周期(含拍照消费周期)
conf / iouCameraInferenceManager0.6 / 0.6YOLO 检测置信度与 IoU
zero_count_thresholdRoomPeopleCounter3600 s全零触发系统重置阈值
区域零人复位时长cross_line_region30 s底层过线计数自复位
房间统计零人归零room_statistics60 s过线+区域统计器归零等待
众数窗口RegionCounter20 帧区域人数平滑窗口
随机补点网格跨线类算法3×3 / 间距0.5m补足检测点与人数一致
HTTP 端口HTTPServerManager8080+图片服务(自动找空闲)
MQTTMQTTPublisher1883 / 话题 room、model_room数据上报与容量下发

七、设计要点与容错机制汇总

  1. 双向计数:跨线差分天然支持”进加出减”,配合较大值覆盖避免检测漏帧导致的低估。
  2. 两级抗漂移:底层相机级”区域 0 人超时自复位”,顶层房间级”全零 3600 s 系统重置”,双保险防止长跑后计数残留。
  3. 重置信令同步:清零瞬间通过连续多帧 reset_triggered 信号通知统计器同步基线、屏蔽假增量,这是本系统人数不跳变的关键设计。
  4. 点补足机制:检测点不足时用固定网格随机点补齐,保证”人数 = 可见点数”的自洽性,便于前端展示。
  5. 异步流水线 + 单帧锁:跟踪与后处理分属两个线程池,每相机单帧串行,兼顾吞吐与有序性。
  6. 内外比对校正:以全厂汇总(外部)为准修剪内部多余点,抑制跟踪碎片导致的虚增。
  7. 拍照降频:入口拍照由”周期连拍”改为”人数变化 + 超员相关状态”触发,避免磁盘膨胀;90 天自动清理兜底。