作者:Jack OmniXRI
小時候父母總是會叮嚀,離開房間時要記得關燈才不會費電。如今 Edge AI 時代來臨,若有一組智慧環境偵測系統來檢查是否有人在房間,再依偵測結果來控制電燈、風扇、冷氣及各種家電的開關,這樣就能更節能省碳了。為了讓大家更了解如何實現這樣的系統,本文將使用 Arduino UNO Q(以下簡稱 UNO Q)加上一般市售網路攝影機(Webcam)來控制板載的 LED 的點亮和熄滅,未來還有機會擴展到其它家電的開關甚至調整溫濕度、照度、空氣品質等。
1. 系統軟硬體架構
如圖1上半部份所示,UNO Q 為了能接上網路攝影機,必須外掛一組 USB 集線器。由於此時 MPU 及週邊工作較耗電,所以一定要使用可以外接電源的集線器,配合外接變壓器提供 5V/3A 直流電源,原廠建議要 3A,但實務上可接 1~2A 視工作內容複雜度及耗能而定。
在這個應用例中由於 UNO Q 的 USB Type C 埠被集線器佔用,所以筆電上的 Arduino APP LAB(以下簡稱 APP LAB)軟體無法透過 USB 實體纜線連接 UNO Q,必須改用 WIFI 進行連接。通常 APP LAB 啟動後會自動搜尋相同網域下的 UNO Q,大約 5~10 秒 APP LAB 就會列出已連接 WIFI 的 UNO Q 開發板選項,點擊後輸入 UNO Q Liunx 密碼(使用者名稱固定為 arduino 不用輸入),就可進入工作畫面。
若忘了 Liunx 登入密碼,可使用 USB 纜線直接連接 UNO Q 後再啟動 APP LAB,進入後點擊畫面左下角齒輪狀的按鈕進入設定(Settings)模式,找到系統資訊(System Info)下的作業系統密碼(OS password)按下變更密碼(Change password),輸入新密碼並儲存後,就可移除 USB 纜線。重新插回集線器,回到上一步驟就可重新以 WIFI 方式連接 UNO Q 了。
如果遇到 APP LAB 啟動後 WIFI 無法順利連線到 APP LAB ,可試著重新按下 UNO Q 的重置鍵或插拔 USB 集線器或將 APP LAB 關閉後再重啟來解決。

圖1:使用 Arduino UNO Q 進行人臉偵測系統軟硬體架構。(OmniXRI整理製作,2026/06/17)
如圖2所示,進入 APP LAB 後,點擊左側範例(Examples)按鈕,向下捲找到「Face Detection on Camera」,點擊進入後,按下右上角複製(Copy)按鈕,輸入新專案名稱按下建立新專案(Create New)即可獲得完整範例專案。
接著按下右上角執行(Run),等待程式完成編譯和上傳,完成後會自動執行並開啟網頁,看到人臉檢測結果。若瀏覽器沒有自行開啟,則可手動開啟瀏覽器(Google Chrome, Firefox, Microsoft Edge 等皆可),輸入 http://UNO Q IP:7000 即可看到人臉檢測畫面。若不知道目前網址(IP)為何?可以看一下 APP LAB 下方的 IP 位址。另外亦可輸入 http://使用者名稱.local:7000 會得到相同的結果。

圖2:啟動人臉偵測範例流程。(OmniXRI整理製作,2026/06/17)
如圖 1下半部份所示,在這個範例中應用到 web_ui 和 video_objectdetection 兩個主要的磚塊(Brick),前者負責顯示網頁,而後者負責接收網路攝影機影像、偵測影像中的人臉並將結果資訊傳送到 web_ui 進行顯示。接下來會更進一步說明程式的工作細節。
2. Arduino 磚塊(Brick) 簡介
Arduino 為了讓大家更容易開發基於 Linux 和 MPU 環境的 UNO Q,在推出 APP LAB 時同步給出了新的磚塊(Brick)開發模式,預先將較複雜通訊協定、資料處理及 AI 辨識等包裝成模組化軟體,讓大家可以「即插即用(Plug and Play)」,加快開發速度。
如 圖3 所示,目前預設提供了二十多種磚塊,在 AI 辨識部份包括了傳統鑑別式 AI 如聲音、影像、振動、時序信號分析等,生成式 AI 如語音辨識(ASR)、大語言模型(LLM)、文字轉語音(TTS)、視覺語言模型(VLM)等也都有提供。而本文主要會專注在 web_ui 和 video_objectdetection,其它磚塊用法可點擊後即可在畫面上顯示完整介紹包含基礎範例。

