搜尋此網誌

2025年7月31日 星期四

Raspberry pi PyTorch 影像識別 (二)

緣起:


    接續這篇雖然我在文章最後說我停手了,但後面其實還是有改了些東西,也有把程式搬到我那台裝 ssd 的 pi 5 上跑,那執行的速度確實快上了不少,而且我們神明廳就算加了鐵網,也還是阻止不了那臭狗在我們庭院前的其它地方大便,所以此專案還是有繼續執行的必要。

    我那時是直接把 pi 跟螢幕搬到門口附近,然後用膠帶把 usb 網路攝影機黏在鐵椅上,放到外面。超蠢的,攝影機到晚上時就沒作用了,一片黑;下雨時也是要把它收進來,每天重新放回去又要在那邊橋角度;東西擺門口也很擋路;當蜂鳴器響起時我還是要離開位置跑到門口查看.....。幾天後就懶得再每天手動擺那些東西了,所以最後還是沒起到什麼作用啊 !!!

    我上禮拜真的就受不了,直接跑去外面買了台室外用的 wifi 監視器,果然還是得用專業的器具才能確實的解決我的問題。



Tapo C310:


    在跑出門前有去查,有確認那些 wifi 的監視器大部份都能開啟 rtsp 的功能,這代表我能使用它官方軟體外的渠道來擷取影像,提供的很大的彈性,這對需要自己客製功能的我來說是很重要的一件事。我跑去燦坤買的,那邊有一個櫃子都是這類的 wifi 監視器,哇嗚,只要 999 就能買到功能這麼好的監視器,真棒 !!

    買回家拆開後才想到,哎呀,tp-link 這公司不是跟共匪有關嗎 ? 算了,錢都花了,反正我只在區網內使用,問題應該不大 (?)。讓我覺得很煩的是,我要調整初始設定還要載它的 app,然後還要建帳號,我在添加設備時它還要我開啟位置,乾,好噁啊。我只有在初次使用,給它韌體做更新時連上我手機的 wifi,之後就都只連為它準備的 wifi 分享器,完全在區網內作業。

    在 app 上設定好 C310 要自動連上的 wifi,還有啟用它的 rtsp 跟 ONVIF 功能後,我就盡量不
去使用它的 app 了。那個 ONVIF 是我在的學習過程中認識的一個標準,它讓監控設備的設定調整變得更容易整合,有了它,我就不需要再透過 app 來調整監視器的設定,雖然不是什麼功能都能調整,但對我來說很夠用了,Windows 上可以載 ODM 來操作。


    那天晚上,我跟我爸借了電動工具,找好位置,把監視器鎖在牆上


    在安裝前有做勘查,發現電錶旁就有插座,這個位置非常的棒,就算下大雨,屋簷也能擋下那些雨水。


    wifi 真的方便,只要訊號能涵蓋到就行,省了拉網路線的功夫。


include-system-site-packages:


    在重新調整程式、測試執行時,我發現一個問題,python venv 執行的程式無法存取 pi 的 GPIO,怪了,上次跑的時候可以,怎麼這次就不行 ? 既虛擬環境調用 RPi.GPIO 程式庫會有問題,那是不是該換調用系統的 RPi.GPIO 程式庫 ? 去查了資料後得知,你可以編輯你 Python 虛擬環境裡的 pyvenv.cfg 檔,把 include-system-site-packages 那行改成

include-system-site-packages=true

    這樣你 python 虛擬環境的程式就能存取安裝在你本系統的 python 程式庫,我再來把虛擬環境的 RPi.GPIO 程式庫砍掉,再執行程式後,它就正常運行了,所以真的是虛擬環境無法存取 GPIO 的問題。

    雖然我後來不是用 pi 的 GPIO 來控制蜂鳴器,但這個雷點還是紀錄個。


Python OpenCV Rtsp:


    用 python 的 opencv 程式庫擷取 Rtsp 的串流影像超簡單的,改一下傳入 cv2.VideoCapture 的字串就行了,改成

