Open4

OpenCV.jsで魚眼補正

coindarwcoindarw

途中まで作業中のメモから転記

OpenCVのReleasesページからopencv-4.8.0-docs.zipをダウンロード。4.8.1はこのファイルに当たるものがないようなので1つ前のバージョンを使用。
展開して中にあるファイルopencv.jsを持ってくる

https://github.com/opencv/opencv/releases

coindarwcoindarw

チュートリアルを参考に補正行うコードを作成。パラメータは適当な値をハードコードしているだけ

https://docs.opencv.org/4.8.0/d5/d10/tutorial_js_root.html

cv.getOptimalNewCameraMatrixのところでエラーが出てしまう。

どうやらOpenCV.jsのデフォルトビルドにはこの関数が入っていないようなので、カスタムビルドを作成する必要があるらしい。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Fisheye</title>
</head>

<body>
    <h2>Fisheye</h2>
    <p id="status">OpenCV.js is loading...</p>

    <div>
        <table cellpadding="0" cellspacing="0" width="0" border="0">
            <tr>
                <td>
                    <video id="videoInput" width=640 height=480></video>
                </td>
                <td>
                    <canvas id="canvasOutput" width=640 height=480></canvas>
                </td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>
                    <div class="caption">videoInput</div>
                </td>
                <td>
                    <div class="caption">canvasOutput</div>
                </td>
                <td></td>
                <td></td>
            </tr>
        </table>
    </div>

    <script type="text/javascript">
        var Module = {
            onRuntimeInitialized() {
                document.getElementById('status').innerHTML = 'OpenCV.js is ready.';

                let video = document.getElementById('videoInput');
                let src = new cv.Mat(video.height, video.width, cv.CV_8UC4);
                let dst = new cv.Mat(video.height, video.width, cv.CV_8UC1);
                let cap = new cv.VideoCapture(video);

                navigator.mediaDevices.getUserMedia({
                        video: true,
                        audio: false
                    })
                    .then(stream => {
                        video.srcObject = stream;
                        video.play();
                    })
                    .catch(err => {
                        console.log("An error occurred! " + err);
                    });

                const FPS = 30;

                const processVideo = () => {
                    try {
                        const begin = Date.now();
                        cap.read(src);

                        // パラメータ
                        const K = cv.matFromArray(3, 3, cv.CV_64FC1, [350, 0, 250, 0, 350, 150, 0, 0, 1]);
                        const D = cv.matFromArray(1, 5, cv.CV_64FC1, [-0.4, 0.1, 0, 0, 0]);

                        // カメラ行列の計算
                        const newK = cv.getOptimalNewCameraMatrix(K, D, new cv.Size(src.cols, src.rows), 1);

                        let map1 = new cv.Mat(),
                            map2 = new cv.Mat();
                        cv.initUndistortRectifyMap(K, D, new cv.Mat(), newK, new cv.Size(src.cols, src.rows), cv.CV_16SC2, map1, map2);

                        // 魚眼補正
                        cv.remap(src, dst, map1, map2, cv.INTER_LINEAR, cv.BORDER_CONSTANT);

                        cv.imshow('canvasOutput', dst);

                        // OpenCV.jsではnewしたmatはdeleteで明示的に解放する必要がある
                        map1.delete();
                        map2.delete();
                        newK.delete();

                        // 次回実行のスケジュール
                        const delay = 1000 / FPS - (Date.now() - begin);
                        console.log(delay)
                        setTimeout(processVideo, delay);
                    } catch (err) {
                        console.log(err);
                    }
                };

                // 初回実行のスケジュール
                setTimeout(processVideo, 0);

                // 終了時処理
                window.onunload = () => {
                    video.pause();
                    video.srcObject = null;
                    if (video.srcObject) video.srcObject.getVideoTracks()[0].stop();
                    if (src) src.delete();
                    if (dst) dst.delete();
                };
            }
        };
    </script>
    <script async src="opencv.js" type="text/javascript"></script>
</body>

</html>
coindarwcoindarw

OpenCV.jsをデフォルト設定でビルド

cv.getOptimalNewCameraMatrixを含めてビルドを行いたいが、その前にまずはデフォルト設定でビルドできることを確認したい

OpenCV.jsの公式サイトにDockerでビルドする方法が載っているのでこれを実行してみる
https://docs.opencv.org/4.8.0/d4/da1/tutorial_js_setup.html

PowerShell前提のコマンドなのでLinux、macOSの場合は少し変わる。上のチュートリアル参照

git clone https://github.com/opencv/opencv.git
cd opencv
docker run --rm --workdir /src -v "$(get-location):/src" "emscripten/emsdk:2.0.10" emcmake python3 ./platforms/js/build_js.py build_js

ビルドは成功するもののOpenCVの関数を動かそうとすると動かない

調べてみるとemscriptenのバージョンが良くないらしい

https://qiita.com/sdozono/items/a7fd3d33c4e1474caf6e

https://stackoverflow.com/questions/67190799/how-to-include-cv-imread-when-building-opencv-js

とりあえずバージョンを変更して実行すると動いた。

docker run --rm --workdir /src -v "$(get-location):/src" "emscripten/emsdk:1.39.15" emcmake python3 ./platforms/js/build_js.py build_js
coindarwcoindarw

カスタムビルドを作成

あとは必要な関数を含めるようにする。

クローンしたリポジトリの中のopencv\platforms\js\opencv_js.config.pyを変更することで必要なモジュール、関数を絞ってビルドすることができる

今回の場合、190行目当たりのcalib3dのところに必要な関数を追加すればよい

いらない関数は削除した方が軽量化できるが、面倒なのでやっていない

calib3d = {
    "": [
        "findHomography",
        "calibrateCameraExtended",
        "drawFrameAxes",
        "estimateAffine2D",
        "getDefaultNewCameraMatrix",
        "initUndistortRectifyMap",
        "Rodrigues",
        "solvePnP",
        "solvePnPRansac",
        "solvePnPRefineLM",
        "projectPoints",
        "undistort",
        # cv::fisheye namespace
        "fisheye_initUndistortRectifyMap",
        "fisheye_projectPoints",

        "getOptimalNewCameraMatrix", # この行を追加
    ],
}

さっきと同じコマンドを実行すればビルド完了。これで最初のコードが正常に動作した。

docker run --rm --workdir /src -v "$(get-location):/src" "emscripten/emsdk:1.39.15" emcmake python3 ./platforms/js/build_js.py build_js