Explorar o código

提交代码更新

lsfoo hai 4 meses
pai
achega
68e260e7dd
Modificáronse 4 ficheiros con 115 adicións e 38 borrados
  1. 2 2
      config.ini
  2. 105 33
      hookvision/main_window.py
  3. 6 2
      hookvision/rtsp_player.py
  4. 2 1
      requirements.txt.py

+ 2 - 2
config.ini

@@ -44,7 +44,7 @@ count = 2
 
 [Bucket:0]
 id = 1
-description = 1#钢包位
+description = 1号钢包位
 code_x = 10000001
 code_y = 1
 left_rtsp = rtsp://admin:hd135792468@192.168.1.124:554/Streaming/Channels/101
@@ -54,7 +54,7 @@ right_topic = ds/channel/1
 
 [Bucket:1]
 id = 2
-description = 2#钢包位
+description = 2号钢包位
 code_x = 10000001
 code_y =2
 left_rtsp = rtsp://admin:12345@192.168.1.64:554/Streaming/Channels/201

+ 105 - 33
hookvision/main_window.py

@@ -1,4 +1,5 @@
 import os
+import time
 import logging
 import threading
 import queue
@@ -12,7 +13,6 @@ from .rtsp_player import RTSPPlayer
 from .rfid_client import RFIDClient
 from .modbus_client import ModbusClient
 from .mqtt_client import MQTTClient
-import pyttsx3
 
 logger = logging.getLogger(__name__)
 
@@ -54,11 +54,13 @@ class MainWindow(QMainWindow):
         self.prev_left_lug = None
         self.prev_right_lug = None
 
-        # 语音引擎
-        self.tts_engine = pyttsx3.init()
-        self.tts_engine.setProperty('rate', 150)
+        # 语音引擎(在工作线程内初始化)
         self.last_voice_state = None
+        self._last_hooking_voice_time = 0.0
         self.voice_enabled = True
+        self.ai_enabled = True
+        self._last_left_draw: list = []
+        self._last_right_draw: list = []
         self._voice_queue = queue.Queue()
         self._voice_thread = threading.Thread(target=self._voice_worker, daemon=True)
         self._voice_thread.start()
@@ -238,6 +240,11 @@ class MainWindow(QMainWindow):
         self.voice_checkbox.setStyleSheet("color: #606266; font-weight: bold;")
         self.voice_checkbox.stateChanged.connect(self.on_voice_toggle)
 
+        self.ai_checkbox = QCheckBox("AI启用")
+        self.ai_checkbox.setChecked(True)
+        self.ai_checkbox.setStyleSheet("color: #606266; font-weight: bold;")
+        self.ai_checkbox.stateChanged.connect(self.on_ai_toggle)
+
         self.auto_switch_btn = QPushButton("自动模式")
         self.auto_switch_btn.setCheckable(True)
         self.auto_switch_btn.setChecked(True)
@@ -249,6 +256,7 @@ class MainWindow(QMainWindow):
         status_layout.addWidget(self.hook_status_label)
         status_layout.addStretch()
         status_layout.addWidget(self.voice_checkbox)
+        status_layout.addWidget(self.ai_checkbox)
         status_layout.addWidget(self.auto_switch_btn)
         main_layout.addWidget(status_widget)
 
@@ -273,6 +281,33 @@ class MainWindow(QMainWindow):
     def on_voice_toggle(self, state):
         self.voice_enabled = (state == Qt.Checked)
 
+    def on_ai_toggle(self, state):
+        enabled = (state == Qt.Checked)
+        if enabled == self.ai_enabled:
+            return
+        self.ai_enabled = enabled
+        if not enabled:
+            self.hook_state = HookState.IDLE
+            self.prev_state = HookState.IDLE
+            self.state_counter = 0
+            self.left_objects = []
+            self.right_objects = []
+            self.prev_left_hook = None
+            self.prev_right_hook = None
+            self.prev_left_lug = None
+            self.prev_right_lug = None
+            self.update_ui_for_state(HookState.IDLE)
+            if self.left_player:
+                self.left_player.set_ai_objects([])
+            if self.right_player:
+                self.right_player.set_ai_objects([])
+        else:
+            # 恢复最近一帧的标注框
+            if self.left_player and self._last_left_draw:
+                self.left_player.set_ai_objects(self._last_left_draw)
+            if self.right_player and self._last_right_draw:
+                self.right_player.set_ai_objects(self._last_right_draw)
+
     def _speak(self, msg: str):
         self._voice_queue.put(msg)
 
