本文博主再给你一个可直接落地的增强版脚本:在原有「区域人流统计」基础上,新增:
✅进出双向计数(进+1,出-1)
✅越界报警(非法闯入/离开触发)
✅轨迹方向判断(基于中心点穿越区域边界)
✅报警弹窗 + 日志保存
实现思路是:用 TrackZone 做区域过滤 + 自己维护每个ID的历史中心点 → 判断穿越方向。
一、实现原理(先看这个,代码更好懂)
TrackZone负责:
- 只在指定多边形区域内追踪目标
- 返回
track_ids+boxes
方向判断逻辑:
- 记录每个
track_id上一帧的中心点(x1, y1) - 当前帧中心点
(x2, y2) - 判断线段是否穿过区域某条边
- 根据穿越边的法向量判断是「进入」还是「离开」
- 记录每个
越界报警:
- 进入/离开时触发
- 画面红色高亮 + 控制台打印 + 日志文件
二、完整代码:进出双向统计 + 越界报警
✅ 依赖:
ultralytics,opencv-python,numpy
fromultralyticsimportYOLO,solutionsimportcv2importnumpyasnpfromdatetimeimportdatetime# ======================# 1. 基础配置# ======================MODEL_PATH="yolo26n.pt"VIDEO_PATH="video.mp4"# 0=摄像头OUTPUT_PATH="output_people_counting.mp4"# 监控区域(多边形,顺时针)region_points=np.array([(200,200),(1000,200),(1000,600),(200,600)],dtype=np.int32)# ======================# 2. 全局状态# ======================id_history={}# track_id -> 上一帧中心点enter_count=0exit_count=0ALARM_LOG="alarm.log"# ======================# 3. 工具函数# ======================defpoint_in_polygon(pt,polygon):"""判断点是否在多边形内"""returncv2.pointPolygonTest(polygon,pt,False)>=0defsegment_intersects_polygon(p1,p2,polygon):""" 判断线段p1->p2是否穿过polygon任意边 返回 True + 边的法向量方向 """foriinrange(len(polygon)):a=tuple(polygon[i])b=tuple(polygon[(i+1)%len(polygon)])# 线段ab(边)与线段p1p2相交ifcv2.clipLine(a,b,p1,p2)[0]:edge_vec=np.array(b)-np.array(a)normal=np.array([-edge_vec[1],edge_vec[0]])# 法向量returnTrue,normalreturnFalse,Nonedeflog_alarm(event,track_id):timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")line=f"[{timestamp}] ALARM:{event}| TrackID={track_id}\n"print(line.strip())withopen(ALARM_LOG,"a",encoding="utf-8")asf:f.write(line)# ======================# 4. 初始化 TrackZone# ======================trackzone=solutions.TrackZone(model=MODEL_PATH,region=region_points.tolist(),classes=[0],# 只统计人tracker="bytetrack.yaml",show=False,persist=True)# ======================# 5. 视频IO# ======================cap=cv2.VideoCapture(VIDEO_PATH)fps=int(cap.get(cv2.CAP_PROP_FPS))w=int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))h=int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))out=cv2.VideoWriter(OUTPUT_PATH,cv2.VideoWriter_fourcc(*"mp4v"),fps,(w,h))# ======================# 6. 主循环# ======================whilecap.isOpened():success,frame=cap.read()ifnotsuccess:breakresults=trackzone(frame)# TrackZone返回的boxes和idsboxes=results.boxes.xyxy.cpu().numpy()ids=results.boxes.id.cpu().numpy().astype(int)forbox,tidinzip(boxes,ids):x1,y1,x2,y2=map(int,box)cx,cy=(x1+x2)//2,(y1+y2)//2pt=(cx,cy)# 首次出现iftidnotinid_history:id_history[tid]=ptcontinueprev_pt=id_history[tid]id_history[tid]=pt# 判断是否穿越区域边界intersect,normal=segment_intersects_polygon(prev_pt,pt,region_points)ifintersectandnormalisnotNone:move_vec=np.array(pt)-np.array(prev_pt)direction=np.dot(move_vec,normal)ifdirection>0:enter_count+=1event="ENTER"else:exit_count-=1event="EXIT"log_alarm(event,tid)# 越界报警:红框高亮cv2.rectangle(frame,(x1,y1),(x2,y2),(0,0,255),3)cv2.putText(frame,f"ALARM:{event}",(x1,y1-10),cv2.FONT_HERSHEY_SIMPLEX,0.8,(0,0,255),2)else:# 正常追踪框cv2.rectangle(frame,(x1,y1),(x2,y2),(0,255,0),2)cv2.putText(frame,f"ID:{tid}",(x1,y1-10),cv2.FONT_HERSHEY_SIMPLEX,0.6,(0,255,0),2)# 绘制区域cv2.polylines(frame,[region_points],True,(255,0,0),2)# 统计信息cv2.putText(frame,f"In:{enter_count}Out:{exit_count}Total:{enter_count+exit_count}",(20,40),cv2.FONT_HERSHEY_SIMPLEX,1.2,(0,255,255),2)out.write(frame)cv2.imshow("People Counting & Intrusion Alarm",frame)ifcv2.waitKey(1)&0xFF==ord("q"):break# ======================# 7. 清理# ======================cap.release()out.release()cv2.destroyAllWindows()print("✅ 处理完成")print(f"进入人数:{enter_count}")print(f"离开人数:{abs(exit_count)}")print(f"当前在场人数:{enter_count+exit_count}")print(f"报警日志:{ALARM_LOG}")三、运行效果说明
| 功能 | 表现 |
|---|---|
| 进入区域 | 绿色框 +In +1 |
| 离开区域 | 绿色框 +Out -1 |
| 越界瞬间 | 红色高亮框 + ALARM提示 |
| 日志 | alarm.log自动记录 |
| 输出视频 | 带统计面板 + 报警标记 |
四、常见优化建议
| 场景 | 优化 |
|---|---|
| 斜向进出误判 | 改用射线法 + 方向角度阈值 |
| 多人密集 | 提高track_buffer,降低match_thresh |
| 夜间误报 | 加亮度判断 / 红外摄像头 |
| 只想统计“进入” | 删除 EXIT 分支即可 |