行車記錄器

      在〈行車記錄器〉中尚無留言

本篇將樹莓派當成行車記錄器, 樹莓派系統為raspbin server版, 只需將電源及 web cam插入, 開機自動執行CarRecord.py, 就會將影像存在 sdcard, 並將影像傳回 server.

本程式會自動切割檔案, 每隔30秒就換一個新檔案儲存

作業系統

作業系統使用 raspbian server為最佳選擇. 

ubuntu offical server版, usb 抓不到webcam. 而ubuntu mate也怪怪的, 必需接螢幕才抓得到web cam, 不過這些都還在測試中.

注意事項

1. 樹莓派的電源供應器, 必需2A以上, 否則會因為插入web cam造成電流不足
2. 請注意樹莓派的 wifi ip 是否正確, pi4 raspbin有bug, 有時會跑掉
3. 注意是否將帳號加入 video群組, 否則無法開啟 web cam
4. Python 變更照片品質時, 以 '.jpg' 傳送
5. C#接收端, 需轉換成 .jpg, 不能用 .png
6. fps 是告知播放器一秒要放映幾張的意思, 比如30fps, 是告知播放器一秒要放映30張. 
   但如果檔案裏並沒有這麼多張, 播放器還是拼命抓取, 就會造成影片快速播放的現像.
   所以fps是要以實際sdcard存放的時間為主, 樹莓派每秒約7張
7. 樹莓派的電源線很挑, 一定要選品質較好, 較粗的電源線

Python 完整代碼

下面為樹莓派端的Python代碼, 此代碼將網路傳送及儲存sdcard獨立成不同的執行緒. 所以就算PC端的Server沒有打開監聽, 一樣會儲存到樹莓派的sdcard. 當PC Server一打開, 就立即傳送影像到Server

#!/usr/bin/python3
import socket
import cv2
import numpy
from datetime import datetime
import threading
runFlag=True;
frame=None
frameRate = 7
file_path='/home/thomas/videos'

def saveVideo():
    global frame
    video_code = cv2.VideoWriter_fourcc(*'mp4v')
    output = cv2.VideoWriter(f"{file_path}/cam_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.mp4", video_code, frameRate,(1920, 1080))
    t1 = datetime.now()
    t2 = t1
    while(runFlag):
        t2 = datetime.now()
        if (t2 - t1).seconds >= 30:
            output.release()
            output = cv2.VideoWriter(f"{file_path}/cam_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.mp4",video_code, frameRate, (1920, 1080))
            t1 = t2
        if frame is not None:
            output.write(frame)
        cv2.waitKey(5)
    output.release()
def submitServer():
    global frame
    TCP_IP = "you server ip"
    TCP_PORT = 8002
    sock = socket.socket()
    encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 80]
    while(runFlag):
        if frame is not None:
            result, imgencode = cv2.imencode('.jpg', frame, encode_param)
            data = numpy.array(imgencode)
            byteData = data.tobytes()
            try:
                sock.send(str(len(byteData)).ljust(16).encode(encoding='utf_8', errors='strict'))
                sock.send(byteData);
            except Exception as e:
                print("send error : ", e)
                try:
                    sock = socket.socket()
                    sock.connect((TCP_IP, TCP_PORT))
                except:
                    cv2.waitKey(1000)
    sock.close()
cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
threading.Thread(target=saveVideo, daemon=True).start()
threading.Thread(target=submitServer, daemon=True).start()
while(runFlag):
    ret, img = cam.read()
    if ret:
        datetime_dt = datetime.today()  # 獲得當地時間
        datetime_str = datetime_dt.strftime("%Y/%m/%d %H:%M:%S")  # 格式化日期
        cv2.putText(img, datetime_str, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
        frame=img#此處是防止影像日期會閃爍
    if 0xFF & cv2.waitKey(20) == 27:
        break
runFlag=False
#cv2.destroyAllWindows()

C# Server接收器

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace WebCam
{
    public partial class MainWindow : Window
    {
        Socket SocketWatch = null;
        bool runFlag = true;
        public MainWindow()
        {
            InitializeComponent();
            int port = 8002;
            //IPAddress ip = IPAddress.Parse("192.168.1.2");
            IPAddress ip = IPAddress.Any;
            IPEndPoint ipe = new IPEndPoint(ip, port);
            SocketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            SocketWatch.Bind(ipe);
            //將套接字的監聽佇列長度限制為20  
            SocketWatch.Listen(20);
            Thread watchThread = new Thread(WatchThread);
            //將窗體執行緒設定為與後臺同步,隨著主執行緒結束而結束  
            watchThread.IsBackground = true;
            watchThread.Start();
            Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => {
                lblStatus.Content = "監聽中....";
            }));
        }
        void WatchThread()
        {
            Socket connection = null;
            while (runFlag)
            {
                try
                {
                    connection = SocketWatch.Accept();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    break;
                }
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => {
                    rowStatus.Height = new GridLength(0);
                }));
                Thread receiveThread = new Thread(ReceiveThread);
                receiveThread.IsBackground = true;
                receiveThread.Start(connection);
            }
        }

        private void ReceiveThread(object socketclientpara)
        {
            Socket socket = socketclientpara as Socket;
            while (runFlag)
            {
                try
                {
                    byte[] buff = ReceiveAll(socket, 16);
                    byte[] byteCvImg = ReceiveAll(socket, Int64.Parse(Encoding.UTF8.GetString(buff, 0, 16)));
                    Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => {
                        OpenCvSharp.Mat mat = OpenCvSharp.Cv2.ImDecode(byteCvImg, OpenCvSharp.ImreadModes.Color);
                        Bitmap bitmap=OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mat);
                        try
                        {
                            BitmapImage image = GetImage(bitmap);
                            img.Source = image;
                        }
                        catch { }
                    }));
                }
                catch (Exception e)
                {
                    Console.WriteLine("Receive socket error : {0}", e.Message);
                }
                Thread.Sleep(100);
            }
        }
        private byte[] ReceiveAll(Socket socket, long count)
        {
            List<byte> buf = new List<byte>();
            byte[] tmp = null;
            while (count > 0)
            {
                tmp = new byte[count];//注意, buffer不能超出剩餘的大小, 否則會取到下一筆資料
                int c = socket.Receive(tmp);
                for (int i = 0; i < c; i++) 
                { 
                    buf.Add(tmp[i]); 
                } 
                if (count > 0) count -= c;
            }
            return buf.ToArray();
        }
        private BitmapImage GetImage(Bitmap bitmap)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
                stream.Position = 0;
                BitmapImage image = new BitmapImage();
                image.BeginInit();
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.StreamSource = stream;
                image.EndInit();
                image.Freeze();
                return image;
            }
        }

        private void btnExit_Click(object sender, RoutedEventArgs e)
        {
            runFlag = false;
            this.Close();
        }
    }
}

Ramdisk

在 Linux 中若要使用 RAM Disk,只需要使用 mount 這個指令就可以了,非常簡單!

首先在 /tmp 中建立一個資料夾:

mkdir /tmp/ramdisk
chmod 777 /tmp/ramdisk

接著再使用 mount 指令將 4G 的記憶體掛上去:

mount -t tmpfs -o size=4G tmpfs /tmp/ramdisk/

這裡要使用 -t 參數指定檔案系統類型為 tmpfs,並且使用 -o 參數指定 RAM Disk 大小為 4G。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *