0.前言
经过前面的实验,队长要求我使用opencv的camera作为摄像头来实现真正的视频流传输,因此本次实验我将讲述如何实现一个视频流的传输
附之前文章的链接:
【编程实践/嵌入式比赛】嵌入式比赛学习记录(一):TCP服务器和web界面的建立 【编程实践/嵌入式比赛】嵌入式比赛学习记录(二):基于TCP的图片流传输
1.视频流传输思想
我们提到视频,一般会想到帧的概念。从不严谨的角度来看,帧率就是指视频每秒刷新多少次。如果视频帧率是30,那么就是指1秒视频刷新30次。
因此我们可以将摄像头的视频截获下来,这样就是一张图片。通过刷新这样一张图片便可以视频流的形式展现。事实上很多网络IP摄像头就是通过传视频截获的帧来展示在前端的。
因此对于客户端(摄像头)而言,关键在于如何把视频帧进行编码传出去。而对于服务器端而言,则在于如何进行视频帧的解码。 大致的传输流程如下: 其中下位机部分暂且用一个单独进程来实现,摄像头使用opencv的camera,TCP client使用python socket实现。
2.摄像头部分
摄像头部分就是如之前所说,使用opencv来调用电脑的摄像头,模拟下位机的摄像头
另:貌似opencv的摄像头窗口大小只能选择预设的几个值,如果必须要自定义大小只能自己手动修改大小。
import cv2
from MyClient import *
from MyImage import *
client=TCPClient("192.168.71.1",5001)
def StartCamera():
cap=cv2.VideoCapture(0,cv2.CAP_DSHOW)
if not cap.isOpened():
print("Camera error!")
return
fps=15
cap.set(3,height)
cap.set(4,width)
cap.set(cv2.CAP_PROP_FPS,fps)
while 1:
ret,frame=cap.read()
if not ret:
print("Get frame error")
break
frame=cv2.flip(frame,180)
'''调用TCP client的接口传输'''
'''image_encode是图片编码'''
client.send_data(image_encode(frame))
time.sleep(0.05)
if __name__=="__main__":
StartCamera()
3.图片编码格式
本实验中,由于打算传输彩色图片,因此对于传输图片流的格式进行了修改,如下: 相较于上一次实验,多了个通道值,是因为opencv截取的图片帧是RGB通道的值,因此需要指明第三个维度,即通道数量,一般为3。
前两个分别是图片的宽度和高度,因为实际上我们图片的尺寸可能大于255*255,且为了方便处理,我们将图片的尺寸值放大到2字节,也就是最大可以允许图片的长宽为65535,这绝对够用
处理部分的代码如下
height=480
width=640
channel=3
def image_decode(hexstream:bytes):
img=list(hexstream[5:width*height*channel+5])
img=np.array(img).reshape((height,width,channel))
img=cv2.imencode(".jpg",img)[1]
img=img.tobytes()
return img
def image_encode(img:np.ndarray):
height,width,channel=img.shape
stream=bytes(0)
stream+=width.to_bytes(2,byteorder="big")
stream+=height.to_bytes(2,byteorder="big")
stream+=channel.to_bytes(1,byteorder="big")
array=img.flatten().tolist()
stream+=bytes(array)
return stream
4.TCP client部分
为了模拟下位机发送数据,client不可少,这里同样用socket实现。 稍作封装,便于其他模块调用
class TCPClient:
def __init__(self,host:str,port:int):
self.client=socket.socket()
self.client.connect((host,port))
def send_data(self,data:bytes):
self.client.sendall(data)
def close(self):
self.client.close()
5.TCP server部分
实现与之前差不多,这里不再修改
import threading
import socketserver
from MyImage import *
class DataBuffer:
def __init__(self,max_size):
self.tempdata=bytes(0)
self.pic=bytes(0)
self.mutex=threading.Semaphore(1)
self.pic_mutex=threading.Semaphore(1)
self.max_size=max_size
def insert_data(self,data:bytes):
self.mutex.acquire()
self.tempdata+=data
if(len(self.tempdata)>=self.max_size):
next_length=len(self.tempdata)-self.max_size
self.pic_mutex.acquire()
self.pic=image_decode(self.tempdata)
self.pic_mutex.release()
if(next_length>0):
self.tempdata=data[-next_length:]
else:
self.tempdata=bytes(0)
self.mutex.release()
def get_data(self):
self.pic_mutex.acquire()
pic=self.pic
self.pic_mutex.release()
return pic
def clearbuffer(self):
self.tempdata=bytes(0)
Buffer=DataBuffer(max_size=width*height*channel+5)
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)
6.web server部分
也是与之前差不多
from flask import *
from MyServer import Buffer
app1=Flask(__name__)
@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>
7.验收
启动摄像头部分和server部分,效果如下: 这里其实是开启了图片翻转,如果各位不喜欢这样也可以去掉摄像头中的cv2.flip一行
8.思考
- 帧率超过30的时候,如果不加sleep就会出现明显的卡顿,如何解决?
目前我感觉是传输的图片帧太大,一帧就有将近90KB,感觉可以把截取到的图片缩小一下再传,前端用img标签放大一下 - 如果前端打算用vue等框架,有没有办法将视频流传过去?
因为目前是用了Flask框架下的Response来实现视频流的传输,不知道如果使用了特定的前端框架是否需要做出修改,看后续的需求吧
最后,感谢你的观看!
|