圖3:Arduino APP LAB 提供的 UNO Q 磚塊(Brick)清單。(OmniXRI整理製作,2026/06/17)
3. 人臉辨識範例程式詳解
「Face Detector on Camera範例完整程式 main.py 如下所示,人機介面顯示內容如 圖4 所示。
程式首先建立網頁人機介面(WebUI),接著初始化影片物件偵測磚塊(人臉辨識模組)指定預設人臉偵測置信度為0.5(最小為0.0,最大為1.0)。當網頁人機介面改變置信度(Cofidence)拉桿位置時,數值會通過 web_ui 磚塊傳回,如此就不用為了想改變置信度就要重新編譯程式並上傳。
所建立的 detection_stream 會一直不斷從網路攝影機接收影像和進行物件偵測。理論上每接收到一幀影像不管畫面中是否有人臉都會呼叫 on_detect_all 即 send_detections_to_ui() 函式,而確定有人臉的則會呼叫 on_detect 即 face_detected() 函式。
但經反覆實驗確認後,目前版本的 video_objectdetection 磚塊當沒有偵測到人臉時並不會進到 send_detections_to_ui() 函式,和官方文件描述的每一幀都會呼叫並不相符,這對後續要控制燈光點滅有很大的問題,這部份將於第五節說明如何解決。
當偵測到人臉時,會將所有人臉物件資訊 detections: dict 傳入 send_detections_to_ui() 函式中進行解析。由於可能不只一張人臉,所以會依序將 items() 分解成內容(content)、置信度(confidence)和時間戳(timestamp),最後組合成 JSON 格式傳送到 web_ui 磚塊中,顯示到網頁人機介面右下角區域中。網頁相關內容可自行參閱專案中 /assets 下的 index.html, app.js, style.ccs 等程式,這裡就先不多作說明。
# SPDX-FileCopyrightText: Copyright (C) Arduino s.r.l. and/or its affiliated companies
# SPDX-License-Identifier: MPL-2.0
from arduino.app_utils import App
from arduino.app_bricks.web_ui import WebUI
from arduino.app_bricks.video_objectdetection import VideoObjectDetection
from datetime import datetime, UTC
# 初始化網頁人機介面
ui = WebUI()
# 初始化人臉偵測模組(VideoObjectDetection Brick) 置信度預設為 0.5
detection_stream = VideoObjectDetection(confidence=0.5, debounce_sec=0.0)
# 當網頁修改置信度(Confidence)時數值會同步到 threshold 變數中
ui.on_message("override_th", lambda sid, threshold: detection_stream.override_threshold(threshold))
# 已偵測到人臉處理函式
def face_detected():
pass # Implement your logic here, e.g., send a notification
# 註冊回呼函式:on_detect 會在偵測到人臉時執行 face_detected 函式
detection_stream.on_detect("face", face_detected)
# 將偵測到的人臉資訊送到網頁(WebUI)顯示
def send_detections_to_ui(detections: dict):
for key, values in detections.items():
for value in values:
# 組識 JSON 格式輸出字串
entry = {
"content": key,
"confidence": value.get("confidence"),
"timestamp": datetime.now(UTC).isoformat()
}
ui.send_message("detection", message=entry)
# 註冊回呼函式:on_detect_all 會在每一幀都觸發(理論上)
detection_stream.on_detect_all(send_detections_to_ui)
App.run()

圖4:人臉偵測範例人機介面及對應資訊。(OmniXRI整理製作,2026/06/17)
經初步實驗,如圖5所示,這個範例對於人臉偵測能力還不錯,不管是旋轉、遮蔽、多人都有不錯的表現,置信度也都很高。當人臉消失超過一段時間,網頁上的回應區圖示也會改變,很符合本次主題所需。大家亦可試著給出更多、更小、背景更雜亂的人臉來測試一下這個 AI 模型極限。

圖5:人臉偵測範例在不同情境的偵測效果。(OmniXRI整理製作,2026/06/17)
4. MCU 控制板載 LED
如【Arduino UNO Q 專欄02】軟體開發初體驗一文提及,想要在 MPU 使用 Python 來控制 MCU 所負責的板載週邊元件(如LED),除了要寫一個傳統的 MCU C++ 程式(*.ino)外,還要在程式中提供橋接回呼(Bridge Callback)函式,如此才能順利被 MPU Python 程式呼叫並執行。
如圖6所示, UNO Q 板載右上角有四組 RGB 三色 LED,其中 LED3 和 LED4 是 MCU 可直接控制的。這裡在 MCU 程式中加入 set_led_state() 函式負責處理是否偵測到人臉的狀態,當有偵測到人臉時(True)熄滅 LED3 紅燈並點亮綠燈,而沒有偵測到人臉時(False)則令 LED3 點亮紅燈並熄滅綠燈。最後透過 Bridge.provoid() 提供給 MPU Python 程式呼叫。

