使用ImageZMQ将本地摄像头画面传输到服务器

背景:将连接在本地电脑的摄像头画面传输至服务器端,服务器端接收到这个画面可以进行一些实时的处理,然后将处理后的画面实时返回到本地(通过flask上传至网页的形式)

这里前提是本地电脑和服务器使用的是同一片局域网,然后我用的是d435i相机

整体流程:

  1. 本地 (Windows + D435i):读取摄像头 -> 通过 TCP 发送图片数据 -> 给服务器。

  2. 服务器 (Linux):监听端口接收图片 -> 将图片转为网页视频流 (Flask) -> 发布。

  3. 本地 (浏览器):访问服务器网页 -> 看到回传的画面。


1. 环境准备

分别在 本地Windows服务器Linux 上安装必要的库。

服务器端 (Linux):

python 复制代码
pip install flask opencv-python imagezmq

本地端 (Windows)(记得关闭代理):

python 复制代码
pip install opencv-python imagezmq

2. 服务器端代码(接收+网页展示)

在服务器上创建一个文件 server_bridge.py。这个脚本的作用是"左手接数据,右手推网页"。

python 复制代码
# 文件名: server_bridge.py (服务器端)
import cv2
import imagezmq
from flask import Flask, Response

app = Flask(__name__)

# 1. 初始化 ImageHub,监听 5555 端口等待图像传入
# open_port='tcp://*:5555' 表示接收来自任何 IP 的连接
image_hub = imagezmq.ImageHub(open_port='tcp://*:5555')

