【编程实践/嵌入式比赛】嵌入式比赛学习记录(二):基于TCP的图片流传输

0.前言

经过上次的实验,比赛小组组长给我下了新的任务:设法模拟下位机向上位机发送图片比特流,并接收再展示在web前端。

经过确认,下位机是将图片以像素点的RGB值传输,依次传输图片的长宽、左上角到右下角每个像素点的像素值,即每个字节都是像素点的颜色值,而非诸如png、jpg等图片编码。目前组长要求我只要能传出灰度图即可,因此我后面的处理部分都是基于灰度图的。传彩色图片的道理是类似的。

1.图片编码

本次实验的重点在于如何接收client传来的比特流并进行处理在放到前端,因此数据格式不是重点,但为了后续讲解方便,这里还是给出本次实验图片的格式:
在这里插入图片描述

(这很像计算机网络中的数据链路层的字节计数法,不是嘛?)
其中,width字段占有1或2字节,表示图片宽度,height占有1或2字节,表示图片长度,pixels为像素位,由于本次实验是灰度图,因此只需要一个通道,也可以改成3个通道。

接收数据的时候,通过前两个字段就可以确认图片的样子,以及有多少个像素值了,因为传的时候往往不是一次只传同一个图片,可能是视频的多个帧传过来,因此需要划清每张图片的边界。

本实验中,width、height字段的长度取为1字节

2.图片传输方式

按照上面格式,我将3张255*255的灰度图以二进制编码的形式保存下来,得到了3个文件

之后会用网络调试助手选择一个文件并发送

3.图片接收及编码处理

首先是图片的接收部分,由于TCP server每次最多只能接收1024B的数据(这应该与数据链路层的MTU最大为1500B有关,但我也不清楚为什么是1024B),因此我们需要将接收的数据的数据缓存下来,待一帧图片接收完毕后,进行处理并保存,以供web server使用。
由于TCP server和web server是两个线程,因此存在着互斥问题,这里使用信号量进行控制,如果你对信号量不熟悉,你可以百度搜索信号量进行学习,这里不再展示。
另外说一下为什么不再使用post请求向web server发送图片编码,是因为图片编码是二进制比特流,貌似很难编码到json中,也没法转变成字符串,因此这里只能用线程间共享变量实现。
代码如下,稍显臃肿,这也是后续可以改进的地方

class DataBuffer:
    def __init__(self):
        self.tempdata=bytes(0)
        self.pic=bytes(0)
        #控制读取互斥
        self.mutex=threading.Semaphore(1)
        #通知可以取
        self.pic_mutex=threading.Semaphore(1)
        #最大量
        self.max_size=0x7fffffff

    def insert_data(self,data:bytes):
        self.mutex.acquire()
        # if(len(self.tempdata)==0):
        #     self.max_size=int(data[0])*int(data[1])+2
        self.tempdata+=data
        #图片再大其大小也不会超过0x7fffffff
        if(self.max_size>=0x7fffffff):
            self.max_size=int(self.tempdata[0])*int(self.tempdata[1])+2
        if(len(self.tempdata)>=self.max_size):
            next_length=len(self.tempdata)-self.max_size
            self.pic_mutex.acquire()
            width,height,self.pic=hex_to_jpgstream(self.tempdata)
            if(next_length>0):
                self.tempdata=data[-next_length:]
                #self.max_size=int(self.tempdata[0])*int(self.tempdata[1])+2
            else:
                self.tempdata=bytes(0)
            self.max_size=0x7fffffff
            self.pic_mutex.release()
        self.mutex.release()

    def get_data(self):
        self.pic_mutex.acquire()
        pic=self.pic
        self.pic_mutex.release()
        return pic

    def clearbuffer(self):
        self.max_size=0x7fffffff
        self.tempdata=bytes(0)

Buffer=DataBuffer()

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        global Buffer
        while True:
            data=self.request.recv(8192)
            if not data:
                break                
            else:
                Buffer.insert_data(data)

大致过程是:

  1. 每次接收1024个字节的数据,存入缓存变量tempdata中
  2. 判断是否完成接受完一个图片(通过接收数据量和图片尺寸进行比对,图片尺寸由图片格式段求出)
  3. 若接收完,进行处理后保存在变量pic中,同时修改tempdata为下一个图片的数据部分,并重新计算图片尺寸。
  4. web server取图片的时候,取得是pic而非tempdata

以下是图片处理部分,主要是将图片编码转换成jpg格式以便在前端展示

def hex_to_jpgstream(hexstream:bytes):
    #hex -> np.array
    #bytes[0]:width bytes[1]:height
    width,height=int(hexstream[0]),int(hexstream[1])
    img=[int(hexstream[i]) for i in range(2,len(hexstream))]
    img=np.array(img).reshape((width,height))
    #np.array->jpg stream
    img=cv2.imencode(".jpg",img)[1]
    img=img.tobytes()
    #jpg stream => base64
    # b64stream=base64.b64encode(img)
    return width,height,img

4.图片流展示在前端

图片展示在前端的处理与普通的后端传参的方式不同,本来考虑再使用Ajax轮询的方式更新,但后来考虑这样的负担太大了,因此改为使用Response相应返回。
这里涉及到了yield语法,大概就是生成了一个生成器,可以不断的返回一些数据,这也是视频流传输的一种方法

from flask import *
from MyServer import Buffer

app1=Flask(__name__)

data=None

@app1.route("/",methods=["GET","POST"])
def index_page():
    return render_template("index0.html")

def gen():
    while True:
        pic=Buffer.get_data()
        yield (b'--frame\r\n'
        b'Content-Type: image/jpeg\r\n\r\n' + pic + b'\r\n')

@app1.route("/video_feed")
def video_feed():
    return Response(gen(),mimetype="multipart/x-mixed-replace; boundary=frame")

前端部分

<!DOCTYPE html>
<html>
    <head>

    </head>
    <body>
        <img id="camera" src="{{url_for('video_feed')}}">
    </body>
</html>

最后是启动模块,启动TCP server和Web server

from flask import *
import threading
import socketserver
import time

from app_views import *
from MyServer import *



def start_TCP(host,port):
    myserver=socketserver.ThreadingTCPServer((host,port),MyHandler)
    myserver.serve_forever()

def start_flask(host,port):
    app1.run(host=host,port=port)

def main():
    th1=threading.Thread(target=start_flask,args=("192.168.71.1",5000))
    th2=threading.Thread(target=start_TCP,args=("192.168.71.1",5001))
    th1.start()
    th2.start()
    
if __name__=="__main__":
    main()

5.验收

启动web服务器和TCP服务器
在网络调试助手中发送数据
在这里插入图片描述
输入web服务器地址,发现图片展示成功
在这里插入图片描述
证明实验成功

6.思考

  1. 此方法是否能正确的应用到视频中?事实上在下一个实验中我将进行尝试
  2. 有没有更好的方法使TCP server和web server共享数据?有机会可以尝试其他的方式

最后,感谢观看!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值