Son aktivite 1782445070

Revizyon 28505731d986ef1d3c7e48160cbcd15444dced7e

第一版主程序.py Ham
1import cv2
2import threading
3import numpy as np
4import time
5import os
6import serial
7
8if 'DISPLAY' not in os.environ:
9 os.environ['DISPLAY'] = ':0'
10 print(f"Set DISPLAY to: {os.environ['DISPLAY']}")
11import pyautogui
12
13# ---------- 1. 固定参数区 ----------
14MODEL_PATH = './yolov5s-640-640.rknn' # 模型路径
15TARGET = 'rk3588' # 目标设备
16
17IMG_SHOW = True # 是否弹窗显示结果
18IMG_SAVE = False # 是否保存结果图片
19COCO_MAP_TEST = False # 是否跑COCO mAP测试
20FULLSCREEN = True # 是否全屏显示
21
22SOURCES_FILE = './sources.json' # 视频源配置文件
23ANCHORS_FILE = './model/anchors_yolov5.txt' # anchor文件
24
25# 优化参数
26DETECTION_INTERVAL = 5 # 检测间隔(每5帧检测一次)
27DISPLAY_REFRESH_RATE = 30 # 显示刷新率(Hz)
28
29# 屏幕尺寸
30SCREEN_WIDTH = 1024
31SCREEN_HEIGHT = 600
32
33# 显示区域尺寸
34VIDEO_WIDTH = int(SCREEN_WIDTH * 0.75) # 左侧四分之三显示视频
35RADIO_WIDTH = SCREEN_WIDTH - VIDEO_WIDTH # 右侧四分之一显示雷达
36
37# ---------- 2. 雷达触发参数 ----------
38ALARM_DISTANCE = 1.5 # 报警距离(米)
39SWITCH_INTERVAL = 3.0 # 切换间隔(秒)
40
41# ---------- 3. 其他全局常量 ----------
42OBJ_THRESH = 0.6
43NMS_THRESH = 0.6
44IMG_SIZE = (640, 640) # (width, height)
45
46CLASSES = ("person", "bicycle", "car", "motorbike ", "aeroplane ", "bus ", "train", "truck ", "boat", "traffic light")
47
48# --------------------------
49# RTSP流配置部分 - 简化版本
50# --------------------------
51RTSP_URLS = [
52 "rtsp://admin:Admin888@192.168.112.200:554/streaming/channels/102",
53 "rtsp://admin:Admin888@192.168.112.201:554/streaming/channels/102",
54 "rtsp://admin:Admin888@192.168.112.202:554/streaming/channels/102",
55 "rtsp://admin:Admin888@192.168.112.203:554/streaming/channels/102"
56]
57
58# 全局变量
59frames = [None] * 4
60locks = [threading.Lock() for _ in range(4)]
61stop_threads = False
62combined_frame = None
63combined_lock = threading.Lock()
64
65# 显示模式控制
66display_mode = 0 # 0:四分屏全屏, 1-4:单画面分屏
67last_switch_time = time.time()
68current_alarm_index = 0
69
70# 雷达数据相关
71radar_distances = [999.0] * 4 # 存储4路雷达距离(米)
72radar_alarm_status = [False] * 4 # 雷达报警状态
73radar_lock = threading.Lock()
74
75# 雷达图片相关
76radio_images = {} # 存储4个雷达图片
77current_radio_image = None
78
79# 简化的流状态管理
80stream_status = [False] * 4 # 每路流的连接状态
81stream_last_frame_time = [0] * 4 # 每路流最后收到帧的时间
82
83
84# ---------- 简化的RTSP流处理 ----------
85def read_rtsp_stream(index, rtsp_url):
86 """简化的RTSP流读取函数"""
87 global stop_threads
88
89 cap = None
90 reconnect_count = 0
91 max_reconnect = 20
92
93 print(f"[Stream {index}] 启动RTSP流: {rtsp_url}")
94
95 while not stop_threads and reconnect_count < max_reconnect:
96 try:
97 # 释放之前的连接
98 if cap is not None:
99 cap.release()
100 cap = None
101
102 # 创建新的连接
103 print(f"[Stream {index}] 尝试连接...")
104 cap = cv2.VideoCapture(rtsp_url)
105
106 # 设置连接参数
107 cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
108 cap.set(cv2.CAP_PROP_FPS, 15)
109 cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'H264'))
110 cap.set(cv2.CAP_PROP_OPEN_TIMEOUT_MSEC, 5000)
111 cap.set(cv2.CAP_PROP_READ_TIMEOUT_MSEC, 3000)
112
113 if not cap.isOpened():
114 print(f"[Stream {index}] 连接失败")
115 reconnect_count += 1
116 time.sleep(3)
117 continue
118
119 print(f"[Stream {index}] ✓ 连接成功")
120 stream_status[index] = True
121 reconnect_count = 0
122
123 # 持续读取帧
124 while not stop_threads and cap.isOpened():
125 ret, frame = cap.read()
126
127 if ret:
128 with locks[index]:
129 frames[index] = frame.copy()
130 stream_last_frame_time[index] = time.time()
131 stream_status[index] = True
132 else:
133 print(f"[Stream {index}] ✗ 读取帧失败")
134 stream_status[index] = False
135 break
136
137 # 控制读取频率
138 time.sleep(0.03)
139
140 except Exception as e:
141 print(f"[Stream {index}] 异常: {e}")
142 stream_status[index] = False
143
144 # 连接断开,准备重连
145 if cap is not None:
146 cap.release()
147 cap = None
148
149 reconnect_count += 1
150 print(f"[Stream {index}] 等待重连 ({reconnect_count}/{max_reconnect})...")
151 time.sleep(3)
152
153 print(f"[Stream {index}] 线程退出")
154
155
156# ---------- 简化的画面合成函数 ----------
157def combine_frames():
158 global combined_frame, stop_threads
159
160 while not stop_threads:
161 frame_list = []
162 current_time = time.time()
163
164 for i in range(4):
165 with locks[i]:
166 # 检查流是否超时(5秒无数据认为超时)
167 if current_time - stream_last_frame_time[i] > 5.0:
168 stream_status[i] = False
169 frame_list.append(None)
170 elif frames[i] is not None:
171 frame_list.append(frames[i])
172 else:
173 frame_list.append(None)
174
175 # 检查是否有有效帧
176 valid_frames = [frame for frame in frame_list if frame is not None]
177 if len(valid_frames) == 0:
178 time.sleep(0.1)
179 continue
180
181 # 获取参考尺寸(使用第一个有效帧)
182 first_valid_frame = next(frame for frame in frame_list if frame is not None)
183 h, w, _ = first_valid_frame.shape
184
185 # 创建合成画面
186 combined = np.zeros((h * 2, w * 2, 3), dtype=np.uint8)
187
188 positions = [
189 (0, 0), # 左上 - Stream 0
190 (0, w), # 右上 - Stream 1
191 (h, 0), # 左下 - Stream 2
192 (h, w) # 右下 - Stream 3
193 ]
194
195 for i, (y, x) in enumerate(positions):
196 if frame_list[i] is not None and stream_status[i]:
197 resized_frame = cv2.resize(frame_list[i], (w, h))
198 combined[y:y + h, x:x + w] = resized_frame
199
200 # 添加状态指示器
201 status_color = (0, 255, 0) # 绿色 - 在线
202 status_text = f"Cam{i + 1}"
203 else:
204 # 显示无信号画面
205 no_signal = create_single_no_signal(i + 1, w, h)
206 combined[y:y + h, x:x + w] = no_signal
207 status_color = (0, 0, 255) # 红色 - 离线
208 status_text = f"Cam{i + 1}"
209
210 # 在画面角落添加状态指示
211 cv2.putText(combined, status_text, (x + 10, y + 30),
212 cv2.FONT_HERSHEY_SIMPLEX, 0.6, status_color, 2)
213
214 with combined_lock:
215 combined_frame = combined.copy()
216
217 time.sleep(0.03)
218
219
220# ---------- 雷达串口读取类 ----------
221class RadarUARTReader:
222 def __init__(self, port: str = '/dev/ttyS0', baudrate: int = 115200):
223 self.port = port
224 self.baudrate = baudrate
225 self.ser = None
226 self.running = False
227 self.receive_thread = None
228
229 def start(self) -> bool:
230 """启动雷达UART读取"""
231 try:
232 self.ser = serial.Serial(
233 port=self.port,
234 baudrate=self.baudrate,
235 bytesize=8,
236 parity='N',
237 stopbits=1,
238 timeout=0.1
239 )
240
241 self.running = True
242 self.receive_thread = threading.Thread(target=self._receive_worker)
243 self.receive_thread.daemon = True
244 self.receive_thread.start()
245
246 print(f"雷达数据读取器已启动: {self.port} @ {self.baudrate}bps")
247 return True
248
249 except Exception as e:
250 print(f"雷达启动失败: {e}")
251 return False
252
253 def _parse_frame(self, frame_data: bytearray):
254 """解析雷达数据帧"""
255 if len(frame_data) != 16:
256 return None
257
258 # 验证帧头帧尾
259 if frame_data[0] != 0xA5 or frame_data[1] != 0x5A or frame_data[14] != 0x5A or frame_data[15] != 0xA5:
260 return None
261
262 radar_data_list = []
263 for i in range(4):
264 start_idx = 2 + i * 3
265 radar_id = frame_data[start_idx]
266 distance_high = frame_data[start_idx + 1]
267 distance_low = frame_data[start_idx + 2]
268
269 distance_mm = (distance_high << 8) | distance_low
270 distance_m = distance_mm / 1000.0
271
272 radar_data = {
273 'radar_id': radar_id,
274 'distance_mm': distance_mm,
275 'distance_m': distance_m
276 }
277 radar_data_list.append(radar_data)
278
279 return radar_data_list
280
281 def _receive_worker(self):
282 """接收数据工作线程"""
283 state = 0 # 0:等待头, 1:接收数据
284 frame_buffer = bytearray()
285
286 while self.running and self.ser and self.ser.is_open:
287 try:
288 bytes_to_read = min(self.ser.in_waiting, 1024)
289 if bytes_to_read > 0:
290 data = self.ser.read(bytes_to_read)
291
292 for byte in data:
293 if state == 0:
294 if byte == 0xA5:
295 frame_buffer = bytearray([byte])
296 state = 1
297 elif state == 1:
298 frame_buffer.append(byte)
299 if len(frame_buffer) == 16:
300 # 完整帧接收完成
301 radar_data = self._parse_frame(frame_buffer)
302 if radar_data:
303 self._update_radar_distances(radar_data)
304 state = 0
305 frame_buffer = bytearray()
306 elif len(frame_buffer) > 16:
307 state = 0
308 frame_buffer = bytearray()
309
310 except Exception as e:
311 print(f"雷达接收错误: {e}")
312 time.sleep(0.01)
313
314 def _update_radar_distances(self, radar_data_list):
315 """更新雷达距离数据"""
316 global radar_distances, radar_alarm_status
317
318 with radar_lock:
319 for data in radar_data_list:
320 radar_id = data['radar_id']
321 if 1 <= radar_id <= 4:
322 distance = data['distance_m']
323 radar_distances[radar_id - 1] = distance
324 radar_alarm_status[radar_id - 1] = (distance <= ALARM_DISTANCE and distance > 0)
325
326 # 打印报警信息
327 if radar_alarm_status[radar_id - 1]:
328 print(f"🚨 雷达{radar_id}报警! 距离: {distance:.2f}m")
329
330 def stop(self):
331 """停止读取"""
332 self.running = False
333 if self.ser and self.ser.is_open:
334 self.ser.close()
335 print("雷达数据读取器已停止")
336
337
338# ---------- 核心函数 ----------
339def filter_boxes(boxes, box_confidences, box_class_probs):
340 box_confidences = box_confidences.reshape(-1)
341 class_max_score = np.max(box_class_probs, axis=-1)
342 classes = np.argmax(box_class_probs, axis=-1)
343
344 person_mask = (classes == 0)
345 _class_pos = np.where((class_max_score * box_confidences >= OBJ_THRESH) & person_mask)
346 scores = (class_max_score * box_confidences)[_class_pos]
347
348 boxes = boxes[_class_pos]
349 classes = classes[_class_pos]
350
351 return boxes, classes, scores
352
353
354def nms_boxes(boxes, scores):
355 x = boxes[:, 0]
356 y = boxes[:, 1]
357 w = boxes[:, 2] - boxes[:, 0]
358 h = boxes[:, 3] - boxes[:, 1]
359
360 areas = w * h
361 order = scores.argsort()[::-1]
362
363 keep = []
364 while order.size > 0:
365 i = order[0]
366 keep.append(i)
367
368 xx1 = np.maximum(x[i], x[order[1:]])
369 yy1 = np.maximum(y[i], y[order[1:]])
370 xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
371 yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
372
373 w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
374 h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
375 inter = w1 * h1
376
377 ovr = inter / (areas[i] + areas[order[1:]] - inter)
378 inds = np.where(ovr <= NMS_THRESH)[0]
379 order = order[inds + 1]
380 keep = np.array(keep)
381 return keep
382
383
384def box_process(position, anchors):
385 grid_h, grid_w = position.shape[2:4]
386 col, row = np.meshgrid(np.arange(0, grid_w), np.arange(0, grid_h))
387 col = col.reshape(1, 1, grid_h, grid_w)
388 row = row.reshape(1, 1, grid_h, grid_w)
389 grid = np.concatenate((col, row), axis=1)
390 stride = np.array([IMG_SIZE[1] // grid_h, IMG_SIZE[0] // grid_w]).reshape(1, 2, 1, 1)
391
392 col = col.repeat(len(anchors), axis=0)
393 row = row.repeat(len(anchors), axis=0)
394 anchors = np.array(anchors)
395 anchors = anchors.reshape(*anchors.shape, 1, 1)
396
397 box_xy = position[:, :2, :, :] * 2 - 0.5
398 box_wh = pow(position[:, 2:4, :, :] * 2, 2) * anchors
399
400 box_xy += grid
401 box_xy *= stride
402 box = np.concatenate((box_xy, box_wh), axis=1)
403
404 xyxy = np.copy(box)
405 xyxy[:, 0, :, :] = box[:, 0, :, :] - box[:, 2, :, :] / 2
406 xyxy[:, 1, :, :] = box[:, 1, :, :] - box[:, 3, :, :] / 2
407 xyxy[:, 2, :, :] = box[:, 0, :, :] + box[:, 2, :, :] / 2
408 xyxy[:, 3, :, :] = box[:, 1, :, :] + box[:, 3, :, :] / 2
409
410 return xyxy
411
412
413def post_process(input_data, anchors):
414 boxes, scores, classes_conf = [], [], []
415 input_data = [_in.reshape([len(anchors[0]), -1] + list(_in.shape[-2:])) for _in in input_data]
416 for i in range(len(input_data)):
417 boxes.append(box_process(input_data[i][:, :4, :, :], anchors[i]))
418 scores.append(input_data[i][:, 4:5, :, :])
419 classes_conf.append(input_data[i][:, 5:, :, :])
420
421 def sp_flatten(_in):
422 ch = _in.shape[1]
423 _in = _in.transpose(0, 2, 3, 1)
424 return _in.reshape(-1, ch)
425
426 boxes = [sp_flatten(_v) for _v in boxes]
427 classes_conf = [sp_flatten(_v) for _v in classes_conf]
428 scores = [sp_flatten(_v) for _v in scores]
429
430 boxes = np.concatenate(boxes)
431 classes_conf = np.concatenate(classes_conf)
432 scores = np.concatenate(scores)
433
434 boxes, classes, scores = filter_boxes(boxes, scores, classes_conf)
435
436 nboxes, nclasses, nscores = [], [], []
437 for c in set(classes):
438 inds = np.where(classes == c)
439 b = boxes[inds]
440 c = classes[inds]
441 s = scores[inds]
442 keep = nms_boxes(b, s)
443
444 if len(keep) != 0:
445 nboxes.append(b[keep])
446 nclasses.append(c[keep])
447 nscores.append(s[keep])
448
449 if not nclasses and not nscores:
450 return None, None, None
451
452 boxes = np.concatenate(nboxes)
453 classes = np.concatenate(nclasses)
454 scores = np.concatenate(nscores)
455
456 return boxes, classes, scores
457
458
459def draw(image, boxes, scores, classes):
460 """正确的人体检测框绘制函数"""
461 if boxes is None or len(boxes) == 0:
462 return image
463
464 box_color = (0, 0, 255)
465 box_thickness = 3
466 text_color = (0, 0, 255)
467 text_thickness = 2
468
469 for box, score, cl in zip(boxes, scores, classes):
470 x1, y1, x2, y2 = [int(_b) for _b in box]
471
472 x1 = max(0, min(x1, image.shape[1] - 1))
473 y1 = max(0, min(y1, image.shape[0] - 1))
474 x2 = max(0, min(x2, image.shape[1] - 1))
475 y2 = max(0, min(y2, image.shape[0] - 1))
476
477 cv2.rectangle(image, (x1, y1), (x2, y2), box_color, box_thickness)
478
479 text = f'{CLASSES[cl]} {score:.2f}'
480 cv2.putText(image, text, (x1, y1 - 10),
481 cv2.FONT_HERSHEY_SIMPLEX, 0.6, text_color, text_thickness)
482
483 return image
484
485
486def setup_model(model_path, device_id=None):
487 """为每个NPU核心创建独立模型实例"""
488 if model_path.endswith('.pt') or model_path.endswith('.torchscript'):
489 platform = 'pytorch'
490 from py_utils.pytorch_executor import Torch_model_container
491 model = Torch_model_container(model_path)
492 elif model_path.endswith('.rknn'):
493 platform = 'rknn'
494 from py_utils.rknn_executor import RKNN_model_container
495 model = RKNN_model_container(model_path, TARGET, device_id)
496 elif model_path.endswith('onnx'):
497 platform = 'onnx'
498 from py_utils.onnx_executor import ONNX_model_container
499 model = ONNX_model_container(model_path)
500 else:
501 raise RuntimeError(f"{model_path} is not rknn/pytorch/onnx model")
502 print(f'Model {model_path} initialized for device {device_id}')
503 return model, platform
504
505
506# ---------- COCO_test_helper类 ----------
507class COCO_test_helper:
508 def __init__(self, enable_letter_box=True):
509 self.enable_letter_box = enable_letter_box
510 self.image_ids = []
511 self.category_ids = []
512 self.bboxes = []
513 self.scores = []
514 self.pad_top = 0
515 self.pad_left = 0
516 self.scale_ratio = 1.0
517 self.original_shape = (0, 0)
518 self.new_shape = (0, 0)
519
520 def letter_box(self, im, new_shape, pad_color=(0, 0, 0)):
521 shape = im.shape[:2]
522 if isinstance(new_shape, int):
523 new_shape = (new_shape, new_shape)
524
525 r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
526 new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
527 dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
528
529 dw /= 2
530 dh /= 2
531
532 if shape[::-1] != new_unpad:
533 im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
534
535 top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
536 left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
537
538 self.pad_top = top
539 self.pad_left = left
540 self.scale_ratio = r
541 self.original_shape = shape
542 self.new_shape = new_shape
543
544 im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=pad_color)
545 return im
546
547 def get_real_box(self, boxes):
548 if boxes is None or len(boxes) == 0:
549 return boxes
550
551 real_boxes = []
552 for box in boxes:
553 x1, y1, x2, y2 = box
554
555 x1 = (x1 - self.pad_left) / self.scale_ratio
556 y1 = (y1 - self.pad_top) / self.scale_ratio
557 x2 = (x2 - self.pad_left) / self.scale_ratio
558 y2 = (y2 - self.pad_top) / self.scale_ratio
559
560 x1 = max(0, min(x1, self.original_shape[1]))
561 y1 = max(0, min(y1, self.original_shape[0]))
562 x2 = max(0, min(x2, self.original_shape[1]))
563 y2 = max(0, min(y2, self.original_shape[0]))
564
565 real_boxes.append([x1, y1, x2, y2])
566
567 return np.array(real_boxes)
568
569
570# ---------- 加载雷达图片 ----------
571def load_radio_images():
572 """加载1-4号雷达图片"""
573 global radio_images
574
575 for i in range(1, 5):
576 try:
577 img_path = f'{i}.png'
578 radio_img = cv2.imread(img_path)
579 if radio_img is None:
580 print(f"Warning: Cannot load {img_path}, creating default radar image")
581 radio_img = create_default_radio(i)
582 else:
583 print(f"✓ Loaded radar image: {img_path}")
584
585 radio_images[i] = cv2.resize(radio_img, (RADIO_WIDTH, SCREEN_HEIGHT))
586 except Exception as e:
587 print(f"Failed to load radar image {i}.png: {e}")
588 radio_images[i] = create_default_radio(i)
589
590 return len(radio_images) > 0
591
592
593def create_default_radio(radar_id):
594 """创建默认雷达图片"""
595 radio_img = np.zeros((SCREEN_HEIGHT, RADIO_WIDTH, 3), dtype=np.uint8)
596 radio_img[:] = (20, 20, 20)
597
598 center_x = RADIO_WIDTH // 2
599 center_y = SCREEN_HEIGHT // 2
600 radius = min(RADIO_WIDTH, SCREEN_HEIGHT) // 3
601
602 # 绘制雷达圆圈
603 for r in range(radius, 0, -radius // 4):
604 color = (0, 255, 0)
605 cv2.circle(radio_img, (center_x, center_y), r, color, 2)
606
607 # 绘制雷达线
608 for angle in range(0, 360, 30):
609 rad = np.deg2rad(angle)
610 end_x = int(center_x + radius * np.cos(rad))
611 end_y = int(center_y + radius * np.sin(rad))
612 cv2.line(radio_img, (center_x, center_y), (end_x, end_y), (0, 255, 0), 1)
613
614 # 显示雷达编号
615 title = f"Radar {radar_id}"
616 title_size = cv2.getTextSize(title, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)[0]
617 title_x = (RADIO_WIDTH - title_size[0]) // 2
618 cv2.putText(radio_img, title, (title_x, 30),
619 cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
620
621 return radio_img
622
623
624# ---------- 雷达触发显示逻辑 ----------
625def update_display_mode_by_radar():
626 """根据雷达数据更新显示模式"""
627 global display_mode, last_switch_time, current_alarm_index, current_radio_image
628
629 current_time = time.time()
630
631 with radar_lock:
632 # 获取报警的雷达列表
633 alarm_radars = [i for i in range(4) if radar_alarm_status[i]]
634
635 if not alarm_radars:
636 # 没有报警,显示四分屏
637 if display_mode != 0:
638 display_mode = 0
639 current_radio_image = None
640 print("✓ No alarm, switching to Quad View")
641 return
642
643 # 有报警,检查是否需要切换
644 if current_time - last_switch_time >= SWITCH_INTERVAL:
645 if len(alarm_radars) == 1:
646 # 单路报警,直接显示
647 new_mode = alarm_radars[0] + 1
648 if display_mode != new_mode:
649 display_mode = new_mode
650 current_radio_image = radio_images.get(new_mode)
651 print(f"✓ Single alarm: Switching to Camera {new_mode}")
652 else:
653 # 多路报警,循环显示
654 current_alarm_index = (current_alarm_index + 1) % len(alarm_radars)
655 new_mode = alarm_radars[current_alarm_index] + 1
656 display_mode = new_mode
657 current_radio_image = radio_images.get(new_mode)
658 print(f"✓ Multiple alarms: Cycling to Camera {new_mode}")
659
660 last_switch_time = current_time
661
662
663# ---------- 人体检测处理 ----------
664def process_frame_for_detection(frame, model, platform, co_helper, anchors):
665 if frame is None:
666 return None, None, None
667
668 original_h, original_w = frame.shape[:2]
669 pad_color = (0, 0, 0)
670 img = co_helper.letter_box(im=frame.copy(), new_shape=(IMG_SIZE[1], IMG_SIZE[0]), pad_color=pad_color)
671 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
672
673 if platform in ['pytorch', 'onnx']:
674 input_data = img.transpose(2, 0, 1)
675 input_data = input_data.reshape(1, *input_data.shape).astype(np.float32) / 255.
676 else:
677 input_data = img
678
679 outputs = model.run([np.expand_dims(input_data, 0)])
680 boxes, classes, scores = post_process(outputs, anchors)
681
682 if boxes is not None:
683 boxes = co_helper.get_real_box(boxes)
684
685 return boxes, classes, scores
686
687
688# ---------- 窗口设置函数 ----------
689def setup_borderless_fullscreen_window():
690 window_name = 'RTSP Surveillance System'
691 cv2.setUseOptimized(True)
692 cv2.namedWindow(window_name, cv2.WND_PROP_FULLSCREEN)
693 cv2.resizeWindow(window_name, SCREEN_WIDTH, SCREEN_HEIGHT)
694 cv2.moveWindow(window_name, 0, 0)
695 cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
696 cv2.setWindowProperty(window_name, cv2.WND_PROP_TOPMOST, 1)
697
698 print(f"✓ Borderless fullscreen window created: {SCREEN_WIDTH}x{SCREEN_HEIGHT}")
699 print("✓ Window set to topmost and borderless")
700 return window_name
701
702
703def hide_mouse():
704 try:
705 pyautogui.moveTo(1024, 600)
706 pyautogui.moveRel(0, 0)
707 except Exception as e:
708 print(f"Note: Could not hide mouse cursor: {e}")
709
710
711# ---------- 创建显示帧 ----------
712def create_display_frame(display_mode, current_combined, detection_cache, frame_counter):
713 """根据显示模式创建显示帧"""
714 global current_radio_image, radar_distances
715
716 if display_mode == 0:
717 # 四分屏模式 - 全屏显示
718 if current_combined is None:
719 display_frame = create_no_signal_frame(SCREEN_WIDTH, SCREEN_HEIGHT)
720 else:
721 display_frame = cv2.resize(current_combined, (SCREEN_WIDTH, SCREEN_HEIGHT))
722
723 if detection_cache['boxes'] is not None:
724 original_h, original_w = current_combined.shape[:2]
725 scale_x = SCREEN_WIDTH / original_w
726 scale_y = SCREEN_HEIGHT / original_h
727
728 scaled_boxes = []
729 for box in detection_cache['boxes']:
730 x1, y1, x2, y2 = box
731 scaled_boxes.append([
732 x1 * scale_x, y1 * scale_y,
733 x2 * scale_x, y2 * scale_y
734 ])
735
736 display_frame = draw(display_frame,
737 np.array(scaled_boxes),
738 detection_cache['scores'],
739 detection_cache['classes'])
740
741 # 显示雷达状态
742 with radar_lock:
743 alarm_count = sum(radar_alarm_status)
744 radar_text = f"雷达警告: {alarm_count}/4"
745 cv2.putText(display_frame, radar_text, (10, 60),
746 cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)
747
748 else:
749 # 单画面模式 - 分屏显示
750 camera_id = display_mode
751
752 # 创建空白画布
753 display_frame = np.zeros((SCREEN_HEIGHT, SCREEN_WIDTH, 3), dtype=np.uint8)
754
755 # 左侧视频区域
756 with locks[camera_id - 1]:
757 single_frame = frames[camera_id - 1]
758
759 if single_frame is None or not stream_status[camera_id - 1]:
760 video_frame = create_single_no_signal(camera_id, VIDEO_WIDTH, SCREEN_HEIGHT)
761 else:
762 video_frame = cv2.resize(single_frame, (VIDEO_WIDTH, SCREEN_HEIGHT))
763
764 display_frame[0:SCREEN_HEIGHT, 0:VIDEO_WIDTH] = video_frame
765
766 # 右侧雷达区域
767 if current_radio_image is not None:
768 radar_display = current_radio_image.copy()
769
770 # 在雷达图片上叠加距离信息
771 with radar_lock:
772 distance = radar_distances[camera_id - 1]
773 alarm_status = radar_alarm_status[camera_id - 1]
774
775 # 显示距离信息
776 distance_text = f" {distance:.2f}m"
777
778 if alarm_status:
779 alarm_text = "接近预警!"
780 cv2.putText(radar_display, alarm_text + distance_text, (20, SCREEN_HEIGHT - 20),
781 cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 3)
782
783 display_frame[0:SCREEN_HEIGHT, VIDEO_WIDTH:SCREEN_WIDTH] = radar_display
784
785 # 显示报警状态
786 with radar_lock:
787 alarm_count = sum(radar_alarm_status)
788 alarm_text = f"雷达编号: {alarm_count}"
789 cv2.putText(display_frame, alarm_text, (10, 60),
790 cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
791
792 return display_frame
793
794
795def create_no_signal_frame(width, height):
796 frame = np.zeros((height, width, 3), dtype=np.uint8)
797 frame[:] = (40, 40, 40)
798
799 text = "无信号"
800 font_scale = 1.5
801 thickness = 3
802 color = (100, 100, 255)
803
804 text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, thickness)[0]
805 text_x = (width - text_size[0]) // 2
806 text_y = (height + text_size[1]) // 2
807
808 cv2.putText(frame, text, (text_x, text_y),
809 cv2.FONT_HERSHEY_SIMPLEX, font_scale, color, thickness)
810
811 return frame
812
813
814def create_single_no_signal(camera_id, width, height):
815 frame = np.zeros((height, width, 3), dtype=np.uint8)
816 frame[:] = (40, 40, 40)
817
818 camera_text = f"相机 {camera_id}"
819 no_signal_text = "无信号"
820
821 camera_size = cv2.getTextSize(camera_text, cv2.FONT_HERSHEY_SIMPLEX, 1.2, 2)[0]
822 camera_x = (width - camera_size[0]) // 2
823 camera_y = height // 2 - 40
824
825 cv2.putText(frame, camera_text, (camera_x, camera_y),
826 cv2.FONT_HERSHEY_SIMPLEX, 1.2, (100, 255, 100), 2)
827
828 no_signal_size = cv2.getTextSize(no_signal_text, cv2.FONT_HERSHEY_SIMPLEX, 1.5, 3)[0]
829 no_signal_x = (width - no_signal_size[0]) // 2
830 no_signal_y = camera_y + 60
831
832 cv2.putText(frame, no_signal_text, (no_signal_x, no_signal_y),
833 cv2.FONT_HERSHEY_SIMPLEX, 1.5, (100, 100, 255), 3)
834
835 status_text = "等待信号接入..."
836 status_size = cv2.getTextSize(status_text, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2)[0]
837 status_x = (width - status_size[0]) // 2
838 status_y = no_signal_y + 50
839 cv2.putText(frame, status_text, (status_x, status_y),
840 cv2.FONT_HERSHEY_SIMPLEX, 0.7, (100, 200, 255), 2)
841
842 return frame
843
844
845# ---------- 主显示和检测循环 ----------
846def main_detection_loop():
847 global stop_threads, combined_frame, display_mode
848
849 # 加载雷达图片
850 if not load_radio_images():
851 print("Warning: Radar images not loaded properly")
852
853 # 启动雷达读取器
854 radar_reader = RadarUARTReader('/dev/ttyS0', 115200)
855 if not radar_reader.start():
856 print("Warning: Radar reader failed to start")
857
858 # 加载 anchor
859 try:
860 with open(ANCHORS_FILE, 'r') as f:
861 values = [float(_v) for _v in f.readlines()]
862 anchors = np.array(values).reshape(3, -1, 2).tolist()
863 print(f"Using anchor file: {ANCHORS_FILE}")
864 except:
865 print("Warning: Cannot load anchor file, using default anchors")
866 anchors = []
867
868 # 初始化COCO帮助类
869 co_helper = COCO_test_helper(enable_letter_box=True)
870
871 # 加载模型
872 model, platform = setup_model(MODEL_PATH, device_id=0)
873 print("Model loaded, starting detection...")
874
875 # 设置无边框全屏窗口
876 if IMG_SHOW:
877 window_name = setup_borderless_fullscreen_window()
878 hide_mouse()
879
880 # 初始显示画面
881 initial_frame = np.zeros((SCREEN_HEIGHT, SCREEN_WIDTH, 3), dtype=np.uint8)
882 cv2.putText(initial_frame, "Initializing...", (SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2),
883 cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
884 cv2.imshow(window_name, initial_frame)
885 cv2.waitKey(100)
886
887 frame_counter = 0
888 detection_cache = {
889 'boxes': None,
890 'classes': None,
891 'scores': None,
892 'last_frame': -1
893 }
894
895 try:
896 print("Entering main display loop...")
897
898 while not stop_threads:
899 # 根据雷达数据更新显示模式
900 update_display_mode_by_radar()
901
902 # 获取最新的合成帧
903 with combined_lock:
904 current_combined = combined_frame.copy() if combined_frame is not None else None
905
906 # 只在四分屏模式下进行人体检测
907 if display_mode == 0 and frame_counter % DETECTION_INTERVAL == 0:
908 if current_combined is not None:
909 boxes, classes, scores = process_frame_for_detection(
910 current_combined, model, platform, co_helper, anchors)
911 detection_cache.update({
912 'boxes': boxes,
913 'classes': classes,
914 'scores': scores,
915 'last_frame': frame_counter
916 })
917
918 # 创建并显示帧
919 if IMG_SHOW:
920 try:
921 display_frame = create_display_frame(display_mode, current_combined,
922 detection_cache, frame_counter)
923 cv2.imshow(window_name, display_frame)
924
925 # 只处理ESC键退出
926 key = cv2.waitKey(1) & 0xFF
927 if key == 27: # ESC键退出
928 stop_threads = True
929 break
930
931 except Exception as e:
932 print(f"Display error: {e}")
933
934 frame_counter += 1
935 time.sleep(0.01)
936
937 finally:
938 if IMG_SHOW:
939 cv2.destroyAllWindows()
940 if hasattr(model, 'release'):
941 model.release()
942 radar_reader.stop()
943 print("Detection loop ended")
944
945
946# ---------- 主函数 ----------
947if __name__ == "__main__":
948 os.environ['DISPLAY'] = ':0'
949
950 print("=" * 60)
951 print("RTSP监控系统 - 简化版本")
952 print("=" * 60)
953 print(f"RTSP URLs:")
954 for i, url in enumerate(RTSP_URLS):
955 print(f" Camera {i + 1}: {url}")
956 print("=" * 60)
957
958 # 初始化时间戳
959 current_time = time.time()
960 for i in range(4):
961 stream_last_frame_time[i] = current_time
962
963 # 启动四个RTSP流读取线程
964 rtsp_threads = []
965 for i in range(4):
966 t = threading.Thread(target=read_rtsp_stream, args=(i, RTSP_URLS[i]))
967 t.daemon = True
968 t.start()
969 rtsp_threads.append(t)
970 print(f"✓ 启动RTSP线程 {i}")
971 time.sleep(1) # 错开连接时间
972
973 # 启动画面合成线程
974 combine_thread = threading.Thread(target=combine_frames)
975 combine_thread.daemon = True
976 combine_thread.start()
977 print("✓ 启动画面合成线程")
978
979 try:
980 # 在主线程中启动检测循环
981 main_detection_loop()
982 except KeyboardInterrupt:
983 print("用户中断")
984 except Exception as e:
985 print(f"程序错误: {e}")
986 finally:
987 stop_threads = True
988 time.sleep(2)
989 print("所有线程已停止,程序退出")