🔖

[環境構築]React+TypeScriptでWebAssembly004。WindowsでC++開発->Ubuntuでwasmビルド。

2024/01/21に公開

<- React+TypeScriptでWebAssembly003
                      React+TypeScriptでWebAssembly005 ->


Abstract


開発の主体はVMを使わないWindowsで、WebAssemblyビルドの時にVMのUbuntuを使う開発環境を構築してみた。
ホントは、Windowsだけで完結したかったとけど、OpenCVのWebAssemblyビルドができんかった~。

Windows編

バージョン
$ opencv_version
4.9.0-dev
$ cmake --version
cmake version 3.28.1
$ emcc
emccは未インストール

結論

今回の成果物はココ↓
https://github.com/aaaa1597/cppwasm_template

前提

Windows側のC++ビルド環境構築

Windowsで。

OpenCVインストール

1.OpenCVのConfigure実行

  • opencvインストールフォルダ作成
opencvフォルダ作成。
$ mkdir d:\apps\opencv && cd d:\apps\opencv
  • opencvとopencv_contribをダウンロード
opencvとopencv_contribをダウンロード
$ curl -Lo opencv4.zip https://github.com/opencv/opencv/archive/4.x.zip
$ curl -Lo opencv_contrib4.zip https://github.com/opencv/opencv_contrib/archive/4.x.zip
  • opencv4.zip,opencv_contrib4.zipを解凍。
opencvフォルダ確認
$ dir D:\apps\opencv
~略~
2024/01/19  22:53    <DIR>          opencv-4.x
2024/01/20  18:18        97,473,020 opencv4.zip
2024/01/20  05:13    <DIR>          opencv_contrib-4.x
2024/01/20  18:19        62,105,890 opencv_contrib4.zip
~略~
  • CMake GUI起動
    (ココ見とくと幸せになれます。)
    Where is the source code: D:\apps\opencv\opencv-4.x
    Where to build the binaries: D:\apps\opencv\opencv-4.x\build
    → Configure押下
    Optilnal platform for generator: Win32
    Finish押下

          ↓

          ↓
    この中から、"OPENCV_EXTRA_MODULE_PATH"を頑張って探して設定。
    OPENCV_EXTRA_MODULE_PATH : D:\apps\opencv\opencv_contrib-4.x\modules
    // "BUILD_opencv_world"を頑張って探して設定。
    // BUILD_opencv_world: レ点
    → Configureをも一回押下
    Configuring done
          ↓

    Generate押下
          ↓

    Generating done
          ↓
    閉じる。

2. OpenCVをVisual Studio 2022でビルド

OpenCV.sln をダブルクリックで開く。

      ↓
OpenCVの中をデバッグするつもりはなので、ReleaseとWin32を選んで、ALL_BUILDを選ぶ。

      ↓
ビルド。15分ぐらいかな~。

ビルドエラーなし。OpenCVすばらしい~。
      ↓
INSTALL -> ビルドを選ぶ。

      ↓
ビルド。

ビルドエラーなし。さすがOpenCV。
      ↓
ビルド完了。閉じる。

3. 環境変数Pathに設定。

以下のパスを環境変数Pathに追加する。

  • D:\apps\opencv\opencv-4.x\build\install
  • D:\apps\opencv\opencv-4.x\build\install\include
  • D:\apps\opencv\opencv-4.x\build\install\x86\vc17\bin
    ※D:\apps\opencv\opencv-4.xの部分は自身の環境に読み替えてね。

4. 再起動

環境変数をいじったのでシステムに反映させるために再起動する。

5. プロジェクトフォルダとソース一式を作成

  • プロジェクトフォルダ作成
cppwasm_templateフォルダを作成。
mkdir cppwasm_template && cd cppwasm_template
  • cppフォルダ作成
cppフォルダを作成。
mkdir cpp && cd cpp
  • CMakeLists.txtを作成
CMakeLists.txtの中身
cmake_minimum_required(VERSION 3.5)
project(OpenCVExample)

set(CMAKE_CXX_STANDARD 17)

# OpenCVのパッケージを見つける
find_package(OpenCV REQUIRED)

# #OpenCV関係のインクルードディレクトリのパスを設定
# include_directories( ${OpenCV_INCLUDE_DIRS} )

# ソースファイルを指定
set(SOURCES ../src/ifcpp.cpp ../src/MainProcess.cpp)

# 実行可能ファイルをビルド
add_executable(OpenCVExample ${SOURCES})

message('----------------s')
message(${SOURCES})
message(${OpenCV_LIBS})
message('----------------e')

# OpenCVをリンク
target_link_libraries(OpenCVExample ${OpenCV_LIBS})
  • ifcpp.cppを作成
    ifcpp.cppは、Windowsでのみ動作するコードを実装する。
ifcpp.cppから
 MainProcess.cpを呼び出す構成
ifcpp.cppの中身
#include <opencv2/opencv.hpp>
#include "../src/MainProcess.h"

