1 import sys 2 from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QPushButton, QLabel, QLineEdit, QTextEdit 3 from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QColor, QPalette 4 from PyQt5.QtMultimedia import QSound 5 from PyQt5.QtCore import Qt, QThread, pyqtSignal, pyqtSlot, QTimer,QMetaObject, QMetaMethod 6 import cv2 7 import time 8 import cProfile 9 from datetime import datetime 10 import numpy as np 11 12 class VideoThread(QThread): 13 change_pixmap_signal = pyqtSignal(QImage) 14 change_zoom_signal = pyqtSignal(QImage) 15 change_fps_signal = pyqtSignal(str) 16 change_process_time_signal = pyqtSignal(float) 17 motion_detected_signal = pyqtSignal(bool, np.ndarray) 18 error_signal = pyqtSignal(str) 19 connection_status_signal = pyqtSignal(bool) # 定义新的信号 20 21 22 def __init__(self, get_coordinates, get_zoom_factor,get_address): 23 super().__init__() 24 self.get_coordinates = get_coordinates 25 self.get_zoom_factor = get_zoom_factor 26 self.get_address=get_address 27 self.fgbg = cv2.createBackgroundSubtractorMOG2() 28 self.motion_detected = False 29 self.stream_connected = False # 新增标志,表示视频流是否已经连接 30 self.prev_frame = None # 新增变量,用于保存前一帧 31 32 33 def run(self): 34 frame_counter = 0 # 新增帧计数器 35 max_retries = 5 # 最大重试次数 36 retry_delay = 1 # 重试延迟,单位为秒 37 retries = 0 # 当前的重试次数 38 while True: 39 40 cap = cv2.VideoCapture(self.get_address()) 41 42 43 if not cap.isOpened(): 44 if retries < max_retries: 45 time.sleep(retry_delay) # 等待一段时间再尝试重新连接 46 retries += 1 47 continue 48 else: 49 print('Failed to connect to video stream after %s retries.' % retries) 50 self.error_signal.emit('重连 %s 次失败.' % retries) 51 return 52 self.video_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) 53 self.video_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) 54 prev_time = time.time() 55 56 while True: 57 58 if cap.get(cv2.CAP_PROP_FPS) == 0: # 如果获取帧率失败,表示视频流已经中断 59 if self.stream_connected: 60 self.connection_status_signal.emit(False) 61 self.stream_connected = False 62 break 63 64 start_time = time.time() # 开始处理的时间 65 ret, frame = cap.read() 66 67 if not ret: 68 if self.stream_connected: # 如果之前的状态是已连接 69 self.connection_status_signal.emit(False) # 发出连接状态改变的信号 70 self.stream_connected = False # 如果读取帧失败,设置视频流未连接 71 break 72 73 if not self.stream_connected: # 如果之前的状态是未连接 74 self.connection_status_signal.emit(True) # 发出连接状态改变的信号 75 self.stream_connected = True # 如果读取帧成功,设置视频流已连接 76 77 78 79 80 81 frame_counter += 1 # 每处理一帧视频,帧计数器就加1 82 83 if frame_counter % 3 != 0: # 如果帧计数器不是5的倍数,跳过这一帧 84 continue 85 rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 86 h, w, ch = rgb_image.shape 87 bytes_per_line = ch * w 88 convert_to_Qt_format = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) 89 p = convert_to_Qt_format.scaled(270, 480, Qt.KeepAspectRatio) 90 self.change_pixmap_signal.emit(p) 91 92 x, y, width, height = self.get_coordinates() 93 zoom_image = rgb_image[y:y+height, x:x+width] 94 zoom_image = cv2.GaussianBlur(zoom_image, (7, 7), 1) 95 #锐化边缘 96 laplacian = cv2.Laplacian(zoom_image, cv2.CV_64F) 97 zoom_image = cv2.convertScaleAbs(zoom_image - laplacian) 98 99 #-------------------------------- 100 fgmask = self.fgbg.apply(zoom_image) 101 contours, _ = cv2.findContours(fgmask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 102 103 104 105 motion_detected = False 106 for contour in contours: 107 if cv2.contourArea(contour) > 300: 108 x, y, w, h = cv2.boundingRect(contour) 109 cv2.rectangle(zoom_image, (x, y), (x+w, y+h), (0, 255, 0), 2) 110 motion_detected = True 111 112 self.motion_detected = motion_detected 113 self.motion_detected_signal.emit(motion_detected,zoom_image) 114 # 使用帧差法检测运动 115 #-------------------------------- 116 # gray = cv2.cvtColor(zoom_image, cv2.COLOR_BGR2GRAY) 117 118 # if self.prev_frame is not None: 119 # frame_diff = cv2.absdiff(self.prev_frame, gray) 120 # _, fgmask = cv2.threshold(frame_diff, 25, 255, cv2.THRESH_BINARY) 121 # contours, _ = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 122 123 # motion_detected = False 124 # for contour in contours: 125 # if cv2.contourArea(contour) > 300: 126 # x, y, w, h = cv2.boundingRect(contour) 127 # cv2.rectangle(zoom_image, (x, y), (x+w, y+h), (0, 255, 0), 2) 128 # motion_detected = True 129 130 # self.motion_detected = motion_detected 131 # self.motion_detected_signal.emit(motion_detected, zoom_image) 132 133 # self.prev_frame = gray # 保存当前帧,以便下一次循环使用 134 #-------------------------------- 135 136 137 138 139 140 #-------------------------------- 141 142 143 144 zoom_factor = self.get_zoom_factor() 145 zoom_image = cv2.resize(zoom_image, None, fx=zoom_factor, fy=zoom_factor) 146 h, w, ch = zoom_image.shape 147 bytes_per_line = ch * w 148 convert_to_Qt_format = QImage(zoom_image.data, w, h, bytes_per_line, QImage.Format_RGB888) 149 self.change_zoom_signal.emit(convert_to_Qt_format) 150 end_time = time.time() # 结束处理的时间 151 self.change_process_time_signal.emit(end_time - start_time) # 发送处理时间 152 153 curr_time = time.time() 154 fps = 1 / (curr_time - prev_time) 155 prev_time = curr_time 156 self.change_fps_signal.emit('FPS 帧率: %s' % str(int(fps))) 157 cap.release() # 释放VideoCapture对象 158 159 160 class App(QWidget): 161 def __init__(self): 162 super().__init__() 163 self.setWindowTitle("Glass Flow Monitor ver0.0") 164 self.disply_width = 270 165 self.display_height = 480 166 self.image_label = QLabel(self) 167 self.image_label.resize(self.disply_width, self.display_height) 168 self.zoom_label = QLabel(self) 169 self.fps_label = QLabel(self) 170 self.process_time_label = QLabel(self) 171 self.alarm_delay_input = QLineEdit(self) 172 self.alarm_delay_input.setText('10') 173 self.alarm_countdown_label = QLabel(self) 174 self.alarm_text = QTextEdit(self) 175 self.alarm_text.setReadOnly(True) 176 self.x_input = QLineEdit(self) 177 self.x_input.setText('320') 178 self.y_input = QLineEdit(self) 179 self.y_input.setText('490') 180 self.width_input = QLineEdit(self) 181 self.width_input.setText('100') 182 self.height_input = QLineEdit(self) 183 self.height_input.setText('150') 184 self.zoom_input = QLineEdit(self) 185 self.zoom_input.setText('2') 186 self.ip_address_input=QLineEdit(self) 187 self.ip_address_input.setText('192.168.1.1') 188 self.ip_address_input.textChanged.connect(self.change_address) 189 190 191 self.thread = self.create_video_thread() # 使用新的方法创建 VideoThread 192 193 self.layout = QHBoxLayout() 194 self.setLayout(self.layout) 195 196 self.left_layout = QVBoxLayout() 197 self.left_layout.addWidget(self.image_label) 198 199 self.input_layout = QGridLayout() 200 201 self.input_layout.addWidget(QLabel('检测区X坐标:'), 0, 0) 202 self.input_layout.addWidget(self.x_input, 0, 1) 203 self.input_layout.addWidget(QLabel('检测区Y坐标:'), 1, 0) 204 self.input_layout.addWidget(self.y_input, 1, 1) 205 self.input_layout.addWidget(QLabel('检测区宽 Width:'), 2, 0) 206 self.input_layout.addWidget(self.width_input, 2, 1) 207 self.input_layout.addWidget(QLabel('检测区高 Height:'), 3, 0) 208 self.input_layout.addWidget(self.height_input, 3, 1) 209 self.input_layout.addWidget(QLabel('缩放 Zoom:'), 4, 0) 210 self.input_layout.addWidget(self.zoom_input, 4, 1) 211 self.left_layout.addLayout(self.input_layout) 212 213 self.input_layout.addWidget(QLabel('IP:'), 5, 0) 214 self.input_layout.addWidget(self.ip_address_input, 5, 1) 215 self.left_layout.addLayout(self.input_layout) 216 217 self.layout.addLayout(self.left_layout) 218 219 self.middle_layout = QVBoxLayout() 220 self.middle_layout.addWidget(self.zoom_label) 221 self.middle_layout.addWidget(self.fps_label) 222 self.middle_layout.addWidget(self.process_time_label) 223 self.middle_layout.addWidget(QLabel('Alarm Delay 报警阈值(S):')) 224 self.middle_layout.addWidget(self.alarm_delay_input) 225 226 self.middle_layout.addWidget(self.alarm_countdown_label) 227 self.middle_layout.addWidget(self.alarm_text) 228 self.middle_layout.addStretch() 229 230 self.layout.addLayout(self.middle_layout) 231 232 233 self.password_input = QLineEdit(self) 234 self.password_input.setEchoMode(QLineEdit.Password) # 设置为密码输入框 235 self.login_button = QPushButton('Login 登录', self) 236 self.login_button.clicked.connect(self.login) 237 self.middle_layout.addWidget(QLabel('Password 密码:')) 238 self.middle_layout.addWidget(self.password_input) 239 self.middle_layout.addWidget(self.login_button) 240 241 self.logout_timer = QTimer(self) 242 self.logout_timer.timeout.connect(self.logout) 243 self.logout_timer.start(30000) # 设置定时器超时时间为1分钟 244 245 246 247 248 249 250 self.btn_quit = QPushButton('Quit 关闭', self) 251 self.btn_quit.clicked.connect(self.close) 252 self.middle_layout.addWidget(self.btn_quit) 253 254 self.alarm_timer = QTimer(self) 255 self.alarm_timer.timeout.connect(self.on_alarm_timeout) 256 self.alarm_time = None 257 self.alarm_timer.start(1000) 258 self.alarm_generated = False # 新增标志,表示是否已经生成了报警信息 259 260 self.alarm_sound = QSound('alarm.wav') # 报警音,需要一个.wav文件 261 self.alarm_sound.setLoops(-1) # 设置无限循环 262 self.alarm_playing = False # 新增标志位,表示是否正在播放报警音 263 self.acknowledge_button = QPushButton('Acknowledge Alarm 确认报警', self) # 新增确认按钮 264 self.acknowledge_button.clicked.connect(self.acknowledge_alarm) 265 self.middle_layout.addWidget(self.acknowledge_button) 266 267 self.enable_controls(False) # 初始化时禁用所有的控件 268 self.thread.error_signal.connect(self.update_alarm_text) 269 270 271 self.alarm_image_saved = False # 新增标志位,表示是否已经保存过报警图片 272 273 274 275 276 277 def acknowledge_alarm(self): 278 self.alarm_playing = False # 复位标志位 279 self.alarm_sound.stop() # 停止播放报警音 280 281 def login(self): 282 password = self.password_input.text() 283 if password == '8888': # 这里替换为你的密码 284 self.enable_controls(True) 285 self.reset_logout_timer() 286 287 def logout(self): 288 self.enable_controls(False) 289 290 def reset_logout_timer(self): 291 self.logout_timer.start(30000) # 重置定时器 292 293 294 def enable_controls(self, enabled): 295 self.x_input.setEnabled(enabled) 296 self.y_input.setEnabled(enabled) 297 self.width_input.setEnabled(enabled) 298 self.height_input.setEnabled(enabled) 299 self.zoom_input.setEnabled(enabled) 300 self.alarm_delay_input.setEnabled(enabled) 301 self.btn_quit.setEnabled(enabled) 302 self.acknowledge_button.setEnabled(True) # 报警确认按钮总是可用 303 304 305 def create_video_thread(self): 306 # 创建一个新的 VideoThread 307 thread = VideoThread(self.get_coordinates, self.get_zoom_factor, self.get_address) 308 thread.change_pixmap_signal.connect(self.update_image) 309 thread.change_zoom_signal.connect(self.update_zoom) 310 thread.change_fps_signal.connect(self.update_fps) 311 thread.change_process_time_signal.connect(self.update_process_time) 312 thread.motion_detected_signal.connect(self.on_motion_detected) 313 thread.start() 314 return thread 315 316 def change_address(self): 317 # 停止当前的 VideoThread 318 self.thread.terminate() 319 # 创建一个新的 VideoThread 320 self.thread = self.create_video_thread() 321 322 323 324 @pyqtSlot(QImage) 325 def update_image(self, qt_image): 326 x = int(self.x_input.text()) 327 y = int(self.y_input.text()) 328 width = int(self.width_input.text()) 329 height = int(self.height_input.text()) 330 x_scaled = int(x * 270 / self.thread.video_width) 331 y_scaled = int(y * 480 / self.thread.video_height) 332 width_scaled = int(width * 270 / self.thread.video_width) 333 height_scaled = int(height * 480 / self.thread.video_height) 334 painter_instance = QPainter(qt_image) 335 painter_instance.setPen(QPen(Qt.blue, 3)) 336 painter_instance.drawRect(x_scaled, y_scaled, width_scaled, height_scaled) 337 self.image_label.setPixmap(QPixmap.fromImage(qt_image)) 338 339 @pyqtSlot(QImage) 340 def update_zoom(self, qt_image): 341 self.zoom_label.setPixmap(QPixmap.fromImage(qt_image)) 342 343 @pyqtSlot(str) 344 def update_fps(self, fps): 345 self.fps_label.setText(fps) 346 347 @pyqtSlot(float) 348 def update_process_time(self, process_time): 349 self.process_time_label.setText('Process time 处理时间: %.1f ms' % (process_time * 1000)) 350 351 @pyqtSlot(bool, np.ndarray) 352 def on_motion_detected(self, motion_detected, image): 353 if motion_detected and self.thread.stream_connected: 354 self.alarm_time = time.time() 355 self.alarm_image = image # 保存截取的图像,以便在需要时保存为文件 356 357 @pyqtSlot(str) 358 def update_alarm_text(self, text): 359 self.alarm_text.append(text) 360 361 362 @pyqtSlot(bool) 363 def update_connection_status(self, connected): 364 if connected: 365 self.connection_status_light.setStyleSheet('background-color: green; border-radius: 10px;') # 设置标签的背景颜色为绿色 366 self.connection_status_text.setText('Connected') 367 else: 368 self.connection_status_light.setStyleSheet('background-color: red; border-radius: 10px;') # 设置标签的背景颜色为红色 369 self.connection_status_text.setText('Disconnected') 370 371 @pyqtSlot() 372 def on_alarm_timeout(self): 373 if self.alarm_time is not None and self.thread.stream_connected: # 如果视频流已经连接: 374 countdown = int(self.alarm_delay_input.text()) - int(time.time() - self.alarm_time) 375 if countdown <= 0: 376 if not self.alarm_generated: # 如果还没有生成报警信息 377 timestamp = datetime.now().strftime('%Y-%m-%d %H-%M-%S') 378 self.alarm_text.append('%s Alarm: 未检测到移动玻璃液 %s seconds.' % (timestamp, self.alarm_delay_input.text())) 379 self.alarm_generated = True # 设置标志,表示已经生成了报警信息 380 self.alarm_playing = True # 设置标志,表示应该播放报警音 381 self.alarm_sound.play() # 开始播放报警音 382 if not self.alarm_image_saved: # 如果还没有保存过报警图片 383 bgr_image = cv2.cvtColor(self.alarm_image, cv2.COLOR_RGB2BGR) # 将图像从 RGB 格式转换为 BGR 格式 384 try: 385 cv2.imwrite('alarm_%s.png' % timestamp, bgr_image) # 保存报警图片 386 except Exception as e: 387 print('Failed to save alarm image:', e) 388 cv2.imwrite('alarm_%s.png' % timestamp, bgr_image) # 保存报警图片 389 self.alarm_image_saved = True # 设置标志,表示已经保存过报警图片 390 391 palette = self.palette() 392 if palette.color(QPalette.Background) == Qt.red: 393 palette.setColor(QPalette.Background, Qt.yellow) 394 else: 395 palette.setColor(QPalette.Background, Qt.red) 396 self.setPalette(palette) 397 else: 398 self.alarm_generated = False # 重置标志,因为报警已经结束 399 self.alarm_image_saved = False # 重置标志,因为报警已经结束 400 self.acknowledge_alarm() # 停止播放报警音 401 palette = self.palette() 402 palette.setColor(QPalette.Background, Qt.white) 403 self.setPalette(palette) 404 self.alarm_countdown_label.setText('Alarm Countdown 倒计时(s): %s s' % countdown) 405 else: 406 palette = self.palette() 407 palette.setColor(QPalette.Background, Qt.white) 408 self.setPalette(palette) 409 self.alarm_countdown_label.setText('Alarm Countdown 倒计时(s):: no motion detected') 410 self.acknowledge_alarm() # 停止播放报警音 411 412 def closeEvent(self, event): 413 self.thread.terminate() 414 415 def get_coordinates(self): 416 return int(self.x_input.text()), int(self.y_input.text()), int(self.width_input.text()), int(self.height_input.text()) 417 418 def get_zoom_factor(self): 419 return float(self.zoom_input.text()) 420 421 def get_address(self): 422 PATH='rtsp://root:root@'+str(self.ip_address_input.text())+':554/axis-media/media.amp' 423 print(PATH) 424 return PATH 425 426 def main(): 427 start_time = time.time() 428 app = QApplication(sys.argv) 429 ex = App() 430 ex.show() 431 sys.exit(app.exec_()) 432 print("Total execution time: %.2f ms" % ((time.time() - start_time) * 1000)) 433 434 if __name__ == '__main__': 435 cProfile.run('main()')
标签:layout,Pyhon,self,翻板,zoom,time,input,image,Axis From: https://www.cnblogs.com/AutomationDoge/p/18600011