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

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

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

實(shí)操丨米爾MYD-YT507H開(kāi)發(fā)板基于Fluter+Django+OpenCV的行車(chē)記錄儀

米爾電子 ? 2022-09-30 09:34 ? 次閱讀

本篇測(cè)評(píng)由電子工程世界的優(yōu)秀測(cè)評(píng)者“HonestQiao”提供。
此次的板卡測(cè)試,是米爾MYD-YT507H開(kāi)發(fā)板的行車(chē)記錄儀測(cè)試體驗(yàn)。

之前分享的文章中,在米爾MYD-YT507H開(kāi)發(fā)板上進(jìn)行了攝像頭流媒體的嘗試,在此基礎(chǔ)上,進(jìn)一步對(duì)之前的評(píng)測(cè)計(jì)劃進(jìn)行了實(shí)現(xiàn)。經(jīng)過(guò)充分的學(xué)習(xí),最終應(yīng)用Fluter+Django+OpenCV,實(shí)現(xiàn)了一款米爾行車(chē)記錄儀,現(xiàn)將實(shí)現(xiàn)的具體內(nèi)容,與大家分享。目錄:

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

一、行車(chē)記錄儀業(yè)務(wù)邏輯規(guī)劃經(jīng)過(guò)詳細(xì)的分析,規(guī)劃了如下的基本業(yè)務(wù)邏輯結(jié)構(gòu):2d1058b4-4013-11ed-b180-dac502259ad0.png整體分為三個(gè)部分:

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

為了又快又好的開(kāi)發(fā)行車(chē)記錄儀的實(shí)際界面,以及后續(xù)進(jìn)行各移動(dòng)平臺(tái)的App開(kāi)發(fā),選擇了Flutter。事實(shí)證明,坑太多了。不過(guò),跨平臺(tái)特性,確實(shí)好。


二、硬件設(shè)備準(zhǔn)備:開(kāi)發(fā)這款行車(chē)記錄儀,實(shí)際使用到的硬件設(shè)備如下:

  1. 主控板:米爾MYD-YT507H開(kāi)發(fā)板
  2. 攝像頭:??低?/u>DS-E11 720P USB攝像頭
  3. 存儲(chǔ)卡:閃迪32GB高速M(fèi)icroSD存儲(chǔ)卡
  4. 路由器:云來(lái)寶盒無(wú)線(xiàn)路由器

路由器沒(méi)有拍照,用普通無(wú)線(xiàn)路由器即可,當(dāng)然帶寬越高越好。開(kāi)發(fā)板上有兩個(gè)USB3.0接口,選一個(gè)接上路由器即可。然后,將開(kāi)發(fā)板使用網(wǎng)線(xiàn)連接到路由器,再上電,就可以進(jìn)行實(shí)際的操作了。我這邊實(shí)際使用中,電源接口有點(diǎn)松,容易突然斷電,所以使用膠帶進(jìn)行了加固。三、攝像頭實(shí)時(shí)畫(huà)面播放服務(wù)開(kāi)發(fā)在之前嘗試MJPEG視頻流直播的時(shí)候,使用了mjpeg_streamer,但不清楚如何進(jìn)行視頻的分割。因?yàn)樾熊?chē)記錄儀,一般都是按照一定的時(shí)間進(jìn)行視頻的分割存放,避免單個(gè)視頻過(guò)大。經(jīng)過(guò)仔細(xì)的學(xué)習(xí)了解,OpenCV也可以獲取攝像頭的信息,并按照需要寫(xiě)入文件。最后,采用了Python+OpenCV的方案,有Python負(fù)責(zé)具體的邏輯,Python-OpenCV負(fù)責(zé)攝像頭視頻數(shù)據(jù)的采集。視頻采集部分,包含的具體功能為:

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

采集攝像頭的數(shù)據(jù),Python-opencv搞定。寫(xiě)入視頻數(shù)據(jù)到文件,Python簡(jiǎn)單搞定。提供實(shí)時(shí)視頻預(yù)覽,這個(gè)花了不少功夫。因?yàn)橥瑫r(shí)要寫(xiě)入到文件,還要提供預(yù)覽,數(shù)據(jù)需要復(fù)用。經(jīng)過(guò)學(xué)習(xí)了解,可以將Python-opencv采集的畫(huà)面,按幀在HTTP以JPEG數(shù)據(jù)發(fā)送,那么播放端,就能收到MJPEG數(shù)據(jù)流,進(jìn)行播放了。因此,第一版,參考資料,實(shí)現(xiàn)了一個(gè)Python版的MJPEG播放服務(wù),讀取幀,寫(xiě)入臨時(shí)文件,然后從臨時(shí)文件讀取數(shù)據(jù)返回。為了提高效率,還進(jìn)行了優(yōu)化,不寫(xiě)入臨時(shí)文件,直接在內(nèi)存中進(jìn)行轉(zhuǎn)換。最終形成的代碼如下:

