色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

【米爾MYD-YT507開發(fā)板試用體驗】基于Fluter+Django+OpenCV的米爾行車記錄儀

開發(fā)板試用精選 ? 來源:開發(fā)板試用 ? 作者:電子發(fā)燒友論壇 ? 2022-10-26 14:51 ? 次閱讀

本文來源電子發(fā)燒友社區(qū),作者:HonestQiao, 帖子地址:https://bbs.elecfans.com/jishu_2303715_1_1.html


之前分享的文章中,在米爾MYD-YT507開發(fā)板上進行了攝像頭流媒體的嘗試,在此基礎(chǔ)上,進一步對之前的評測計劃進行了實現(xiàn)。

經(jīng)過充分的學習,最終應(yīng)用Fluter+Django+OpenCV,實現(xiàn)了一款米爾行車記錄儀,現(xiàn)將實現(xiàn)的具體內(nèi)容,與大家分享。

目錄:

  1. 行車記錄儀業(yè)務(wù)邏輯規(guī)劃
  2. 硬件設(shè)備準備
  3. 攝像頭信息記錄和實時畫面播放服務(wù)開發(fā)
  4. 攝像頭視頻信息記錄
  5. 攝像頭服務(wù)的完整代碼
  6. 歷史數(shù)據(jù)RestFul服務(wù)開發(fā)
  7. Flutter Web界面開發(fā)
  8. 整體運行效果
  9. 車試
  10. 實際代碼使用
  11. 感謝
  12. 總結(jié)

一、行車記錄儀業(yè)務(wù)邏輯規(guī)劃

經(jīng)過詳細的分析,規(guī)劃了如下的基本業(yè)務(wù)邏輯結(jié)構(gòu):

米爾行車記錄儀.drawio.png

整體分為三個部分:

  1. 記錄服務(wù):用于記錄攝像頭拍攝的視頻信息,以及提供攝像頭當前畫面的實時播放服務(wù)
  2. Django服務(wù):包括RestFul提供API接口獲取歷史數(shù)據(jù)信息,以及為Flutter的Web界面提供訪問服務(wù)
  3. Flutter Web界面,用于實時畫面播放、歷史記錄播放的界面

為了又快又好的開發(fā)行車記錄儀的實際界面,以及后續(xù)進行各移動平臺的App開發(fā),選擇了Flutter。事實證明,坑太多了。不過,跨平臺特性,確實好。

二、硬件設(shè)備準備:

開發(fā)這款行車記錄儀,實際使用到的硬件設(shè)備如下:

  1. 主控板:米爾MYD-YT507開發(fā)板
  2. 攝像頭:海康威視DS-E11 720P USB攝像頭
  3. 存儲卡:閃迪32GB高速MicroSD存儲卡
  4. 路由器:云來寶盒無線路由器
    image.png

路由器沒有拍照,用普通無線路由器即可,當然帶寬越高越好。

開發(fā)板上有兩個USB3.0接口,選一個接上路由器即可。

然后,將開發(fā)板使用網(wǎng)線連接到路由器,再上電,就可以進行實際的操作了。

我這邊實際使用中,電源接口有點松,容易突然斷電,所以使用膠帶進行了加固。

三、攝像頭實時畫面播放服務(wù)開發(fā)

在之前嘗試MJPEG視頻流直播的時候,使用了mjpeg_streamer,但不清楚如何進行視頻的分割。

因為行車記錄儀,一般都是按照一定的時間進行視頻的分割存放,避免單個視頻過大。

經(jīng)過仔細的學習了解,OpenCV也可以獲取攝像頭的信息,并按照需要寫入文件。

最后,采用了Python+OpenCV的方案,有Python負責具體的邏輯,Python-OpenCV負責攝像頭視頻數(shù)據(jù)的采集。

視頻采集部分,包含的具體功能為:

  1. 能夠采集攝像頭的數(shù)據(jù)
  2. 能夠提供實時視頻查看
  3. 能夠按時間寫入視頻數(shù)據(jù)到文件,自動進行分割

采集攝像頭的數(shù)據(jù),Python-opencv搞定。

寫入視頻數(shù)據(jù)到文件,Python簡單搞定。

提供實時視頻預覽,這個花了不少功夫。

因為同時要寫入到文件,還要提供預覽,數(shù)據(jù)需要復用。

經(jīng)過學習了解,可以將Python-opencv采集的畫面,按幀在HTTP以JPEG數(shù)據(jù)發(fā)送,那么播放端,就能收到MJPEG數(shù)據(jù)流,進行播放了。

因此,第一版,參考資料,實現(xiàn)了一個Python版的MJPEG播放服務(wù),讀取幀,寫入臨時文件,然后從臨時文件讀取數(shù)據(jù)返回。

為了提高效率,還進行了優(yōu)化,不寫入臨時文件,直接在內(nèi)存中進行轉(zhuǎn)換。

最終形成的代碼如下:

# http服務(wù)器請求處理:網(wǎng)頁、MJPEG數(shù)據(jù)流
class CamHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # mjpeg推流
        if self.path.endswith('.mjpg'):
            self.send_response(200)
            self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
            self.end_headers()
            while True:
                if is_stop:
                    break
                try:
                    # rc,img = cameraCapture.read()
                    rc,img = success,frame
                    if not rc:
                        continue
                    if True:
                        imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
                        jpg = Image.fromarray(imgRGB)
                        tmpFile = BytesIO()
                        jpg.save(tmpFile,'JPEG')
                        self.wfile.write(b"--jpgboundary")
                        self.send_header(b'Content-type','image/jpeg')
                        self.send_header(b'Content-length',str(tmpFile.getbuffer().nbytes))
                        self.end_headers()
                        jpg.save(self.wfile,'JPEG')
                    else:
                        img_fps = JPEG_QUALITY_VALUE
                        img_param = [int(cv2.IMWRITE_JPEG_QUALITY), img_fps]
                        img_str = cv2.imencode('.jpg', img, img_param)[1].tobytes() # change image to jpeg format
                        self.send_header('Content-type','image/jpeg')
                        self.end_headers()
                        self.wfile.write(img_str)
                        self.wfile.write(b"
--jpgboundary
") # end of this part
                    time.sleep(0.033)
                except KeyboardInterrupt:
                    self.wfile.write(b"
--jpgboundary--
")
                    break
                except BrokenPipeError:
                    continue
            return
        # 網(wǎng)頁
        if self.path == '/' or self.path.endswith('.html'):
            self.send_response(200)
            self.send_header('Content-type','text/html')
            self.end_headers()
            self.wfile.write(b'
') self.wfile.write(('live.mjpg' % self.headers.get('Host')).encode()) self.wfile.write(b'') return

這段代碼,提供了兩個功能:

  1. 如果通過瀏覽器訪問http://ip:端口/index.html,就會返回包含MJPEG調(diào)用地址的網(wǎng)頁
  2. 如果通過瀏覽器訪問http://ip:端口/live.mjpg,就會返回MJPEG流媒體數(shù)據(jù),以便播放

在開發(fā)過程中,運行該服務(wù)后,隨時可以通過瀏覽器查看效果。

其中涉及到opencv相關(guān)的知識,以及webserver相關(guān)的知識,大家可以了解相關(guān)的資料做基礎(chǔ),這里就不詳細說了。

本來以為提供了MJPEG服務(wù),就能夠在Flutter開發(fā)的Web界面中調(diào)用了。然而,實際使用時,發(fā)現(xiàn)坑來了。

Flutter的公共庫里面,有MJPEG的庫,但是在目前的版本中,已經(jīng)不能使用了。且官方認為用的人不多,在可預見的將來,不會修復。悲催啊!!!

條條大道通羅馬,此處不通開新路。

經(jīng)過再次的學習了解,F(xiàn)lutter的Video功能,支持Stream模式,其可以采用WebSocket的方式來獲取數(shù)據(jù),然后進行播放。

那么,只要能夠在服務(wù)端,將獲取的幀數(shù)據(jù),使用WebSocket提供,就能夠正常播放了。

最終,使用Python開發(fā)了能夠提供實時視頻數(shù)據(jù)的WebSocket服務(wù),具體代碼如下:

# websocket服務(wù)請求處理
async def CamTransmitHandler(websocket, path):
    print("Client Connected !")
    try :
        while True:
            # rc,img = cameraCapture.read()
            rc,img = success,frame
            if not rc:
                continue

            img_fps = JPEG_QUALITY_VALUE
            img_param = [int(cv2.IMWRITE_JPEG_QUALITY), img_fps]
            encoded = cv2.imencode('.jpg', img, img_param)[1]
            data = str(base64.b64encode(encoded))
            data = data[2:len(data)-1]
            await websocket.send(data)

            # cv2.imshow("Transimission", frame)
            # if cv2.waitKey(1) & 0xFF == ord('q'):
            #     break
        # cap.release()
    except EXCEPTION_CONNECTION_CLOSE as e:
        print("Client Disconnected !")
        # cap.release()
    except:
        print("Someting went Wrong !")

這個部分比之前的更簡單,就是簡單的轉(zhuǎn)換數(shù)據(jù),喂數(shù)據(jù)給WebSocket即可。

上述的兩部分代碼中,都沒有包含完整的邏輯處理過程,只有關(guān)鍵代碼部分。

各部分分別講完以后,將提供完整的代碼以供學習。

到這里,實時流媒體功能就實現(xiàn)了。

四、攝像頭視頻信息記錄

實際上,上一步的實時視頻功能,也依賴于這一步,因為其需要共享實際獲取的攝像頭信息。

其基本邏輯也比較簡單,步驟如下:

  1. 初始化opencv,開始攝像頭數(shù)據(jù)幀的獲取
  2. 檢測是否達到預定時間
  3. 未達到時間,則繼續(xù)寫入當前視頻
  4. 達到時間了,則關(guān)閉當前視頻,寫入縮略圖,并開啟新的文件寫入

具體代碼如下:

# 捕獲攝像頭
cameraCapture = cv2.VideoCapture(CAMERA_NO)

# 攝像頭參數(shù)設(shè)置
cameraCapture.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cameraCapture.set(cv2.CAP_PROP_FRAME_WIDTH, 240)
cameraCapture.set(cv2.CAP_PROP_SATURATION, 135)

fps = 30
size=(int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT)))

# 讀取捕獲的數(shù)據(jù)
success,frame = cameraCapture.read()

...

while True:
    if is_stop:
        success = False
        break;

    success,frame = cameraCapture.read()
    if not success:
        continue

    time_now = get_current_time()
    if time_now["time"] - time_record["time"] >= ROTATE_TIME:
        if time_record_prev:
            thubm_file = get_file_name(time_record_prev, 'thumbs', 'jpg')
            print("[Info] write to thumb: %s" % thubm_file)
            if not os.path.isfile(thubm_file):
                cv2.imwrite(thubm_file, frame)

        time_record = time_now
        time_record_prev = get_current_time()
        video_file = get_file_name(time_record_prev, 'videos', MEDIA_EXT)
        print("[Info] write to video: %s" % video_file)

    # encode = cv2.VideoWriter_fourcc(*"mp4v")
    encode = cv2.VideoWriter_fourcc(*'X264')
    # encode = cv2.VideoWriter_fourcc(*'AVC1')
    # encode = cv2.VideoWriter_fourcc(*'XVID')
    # encode = cv2.VideoWriter_fourcc(*'H264')
    videoWriter=cv2.VideoWriter(video_file, encode,fps,size) # mp4
    numFrameRemaining = ROTATE_TIME * fps    #攝像頭捕獲持續(xù)時間
    while success and numFrameRemaining > 0:
        videoWriter.write(frame)
        success,frame = cameraCapture.read()
        numFrameRemaining -= 1

cameraCapture.release()

上述代碼的邏輯其實很清晰,有opencv的基礎(chǔ),一看就懂。

有一個關(guān)鍵點需要注意的就是encode = cv2.VideoWriter_fourcc(*'X264'),在不同的環(huán)境下面,提供的編碼方式不完全相同。

在米爾MYD-YT507開發(fā)板的Ubuntu環(huán)境中,可以使用X264編碼。

上述代碼,會持續(xù)不斷的讀取攝像頭的數(shù)據(jù)幀,存放到frame變量中,然后寫入到視頻文件中。并進行時間判斷,以確定是否需要寫入到新的視頻文件中。

frame變量,在之前實時視頻服務(wù)中,也會使用,相當于是共享了。

五、攝像頭服務(wù)的完整代碼

經(jīng)過上面的兩個部分,就完成了攝像頭部分的服務(wù)代碼。

整體的代碼如下:

# -*- coding: utf-8 -*-
import signal
import cv2
import time
from PIL import Image
from threading import Thread
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
from io import BytesIO

import os
import sys
import websockets
import asyncio
import base64
import ctypes
import inspect

CAMERA_NO = 2
ROTATE_TIME = 120
MJPEG_ENABLE = 1
WEBSOCKET_ENABLE = 1
MJPEG_SERVER_PORT = 28888
WEBSOCKET_PORT = 28889
JPEG_QUALITY_VALUE = 65
STORE_DIR = "./data/" if os.uname()[0] == 'Darwin' else "/sdcard/data/"
MEDIA_EXT = "mkv"

EXCEPTION_CONNECTION_CLOSE = websockets.exceptions.ConnectionClosed if sys.version[:3] == '3.6' else websockets.ConnectionClosed

def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    try:
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            # pass
            raise ValueError("invalid thread id")
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")
    except Exception as err:
        print(err)


def stop_thread(thread):
    """終止線程"""
    _async_raise(thread.ident, SystemExit)

# 信號處理回調(diào)
def signal_handler(signum, frame):
    # global cameraCapture
    # global thread
    # global server
    # global is_stop
    # global success
    print('signal_handler: caught signal ' + str(signum))
    if signum == signal.SIGINT.value:
        print('stop server:')
        is_stop = True
        success = False
        print("mjpeg server.socket.close...")
        server.socket.close()
        print("mjpeg server.shutdown...")
        server.shutdown()
        print("ws server.socket.close...")
        server_ws.ws_server.close()
        time.sleep(1)
        # print("ws server.shutdown...")
        # await server_ws.ws_server.wait_closed()
        print("mjpeg thread.shutdown...")
        thread_mjpeg.join()
        print("ws loop.shutdown...")  
        # event_loop_ws.stop()
        event_loop_ws.call_soon_threadsafe(event_loop_ws.stop)
        time.sleep(1)
        # print("ws thread.shutdown...")  
        # stop_thread(thread_ws)
        # time.sleep(1)
        # print(server)
        # print(server_ws)
        print(thread_mjpeg.is_alive())
        print(thread_ws.is_alive())
        print(event_loop_ws.is_running())
        # thread_ws.join()
        print("cameraCapture.release...")
        cameraCapture.release()
        print("quit...")
        # print(server_ws)
        sys.exit(0)

# http服務(wù)器請求處理:網(wǎng)頁、MJPEG數(shù)據(jù)流
class CamHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # mjpeg推流
        if self.path.endswith('.mjpg'):
            self.send_response(200)
            self.send_header('Content-type','multipart/x-mixed-replace; boundary=--jpgboundary')
            self.end_headers()
            while True:
                if is_stop:
                    break
                try:
                    # rc,img = cameraCapture.read()
                    rc,img = success,frame
                    if not rc:
                        continue
                    if True:
                        imgRGB=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
                        jpg = Image.fromarray(imgRGB)
                        tmpFile = BytesIO()
                        jpg.save(tmpFile,'JPEG')
                        self.wfile.write(b"--jpgboundary")
                        self.send_header(b'Content-type','image/jpeg')
                        self.send_header(b'Content-length',str(tmpFile.getbuffer().nbytes))
                        self.end_headers()
                        jpg.save(self.wfile,'JPEG')
                    else:
                        img_fps = JPEG_QUALITY_VALUE
                        img_param = [int(cv2.IMWRITE_JPEG_QUALITY), img_fps]
                        img_str = cv2.imencode('.jpg', img, img_param)[1].tobytes() # change image to jpeg format
                        self.send_header('Content-type','image/jpeg')
                        self.end_headers()
                        self.wfile.write(img_str)
                        self.wfile.write(b"
--jpgboundary
") # end of this part
                    time.sleep(0.033)
                except KeyboardInterrupt:
                    self.wfile.write(b"
--jpgboundary--
")
                    break
                except BrokenPipeError:
                    continue
            return
        # 網(wǎng)頁
        if self.path == '/' or self.path.endswith('.html'):
            self.send_response(200)
            self.send_header('Content-type','text/html')
            self.end_headers()
            self.wfile.write(b'
') self.wfile.write(('live.mjpg' % self.headers.get('Host')).encode()) self.wfile.write(b'') return class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): """Handle requests in a separate thread.""" # 啟動MJPEG服務(wù) def mjpeg_server_star(): global success global server global thread_mjpeg try: server = ThreadedHTTPServer(('0.0.0.0', MJPEG_SERVER_PORT), CamHandler) print("mjpeg server started: http://0.0.0.0:%d" % MJPEG_SERVER_PORT) # server.serve_forever() thread_mjpeg = Thread(target=server.serve_forever); thread_mjpeg.start() except KeyboardInterrupt: print("mjpeg server stoping...") server.socket.close() server.shutdown() print("mjpeg server stoped") # websocket服務(wù)請求處理 async def CamTransmitHandler(websocket, path): print("Client Connected !") try : while True: # rc,img = cameraCapture.read() rc,img = success,frame if not rc: continue img_fps = JPEG_QUALITY_VALUE img_param = [int(cv2.IMWRITE_JPEG_QUALITY), img_fps] encoded = cv2.imencode('.jpg', img, img_param)[1] data = str(base64.b64encode(encoded)) data = data[2:len(data)-1] await websocket.send(data) # cv2.imshow("Transimission", frame) # if cv2.waitKey(1) & 0xFF == ord('q'): # break # cap.release() except EXCEPTION_CONNECTION_CLOSE as e: print("Client Disconnected !") # cap.release() except: print("Someting went Wrong !") # websocket服務(wù)器啟動 def websocket_server_start(): global thread_ws global server_ws global event_loop_ws event_loop_ws = asyncio.new_event_loop() def run_server(): global server_ws print("websocket server started: ws://0.0.0.0:%d" % WEBSOCKET_PORT) server_ws = websockets.serve(CamTransmitHandler, port=WEBSOCKET_PORT, loop=event_loop_ws) event_loop_ws.run_until_complete(server_ws) event_loop_ws.run_forever() thread_ws = Thread(target=run_server) thread_ws.start() # try: # yield # except e: # print("An exception occurred") # finally: # event_loop.call_soon_threadsafe(event_loop.stop) # 獲取存儲的文件名 def get_file_name(time_obj, path, ext): file_name_time = "%04d-%02d-%02d_%02d-%02d-%02d" % (time_obj["year"], time_obj["month"], time_obj["day"], time_obj["hour"], time_obj["min"], 0) return '%s/%s/%s.%s' % (STORE_DIR, path, file_name_time, ext) # 獲取當前整分時間 def get_current_time(): time_now = time.localtime() time_int = int(time.time()) return { "year": time_now.tm_year, "month": time_now.tm_mon, "day": time_now.tm_mday, "hour": time_now.tm_hour, "min": time_now.tm_min, "sec": time_now.tm_sec, "time": time_int - time_now.tm_sec } # 設(shè)置信號回調(diào) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # 捕獲攝像頭 cameraCapture = cv2.VideoCapture(CAMERA_NO) # 攝像頭參數(shù)設(shè)置 cameraCapture.set(cv2.CAP_PROP_FRAME_WIDTH, 320) cameraCapture.set(cv2.CAP_PROP_FRAME_WIDTH, 240) cameraCapture.set(cv2.CAP_PROP_SATURATION, 135) fps = 30 size=(int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))) # 讀取捕獲的數(shù)據(jù) success,frame = cameraCapture.read() if not success: print("camera start failed.") quit() is_stop = False server = None server_ws = None event_loop_ws = None thread_mjpeg = None thread_ws = None mjpeg_server_star() websocket_server_start() print("record server star:") thubm_file = None video_file = None time_start = int(time.time()) time_record = {"time":0} time_record_prev = None while True: if is_stop: success = False break; success,frame = cameraCapture.read() if not success: continue time_now = get_current_time() if time_now["time"] - time_record["time"] >= ROTATE_TIME: if time_record_prev: thubm_file = get_file_name(time_record_prev, 'thumbs', 'jpg') print("[Info] write to thumb: %s" % thubm_file) if not os.path.isfile(thubm_file): cv2.imwrite(thubm_file, frame) time_record = time_now time_record_prev = get_current_time() video_file = get_file_name(time_record_prev, 'videos', MEDIA_EXT) print("[Info] write to video: %s" % video_file) # encode = cv2.VideoWriter_fourcc(*"mp4v") encode = cv2.VideoWriter_fourcc(*'X264') # encode = cv2.VideoWriter_fourcc(*'AVC1') # encode = cv2.VideoWriter_fourcc(*'XVID') # encode = cv2.VideoWriter_fourcc(*'H264') videoWriter=cv2.VideoWriter(video_file, encode,fps,size) # mp4 numFrameRemaining = ROTATE_TIME * fps #攝像頭捕獲持續(xù)時間 while success and numFrameRemaining > 0: videoWriter.write(frame) success,frame = cameraCapture.read() numFrameRemaining -= 1 cameraCapture.release()

在上述代碼中,除了前面說過的三個部分,還包括啟動web和websocket線程的部分。因為核心邏輯為讀取視頻數(shù)據(jù)并寫入文件,所以其他部分,以線程的模式啟動,以便同時進行處理。

將上述代碼保存為DrivingRecorderAndMjpegServer.py,然后運行即可。(依賴包,見代碼庫中requirements.txt)

Snipaste_2022-09-03_22-37-21.png

實際訪問效果如下:

Snipaste_2022-09-03_22-39-34.png

六、歷史數(shù)據(jù)RestFul服務(wù)開發(fā)

歷史數(shù)據(jù)服務(wù),本來也可以使用Python直接手寫,但考慮到可擴展性,使用Django來進行了編寫。

Djano服務(wù),需要提供如下的功能:

  1. 提供api接口,以便獲取歷史數(shù)據(jù)記錄列表,便于前端界面呈現(xiàn)展示
  2. 提供Flutter Web界面代碼文件的托管,以便通過瀏覽器訪問
  3. 提供靜態(tài)文件的訪問,例如查看歷史視頻文件

2和3本質(zhì)都是一個問題,通過Django的static功能,就能實現(xiàn)。

也就是在settings.py配置中,提供下面的配置即可:

STATIC_URL = 'static/'

STATICFILES_DIRS = [
    BASE_DIR / "static"
]

1對外提供api服務(wù),則需要設(shè)置對應(yīng)的url接口,以及讀取歷史文件信息,生成前端需要的json數(shù)據(jù)結(jié)構(gòu),這部分的具體代碼如下:

# 媒體文件存放目錄,以及縮略圖和視頻文件的后綴
THUMB_HOME_DIR = "%s/%s/data/thumbs/" % (BASE_DIR, STATIC_URL)
VIDEO_HOME_DIR = "%s/%s/data/videos/" % (BASE_DIR, STATIC_URL)

IMG_FILTER = [".jpg"]
MEDIA_FILTER = [ ".mkv"]
import json
from django.shortcuts import render, HttpResponse
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework.decorators import api_view, permission_classes
import os
from django.conf import settings

THUMB_HOME_DIR = settings.THUMB_HOME_DIR
VIDEO_HOME_DIR = settings.VIDEO_HOME_DIR
IMG_FILTER = settings.IMG_FILTER
MEDIA_FILTER = settings.MEDIA_FILTER

# Create your views here.
@api_view(['GET'],)
@permission_classes([AllowAny],)
def hello_django(request):
    str = '''[
  {
    "id": 1,
    "time": "2022-07-28 21:00",
    "title": "2022-07-28 21:00",
    "body": "videos/2022-07-28_2100.mp4"
  },
  {
    "id": 2,
    "time": "2022-07-28 23:00",
    "title": "2022-07-28 23:00",
    "body": "videos/2022-07-28_2300.mp4"
  },
  {
    "id": 3,
    "time": "2022-07-28 25:00",
    "title": "2022-07-28 25:00",
    "body": "videos/2022-07-28_2500.mp4"
  }
]'''
    _json = json.loads(str)
    return HttpResponse(json.dumps(_json), content_type='application/json')


@api_view(['GET'],)
@permission_classes([AllowAny],)
def history_list(request):
    next = request.GET.get("next", '')
    print(f"thumb next = {next}")
    path = "/".join(request.path.split("/")[3:])
    print(f"thumb request.path= {request.path}")
    print(f"thumb path = {path}")

    #print os.listdir(FILE_HOME_DIR+".none/")
    data = {"files":[], "dirs":[]}
    print(data)
    child_path = THUMB_HOME_DIR+next
    print(f"child_path = {child_path}")
    data['cur_dir'] = path+next
    print(data)
    for dir in os.listdir(child_path):
        if os.path.isfile(child_path+"/"+dir):
            if os.path.splitext(dir)[1] in IMG_FILTER:
                data['files'].append(dir)
        else:
            data['dirs'].append(dir)

    print(data)
    data['files']=sorted(data['files'])
    data['files'].reverse()
    data['infos'] = []

    for i in range(0,len(data['files'])):
        thumb_name = data['files'][i]
        video_name = thumb_name.replace('.jpg', MEDIA_FILTER[0])
        file_time = thumb_name.replace('.jpg', '').replace('_', ' ')
        data['infos'].append(
          {
            "id": i,
            "time": file_time,
            "title": file_time,
            "body": thumb_name,
            'thumb': thumb_name, 
            'video': video_name
          }
        )
    return Response(data['infos'], status = 200)

其中有兩個接口:

hello_django是最開始學習使用的,返回寫死的json數(shù)據(jù)。

history_list,則是自動遍歷縮略圖文件夾,獲取縮略圖文件信息,并生成所需要的json數(shù)據(jù)格式。

在對應(yīng)的代碼庫文件中,也包含了requirements.txt,其中標明了實際需要的依賴庫。

下載代碼,進入manage.py所在的目錄后,執(zhí)行下面的命令即可啟動:

image.png

訪問192.168.1.15:8000/app/hellodjango

image.png

訪問:History List – Django REST framework

image.png

可以看到 history_list接口,已經(jīng)可以提供實際需要的數(shù)據(jù)了。

七、Flutter Web界面開發(fā)

這個部分設(shè)計的代碼比較多,所以只對關(guān)鍵部分的代碼進行說明。

開發(fā)的實際代碼,位于lib目錄,具體為:

image.png

  • globals.dart:全局變量定義
  • main.dart:程序入口
  • home_page.dart:首頁
  • live_page.dart:實時播放
  • live_page_mp4.dart:測試播放mp4視頻
  • history_page.dart:歷史記錄列表頁面
  • video_detail.dart:單條歷史記錄詳情
  • video_play.dart:播放具體的歷史視頻
  • video_model.dart:單條記錄的數(shù)據(jù)模型
  • http_service.dart:請求RestFul接口
  • websocket.dart:實時視頻的WebSocket請求

整個界面,使用了Scaffold模擬手機/Pad的操作界面,具體界面如下:

image.png

在實時畫面界面中,使用了WebSocket監(jiān)聽,獲取到信息,就使用Stream模式,推送給視頻播放。

在歷史記錄界面中,則通過RestFul請求列表數(shù)據(jù),然后呈現(xiàn)。

八、整體運行效果

實際的運行效果,不用多說,看界面就成:

  1. 實時畫面:Snipaste_2022-09-03_23-09-04.png
  2. 歷史記錄列表:Snipaste_2022-09-03_23-09-15.png
  3. 歷史記錄播放:Snipaste_2022-09-03_23-09-25.png

九、車試:

經(jīng)過反復的測試驗證,確保各項功能完整后,進行了上車實測。

image.png

因為最近的疫情原因,所以只在村里轉(zhuǎn)了一圈,進行了實際測試,可以查看最后的視頻。后續(xù)有機會,再找個晴朗的天氣,去環(huán)境優(yōu)美的地方實際拍攝錄制。

十、實際代碼說明:

完整的代碼,請通過米爾行車記錄儀: 米爾行車記錄儀 (https://gitee.com/honestqiao/MYiR-Driving-Recorder)獲取。

代碼目錄說明如下:

  • DrivingRecorder:攝像頭服務(wù)
  • backend:RestFul服務(wù)
  • frontend:Flutter Web界面

在以上倉庫中,包含了詳細的代碼使用說明。

在實際應(yīng)用中,將記錄視頻的data目錄與后端static/data目錄關(guān)聯(lián),以便兩者統(tǒng)一。

十一、感謝

在研究學習的過程中,參考了數(shù)十篇各類資料,先將部分列出如下。對所有學習過的資料的作者,表示深深的感謝。

十二、總結(jié)

在研究學習的過程中,對Linux系統(tǒng)下的UVC框架有了進一步的了解,對Flutter進行應(yīng)用開發(fā)有了實際的了解,對OpenCV的實際應(yīng)用也有了具體的了解。

在實際開發(fā)的過程中,遇到的最大的坑來自Flutter,因為變化太快,有一些功能可能兼容性沒有跟上。不過更多是自己學藝不精導致的。

另外,目前還只是V1.0版本,后續(xù)還存在較大的優(yōu)化空間。例如對于OpenCV的應(yīng)用,可以調(diào)整參數(shù),優(yōu)化獲取的視頻數(shù)據(jù)的指令和大小等。這些有待于進一步學習后進行。

最主要的,對米爾MYD-YT507開發(fā)板有了深入的了解,進行了實際的應(yīng)用。作為一款車規(guī)級處理器T507的開發(fā)板,名不虛傳!

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 米爾科技
    +關(guān)注

    關(guān)注

    5

    文章

    227

    瀏覽量

    20957
收藏 人收藏

    評論

    相關(guān)推薦

    人臉疲勞檢測應(yīng)用-米爾基于RK3576核心/開發(fā)板

    本文將介紹基于米爾電子MYD-LR3576開發(fā)板米爾基于瑞芯微RK3576開發(fā)板)的人臉疲勞檢測方案測試。
    的頭像 發(fā)表于 12-20 08:06 ?130次閱讀
    人臉疲勞檢測應(yīng)用-<b class='flag-5'>米爾</b>基于RK3576核心<b class='flag-5'>板</b>/<b class='flag-5'>開發(fā)板</b>

    如何用OpenCV進行手勢識別--基于米爾全志T527開發(fā)板

    本文將介紹基于米爾電子MYD-LT527開發(fā)板米爾基于全志T527開發(fā)板)的OpenCV手勢識
    的頭像 發(fā)表于 12-13 08:04 ?608次閱讀
    如何用<b class='flag-5'>OpenCV</b>進行手勢識別--基于<b class='flag-5'>米爾</b>全志T527<b class='flag-5'>開發(fā)板</b>

    米爾-Xilinx XC7A100T FPGA開發(fā)板試用】測試一

    感謝米爾電子和電子發(fā)燒友提供的米爾-Xilinx XC7A100T FPGA開發(fā)板MYD-J7A100T用的 FPGA 為 XILINX 公司 ARTIX-7 系列的 XC 7A1
    發(fā)表于 12-08 08:48

    FacenetPytorch人臉識別方案--基于米爾全志T527開發(fā)板

    本篇測評由電子工程世界的優(yōu)秀測評者“小火苗”提供。本文將介紹基于米爾電子MYD-LT527開發(fā)板米爾基于全志T527開發(fā)板)的Facene
    的頭像 發(fā)表于 11-28 15:12 ?287次閱讀
    FacenetPytorch人臉識別方案--基于<b class='flag-5'>米爾</b>全志T527<b class='flag-5'>開發(fā)板</b>

    追加名額丨米爾瑞芯微RK3576開發(fā)板有獎試用

    米爾與瑞芯微合作發(fā)布的新品基于瑞芯微RK3576應(yīng)用處理器的MYD-LR3576開發(fā)板免費試用活動加碼啦~~米爾追加了2塊價值849元的
    的頭像 發(fā)表于 11-22 01:00 ?167次閱讀
    追加名額丨<b class='flag-5'>米爾</b>瑞芯微RK3576<b class='flag-5'>開發(fā)板</b>有獎<b class='flag-5'>試用</b>

    米爾-Xilinx XC7A100T FPGA開發(fā)板試用】+01.開箱(zmj)

    米爾-Xilinx XC7A100T FPGA開發(fā)板試用】+01.開箱(zmj) 1.感謝 感謝米爾電子和電子發(fā)燒友提供的此次試用機會,可
    發(fā)表于 11-12 15:45

    有獎丨米爾 瑞芯微RK3576開發(fā)板免費試用

    米爾與瑞芯微合作發(fā)布的新品基于瑞芯微RK3576應(yīng)用處理器的MYD-LR3576開發(fā)板免費試用活動來啦~~米爾提供了7塊價值849元的
    的頭像 發(fā)表于 11-12 01:00 ?301次閱讀
    有獎丨<b class='flag-5'>米爾</b> 瑞芯微RK3576<b class='flag-5'>開發(fā)板</b>免費<b class='flag-5'>試用</b>

    基于OPENCV的相機捕捉視頻進行人臉檢測--米爾NXP i.MX93開發(fā)板

    本文將介紹基于米爾電子MYD-LMX93開發(fā)板米爾基于NXPi.MX93開發(fā)板)的基于OpenCV
    的頭像 發(fā)表于 11-07 09:03 ?1081次閱讀
    基于<b class='flag-5'>OPENCV</b>的相機捕捉視頻進行人臉檢測--<b class='flag-5'>米爾</b>NXP i.MX93<b class='flag-5'>開發(fā)板</b>

    點擊參與米爾NXP i.MX 93開發(fā)板有獎試用

    米爾與NXP合作發(fā)布的新品基于NXPi.MX93應(yīng)用處理器的MYD-LMX9X開發(fā)板免費試用活動來啦~~米爾提供了3塊價值678元的
    的頭像 發(fā)表于 06-13 08:02 ?523次閱讀
    點擊參與<b class='flag-5'>米爾</b>NXP i.MX 93<b class='flag-5'>開發(fā)板</b>有獎<b class='flag-5'>試用</b>

    米爾NXP i.MX 93開發(fā)板的Qt開發(fā)指南

    1.概述Qt是一個跨平臺的圖形應(yīng)用開發(fā)框架,被應(yīng)用在不同尺寸設(shè)備和平臺上,同時提供不同版權(quán)版本供用戶選擇。米爾NXPi.MX93開發(fā)板MYD-LMX9X
    的頭像 發(fā)表于 06-07 08:01 ?1730次閱讀
    <b class='flag-5'>米爾</b>NXP i.MX 93<b class='flag-5'>開發(fā)板</b>的Qt<b class='flag-5'>開發(fā)</b>指南

    免費!NXP i.MX 93開發(fā)板有獎試用

    米爾與NXP合作發(fā)布的新品基于NXPi.MX93應(yīng)用處理器的MYD-LMX9X開發(fā)板免費試用活動來啦~~米爾提供了2塊價值678
    的頭像 發(fā)表于 05-23 08:01 ?722次閱讀
    免費!NXP i.MX 93<b class='flag-5'>開發(fā)板</b>有獎<b class='flag-5'>試用</b>

    通過物聯(lián)網(wǎng)管理多臺MQTT設(shè)備-基于米爾T527開發(fā)板

    本篇測評由電子工程世界的優(yōu)秀測評者“JerryZhen”提供。本文將介紹基于米爾電子MYD-LT527開發(fā)板的網(wǎng)關(guān)方案測試。一、系統(tǒng)概述基于米爾-全志T527設(shè)計一個簡易的物聯(lián)網(wǎng)網(wǎng)關(guān),
    的頭像 發(fā)表于 05-10 08:01 ?683次閱讀
    通過物聯(lián)網(wǎng)管理多臺MQTT設(shè)備-基于<b class='flag-5'>米爾</b>T527<b class='flag-5'>開發(fā)板</b>

    G2D圖像處理硬件調(diào)用和測試-基于米爾-全志T113-i開發(fā)板

    本篇測評由電子工程世界的優(yōu)秀測評者“jf_99374259”提供。本文將介紹基于米爾電子MYD-YT113i開發(fā)板的G2D圖像處理硬件調(diào)用和測試。MYC-YT113i核心
    的頭像 發(fā)表于 04-12 08:01 ?664次閱讀
    G2D圖像處理硬件調(diào)用和測試-基于<b class='flag-5'>米爾</b>-全志T113-i<b class='flag-5'>開發(fā)板</b>

    米爾-全志T113-i開發(fā)板試用】發(fā)布opencv-mobile米爾t113i專享預編譯包

    MYC-YT113i核心開發(fā)板 真正的國產(chǎn)核心,100%國產(chǎn)物料認證 國產(chǎn)T113-i處理器配備2*Cortex-A7@1.2GHz ,RISC-V 外置DDR3接口、支持視頻編
    發(fā)表于 02-25 20:23

    ROS系統(tǒng)的智能車開發(fā)-基于米爾芯馳MYD-JD9X開發(fā)板

    本篇測評由電子工程世界的優(yōu)秀測評者“mameng”提供。本文將介紹基于米爾電子MYD-JD9X開發(fā)板的ROS系統(tǒng)智能車開發(fā)。目前實現(xiàn)ROS的方式主要有兩種:Ubuntu系統(tǒng)+ROS;U
    的頭像 發(fā)表于 01-26 08:01 ?919次閱讀
    ROS系統(tǒng)的智能車<b class='flag-5'>開發(fā)</b>-基于<b class='flag-5'>米爾</b>芯馳<b class='flag-5'>MYD</b>-JD9X<b class='flag-5'>開發(fā)板</b>
    主站蜘蛛池模板: 美女张开大腿| 国产在线AV一区二区香蕉| 亚洲成A人片在线观看中文不卡| 激情内射亚州一区二区三区爱妻 | 2020国产成人精品免费视频| 欧美视频 亚洲视频| 国产精品一区二区人妻无码| 中文国产成人精品久久免费| 千禧金瓶梅快播| 国产伊人久久| 98久久人妻无码精品系列蜜桃 | 国产 亚洲 中文在线 字幕| 亚洲精品理论电影在线观看| 蜜芽tv在线观看免费网站| 二级毛片免费观看全程| 艳照门在线播放| 日本色呦呦| 精品性影院一区二区三区内射 | 国精产品一区一区三区M| 97精品在线| 亚洲AV成人片色在线观看网站| 麻豆高清免费国产一区| 国产成人午夜精品免费视频| 影音先锋av男人资源| 色99蜜臀AV无码| 久久最新地址获取| 广播电台在线收听| 中文字幕精品在线观看| 视频一区国产第一页| 老师洗澡让我吃她胸的视频 | 羞羞影院午夜男女爽爽影院网站| 老师系列高H文| 国产欧美日韩精品a在线观看高清| 69日本xxⅹxxxxx18| 小草观看免费高清视频| 女人把腿张开叫男人桶免费视频| 国产午夜福利100集发布| CHINA中国东北GURMA| 亚洲天堂久久久| 色裕插插插影视| 麻豆一区二区免费播放网站|