基于OpenCv+Django的网络实时视频流传输(前后端分离)
秋風(fēng)閣——北溪入江流:https://focus-wind.com/
秋風(fēng)閣——基于OpenCv+Django的網(wǎng)絡(luò)實(shí)時(shí)視頻流傳輸(前后端分離)
使用OpenCv捕獲攝像機(jī)畫面后,我們有時(shí)候需要將畫面顯示在界面上。本博客基于Django的前后端分離模式,將視頻流從后端讀取,傳送給前端顯示。
Django流傳輸實(shí)例:StreamingHttpResponse
在使用Django進(jìn)行視頻流傳輸時(shí),無(wú)法使用HttpResponse,JsonResponse等對(duì)象對(duì)內(nèi)容直接傳輸,需要使用StreamingHttpResponse流式傳輸一個(gè)響應(yīng)給瀏覽器。StreamingHttpResponse不是HttpResponse的子類,因此他們之間的API略有不同。StreamingHttpResponse與HttpResponse之間有以下顯著區(qū)別:
- 應(yīng)該給StreamingHttpResponse一個(gè)迭代器,產(chǎn)生字節(jié)字符串作為內(nèi)容。
- 不應(yīng)該直接訪問(wèn)StreamingHttpResponse的內(nèi)容,除非通過(guò)迭代器響應(yīng)對(duì)象本身。
- StreamingHttpResponse沒(méi)有content屬性。相反,他有一個(gè)streaming_content屬性。
- 無(wú)法使用類文件對(duì)象的tell()何write()方法。這樣會(huì)引起一個(gè)異常。
Django傳輸視頻流
因?yàn)槭褂肈jango的StreamingHttpResponse類進(jìn)行流傳輸,所以我們首先需要生成一個(gè)視頻流的迭代器,在迭代器中,需要將從opencv中獲取到的numpy.ndarray三維數(shù)組轉(zhuǎn)換為字節(jié)類型的,然后傳輸?shù)角岸恕?/p>
傳輸視頻流:
在使用海康威視等分辨率較高的相機(jī)時(shí),直接解碼,延遲過(guò)高,所以需要先對(duì)圖片進(jìn)行壓縮,然后解碼。
經(jīng)測(cè)試,海康相機(jī)使用0.25的壓縮倍率顯示壓縮效率較好,當(dāng)大于0.25時(shí),延遲較高,小于0.25時(shí),界面顯示較差
迭代器優(yōu)化:
def gen_display(camera):"""視頻流生成器功能。"""while True:# 讀取圖片ret, frame = camera.read()if ret:frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)# 將圖片進(jìn)行解碼ret, frame = cv2.imencode('.jpeg', frame)if ret:# 轉(zhuǎn)換為byte類型的,存儲(chǔ)在迭代器中yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame.tobytes() + b'\r\n')前端顯示視頻流
在Django中配置路由后,在瀏覽器端直接訪問(wèn)視頻url即可看到視頻顯示畫面。
在前端HTML5中,將視頻路由寫入img標(biāo)簽的src屬性中,即可訪問(wèn)視頻流界面。例如:<img src=‘https://ip:port/uri’
前端顯示視頻流:
顯示結(jié)果:
在前端顯示視頻流中,可以通過(guò)調(diào)整img標(biāo)簽的屬性來(lái)調(diào)整界面顯示位置,顯示大小。所以在進(jìn)行視頻流前后端傳輸中,在保證視頻顯示清晰度的情況下,建議使用前端來(lái)調(diào)整界面大小。
調(diào)整界面前端顯示視頻樣式:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>基于OpenCv+Django的網(wǎng)絡(luò)實(shí)時(shí)視頻流傳輸(前后端分離)</title><style>#video {width: 500px;height: 500px;}</style> </head> <body><!-- 顯示視頻流 --><div align="center"><img src="http://127.0.0.1:8000/api/cv/display" id="video"></div> </body> </html>顯示結(jié)果:
視頻流傳輸優(yōu)化
在項(xiàng)目中,我們可能經(jīng)常需要對(duì)多個(gè)相機(jī)進(jìn)行處理,而不是對(duì)一個(gè)相機(jī)進(jìn)行操作,所以我們可以使用相機(jī)工廠來(lái)獲取相機(jī)。在實(shí)例化相機(jī)后,需要開(kāi)啟一個(gè)線程,及時(shí)更新緩存隊(duì)列,確保OpenCv不會(huì)因?yàn)榫彺孢^(guò)多而造成緩存區(qū)堵塞,界面延遲。
- 使用線程實(shí)時(shí)讀取OpenCv的內(nèi)容到隊(duì)列中
- 使用相機(jī)工廠來(lái)獲取相機(jī)
在示例代碼中,camera_model為自定義model,其中代碼需要用到的數(shù)據(jù)有數(shù)據(jù)表記錄的唯一標(biāo)識(shí)id,相機(jī)的訪問(wèn)api:camera_api
相機(jī)類:
import queue import threadingimport cv2from apps.device.models import Cameraclass CameraException(Exception):message = None# 初始化異常def __init__(self, message: str):# 初始化異常,定位異常信息描述self.message = messagedef __str__(self):return self.messageclass BaseCamera:# 相機(jī)操作對(duì)象cam = None# 保存每一幀從rtsp流中讀取到的畫面,使用opencv讀取,為BGR圖片queue_image = queue.Queue(maxsize=10)# 后臺(tái)取幀線程thread = None# 相機(jī)Modelcamera_model = None# 相機(jī)基類def __init__(self, camera_model: Camera):"""使用rtsp流初始化相機(jī)參數(shù)rtsp格式:rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_streamusername: 用戶名。例如admin。password: 密碼。例如12345。ip: 為設(shè)備IP。例如 192.0.0.64。port: 端口號(hào)默認(rèn)為554,若為默認(rèn)可不填寫。codec:有h264、MPEG-4、mpeg4這幾種。channel: 通道號(hào),起始為1。例如通道1,則為ch1。subtype: 碼流類型,主碼流為main,輔碼流為sub。"""self.cam = cv2.VideoCapture(camera_model.camera_api)if self.cam.isOpened():# 相機(jī)打開(kāi)成功,啟動(dòng)線程讀取數(shù)據(jù)self.thread = threading.Thread(target=self._thread, daemon=True)self.thread.start()else:# 打開(kāi)失敗,相機(jī)流錯(cuò)誤raise CameraException("視頻流接口訪問(wèn)失敗")def _thread(self):"""相機(jī)后臺(tái)進(jìn)程,持續(xù)讀取相機(jī)opencv讀取時(shí)會(huì)將信息存儲(chǔ)到緩存區(qū)里,處理速度小于緩存區(qū)速度,會(huì)導(dǎo)致資源積累"""# 線程一直讀取視頻流,將最新的視頻流存在隊(duì)列中while self.cam.isOpened():ret, img = self.cam.read()if not ret or img is None:# 讀取相機(jī)失敗passelse:# 讀取內(nèi)容成功,將數(shù)據(jù)存放在緩存區(qū)if self.queue_image.full():# 隊(duì)列滿,隊(duì)頭出隊(duì)self.queue_image.get()# 隊(duì)尾添加數(shù)據(jù)self.queue_image.put(img)else:# 隊(duì)尾添加數(shù)據(jù)self.queue_image.put(img)# 直接讀取圖片def read(self):"""直接讀取從rtsp流中獲取到的圖片,不進(jìn)行額外加工可能為空,需做判空處理"""return self.queue_image.get()# 讀取視頻幀def get_frame(self):"""獲取加工后的圖片,可以直接返回給前端顯示"""img = self.queue_image.get()if img is None:return Noneelse:# 壓縮圖片,否則圖片過(guò)大,編碼效率慢,視頻延遲過(guò)高img = cv2.resize(img, (0, 0), fx=0.25, fy=0.25)# 對(duì)圖片進(jìn)行編碼ret, jpeg = cv2.imencode('.jpeg', img)return jpeg.tobytes()class CameraFactory:"""相機(jī)工廠"""# 存儲(chǔ)實(shí)例化的所有相機(jī)cameras = {}@classmethoddef get_camera(cls, camera_id: int):# 通過(guò)相機(jī)id獲取相機(jī)camera = cls.cameras.get(camera_id)if camera is None:# 查看是否存在相機(jī),存在訪問(wèn)try:camera_model = Camera.objects.get(id=camera_id)base_camera = BaseCamera(camera_model=camera_model)if base_camera is not None:cls.cameras.setdefault(camera_id, base_camera)return cls.cameras.get(camera_id)else:return Noneexcept Camera.DoesNotExist:# 相機(jī)不存在return Noneexcept CameraException:# 相機(jī)實(shí)例失敗return Noneelse:# 存在相機(jī),直接返回return cameraDjango views.py:
from django.http import StreamingHttpResponsefrom apps.device.Camera import CameraFactory, BaseCameradef gen_display(camera: BaseCamera):"""視頻流生成器功能。"""while True:# 讀取圖片frame = camera.get_frame()if frame is not None:yield (b'--frame\r\n'b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')def video(request):"""視頻流路由。將其放入img標(biāo)記的src屬性中。例如:<img src='https://ip:port/uri' >"""# 視頻流相機(jī)對(duì)象camera_id = request.GET.get('camera_id')camera: BaseCamera = CameraFactory.get_camera(camera_id)# 使用流傳輸傳輸視頻流return StreamingHttpResponse(gen_display(camera), content_type='multipart/x-mixed-replace; boundary=frame')總結(jié)
以上是生活随笔為你收集整理的基于OpenCv+Django的网络实时视频流传输(前后端分离)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 谷歌浏览器Chrome播放rtsp实时视
- 下一篇: vba 添加outlook 签名_如何在