main.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. """
  2. 双摄像头联动抓拍系统 - 主程序
  3. 系统功能:
  4. 1. 全景摄像头实时监控和物体检测
  5. 2. 检测到人体后,球机自动变焦定位
  6. 3. 对人体进行分割并OCR识别衣服上的编号
  7. """
  8. # 必须在import cv2之前设置,否则FFmpeg多线程解码会导致
  9. # "Assertion fctx->async_lock failed at pthread_frame.c:167" 崩溃
  10. import os
  11. os.environ['OPENCV_FFMPEG_CAPTURE_OPTIONS'] = 'threads;1'
  12. import sys
  13. import time
  14. import argparse
  15. import logging
  16. import threading
  17. from typing import Optional
  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. PANORAMA_CAMERA, PTZ_CAMERA, SDK_PATH,
  24. DETECTION_CONFIG, PTZ_CONFIG, OCR_CONFIG, COORDINATOR_CONFIG,
  25. CALIBRATION_CONFIG, LOG_CONFIG, SYSTEM_CONFIG
  26. )
  27. from dahua_sdk import DahuaSDK
  28. from panorama_camera import PanoramaCamera, ObjectDetector, PersonTracker, DetectedObject
  29. from ptz_camera import PTZCamera, PTZController
  30. from ocr_recognizer import NumberDetector, PersonInfo
  31. from coordinator import Coordinator, EventDrivenCoordinator, AsyncCoordinator
  32. # 配置日志 - 使用LOG_CONFIG
  33. def setup_logging():
  34. """设置日志配置"""
  35. log_level = getattr(logging, LOG_CONFIG.get('level', 'INFO'), logging.INFO)
  36. log_format = LOG_CONFIG.get('format', '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  37. log_file = LOG_CONFIG.get('file')
  38. handlers = [logging.StreamHandler()]
  39. if log_file:
  40. from logging.handlers import RotatingFileHandler
  41. file_handler = RotatingFileHandler(
  42. log_file,
  43. maxBytes=LOG_CONFIG.get('max_bytes', 10*1024*1024),
  44. backupCount=LOG_CONFIG.get('backup_count', 5)
  45. )
  46. file_handler.setFormatter(logging.Formatter(log_format))
  47. handlers.append(file_handler)
  48. logging.basicConfig(
  49. level=log_level,
  50. format=log_format,
  51. handlers=handlers
  52. )
  53. setup_logging()
  54. logger = logging.getLogger(__name__)
  55. class DualCameraSystem:
  56. """
  57. 双摄像头联动抓拍系统
  58. """
  59. def __init__(self, config_override: dict = None):
  60. """
  61. 初始化系统
  62. Args:
  63. config_override: 配置覆盖
  64. """
  65. self.config = config_override or {}
  66. # SDK
  67. self.sdk = None
  68. # 摄像头
  69. self.panorama_camera = None
  70. self.ptz_camera = None
  71. # 检测器和识别器
  72. self.detector = None
  73. self.number_detector = None
  74. # 联动控制器
  75. self.coordinator = None
  76. # 校准器
  77. self.calibrator = None
  78. self.calibration_manager = None
  79. # 定时校准
  80. self.calibration_interval = CALIBRATION_CONFIG.get('interval', 5 * 60) # 默认5分钟
  81. self.calibration_thread = None
  82. self.calibration_running = False
  83. self.last_calibration_time = 0
  84. # 运行标志
  85. self.running = False
  86. def initialize(self, skip_calibration: bool = False) -> bool:
  87. """
  88. 初始化系统组件
  89. Args:
  90. skip_calibration: 是否跳过校准
  91. Returns:
  92. 是否成功
  93. """
  94. logger.info("初始化双摄像头联动系统...")
  95. # 先初始化检测器(YOLO/PyTorch),再加载大华SDK
  96. # 大华SDK与PyTorch共享进程空间时可能导致内存冲突,
  97. # 先加载PyTorch可避免SDK的内存映射覆盖PyTorch运行时
  98. # 初始化检测器 (YOLO11/RKNN/ONNX)
  99. try:
  100. from config import DETECTION_CONFIG
  101. self.detector = ObjectDetector(
  102. model_path=self.config.get('model_path', DETECTION_CONFIG.get('model_path')),
  103. use_gpu=self.config.get('use_gpu', DETECTION_CONFIG.get('use_gpu', True)),
  104. model_size=self.config.get('model_size', 'n'),
  105. model_type=self.config.get('model_type', DETECTION_CONFIG.get('model_type', 'auto'))
  106. )
  107. logger.info("检测器初始化成功")
  108. except Exception as e:
  109. logger.warning(f"检测器初始化失败: {e}")
  110. # 初始化编号检测器 (使用llama-server API)
  111. try:
  112. ocr_config = {
  113. 'api_host': self.config.get('ocr_host', OCR_CONFIG['api_host']),
  114. 'api_port': self.config.get('ocr_port', OCR_CONFIG['api_port']),
  115. 'model': self.config.get('ocr_model', OCR_CONFIG['model']),
  116. }
  117. self.number_detector = NumberDetector(use_api=True, ocr_config=ocr_config)
  118. logger.info("编号检测器初始化成功 (使用llama-server API)")
  119. except Exception as e:
  120. logger.warning(f"编号检测器初始化失败: {e}")
  121. # 初始化SDK(在检测器之后,避免SDK内存映射与PyTorch冲突)
  122. sdk_path = os.path.join(
  123. self.config.get('sdk_path', SDK_PATH['lib_path']),
  124. self.config.get('netsdk', SDK_PATH['netsdk'])
  125. )
  126. try:
  127. self.sdk = DahuaSDK(sdk_path)
  128. if not self.sdk.init():
  129. logger.error("SDK初始化失败")
  130. return False
  131. logger.info("SDK初始化成功")
  132. except Exception as e:
  133. logger.error(f"SDK加载失败: {e}")
  134. return False
  135. # 初始化摄像头
  136. panorama_config = self.config.get('panorama_camera', PANORAMA_CAMERA)
  137. self.panorama_camera = PanoramaCamera(self.sdk, panorama_config)
  138. ptz_config = self.config.get('ptz_camera', PTZ_CAMERA)
  139. self.ptz_camera = PTZCamera(self.sdk, ptz_config)
  140. # 初始化联动控制器(使用异步版本,检测与PTZ分线程)
  141. self.coordinator = AsyncCoordinator(
  142. self.panorama_camera,
  143. self.ptz_camera,
  144. self.detector,
  145. self.number_detector
  146. )
  147. # 设置回调
  148. self._setup_callbacks()
  149. logger.info("系统初始化完成")
  150. # 执行自动校准(受配置开关和参数双重控制)
  151. should_calibrate = SYSTEM_CONFIG.get('enable_calibration', True)
  152. if not should_calibrate:
  153. logger.info("自动校准已禁用 (enable_calibration=False)")
  154. elif skip_calibration:
  155. logger.info("自动校准已跳过 (--skip-calibration)")
  156. else:
  157. if not self._auto_calibrate():
  158. logger.error("自动校准失败!")
  159. return False
  160. return True
  161. def _auto_calibrate(self) -> bool:
  162. """
  163. 执行自动校准
  164. Returns:
  165. 是否成功
  166. """
  167. from calibration import CameraCalibrator, CalibrationManager
  168. logger.info("=" * 50)
  169. logger.info("开始自动校准...")
  170. logger.info("=" * 50)
  171. # 连接摄像头
  172. if not self.panorama_camera.connect():
  173. logger.error("连接全景摄像头失败,无法进行校准")
  174. return False
  175. if not self.ptz_camera.connect():
  176. logger.error("连接球机失败,无法进行校准")
  177. self.panorama_camera.disconnect()
  178. return False
  179. # 启动视频流获取帧数据
  180. if not self.panorama_camera.start_stream_rtsp():
  181. logger.warning("RTSP视频流启动失败,尝试SDK方式...")
  182. if not self.panorama_camera.start_stream():
  183. logger.error("无法启动视频流,校准可能无法获取画面")
  184. # 启动球机视频流(校准需要球机画面做特征匹配)
  185. if not self.ptz_camera.start_stream_rtsp():
  186. logger.warning("球机RTSP视频流启动失败,校准将无法进行特征匹配")
  187. # 等待视频流稳定,确保帧数据可用
  188. logger.info("等待视频流稳定...")
  189. max_wait = 15
  190. for i in range(max_wait):
  191. pan_frame = self.panorama_camera.get_frame()
  192. ptz_frame = self.ptz_camera.get_frame() if self.ptz_camera else None
  193. if pan_frame is not None:
  194. logger.info(f"全景帧就绪 ({pan_frame.shape}), 等待中...")
  195. break
  196. time.sleep(1)
  197. if (i + 1) % 3 == 0:
  198. logger.info(f"等待全景帧... ({i + 1}/{max_wait}秒)")
  199. # 再等几秒让流完全稳定
  200. ptz_ready = False
  201. for i in range(10):
  202. ptz_frame = self.ptz_camera.get_frame() if self.ptz_camera else None
  203. if ptz_frame is not None:
  204. ptz_ready = True
  205. logger.info(f"球机帧就绪 ({ptz_frame.shape})")
  206. break
  207. time.sleep(1)
  208. if not ptz_ready:
  209. logger.warning("球机帧未就绪,校准可能仅依赖运动检测")
  210. final_frame = self.panorama_camera.get_frame()
  211. if final_frame is None:
  212. logger.error("5秒后仍无法获取全景帧,校准可能失败")
  213. # 创建校准器 - 支持视野重叠发现
  214. self.calibrator = CameraCalibrator(
  215. ptz_camera=self.ptz_camera,
  216. get_frame_func=self.panorama_camera.get_frame,
  217. detect_marker_func=None,
  218. ptz_capture_func=self._capture_ptz_frame
  219. )
  220. # 配置重叠发现参数
  221. overlap_cfg = CALIBRATION_CONFIG.get('overlap_discovery', {})
  222. self.calibrator.overlap_pan_range = overlap_cfg.get('pan_range', (0, 360))
  223. self.calibrator.overlap_tilt_range = overlap_cfg.get('tilt_range', (-30, 30))
  224. self.calibrator.overlap_pan_step = overlap_cfg.get('pan_step', 20)
  225. self.calibrator.overlap_tilt_step = overlap_cfg.get('tilt_step', 15)
  226. self.calibrator.stabilize_time = overlap_cfg.get('stabilize_time', 2.0)
  227. self.calibrator.max_overlap_ranges = overlap_cfg.get('max_overlap_ranges', 3)
  228. self.calibrator.min_positions_per_range = overlap_cfg.get('min_positions_per_range', 3)
  229. # 校准进度回调
  230. def on_progress(current: int, total: int, message: str):
  231. logger.info(f"校准进度: {current}/{total} - {message}")
  232. # 校准完成回调
  233. def on_complete(result):
  234. if result.success:
  235. logger.info(f"校准完成! RMS误差: {result.rms_error:.4f}")
  236. else:
  237. logger.error(f"校准失败: {result.error_message}")
  238. self.calibrator.on_progress = on_progress
  239. self.calibrator.on_complete = on_complete
  240. # 创建校准管理器
  241. self.calibration_manager = CalibrationManager(self.calibrator)
  242. # 执行视觉校准
  243. result = self.calibration_manager.auto_calibrate(force=True)
  244. if not result.success:
  245. logger.error("=" * 50)
  246. logger.error("校准失败!")
  247. logger.error(f"原因: {result.error_message}")
  248. logger.error("=" * 50)
  249. logger.error("")
  250. logger.error("请检查以下问题:")
  251. logger.error("1. 全景摄像头和球机是否正确连接")
  252. logger.error("2. 球机PTZ控制是否正常")
  253. logger.error("3. 两台摄像头的视野是否有重叠区域")
  254. logger.error("4. 场景是否有足够的纹理/特征用于匹配")
  255. logger.error("5. 球机RTSP视频流是否正常(特征匹配需要球机画面)")
  256. logger.error("")
  257. logger.error("您可以尝试:")
  258. logger.error("- 检查摄像头连接和网络")
  259. logger.error("- 手动移动球机确认PTZ控制正常")
  260. logger.error("- 确保场景有足够的纹理/特征")
  261. logger.error("- 使用 --skip-calibration 跳过校准")
  262. logger.error("=" * 50)
  263. # 释放资源
  264. self.panorama_camera.disconnect()
  265. self.ptz_camera.stop_stream()
  266. self.ptz_camera.disconnect()
  267. return False
  268. logger.info("=" * 50)
  269. logger.info("校准成功!")
  270. logger.info(f"有效校准点: {len(result.points)}")
  271. logger.info(f"RMS误差: {result.rms_error:.4f} 度")
  272. logger.info("=" * 50)
  273. return True
  274. def _capture_ptz_frame(self) -> Optional[np.ndarray]:
  275. """
  276. 从球机抓拍一帧图像
  277. 用于校准时特征匹配
  278. """
  279. if self.ptz_camera is None:
  280. return None
  281. try:
  282. return self.ptz_camera.get_frame()
  283. except Exception as e:
  284. logger.error(f"球机抓拍失败: {e}")
  285. return None
  286. def _setup_callbacks(self):
  287. """设置回调函数"""
  288. def on_person_detected(person: DetectedObject, frame: np.ndarray):
  289. """人体检测回调"""
  290. logger.info(f"检测到人体: 位置={person.center}, 置信度={person.confidence:.2f}")
  291. def on_number_recognized(person_info: PersonInfo):
  292. """编号识别回调"""
  293. logger.info(
  294. f"识别到编号: ID={person_info.person_id}, "
  295. f"编号={person_info.number_text}, "
  296. f"置信度={person_info.number_confidence:.2f}, "
  297. f"位置={person_info.number_location}"
  298. )
  299. self.coordinator.on_person_detected = on_person_detected
  300. self.coordinator.on_number_recognized = on_number_recognized
  301. def start(self) -> bool:
  302. """
  303. 启动系统
  304. Returns:
  305. 是否成功
  306. """
  307. if self.running:
  308. logger.warning("系统已在运行")
  309. return True
  310. logger.info("启动联动系统...")
  311. if not self.coordinator.start():
  312. logger.error("联动系统启动失败")
  313. return False
  314. self.running = True
  315. logger.info("联动系统启动成功")
  316. # 启动定时校准
  317. self._start_periodic_calibration()
  318. return True
  319. def stop(self):
  320. """停止系统"""
  321. if not self.running:
  322. return
  323. logger.info("停止联动系统...")
  324. # 停止定时校准
  325. self._stop_periodic_calibration()
  326. self.coordinator.stop()
  327. self.running = False
  328. logger.info("联动系统已停止")
  329. def get_results(self):
  330. """获取识别结果"""
  331. if self.coordinator:
  332. return self.coordinator.get_results()
  333. return []
  334. def _start_periodic_calibration(self):
  335. """启动定时校准"""
  336. if not SYSTEM_CONFIG.get('enable_calibration', True):
  337. logger.info("定时校准已禁用 (enable_calibration=False)")
  338. return
  339. if self.calibration_running:
  340. return
  341. self.calibration_running = True
  342. self.calibration_thread = threading.Thread(
  343. target=self._periodic_calibration_worker,
  344. daemon=True
  345. )
  346. self.calibration_thread.start()
  347. # 格式化显示间隔
  348. interval_hours = self.calibration_interval // 3600
  349. if interval_hours >= 24:
  350. interval_str = f"{interval_hours // 24}天"
  351. elif interval_hours >= 1:
  352. interval_str = f"{interval_hours}小时"
  353. else:
  354. interval_str = f"{self.calibration_interval // 60}分钟"
  355. logger.info(f"定时校准已启动 (间隔: {interval_str})")
  356. def _stop_periodic_calibration(self):
  357. """停止定时校准"""
  358. self.calibration_running = False
  359. if self.calibration_thread:
  360. self.calibration_thread.join(timeout=2)
  361. self.calibration_thread = None
  362. logger.info("定时校准已停止")
  363. def _periodic_calibration_worker(self):
  364. """定时校准工作线程"""
  365. import time
  366. while self.calibration_running:
  367. try:
  368. # 等待校准间隔
  369. for _ in range(self.calibration_interval):
  370. if not self.calibration_running:
  371. return
  372. time.sleep(1)
  373. if not self.calibration_running:
  374. return
  375. # 执行校准
  376. logger.info("=" * 50)
  377. logger.info("执行定时校准...")
  378. logger.info("=" * 50)
  379. result = self._auto_calibrate()
  380. if result:
  381. logger.info("定时校准成功!")
  382. else:
  383. logger.warning("定时校准失败, 将在下次间隔重试")
  384. except Exception as e:
  385. logger.error(f"定时校准错误: {e}")
  386. time.sleep(10)
  387. def manual_calibrate(self) -> bool:
  388. """
  389. 手动触发校准
  390. Returns:
  391. 是否成功
  392. """
  393. logger.info("手动触发校准...")
  394. return self._auto_calibrate()
  395. def cleanup(self):
  396. """清理资源"""
  397. self.stop()
  398. # 确保定时校准停止
  399. self._stop_periodic_calibration()
  400. if self.sdk:
  401. self.sdk.cleanup()
  402. logger.info("系统资源已清理")
  403. def run_interactive(system: DualCameraSystem):
  404. """
  405. 交互模式运行
  406. Args:
  407. system: 系统实例
  408. """
  409. print("\n双摄像头联动系统 - 交互模式")
  410. print("=" * 50)
  411. print("命令:")
  412. print(" s - 开始/停止联动")
  413. print(" r - 获取识别结果")
  414. print(" t - 手动跟踪 (输入 x y)")
  415. print(" c - 抓拍快照")
  416. print(" b - 手动校准")
  417. print(" q - 退出")
  418. print("=" * 50)
  419. running = False
  420. while True:
  421. try:
  422. cmd = input("\n> ").strip().lower()
  423. if cmd == 'q':
  424. break
  425. elif cmd == 's':
  426. if running:
  427. system.stop()
  428. running = False
  429. print("联动已停止")
  430. else:
  431. if system.start():
  432. running = True
  433. print("联动已启动")
  434. elif cmd == 'r':
  435. results = system.get_results()
  436. if results:
  437. print(f"获取到 {len(results)} 个识别结果:")
  438. for r in results:
  439. print(f" ID={r.person_id}, 编号={r.number_text}, 置信度={r.number_confidence:.2f}")
  440. else:
  441. print("暂无识别结果")
  442. elif cmd == 't':
  443. try:
  444. coords = input("输入坐标 (x y, 范围0-1): ").strip().split()
  445. x, y = float(coords[0]), float(coords[1])
  446. system.coordinator.force_track_position(x, y)
  447. print(f"已移动到位置 ({x:.2f}, {y:.2f})")
  448. except Exception as e:
  449. print(f"输入错误: {e}")
  450. elif cmd == 'c':
  451. frame = system.coordinator.capture_snapshot()
  452. if frame is not None:
  453. filename = f"snapshot_{int(time.time())}.jpg"
  454. cv2.imwrite(filename, frame)
  455. print(f"快照已保存: {filename}")
  456. else:
  457. print("抓拍失败")
  458. elif cmd == 'b':
  459. print("开始手动校准...")
  460. if system.manual_calibrate():
  461. print("校准成功!")
  462. else:
  463. print("校准失败!")
  464. else:
  465. print("未知命令")
  466. except KeyboardInterrupt:
  467. break
  468. except Exception as e:
  469. print(f"错误: {e}")
  470. print("退出交互模式")
  471. def main():
  472. """主函数"""
  473. parser = argparse.ArgumentParser(description='双摄像头联动抓拍系统')
  474. parser.add_argument('--panorama-ip', type=str, help='全景摄像头IP')
  475. parser.add_argument('--ptz-ip', type=str, help='球机IP')
  476. parser.add_argument('--username', type=str, default='admin', help='用户名')
  477. parser.add_argument('--password', type=str, default='admin123', help='密码')
  478. parser.add_argument('--model', type=str, help='检测模型路径 (默认使用YOLO11n)')
  479. parser.add_argument('--model-size', type=str, default='n',
  480. choices=['n', 's', 'm', 'l', 'x'],
  481. help='YOLO11模型尺寸 (n/s/m/l/x)')
  482. parser.add_argument('--no-gpu', action='store_true', help='不使用GPU')
  483. parser.add_argument('--ocr-host', type=str, default='localhost', help='OCR API服务器地址')
  484. parser.add_argument('--ocr-port', type=int, default=8111, help='OCR API端口')
  485. parser.add_argument('--ocr-model', type=str, default='PaddleOCR-VL-1.5-GGUF.gguf', help='OCR模型名称')
  486. parser.add_argument('--interactive', action='store_true', help='交互模式')
  487. parser.add_argument('--demo', action='store_true', help='演示模式(不连接实际摄像头)')
  488. parser.add_argument('--skip-calibration', action='store_true', help='跳过自动校准')
  489. parser.add_argument('--force-calibration', action='store_true', help='强制重新校准')
  490. args = parser.parse_args()
  491. # 构建配置
  492. config = {}
  493. if args.panorama_ip:
  494. config['panorama_camera'] = {
  495. **PANORAMA_CAMERA,
  496. 'ip': args.panorama_ip,
  497. 'username': args.username,
  498. 'password': args.password,
  499. }
  500. if args.ptz_ip:
  501. config['ptz_camera'] = {
  502. **PTZ_CAMERA,
  503. 'ip': args.ptz_ip,
  504. 'username': args.username,
  505. 'password': args.password,
  506. }
  507. if args.model:
  508. config['model_path'] = args.model
  509. config['model_size'] = args.model_size
  510. config['use_gpu'] = not args.no_gpu
  511. config['ocr_host'] = args.ocr_host
  512. config['ocr_port'] = args.ocr_port
  513. config['ocr_model'] = args.ocr_model
  514. # 演示模式
  515. if args.demo:
  516. print("演示模式: 使用模拟数据")
  517. run_demo()
  518. return
  519. # 创建系统实例
  520. system = DualCameraSystem(config)
  521. try:
  522. # 初始化 (包含自动校准)
  523. if not system.initialize(skip_calibration=args.skip_calibration):
  524. print("\n系统初始化失败!")
  525. if not args.skip_calibration:
  526. print("提示: 可使用 --skip-calibration 跳过校准")
  527. return 1
  528. # 运行
  529. if args.interactive:
  530. run_interactive(system)
  531. else:
  532. # 自动模式
  533. print("启动联动系统...")
  534. if not system.start():
  535. print("启动失败")
  536. return 1
  537. print("系统运行中,按Ctrl+C停止")
  538. while True:
  539. time.sleep(1)
  540. results = system.get_results()
  541. if results:
  542. for r in results:
  543. if r.number_text:
  544. print(f"[识别] ID={r.person_id}, 编号={r.number_text}")
  545. except KeyboardInterrupt:
  546. print("\n接收到停止信号")
  547. finally:
  548. system.cleanup()
  549. return 0
  550. def run_demo():
  551. """演示模式"""
  552. print("\n演示模式 - 双摄像头联动系统")
  553. print("=" * 60)
  554. print("""
  555. 系统架构:
  556. ┌─────────────────────────────────────────────────────────────┐
  557. │ 全景摄像头 (Panorama) │
  558. │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
  559. │ │ 视频流 │ -> │ 人体检测 │ -> │ 位置计算 │ │
  560. │ └─────────┘ └─────────┘ └─────────┘ │
  561. └─────────────────────────────────────────────────────────────┘
  562. ▼ 检测到人体位置 (x_ratio, y_ratio)
  563. ┌─────────────────────────────────────────────────────────────┐
  564. │ 球机 (PTZ Camera) │
  565. │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
  566. │ │ PTZ控制 │ -> │ 精确定位 │ -> │ 变焦放大 │ │
  567. │ └─────────┘ └─────────┘ └─────────┘ │
  568. └─────────────────────────────────────────────────────────────┘
  569. ▼ 变焦后的人体图像
  570. ┌─────────────────────────────────────────────────────────────┐
  571. │ 识别模块 (OCR) │
  572. │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
  573. │ │人体分割 │ -> │ 区域检测 │ -> │ OCR识别 │ -> 编号结果 │
  574. │ └─────────┘ └─────────┘ └─────────┘ │
  575. └─────────────────────────────────────────────────────────────┘
  576. 工作流程:
  577. 1. 全景摄像头实时获取视频流
  578. 2. 使用YOLO11检测画面中的人体
  579. 3. 计算人体在画面中的相对位置
  580. 4. 控制球机PTZ移动到对应位置
  581. 5. 球机变焦放大人体区域
  582. 6. 对人体进行分割,提取服装区域
  583. 7. 使用OCR识别服装上的编号
  584. 8. 输出识别结果
  585. 主要组件:
  586. - dahua_sdk.py: 大华SDK封装
  587. - panorama_camera.py: 全景摄像头和人体检测
  588. - ptz_camera.py: 球机PTZ控制
  589. - ocr_recognizer.py: 人体分割和OCR识别
  590. - coordinator.py: 联动控制逻辑
  591. """)
  592. print("=" * 60)
  593. print("\n使用方法:")
  594. print(" python main.py --panorama-ip 192.168.1.100 --ptz-ip 192.168.1.101")
  595. print(" python main.py --interactive # 交互模式")
  596. print(" python main.py --demo # 演示说明")
  597. if __name__ == '__main__':
  598. sys.exit(main() or 0)