圖6:UNO Q MPU 透過橋接回呼函式控制板載 LED 架構。(OmniXRI整理製作,2026/06/17)
由於 「Face detection on camera」範例只有在網頁上顯示人臉偵測結果,並沒有控制實體週邊,所以我們要自行加入 MCU 程式。如 Fig. 7 所示,首先於左側點擊滑鼠右鍵建立一個檔案夾(Create new folder)名為 sketch。接著點擊這個目錄再點擊滑鼠右鍵建立一個新檔案(Create file)名為 sketch.ino ,再貼入下面完整程式到 sketch.ino。
// 作者: Jack OmniXRI, 2026/06/17
#include // 引入 RouterBridge 相關函式
// 定義 Bridge 回呼函式:由 Python 端遠端呼叫
// 輸入參數: 是否偵測到人臉
bool set_led_state(bool face_detected) {
//Serial.println(face_detected); // 於串列埠列印人臉偵測狀態(可略)
if (face_detected) {
// 有人臉:亮綠燈,熄滅紅燈
digitalWrite(LED3_R, HIGH);
digitalWrite(LED3_G, LOW);
return true;
} else {
// 沒人臉:亮紅燈,熄滅綠燈
digitalWrite(LED3_R, LOW);
digitalWrite(LED3_G, HIGH);
return true;
}
return false;
}
void setup() {
//Serial.begin(9600); // 初始化序列埠, 通訊速度為 9600 bps(可略)
// 設定板載 LED3 為輸出腳位
pinMode(LED3_R, OUTPUT);
pinMode(LED3_G, OUTPUT);
pinMode(LED3_B, OUTPUT); // 藍燈暫不使用(可略)
// 熄滅板載 LED3, 由於 LED 採低電位驅動(Active Low) 所以輸出 HIGH 為熄滅
digitalWrite(LED3_R, HIGH);
digitalWrite(LED3_G, HIGH);
digitalWrite(LED3_B, HIGH); // 藍燈暫不使用(可略)
// 初始化內建的 RouterBridge 通訊
Bridge.begin();
// 將 set_led_state 函式提供到 Bridge 中,讓 Python 能夠呼叫
// 參數一: 提供給 Python 的服務名稱
// 參數二: 實際執行的 C++ 函式名稱
Bridge.provide("set_led_state", set_led_state);
}
void loop() {
}
接下來要新增 sketch.yaml 來描述 MCU 程式主要項目,以利 MCU 程式能正常編譯。由於 APP LAB 對所有 *.yaml 有保護作用,因此即便新增也不能編輯,須要進到命令列(CLI)模式才能編輯。如圖7所示,點擊 APP LAB 下方命令列符號進入命令列模式,此時要輸入 Linux 的密碼才能進入。接著進入範例工作路徑(即專案名稱),使用 nano 文字編輯器新增 sketch.yaml。
cd ArduinoApps
cd copy-of-face-detector-on-camera
cd sketch
nano sketch.yaml
進入 nano 編輯區後,貼上下列內容,按 ctrl+O 儲存及 ctrl+X 離開,然後關閉命令列模式工作視窗回到 APP LAB,便可看到 sketch.yaml 檔案已新增到 /sketch 路徑下了。
profiles:
default:
platforms:
- platform: arduino:zephyr
libraries:
default_profile: default

