🌟

[環境構築]Dockerで、WebAssemblyのOpenCV,C++,CMakeの開発環境を構築してみた。

2024/01/17に公開

Abstract

WebAssemblyのOpenCVってなかなか情報がない!! そんな中、DockerでWebAssemblyのOpenCV,C++,CMakeの開発環境を構築してみた。
ここ(やっぱりWasm は C++!!!~Wasm/EmscriptenでOpenCVを使う~)がすごい参考になった。

前提

1.作業フォルダ作成

Ubuntuで、適当にフォルダを新規作成。

$ cd ~ && mkdir wasm1st && cd wasm1st

2.先に必要ファイルを作成する。(CMakeLists.txt,Dockerfile,index.html,src/main.cpp)

2-1.CMakeLists.txtを作成する。

CMakeLists.txt
$ vi CMakeLists.txt
CMakeLists.txtの中身
cmake_minimum_required(VERSION 3.5)
project(cppmain LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s USE_ZLIB=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0 --bind -O3 -s LLD_REPORT_UNDEFINED -s DEMANGLE_SUPPORT=1")

include_directories(/build/opencv/build_wasm/install/include/opencv4)

file(GLOB_RECURSE sources1 "src/main.cpp")
add_executable(${PROJECT_NAME} ${sources1})

file(GLOB opencv_core "/build/opencv/build_wasm/install/lib/*.a")
file(GLOB opencv_3rdparty "/build/opencv/build_wasm/install/lib/opencv4/3rdparty/*.a")
target_link_libraries(${PROJECT_NAME} ${opencv_core} ${opencv_3rdparty})

2-2.main.cppを作成する。

CMakeLists.txt
$ mkdir src && cd src
$ vi main.cpp
main.cppの中身
#include <SDL/SDL.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <opencv2/opencv.hpp>

namespace {

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

} // namespace

extern "C" int main(int argc, char **argv) {

  SDL_Init(SDL_INIT_VIDEO);
  screen = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_SWSURFACE);
  return 0;
}

void doOpenCvTask(size_t addr, int width, int height, int cnt) {
  auto data = reinterpret_cast<void *>(addr);
  cv::Mat rgbaMat(height, width, CV_8UC4, data);
  cv::Mat rgbMat;
  cv::Mat rgbOutMat;
  cv::Mat outMat;
  cv::cvtColor(rgbaMat, rgbMat, cv::COLOR_RGBA2RGB);
  rgbMat.convertTo(rgbOutMat, -1, 1.0, cnt - 100.0);
  cv::cvtColor(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);
}

EMSCRIPTEN_BINDINGS(my_module) {
  emscripten::function("doOpenCvTask", &doOpenCvTask);
}

2-3.index.htmlを作成する。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>

<body>
  <h1>By Wasm OpenCV</h1>
  <video id="video"></video>
  <canvas id="canvas" width="640" height="480"></canvas>
  <script>
const init = async () => {
  const video = document.getElementById("video");
  video.width = 640;
  video.height = 480;
  const stream = await navigator.mediaDevices.getUserMedia({
    audio: false,
    video: {
      width: { ideal: video.width },
      height: { ideal: video.height },
    },
  });
  video.srcObject = stream;
  video.play();

  const videoCanvas = document.getElementById("canvas");
  videoCanvas.width = video.width;
  videoCanvas.height = video.height;
  const context = videoCanvas.getContext("2d");
  let cnt = 0;
  const loopNum =200;
  let sumTime = 0.0;

  const updateCanvas = () => {
    context.drawImage(video, 0, 0);
    const data = context.getImageData(
      0,
      0,
      videoCanvas.width,
      videoCanvas.height
    );

    const start1 = performance.now();
    const buffer = Module._malloc(data.data.length);
    Module.HEAPU8.set(data.data, buffer);
    Module.doOpenCvTask(buffer, data.width, data.height, cnt);
    Module._free(buffer);
    const end1 = performance.now();
    cnt++;
    sumTime += end1 - start1;
    if(cnt == loopNum) {
      console.log(`doOpenCvTask+Memory: ${sumTime / 100} msec (${loopNum} times avg)`);
      sumTime = 0;
      cnt = 0;
    }

    requestAnimationFrame(updateCanvas);
  };
  updateCanvas();
};

window.Module = {
  canvas: document.getElementById("canvas"),
  onRuntimeInitialized: init,
  // print: function(text) { console.log(text); },
  print: function(text){},
  printErr: function(text) { console.log(text); }
};
  </script>
  <script src="dist/build/cppmain.js"></script>
</body>
</html>

2-4.Dockerfileを作成する。

Dockerfile
$ vi Dockerfile
Dockerfileの中身
FROM emscripten/emsdk:1.39.16
WORKDIR /build/app
COPY CMakeLists.txt ./
COPY src ./src
COPY index.html ./
SHELL ["/bin/bash", "-c"]

この段階のフォルダ構成

$ ls -l ~/wasm1st
-rw-rw-r-- 1 jun jun  670  117 19:44 CMakeLists.txt
-rw-rw-r-- 1 jun jun  113  117 20:11 Dockerfile
-rw-rw-r-- 1 jun jun  113  117 20:11 index.html
drwxrwxr-x 2 jun jun 4096  117 19:47 src

4.Dockerビルド

cd ~/wasm1stで移動して、

Dockerビルド
$ docker build -t aaa:000 .
$ docker images
aaa                                    000       d41496b35747   3 years ago   1.41GB

5.Docker起動

Docker起動
$ docker run --name bbb111 -it aaa:000 /bin/bash

6.OpenCVビルド

OpenCVビルド
$ cd /build
$ git clone https://github.com/opencv/opencv.git -b 4.5.0 --depth 1
$ git clone https://github.com/opencv/opencv_contrib.git -b 4.5.0 --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=/build/opencv/platforms/js/opencv_js.config.py 
$ cd build_wasm && emmake make -j && emmake make install

7.自作srcをビルド

自作srcをビルド
$ cd /build/app
$ mkdir dist && cd dist && mkdir build && cd build
$ emcmake cmake ../..
$ emmake make

出来た!!
/build/app配下のindex.htmlと、dist/build/cppmain.jsと、dist/build/cppmain.wasmを、どこかのサーバに置けば動くはず!!

カメラの設定して、

起動して、

ブラウザからアクセスする。

Discussion