2023/05/13

macOS Build OpenCV 4.7.0 to WebAssembly

還沒安裝Emscripten可以先查閱macOS 安裝 Emscripten 3.1.39,配置好後就開始編譯吧
git clone https://github.com/opencv/opencv.git
emcmake python ./opencv/platforms/js/build_js.py build_wasm --build_wasm
cd ./build_wasm/bin/ && open .
接著打開loader.js會如下:

async function loadOpenCV(paths, onloadCallback) {
    let OPENCV_URL = "";
    let asmPath = "";
    let wasmPath = "";
    let simdPath = "";
    let threadsPath = "";
    let threadsSimdPath = "";

    if(!(paths instanceof Object)) {
        throw new Error("The first input should be a object that points the path to the OpenCV.js");
    }

    if ("asm" in paths) {
        asmPath = paths["asm"];
    }

    if ("wasm" in paths) {
        wasmPath = paths["wasm"];
    }

    if ("threads" in paths) {
        threadsPath = paths["threads"];
    }

    if ("simd" in paths) {
        simdPath = paths["simd"];
    }

    if ("threadsSimd" in paths) {
        threadsSimdPath = paths["threadsSimd"];
    }

    let wasmSupported = !(typeof WebAssembly === 'undefined');
    if (!wasmSupported && OPENCV_URL === "" && asmPath != "") {
        OPENCV_URL = asmPath;
        console.log("The OpenCV.js for Asm.js is loaded now");
    } else if (!wasmSupported && asmPath == ""){
        throw new Error("The browser supports the Asm.js only, but the path of OpenCV.js for Asm.js is empty");
    }

    let simdSupported = wasmSupported ? await wasmFeatureDetect.simd() : false;
    let threadsSupported = wasmSupported ? await wasmFeatureDetect.threads() : false;

    if (simdSupported && threadsSupported && threadsSimdPath != "") {
        OPENCV_URL = threadsSimdPath;
        console.log("The OpenCV.js with simd and threads optimization is loaded now");
    } else if (simdSupported && simdPath != "") {
        if (threadsSupported && threadsSimdPath === "") {
            console.log("The browser supports simd and threads, but the path of OpenCV.js with simd and threads optimization is empty");
        }
        OPENCV_URL = simdPath;
        console.log("The OpenCV.js with simd optimization is loaded now.");
    } else if (threadsSupported && threadsPath != "") {
        if (simdSupported && threadsSimdPath === "") {
            console.log("The browser supports simd and threads, but the path of OpenCV.js with simd and threads optimization is empty");
        }
        OPENCV_URL = threadsPath;
        console.log("The OpenCV.js with threads optimization is loaded now");
    } else if (wasmSupported && wasmPath != "") {
        if(simdSupported && threadsSupported) {
            console.log("The browser supports simd and threads, but the path of OpenCV.js with simd and threads optimization is empty");
        }

        if (simdSupported) {
            console.log("The browser supports simd optimization, but the path of OpenCV.js with simd optimization is empty");
        }

        if (threadsSupported) {
            console.log("The browser supports threads optimization, but the path of OpenCV.js with threads optimization is empty");
        }

        OPENCV_URL = wasmPath;
        console.log("The OpenCV.js for wasm is loaded now");
    } else if (wasmSupported) {
        console.log("The browser supports wasm, but the path of OpenCV.js for wasm is empty");
    }

    if (OPENCV_URL === "") {
        throw new Error("No available OpenCV.js, please check your paths");
    }

    let script = document.createElement('script');
    script.setAttribute('async', '');
    script.setAttribute('type', 'text/javascript');
    script.addEventListener('load', () => {
        onloadCallback();
    });
    script.addEventListener('error', () => {
        console.log('Failed to load opencv.js');
    });
    script.src = OPENCV_URL;
    let node = document.getElementsByTagName('script')[0];
    if (node.src != OPENCV_URL) {
        node.parentNode.insertBefore(script, node);
    }
}

接著修改為為如下代碼:
async function loadOpenCV(onloadCallback) {
    const OPENCV_URL = './opencv.js'

    let script = document.createElement('script');
    script.setAttribute('async', '');
    script.setAttribute('type', 'text/javascript');
    script.addEventListener('load', async () => {
        if (cv.getBuildInformation) {
            onloadCallback()
        } else {
            if (cv instanceof Promise) {
                cv = await cv
                console.log(cv.getBuildInformation())
                onloadCallback()
            } else {
                cv['onRuntimeInitialized'] = () => {
                    onloadCallback()
                }
            }
        }
        onloadCallback();

    });
    script.addEventListener('error', () => {
        console.log('Failed to load opencv.js')
    });
    script.src = OPENCV_URL;
    let node = document.getElementsByTagName('script')[0]
    if (node.src !== OPENCV_URL) {
        node.parentNode.insertBefore(script, node)
    }
}

OpenCV的Hello world當然非圖片轉灰階莫屬,所以接著將示範代碼複製到bin資料夾的index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Opencv 4.7.0 to WebAssembly by Follow Fang</title>
    <script src="./loader.js"></script>
    <script>
        let isLoadCV = false
        let originImg
        let grayImg
        const reader = new FileReader()

        async function onLoad() {
            await loadOpenCV(loadCV)
        }

        const loadCV = () => {
            isLoadCV = true
            originImg = document.getElementById('originImg')
            grayImg = document.getElementById('grayImg')
            originImg.onload = () => imgLoad()
        }

        const imgLoad = () => {
            const src = cv.imread(originImg)
            const gray = new cv.Mat()
            cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY)
            cv.imshow('grayImg', gray)
            src.delete()
        }

        const uploadImage = (e) => reader.readAsDataURL(e.target.files[0]);

        reader.onload = () => {
            originImg.src = reader.result
        }


    </script>
</head>
<body onload="onLoad()">
<div>
    <input type="file" accept="image/*" onchange="uploadImage(event)"><br>upload images<br>
    <img id="originImg" alt=""/>
</div>
<canvas id="grayImg" alt=""/>
</div>
</body>
</html>

效果圖如下:



參考資料:
Build OpenCV.js