# http服務(wù)器請(qǐng)求處理:網(wǎng)頁(yè)、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)頁(yè)
        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'Live video')
            self.wfile.write(('' % self.headers.get('Host')).encode())
            self.wfile.write(b'')
            return

這段代碼,提供了兩個(gè)功能:

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

在開(kāi)發(fā)過(guò)程中,運(yùn)行該服務(wù)后,隨時(shí)可以通過(guò)瀏覽器查看效果。其中涉及到opencv相關(guān)的知識(shí),以及webserver相關(guān)的知識(shí),大家可以了解相關(guān)的資料做基礎(chǔ),這里就不詳細(xì)說(shuō)了。本來(lái)以為提供了MJPEG服務(wù),就能夠在Flutter開(kāi)發(fā)的Web界面中調(diào)用了。然而,實(shí)際使用時(shí),發(fā)現(xiàn)坑來(lái)了。Flutter的公共庫(kù)里面,有MJPEG的庫(kù),但是在目前的版本中,已經(jīng)不能使用了。且官方認(rèn)為用的人不多,在可預(yù)見(jiàn)的將來(lái),不會(huì)修復(fù)。悲催啊?。?!條條大道通羅馬,此處不通開(kāi)新路。經(jīng)過(guò)再次的學(xué)習(xí)了解,F(xiàn)lutter的Video功能,支持Stream模式,其可以采用WebSocket的方式來(lái)獲取數(shù)據(jù),然后進(jìn)行播放。那么,只要能夠在服務(wù)端,將獲取的幀數(shù)據(jù),使用WebSocket提供,就能夠正常播放了。最終,使用Python開(kāi)發(fā)了能夠提供實(shí)時(shí)視頻數(shù)據(jù)的WebSocket服務(wù),具體代碼如下:

# websocket服務(wù)請(qǐng)求處理
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 !")

這個(gè)部分比之前的更簡(jiǎn)單,就是簡(jiǎn)單的轉(zhuǎn)換數(shù)據(jù),喂數(shù)據(jù)給WebSocket即可。上述的兩部分代碼中,都沒(méi)有包含完整的邏輯處理過(guò)程,只有關(guān)鍵代碼部分。各部分分別講完以后,將提供完整的代碼以供學(xué)習(xí)。到這里,實(shí)時(shí)流媒體功能就實(shí)現(xiàn)了。
四、攝像頭視頻信息記錄實(shí)際上,上一步的實(shí)時(shí)視頻功能,也依賴(lài)于這一步,因?yàn)槠湫枰蚕韺?shí)際獲取的攝像頭信息。其基本邏輯也比較簡(jiǎn)單,步驟如下:

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

具體代碼如下:

# 捕獲攝像頭
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ù)時(shí)間
    while success and numFrameRemaining > 0:
        videoWriter.write(frame)
        success,frame = cameraCapture.read()
        numFrameRemaining -= 1

cameraCapture.release()

上述代碼的邏輯其實(shí)很清晰,有opencv的基礎(chǔ),一看就懂。有一個(gè)關(guān)鍵點(diǎn)需要注意的就是encode = cv2.VideoWriter_fourcc(*'X264'),在不同的環(huán)境下面,提供的編碼方式不完全相同。在米爾MYD-YT507H開(kāi)發(fā)板的Ubuntu環(huán)境中,可以使用X264編碼。上述代碼,會(huì)持續(xù)不斷的讀取攝像頭的數(shù)據(jù)幀,存放到frame變量中,然后寫(xiě)入到視頻文件中。并進(jìn)行時(shí)間判斷,以確定是否需要寫(xiě)入到新的視頻文件中。frame變量,在之前實(shí)時(shí)視頻服務(wù)中,也會(huì)使用,相當(dāng)于是共享了。
五、攝像頭服務(wù)的完整代碼經(jīng)過(guò)上面的兩個(gè)部分,就完成了攝像頭部分的服務(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):
    """終止線(xiàn)程"""
    _async_raise(thread.ident, SystemExit)

# 信號(hào)處理回調(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ù)器請(qǐng)求處理:網(wǎng)頁(yè)、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)頁(yè)
        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'Live video')
            self.wfile.write(('' % self.headers.get('Host')).encode())
            self.wfile.write(b'')
            return

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

# 啟動(dòng)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ù)請(qǐng)求處理
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ù)器啟動(dòng)
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)

# 獲取存儲(chǔ)的文件名
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)

# 獲取當(dāng)前整分時(shí)間
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è)置信號(hào)回調(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ù)時(shí)間
    while success and numFrameRemaining > 0:
        videoWriter.write(frame)
        success,frame = cameraCapture.read()
        numFrameRemaining -= 1

cameraCapture.release()

在上述代碼中,除了前面說(shuō)過(guò)的三個(gè)部分,還包括啟動(dòng)web和websocket線(xiàn)程的部分。因?yàn)楹诵倪壿嫗樽x取視頻數(shù)據(jù)并寫(xiě)入文件,所以其他部分,以線(xiàn)程的模式啟動(dòng),以便同時(shí)進(jìn)行處理。將上述代碼保存為DrivingRecorderAndMjpegServer.py,然后運(yùn)行即可。(依賴(lài)包,見(jiàn)代碼庫(kù)中requirements.txt)2d41a2c0-4013-11ed-b180-dac502259ad0.png  實(shí)際訪(fǎng)問(wèn)效果如下:2d677842-4013-11ed-b180-dac502259ad0.png六、歷史數(shù)據(jù)RestFul服務(wù)開(kāi)發(fā)歷史數(shù)據(jù)服務(wù),本來(lái)也可以使用Python直接手寫(xiě),但考慮到可擴(kuò)展性,使用Django來(lái)進(jìn)行了編寫(xiě)。Djano服務(wù),需要提供如下的功能:

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

2和3本質(zhì)都是一個(gè)問(wèn)題,通過(guò)Django的static功能,就能實(shí)現(xiàn)。也就是在settings.py配置中,提供下面的配置即可:

STATIC_URL = 'static/'

STATICFILES_DIRS = [
    BASE_DIR / "static"
]

1對(duì)外提供api服務(wù),則需要設(shè)置對(duì)應(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)

其中有兩個(gè)接口:hello_django是最開(kāi)始學(xué)習(xí)使用的,返回寫(xiě)死的json數(shù)據(jù)。history_list,則是自動(dòng)遍歷縮略圖文件夾,獲取縮略圖文件信息,并生成所需要的json數(shù)據(jù)格式。在對(duì)應(yīng)的代碼庫(kù)文件中,也包含了requirements.txt,其中標(biāo)明了實(shí)際需要的依賴(lài)庫(kù)。下載代碼,進(jìn)入manage.py所在的目錄后,執(zhí)行下面的命令即可啟動(dòng):2d9b6652-4013-11ed-b180-dac502259ad0.png  訪(fǎng)問(wèn)?192.168.1.15:8000/app/hellodjango?:2dab92ca-4013-11ed-b180-dac502259ad0.png  訪(fǎng)問(wèn):History List – Django REST framework2dce35a0-4013-11ed-b180-dac502259ad0.png

可以看到 history_list接口,已經(jīng)可以提供實(shí)際需要的數(shù)據(jù)了。七、Flutter Web界面開(kāi)發(fā)這個(gè)部分設(shè)計(jì)的代碼比較多,所以只對(duì)關(guān)鍵部分的代碼進(jìn)行說(shuō)明。開(kāi)發(fā)的實(shí)際代碼,位于lib目錄,具體為:2de226f0-4013-11ed-b180-dac502259ad0.png

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

整個(gè)界面,使用了Scaffold來(lái)模擬手機(jī)/Pad的操作界面,具體界面如下:2df66304-4013-11ed-b180-dac502259ad0.png  在實(shí)時(shí)畫(huà)面界面中,使用了WebSocket監(jiān)聽(tīng),獲取到信息,就使用Stream模式,推送給視頻播放?! ≡跉v史記錄界面中,則通過(guò)RestFul請(qǐng)求列表數(shù)據(jù),然后呈現(xiàn)。
八、整體運(yùn)行效果實(shí)際的運(yùn)行效果,不用多說(shuō),看界面就成:

  1. 實(shí)時(shí)畫(huà)面:
  2. 歷史記錄列表:2e3168fa-4013-11ed-b180-dac502259ad0.png
  3. 歷史記錄播放:2e545a22-4013-11ed-b180-dac502259ad0.png