int main(int argc, char *argv[]) {
  /* 画像の読み込み */
  cv::Mat inputImage = cv::imread("../../input.jpg");

  /* 画像が正しく読み込まれたかを確認 */
  if (inputImage.empty()) {
    std::cerr << "画像を読み込めませんでした" << std::endl;
    return -1;
  }

  /* 画像処理 */
  cv::Mat grayscaleImage;
  ConvertColor(inputImage, grayscaleImage);

  /* 処理画像を表示 */
  cv::imshow("Grayscale Image", grayscaleImage);

  /* キー入力待ち */
  cv::waitKey(0);

  /* ウィンドウを閉じる */
  cv::destroyAllWindows();
  return 0;
}
srcフォルダを作成。
cd ..
mkdir src && cd src
  • MainProcess.hを作成
MainProcess.hの中身
#include <opencv2/opencv.hpp>

void ConvertColor(const cv::Mat &inmat, cv::Mat &outmat);
  • MainProcess.cppを作成
    メイン処理をMainProcess.cppに書き、ifcpp.cpp/ifwasm.cppの両方から呼ばれる様に構成する。
MainProcess.cppはifcpp.cpp/ifwasm.cppの両方から呼ばれる。
メイン処理をMainProcess.cppに書く。
MainProcess.cppの中身
#include <opencv2/opencv.hpp>

void ConvertColor(const cv::Mat &inmat, cv::Mat &outmat) {
  cv::cvtColor(inmat, outmat, cv::COLOR_BGR2GRAY);
  return;
}
フォルダ構成
cppwasm_template
 ├ cpp
 |  ├ CMakeLists.txt   # windowsC++開発用のCMakeLists.txt,VSCodeで開く
 |  ├ input.jpg        # お試し画像
 |  └ ifcpp.cpp        # windows側で動くコード。MainProcess.cppのコードを呼ぶ。
 └ src
     ├ MainProcess.cpp  # メイン処理。windows/weasmの両方から呼ばれる。
     └ MainProcess.h    # メイン処理のヘッダ。

6. VSCodeでプロジェクトフォルダを開く


VSCodeの拡張機能C/C++、CMake Toolsを入れてなければ入れておく。
      ↓

  • C/C++: Edit Configurations(JSON)を選択
    Ctrl + Shift + P -> "C/C++: Edi"まで入力と出てくる。

          ↓
    c_cpp_properties.jsonが生成される。ついでに右下にビルド/デバッグ/実行ボタンも表示される。

7. CMakeの設定

Ctrl + Shift + P -> "CMake: Configure"を選択。
X86を選ぶ。(WebAssemblyは32bitなので。)

8. インクルードパスを追加

ifcpp.cppに戻ると、赤波線でエラーになっている。

      ↓
手順3でビルドしたパスを設定。
D:\apps\opencv\opencv-4.x\build\install\include

      ↓
赤波線が消えている。

9. 実行

ブレークポイントを設定して、カブト虫ボタンを押す。

      ↓
いろいろ動き出す。

      ↓
ちょっと待つと、ブレークポイントで止まる。

      ↓
再開。

出来た!!




Ubuntu編

windows側で開発したコードをwasmビルドして、ブラウザで動作確認する。

バージョン
$ opencv_version
4.9.0
$ cmake --version
cmake version 3.28.20240119-g62ca955
$ emcc(=WebAssembly) --version
emcc ~略~ 3.1.52 

前提

cmakeインストール
$ git clone https://github.com/Kitware/CMake.git
$ cd CMake && ./bootstrap && make && sudo make install

Ubuntu側のwasmビルド

1.Emscripten のインストール

C++をEmscriptenにビルドするのにEmscriptenが必要。なのでインストールする。
参考 : Emscripten Download and install

Emscripten のインストール
cd ~
git clone https://github.com/emscripten-core/emsdk.git # gitから取得
cd emsdk                                               # emsdk移動
git pull                                               # 最新化
./emsdk install latest                                 # インストール
./emsdk activate latest                                # make
# source ./emsdk_env.sh                                # 環境変数設定
echo 'source "/home/jun/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile
 # 環境変数設定の永続化

一旦、ログアウト -> ログイン

2.OpenCVビルド

OpenCVビルド
$ mkdir ~/tmp-opencv && cd ~/tmp-opencv
$ mkdir build && cd build
$ git clone https://github.com/opencv/opencv.git --depth 1
### git clone https://github.com/opencv/opencv_contrib.git --depth 1
$ cd opencv
$ python3 ./platforms/js/build_js.py build_wasm --build_wasm --emscripten_dir=../../../emsdk/upstream/emscripten --config_only
### --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=build/opencv_contrib/"
$ export OPENCV_JS_WHITELIST=~/tmp-opencv/build/opencv/platforms/js/opencv_js.config.py
$ cd build_wasm
$ emmake make -j
$ emmake sudo make install
#        ↑↑↑ sudo付けんと file cannot create directory:~のエラーになるけん。

