:低成本實現(xiàn)電腦存在感知)
1. 項目緣起當你的電腦學會“察言觀色”你有沒有過這樣的經(jīng)歷起身去接杯水或者轉(zhuǎn)頭和同事聊兩句電腦屏幕就那么亮著上面可能還開著某個敏感文檔或私人聊天窗口。雖然大多數(shù)操作系統(tǒng)都提供了自動鎖屏功能但那個固定的超時時間總讓人糾結(jié)設得太短稍微走神屏幕就黑了頻繁輸入密碼很煩人設得太長安全風險又上去了。這個矛盾的核心在于電腦并不知道你“在”還是“不在”。我最近就在琢磨能不能讓鎖屏這件事變得更智能、更人性化一些正好手頭有一個來自Adafruit的Person Sensor——一個小巧的、能識別人臉是否存在的傳感器模塊。它的原理不復雜內(nèi)置了一個低分辨率攝像頭和輕量級的人臉檢測模型只輸出一個簡單的二進制信號檢測到人臉或者沒檢測到。這不正是解決上述問題的完美觸發(fā)器嗎于是一個想法誕生了用這個傳感器來監(jiān)控電腦前的狀態(tài)當它發(fā)現(xiàn)你離開座位即檢測不到人臉超過一個設定的“寬容時間”后就自動觸發(fā)系統(tǒng)鎖屏。當你回來坐下屏幕又能自動亮起或者至少你只需要動動鼠標或按個鍵而不用輸密碼如果系統(tǒng)支持“喚醒后不要求密碼”策略的話。這個項目我稱之為“基于人員感知的筆記本自動鎖屏系統(tǒng)”。它聽起來可能有點像那些昂貴的商用“存在感知”配件但我們的實現(xiàn)成本極低核心是一個幾十美元的傳感器和一點代碼。更重要的是整個過程是本地化的你的人臉圖像數(shù)據(jù)不會離開你的設備隱私性有保障。下面我就來拆解一下如何從零開始把這個想法變成現(xiàn)實。2. 核心組件選型與工作原理剖析要實現(xiàn)這個自動鎖屏系統(tǒng)我們需要幾個核心部分感知端、控制邏輯端和執(zhí)行端。每一部分的選擇都直接關(guān)系到項目的穩(wěn)定性、易用性和隱私安全。2.1 感知核心為什么是Adafruit Person Sensor市面上能檢測人的傳感器不少比如PIR被動紅外運動傳感器、超聲波測距模塊甚至更復雜的攝像頭加OpenCV方案。我最終選擇Adafruit Person Sensor是基于以下幾個關(guān)鍵的考量首先精準度與誤報的平衡。PIR傳感器對運動敏感但如果你坐在電腦前一動不動它可能認為你離開了反之室內(nèi)氣流、寵物經(jīng)過也可能觸發(fā)它。這會導致鎖屏頻繁誤觸發(fā)體驗極差。Person Sensor專注于人臉檢測只要你的臉在視野內(nèi)且基本正對傳感器它就能穩(wěn)定識別對于“在位”的判斷準確得多。其次隱私與計算開銷。這是一個至關(guān)重要的點。Person Sensor的設計哲學是“隱私優(yōu)先”。它內(nèi)置的機器學習模型在傳感器本地運行只輸出檢測結(jié)果如人臉數(shù)量、邊界框而不會輸出原始圖像或任何生物特征數(shù)據(jù)。這意味著你的人臉數(shù)據(jù)從未離開過傳感器芯片從根本上杜絕了隱私泄露風險。相比之下用普通USB攝像頭電腦跑OpenCV圖像數(shù)據(jù)需要在電腦內(nèi)存中處理潛在風險更高而且會持續(xù)占用CPU資源。第三接口與易用性。Person Sensor通過I2C接口通信這是一種在嵌入式領(lǐng)域非常通用的雙線串行協(xié)議接線簡單只需連接電源、地線、時鐘線SCL和數(shù)據(jù)線SDA并且有大量微控制器支持。Adafruit為其提供了完善的CircuitPython庫幾乎做到了開箱即用大大降低了開發(fā)門檻。最后是功耗與體積。它體積小巧功耗很低非常適合長期插在電腦USB口上供電運行。注意Person Sensor的檢測范圍有限大約在1-2米內(nèi)視角約120度且需要人臉大致正對。這意味著它不適合用來檢測整個房間的進出而是精準服務于“電腦前座位”這個場景。這正是我們需要的。2.2 控制中樞微控制器的角色與選擇傳感器需要有一個“大腦”來讀取它的狀態(tài)并做出“是否觸發(fā)鎖屏”的邏輯判斷。這個大腦就是微控制器。它的核心職責包括以固定頻率例如每秒1-2次查詢Person Sensor的狀態(tài)。實現(xiàn)一個“狀態(tài)機”邏輯當從“檢測到人臉”變?yōu)椤拔礄z測到人臉”時啟動一個計時器如果計時器超過預設的“離開閾值”比如10秒則判定為“用戶已離開”觸發(fā)鎖屏動作。提供一種方式與電腦通信傳達“鎖屏”指令。常見的選項有Arduino Uno、Raspberry Pi Pico、Adafruit的各類板子如Feather、QT Py。我推薦使用支持CircuitPython的板子例如Adafruit QT Py RP2040或Raspberry Pi Pico。原因如下開發(fā)效率CircuitPython語法接近Python簡單易學交互式開發(fā)直接編輯代碼文件比Arduino C的“編輯-編譯-上傳”循環(huán)要快得多。庫生態(tài)Adafruit為Person Sensor提供了現(xiàn)成的CircuitPython庫幾行代碼就能完成初始化與數(shù)據(jù)讀取。USB通信這些板子通過USB連接到電腦后可以被識別為一個串行設備COM端口或/dev/ttyACM0。我們可以讓微控制器在需要鎖屏時通過這個串口向電腦發(fā)送一條特定的命令字符串。2.3 執(zhí)行終端如何讓電腦聽話地鎖屏這是最后一步也是最需要適配不同操作系統(tǒng)的一步。微控制器發(fā)送的指令需要電腦上的一個“助手程序”來接收并執(zhí)行對應的系統(tǒng)鎖屏命令?;舅悸肥窃陔娔X上運行一個后臺守護進程Daemon或服務Service持續(xù)監(jiān)聽來自微控制器的串口數(shù)據(jù)。一旦接收到約定的鎖屏指令例如字符串“LOCK”就調(diào)用操作系統(tǒng)的鎖屏命令。在Windows上鎖屏命令是rundll32.exe user32.dll,LockWorkStation。你可以用Python的pyserial庫監(jiān)聽串口收到指令后用subprocess.run()調(diào)用這個命令。在macOS上可以使用osascript -e ‘tell application “System Events” to keystroke “q” using {control down, command down}’模擬按下CtrlCmdQ前提是已在“安全性與隱私”中設置使用Control-Command-Q快速鎖屏或者更直接地調(diào)用/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend。在Linux上取決于桌面環(huán)境。對于GNOMEUbuntu默認可以用gnome-screensaver-command -l或dbus-send --typemethod_call --destorg.gnome.ScreenSaver /org/gnome/ScreenSaver org.gnome.ScreenSaver.Lock。對于KDE等其他環(huán)境命令有所不同。這個“助手程序”需要做到開機自啟、后臺靜默運行并且能穩(wěn)定地處理串口連接。用Python編寫是一個跨平臺的好選擇。3. 硬件連接與CircuitPython環(huán)境搭建現(xiàn)在我們開始動手。假設你手頭有Adafruit Person SensorAdafruit QT Py RP2040或其他支持CircuitPython的板子4根母對母杜邦線一根USB數(shù)據(jù)線用于連接板子和電腦3.1 電路連接連接非常簡單只有四根線Person Sensor 引腳QT Py RP2040 引腳說明Vin3V電源正極3.3V。切勿接5VGNDGND電源地線。SCLSCLI2C時鐘線。在QT Py上通常有標明的SCL引腳。SDASDAI2C數(shù)據(jù)線。同樣板子上有SDA引腳。將Person Sensor和QT Py RP2040并排擺放用杜邦線一一對應連接即可。連接好后用USB線將QT Py連接到你的開發(fā)電腦用于編程的電腦上。3.2 刷入CircuitPython固件如果你的板子第一次使用或者里面是其他固件需要先刷入CircuitPython。訪問CircuitPython官網(wǎng)找到你的板子型號如QT Py RP2040的下載頁面。下載最新的.uf2固件文件。讓板子進入“引導加載程序”模式按住QT Py上的“BOOT”或“RESET”按鈕具體請查閱板子說明書同時插入USB線或者先插入USB線再按復位鍵。此時電腦上會出現(xiàn)一個名為RPI-RP2的可移動磁盤。將下載好的.uf2文件拖入這個磁盤。磁盤會自動彈出板子重啟后會出現(xiàn)一個名為CIRCUITPY的新磁盤。這說明CircuitPython固件刷寫成功。3.3 安裝必要的庫CIRCUITPY磁盤就是板子的“文件系統(tǒng)”我們的代碼和庫都放在這里。訪問Adafruit的CircuitPython庫包頁面下載最新的“Adafruit CircuitPython Library Bundle”。解壓下載的Bundle在里面找到lib文件夾。我們需要兩個庫文件adafruit_person_sensor.mpyPerson Sensor驅(qū)動adafruit_bus_device這是一個文件夾I2C通信的基礎支持將這兩個庫文件或文件夾復制到CIRCUITPY磁盤的lib目錄下。如果lib目錄不存在就新建一個。至此硬件和基礎軟件環(huán)境就準備好了。4. 微控制器端代碼詳解與狀態(tài)機邏輯在CIRCUITPY磁盤的根目錄下創(chuàng)建一個名為code.py的文件。CircuitPython會自動運行這個文件。我們將在這里編寫核心邏輯。import time import board import busio from adafruit_person_sensor import PersonSensor # 初始化I2C總線 i2c busio.I2C(board.SCL, board.SDA) # 初始化Person Sensor I2C地址默認為0x62 person PersonSensor(i2c) # 狀態(tài)與參數(shù)定義 LOCK_COMMAND bLOCK\n # 發(fā)送給電腦的鎖屏指令以換行符結(jié)尾便于讀取 ABSENCE_THRESHOLD 10 # 單位秒。檢測不到人臉超過此時間則鎖屏 DEBOUNCE_TIME 2 # 單位秒。狀態(tài)防抖時間避免短暫遮擋誤觸發(fā) # 狀態(tài)變量 person_present False last_seen_time time.monotonic() lock_triggered False # 初始化串口通信USB CDC import usb_cdc serial usb_cdc.data # 這是用于數(shù)據(jù)傳輸?shù)拇趯ο?print(Auto-Lock System Started...) print(fAbsence threshold: {ABSENCE_THRESHOLD}s) while True: # 1. 讀取傳感器狀態(tài) people person.people current_person_present (len(people) 0) # 檢測到至少一個人臉即為“在位” # 2. 狀態(tài)防抖處理 (Debouncing) # 這是一個簡單的防抖邏輯狀態(tài)必須持續(xù)穩(wěn)定DEBOUNCE_TIME秒才被確認 # 這里為了簡化我們采用一種更直觀的“寬容窗口”思路見下方主邏輯 # 更嚴謹?shù)姆蓝缎枰涗洜顟B(tài)變化時間本例暫略以保持清晰。 # 3. 核心狀態(tài)機邏輯 if current_person_present: # 檢測到人在 if not person_present: # 狀態(tài)從“不在”變?yōu)椤霸凇?print(Person detected. Resetting timer.) lock_triggered False # 重置鎖屏觸發(fā)標志為下一次離開準備 person_present True last_seen_time time.monotonic() # 更新最后看到的時間 else: # 未檢測到人 if person_present: # 狀態(tài)剛剛從“在”變?yōu)椤安辉凇遍_始計時 absence_start time.monotonic() # 等待一個短暫的防抖時間防止轉(zhuǎn)頭、低頭瞬間誤判 time.sleep(DEBOUNCE_TIME) # 防抖后再次確認狀態(tài) people person.people if len(people) 0: # 確認人已離開 person_present False print(fPerson left. Timer started at {absence_start}) else: # 防抖期間人又回來了認為是短暫動作忽略此次狀態(tài)變化 person_present True last_seen_time time.monotonic() continue # 人不在的狀態(tài)持續(xù)中... current_time time.monotonic() absence_duration current_time - last_seen_time if not lock_triggered and absence_duration ABSENCE_THRESHOLD: # 離開時間超過閾值且尚未觸發(fā)鎖屏 print(fAbsence duration ({absence_duration:.1f}s) exceeds threshold. Locking screen!) try: # 通過串口發(fā)送鎖屏指令 serial.write(LOCK_COMMAND) print(Lock command sent.) except Exception as e: print(fFailed to send lock command: {e}) lock_triggered True # 設置標志防止重復發(fā)送 # 4. 循環(huán)間隔 time.sleep(0.5) # 每0.5秒檢測一次可根據(jù)需要調(diào)整代碼邏輯深度解析初始化導入必要的庫建立I2C連接創(chuàng)建傳感器對象。定義關(guān)鍵參數(shù)鎖屏指令字符串、離開閾值、防抖時間。狀態(tài)變量person_present: 當前確認的“人在”狀態(tài)。last_seen_time: 最后一次確認看到人臉的時間戳用于計算離開時長。lock_triggered: 鎖屏動作是否已觸發(fā)標志防止在用戶回來前重復發(fā)送鎖屏命令。主循環(huán)讀取與判斷每次循環(huán)讀取傳感器判斷當前是否有人。防抖處理這是提升體驗的關(guān)鍵。當狀態(tài)從“有”變“無”時不是立即開始計時而是等待一個短暫的DEBOUNCE_TIME如2秒然后再次確認。如果人確實不在了才更新狀態(tài)并開始正式計時。這有效過濾了“轉(zhuǎn)頭拿東西”、“低頭看手機”等短暫動作。計時與觸發(fā)在“人不在”的狀態(tài)下持續(xù)計算離開時長。當時長超過ABSENCE_THRESHOLD且未觸發(fā)過鎖屏時執(zhí)行鎖屏動作通過usb_cdc.data.write()發(fā)送LOCK_COMMAND。復位機制一旦檢測到人回來立即重置last_seen_time和lock_triggered標志系統(tǒng)恢復待命狀態(tài)。這個狀態(tài)機邏輯平衡了響應速度和防誤觸是項目可用的核心。你可以通過調(diào)整ABSENCE_THRESHOLD和DEBOUNCE_TIME來個性化你的鎖屏策略。5. 電腦端守護程序跨平臺鎖屏指令派發(fā)微控制器已經(jīng)能發(fā)送指令了現(xiàn)在需要在你的日常使用的筆記本電腦上安裝一個“接收器”程序。這個程序需要識別并連接正確的串口設備。持續(xù)監(jiān)聽該串口。收到特定指令后執(zhí)行系統(tǒng)鎖屏命令。下面是一個使用Python編寫的跨平臺示例。你需要先在電腦上安裝Python和pyserial庫 (pip install pyserial)。# auto_lock_host.py import serial import serial.tools.list_ports import subprocess import sys import time import platform def get_system_lock_command(): 根據(jù)操作系統(tǒng)返回鎖屏命令 system platform.system() if system Windows: return [rundll32.exe, user32.dll,LockWorkStation] elif system Darwin: # macOS # 方法1模擬快捷鍵 (需在系統(tǒng)偏好設置中啟用) # return [osascript, -e, tell application System Events to keystroke q using {control down, command down}] # 方法2使用cgsession (更可靠) return [/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession, -suspend] elif system Linux: # 嘗試多種常見Linux桌面環(huán)境的鎖屏命令 # 你可以根據(jù)你的桌面環(huán)境注釋/取消注釋 try: # 檢查是否是GNOME desktop os.environ.get(XDG_CURRENT_DESKTOP, ).lower() if gnome in desktop or ubuntu in desktop: # 嘗試dbus方式 return [dbus-send, --typemethod_call, --destorg.gnome.ScreenSaver, /org/gnome/ScreenSaver, org.gnome.ScreenSaver.Lock] # 如果是KDE elif kde in desktop: return [qdbus, org.kde.screensaver, /ScreenSaver, Lock] except: pass # 通用回退命令可能不適用于所有環(huán)境 return [gnome-screensaver-command, -l] else: raise OSError(fUnsupported operating system: {system}) def find_device_port(vid_pidNone): 嘗試自動查找設備串口。 可以傳入設備的USB VID和PID元組來精準定位例如 (0x239A, 0x80D0) 對應 Adafruit QT Py RP2040。 如果未指定則列出所有端口供選擇。 ports list(serial.tools.list_ports.comports()) if vid_pid: target_vid, target_pid vid_pid for port in ports: if port.vid target_vid and port.pid target_pid: print(fFound device at {port.device} (VID:PID{hex(port.vid)}:{hex(port.pid)})) return port.device print(fDevice with VID:PID {hex(target_vid)}:{hex(target_pid)} not found.) else: if not ports: print(No serial ports found.) return None print(Available serial ports:) for i, port in enumerate(ports): print(f [{i}] {port.device} - {port.description} (VID:PID{hex(port.vid) if port.vid else N/A}:{hex(port.pid) if port.pid else N/A})) try: choice int(input(Select the port number: )) return ports[choice].device except (ValueError, IndexError): print(Invalid selection.) return None return None def main(): lock_command get_system_lock_command() print(fSystem lock command: { .join(lock_command)}) # 方法1自動查找推薦需要知道設備的VID/PID # 以Adafruit QT Py RP2040為例其USB VID:PID通常是 0x239A:0x80D0 # port_name find_device_port((0x239A, 0x80D0)) # 方法2手動選擇 port_name find_device_port() if not port_name: print(Could not determine serial port. Exiting.) sys.exit(1) print(fConnecting to {port_name}...) try: # 串口參數(shù)必須與微控制器端設置一致 ser serial.Serial( portport_name, baudrate9600, # 波特率與code.py中usb_cdc的默認速率匹配 timeout1 # 讀超時時間秒 ) # 給設備一點時間初始化 time.sleep(2) ser.flushInput() # 清空輸入緩沖區(qū) except serial.SerialException as e: print(fFailed to open serial port {port_name}: {e}) sys.exit(1) print(Listener started. Waiting for lock command...) try: while True: # 讀取一行數(shù)據(jù)以換行符\n為結(jié)束標志 line ser.readline().decode(utf-8, errorsignore).strip() if line: print(fReceived: {line}) if line LOCK: print(Lock command received. Locking screen...) try: # 執(zhí)行系統(tǒng)鎖屏命令 subprocess.run(lock_command, checkTrue) print(Screen lock command executed.) except subprocess.CalledProcessError as e: print(fFailed to execute lock command: {e}) except FileNotFoundError: print(Lock command not found. Please check the command for your OS.) time.sleep(0.1) # 短暫休眠降低CPU占用 except KeyboardInterrupt: print(\nExiting...) finally: ser.close() if __name__ __main__: main()關(guān)鍵點與適配說明串口發(fā)現(xiàn)find_device_port函數(shù)嘗試自動識別你的微控制器。最可靠的方式是傳入設備的USB Vendor ID和Product ID。你可以在設備管理器中查看或者讓程序列出所有端口手動選擇。鎖屏命令適配get_system_lock_command函數(shù)根據(jù)操作系統(tǒng)返回不同的命令。你需要根據(jù)你的系統(tǒng)環(huán)境進行測試和調(diào)整。特別是Linux桌面環(huán)境眾多命令差異很大。腳本中提供了一些常見選項你可能需要查閱你的桌面環(huán)境文檔來找到正確的命令。運行與自啟動測試首先在命令行運行python auto_lock_host.py確保能正確連接并觸發(fā)鎖屏。后臺運行你可以使用nohup(Linux/macOS) 或?qū)⑵浒b為后臺服務/計劃任務。開機自啟Windows可以將.py文件創(chuàng)建快捷方式放到“啟動”文件夾或者用pyinstaller打包成.exe后創(chuàng)建Windows服務。macOS創(chuàng)建.plist文件放入~/Library/LaunchAgents/。Linux創(chuàng)建.service文件放入~/.config/systemd/user/并啟用。6. 實測優(yōu)化與避坑指南將硬件連接好兩端代碼分別部署后系統(tǒng)就可以工作了。但在實際使用中你可能會遇到一些問題。以下是我在實測中總結(jié)的經(jīng)驗和優(yōu)化點。6.1 傳感器擺放與校準Person Sensor的檢測性能受擺放位置影響很大。角度與高度最好將傳感器放在屏幕頂部中央略微向下傾斜使其視野中心對準你通常坐姿的臉部位置。避免放在側(cè)面或過低的位置。環(huán)境光傳感器對光照有一定要求。避免強烈的逆光如背后有窗戶或極度昏暗的環(huán)境。適中的、均勻的室內(nèi)光效果最佳。校準Adafruit Person Sensor庫通常不需要復雜校準。但如果發(fā)現(xiàn)檢測不穩(wěn)定可以嘗試在初始化后增加一個短暫的穩(wěn)定期或者對連續(xù)多次的檢測結(jié)果進行“投票”濾波例如5次讀取中3次以上有臉才判定為有人這能進一步提升抗干擾能力。6.2 防誤觸策略的精細化最初的防抖邏輯可能還不夠。可以考慮更高級的策略多狀態(tài)濾波維護一個短暫的歷史狀態(tài)隊列如最近5次檢測結(jié)果只有隊列中“無人”狀態(tài)占絕大多數(shù)時才切換主狀態(tài)。這能應對偶爾的檢測失敗?!盎貧w”防抖同理從“無人”切換回“有人”時也可以加一個短暫的延遲確認防止傳感器因光線變化產(chǎn)生的瞬間誤報。動態(tài)閾值在深夜或環(huán)境光很暗時可以適當延長ABSENCE_THRESHOLD因為傳感器誤報率可能升高避免頻繁鎖屏。6.3 電腦端程序的穩(wěn)定性串口重連USB設備可能因休眠、拔插而斷開。一個健壯的守護程序應該包含串口斷開重連機制??梢圆东@serial.SerialException異常并在異常后等待一段時間重新執(zhí)行find_device_port和連接流程。權(quán)限問題在Linux/macOS上運行Python腳本的用戶可能需要有訪問串口設備的權(quán)限通常需要將用戶加入dialout或uucp組。資源占用監(jiān)聽串口的循環(huán)中加入了time.sleep(0.1)這能顯著降低CPU占用。確保你的腳本不會在后臺消耗過多資源。6.4 擴展思路這個基礎框架有很大的擴展空間狀態(tài)反饋讓微控制器板載的LED燈指示不同狀態(tài)如常亮表示有人慢閃表示監(jiān)測中快閃表示已鎖屏。距離感應Person Sensor返回的人臉邊界框大小可以粗略估計距離。你可以設定一個“最小人臉像素”閾值只有當人臉足夠大即人坐得足夠近時才認為是“有效在位”避免路過被識別。與系統(tǒng)電源管理聯(lián)動不僅鎖屏在長時間離開后可以進一步發(fā)送系統(tǒng)休眠或關(guān)屏指令。多平臺適配增強為電腦端程序開發(fā)一個簡單的托盤圖標GUI可以方便地開關(guān)監(jiān)聽、調(diào)整閾值、查看連接狀態(tài)。這個項目將一個小小的硬件傳感器與簡單的自動化腳本相結(jié)合巧妙地解決了一個日常痛點。它不追求復雜的生物識別只專注于“存在”這一二進制狀態(tài)在隱私、成本和實用性之間取得了很好的平衡。我把它放在辦公室和家里的電腦上用了幾個星期那種無需再操心鎖屏問題的“無感”體驗確實讓人回不去了。如果你也厭倦了在安全與便利之間做選擇題不妨花上一個下午動手搭建一套屬于你自己的智能鎖屏哨兵。