
1. 項目緣起為什么要在行空板上折騰MediaPipe摳圖最近在做一個智能交互展項需要在資源受限的嵌入式設備上實時處理攝像頭畫面把人物從背景里干凈地“摳”出來。一開始想到的是上云或者用高性能工控機跑OpenCVDeeplabv3但成本、延遲和部署復雜度都成了問題。直到我把目光投向了手邊的行空板UNIHIKER和Google的MediaPipe一個想法冒了出來能不能在這塊小巧的板子上實現一個夠用、實時的人物摳圖系統行空板是一塊基于Linux系統的開源硬件集成了屏幕、按鍵和豐富的傳感器性能對于嵌入式應用來說相當不錯。而MediaPipe作為一個跨平臺的機器學習管道框架其輕量級模型和高效的推理引擎是出了名的。它的Selfie Segmentation模型專門為實時人像分割優化模型小巧僅幾MB在移動端都能流暢運行。那么把它移植到行空板的ARM架構上理論上完全可行。這個組合的意義在于它把需要強大算力的“摳圖”這件事下沉到了邊緣設備實現了低延遲、高隱私、離線可用的實時人像處理非常適合教育、互動藝術、輕量級智能終端等場景。2. 行空板開發環境搭建與核心依賴部署要在行空板上跑通MediaPipe第一步就是把環境給配齊了。行空板默認運行的是Debian系統這給我們提供了很大的便利。2.1 系統準備與基礎包安裝首先通過SSH或者直接使用行空板的終端更新軟件源并安裝一些基礎編譯工具和Python環境管理工具。sudo apt update sudo apt upgrade -y sudo apt install -y python3-pip python3-venv git wget curl build-essential cmake我強烈建議使用Python虛擬環境來管理項目依賴避免污染系統環境。cd ~ python3 -m venv mp_env source mp_env/bin/activate激活虛擬環境后命令提示符前會出現(mp_env)的標識。2.2 MediaPipe Python包的安裝挑戰與解決MediaPipe官方提供了Python輪子wheel但主要是針對x86_64架構和特定的ARM64架構如樹莓派。行空板使用的處理器可能需要我們尋找兼容的版本或者從源碼編譯。最穩妥的方法是安裝MediaPipe針對Linux ARM的版本。pip3 install --upgrade pip對于大多數基于ARMv8的行空板可以嘗試安裝官方的Linux ARM版本。但根據我的實測直接pip install mediapipe可能會因為架構不匹配而失敗。一個更可靠的方案是使用由社區維護的、針對特定平臺預編譯的版本或者利用MediaPipe的“自定義包”功能。不過為了最廣泛的兼容性我采用了另一種更直接的方法使用MediaPipe的輕量級解決方案即不安裝完整的mediapipe包而是使用其提供的.tflite模型文件結合TensorFlow Lite運行時進行推理。這是因為MediaPipe Selfie Segmentation模型本身就是一個TFLite模型我們可以直接下載并使用它。# 安裝TensorFlow Lite運行時 pip3 install tflite-runtime如果tflite-runtime安裝遇到問題也可以嘗試安裝完整的TensorFlow稍重但兼容性好pip3 install tensorflow2.3 模型文件獲取與OpenCV安裝接下來下載MediaPipe的Selfie Segmentation模型。我們可以從MediaPipe的官方GitHub倉庫獲取。mkdir -p ~/selfie_segmentation/models cd ~/selfie_segmentation/models wget -O selfie_segmentation.tflite https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmentation/float16/1/selfie_segmentation.tflite這個float16版本的模型在精度和速度上取得了很好的平衡非常適合行空板。同時我們還需要安裝OpenCV來處理圖像。pip3 install opencv-python-headless這里安裝的是headless版本因為它不包含GUI相關的庫如GTK在服務器或行空板這種無桌面環境或輕量桌面環境下更節省資源。如果你的應用需要利用行空板自帶的屏幕顯示圖像可能需要額外安裝opencv-python包含GUI支持但通常headless版本配合行空板自帶的顯示接口也是可以的。至此最核心的環境和模型就準備好了。這種“TFLite運行時 獨立模型文件”的方式比安裝完整的MediaPipe Python包更加輕量依賴更少是嵌入式部署的常見做法。3. 圖像分割與摳圖的核心代碼實現有了模型和環境接下來就是編寫代碼實現從讀取圖像到輸出摳圖結果的完整流程。這個過程可以分為幾個關鍵步驟圖像預處理、模型推理、后處理生成掩膜和圖像合成。3.1 圖像預處理與模型輸入適配MediaPipe Selfie Segmentation模型的輸入要求是RGB三通道圖像尺寸為256x256像素像素值歸一化到[0, 1]的浮點數。我們需要將任意尺寸的輸入圖像比如從行空板攝像頭讀取的轉換到這個格式。import cv2 import numpy as np import tflite_runtime.interpreter as tflite def preprocess_image(image): 將輸入圖像預處理為模型所需的格式。 Args: image: numpy數組BGR格式OpenCV默認。 Returns: input_data: 形狀為(1, 256, 256, 3)的numpy數組RGB格式值范圍[0,1]。 original_shape: 原始圖像的形狀用于后續還原。 # 記錄原始尺寸 original_shape image.shape[:2] # (height, width) # 將BGR轉換為RGB rgb_image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 縮放到256x256 resized_image cv2.resize(rgb_image, (256, 256)) # 將像素值從[0,255]轉換為[0,1]的浮點數 input_data resized_image.astype(np.float32) / 255.0 # 添加批次維度變成 (1, 256, 256, 3) input_data np.expand_dims(input_data, axis0) return input_data, original_shape這里有一個關鍵點OpenCV默認讀入的圖像是BGR通道順序而模型通常期望RGB順序。cv2.cvtColor這個轉換必不可少否則顏色通道錯亂會導致模型識別異常。3.2 加載TFLite模型并執行推理我們使用TFLite解釋器來加載和運行模型。def initialize_interpreter(model_path): 初始化TFLite解釋器。 interpreter tflite.Interpreter(model_pathmodel_path) interpreter.allocate_tensors() return interpreter def run_inference(interpreter, input_data): 執行模型推理。 # 獲取輸入和輸出張量的詳細信息 input_details interpreter.get_input_details() output_details interpreter.get_output_details() # 將預處理后的數據設置為輸入 interpreter.set_tensor(input_details[0][index], input_data) # 執行推理 interpreter.invoke() # 獲取輸出結果 # Selfie Segmentation模型輸出一個形狀為(1, 256, 256, 1)的掩膜 output_data interpreter.get_tensor(output_details[0][index]) return output_dataoutput_data就是模型預測的掩膜mask其值在0到1之間通常經過sigmoid激活越接近1表示該像素屬于“人物”的概率越高。3.3 掩膜后處理與圖像合成得到的掩膜是256x256大小的我們需要將其還原到原始圖像的尺寸并利用它來摳圖。def postprocess_mask(mask, original_shape): 將模型輸出的掩膜后處理并縮放到原始圖像尺寸。 Args: mask: 模型輸出形狀(1, 256, 256, 1)或(256, 256, 1)。 original_shape: 目標尺寸 (height, width)。 Returns: resized_mask: 形狀為(original_height, original_width)的二維數組值范圍[0,255] (uint8)。 # 去除批次維度得到(256, 256, 1) mask np.squeeze(mask, axis0) if mask.ndim 4 else mask # 如果模型輸出是(256, 256, 1)則壓縮為(256, 256) mask np.squeeze(mask, axis-1) if mask.shape[-1] 1 else mask # 將概率值[0,1]轉換為[0,255]的灰度圖 mask_uint8 (mask * 255).astype(np.uint8) # 縮放到原始圖像尺寸 resized_mask cv2.resize(mask_uint8, (original_shape[1], original_shape[0]), interpolationcv2.INTER_LINEAR) return resized_mask def apply_mask_to_image(image, mask, background_color(255, 255, 255)): 使用掩膜將人物從原圖中摳出并放置在指定背景色上。 Args: image: 原始BGR圖像。 mask: 與image同尺寸的灰度掩膜值越大代表越可能是前景。 background_color: 背景的BGR顏色。 Returns: result: 摳圖后的BGR圖像。 # 將掩膜歸一化到[0, 1]作為alpha通道 alpha mask.astype(np.float32) / 255.0 # 為alpha增加通道維度便于廣播 (H, W) - (H, W, 1) alpha np.expand_dims(alpha, axis-1) # 創建純色背景圖 background np.full_like(image, background_color, dtypenp.uint8) # 前景 原圖 * alpha foreground image.astype(np.float32) * alpha # 背景 背景色 * (1 - alpha) background_part background.astype(np.float32) * (1 - alpha) # 合成 result (foreground background_part).astype(np.uint8) return resultapply_mask_to_image函數實現了最常見的阿爾法混合。這里background_color可以隨意替換比如換成另一張圖片就能實現“換背景”的效果。alpha通道的平滑過渡值在0到1之間使得摳圖邊緣不會生硬這是直接使用二值化閾值如mask 128所不具備的優勢。4. 在行空板上實現實時攝像頭摳圖將上述代碼模塊整合并接入行空板的攝像頭就能實現實時摳圖了。行空板通常使用/dev/video0作為攝像頭設備。import time def main(): # 初始化 model_path models/selfie_segmentation.tflite interpreter initialize_interpreter(model_path) # 打開攝像頭行空板USB攝像頭通常是video0 cap cv2.VideoCapture(0) # 可以設置分辨率降低分辨率可以提高幀率 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) if not cap.isOpened(): print(無法打開攝像頭) return print(開始實時摳圖按 q 鍵退出...) while True: # 讀取一幀 ret, frame cap.read() if not ret: print(無法獲取幀) break # 記錄開始時間以計算FPS start_time time.time() # 1. 預處理 input_data, orig_shape preprocess_image(frame) # 2. 推理 output_mask run_inference(interpreter, input_data) # 3. 后處理得到原始尺寸的掩膜 mask postprocess_mask(output_mask, orig_shape) # 4. 應用掩膜生成摳圖結果這里背景設為綠色 result apply_mask_to_image(frame, mask, background_color(0, 255, 0)) # 計算并顯示FPS fps 1.0 / (time.time() - start_time) cv2.putText(result, fFPS: {fps:.1f}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) # 顯示結果如果行空板連接了屏幕 cv2.imshow(MediaPipe Selfie Segmentation - UNIHIKER, result) # 按q退出 if cv2.waitKey(1) 0xFF ord(q): break # 釋放資源 cap.release() cv2.destroyAllWindows() if __name__ __main__: main()這段代碼構成了一個完整的實時摳圖應用。在行空板上運行你就能看到攝像頭畫面中的人像被實時地摳出來背景被替換成了綠色。幀率FPS是衡量實時性的關鍵指標在行空板上根據分辨率和模型復雜度達到10-20 FPS是很有希望的。注意行空板運行帶GUI的OpenCV (cv2.imshow) 需要圖形界面環境支持。如果你的行空板運行在純命令行模式無桌面這部分代碼會報錯。此時你有兩個選擇1) 將結果圖像保存為文件2) 使用行空板自帶的pinpong庫或其它方式將圖像數據發送到板載屏幕上顯示。這涉及到行空板特定的顯示API調用需要參考其官方文檔。5. 性能優化與效果提升實戰技巧在資源受限的行空板上讓整個流程跑得又快又好需要一些優化技巧。5.1 推理速度優化輸入分辨率與模型選擇模型的輸入分辨率直接影響推理速度。Selfie Segmentation模型固定輸入256x256這本身已經很小。但我們可以從預處理和后處理環節省時間。降低攝像頭采集分辨率這是最有效的提速方法之一。如果最終顯示或處理不需要太高清的圖像直接將攝像頭分辨率設為320x240或更低能極大減少需要處理的數據量。cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)探索更輕量模型MediaPipe可能還提供更小的模型變體或者可以嘗試將模型量化到int8精度tflite模型。量化模型在保持可接受精度損失的前提下能顯著提升在ARM處理器上的推理速度。你需要尋找或自己使用TensorFlow工具量化模型。5.2 摳圖邊緣優化掩膜平滑與腐蝕膨脹直接使用模型輸出的掩膜進行混合有時在頭發絲、透明物體邊緣處會有鋸齒感或殘留背景色。高斯模糊平滑邊緣對掩膜施加一個輕微的高斯模糊可以使alpha過渡更加平滑自然。def smooth_mask(mask, kernel_size5, sigma1.0): blurred cv2.GaussianBlur(mask.astype(np.float32), (kernel_size, kernel_size), sigma) # 重新歸一化到0-255防止模糊導致整體變暗 blurred np.clip(blurred, 0, 255).astype(np.uint8) return blurred在postprocess_mask函數中對resized_mask調用smooth_mask后再返回。形態學操作處理噪點如果掩膜中存在小的空洞人物內部被誤判為背景或孤立的噪點背景被誤判為人可以使用形態學操作。def denoise_mask(mask, kernel_size3): kernel np.ones((kernel_size, kernel_size), np.uint8) # 先閉運算填充小洞再開運算去除小白點 mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) return mask注意形態學操作會改變掩膜形狀需謹慎調節核大小。5.3 處理復雜背景與多人場景Selfie Segmentation是一個通用模型在復雜背景或多人場景下效果可能會下降。背景復雜度模型在干凈、對比度高的背景下表現最好。如果背景和人物顏色相近比如穿白衣服站在白墻前分割效果會變差。在實際應用中可以通過布光或選擇背景來改善。多人場景該模型設計用于分割單個人像“自拍”。對于多人同框它會嘗試分割出所有人物作為一個整體前景。如果你需要區分不同的個體則需要換用實例分割模型如MediaPipe Holistic或其它模型但那會復雜和沉重得多。對于行空板處理多人作為一個整體前景是更現實的選擇。一個實用的技巧是設置置信度閾值。模型輸出的掩膜值是置信度。我們可以通過閾值化來獲得一個更“干凈”的二值掩膜但會丟失邊緣平滑度。一種折衷是使用閾值來生成一個“確定前景”區域然后利用cv2.grabCut算法進行精細優化但這在行空板上計算成本較高。def threshold_mask(mask, low_thresh0.3, high_thresh0.7): 使用雙閾值生成三區域掩膜確定前景/確定背景/不確定。 可用于后續的grabCut優化。 h, w mask.shape sure_fg np.zeros((h, w), np.uint8) sure_bg np.zeros((h, w), np.uint8) uncertain np.zeros((h, w), np.uint8) sure_fg[mask high_thresh * 255] 1 sure_bg[mask low_thresh * 255] 1 uncertain[(mask low_thresh*255) (mask high_thresh*255)] 1 return sure_fg, sure_bg, uncertain6. 項目集成與進階應用思路一個基礎的實時摳圖系統已經完成。我們可以把它作為核心模塊集成到更大的行空板項目中。思路一虛擬背景視頻通話將摳出的人像與一張靜態圖片或動態視頻流進行混合實現虛擬背景。在行空板上可以預加載幾張背景圖通過按鍵切換。思路二互動藝術裝置將摳出的人物輪廓用于交互。例如計算輪廓的中心點或外接矩形用來控制一個屏幕上的虛擬物體如一個隨著你移動而移動的光斑。OpenCV的cv2.findContours函數可以很容易地從二值化掩膜中提取輪廓。def get_largest_contour(mask): # 將掩膜二值化 _, binary_mask cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) # 查找輪廓 contours, _ cv2.findContours(binary_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: # 找到面積最大的輪廓 largest_contour max(contours, keycv2.contourArea) # 計算輪廓的矩可以獲取質心 M cv2.moments(largest_contour) if M[m00] ! 0: cx int(M[m10] / M[m00]) cy int(M[m01] / M[m00]) return largest_contour, (cx, cy) return None, None思路三姿態觸發拍照結合MediaPipe的其他模型如Pose Detection在檢測到特定姿勢如舉手時自動保存一張當前摳好圖的照片到行空板的存儲中。這就需要同時運行兩個輕量級模型對行空板的算力是一個考驗可能需要采用交替運行或降低檢測頻率的策略。部署與打包為了讓項目脫離開發環境運行可以將所有依賴和代碼打包。使用pip freeze requirements.txt生成依賴列表。對于行空板更徹底的方式是使用Docker構建一個包含所有環境的鏡像或者利用行空板支持的應用打包方式創建一個一鍵運行的腳本或應用。在整個開發過程中最大的體會是“平衡”。在嵌入式設備上精度、速度和資源占用永遠是一個需要權衡的三角。MediaPipe Selfie Segmentation模型提供了一個極佳的起點它在三者之間取得了很好的平衡。而行空板作為載體讓這個平衡得以在真實的物理世界中實現。從一行行代碼到屏幕上實時分離出的人像這個過程本身就是邊緣智能魅力的最佳詮釋。