圖7:新增 sketch.ino 及 sketch.yaml 程序。(OmniXRI整理製作,2026/06/17)
5. 人臉偵測結果控制板載 LED
原本只要在 Python 中加入 Bridge.call() 就能呼叫 MCU 提供的函式來改變 LED3 紅綠燈顯示狀態,但由於 detection_stream.on_detect_all() 沒偵到人臉時 send_detections_to_ui() 並不會被執行,造成 LED3 綠燈只能被點亮但無法熄滅,所以為解決這個問題只能強迫寫一個看門狗(定時執行)的程式來檢查最後一次偵測到人臉的時間和現在時間差多少來決定是要令板載 LED3 亮紅燈還是亮綠燈。
為此我們需要新增三個變數, last_detection_time 記錄最後一次偵測到人臉的時間,NO_FACE_TIMEOUT 沒有偵測到人臉逾時秒數及 LOCK 設定互斥鎖避免兩個以上函數同時在讀寫 last_detection_time 。
偵測到人臉點亮綠燈部份還是可以依賴原先的 send_detections_to_ui(),進入後將目前時間填入最後一次偵測到人臉的時間 last_detection_time ,若有超過一張人臉時則呼叫 MCU 點亮綠燈。
而開門狗(定時執行)程式則是依賴執行緒(Threading)同步執行的功能達到平行監控其它工作中的程式。預設每 0.2 秒就會檢查最後一次偵測到人臉的時間和現在時間的差值,當差值超過 1.0 秒(NO_FACE_TIMEOUT)時就使用 Bridge.call 命令 MCU 亮紅燈。
以下為完整 Python 程式,大家可以比對一下和第三小節原始範例的差異。
# 作者: Jack OmniXRI, 2026/06/17
from arduino.app_utils import App, Bridge
from arduino.app_bricks.web_ui import WebUI
from arduino.app_bricks.video_objectdetection import VideoObjectDetection
from datetime import datetime, UTC
import time
import threading
last_detection_time = time.time() # 啟動時將上次偵測到人臉時間設成目前時間
LOCK = threading.Lock() # 設定互斥鎖, 避免 last_detection_time 變數被不同函數同時改變
NO_FACE_TIMEOUT = 1.0 # 沒偵測到人臉超過秒數
# 初始化 WebUI 介面
ui = WebUI()
# 初始化人臉偵測模組(VideoObjectDetection Brick) 置信度預設為 0.5
detection_stream = VideoObjectDetection(confidence=0.5, debounce_sec=0.0)
# 當網頁修改置信度(Confidence)時數值會同步到 threshold 變數中
ui.on_message("override_th", lambda sid, threshold: detection_stream.override_threshold(threshold))
# 已偵測到人臉處理函式
def face_detected():
pass # Implement your logic here, e.g., send a notification
# 註冊回呼函式:on_detect 會在偵測到人臉時執行 face_detected 函式
detection_stream.on_detect("face", face_detected)
# 將偵測到的人臉資訊送到網頁(WebUI)顯示
def send_detections_to_ui(detections: dict):
#print("callback fired:", detections) # 檢查偵測到的內容(可略)
global last_detection_time # 宣告全域變數, 上一次偵測到人臉時間
with LOCK: # 啟動互鎖避免變數 last_detection_time 被兩個以上函數同時修改
last_detection_time = time.time() # 把目前時間填入上次偵測到人臉的時間
#print(f"上次偵測到人臉的時間: {last_detection_time}") # 列印上次偵測到人臉的時間(可略)
# 檢查 detections 裡有沒有 "face" 這個 key,並且裡面真的有資料
# 若沒人臉,faces 會是空集合 [],len() 等於 0
# 若有人臉則會將相關資訊依序推入 faces (List)
faces = detections.get("face", [])
if len(faces) > 0: # 有偵測到人臉
# print(f"畫面上有人臉!數量:{len(face_list)}") # 顯示偵測到多少張人臉(可略)
Bridge.call("set_led_state", True) # 通知 MCU 有偵測到人臉點亮綠燈
# 將偵測到的人臉資訊送到網頁(WebUI)
for key, values in detections.items():
for value in values:
# 組識 JSON 格式輸出字串
entry = {
"content": key,
"confidence": value.get("confidence"),
"timestamp": datetime.now(UTC).isoformat()
}
ui.send_message("detection", message=entry)
# 註冊回呼函式:on_detect_all 會在每一幀都觸發(理論上)
detection_stream.on_detect_all(send_detections_to_ui)
# 開門狗函式
def watchdog():
while True:
time.sleep(0.2) # 每隔 0.2 秒檢查一次
with LOCK:
# 計算上一次檢查到人臉時間和現在差值
elapsed = time.time() - last_detection_time
#print(f"偵測到人臉時間差: {elapsed} 秒") # 顯示時間差(可略)
if elapsed > NO_FACE_TIMEOUT: # 若時間差值超過設定值則令板載LED3 綠燈滅紅燈亮
Bridge.call("set_led_state", False)
# 啟動執行緒並執行開門狗(watchdog)函式
threading.Thread(target=watchdog, daemon=True).start()
App.run()
結語
Arduino APP LAB 的磚塊(Brick) 開發方式,帶來許多便利,同時也解決了大家不太會開發網頁人機介面、網路攝影機取像及 AI 辨識程式的問題。本文採用了基礎的人臉偵測磚塊範例,並添加了 MCU 控制板載 LED 亮滅的功能,還利用看門狗技術解決人臉未偵測到的 LED 亮滅問題。未來只要把板載 LED 換成其它電器,這樣用人臉偵測來完成節能減碳的工作就不再是難題了。更多 AI 磚塊應用的介紹就留待下期分解。
(責編:Judith Cheng;編按:本文內的程式碼可能因顯示模式而有跑版情況,完整內容也可參考作者的部落格)
- 【Arduino UNO Q專欄04】人臉偵測開關燈具 - 2026/06/22
- 【Arduino UNO Q專欄03】板載點矩陣LED應用 - 2026/06/08
- 【Arduino UNO Q專欄02】軟體開發初體驗 - 2026/05/21
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!



