之前尝试过使用UDP进行图像传输,而UDP协议要求包小于64K,对于较大的图像,需要使用分片压缩的方式进行传输,操作较复杂,同时不能保证图片的每一部分都能够正确传输。详见:UDP实时图像传输,UDP实时图像传输进阶篇——1080P视频传输
TCP对于传输的数据大小没有限制,同时TCP在发送失败时还有重传机制,可以保证传输的可靠性,所以本文将使用TCP协议来进行图像的实时传输。
基本流程
TCP连接过程见后面的程序,一般服务端创建一个套接字,绑定本地IP,开启监听,然后客户端也创建一个套接字,连接服务端就可以了,详见后面的代码。直接介绍数据传输流程,如下图:
由于TCP是以字节流的形式发送数据的,不能预知数据的大小,所以客户端在发送图像数据之前,需要先发送数据长度等信息。同时为了防止粘包(服务端接收到的数据会先缓存在缓冲区,在接收一次数据后,如果不及时处理,下一次接收到的数据也会送到缓冲区。由于这些数据都是字节流形式的,这样两次接收到的数据就会黏在一起,无法分开),客户端在发送完数据长度信息后,不能马上发送图像数据,需要等待服务端返回的应答信号。客户端接收到应答信号后,就可以开始发送图像字节流数据了。服务端完成图像数据接收后,还要返回给客户端一个应答信号,通知客户端开始下一帧图像的传输
程序实现
与前两篇文章不同,本文使用Python来实现主要功能(因为方便)。
运行环境
- 软件环境:Python3.8
- 硬件环境:千兆网口(低分辨率下百兆也行)、i3及以上的CPU
服务端
#-*- coding: UTF-8 -*-
import socket
import cv2
import numpy as np
HOST = ''
PORT = 8080
ADDRESS = (HOST, PORT)
# 创建一个套接字
tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地ip
tcpServer.bind(ADDRESS)
# 开始监听
tcpServer.listen(5)
while True:
print("等待连接……")
client_socket, client_address = tcpServer.accept()
print("连接成功!")
try:
while True:
# 接收标志数据
data = client_socket.recv(1024)
if data:
# 通知客户端“已收到标志数据,可以发送图像数据”
client_socket.send(b"ok")
# 处理标志数据
flag = data.decode().split(",")
# 图像字节流数据的总长度
total = int(flag[0])
# 接收到的数据计数
cnt = 0
# 存放接收到的数据
img_bytes = b""
while cnt < total:
# 当接收到的数据少于数据总长度时,则循环接收图像数据,直到接收完毕
data = client_socket.recv(256000)
img_bytes += data
cnt += len(data)
print("receive:" + str(cnt) + "/" + flag[0])
# 通知客户端“已经接收完毕,可以开始下一帧图像的传输”
client_socket.send(b"ok")
# 解析接收到的字节流数据,并显示图像
img = np.asarray(bytearray(img_bytes), dtype="uint8")
img = cv2.imdecode(img, cv2.IMREAD_COLOR)
cv2.imshow("img", img)
cv2.waitKey(1)
else:
print("已断开!")
break
finally:
client_socket.close()
客户端
#-*- coding: UTF-8 -*-
import cv2
import time
import socket
# 服务端ip地址
HOST = '192.168.0.100'
# 服务端端口号
PORT = 8080
ADDRESS = (HOST, PORT)
# 创建一个套接字
tcpClient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接远程ip
tcpClient.connect(ADDRESS)
cap = cv2.VideoCapture(0)
while True:
# 计时
start = time.perf_counter()
# 读取图像
ref, cv_image = cap.read()
# 压缩图像
img_encode = cv2.imencode('.jpg', cv_image, [cv2.IMWRITE_JPEG_QUALITY, 99])[1]
# 转换为字节流
bytedata = img_encode.tostring()
# 标志数据,包括待发送的字节流长度等数据,用‘,’隔开
flag_data = (str(len(bytedata))).encode() + ",".encode() + " ".encode()
tcpClient.send(flag_data)
# 接收服务端的应答
data = tcpClient.recv(1024)
if ("ok" == data.decode()):
# 服务端已经收到标志数据,开始发送图像字节流数据
tcpClient.send(bytedata)
# 接收服务端的应答
data = tcpClient.recv(1024)
if ("ok" == data.decode()):
# 计算发送完成的延时
print("延时:" + str(int((time.perf_counter() - start) * 1000)) + "ms")
注:如果是Python3.7及以下版本,则将time.perf_counter()
改为time.clock()
即可