之前尝试过使用UDP进行图像传输,而UDP协议要求包小于64K,对于较大的图像,需要使用分片压缩的方式进行传输,操作较复杂,同时不能保证图片的每一部分都能够正确传输。详见:UDP实时图像传输,UDP实时图像传输进阶篇——1080P视频传输

TCP对于传输的数据大小没有限制,同时TCP在发送失败时还有重传机制,可以保证传输的可靠性,所以本文将使用TCP协议来进行图像的实时传输。

基本流程

TCP连接过程见后面的程序,一般服务端创建一个套接字,绑定本地IP,开启监听,然后客户端也创建一个套接字,连接服务端就可以了,详见后面的代码。直接介绍数据传输流程,如下图:



图像通过bytes传输 图像传输是怎么实现的_socket


由于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()即可