def generate_frames():
    print("正在等待本地摄像头连接及发送数据...")
    while True:
        # 2. 接收图像 (rpi_name 是发送端的主机名,frame 是图像矩阵)
        rpi_name, frame = image_hub.recv_image()
        
        # 3. 【必须】给发送端回复确认,否则发送端发完一帧就会卡死等待
        image_hub.send_reply(b'OK')
        
        # 可以在画面上打个标,证明是服务器处理过的
        cv2.putText(frame, "Processed by Server", (30, 50), 
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

        # 4. 编码为 JPEG 推流给 Flask
        ret, buffer = cv2.imencode('.jpg', frame)
        if not ret: continue
        frame_bytes = buffer.tobytes()

        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')

@app.route('/video_feed')
def video_feed():
    return Response(generate_frames(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/')
def index():
    return "<h1>服务器实时接收D435i画面</h1><img src='/video_feed' width='100%'>"

if __name__ == '__main__':
    # 启动 Flask,监听 5000 端口
    print("Web服务已启动: http://0.0.0.0:5000")
    app.run(host='0.0.0.0', port=5000, debug=False)

3. 本地代码 (D435i 采集 + 发送)

在 Windows 电脑上创建一个文件 local_send.py

需要知道服务器的 局域网 IP 地址 (例如 192.168.1.100)。在服务器终端输入 ifconfigip addr 查看。

python 复制代码
# 文件名: local_send.py (Windows 本地端)
import socket
import time
import cv2
import imagezmq

# ================= 配置区 =================
# 修改为你服务器的真实局域网 IP
SERVER_IP = '192.168.X.X'  
# =========================================

# 初始化发送端,连接到服务器的 5555 端口
sender = imagezmq.ImageSender(connect_to=f'tcp://{SERVER_IP}:5555')

# 获取主机名作为标识
host_name = socket.gethostname()

print(f"正在尝试连接服务器 {SERVER_IP}...")

# 打开 D435i 摄像头
# 注意:D435i 是双目+RGB相机。
# index=0, 1, 2 分别对应不同的传感器。
# 通常 0 或 1 是 RGB 图像。如果你看到黑白图或花屏,请把 0 改成 1 或 2 试试。
cap = cv2.VideoCapture(0) 

# 设置一下分辨率,防止 D435i 默认分辨率太高导致传输卡顿
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

time.sleep(2.0)  # 给摄像头一点预热时间
print("摄像头已启动,开始传输...")

try:
    while True:
        ret, frame = cap.read()
        if not ret:
            print("无法读取摄像头画面!")
            break
        
        # 发送图像给服务器
        #这一步会阻塞,直到收到服务器的 "OK" 回复
        sender.send_image(host_name, frame)
        
        # 本地也显示一个小窗口确认摄像头是好的(可选)
        cv2.imshow("Local Camera Monitor", frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
except KeyboardInterrupt:
    print("停止传输")
finally:
    cap.release()
    cv2.destroyAllWindows()

4. 优化

**上述代码有可能会非常的卡顿,延时很高!!**所以需要小动一下:

wins发送端:

  1. 设置更小的采集分辨率(D435i 支持 640x480 或 424x240)

  2. 设置帧率,限制采集速度放置积压

  3. 设置JPEG压缩参数[质量标志, 质量数值0-100],建议设置为 50~70,能在画质和速度间取得平衡

  4. 手动编码压缩,不发送原始庞大的 numpy 数组,而是发送压缩后的 JPG 数据

python 复制代码
# 文件名: local_send.py (Windows 本地端)
import socket
import time
import cv2
import imagezmq

# ================= 配置区 =================
# 修改为你服务器的真实局域网 IP
SERVER_IP = '10.3.86.255'
# =========================================

# 初始化发送端,连接到服务器的 5555 端口
sender = imagezmq.ImageSender(connect_to=f'tcp://{SERVER_IP}:5555')

# 获取主机名作为标识
host_name = socket.gethostname()

print(f"正在尝试连接服务器 {SERVER_IP}...")

# 打开 D435i 摄像头
# 注意:D435i 是双目+RGB相机。
# index=0, 1, 2 分别对应不同的传感器。
# 通常 0 或 1 是 RGB 图像。如果你看到黑白图或花屏,请把 0 改成 1 或 2 试试。
cap = cv2.VideoCapture(2, cv2.CAP_DSHOW) # 我这里2能看到画面

# 设置一下分辨率,防止 D435i 默认分辨率太高导致传输卡顿
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 424)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)

# 设置帧率 (FPS),限制采集速度防止积压
cap.set(cv2.CAP_PROP_FPS, 30)

time.sleep(2.0)  # 给摄像头一点预热时间
print("摄像头已启动,开始传输...")
# 2. 提前设置 JPEG 压缩参数 [质量标志, 质量数值0-100]
# 建议设置为 50~70,能在画质和速度间取得平衡
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 50]

try:
    while True:
        ret, frame = cap.read()
        if not ret:
            print("无法读取摄像头画面!")
            break

        # 3. 【关键步骤】手动编码压缩
        # 不发送原始庞大的 numpy 数组,而是发送压缩后的 JPG 数据
        ret_code, jpg_buffer = cv2.imencode('.jpg', frame, encode_param)

        if ret_code:
            # 发送 jpg 数据流,ImageZMQ 会自动处理 bytes 类型的输入
            # 注意:这里改了发送内容,服务器端接收代码不用变,imagezmq 会自适应
            sender.send_jpg(host_name, jpg_buffer)

            # 本地显示(可选,如果觉得卡可以注释掉这行,cv2.imshow 也会拖慢速度)
            cv2.imshow("Local Monitor", frame)

            # 4. 降低 waitKey 时间
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
except KeyboardInterrupt:
    print("停止传输")
finally:
    cap.release()
    cv2.destroyAllWindows()

linux接收端:

  1. wins发送端将snder.send_image改成了sender.send_jpg,linux端需要对应改为image_hub.recv_jpg()

  2. 关闭ImageHub的"确认等待"模式(默认情况下,ImageZMQ 是"请求-回复"模式(REQ-REP),发送端发一张,必须等服务器回一个 "OK" 才能发下一张。这最稳定,但如果网络有抖动,帧率就会暴跌)

python 复制代码
'''
左手接数据,右手推网页
'''

import cv2
import imagezmq
from flask import Flask, Response
import numpy as np

app = Flask(__name__)

# 1. 初始化 ImageHub,监听 5555 端口等待图像传入
# open_port='tcp://*:5555' 表示接收来自任何 IP 的连接
image_hub = imagezmq.ImageHub(open_port='tcp://*:5555')

def generate_frames():
    print("正在等待本地摄像头连接及发送数据...")
    while True:
        # 2. 接收图像 (rpi_name 是发送端的主机名,frame 是图像矩阵)
        rpi_name, jpg_buffer = image_hub.recv_jpg()
        
        # 3. 【必须】给发送端回复确认,否则发送端发完一帧就会卡死等待
        image_hub.send_reply(b'OK')

        # 将接收到的 bytes 解码回 opencv 图像矩阵
        frame = cv2.imdecode(np.frombuffer(jpg_buffer, dtype='uint8'), -1)
        
        # 可以在画面上打个标,证明是服务器处理过的
        cv2.putText(frame, "Processed by Server", (30, 50), 
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)

        # 4. 编码为 JPEG 推流给 Flask
        ret, buffer = cv2.imencode('.jpg', frame)
        if not ret: continue
        frame_bytes = buffer.tobytes()

        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n')

@app.route('/video_feed')
def video_feed():
    return Response(generate_frames(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/')
def index():
    return "<h1>服务器实时接收D435i画面</h1><img src='/video_feed' width='100%'>"

if __name__ == '__main__':
    # 启动 Flask,监听 5000 端口
    print("Web服务已启动: http://0.0.0.0:5000")
    app.run(host='0.0.0.0', port=5000, debug=False)

4. 开始运行

  • 先在服务器运行---- [终端会显示:Web服务已启动... 正在等待本地摄像头连接...]
  • 后在 Windows 运行----[如果连接成功,本地会弹出一个名为 "Local Camera Monitor" 的窗口显示实时画面。]
  • 观看结果----[回到 Windows,打开浏览器,访问: http://<服务器IP>:5000]
相关推荐
wjykp2 小时前
1.vmware虚拟机安装和配置os
linux·运维·服务器
Henry Zhu1232 小时前
VPP中的DPDK插件源码详解第一篇:DPDK插件的作用和意义以及整体架构
运维·服务器·网络·计算机网络·云原生
测试人社区-千羽3 小时前
智能测试的终极形态:从自动化到自主化的范式变革
运维·人工智能·python·opencv·测试工具·自动化·开源软件
秋刀鱼 ..3 小时前
2026年机器人感知与智能控制国际学术会议(RPIC 2026)
运维·人工智能·科技·金融·机器人·自动化
roman_日积跬步-终至千里3 小时前
【源码分析】StarRocks 跨集群数据迁移工具 - 基于快照进行的快速迁移
运维
潘晓可3 小时前
Docker部署Bookstack
运维·docker·容器
Serverless社区3 小时前
阿里云新发的AgentRun 有哪些“大招”,一文详解来了
运维·阿里云·云原生·serverless
誰能久伴不乏3 小时前
深入理解 `poll` 函数:详细解析与实际应用
linux·服务器·c语言·c++·unix
倔强的石头1063 小时前
Linux 进程深度解析(二):进程状态、fork 创建与特殊进程(僵尸 与 孤儿)
linux·运维·服务器