prompt优化、ROI更新

This commit is contained in:
2026-01-20 11:39:23 +08:00
parent 35a1d1ae50
commit 232a3827d4
2 changed files with 146 additions and 35 deletions

View File

@@ -13,7 +13,7 @@ llm:
common:
# 工作时间段:支持多个时间段,格式为 [开始小时, 开始分钟, 结束小时, 结束分钟]
# 8:30-11:00, 12:00-17:30
working_hours:
working_hours:
- [8, 30, 11, 0] # 8:30-11:00
- [12, 0, 17, 30] # 12:00-17:30
process_every_n_frames: 3 # 每3帧处理1帧用于人员离岗

View File

@@ -33,11 +33,19 @@ def save_alert_image(frame, cam_id, roi_name, alert_type, alert_info=""):
os.makedirs(alert_dir, exist_ok=True)
# 生成文件名:摄像头ID_ROI名称_时间戳.jpg
# 生成文件名:根据告警类型使用不同的命名方式
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
# 清理文件名中的特殊字符
safe_roi_name = roi_name.replace("/", "_").replace("\\", "_").replace(":", "_")
filename = f"{cam_id}_{safe_roi_name}_{timestamp}.jpg"
# 对于入侵告警,使用告警类型+ROI名称对于离岗告警使用ROI名称
if alert_type == "入侵":
# 周界入侵:使用"入侵_区域名称"格式
filename = f"{cam_id}_入侵_{safe_roi_name}_{timestamp}.jpg"
else:
# 离岗:使用原有格式
filename = f"{cam_id}_{safe_roi_name}_{timestamp}.jpg"
filepath = os.path.join(alert_dir, filename)
# 保存图片
@@ -139,12 +147,12 @@ class LLMClient:
请根据以下规则生成结构化响应:
### 【判定标准】
✅ **本单位物业员工**需同时满足:
1. **清晰可见的正式工牌**(胸前佩戴,含照片/姓名/公司LOGO
2. **穿着标准制服**(如:黄蓝工程服、白衬衫+黑领带、蓝色清洁装、浅色客服装等)
✅ **本单位物业员工**需满足下列条件之一
1. **清晰可见的正式工牌**(胸前佩戴)
2. **穿着标准制服**(如:带有白色反光条的深色工程服、黄蓝工程服、白衬衫+黑领带、蓝色清洁装、浅色客服装等)
3. **行为符合岗位规范**(如巡检、维修、清洁,无徘徊、张望、翻越)
> ⚠️ 仅满足部分条件(如有安全帽、仅有类似颜色衣服、工牌不可见)→ 视为员工。
> 注意: 满足部分关键条件(如有安全帽、穿有工作人员服饰、带有工牌)→ 视为员工,不生成告警
### 【输出规则】
#### 情况1ROI区域内**无人**
@@ -174,11 +182,11 @@ class LLMClient:
▶ 示例2员工
🟢无异常:检测到本单位工作人员正常作业。
1名工程人员穿黄蓝工服佩戴工牌在高配间巡检。
1名工程人员穿带有反光条的深蓝色工服在高配间巡检。
▶ 示例3非员工
🚨天台区域入侵告警:检测到疑似非工作人员,请立即核查。
1人穿色外套未佩戴工牌进入天台区域并短暂停留
1人穿绿色外套未佩戴工牌进入天台区域。
---
请分析摄像头{cam_id}{roi_name}区域,按照上述格式输出结果。"""
@@ -308,25 +316,53 @@ class ThreadedFrameReader:
def __init__(self, cam_id, rtsp_url):
self.cam_id = cam_id
self.rtsp_url = rtsp_url
self.cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self._lock = threading.Lock() # 添加锁保护VideoCapture访问
self.cap = None
try:
self.cap = cv2.VideoCapture(rtsp_url, cv2.CAP_FFMPEG)
if self.cap.isOpened():
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
else:
print(f"[{cam_id}] 警告:无法打开视频流: {rtsp_url}")
except Exception as e:
print(f"[{cam_id}] 初始化VideoCapture失败: {e}")
self.q = queue.Queue(maxsize=2)
self.running = True
self.thread = threading.Thread(target=self._reader, 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 self.q.full():
try:
self.q.get_nowait()
except queue.Empty:
pass
self.q.put(frame)
"""读取帧的线程函数"""
try:
while self.running:
with self._lock:
if self.cap is None or not self.cap.isOpened():
break
ret, frame = self.cap.read()
if not ret:
time.sleep(0.1)
continue
if self.q.full():
try:
self.q.get_nowait()
except queue.Empty:
pass
self.q.put(frame)
except Exception as e:
print(f"[{self.cam_id}] 读取帧线程异常: {e}")
finally:
# 确保资源释放(使用锁保护)
with self._lock:
if self.cap is not None:
try:
if self.cap.isOpened():
self.cap.release()
except Exception as e:
print(f"[{self.cam_id}] 释放VideoCapture时出错: {e}")
finally:
self.cap = None
def read(self):
if not self.q.empty():
@@ -334,8 +370,26 @@ class ThreadedFrameReader:
return False, None
def release(self):
"""释放资源,等待线程结束"""
if not self.running:
return # 已经释放过了
self.running = False
self.cap.release()
# 等待线程结束最多等待3秒
if self.thread.is_alive():
self.thread.join(timeout=3.0)
if self.thread.is_alive():
print(f"[{self.cam_id}] 警告读取线程未能在3秒内结束")
# 清空队列
while not self.q.empty():
try:
self.q.get_nowait()
except queue.Empty:
break
# VideoCapture的释放由_reader线程的finally块处理这里不再重复释放
class MultiCameraMonitor:
@@ -497,10 +551,39 @@ class MultiCameraMonitor:
self.stop()
def stop(self):
"""停止监控,清理所有资源"""
print("正在停止监控系统...")
self.running = False
for reader in self.frame_readers.values():
reader.release()
cv2.destroyAllWindows()
# 等待推理线程结束
if hasattr(self, 'inference_thread') and self.inference_thread.is_alive():
self.inference_thread.join(timeout=2.0)
if hasattr(self, 'perimeter_thread') and self.perimeter_thread.is_alive():
self.perimeter_thread.join(timeout=2.0)
# 释放所有摄像头资源
for cam_id, reader in self.frame_readers.items():
try:
print(f"正在释放摄像头 {cam_id}...")
reader.release()
except Exception as e:
print(f"释放摄像头 {cam_id} 时出错: {e}")
# 关闭所有窗口
try:
cv2.destroyAllWindows()
except:
pass
# 强制清理(如果还有线程在运行)
import sys
import os
if sys.platform == 'win32':
# Windows下可能需要额外等待
time.sleep(0.5)
print("监控系统已停止")
class ROILogic:
@@ -558,7 +641,7 @@ class ROILogic:
# 周界入侵相关状态如果没有points使用整张画面
if '周界入侵' in self.algorithms:
self.perimeter_last_check_time = 0
self.perimeter_alert_cooldown = 60 # 周界入侵告警冷却60秒
self.perimeter_alert_cooldown = 120 # 周界入侵告警冷却120秒2分钟
if self.use_full_frame:
print(f"[{cam_id}] 提示:{self.roi_name} 周界入侵算法将使用整张画面进行检测")
@@ -903,13 +986,13 @@ class CameraLogic:
# 非工作人员
print(f"[{self.cam_id}] [{roi.roi_name}] 🚨 周界入侵告警!检测到非工作人员(检测区域:{area_desc}")
print(f"大模型判断结果: {result}")
# 保存告警图片
# 保存告警图片(使用区域描述作为名称,更清晰)
save_alert_image(
frame.copy(),
self.cam_id,
roi.roi_name,
area_desc, # 使用area_desc而不是roi.roi_name
"入侵",
f"检测区域: {area_desc}\n大模型判断结果:\n{result}"
f"检测区域: {area_desc}\nROI名称: {roi.roi_name}\n大模型判断结果:\n{result}"
)
else:
# 工作人员
@@ -919,13 +1002,13 @@ class CameraLogic:
# 没有大模型时,直接告警
area_desc = "整张画面" if roi.use_full_frame else roi.roi_name
print(f"[{self.cam_id}] [{roi.roi_name}] 🚨 周界入侵告警!检测到人员进入(检测区域:{area_desc}")
# 保存告警图片
# 保存告警图片(使用区域描述作为名称,更清晰)
save_alert_image(
frame.copy(),
self.cam_id,
roi.roi_name,
area_desc, # 使用area_desc而不是roi.roi_name
"入侵",
f"检测区域: {area_desc}\n检测到人员进入"
f"检测区域: {area_desc}\nROI名称: {roi.roi_name}\n检测到人员进入"
)
self.display_frame = frame.copy()
@@ -1014,12 +1097,40 @@ class CameraLogic:
def main():
import signal
import sys
parser = argparse.ArgumentParser()
parser.add_argument("--config", default="config.yaml", help="配置文件路径")
args = parser.parse_args()
monitor = MultiCameraMonitor(args.config)
monitor.run()
monitor = None
try:
monitor = MultiCameraMonitor(args.config)
# 注册信号处理,确保优雅退出
def signal_handler(sig, frame):
print("\n收到退出信号,正在关闭...")
if monitor:
monitor.stop()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
if sys.platform != 'win32':
signal.signal(signal.SIGTERM, signal_handler)
monitor.run()
except KeyboardInterrupt:
print("\n收到键盘中断,正在关闭...")
except Exception as e:
print(f"程序异常: {e}")
import traceback
traceback.print_exc()
finally:
if monitor:
monitor.stop()
# 确保进程退出
sys.exit(0)
if __name__ == "__main__":