九、車(chē)試:經(jīng)過(guò)反復(fù)的測(cè)試驗(yàn)證,確保各項(xiàng)功能完整后,進(jìn)行了上車(chē)實(shí)測(cè)。

2eb024e2-4013-11ed-b180-dac502259ad0.jpg

因?yàn)樽罱囊咔樵?,所以只在村里轉(zhuǎn)了一圈,進(jìn)行了實(shí)際測(cè)試,可以查看最后的視頻。后續(xù)有機(jī)會(huì),再找個(gè)晴朗的天氣,去環(huán)境優(yōu)美的地方實(shí)際拍攝錄制。
十、實(shí)際代碼說(shuō)明:完整的代碼,請(qǐng)通過(guò)米爾行車(chē)記錄儀: 米爾行車(chē)記錄儀 (https://gitee.com/honestqiao/MYiR-Driving-Recorder)獲取。代碼目錄說(shuō)明如下:

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

在以上倉(cāng)庫(kù)中,包含了詳細(xì)的代碼使用說(shuō)明。在實(shí)際應(yīng)用中,將記錄視頻的data目錄與后端static/data目錄關(guān)聯(lián),以便兩者統(tǒng)一。
十一、感謝在研究學(xué)習(xí)的過(guò)程中,參考了數(shù)十篇各類(lèi)資料,先將部分列出如下。對(duì)所有學(xué)習(xí)過(guò)的資料的作者,表示深深的感謝。

  • janakj/py-mjpeg: Python MJPEG streaming utilities (github.com)
  • Simple Python Motion Jpeg (mjpeg server) from webcam. Using: OpenCV,BaseHTTPServer (github.com)
  • Python 使用USB Camera錄制MP4視頻_Frank_Abagnale的博客-CSDN博客
  • 用 Python、nginx 搭建在線(xiàn)家庭影院 - 知乎 (zhihu.com)
  • Django報(bào)錯(cuò)解決:RuntimeError: Model class ...apps... doesn't declare an explicit app_label and isn't in a_lyp039078的博客-CSDN博客
  • Python OpenCV 調(diào)用攝像頭并截圖保存_Clannad_niu的博客-CSDN博客
  • 用 Python、nginx 搭建在線(xiàn)家庭影院mob604756e97f09的技術(shù)博客51CTO博客
  • Python-OpenCV錄制H264編碼的MP4視頻 - 掘金 (juejin.cn)
  • ****[VideoWriter]保存H264/MPEG4格式MP4視頻 - image processing (zj-image-processing.readthedocs.io)
  • Manual USB camera settings in Linux | KUROKESU
  • UVC Web Cameras (indilib.org)
  • 編寫(xiě)你的第一個(gè) Flutter 網(wǎng)頁(yè)應(yīng)用 - Flutter 中文文檔 - Flutter 中文開(kāi)發(fā)者網(wǎng)站 - Flutter
  • macOS install | Flutter
  • [Django 設(shè)定 LANGUAGE_CODE 時(shí)所遇到的麻煩] OSError: No translation files found for default language zh-TW. (github.com)**
  • Django And Flutter — 樣板應(yīng)用程序|的分步教程作者:Clever Tech Memes |中等 (medium.com)
  • joke2k/django-environ: Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application. (github.com)
  • django 之跨域訪(fǎng)問(wèn)問(wèn)題解決 access-control-allow-origin - 騰訊云開(kāi)發(fā)者社區(qū)-騰訊云 (tencent.com)
  • django-cors-headers · PyPI
  • Django項(xiàng)目解決跨域問(wèn)題 - SegmentFault 思否
  • video_player | Flutter Package (pub.dev)
  • 視頻的播放和暫停 - Flutter 中文文檔 - Flutter 中文開(kāi)發(fā)者網(wǎng)站 - Flutter
  • 5.7 頁(yè)面骨架(Scaffold) | 《Flutter實(shí)戰(zhàn)·第二版》 (flutterchina.club)
  • itfitness/BottomNavigationBarDemo - 碼云 - 開(kāi)源中國(guó) (gitee.com)
  • Flutter底部導(dǎo)航 - 簡(jiǎn)書(shū) (jianshu.com)
  • Flutter之自定義底部導(dǎo)航條以及頁(yè)面切換實(shí)例——Flutter基礎(chǔ)系列houruoyu3的博客-CSDN博客flutter 自定義底部導(dǎo)航
  • How To Use HTTP Requests in Flutter | DigitalOcean
  • 在Flutter中發(fā)起HTTP網(wǎng)絡(luò)請(qǐng)求 - Flutter中文網(wǎng) (flutterchina.club)
  • Fetch data from the internet | Flutter
  • 獲取網(wǎng)絡(luò)數(shù)據(jù) - Flutter 中文文檔 - Flutter 中文開(kāi)發(fā)者網(wǎng)站 - Flutter
  • 深入理解 Function & Closure - Flutter 中文文檔 - Flutter 中文開(kāi)發(fā)者網(wǎng)站 - Flutter
  • Django And Flutter — A Step by Step Tutorial for a Boilerplate Application | by Clever Tech Memes | Medium
  • multithreading - Multithreaded web server in python - Stack Overflow
  • Simple Python HTTP Server with multi-threading and partial-content support (github.com)
  • meska/mjpeg_stream_webcam: Webcam Streamer for Octoprint MacOs (github.com)
  • blueimp/mjpeg-server: MJPEG Server implements MJPEG over HTTP using FFmpeg or any other input source capable of piping a multipart JPEG stream to stdout. Its primary use case is providing Webdriver screen recordings. (github.com)
  • n3wtron/simple_mjpeg_streamer_http_server: simple python mjpeg streamer http server (github.com)
  • Python3遠(yuǎn)程監(jiān)控程序?qū)崿F(xiàn)肥宅Sean的博客-CSDN博客
  • opencv imencode跟imdecode函數(shù)jpg(python) - PythonTechWorld
  • flutter_mjpeg | Flutter Package (pub.dev)
  • Can't work on web platform. · Issue #13 · mylisabox/flutter_mjpeg (github.com)
  • Consider iffetchis widely supported enough to use · Issue #595 · dart-lang/http (github.com)
  • 在 Flutter | 中創(chuàng)建實(shí)時(shí)視頻流應(yīng)用程序作者:Mitrajeet Golsangi |開(kāi)發(fā)人員學(xué)生社區(qū) Vishwakarma 技術(shù)學(xué)院,浦那 |中等 (medium.com)
  • Python websockets.serve方法代碼示例 - 純淨(jìng)天空 (vimsky.com)
  • Flutter 常用組件講解 | ImageWidget - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 (ithome.com.tw)
  • Bad State: Stream has already been listened to. · Issue #29105 · flutter/flutter (github.com)
  • flutter - Streambuilder with WebSockets stream in TabBarView: Bad state: Stream has already been listened to - Stack Overflow
  • 使用WebSockets - Flutter中文網(wǎng) (flutterchina.club)
  • VideoStreaming.dart (github.com)
  • 在 Flutter | 中創(chuàng)建實(shí)時(shí)視頻流應(yīng)用程序作者:Mitrajeet Golsangi |開(kāi)發(fā)人員學(xué)生社區(qū) Vishwakarma 技術(shù)學(xué)院,浦那 |中等 (medium.com)
  • Flutter:WebSocket封裝-實(shí)現(xiàn)心跳、重連機(jī)制 - 讓我留在你身邊 (ricardolsw.github.io)
  • 2.3 狀態(tài)管理 | 《Flutter實(shí)戰(zhàn)·第二版》 (flutterchina.club)
  • 路由和導(dǎo)航 - Flutter 中文文檔 - Flutter 中文開(kāi)發(fā)者網(wǎng)站 - Flutter
  • 7.6 異步UI更新(FutureBuilder、StreamBuilder) | 《Flutter實(shí)戰(zhàn)·第二版》 (flutterchina.club)
  • Flutter 常用組件講解 | ImageWidget - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天 (ithome.com.tw)
  • Step By Step Tutorial in Learning Flutter: Lesson 12 — Adding Image (Ice Pokemon) | by Misterflutter | Quick Code | Medium
  • Django CORS on static asset - Stack Overflow
  • 配置 | Django 文檔 | Django (djangoproject.com)
  • Global Variables in Dart - Stack Overflow
  • UVC - Community Help Wiki (ubuntu.com)
  • 利用OpenCV進(jìn)行H264視頻編碼的簡(jiǎn)易方式 - 知乎 (zhihu.com)
  • Documentation for OPENCV_FFMPEG_WRITER_OPTIONS and OPENCV_FFMPEG_CAPTURE_OPTIONS · Issue #21155 · opencv/opencv (github.com)
  • 利用OpenCV進(jìn)行H264視頻編碼的簡(jiǎn)易方式 - 知乎 (zhihu.com)
  • FFmpeg概述及編碼支持 - 知乎 (zhihu.com)
  • Web 渲染器 - Flutter 中文文檔 - Flutter 中文開(kāi)發(fā)者網(wǎng)站 - Flutter

