opencv.jsでcharuco(aruco マーカーチェッカーボード)を検知してみる

opencv.js は OpenCV を JavaScript で使用できるようにしたものです。
今回はこの opencv.js で
aruco マーカーとチェッカーボードの合体である[charucoボード](https://docs.opencv.org/4.8.0/df/d4a/tutorial_charuco_detection.html)を検知してみようと思います。

index.html, index.js および board3.jpg をこのページから、opencv.js を公式からダウンロードしてきて
同じ場所に配置し、index.html から実行できます。

[opencv.js 4.8.0](https://docs.opencv.org/4.8.0/opencv.js)

2023年12月21日現在、4.8.0 で実行できています。

```index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>charuco</title>
        <style>canvas { max-height: 95dvh; }</style>
    </head>
    <body>
        <canvas id="result"></canvas>
        <script src="index.js"></script>
        <script async src="opencv.js"></script>
    </body>
</html>
```

```index.js
const act = (image) => {
    const dict = cv.getPredefinedDictionary(cv.DICT_6X6_1000);
    const charucoParam = new cv.aruco_CharucoParameters();
    const detectParam = new cv.aruco_DetectorParameters();
    const refineParam = new cv.aruco_RefineParameters(10, 3, true);

    const squareNum = new cv.Size(5, 7);
    const squareLength = 34.7 * 0.001;
    const markerLength = squareLength * 120 / 200;
    const ids = new cv.Mat();
    const board = new cv.aruco_CharucoBoard(squareNum, squareLength, markerLength, dict, ids);
    const multiDetector = new cv.aruco_CharucoDetector(board, charucoParam, detectParam, refineParam);

    const src = cv.imread(image);

    const rgb = new cv.Mat();
    cv.cvtColor(src, rgb, cv.COLOR_RGBA2RGB, 0);
// ボードの検出
    const charucoCorners = new cv.Mat();
    const charucoCornerIds = new cv.Mat();   
    const markerCorners = new cv.MatVector();
    const markerIds = new cv.Mat();            
    multiDetector.detectBoard(rgb, charucoCorners, charucoCornerIds, markerCorners, markerIds);

    cv.drawDetectedMarkers(rgb, markerCorners, markerIds);
// チェックコーナーを描画する
    const col = new cv.Scalar(255, 128, 0);
    cv.drawDetectedCornersCharuco(rgb, charucoCorners, charucoCornerIds, col);
// カメラキャリブレーション
    const obj3ds = new cv.MatVector();
    const foundNum = charucoCornerIds.rows;
    const corner3d = new cv.Mat(foundNum, 1, cv.CV_32FC3);
    for (let i = 0; i < foundNum; ++i) {
        const index = charucoCornerIds.intPtr(i, 0)[0];
        let x = 1 + (index % (squareNum.width - 1));
        let y = 1 + Math.floor(index / (squareNum.width - 1));

        const cell = corner3d.floatPtr(i, 0);
        cell[0] = x * 2 - squareNum.width;
        cell[1] = y * 2 - squareNum.height;
        cell[2] = 0;
    }
    obj3ds.push_back(corner3d); // ビュー1個だけ

    const obj2ds = new cv.MatVector();
    obj2ds.push_back(charucoCorners);

    const size = new cv.Size(rgb.cols, rgb.rows);

    const cameraMatrix = new cv.Mat();
    const distCoeffs = new cv.Mat();
    const rvecs = new cv.MatVector();
    const tvecs = new cv.MatVector();
    const intrinsics = new cv.Mat();
    const extrinsics = new cv.Mat();
    const errs = new cv.Mat();
    cv.calibrateCameraExtended(obj3ds, obj2ds, size,
        cameraMatrix, distCoeffs, rvecs, tvecs, intrinsics, extrinsics, errs);
// キャリブレーション結果で原点に軸を描画
    const index = 0;
    cv.drawFrameAxes(rgb, cameraMatrix, distCoeffs, rvecs.get(index), tvecs.get(index), 3);
// 可視化
    cv.imshow('result', rgb);
};

var Module = {
    onRuntimeInitialized: () => {
        const img = new Image();
        img.addEventListener('load', () => {
            act(img);
        });
        img.src = 'board3.jpg';
    }
};
```

|board3.jpg|
|:-:|
|![board3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/276bcd86-3e55-4c77-767f-8999d4432046.jpeg)|
|[公式ページ](https://docs.opencv.org/4.8.0/df/d4a/tutorial_charuco_detection.html)の Image with Charuco board 画像でも構いません|


|実行結果|
|:-:|
|![result5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/51344593-d9a4-190a-d0fe-e69e4fa52766.png)|
|なぜかX軸とZ軸の色が青と赤に…|


# 補足など

rvec から回転行列を得るには cv.Rodrigues 関数を使用するようです。

```js
    const rot = new cv.Mat(); // 変換後回転行列の受け取り先
    cv.Rodrigues(rvecs.get(0), rot);
```

# リンク

- cv.calibrateCameraExtended
https://docs.opencv.org/4.8.0/d9/d0c/group__calib3d.html#ga3207604e4b1a1758aa66acb6ed5aa65d
結果の受け取りデータの説明有り。