使用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]
相关推荐
七夜zippoe1 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥1 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
Fcy6482 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满3 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠3 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
主机哥哥3 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
Harvey9033 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技4 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀4 小时前
Linux环境变量
linux·运维·服务器
zzzsde4 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器