@@ -282,13 +317,19 @@ class MainWindow(QMainWindow):
             pythoncom.CoInitialize()
         except ImportError:
             pass
+        try:
+            import win32com.client as _win32com
+        except ImportError:
+            logging.warning("pywin32 未安装,语音播报不可用")
+            return
         while True:
             msg = self._voice_queue.get()
             if msg is None:
                 break
             try:
-                self.tts_engine.say(msg)
-                self.tts_engine.runAndWait()
+                speaker = _win32com.Dispatch("SAPI.SpVoice")
+                speaker.Rate = 2
+                speaker.Speak(msg)
             except Exception as e:
                 logging.warning(f"语音播报失败: {e}")
             self._voice_queue.task_done()
@@ -481,6 +522,7 @@ class MainWindow(QMainWindow):
         self.prev_left_lug = None
         self.prev_right_lug = None
         self.last_voice_state = None
+        self._last_hooking_voice_time = 0.0
 
         if bucket:
             left_cam = next((c for c in bucket.cameras if c.position == CameraPosition.LEFT), None)
@@ -599,6 +641,8 @@ class MainWindow(QMainWindow):
             return
         if not self.active_bucket:
             return
+        if not self.ai_enabled:
+            return
         for cam in self.active_bucket.cameras:
             if cam.topic == topic:
                 self.update_video_size()
@@ -612,22 +656,31 @@ class MainWindow(QMainWindow):
                     display_width = 640
                     display_height = 360
 
-                # 解析所有对象(包括 ready)用于状态判断
+                # 解析所有对象(包括 ready)
                 all_objects = self.parse_ai_objects(payload, display_width, display_height, include_ready=True)
-                # 解析仅用于绘制的对象(排除 ready)
-                draw_objects = self.parse_ai_objects(payload, display_width, display_height, include_ready=False)
 
                 if cam.position == CameraPosition.LEFT:
                     self.left_objects = all_objects
+                else:
+                    self.right_objects = all_objects
+
+                # 触发状态评估(会标记配对的 hook/lug)
+                self.evaluate_hook_state()
+
+                # 构造绘制对象(排除 ready,无关目标变淡)
+                draw_objects = [o for o in all_objects if 'ready' not in o['label'].lower()]
+                for obj in draw_objects:
+                    if not obj.get('paired'):
+                        obj['dimmed'] = True
+
+                if cam.position == CameraPosition.LEFT:
+                    self._last_left_draw = draw_objects
                     if self.left_player:
                         self.left_player.set_ai_objects(draw_objects)
                 else:
-                    self.right_objects = all_objects
+                    self._last_right_draw = draw_objects
                     if self.right_player:
                         self.right_player.set_ai_objects(draw_objects)
-
-                # 触发状态评估
-                self.evaluate_hook_state()
                 break
 
     # ---------- 挂钩状态评估 ----------
@@ -692,41 +745,54 @@ class MainWindow(QMainWindow):
             new_state = HookState.RELEASING
         else:
             any_ready = any('ready' in o['label'].lower() for o in self.left_objects + self.right_objects)
-            if any_ready:
+            has_intersecting_pair = self._has_intersecting_pair(left_pair, right_pair)
+            if any_ready and has_intersecting_pair:
                 new_state = HookState.READY
+            elif has_intersecting_pair:
+                new_state = HookState.HOOKING
             else:
