背景:将连接在本地电脑的摄像头画面传输至服务器端,服务器端接收到这个画面可以进行一些实时的处理,然后将处理后的画面实时返回到本地(通过flask上传至网页的形式)
这里前提是本地电脑和服务器使用的是同一片局域网,然后我用的是d435i相机
整体流程:
-
本地 (Windows + D435i):读取摄像头 -> 通过 TCP 发送图片数据 -> 给服务器。
-
服务器 (Linux):监听端口接收图片 -> 将图片转为网页视频流 (Flask) -> 发布。
-
本地 (浏览器):访问服务器网页 -> 看到回传的画面。
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)。在服务器终端输入ifconfig或ip 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发送端:
-
设置更小的采集分辨率(D435i 支持 640x480 或 424x240)
-
设置帧率,限制采集速度放置积压
-
设置JPEG压缩参数[质量标志, 质量数值0-100],建议设置为 50~70,能在画质和速度间取得平衡
-
手动编码压缩,不发送原始庞大的 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接收端:
-
wins发送端将snder.send_image改成了sender.send_jpg,linux端需要对应改为image_hub.recv_jpg()
-
关闭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]