[開發環境紀錄] 使用 NCNN + OpenCV 建置 WebAssembly

在 上一篇 文章中分享了透過別人建置好的 WebAssembly 來進行真假人臉辨識,想說趁此機會順便了解一下 WebAssembly 的開發方式。

由於我是使用 Mac 在訓練深度學習的模型,因此也順理成章的使用 Mac 來進行開發環境的設置,但........... 真沒想到過程異常艱辛,解了一個問題又多出另外一個新的問題,於是只好狠下心來改用 Ubuntu 重頭來過,也因此有了這篇血淚記錄文(執行結果如下圖)。

另外因為牽扯技術過多,本文將不會深入探討各項技術的原理,僅記錄環境的設定過程,對於相關技術有興趣的人,可參閱文末參考網站的連結。


在開始詳細說明之前,先列出最終完成時的開發環境版本:

  • Ubuntu : 24.04 ,作業系統。
  • Python : 3.12.3,使用 OS 內建版本。
  • Cmake : 3.28.3,C++ 編譯器。
  • Emscripten : 3.1.64,用來將 C++ 程式編譯成 WebAssembly 用。
  • OpenCV: 4.10.0,影像處理套件。


環境設定及建置

安裝 Emscripten

WebAssembly,簡稱 WASM,是一種可以讓 C / C++ / C# 等程式語言所產生出來程式(二進制檔案)透過 JavaScript 來進行互動的一種技術,而 Emscripten 則是協助將 C++ 轉譯成 wasm 的一個 toolchain,簡單的運作原理如下圖所示。

圖片來源 : https://juejin.cn/post/7291856546815361064

安裝步驟如下,

  • 安裝並啟用 Emscripten,此步驟僅須執行一次。
    git clone https://github.com/emscripten-core/emsdk.git 
    cd emsdk 
    ./emsdk install latest 
    ./emsdk activate latest
    
  • 設定環境變數,每次啟動一個新的 Console 視窗都必須先執行此命令(因為沒有設定 Path)。
    emsdk_env.bat
    


安裝 OpenCV

OpenCV 用於開發即時的圖像處理、電腦視覺以及圖型識別的程式,但請注意,就是這裡卡超久、超雷,因為我們要建置成 WebAssembly,所以請不要使用一般網路上教學的建置 OpenCV 的步驟,安裝建置步驟如下,

  • 下載 Open CV Source Code。
    # 安裝更新會使用到的相關套件
    sudo apt update && sudo apt install -y cmake g++ wget unzip
    
    # 直接下載 github 上的版本
    git clone https://github.com/opencv/opencv.git
    
    # 下載封存的開發版本(我用的方式)
    wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip
    unzip opencv.zip
    
  • 很重要、很重要、很重要,建置支援 WebAssembly 版的 OpenCV,這個步驟需要一點時間,完成後會在 Source Code 相同目錄下產生 build_wasm 資料夾,相關參數請參考 Build OpenCV.js 說明。
    emcmake python3 ./opencv+4.x/platforms/js/build_js.py build_wasm --build_wasm
  • (不需要) 底下這段是原本建置 OpenCV 的指令,請注意,如果要建置支援 WebAssembly 版本,請不要使用此方法,完成後會在 Source Code 相同目錄下產生 build 資料夾。
    mkdir -p build
    cmake  ../opencv-4.x
    cmake --build .
    


安裝 Ncnn

Ncnn,這個框架是西台灣大神開發的,是一個針對行動平台最佳化的高效能神經網路推理框架,它的核心概念是利用 C++ 程式,讓手機使用 CPU 來進行卷積神經網路(CNN)的推論,其中有一個蠻厲害的人將這個框架成功移植到 WebAssembly 上,讓網頁也能進行神經網路推論。

在他的 程式碼範例 中清楚的說明建置的步驟,但先說結論,Ncnn 框架提供了一個簡單的 opencv 方法的類別庫 simpleocv.h,因此其實不需要安裝 OpenCV 也可以執行範例,理論上造著做應該可以成功建置出一個 WebAssembly,但因為秉持著好奇心的關係,想要試著自己調整看看,於是開了一個新的 C++ 程式,引入了 #include "opencv2/opencv.hpp",接著就開始一連串的錯誤。

PS. 我沒有深入研究作者範例中提供的 ncnn-20230223-webassembly 的差異是什麼,但若你依照本文上述的步驟,請改下載成最新版本 ncnn-20240410-webassembly


建置範例程式

