You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Socket与OpenCV的双设备同步显示合并摄像头画面故障排查

双设备同步显示合并摄像头画面的问题解决

问题概述

我有两台带摄像头的设备A和B,希望通过Socket、OpenCV与Numpy实现两台设备同时显示两个摄像头的合并画面。当前代码仅能在一台设备上看到合并画面,尝试让两台设备同时显示时程序会冻结。

问题诊断

  1. 角色不对等:原代码中仅设备A生成合并帧并发送给B,设备B仅接收合并帧而不自主生成,导致B无法展示本地摄像头+A摄像头的合并画面;单向依赖关系易引发同步阻塞。
  2. 重复waitKey调用:设备B的send_frames函数中连续两次调用cv2.waitKey,会导致输入事件重复处理,引发线程阻塞。
  3. 线程安全缺失:全局变量frame在多线程中读写未加锁,可能导致数据损坏或程序崩溃。
  4. 跨线程UI操作:OpenCV的imshow在子线程中调用会破坏UI消息循环,导致窗口冻结。

解决方案

让两台设备对等运行,每台设备独立完成以下逻辑:

  1. 发送本地摄像头的原始帧给对方
  2. 接收对方发送的原始帧
  3. 在本地合并两个原始帧并显示
    同时修复线程安全与UI操作问题。

修改后的设备A代码

import cv2
import numpy as np
import socket
import threading
from threading import Lock

# 对方设备(B)的IP和端口
peer_ip = '10.10.0.100'
peer_port = 1234
# 本地监听端口
local_port = 5678

# 全局变量存储对方的帧,加锁保证线程安全
remote_frame = None
frame_lock = Lock()

# 创建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', local_port))  # 绑定本地所有IP的指定端口

def send_local_frames():
    cap = cv2.VideoCapture(0)
    # 统一摄像头分辨率,确保两边帧尺寸一致
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 800)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 800)
    
    while True:
        ret, local_frame = cap.read()
        if not ret:
            continue
        
        # 编码帧为JPEG,平衡传输效率与画质
        ret, encoded = cv2.imencode('.jpg', local_frame, [int(cv2.IMWRITE_JPEG_QUALITY), 50])
        if ret:
            sock.sendto(encoded.tobytes(), (peer_ip, peer_port))
        
        # 处理退出按键
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    cap.release()

def receive_remote_frames():
    global remote_frame
    while True:
        data, _ = sock.recvfrom(65535)
        nparr = np.frombuffer(data, np.uint8)
        received_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        # 加锁更新全局变量,避免多线程读写冲突
        with frame_lock:
            remote_frame = received_frame
        
        # 处理退出按键
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

# 启动发送和接收线程,设置为守护线程随主线程退出
send_thread = threading.Thread(target=send_local_frames, daemon=True)
recv_thread = threading.Thread(target=receive_remote_frames, daemon=True)
send_thread.start()
recv_thread.start()

# 主线程负责合并帧和显示,符合OpenCV UI操作要求
cap_local = cv2.VideoCapture(0)
cap_local.set(cv2.CAP_PROP_FRAME_WIDTH, 800)
cap_local.set(cv2.CAP_PROP_FRAME_HEIGHT, 800)

while True:
    ret, local_frame = cap_local.read()
    if not ret:
        continue
    
    # 获取对方帧(加锁保证数据完整性)
    with frame_lock:
        current_remote_frame = remote_frame
    
    # 合并并显示帧,未收到远程帧时显示本地画面
    if current_remote_frame is not None:
        current_remote_frame = cv2.resize(current_remote_frame, (800, 800))
        # 左右拼接合并,可替换为cv2.addWeighted实现叠加效果
        combined_frame = np.hstack((local_frame, current_remote_frame))
        cv2.imshow("Combined Camera Feed", combined_frame)
    else:
        cv2.imshow("Waiting for Remote Camera...", local_frame)
    
    # 统一退出逻辑
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放所有资源
cap_local.release()
sock.close()
cv2.destroyAllWindows()

修改后的设备B代码

import cv2
import numpy as np
import socket
import threading
from threading import Lock

# 对方设备(A)的IP和端口
peer_ip = '10.10.0.105'
peer_port = 5678
# 本地监听端口
local_port = 1234

# 全局变量存储对方的帧,加锁保证线程安全
remote_frame = None
frame_lock = Lock()

# 创建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', local_port))  # 绑定本地所有IP的指定端口

def send_local_frames():
    cap = cv2.VideoCapture(0)
    # 统一摄像头分辨率,确保两边帧尺寸一致
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 800)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 800)
    
    while True:
        ret, local_frame = cap.read()
        if not ret:
            continue
        
        # 编码帧为JPEG,平衡传输效率与画质
        ret, encoded = cv2.imencode('.jpg', local_frame, [int(cv2.IMWRITE_JPEG_QUALITY), 50])
        if ret:
            sock.sendto(encoded.tobytes(), (peer_ip, peer_port))
        
        # 处理退出按键
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    cap.release()

def receive_remote_frames():
    global remote_frame
    while True:
        data, _ = sock.recvfrom(65535)
        nparr = np.frombuffer(data, np.uint8)
        received_frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        # 加锁更新全局变量,避免多线程读写冲突
        with frame_lock:
            remote_frame = received_frame
        
        # 处理退出按键
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

# 启动发送和接收线程,设置为守护线程随主线程退出
send_thread = threading.Thread(target=send_local_frames, daemon=True)
recv_thread = threading.Thread(target=receive_remote_frames, daemon=True)
send_thread.start()
recv_thread.start()

# 主线程负责合并帧和显示,符合OpenCV UI操作要求
cap_local = cv2.VideoCapture(0)
cap_local.set(cv2.CAP_PROP_FRAME_WIDTH, 800)
cap_local.set(cv2.CAP_PROP_FRAME_HEIGHT, 800)

while True:
    ret, local_frame = cap_local.read()
    if not ret:
        continue
    
    # 获取对方帧(加锁保证数据完整性)
    with frame_lock:
        current_remote_frame = remote_frame
    
    # 合并并显示帧,未收到远程帧时显示本地画面
    if current_remote_frame is not None:
        current_remote_frame = cv2.resize(current_remote_frame, (800, 800))
        # 左右拼接合并,可替换为cv2.addWeighted实现叠加效果
        combined_frame = np.hstack((local_frame, current_remote_frame))
        cv2.imshow("Combined Camera Feed", combined_frame)
    else:
        cv2.imshow("Waiting for Remote Camera...", local_frame)
    
    # 统一退出逻辑
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放所有资源
cap_local.release()
sock.close()
cv2.destroyAllWindows()

关键改进点

  1. 对等架构:两台设备逻辑完全对称,各自独立处理帧的发送、接收与合并,避免单向依赖导致的阻塞。
  2. 线程安全:使用threading.Lock保护全局帧变量的读写,消除多线程冲突问题。
  3. UI规范:合并与显示逻辑放在主线程,符合OpenCV的UI操作要求,避免子线程操作窗口引发的冻结。
  4. 统一分辨率:强制设置摄像头分辨率,确保合并时帧尺寸一致。
  5. 精简输入处理:每个循环仅调用一次waitKey,避免输入事件混乱。

内容的提问来源于stack exchange,提问作者Koushik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:54:56