3.テンプレートのソースコード一式を取得

git取得
$ cd ~
$ git clone https://github.com/aaaa1597/cppwasm_template.git
$ cd cppwasm_template/wasm
  • ifwasm.cppを作成
    ifwasm.cppは、WebAssembly依存部分を実装する。
    JavaScriptからカメラ画像を受け取って、MainProcess.cppに渡す役目とか。
    ログをconsole.logに出力する機能とか。
    WebAssemblyとしてビルドするのはこっちの構成になる。
ifwasm.cppから
 MainProcess.cpを呼び出す構成
wasmフォルダを作成。
$ cd ~/cppwasm_template/
$ mkdir wasm
$ cd ~/cppwasm_template/wasm
ifwasm.cppの中身
#include <stdlib.h>
#include <SDL/SDL.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <opencv2/opencv.hpp>
#include <opencv2/opencv.hpp>
#include "../src/MainProcess.h"

namespace {

constexpr int WIDTH = 640;
constexpr int HEIGHT = 480;
SDL_Surface *screen = nullptr;

} // namespace

#define LOG_OUTPUT 0

#if LOG_OUTPUT
EM_JS(int, console_log, (const char *logstr), {
  console.log('aaaaa ' + UTF8ToString(logstr));
  return 0;
});
#else
#define console_log(logstr)
#endif

extern "C" int main(int argc, char **argv) {
  console_log(__PRETTY_FUNCTION__);
  SDL_Init(SDL_INIT_VIDEO);
  screen = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_SWSURFACE);

  return 0;
}

extern "C" {
size_t EMSCRIPTEN_KEEPALIVE creata_buffer(int size) {
  console_log(__PRETTY_FUNCTION__);
  return (size_t)malloc(size * sizeof(uint8_t));
}

void EMSCRIPTEN_KEEPALIVE destroy_buffer(size_t p) {
  console_log(__PRETTY_FUNCTION__);
  void *pbuf = (void*)p;
  free(pbuf);
}

void EMSCRIPTEN_KEEPALIVE Convert(size_t addr, int width, int height, int cnt) {
  console_log(__PRETTY_FUNCTION__);
  auto data = reinterpret_cast<void *>(addr);
  cv::Mat rgbaMat(height, width, CV_8UC4, data);
  cv::Mat rgbMat;
  cv::Mat rgbOutMat;
  cv::Mat outMat;
  ConvertColor(rgbaMat, rgbMat, cv::COLOR_RGBA2RGB);
  rgbMat.convertTo(rgbOutMat, -1, 1.0, cnt - 100.0);
  ConvertColor(rgbOutMat, outMat, cv::COLOR_RGB2RGBA);

  if (SDL_MUSTLOCK(screen))
    SDL_LockSurface(screen);
  cv::Mat dstRGBAImage(height, width, CV_8UC4, screen->pixels);
  outMat.copyTo(dstRGBAImage);
  if (SDL_MUSTLOCK(screen))
    SDL_UnlockSurface(screen);
  SDL_Flip(screen);
}
} /* extern "C" */

EMSCRIPTEN_BINDINGS(my_module) {
  emscripten::function("convert", &Convert);
  emscripten::function("creatabuffer", &creata_buffer);
  emscripten::function("destroybuffer", &destroy_buffer);
}

4.ソースコードをビルド

srcをビルド
$ cd ~/cppwasm_template/wasm
$ mkdir build && cd build
$ emcmake cmake ..
$ emmake make
$ mv cppmain.js ..
$ mv cppmain.wasm ..
ls -l
$ ls -l ~/cppwasm_template/wasm
-rw-rw-r-- 1 jun jun    618  120 14:29 CMakeLists.txt
drwxrwxr-x 3 jun jun   4096  120 15:52 build
-rw-rw-r-- 1 jun jun 182218  120 15:52 cppmain.js   # ← これが出来た!!
-rwxrwxr-x 1 jun jun 587651  120 15:52 cppmain.wasm # ← これが出来た!!
-rw-rw-r-- 1 jun jun   1936  120 15:13 index.html

index.htmlと、出力されたcppmain.js、cppmain.wasmを、どこかのサーバに置けば動く!!


で、動かす。

5.Webカメラの設定(内蔵カメラならこの手順は不要)

6.サーバ起動

サーバ起動
$ cd ~/cppwasm_template/wasm
$ python3 -m http.server 8080

7.サーバにアクセス。

ブラウザから http://localhost:8080/ にアクセスする。


出来た!!
これで、Ubuntu側のwasmビルドも出来た!!

Windowsで開発して、Ubuntuでwasmビルドと動作確認の環境が出来た!!
ふぃ~。お疲れ様でした。
ハマったな~。


<- React+TypeScriptでWebAssembly003
                      React+TypeScriptでWebAssembly005 ->

Discussion