Files
Security_AI_integrated/detector.py

218 lines
8.4 KiB
Python
Raw Normal View History

2026-01-12 17:38:39 +08:00
import cv2
import numpy as np
from ultralytics import YOLO
from sort import Sort
import time
import datetime
import threading
import queue
import torch
from collections import deque
class ThreadedFrameReader:
def __init__(self, src, maxsize=1):
self.cap = cv2.VideoCapture(src, cv2.CAP_FFMPEG)
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self.q = queue.Queue(maxsize=maxsize)
self.running = True
self.thread = threading.Thread(target=self._reader)
self.thread.daemon = True
self.thread.start()
def _reader(self):
while self.running:
ret, frame = self.cap.read()
if not ret:
time.sleep(0.1)
continue
if not self.q.empty():
try:
self.q.get_nowait()
except queue.Empty:
pass
self.q.put(frame)
def read(self):
if not self.q.empty():
return True, self.q.get()
return False, None
def release(self):
self.running = False
self.cap.release()
def is_point_in_roi(x, y, roi):
return cv2.pointPolygonTest(roi, (int(x), int(y)), False) >= 0
class OffDutyCrowdDetector:
def __init__(self, config, model, device, use_half):
self.config = config
self.model = model
self.device = device
self.use_half = use_half
# 解析 ROI
self.roi = np.array(config["roi_points"], dtype=np.int32)
self.crowd_roi = np.array(config["crowd_roi_points"], dtype=np.int32)
# 状态变量
self.tracker = Sort(
max_age=30,
min_hits=2,
iou_threshold=0.3
)
self.is_on_duty = False
self.on_duty_start_time = None
self.is_off_duty = True
self.last_no_person_time = None
self.off_duty_timer_start = None
self.last_alert_time = 0
self.last_crowd_alert_time = 0
self.crowd_history = deque(maxlen=1500) # 自动限制5分钟假设5fps
self.last_person_seen_time = None
self.frame_count = 0
# 缓存配置
self.working_start_min = config.get("working_hours", [9, 17])[0] * 60
self.working_end_min = config.get("working_hours", [9, 17])[1] * 60
self.process_every = config.get("process_every_n_frames", 3)
def in_working_hours(self):
now = datetime.datetime.now()
total_min = now.hour * 60 + now.minute
return self.working_start_min <= total_min <= self.working_end_min
def count_people_in_roi(self, boxes, roi):
count = 0
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
if is_point_in_roi(cx, cy, roi):
count += 1
return count
def run(self):
"""主循环:供线程调用"""
frame_reader = ThreadedFrameReader(self.config["rtsp_url"])
try:
while True:
ret, frame = frame_reader.read()
if not ret:
time.sleep(0.01)
continue
self.frame_count += 1
if self.frame_count % self.process_every != 0:
continue
current_time = time.time()
now = datetime.datetime.now()
# YOLO 推理
results = self.model(
frame,
imgsz=self.config.get("imgsz", 480),
conf=self.config.get("conf_thresh", 0.4),
verbose=False,
device=self.device,
half=self.use_half,
classes=[0] # person class
)
boxes = results[0].boxes
# 更新 tracker可选用于ID跟踪
dets = []
for box in boxes:
x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
conf = float(box.conf)
dets.append([x1, y1, x2, y2, conf])
dets = np.array(dets) if dets else np.empty((0, 5))
self.tracker.update(dets)
# === 离岗检测 ===
if self.in_working_hours():
roi_has_person = self.count_people_in_roi(boxes, self.roi) > 0
if roi_has_person:
self.last_person_seen_time = current_time
# 入岗保护期
effective_on_duty = (
self.last_person_seen_time is not None and
(current_time - self.last_person_seen_time) < 1.0
)
if effective_on_duty:
self.last_no_person_time = None
if self.is_off_duty:
if self.on_duty_start_time is None:
self.on_duty_start_time = current_time
elif current_time - self.on_duty_start_time >= self.config.get("on_duty_confirm", 5):
self.is_on_duty = True
self.is_off_duty = False
self.on_duty_start_time = None
print(f"[{self.config['id']}] ✅ 上岗确认")
else:
self.on_duty_start_time = None
self.last_person_seen_time = None
if not self.is_off_duty:
if self.last_no_person_time is None:
self.last_no_person_time = current_time
elif current_time - self.last_no_person_time >= self.config.get("off_duty_confirm", 30):
self.is_off_duty = True
self.is_on_duty = False
self.off_duty_timer_start = current_time
print(f"[{self.config['id']}] ⏳ 开始离岗计时")
# 离岗告警
if self.is_off_duty and self.off_duty_timer_start:
elapsed = current_time - self.off_duty_timer_start
if elapsed >= self.config.get("off_duty_threshold", 300):
if current_time - self.last_alert_time >= self.config.get("alert_cooldown", 300):
print(f"[{self.config['id']}] 🚨 离岗告警!已离岗 {elapsed/60:.1f} 分钟")
self.last_alert_time = current_time
# === 聚集检测 ===
crowd_count = self.count_people_in_roi(boxes, self.crowd_roi)
self.crowd_history.append((current_time, crowd_count))
# 动态阈值
if crowd_count >= 10:
req_dur = 60
elif crowd_count >= 7:
req_dur = 120
elif crowd_count >= 5:
req_dur = 300
else:
req_dur = float('inf')
if req_dur < float('inf'):
recent = [(t, c) for t, c in self.crowd_history if current_time - t <= req_dur]
if recent:
valid = [c for t, c in recent if c >= 4]
ratio = len(valid) / len(recent)
if ratio >= 0.9 and (current_time - self.last_crowd_alert_time) >= self.config.get("crowd_cooldown", 180):
print(f"[{self.config['id']}] 🚨 聚集告警!{crowd_count}人持续{req_dur//60}分钟")
self.last_crowd_alert_time = current_time
# 可视化(可选,部署时可关闭)
if True: # 设为 True 可显示窗口
vis = results[0].plot()
overlay = vis.copy()
cv2.fillPoly(overlay, [self.roi], (0,255,0))
cv2.fillPoly(overlay, [self.crowd_roi], (0,0,255))
cv2.addWeighted(overlay, 0.2, vis, 0.8, 0, vis)
cv2.imshow(f"Monitor - {self.config['id']}", vis)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except Exception as e:
print(f"[{self.config['id']}] Error: {e}")
finally:
frame_reader.release()
cv2.destroyAllWindows()