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

news/2024/5/18 15:13:42 标签: tcp/ip, udp, python, 网络, 嵌入式硬件

文章目录

  • 1、UDP/TCP图像是怎么进行传输的
  • 2、UDP传输
  • 3、TCP传输
  • 4、使用树莓派CSI摄像头传输
  • 5、降低延迟的一些思路

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

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做成上位机,这也是我当时实习的一部分内容


http://www.niftyadmin.cn/n/980095.html

相关文章

分布式处理大数据的目录及学习树

针对大数据的特点,传统集中式处理已不能满足大数据的处理要求。因此,利用分布式处理对大数据的收集、ETL、计算、使用是必由之路。 本博不是什么技术大拿,只是记录在学习利用分布式处理大数据过程中的心得和踩过的坑,自娱自乐&…

socket图传上位机设计记录

文章目录1、上位机页面和功能设计1、页面设计2、功能定义2、上位机程序设计3、测试效果之前的文章已经对TCP/UDP进行数据传输做了一定的个介绍了,并进行了聊天器还有图像传输的设计,因此这里就来结合之前的pyqt来做最后的一点完善,把上位机这…

centos7 开机自启动服务

2019独角兽企业重金招聘Python工程师标准>>> Linux服务器,服务管理--systemctl命令详解,设置开机自启动 Linux服务器,服务管理--systemctl命令详解,设置开机自启动 syetemclt就是service和chkconfig这两个命令的整合&a…

Java模拟Delegate

http://www.cnblogs.com/caca/p/3572881.html C#下面的Delegate可以让方法传递方法,在Java下却没有类似的机制。如何在Java下面模拟出Delegate呢?使用反射试试。贴代码:/** author:licunqing */ import java.lang.reflect.InvocationTargetEx…

ubutun Sogou输入法安装

http://jingyan.baidu.com/article/ad310e80ae6d971849f49ed3.html 文章来自以上链接 安装搜狗输入法 下载搜狗http://pinyin.sogou.com/linux/?rpinyin选择对应版本我的是64位的 进入对应文件夹输入sudodpkg -i sogoupinyin_2.1.0.0082_amd64.deb (或直接双击安装) 在终端中输…

树莓派安装ubuntu mate记录

文章目录1、系统下载1、ubuntu下载2、ubuntu mate下载2、系统安装3、系统使用1、ubuntu系统2、ubuntu mate系统这个算个失败的记录贴吧,这个系统安装过程不太流畅,使用起来也有很多问题,感慨树莓派之所以能卖这么贵还是生态啊,把生…

SVN基于MySQL认证

SVN的简介和工作原理Subversion(简称svn)是近几年崛起的版本管理软件,是cvs的接班人,目前绝大多数开源软件都使用svn作为代码版本管理软件。Subversion支持linux和windows,但是普通应用在Linux上。SVN主要是通过两种方…

函数调用堆栈 涉及汇编(转)

函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果,还有大家比较熟悉的一句话,就是函数调用是在栈上发生的,那么在计算机内部到底是如何实现的呢&a…