-                intersecting = False
-                if left_pair:
-                    h, l = left_pair
-                    if self.rects_intersect(h, l):
-                        intersecting = True
-                if not intersecting and right_pair:
-                    h, l = right_pair
-                    if self.rects_intersect(h, l):
-                        intersecting = True
-                if intersecting:
-                    new_state = HookState.HOOKING
-                else:
-                    new_state = HookState.IDLE
+                new_state = HookState.IDLE
 
-        # 防抖
+        # 防抖(非对称:进入非空闲状态需 STATE_THRESHOLD 帧确认,回到空闲只需 1 帧)
+        threshold = 1 if new_state == HookState.IDLE else self.STATE_THRESHOLD
         if new_state == self.prev_state:
             self.state_counter += 1
         else:
             self.prev_state = new_state
             self.state_counter = 1
 
-        if self.state_counter >= self.STATE_THRESHOLD:
+        if self.state_counter >= threshold:
             if new_state != self.hook_state:
                 self.hook_state = new_state
                 self.update_ui_for_state(new_state)
             self.state_counter = 0
 
+        # 标记配对的 hook/lug(用于绘制时变淡无关目标)
+        for obj in self.left_objects + self.right_objects:
+            obj.pop('paired', None)
+        if left_pair:
+            left_pair[0]['paired'] = True
+            left_pair[1]['paired'] = True
+        if right_pair:
+            right_pair[0]['paired'] = True
+            right_pair[1]['paired'] = True
+
     def rects_intersect(self, a, b):
         ax1, ay1, ax2, ay2 = a['x'], a['y'], a['x']+a['w'], a['y']+a['h']
         bx1, by1, bx2, by2 = b['x'], b['y'], b['x']+b['w'], b['y']+b['h']
         return not (ax2 < bx1 or bx2 < ax1 or ay2 < by1 or by2 < ay1)
 
+    def _has_intersecting_pair(self, left_pair, right_pair):
+        if left_pair:
+            h, l = left_pair
+            if self.rects_intersect(h, l):
+                return True
+        if right_pair:
+            h, l = right_pair
+            if self.rects_intersect(h, l):
+                return True
+        return False
+
     # ---------- UI 更新与语音 ----------
     def update_ui_for_state(self, state):
         if state == HookState.IDLE:
@@ -760,10 +826,16 @@ class MainWindow(QMainWindow):
         self.right_label.setStyleSheet(style)
 
         # 语音播报(带开关和防重复)
-        if self.voice_enabled and state in (HookState.HOOKING, HookState.READY) and state != self.last_voice_state:
-            msg = "挂包中" if state == HookState.HOOKING else "挂包成功"
-            self._speak(msg)
-            self.last_voice_state = state
+        if self.voice_enabled and state in (HookState.HOOKING, HookState.READY):
+            now = time.time()
+            if state == HookState.HOOKING:
+                if state != self.last_voice_state or now - self._last_hooking_voice_time > 15:
+                    self._speak("挂包中")
+                    self.last_voice_state = state
+                    self._last_hooking_voice_time = now
+            elif state != self.last_voice_state:
+                self._speak("挂包成功")
+                self.last_voice_state = state
 
     # ---------- 定时任务 ----------
     def check_idle(self):

+ 6 - 2
hookvision/rtsp_player.py

@@ -114,8 +114,12 @@ class RTSPPlayer(QThread):
             h = obj.get('h', 0)
             label = obj.get('label', '')
             color = obj.get('color', (0, 255, 0))
-            cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
-            cv2.putText(frame, label, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
+            dimmed = obj.get('dimmed', False)
+            thickness = 1 if dimmed else 2
+            if dimmed:
+                color = tuple(c // 2 for c in color)
+            cv2.rectangle(frame, (x, y), (x+w, y+h), color, thickness)
+            cv2.putText(frame, label, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, thickness)
 
         # 转换为 QPixmap
         h, w, ch = frame.shape

+ 2 - 1
requirements.txt.py

@@ -1,4 +1,5 @@
 PySide6>=6.5.0
 opencv-python>=4.8.0
 pymodbus>=3.5.0
-paho-mqtt>=1.6.0
+paho-mqtt>=1.6.0
+pywin32>=306