TCP/UDP记录-图像传输系统

这个其实要算我当时实习的一项内容了,当时实习去了家老师开的小公司,工作压力相对轻松,很放开的感觉,觉得有什么需求就做什么,做完我之前的两个项目之后我就开始做这个了,做的图像传输,当然我这个到最后也只是浅尝辄止,没有做的太深了,这里也只是记录下这个过程,真正的应用到产品上还有很长的一段路要走,也希望如果有大佬看到这篇文章有更好的办法可以提出一些宝贵的意见!

1、UDP/TCP图像是怎么进行传输的

前面几篇文章刚刚说了,TCP/UDP传输的其实是通过套接字来进行传输的,貌似跟图像没什么关系,甚至我们都在想怎么把一张张图像这样给他传输过去,这也太麻烦了吧。

不过根据之前opencv的学习可以知道其实一张图像,或者说一段视频(视频就是由一张张的图像来组成的对吧),那么图像是啥呢,其实也就是一个矩阵来组成的对吧,这里我们可以加载一张图片并进行显示,可以看到他是一个numpy的矩阵,然后是6404803的一张图片,一般情况下默认加载图像上RGB格式的,这里就是三个通道了,图像大小还是640480的大小,这里的单位是像素
在这里插入图片描述
我们干脆就更爽快一点,也不用这些东西了,直接打印这个图片看看到底是什么情况,可以看到她其实也是一个个的数值,这里因为图像一般每个通道八位大小也就是(0-255)了,所以每个数值一般不会超过这个
在这里插入图片描述
那么传输这样的一张图像要传输多少数据呢,这里其实也就是在看看他有多大就是了,其实不打印也能知道,就是640
4803了
在这里插入图片描述
所以我们传输图像的本质其实就是在传输这里面的矩阵,因此这里有一点很明显的就是我们传输的图像尺寸越大,必然会导致我们这个传输的速率变慢,同样的我们传输320
240大小的图像和传输640480大小的图像而言,速度上就要减慢四倍,更不要说1080960这样尺寸的了,速度上也会大打折扣的,因此传输的时候需要提前考虑这个隐因素。

那么知道了这些其实传输就简单了,大概流程就是:

  • 一端进行图像的获取和解码,然后通过socket将数据发送出去
  • 另一端接收socket回传的数据
  • 这边再重新转为图片,然后用opencv的方式显示出来

2、UDP传输

这里话不多说,先上代码:

服务器部分:

import cv2
from socket import *

cap = cv2.VideoCapture(0)
# 统一图像大小为640*480
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

Host = '192.168.0.100'
Port = 5555
sock = socket(AF_INET, SOCK_DGRAM) # 创建UDP套接字

while True:
    ret, img = cap.read()

    ret, send_data = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 50])# 将图片转为byte
    sock.sendto(send_data, (Host,Port))

sock.close()
cv2.destroyAllWindows()

客户端部分

import numpy as np
import cv2
from socket import *

sock = socket(AF_INET, SOCK_DGRAM) # 创建UDP套接字
Host = '192.168.0.100'
Port = 5555
sock.bind((Host,Port))
sock.setblocking(0) # 设置为非阻塞模式
# 非阻塞模式 当程序碰到耗时操作,分发给别的线程,主线程继续执行,这样可以提升程序的效率

while True:
    data = None
    try:
        data, address = sock.recvfrom(921600)
        receive_data = np.frombuffer(data, dtype='uint8')
        img = cv2.imdecode(receive_data, 1)

        cv2.imshow('server', img)
    except BlockingIOError as e: # 这里UDP传输有错误是正常的,但是影响不大,所以下面直接pass掉
        pass
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cv2.destroyAllWindows()

一些详情的介绍其实在代码注释里面我已经说明过了,这里就不进行赘述了,还是比较直白的,就直接截图看这个说明吧:

首先是数据发送,也就是客服端这边
在这里插入图片描述
下面是接收图像并显示的部分,也就是接收端,这里有一个很重要的点就是接收的字节,可以看到每次接收的字节是921600,这个其实就是最开始的设定了,因为我们传输的是一张6404803的彩色图像,所以这张图像其实大小就是921600这个大小,所以我们这里不用去判断图像的大小然后进行解码了。
在这里插入图片描述
这里为了说明这个图像传输还不错我这里用树莓派来测试,效果如下,可以看出来其实这个效果在树莓派上也是很不错的,也是基本上没有什么大的延迟。
在这里插入图片描述

3、TCP传输

还是一样,先上代码再说:

客户端部分如下:

import cv2
import socket

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

HOST = '192.168.218.133'
PORT = 12345
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST,PORT))

while True:
    ref, img = cap.read()
    _,img_encode = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 50])
    bytedata = img_encode.tostring() # 转换为字节流
    flag_data = (str(len(bytedata))).encode() + ",".encode() + " ".encode() # 将数据用,隔开
    sock.send(flag_data)

    data = sock.recv(1024)
    if('ok' == data.decode()):
        sock.send(bytedata) # 确认收到数据就开始发送图像

sock.close()
cv2.destroyAllWindows()

这里相比udp的部分就是多了一个判断,其他都没什么东西
在这里插入图片描述

服务器部分如下:

import socket
import cv2
import numpy as np

HOST = '192.168.218.133'
PORT = 12345
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((HOST,PORT))
sock.listen(True)


while True:
    client_socket, client_address = sock.accept()
    try:
        while True:
            data = client_socket.recv(1024) # 接收客户端数据
            if data: # 如果收到客户端数据
                client_socket.send(b'ok')
                img_bytes = client_socket.recv(921600)

                img = np.asarray(bytearray(img_bytes), dtype="uint8")
                img = cv2.imdecode(img, cv2.IMREAD_COLOR)
                cv2.imshow("img", img)
                cv2.waitKey(1)
            else:
                print('已断开')
    finally:
        sock.close()

这里还是一贯的采用我们之前的方法,就是提前计算好图像尺寸,不然就要在图像里面记录了,那样太蛮烦了,网上有很多其他方案都是用的先发送命令在计算图像大小,这样需要专门设计大小计算的函数,不太方便
在这里插入图片描述
这里我用我的电脑来进行了测试,感觉效果还是不错的,也基本上看不到什么延迟,效果如下
在这里插入图片描述

4、使用树莓派CSI摄像头传输

因为当时用的是树莓派的方案,不过最近树莓派涨价涨的太厉害了,因此这个方案可能有一定的局限性了,看看就行,这个效果其实还是不错的,可以参考下。

将树莓派自身提供的picamera应该是使用硬件的方式进行的摄像头的读取的,这里我们就把这个当作读取视频的方式了,就不用opencv的方式来进行一帧帧的图像读取了,这里我参考了这个博客,感觉还是很不错的,有需要的可以看下原博客,链接如下:

参考链接

这里还是看源码吧

客户端部分如下所示,这里相关参数都在注释中说明了

import time
import numpy as np
import cv2
import socket
import struct
import time

from picamera.array import PiRGBArray
from picamera import PiCamera

class Camera(object):
    def __init__(self):
        HOST = '192.168.218.133'  
        PORT = 8000  
        self.server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
        self.server.connect((HOST,PORT))
    
    def CameraInit(self):  # 初始化PiCamera
        self.camera = PiCamera()
        self.camera.resolution = (640, 480)
        self.camera.vflip = True
        self.camera.hflip = True
        self.camera.framerate = 32
        rawCapture = PiRGBArray(self.camera, size=(640, 480))
        rawCapture.truncate(0)
        time.sleep(2) # wait for camera starting 相机需要时间预热
        return self.camera, rawCapture

    def VideoTransmission(self, frame):  # transmit video from Pi to PC
        result, imgencode = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 50])  #编码
        try:
            self.server.sendall(struct.pack('i',imgencode.shape[0]))  # 发送数据长度作为校验
            self.server.sendall(imgencode)
        except:
            print("fail to send the frame")

    def CameraCleanup(self):
        self.server.sendall(struct.pack('c',1)) #发送关闭消息
        self.server.close()
        self.camera.close()

