main.py 25 KB

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