rtsp://{帳號}:{密碼}@{ip位置}:554/stream1

    監視器傳回的影像,左上角有時間,這就讓我發現一個問題,就是程式當下截取到的畫面一定會落後當前實際畫面。就算沒執行影像識別的程式碼,落後的情況還是會存在,而且會愈來愈嚴重,這是個大問題,沒辦法即時識別到狗子的話,我這專案就沒有用了。

    我程式的主架構簡單來說是這樣


    我一直以為,我在用 cv2.VideoCapture 類別的 capture 擷取 rtsp 來源的影像時,在我程式架構下它會這樣運作




    後來去請教 GPT 大神,它給我這樣的回覆


    哦 ~~ 以我的小腦袋理解起來,上它從二到三階時,
實際可能是這樣處理的


    GPT 也有給我一個解法,寫了一個 VideoStream 類別,開 Thread 不斷從 rtsp 讀取最新的影像,等到主程式需要影像時,再回傳當前影像。使用這個類別來取得 rtsp 的影像後,就沒有延遲的問題了

class VideoStream:
    def __init__(self, src):
        self.capture = cv2.VideoCapture(src)
        self.src=src
        self.ret, self.frame = self.capture.read()
        self.running = True
        self.lock = threading.Lock()
        threading.Thread(target=self.update, daemon=True).start()

    def update(self):
        while self.running:
            ret, frame = self.capture.read()
            if not ret:
                continue
            with self.lock:
                self.ret = ret
                self.frame = frame

    def read(self):
        with self.lock:
            return self.ret, self.frame.copy()
    def stop(self):
        self.running = False
        self.capture.release()


完整程式:


    超醜程式碼警告,當初就只想著趕快做出來,所以只依最簡單最直接想得到的方式來把程式拼湊出來

class VideoStream:
    def __init__(self, src):
        self.capture = cv2.VideoCapture(src)
        self.src=src
        self.ret, self.frame = self.capture.read()
        self.running = True
        self.lock = threading.Lock()
        threading.Thread(target=self.update, daemon=True).start()

    def update(self):
        while self.running:
            ret, frame = self.capture.read()
            if not ret:
                continue
            with self.lock:
                self.ret = ret
                self.frame = frame

    def read(self):
        with self.lock:
            return self.ret, self.frame.copy()
    def stop(self):
        self.running = False
        self.capture.release()

if __name__ == '__main__':
    import sys
    import torch
    import cv2
    import time
    import threading
    import RPi.GPIO as GPIO
    import warnings
    from datetime import datetime
    import serial
    
    warnings.filterwarnings('ignore')
    
    arduino_serial = serial.Serial('/dev/ttyUSB0', 9600)
    latest_reconnect_time = datetime.now()
    rtsp_url='rtsp://{帳號}:{密碼}@{ip位置}:554/stream1'
    model = torch.hub.load('ultralytics/yolov5', 'yolov5n', pretrained=True)
    stream=VideoStream(rtsp_url)
    acc_time=0
    buzz_time=0
    
    while True:
        time.sleep(0.05)
        
        if buzz_time > 0:
            buzz_time-=1
            if buzz_time == 0:
                arduino_serial.write('0'.encode('utf-8'))
        else:
            acc_time+=1
        
        
        ret, frame=stream.read()
        frame_h, frame_w, _ = frame.shape
        
        if acc_time >= 20 and buzz_time < 1:
            acc_time=0
            results=model(frame)
            labels = results.pandas().xyxy[0]['name'].values
            if 'dog' in labels:
                arduino_serial.write('1'.encode('utf-8'))
                buzz_time=30
                
        resized = cv2.resize(frame, (1920, 1080), interpolation=cv2.INTER_AREA)
        cv2.imshow('cam', resized)
        
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
    cap.release()
    cv2.destroyAllWindows()
    stream.stop()

    37 行 :  執行時,pytorch 會一直印警告,說某些方法已經適用還什麼的,很煩,所以加這個給它消音。

    39 行 : 電子元件的控制,我後來就交給 arduino 來做,因為那個 Raspberry pi 官方的 SSD Hat 不知有啥毛病,裝上之後,那些 GPIO 孔是沒辦法用杜邦線公頭插入的。arduino 直接用 usb 線接到 pi 上,序列通訊, Arduino 那邊收到 1 就向蜂鳴器輸電。 

