人像分割簡介
? 凌智視覺模塊 是一款基于rv1106芯片開發的視覺模塊,專注于視覺模型部署與開發。
人像分割是一種基于計算機視覺的技術,通過深度學習算法精準識別圖像或視頻中的人物主體,將其與背景進行像素級分離。該技術可實時運行于移動端及嵌入式設備,廣泛應用于虛擬背景、智能摳圖、視頻會議美顏等場景,支持復雜光照、多樣姿態和遮擋情況下的高精度分割,兼顧處理速度與效果。
人像分割常用方法
目前對于實現人像分割任務的方法有很多,下面介紹幾種常用的人像分割實現方法:
- 傳統算法(如GrabCut):基于顏色直方圖與圖割優化,適合簡單背景,計算量小但精度有限。
- U-Net系列:編碼器-解碼器結構,醫學圖像起家,適合精細邊緣,需較高算力。
- DeepLab系列:采用空洞卷積擴大感受野,擅長復雜場景,模型較大。
- BiSeNet:雙分支結構平衡速度與精度,實時分割首選,移動端友好。
- PP-HumanSeg:百度自研輕量模型,專為人像優化,支持半監督訓練。
PP-HumanSeg模型簡介
將人物和背景在像素級別進行區分,是一個圖像分割的經典任務,具有廣泛的應用。 一般而言,該任務可以分為兩類:針對半身人像的分割,簡稱肖像分割;針對全身和半身人像的分割,簡稱通用人像分割。
對于肖像分割和通用人像分割,PaddleSeg發布了PP-HumanSeg系列模型,具有分割精度高、推理速度快、通用型強的優點。而且PP-HumanSeg系列模型可以開箱即用,零成本部署到產品中,也支持針對特定場景數據進行微調,實現更佳分割效果。
2022年7月,PaddleSeg重磅升級的PP-HumanSegV2人像分割方案,以96.63%的mIoU精度, 63FPS的手機端推理速度,再次刷新開源人像分割算法SOTA指標。相比PP-HumanSegV1方案,推理速度提升87.15%,分割精度提升3.03%,可視化效果更佳。V2方案可與商業收費方案媲美,而且支持零成本、開箱即用!
PP-HumanSeg由飛槳官方出品,是PaddleSeg團隊推出的模型和方案。
這些方法各有優勢,其中在工業部署方面 PP-HumanSeg(精度與速度平衡)和 BiSeNet(高性價比)更適合,配合 OpenCV 后處理優化邊緣。
API 介紹
RKNPU2Backend 類
####頭文件
#include "rknpu2_backend/rknpu2_backend.h"
作用:創建一個
RKNPU2Backend
類,用于實現對 RKNN 模型的處理。
構造類函數
ockzhiner_vision_module::vision::RKNPU2Backend backend;
作用:創建一個
RKNPU2Backend
類型的對象實例,用于實現人像分割。
- 參數說明:無
- 返回值:無
Initialize 函數
bool Initialize(const std::string &model_path, const std::string ¶m_path = "") override;
作用:初始化 RKNN 模型,加載模型文件和可選參數文件,完成推理引擎的準備工作。
- 參數說明:
model_path
:必需參數,RKNN 模型文件路徑(.rknn
格式)param_path
:可選參數,額外參數文件路徑(某些場景下用于補充模型配置,默認空字符串)- 返回值:返回
true/false
,表示模型初始化是否成功。
Run 函數
bool Run();
作用:執行模型推理計算,驅動輸入數據通過模型計算得到輸出結果。
- 參數說明:無
- 返回值:
true
:推理執行成功false
:推理失敗(可能原因:輸入數據未準備、內存不足等)
GetInputAttrs 函數
const std::vector<rknn_tensor_attr>& GetInputAttrs() const;
作用:獲取模型所有輸入張量的屬性信息(維度/形狀、數據類型、量化參數等)。
- 參數說明:無
- 返回值:常量引用形式的
rknn_tensor_attr
向量,包含輸入張量屬性。
GetOutputAttrs 函數
const std::vector<rknn_tensor_mem*>& GetInputMemories() const;
作用:獲取模型所有輸出張量的屬性信息。
- 參數說明:無
- 返回值:常量引用形式的
rknn_tensor_attr
向量,包含輸出張量屬性。
PP-Humanseg 人像分割代碼解析
流程圖
開始
│
├── 參數檢查 (argc == 3)
│ └── 錯誤 → 輸出Usage并退出
│
├── 初始化 RKNN 后端
│ ├── 加載模型
│ └── 初始化失敗 → 錯誤退出
│
├── 加載與預處理輸入圖像
│ ├── 讀取圖像文件
│ ├── 獲取輸入屬性
│ ├── 調用 preprocess() 函數
│ ├── 調整尺寸和顏色空間
│ ├── 量化圖像數據
│ └── 驗證尺寸匹配
│ └── 預處理失敗 → 錯誤退出
│
├── 執行推理
│ ├── 拷貝預處理后的圖像數據到輸入內存
│ ├── 執行推理
│ └── 推理失敗 → 錯誤退出
│
├── 后處理
│ ├── 獲取輸出屬性及內存
│ ├── 調用 postprocess() 函數
│ ├── 解析輸出數據生成概率圖
│ ├── 自適應閾值分割
│ ├── 多尺度形態學處理
│ ├── 智能邊緣優化
│ └── 多模態結果融合
│
├── 結果展示與保存
│ ├── 計算推理時間
│ ├── 生成并保存結果圖像
│ ├── 顯示原始圖像、掩膜及結果圖像
│
└── 程序結束
核心代碼解析
初始化模型
backend.Initialize(model_path)
獲取輸入輸出屬性
const auto& input_attrs = backend.GetInputAttrs();
const auto& output_attrs = backend.GetOutputAttrs();
對輸入圖像進行推理
backend.Run()
自定義函數說明
pp-humanseg 輸入預處理
cv::Mat preprocess(const cv::Mat& image, const std::vector<size_t>& input_dims)
作用:對輸入圖像進行預處理操作,包括 尺寸調整、顏色空間轉換 和 量化處理,使其符合 RKNN 模型的輸入要求。
- 參數說明:
image
:輸入圖像(BGR 格式的cv::Mat
對象)input_dims
:模型輸入張量的維度定義(需滿足[1, H, W, 3]
的 NHWC 格式)- 返回值:
- 返回預處理后的量化張量(
cv::Mat
,數據類型為CV_8S
)- 若輸入維度不合法,返回空矩陣(
cv::Mat()
)并報錯
pp-humanseg 輸入后處理
cv::Mat postprocess(const rknn_tensor_mem* output_mem, const std::vector<size_t>& output_dims,const cv::Size& target_size)
作用:將模型輸出的原始張量轉換為高精度分割掩膜,包含 概率解碼、動態閾值分割、形態學優化 和 邊緣增強 等步驟,最終生成與原始圖像尺寸匹配的二值化掩膜。
- 參數說明:
output_mem
:模型輸出的內存指針,包含量化后的原始數據output_dims
:模型輸出的維度信息,需滿足[1, 2, H, W]
的 NCHW 格式target_size
:目標輸出尺寸- 返回值:返回優化后的二值化掩膜
###完整代碼實現
由于篇幅限制,此處僅展示關鍵代碼邏輯。實際開發中請結合具體項目工程使用。
int main(int argc, char* argv[]) {if (argc != 3) {std::cerr << "Usage: " << argv[0] << " <model_path> <image_path>" << std::endl;return 1;}const std::string model_path = argv[1];const std::string image_path = argv[2];// 初始化RKNN后端lockzhiner_vision_module::vision::RKNPU2Backend backend;if (!backend.Initialize(model_path)) {std::cerr << "Failed to initialize RKNN backend" << std::endl;return -1;}// 加載圖像cv::Mat image = cv::imread(image_path);if (image.empty()) {std::cerr << "Failed to read image: " << image_path << std::endl;return -1;}// 獲取輸入屬性const auto& input_attrs = backend.GetInputAttrs();if (input_attrs.empty()) {std::cerr << "No input attributes found" << std::endl;return -1;}const auto& input_attr = input_attrs[0];std::vector<size_t> input_dims(input_attr.dims, input_attr.dims + input_attr.n_dims);// 預處理cv::Mat preprocessed = preprocess(image, input_dims);if (preprocessed.empty()) {std::cerr << "Preprocessing failed" << std::endl;return -1;}// 驗證輸入數據尺寸const size_t expected_input_size = input_attr.size_with_stride;const size_t actual_input_size = preprocessed.total() * preprocessed.elemSize();if (expected_input_size != actual_input_size) {std::cerr << "Input size mismatch! Expected: " << expected_input_size<< ", Actual: " << actual_input_size << std::endl;return -1;}// 拷貝輸入數據const auto& input_memories = backend.GetInputMemories();if (input_memories.empty() || !input_memories[0]) {std::cerr << "Invalid input memory" << std::endl;return -1;}memcpy(input_memories[0]->virt_addr, preprocessed.data, actual_input_size);// 執行推理high_resolution_clock::time_point start_time =high_resolution_clock::now();if (!backend.Run()) {std::cerr << "Inference failed" << std::endl;return -1;}// 獲取輸出const auto& output_attrs = backend.GetOutputAttrs();if (output_attrs.empty()) {std::cerr << "No output attributes found" << std::endl;return -1;}const auto& output_memories = backend.GetOutputMemories();if (output_memories.empty() || !output_memories[0]) {std::cerr << "Invalid output memory" << std::endl;return -1;}// 后處理const auto& output_attr = output_attrs[0];std::vector<size_t> output_dims(output_attr.dims, output_attr.dims + output_attr.n_dims);cv::Mat mask = postprocess(output_memories[0], output_dims, image.size());high_resolution_clock::time_point end_time = high_resolution_clock::now();auto time_span = duration_cast<milliseconds>(end_time - start_time);std::cout << "單張圖片推理時間(ms): " << time_span.count() << std::endl;// 生成結果cv::Mat result;cv::bitwise_and(image, image, result, mask);// 保存結果const std::string output_path = "result.jpg";cv::imwrite(output_path, result);std::cout << "Result saved to: " << output_path << std::endl;// 顯示調試視圖cv::imshow("Original", image);cv::imshow("Mask", mask);cv::imshow("Result", result);cv::waitKey(0);return 0;
}
完整代碼可前往我們的倉庫 凌智視覺模塊 查看
📌 總結
你提到的觀點非常有見地,尤其是在嵌入式邊緣計算設備上部署深度學習模型時,性能瓶頸往往并不在 NPU 的算力本身,而是在 CPU 與內存之間的協同效率。下面我們來系統性地分析一下 RV1106 G3 芯片(256MB RAM,1.2GHz 單核 CPU,NPU 算力 1TOPS INT8) 在運行 PP-HumanSeg 人像分割模型時可能遇到的瓶頸。
🧠 RV1106G3 性能瓶頸分析 —— 以 PP-HumanSeg 模型為例
? 芯片參數概覽
參數 | 值 |
---|---|
CPU | 單核 ARM Cortex-A7 1.2GHz |
內存 | 256MB LPDDR4x |
NPU | 1TOPS INT8 算力 |
應用場景 | 邊緣 AI 推理、圖像處理 |
🔍 從硬件架構角度看瓶頸來源
1. CPU 性能限制
- 單核設計 + 主頻 1.2GHz,對于復雜的數據預處理(如 OpenCV 中的 resize、cvtColor、歸一化等操作)、數據搬運、模型輸入輸出管理、后處理(形態學運算、邊緣優化)等任務來說,很容易成為瓶頸。
- 特別是當模型推理速度很快(NPU 加速),但 CPU 處理圖像慢于推理速度時,整體幀率將受限于 CPU 的處理能力。
2. 內存帶寬 & 容量限制
- 僅 256MB 內存,對于圖像進行處理時,容易出現:
- 圖像緩存不足
- 多幀緩沖困難
- 大尺寸模型加載失敗
- 數據頻繁在內存和 NPU 之間搬運,增加訪存開銷,降低整體吞吐。
3. NPU 并非瓶頸
- PP-HumanSeg 是輕量化模型,且支持 INT8 推理,其計算量對 1TOPS 的 NPU 來說完全足夠。
? 標簽:#圖像分割 #PP-HumanSeg #RKNN #OpenCV #C++ #AI部署 #人像分割 #PaddleSeg #邊緣計算
🔚 版權聲明:本文為原創文章,轉載請注明出處。未經許可,禁止轉載。