十二、總結(jié)在研究學(xué)習(xí)的過(guò)程中,對(duì)Linux系統(tǒng)下的UVC框架有了進(jìn)一步的了解,對(duì)Flutter進(jìn)行應(yīng)用開(kāi)發(fā)有了實(shí)際的了解,對(duì)OpenCV的實(shí)際應(yīng)用也有了具體的了解。在實(shí)際開(kāi)發(fā)的過(guò)程中,遇到的最大的坑來(lái)自Flutter,因?yàn)樽兓?,有一些功能可能兼容性沒(méi)有跟上。不過(guò)更多是自己學(xué)藝不精導(dǎo)致的。另外,目前還只是V1.0版本,后續(xù)還存在較大的優(yōu)化空間。例如對(duì)于OpenCV的應(yīng)用,可以調(diào)整參數(shù),優(yōu)化獲取的視頻數(shù)據(jù)的指令和大小等。這些有待于進(jìn)一步學(xué)習(xí)后進(jìn)行。最主要的,對(duì)米爾MYD-YT507開(kāi)發(fā)板有了深入的了解,進(jìn)行了實(shí)際的應(yīng)用。作為一款車(chē)規(guī)級(jí)處理器T507的開(kāi)發(fā)板,名不虛傳!

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

    關(guān)注

    25

    文章

    5081

    瀏覽量

    97695
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    行車(chē)記錄儀對(duì)圖像傳感器的要求

    行車(chē)記錄儀已經(jīng)成為提升道路安全的重要工具。它能夠持續(xù)記錄從日常通勤到高速事故的各種情況,讓駕駛員對(duì)自己的行為負(fù)責(zé),并促使更多危險(xiǎn)駕駛行為得到定罪,從而幫助提升了道路安全。由于安裝簡(jiǎn)便且具備多種優(yōu)勢(shì),
    的頭像 發(fā)表于 12-27 16:30 ?932次閱讀

    有獎(jiǎng)米爾 全志T536開(kāi)發(fā)板免費(fèi)試用

    米爾與全志合作發(fā)布的新品基于全志T536應(yīng)用處理器的MYD-LT536-GK開(kāi)發(fā)板免費(fèi)試用活動(dòng)來(lái)啦~~米爾提供了3塊價(jià)值750元的MYD-L
    的頭像 發(fā)表于 12-26 08:05 ?151次閱讀
    有獎(jiǎng)<b class='flag-5'>丨</b><b class='flag-5'>米爾</b> 全志T536<b class='flag-5'>開(kāi)發(fā)板</b>免費(fèi)試用

    如何用OpenCV進(jìn)行手勢(shì)識(shí)別--基于米爾全志T527開(kāi)發(fā)板

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

    追加名額米爾瑞芯微RK3576開(kāi)發(fā)板有獎(jiǎng)試用

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

    行車(chē)記錄儀的拆解

    都說(shuō)4s店贈(zèng)送的行車(chē)記錄儀質(zhì)量差,不被看好。很多用戶(hù)提完新車(chē)后,都會(huì)自己重新買(mǎi)一個(gè)質(zhì)量稍微好點(diǎn)的,然后就把4s店贈(zèng)送的放在家里吃灰。包括我也是,這個(gè)行車(chē)記錄儀放在家里差不多一年了,今天
    的頭像 發(fā)表于 11-15 11:21 ?1120次閱讀
    <b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>的拆解

    有獎(jiǎng)米爾 瑞芯微RK3576開(kāi)發(fā)板免費(fèi)試用

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

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

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

    行車(chē)記錄儀中愛(ài)普生晶振的關(guān)鍵作用與類(lèi)型

    行車(chē)記錄儀作為現(xiàn)代車(chē)輛的重要配件之一,不僅記錄行駛過(guò)程中的影像,還能夠提供關(guān)鍵的時(shí)間戳信息,這對(duì)于交通事故的調(diào)查和保險(xiǎn)理賠極為重要。行車(chē)記錄儀
    的頭像 發(fā)表于 09-13 14:56 ?310次閱讀
    <b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>中愛(ài)普生晶振的關(guān)鍵作用與類(lèi)型

    應(yīng)用在行車(chē)記錄儀的愛(ài)普生晶振SG-9101CGA

    隨著汽車(chē)科技的不斷發(fā)展,行車(chē)記錄儀已成為許多車(chē)輛的標(biāo)準(zhǔn)配置。行車(chē)記錄儀是一種安裝在車(chē)輛上,用于錄制行駛過(guò)程中的視頻和音頻的設(shè)備。它主要用于記錄
    的頭像 發(fā)表于 08-30 10:41 ?293次閱讀
    應(yīng)用在<b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>的愛(ài)普生晶振SG-9101CGA

    KOWIN microSD移動(dòng)存儲(chǔ)卡在行車(chē)記錄儀的應(yīng)用

    不管是行車(chē)記錄儀還是無(wú)人機(jī),都需要更加強(qiáng)大的microSD/SD卡配合才能發(fā)揮強(qiáng)勁它們性能。目前越來(lái)越多的人性化、智能化的數(shù)碼產(chǎn)品在豐富著我們的行車(chē)體驗(yàn),其中對(duì)于行車(chē)安全、
    的頭像 發(fā)表于 08-12 16:01 ?379次閱讀
    KOWIN microSD移動(dòng)存儲(chǔ)卡在<b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>的應(yīng)用

    SG-8201CJA晶振在行車(chē)記錄儀中的應(yīng)用

    行車(chē)記錄儀的精確守護(hù)者:愛(ài)普生SG-8201CJA編碼X1G005991xxxx16晶振隨著汽車(chē)科技的不斷發(fā)展,行車(chē)記錄儀已成為許多車(chē)輛的標(biāo)準(zhǔn)配置。
    的頭像 發(fā)表于 07-11 17:29 ?352次閱讀
    SG-8201CJA晶振在<b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>中的應(yīng)用

    行車(chē)記錄儀CCC認(rèn)證的必要性分析

    行車(chē)記錄儀需要申請(qǐng)CCC認(rèn)證嗎?作為每臺(tái)需要正式上路的汽車(chē)來(lái)說(shuō),行車(chē)記錄儀是必備的車(chē)載產(chǎn)品,因此行車(chē)記錄
    的頭像 發(fā)表于 07-05 16:25 ?476次閱讀
    <b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>CCC認(rèn)證的必要性分析

    展頻晶振在行車(chē)記錄儀中的應(yīng)用

    展頻晶振能夠提升整個(gè)系統(tǒng)的穩(wěn)定性和可靠性,避免由于電磁干擾導(dǎo)致的系統(tǒng)崩潰或誤操作。改善音頻和視頻質(zhì)量:在行車(chē)記錄儀中,尤其是涉及音頻和視頻錄制的設(shè)備,減少電磁干擾可以顯著提高錄制的音頻和視頻質(zhì)量
    的頭像 發(fā)表于 07-01 16:01 ?3391次閱讀
    展頻晶振在<b class='flag-5'>行車(chē)</b><b class='flag-5'>記錄儀</b>中的應(yīng)用

    點(diǎn)擊參與米爾NXP i.MX 93開(kāi)發(fā)板有獎(jiǎng)試用

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

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

    本篇測(cè)評(píng)由電子工程世界的優(yōu)秀測(cè)評(píng)者“mameng”提供。本文將介紹基于米爾電子MYD-JD9X開(kāi)發(fā)板的ROS系統(tǒng)智能車(chē)開(kāi)發(fā)。目前實(shí)現(xiàn)ROS的方式主要有兩種:Ubuntu系統(tǒng)+ROS;U
    的頭像 發(fā)表于 01-26 08:01 ?954次閱讀
    ROS系統(tǒng)的智能車(chē)<b class='flag-5'>開(kāi)發(fā)</b>-基于<b class='flag-5'>米爾</b>芯馳<b class='flag-5'>MYD</b>-JD9X<b class='flag-5'>開(kāi)發(fā)板</b>