44、45 行 : 這兩個數字是紀錄幾個週期用,while 裡的 sleep 是 0.05 秒,也就是每 0.05 秒一個週期,每個週期會擷取影像並更新 window 畫面。為了減少 cpu 跟 gpu 負荷,所以每 20 輪才做一次影像識別 (那個 acc_time變數名取得很不好)。然後當識別到狗子時,向 arduino 發送訊號,啟動蜂鳴器,持續 30 個週期後再發送訊號,關閉蜂鳴器。

    69 行 : 我監視器是抓 2K 畫質的畫面,直接在 1080 p 的螢幕上呈現的話會太大,所以在 imshow 前調整它的大小。



    哦對了,這邊再提一件事,原本 Pi 是 Wifi 連接 Wifi 分享器的,但後來發現每次跑個 30 分鐘左右,rtsp 就斷線,那時還以為是監視器有休眠還什麼的問題,測了好一段時間後才發現,原來就只是 Pi 的 Wifi 連線很不穩,用網路線連接後就沒問題了。

    至此,這個 24 小時的監控功能就完成了,這台監視器到晚上時還會自動切成夜間紅外線模式。我前幾天有成功靠這個裝置逮到那隻又想偷大便的賤狗。我當時在看書,聽到警報後,我確認螢幕,看到它鬼鬼祟祟的在我們庭院前面徘徊,我馬上走出門,拿拖鞋丟他,把它給趕跑,送啦,再偷偷過來大便看看啊,臭狗。

2025年6月11日 星期三

Raspberry pi PyTorch 影像識別