專案路徑如下參考

UserHome
|__ Project
   |__ Lib
   |  |__ emsdk
   |  |__ ncnn-20240410-webassembly
   |  |__ opencv_setup
   |     |__ build_wasm (手動建置)
   |     |__ opencv-4.x
   |__ WebAssemblyDemo

為了測試 WebAssembly 的開發流程,我寫了一個簡單的加法程式來進行初步測試(下圖所示),但為了避免範例過於複雜,我另外引入了 OpenCV 和 NCNN 類別庫,這主要是為了測試整體能否順利通過建置,而非進行 OpenCV 和 NCNN 的實際開發。

  • 範例程式 main.cpp 

#include 
#include "net.h" // 測試是否可以使用 ncnn
#include "opencv2/opencv.hpp" // 測試是否可以使用 OpenCV

int main()
{
    printf("預設的 main Method\n");
    return 0;
}
// 故意寫一個 add Method 來測試是否可以被編譯器保留
EM_PORT_API(int) add(int a, int b) 
{
    int sum = a + b;
    printf("The sum of %d and %d is: %d\n", a, b, sum);
    return sum;
}
// 故意寫一個不會被編譯器保留的 helloWorld Method
void helloWorld() 
{
    printf("Hello World\n");
}

  • CMakeList.txt

# 設定建置專案所需的最低 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 定義專案名稱和版本
project(wasm-demo VERSION 1.0)

# 設定建置類型為 release 模式
set(CMAKE_BUILD_TYPE release)

# 指定 C++ 標準版本
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 設定 OpenCV 函式庫的路徑,這是為 WebAssembly 建置的版本
set(OpenCV_DIR "~/Project/Lib/opencv_setup/build_wasm")
find_package(OpenCV REQUIRED)

# 輸出 OpenCV 函式庫的詳細資訊作為狀態訊息。
message(STATUS "OpenCV library: ${OpenCV_INSTALL_PATH}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

# 如果需要進行除錯,取消下面這行的註解,讓建置過程在此處失敗。
# message(FATAL_ERROR "DEBUG...")

# 設定 ncnn 函式庫的路徑,這是為 WebAssembly 建置的版本
set(ncnn_DIR "~/Project/Lib/ncnn-20240410-webassembly/${WASM_FEATURE}/lib/cmake/ncnn")
find_package(ncnn REQUIRED)

# 增加需要編譯的 C++ 檔案
add_executable(${PROJECT_NAME} main.cpp)

# 指定輸出文件名稱,放置於 "dest" 目錄中,並根據 WASM_FEATURE 命名
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ../dest/${PROJECT_NAME}-${WASM_FEATURE})

# 鏈結 OpenCV 函式庫
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})

# 鏈結 ncnn 函式庫
target_link_libraries(${PROJECT_NAME} ncnn)

  • 切到專案路徑後,執行底下指令建置 WebAssembly,底下 -j6 請根據自己的處理器的數量自行調整。

mkdir build && cd build

cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DWASM_FEATURE=simd ..  

make -j6


疑難雜症紀錄

1. cmake 使用 Emscripten.cmake 的 toolchain 建置 OpenCV 引發的錯誤訊息 執行命令

cmake -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DWASM_FEATURE=simd .. && make -j6

網路上查到 的兩個解決方式

  • 我用這個沒用,sudo apt-get install libopencv-dev 
  • 自行編譯安裝,set(OpenCV_DIR {OpenCVConfig.cmake所在目錄})


2. 上一個問題改用自行編譯處理完後,引發另外一個錯誤。

這個就是我卡住的地方,還有網友也有熱心的提供類似的經驗,但我這個時候還沒能發現是要另外建置 OpenCV WebAssembly 版本 Orz... ,浪費了超多時間滴。


3. cmake 同時使用 OpenCV + Ncnn 時出現底下錯誤

解決方式,原本使用的是 ncnn-20230223-webassembly,改下載成最新版本 ncnn-20240410-webassembly。


4. cmake 使用 Emscripten.cmake 的toolchain建置 OpenCV+ NCNN引發的錯誤訊息


解決方式,原本使用的是 ncnn-20230223-webassembly,改下載成最新版本 ncnn-20240410-webassembly。


本文範例可自行到 Github 下載。

參考網站

Emscripten & WebAssembly

NCNN

Yolov8

這裡也記錄一下,我的 Yolo 是在 Mac 環境上進行訓練了,該環境 Python 版本為 3.11。

留言