如何在Python中将多个IP摄像头RTSP流窗口合并为一个?
合并多RTSP摄像头流到单个窗口
问题描述
我正在编写一个安防控制台程序,用来展示多个IP摄像头的RTSP流。目前通过多线程实现了每个流在独立窗口正常播放,但添加多个摄像头后窗口杂乱,希望合并到同一个大窗口。之前尝试用np.hstack合并导致流卡顿,用multiprocessing也因为while循环没成功。
原代码如下:
import cv2 import threading class camThread(threading.Thread): def __init__(self, previewName, camID): threading.Thread.__init__(self) self.previewName = previewName self.camID = camID def run(self): camPreview(self.previewName, self.camID) def camPreview(previewName, camID): # global frame cv2.namedWindow(previewName) cam = cv2.VideoCapture(camID) if cam.isOpened(): # try to get the first frame rval, frame = cam.read() else: rval = False while rval: frame = cv2.resize(frame, (620,400)) cv2.imshow(previewName, frame) rval, frame = cam.read() cv2.waitKey(20) thread1 = camThread("Camera 1", 'my-rtsp-link') thread2 = camThread("Camera 2", "my-second-rtsp-link") thread1.start() thread2.start()
解决方案
核心思路是让子线程仅负责捕获摄像头帧,将帧存储到线程安全的共享容器中;主线程负责统一读取所有帧、拼接成网格并显示。这样拆分职责后,既能避免子线程的显示冲突,也能把拼接的负载集中处理,解决之前的卡顿问题。
修改后的代码如下:
import cv2 import threading import numpy as np # 存储各摄像头的最新帧,线程安全需要锁保护 camera_frames = {} frame_lock = threading.Lock() class camThread(threading.Thread): def __init__(self, cam_name, rtsp_url, frame_size=(620, 400)): super().__init__() self.cam_name = cam_name self.rtsp_url = rtsp_url self.frame_size = frame_size self.running = True def run(self): cam = cv2.VideoCapture(self.rtsp_url) if not cam.isOpened(): print(f"无法打开摄像头: {self.cam_name}") return while self.running: rval, frame = cam.read() if not rval: print(f"{self.cam_name} 读取帧失败,尝试重连...") cam.release() cam = cv2.VideoCapture(self.rtsp_url) continue # 调整帧大小 frame_resized = cv2.resize(frame, self.frame_size) # 线程安全地更新共享帧字典 with frame_lock: camera_frames[self.cam_name] = frame_resized # 给摄像头读取留一点缓冲时间,避免占用过多CPU cv2.waitKey(10) cam.release() def main(): # 配置摄像头列表 cameras = [ ("Camera 1", "my-rtsp-link"), ("Camera 2", "my-second-rtsp-link") # 可以继续添加更多摄像头 ] # 启动所有摄像头线程 threads = [] for cam_name, rtsp_url in cameras: thread = camThread(cam_name, rtsp_url) threads.append(thread) thread.start() # 主线程负责拼接和显示 window_name = "安防控制台" cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) cv2.resizeWindow(window_name, 1280, 480) # 根据摄像头数量调整窗口大小 try: while True: with frame_lock: # 复制当前所有摄像头的帧,避免读取时被修改 current_frames = list(camera_frames.values()) if not current_frames: cv2.waitKey(20) continue # 拼接帧:这里以横向拼接为例,多摄像头可以改成网格(比如2行2列) # 横向拼接 combined_frame = np.hstack(current_frames) # 如果是多行网格,比如2行1列,用np.vstack;如果是2x2,先横向拼每行再纵向拼 # row1 = np.hstack(current_frames[:2]) # row2 = np.hstack(current_frames[2:]) # combined_frame = np.vstack([row1, row2]) cv2.imshow(window_name, combined_frame) # 按下q键退出 if cv2.waitKey(20) & 0xFF == ord('q'): break finally: # 停止所有线程 for thread in threads: thread.running = False for thread in threads: thread.join() cv2.destroyAllWindows() if __name__ == "__main__": main()
关键修改说明
- 线程安全的帧存储:用
threading.Lock保护共享字典camera_frames,避免多线程同时读写导致的帧损坏。 - 职责拆分:子线程只做摄像头帧捕获和更新,不处理显示和拼接;主线程统一处理拼接和显示,避免子线程资源竞争。
- 重连机制:添加了摄像头读取失败时的重连逻辑,提升稳定性。
- 灵活拼接:可以根据摄像头数量调整拼接方式(横向、纵向、网格),只需修改
combined_frame的生成代码。
内容的提问来源于stack exchange,提问作者saatvik
相关产品推荐
相关产品推荐