if __name__ == '__main__':
    try:
        cam = Camera()
        camera, rawCapture = cam.CameraInit()
        for raw_frame in camera.capture_continuous(rawCapture, format="bgr",use_video_port=True):
            frame = np.copy(raw_frame.array)
            car.VideoTransmission(frame) 
            rawCapture.truncate(0)

    except KeyboardInterrupt:
        car.CameraCleanup()

下面是服务器部分

import cv2
import socket
import numpy as np

HOST='192.168.218.133'
PORT= 8000
buffSize=65535

if __name__=='__main__':
    server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建socket对象
    server.bind((HOST,PORT))

    while True:
        data,address=server.recvfrom(buffSize) #先接收的是字节长度
        if len(data)==1 and data[0]==1: #如果收到关闭消息则停止程序
            server.close()
            cv2.destroyAllWindows()
            exit()

        data,address=server.recvfrom(buffSize) #接收编码图像数据
        image = cv2.imdecode(np.frombuffer(data, dtype=np.uint8), cv2.IMREAD_COLOR)
        cv2.imshow('frames',image,) #窗口显示
        key = cv2.waitKey(1)
        if cv2.waitKey(1)==27: #按下“ESC”退出
            break

    server.close()
    cv2.destroyAllWindows()

最终效果如下所示,这个延迟其实还是很不错的,我用的测试方式是热点,基本上也看不出来什么
在这里插入图片描述

5、降低延迟的一些思路

综合上面的几种传输方式,可以总结其实影响延迟的因素无非就是传输的量的大小还有传输速度,因此个人觉得有这么几个是值得去尝试下的:

  • 能用硬件解码就不要用软件的方式,因为软件是用opencv的方式来进行的,这样把图像转成字节本来就要花费一定的时间
  • 尽量用小的图片格式,能不用大的就不用,大的图像造成传输的数据量急剧上升,其实一般情况也用不到那么大的图像
  • 传输的卡顿也有可能是硬件本身造成的,比如路由器这个中继,因为它取决了网络传输的速度,可能电脑这边已经处理完了,但是路由器那边还没有
  • 说到路由器那就可以想到一些硬件了,比如我们用一些其他的硬件例如树莓派等,这个时候影响速度的就有可能是这个设备本身了

好了上面就是本文的全部内容了,后续还将继续把这个传输系统结合qt做成上位机,这也是我当时实习的一部分内容

  • 13
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Qt是一个跨平台的应用程序开发框架,可以用来实现各种应用程序的开发,包括图像处理和视频传输等。Qt提供了简单易用的API,使得开发者可以轻松编写网络传输代码。 图片及视频TCP/UDP网络传输使用Qt的QtNetwork模块来实现。通过创建QTcpServer或QUdpSocket对象来监听传入的连接或数据报,使用QTcpSocket或QUdpSocket对象来建立连接或发送数据。可以使用Qt的QImage和QPixmap类来处理图像,使用QVideoFrame和QVideoSurface类来处理视频。在使用网络传输时,需要将图像和视频转换成字节流,然后在网络传输。 为了保证网络传输的可靠性,TCP协议应该被选择。TCP协议提供了重传机制和流量控制,因此可以保证传输的准确性和稳定性。在需要实时传输的情况下,UDP协议应该被选择。UDP协议不提供重传机制和流量控制,但是传输速度更快,因此可以保证传输时延较低。 在Qt中,可以使用QDataStream类来将数据流转换成字节流,然后发送到网络中。接收方可以使用QDataStream类来接收网络中的字节流,并将其转换回原始数据流。接收方可以使用QImage、QPixmap、QVideoFrame或QVideoSurface类来处理接收到的图像和视频。 总之,Qt是一个功能强大的跨平台框架,可以用来实现图片和视频的TCP/UDP网络传输。Qt提供了易用的API和丰富的类库,使得开发者可以轻松实现网络传输的代码。在实际应用中,需要根据传输的要求和需求选择合适的TCPUDP协议。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桃成蹊2.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值