緣起:


    前兩個禮拜,那個廢物鄰居被他的朋友載出門,不知去哪工作,直接不管他家的小狗,就把它一隻狗放在家中,不管它的死活。要不是我這段時間有去餵它的話,它可能真的會餓死。

    前幾天發現一個問題,那小狗會跑去我們神明廳大便,不知是不是它已經沒辨法在它家裡找到適合大便的位置,總之,這件事造成我們不小的困擾,我已經掃它大便兩次了。

    為了阻止它繼續在我們神明廳大便,我打算自製一個驅狗器。有上網查,能用超音波來趕狗子,所以想說使用 arduino 搭配紅外線感測器,再加個能發出超聲波的電子零件之類的(?,簡單做。後來仔細去查才發現,一般的蜂鳴器模組是沒辨法做到發出 25K Hz 以上的聲音,如果是想使用 HC-SR04 模組的話,聲音太小,需要加強驅動電路。好麻煩,我又不知怎麼設計電路的東西。

    後來想到,好像不一定要超聲波,用喇叭最大音量播放狗吠聲,應該也可以把那隻小狗給嚇跑。所以後來轉而使用 raspberry pi 來處理,我有 3 代的 pi,它有 3.5 mm 的孔能直接連音源線。不過,要用喇叭的話,就不能單純紅外線感測器偵測到物體時播放聲音,如果進來的是我爸媽,然後把他們嚇到,到時我就倒楣了。所以,更好的方案應該是使用 usb 網路攝影機,搭配影像識別。

    前天下午都一直在搞這東西,雖然大部份的時間都是在載套件。隔天,我看我爸用鐵網把神明廳的入口擋住,哇,老祖宗的方法果然還是快速又方便


    感覺有點白忙一場,後來想了想,還是有學到不少東西,所以想寫篇文章紀錄。

2025年3月17日 星期一

dotnet NLog 與 Docker Container

緣起:


    我現在自己獨立弄一個專案,上個禮拜有成功把所需的服務都用 docker container 跑起來,彼此間的溝通與協作也正常,所以接下來要開始正式開發後台的程式了,但在這之前,我想先弄好專案 app 的記錄 log 功能。之前還在哈瑪星工作時,有看到我們產品內有用到 NLog 套件來記錄各種程式執行時的 log,用檔案系統紀錄。當程式發生問題時,有 log 檔能看,會比較有頭緒,不然之後正式上線,程式怎麼爆的都不知道,會很可怕。

    這篇文章主要記錄 Nlog 的設定與使用,還有在 Container 上執行時有碰到些雷。

圖跟本文無關,只是想分享一下最近畫的圖


2025年3月12日 星期三

Rog Ally 安裝 Visual Studio

緣起:


    原本買 Rog Ally 最主要是打 LOL 用的,但後來有幫我舊筆電買新的充電線,所以那台 Rog Ally 就不用再被 LOL 給綁架了,可以拿來玩 steam 或 gamepass 的遊戲。但是啊,我最近都沒什麼想玩遊戲的心,就算是我最愛的魔物也是一樣,wilds 只打了 12 個小時後就不打了。

     現在想拿這台 Rog Ally 來當開發用工具,主要是想跑 Docker,可以跟另一台電腦做網站 API 連線的測試,還想拿來編輯 vs 專案,因為我有點懶得在 arch 跟 windows 間切來切去。

    我其實沒想到,我會需要寫個安裝 Visual Studio 的文章....,因為就真的碰到問題.....。我習慣把安裝檔放到桌面來執行,之前在其它電腦上都沒問題的,怎麼在 Rog Ally 上就不行 ? 還是是因為 Visual Studio 不支援 ? 也不太對啊,撇除那遊戲機的外型,它本身不就是台普通的 WINDOWS 電腦 ?

2025年3月11日 星期二

Dotnet ASP 專案與 docker-compose

緣起:


    上週有思考工作專案的大架構,主幹大概長這樣


    打算都用 docker 來實作,部屬跟搬遷會方便很多,只是要學不少東西,而且在實作時也踩了不少的雷。這篇文章主要是紀錄 dotnet 專案的 Dockerfile ,還有搭配 docker-compose 的使用。

2025年2月26日 星期三

ASP .net core 接收 Esp32 資料

緣起:


    目前回歸社會,到公司後,我的主管請我先研究 Esp32,看這禮拜能不能先弄個簡單的成果出來,傳簡單的資料到後台。由於我那四個月沒什麼碰程式,而且對 dotnet core 其實沒到很熟,所以一開始在開發時碰上好多問題,我只憑著在小哈那最後幾個月的 dotnet core 知識在摸索。

    第一天主要在弄環境,還有研究 Esp32 跟 ASP 的 Web API,第二天中午前有把東西給做出來,這邊想要把結果紀錄下來。

AUR 安裝 VS code

緣起:


    前陣子要裝 vs code 時,發現 arch 官方的是 Code - OSS,如果我想要安裝 Microsoft 發行的版本,需要用去學使用 Arch User Repository 來安裝。所以想簡單紀錄一下安裝的流程。

2025年1月15日 星期三

安裝 Arch Linux

緣起:


    看我師傅都是用 Arch Linux 來作業的,為了跟上他的腳步,所以我也開始學著使用這個輕量的系統。我先是在我 asus x415 筆電上測試,把它當成白老鼠來做實驗,斷斷續續的弄了好幾天後,終於把系統給弄好,而且也有裝了桌面的環境跟其它日常作業所需的功能,後來還有載了 steam,用九日來測試在 arch 上跑遊戲的效果如何,很驚訝地發現跑起來超順的,我想,應該要歸功於 Arch 簡潔的架構,他不會像 windows 那樣在背景跑一堆有的沒的,除非你自己去啟動那些服務。

    體會到 Arch 的強大後,我後來也把那台 MSI KATANA 17 B13V 也換成 Arch,把原本的 Pop_OS 給換掉。這篇文章想記錄安裝系統的經過,如果不小心弄爆系統的話,下次可以快速重裝一個。踩了不少雷,也學到不少經驗。