safety_main.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. """
  2. 施工现场安全行为智能识别系统 - 主程序
  3. 系统功能:
  4. 1. 实时视频监控
  5. 2. 人员、安全帽、反光衣检测
  6. 3. 安全违规识别(未戴安全帽、未穿反光衣)
  7. 4. 事件推送至业务平台
  8. 5. 接收平台指令,TTS 语音播报
  9. """
  10. import os
  11. import sys
  12. import time
  13. import argparse
  14. import logging
  15. import threading
  16. import signal
  17. from typing import Optional, List
  18. import cv2
  19. import numpy as np
  20. # 添加项目路径
  21. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  22. from config import (
  23. LOG_CONFIG, PANORAMA_CAMERA, PTZ_CAMERA, SDK_PATH,
  24. SAFETY_DETECTION_CONFIG, EVENT_PUSHER_CONFIG,
  25. VOICE_ANNOUNCER_CONFIG, SYSTEM_CONFIG,
  26. LLM_CONFIG, LLM_SAFETY_CONFIG
  27. )
  28. from safety_detector import (
  29. SafetyDetector, SafetyDetection, PersonSafetyStatus,
  30. draw_safety_result, SafetyViolationType, LLMSafetyDetector
  31. )
  32. from llm_service import SafetyAnalyzer, NumberRecognizer
  33. from event_pusher import EventPusher, SafetyEvent, EventType
  34. from voice_announcer import VoiceAnnouncer, VoicePriority
  35. from safety_coordinator import SafetyCoordinator, SimpleCamera
  36. # 配置日志
  37. def setup_logging():
  38. """设置日志配置"""
  39. log_level = getattr(logging, LOG_CONFIG.get('level', 'INFO'), logging.INFO)
  40. log_format = LOG_CONFIG.get('format', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  41. log_file = LOG_CONFIG.get('file')
  42. handlers = [logging.StreamHandler()]
  43. if log_file:
  44. from logging.handlers import RotatingFileHandler
  45. file_handler = RotatingFileHandler(
  46. log_file,
  47. maxBytes=LOG_CONFIG.get('max_bytes', 10*1024*1024),
  48. backupCount=LOG_CONFIG.get('backup_count', 5)
  49. )
  50. file_handler.setFormatter(logging.Formatter(log_format))
  51. handlers.append(file_handler)
  52. logging.basicConfig(
  53. level=log_level,
  54. format=log_format,
  55. handlers=handlers
  56. )
  57. setup_logging()
  58. logger = logging.getLogger(__name__)
  59. class SafetyMonitorSystem:
  60. """
  61. 施工现场安全监控系统
  62. """
  63. def __init__(self, config: dict = None):
  64. """
  65. 初始化系统
  66. Args:
  67. config: 配置覆盖
  68. """
  69. self.config = config or {}
  70. # 摄像头
  71. self.camera = None
  72. self.camera_source = None
  73. # PTZ球机(安全模式可选联动)
  74. self.ptz_camera = None
  75. self.calibrator = None
  76. self.sdk = None
  77. # 双路流管理器(用于PTZ模式下的双流并行)
  78. self.stream_manager = None
  79. # 组件
  80. self.detector = None # 安全检测器 (支持 LLM)
  81. self.llm_analyzer = None # 大模型安全分析器
  82. self.number_recognizer = None # 编号识别器
  83. self.event_pusher = None # 事件推送器
  84. self.voice_announcer = None # 语音播报器
  85. # 功能开关 - 从 SYSTEM_CONFIG 读取
  86. self.enable_panorama_camera = SYSTEM_CONFIG.get('enable_panorama_camera', True)
  87. self.enable_ptz_camera = SYSTEM_CONFIG.get('enable_ptz_camera', True)
  88. self.enable_detection = SYSTEM_CONFIG.get('enable_detection', True)
  89. self.enable_safety_detection = SYSTEM_CONFIG.get('enable_safety_detection', True)
  90. self.enable_calibration = SYSTEM_CONFIG.get('enable_calibration', True)
  91. self.enable_ptz_tracking = SYSTEM_CONFIG.get('enable_ptz_tracking', True)
  92. self.enable_ocr = SYSTEM_CONFIG.get('enable_ocr', True)
  93. self.enable_llm = SYSTEM_CONFIG.get('enable_llm', True)
  94. self.enable_event_push = SYSTEM_CONFIG.get('enable_event_push', True)
  95. self.enable_voice_announce = SYSTEM_CONFIG.get('enable_voice_announce', True)
  96. # 状态
  97. self.running = False
  98. self.display = True # 是否显示画面
  99. # 帧处理
  100. self.current_frame = None
  101. self.frame_lock = threading.Lock()
  102. # 统计
  103. self.stats = {
  104. 'frames_processed': 0,
  105. 'persons_detected': 0,
  106. 'violations_detected': 0,
  107. 'events_pushed': 0,
  108. 'voice_announced': 0,
  109. 'start_time': None
  110. }
  111. self.stats_lock = threading.Lock()
  112. # 工作线程
  113. self.detection_thread = None
  114. def initialize(self, camera_source=0) -> bool:
  115. """
  116. 初始化系统组件
  117. Args:
  118. camera_source: 摄像头源 (索引/RTSP/视频文件)
  119. Returns:
  120. 是否成功
  121. """
  122. logger.info("=" * 60)
  123. logger.info(f"初始化 {SYSTEM_CONFIG['name']} v{SYSTEM_CONFIG['version']}")
  124. logger.info("=" * 60)
  125. # 初始化摄像头
  126. self.camera_source = camera_source
  127. if self.enable_panorama_camera:
  128. self.camera = SimpleCamera(camera_source)
  129. if not self.camera.connect():
  130. logger.error("连接摄像头失败")
  131. return False
  132. logger.info(f"摄像头连接成功: {camera_source}")
  133. else:
  134. self.camera = None
  135. logger.info("摄像头功能已禁用")
  136. # 初始化 PTZ 球机(安全模式可选联动)
  137. if self.enable_ptz_camera and self.enable_ptz_tracking:
  138. try:
  139. from dahua_sdk import DahuaSDK
  140. sdk_path = os.path.join(SDK_PATH['lib_path'], SDK_PATH['netsdk'])
  141. self.sdk = DahuaSDK(sdk_path)
  142. if self.sdk.init():
  143. from ptz_camera import PTZCamera
  144. ptz_config = self.config.get('ptz_camera', PTZ_CAMERA)
  145. self.ptz_camera = PTZCamera(self.sdk, ptz_config)
  146. if self.ptz_camera.connect():
  147. logger.info(f"PTZ球机连接成功: {ptz_config['ip']}")
  148. if self.ptz_camera.start_stream_rtsp():
  149. logger.info("PTZ球机RTSP流启动成功")
  150. else:
  151. logger.warning("PTZ球机RTSP流启动失败,PTZ跟踪将无法进行帧验证")
  152. else:
  153. logger.warning(f"PTZ球机连接失败: {ptz_config['ip']}")
  154. self.ptz_camera = None
  155. else:
  156. logger.warning("SDK初始化失败,PTZ功能不可用")
  157. self.sdk = None
  158. except Exception as e:
  159. logger.warning(f"PTZ球机初始化失败: {e},PTZ跟踪将不可用")
  160. self.ptz_camera = None
  161. # 初始化 LLM 大模型服务
  162. if self.enable_llm:
  163. try:
  164. llm_config = {**LLM_CONFIG}
  165. if 'llm_host' in self.config:
  166. llm_config['api_host'] = self.config['llm_host']
  167. if 'llm_port' in self.config:
  168. llm_config['api_port'] = self.config['llm_port']
  169. self.llm_analyzer = SafetyAnalyzer(llm_config)
  170. logger.info(f"大模型分析器初始化成功: {llm_config['api_host']}:{llm_config['api_port']}")
  171. except Exception as e:
  172. logger.warning(f"大模型分析器初始化失败: {e},将使用规则判断")
  173. self.use_llm = False
  174. # 初始化安全检测器 (支持 LLM)
  175. if self.enable_detection and self.enable_safety_detection:
  176. try:
  177. llm_config = {**LLM_CONFIG} if self.enable_llm else None
  178. self.detector = LLMSafetyDetector(
  179. yolo_model_path=self.config.get('model_path', SAFETY_DETECTION_CONFIG.get('model_path')),
  180. llm_config=llm_config,
  181. use_gpu=self.config.get('use_gpu', SAFETY_DETECTION_CONFIG.get('use_gpu', True)),
  182. use_llm=self.enable_llm,
  183. model_type=self.config.get('model_type', SAFETY_DETECTION_CONFIG.get('model_type', 'auto'))
  184. )
  185. logger.info("安全检测器初始化成功")
  186. except Exception as e:
  187. logger.error(f"安全检测器初始化失败: {e}")
  188. return False
  189. else:
  190. self.detector = None
  191. logger.info("安全检测功能已禁用")
  192. # 初始化编号识别器
  193. if self.enable_ocr and self.enable_llm:
  194. try:
  195. self.number_recognizer = NumberRecognizer(LLM_CONFIG)
  196. logger.info("编号识别器初始化成功")
  197. except Exception as e:
  198. logger.warning(f"编号识别器初始化失败: {e}")
  199. self.number_recognizer = None
  200. # 初始化事件推送器
  201. if self.enable_event_push:
  202. try:
  203. push_config = {**EVENT_PUSHER_CONFIG}
  204. if 'api_host' in self.config:
  205. push_config['api_host'] = self.config['api_host']
  206. if 'api_port' in self.config:
  207. push_config['api_port'] = self.config['api_port']
  208. self.event_pusher = EventPusher(push_config)
  209. logger.info("事件推送器初始化成功")
  210. except Exception as e:
  211. logger.warning(f"事件推送器初始化失败: {e}")
  212. # 初始化语音播报器
  213. if self.enable_voice_announce:
  214. try:
  215. self.voice_announcer = VoiceAnnouncer(
  216. tts_config=VOICE_ANNOUNCER_CONFIG.get('tts', {}),
  217. player_config=VOICE_ANNOUNCER_CONFIG.get('player', {})
  218. )
  219. logger.info("语音播报器初始化成功")
  220. except Exception as e:
  221. logger.warning(f"语音播报器初始化失败: {e}")
  222. logger.info("系统初始化完成")
  223. return True
  224. def start(self) -> bool:
  225. """启动系统"""
  226. if self.running:
  227. logger.warning("系统已在运行")
  228. return True
  229. logger.info("启动安全监控系统...")
  230. # 启动事件推送器
  231. if self.event_pusher:
  232. self.event_pusher.start()
  233. # 启动语音播报器
  234. if self.voice_announcer:
  235. self.voice_announcer.start()
  236. # 启动检测线程
  237. self.running = True
  238. self.detection_thread = threading.Thread(target=self._detection_worker, daemon=True)
  239. self.detection_thread.start()
  240. with self.stats_lock:
  241. self.stats['start_time'] = time.time()
  242. logger.info("安全监控系统启动成功")
  243. return True
  244. def stop(self):
  245. """停止系统"""
  246. if not self.running:
  247. return
  248. logger.info("停止安全监控系统...")
  249. self.running = False
  250. if self.detection_thread:
  251. self.detection_thread.join(timeout=3)
  252. if self.event_pusher:
  253. self.event_pusher.stop()
  254. if self.voice_announcer:
  255. self.voice_announcer.stop()
  256. if self.camera:
  257. self.camera.disconnect()
  258. if self.ptz_camera:
  259. self.ptz_camera.stop_stream()
  260. self.ptz_camera.disconnect()
  261. if self.stream_manager:
  262. self.stream_manager.stop_all()
  263. if self.sdk:
  264. try:
  265. self.sdk.cleanup()
  266. except Exception:
  267. pass
  268. self._print_stats()
  269. logger.info("安全监控系统已停止")
  270. def _detection_worker(self):
  271. """检测工作线程"""
  272. # 检查摄像头和检测是否启用
  273. if not self.enable_panorama_camera or not self.enable_detection:
  274. logger.info("摄像头或检测功能已禁用,检测线程休眠")
  275. while self.running:
  276. time.sleep(1)
  277. return
  278. # 优先使用 detection_fps,默认每秒2帧
  279. detection_fps = SAFETY_DETECTION_CONFIG.get('detection_fps', 2)
  280. detection_interval = 1.0 / detection_fps # 根据FPS计算间隔
  281. last_detection_time = 0
  282. # 告警冷却(按违规类型)
  283. alert_cooldown = {}
  284. cooldown_time = SAFETY_DETECTION_CONFIG.get('alert_cooldown', 3.0)
  285. while self.running:
  286. try:
  287. current_time = time.time()
  288. # 获取帧
  289. frame = self.camera.get_frame() if self.camera else None
  290. if frame is None:
  291. time.sleep(0.01)
  292. continue
  293. with self.frame_lock:
  294. self.current_frame = frame.copy()
  295. self._update_stats('frames_processed')
  296. # 周期性检测
  297. if current_time - last_detection_time >= detection_interval:
  298. last_detection_time = current_time
  299. # 执行检测
  300. detections = self.detector.detect(frame)
  301. status_list = self.detector.check_safety(frame, detections)
  302. self._update_stats('persons_detected', len(status_list))
  303. # 轨迹追踪已禁用
  304. # 处理违规
  305. for status in status_list:
  306. if status.is_violation:
  307. # 检查冷却(按违规类型)
  308. violation_key = status.get_violation_desc()
  309. if violation_key in alert_cooldown:
  310. if current_time - alert_cooldown[violation_key] < cooldown_time:
  311. continue
  312. alert_cooldown[violation_key] = current_time
  313. self._handle_violation(status, frame)
  314. # 显示画面
  315. if self.display:
  316. self._display_frame(frame, detections, status_list)
  317. time.sleep(0.01)
  318. except Exception as e:
  319. logger.error(f"检测错误: {e}")
  320. time.sleep(0.1)
  321. def _handle_violation(self, status: PersonSafetyStatus, frame: np.ndarray):
  322. """处理违规"""
  323. description = status.get_violation_desc()
  324. self._update_stats('violations_detected')
  325. # 裁剪人体区域
  326. x1, y1, x2, y2 = status.person_bbox
  327. margin = 20
  328. x1 = max(0, x1 - margin)
  329. y1 = max(0, y1 - margin)
  330. x2 = min(frame.shape[1], x2 + margin)
  331. y2 = min(frame.shape[0], y2 + margin)
  332. person_image = frame[y1:y2, x1:x2].copy()
  333. # 编号识别
  334. number_text = None
  335. if self.number_recognizer:
  336. try:
  337. number_result = self.number_recognizer.recognize_person_number(person_image)
  338. number_text = number_result.get('number')
  339. if number_text:
  340. logger.info(f"识别到编号: {number_text}")
  341. except Exception as e:
  342. logger.warning(f"编号识别失败: {e}")
  343. # 如果识别到编号,添加到描述中
  344. if number_text:
  345. description = f"{description} (编号: {number_text})"
  346. # 推送事件
  347. if self.event_pusher:
  348. self.event_pusher.push_safety_violation(
  349. description=description,
  350. image=person_image,
  351. track_id=status.track_id,
  352. confidence=status.person_conf
  353. )
  354. self._update_stats('events_pushed')
  355. # 语音播报
  356. if self.voice_announcer:
  357. self.voice_announcer.announce_violation(description, urgent=True)
  358. self._update_stats('voice_announced')
  359. logger.warning(f"[违规] {description}")
  360. def _display_frame(self, frame: np.ndarray,
  361. detections: List[SafetyDetection],
  362. status_list: List[PersonSafetyStatus]):
  363. """显示帧"""
  364. # 绘制检测结果
  365. result_frame = draw_safety_result(frame, detections, status_list)
  366. # 添加统计信息
  367. stats_text = f"FPS: {self._get_fps():.1f}"
  368. cv2.putText(result_frame, stats_text, (10, 30),
  369. cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
  370. cv2.imshow('Safety Monitor', result_frame)
  371. # 按 'q' 退出
  372. if cv2.waitKey(1) & 0xFF == ord('q'):
  373. self.running = False
  374. def _get_fps(self) -> float:
  375. """获取帧率"""
  376. with self.stats_lock:
  377. if self.stats['start_time']:
  378. elapsed = time.time() - self.stats['start_time']
  379. if elapsed > 0:
  380. return self.stats['frames_processed'] / elapsed
  381. return 0.0
  382. def _update_stats(self, key: str, value: int = 1):
  383. """更新统计"""
  384. with self.stats_lock:
  385. if key in self.stats:
  386. self.stats[key] += value
  387. def _print_stats(self):
  388. """打印统计"""
  389. with self.stats_lock:
  390. if self.stats['start_time']:
  391. elapsed = time.time() - self.stats['start_time']
  392. print("\n" + "=" * 50)
  393. print("安全检测统计")
  394. print("=" * 50)
  395. print(f"运行时长: {elapsed:.1f} 秒")
  396. print(f"处理帧数: {self.stats['frames_processed']}")
  397. print(f"检测人员: {self.stats['persons_detected']} 次")
  398. print(f"违规检测: {self.stats['violations_detected']} 次")
  399. print(f"事件推送: {self.stats['events_pushed']} 次")
  400. print(f"语音播报: {self.stats['voice_announced']} 次")
  401. if self.event_pusher:
  402. push_stats = self.event_pusher.get_stats()
  403. print(f"推送成功: {push_stats['pushed_events']}")
  404. print(f"推送失败: {push_stats['failed_events']}")
  405. if self.voice_announcer:
  406. voice_stats = self.voice_announcer.get_stats()
  407. print(f"播报成功: {voice_stats['played_commands']}")
  408. print(f"播报失败: {voice_stats['failed_commands']}")
  409. print("=" * 50 + "\n")
  410. def get_stats(self) -> dict:
  411. """获取统计"""
  412. with self.stats_lock:
  413. return self.stats.copy()
  414. def announce(self, text: str):
  415. """
  416. 手动播报语音
  417. Args:
  418. text: 播报文本
  419. """
  420. if self.voice_announcer:
  421. self.voice_announcer.announce(text, priority=VoicePriority.NORMAL)
  422. def run_interactive(system: SafetyMonitorSystem):
  423. """
  424. 交互模式运行
  425. Args:
  426. system: 系统实例
  427. """
  428. print("\n施工现场安全监控系统 - 交互模式")
  429. print("=" * 50)
  430. print("命令:")
  431. print(" s - 开始/停止监控")
  432. print(" a - 手动播报 (输入文本)")
  433. print(" r - 查看统计信息")
  434. print(" q - 退出")
  435. print("=" * 50)
  436. running = False
  437. while True:
  438. try:
  439. cmd = input("\n> ").strip().lower()
  440. if cmd == 'q':
  441. break
  442. elif cmd == 's':
  443. if running:
  444. system.stop()
  445. running = False
  446. print("监控已停止")
  447. else:
  448. if system.start():
  449. running = True
  450. print("监控已启动")
  451. elif cmd == 'a':
  452. text = input("输入播报文本: ").strip()
  453. if text:
  454. system.announce(text)
  455. print(f"已播报: {text}")
  456. elif cmd == 'r':
  457. stats = system.get_stats()
  458. print("\n统计信息:")
  459. for k, v in stats.items():
  460. if v is not None:
  461. print(f" {k}: {v}")
  462. else:
  463. print("未知命令")
  464. except KeyboardInterrupt:
  465. break
  466. except Exception as e:
  467. print(f"错误: {e}")
  468. print("退出交互模式")
  469. def main():
  470. """主函数"""
  471. parser = argparse.ArgumentParser(
  472. description='施工现场安全行为智能识别系统'
  473. )
  474. # 摄像头参数
  475. parser.add_argument('--camera', type=str, default='0',
  476. help='摄像头源 (索引/RTSP地址/视频文件)')
  477. parser.add_argument('--no-display', action='store_true',
  478. help='不显示画面')
  479. # 检测参数
  480. parser.add_argument('--model', type=str,
  481. help='安全检测模型路径')
  482. parser.add_argument('--conf', type=float, default=0.5,
  483. help='置信度阈值')
  484. parser.add_argument('--person-conf', type=float, default=0.8,
  485. help='人员检测置信度阈值')
  486. parser.add_argument('--no-gpu', action='store_true',
  487. help='不使用GPU')
  488. # LLM 大模型参数
  489. parser.add_argument('--llm-host', type=str,
  490. help='大模型 API 主机')
  491. parser.add_argument('--llm-port', type=int,
  492. help='大模型 API 端口')
  493. parser.add_argument('--no-llm', action='store_true',
  494. help='禁用大模型判断,使用规则判断')
  495. parser.add_argument('--no-ocr', action='store_true',
  496. help='禁用编号识别')
  497. # 业务平台参数
  498. parser.add_argument('--api-host', type=str,
  499. help='业务平台 API 主机')
  500. parser.add_argument('--api-port', type=int,
  501. help='业务平台 API 端口')
  502. parser.add_argument('--no-push', action='store_true',
  503. help='禁用事件推送')
  504. parser.add_argument('--no-voice', action='store_true',
  505. help='禁用语音播报')
  506. # 运行模式
  507. parser.add_argument('--interactive', action='store_true',
  508. help='交互模式')
  509. parser.add_argument('--demo', action='store_true',
  510. help='演示模式')
  511. args = parser.parse_args()
  512. # 构建配置
  513. config = {}
  514. if args.model:
  515. config['model_path'] = args.model
  516. config['conf_threshold'] = args.conf
  517. config['person_threshold'] = args.person_conf
  518. config['use_gpu'] = not args.no_gpu
  519. # LLM 配置
  520. if args.llm_host:
  521. config['llm_host'] = args.llm_host
  522. if args.llm_port:
  523. config['llm_port'] = args.llm_port
  524. if args.api_host:
  525. config['api_host'] = args.api_host
  526. if args.api_port:
  527. config['api_port'] = args.api_port
  528. # 演示模式
  529. if args.demo:
  530. print("\n施工现场安全行为智能识别系统")
  531. print("=" * 60)
  532. print("""
  533. 系统功能:
  534. 1. 实时视频监控
  535. 2. YOLO11 检测: 人员、安全帽、反光衣
  536. 3. 大模型判断: 安全状态分析
  537. 4. 编号识别: OCR 识别衣服上的工号
  538. 5. 事件推送到业务平台
  539. 6. 接收平台指令,TTS 语音播报
  540. 系统架构:
  541. ┌─────────────────────────────────────────────────────┐
  542. │ 摄像头视频流 │
  543. └─────────────────────────────────────────────────────┘
  544. ┌─────────────────────────────────────────────────────┐
  545. │ YOLO11 安全检测模型 │
  546. │ 检测类别: 人员(3)、安全帽(0)、反光衣(4) │
  547. └─────────────────────────────────────────────────────┘
  548. ┌─────────────────────────────────────────────────────┐
  549. │ 大模型安全状态判断 │
  550. │ 分析: 是否佩戴安全帽、是否穿反光衣 │
  551. │ 识别: 衣服上的工号/编号 │
  552. └─────────────────────────────────────────────────────┘
  553. ┌─────────────┼─────────────┐
  554. ▼ ▼ ▼
  555. ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
  556. │ 事件推送 │ │ 语音播报 │ │ 编号记录 │
  557. │ 业务平台API │ │ TTS服务 │ │ 身份关联 │
  558. └─────────────┘ └─────────────┘ └─────────────┘
  559. 运行命令:
  560. python safety_main.py --camera 0 # 使用默认摄像头
  561. python safety_main.py --camera rtsp://... # RTSP 流
  562. python safety_main.py --camera video.mp4 # 视频文件
  563. python safety_main.py --interactive # 交互模式
  564. python safety_main.py --no-display # 无界面模式
  565. python safety_main.py --no-llm # 禁用大模型,使用规则判断
  566. python safety_main.py --no-ocr # 禁用编号识别
  567. python safety_main.py --llm-host localhost --llm-port 8111 # 指定大模型服务
  568. """)
  569. return 0
  570. # 创建系统
  571. system = SafetyMonitorSystem(config)
  572. system.display = not args.no_display
  573. # 设置信号处理
  574. def signal_handler(sig, frame):
  575. print("\n接收到停止信号")
  576. system.stop()
  577. sys.exit(0)
  578. signal.signal(signal.SIGINT, signal_handler)
  579. signal.signal(signal.SIGTERM, signal_handler)
  580. try:
  581. # 解析摄像头源
  582. camera_source = args.camera
  583. if camera_source.isdigit():
  584. camera_source = int(camera_source)
  585. # 初始化
  586. if not system.initialize(camera_source):
  587. print("\n系统初始化失败!")
  588. return 1
  589. # 禁用功能 (命令行参数覆盖配置)
  590. if args.no_llm:
  591. system.enable_llm = False
  592. print("大模型判断已禁用,使用规则判断")
  593. if args.no_ocr:
  594. system.enable_ocr = False
  595. system.number_recognizer = None
  596. print("编号识别已禁用")
  597. if args.no_push:
  598. system.enable_event_push = False
  599. system.event_pusher = None
  600. print("事件推送已禁用")
  601. if args.no_voice:
  602. system.enable_voice_announce = False
  603. system.voice_announcer = None
  604. print("语音播报已禁用")
  605. # 运行
  606. if args.interactive:
  607. run_interactive(system)
  608. else:
  609. # 自动模式
  610. if not system.start():
  611. print("启动失败")
  612. return 1
  613. print("\n系统运行中,按 Ctrl+C 停止")
  614. print("(按 'q' 键退出显示窗口)\n")
  615. # 主循环
  616. while system.running:
  617. time.sleep(0.1)
  618. except KeyboardInterrupt:
  619. print("\n接收到停止信号")
  620. finally:
  621. system.stop()
  622. return 0
  623. if __name__ == '__main__':
  624. sys.exit(main() or 0)