OpenCV.jsでK-meansするメモ

## はじめに
OpenCVのドキュメントやサンプルって基本的にC++かPythonなのでJavaScriptでやるときにちょっと調べないといけないんですよね。
私はK-meansやるときにちょっと苦戦したので、備忘録としてやり方載せておきます。
本記事ではK-meansについては解説しませんのであしからず。

## FloatをUint8に変換
こうすると若干早くなります。

```js
        // Convert Float to UInt8
        const floatToUint8 = x => Math.min(Math.max(Math.round(x), 0), 255);
        const ui8Centers = new Uint8Array(k * 3);
        for(let c = 0; c < k; c += 1) {
            for(let z = 0; z < 3; z += 1) {
                ui8Centers[c * 3 + z] = floatToUint8(centers.floatAt(c, z));
            }
        }        
        centers.delete();
```

## ソース
自作クラスのstatic メソッドを貼り付けておきます。
```js
    /**
     * Color reduction processing using K-means
     * @param {Mat} src source mat 
     * @param {Mat} dst destination mat
     * @param {number} k cluster count
     * @param {number} attempts number of attempts 
     * @param {number} maxCount the maximum number of iterations/elements
     * @param {number} epsilon the desired accuracy
     * @returns {Mat} destination mat
     */
    static kmeans(src, dst, k, attempts, maxCount = 1e4, epsilon = 1e-4) {
        // Initialize
        dst = dst || new cv.Mat(src.size(), src.type());
        const [labels, centers] = [new cv.Mat(), new cv.Mat()];
        const criteria = new cv.TermCriteria(cv.TermCriteria_EPS + cv.TermCriteria_MAX_ITER, maxCount, epsilon);
        const flags = cv.KMEANS_PP_CENTERS;

        // Create samples
        const samples = new cv.Mat(src.rows * src.cols, 3, cv.CV_32F);
        for(let y = 0; y < src.rows; y += 1) {
            for(let x = 0; x < src.cols; x += 1) {
                const idx = y * src.cols + x;
                for(let z = 0; z < 3; z += 1) {                    
                    samples.floatPtr(idx)[z] = src.data[idx * 4 + z];
                }
            }
        }

        // K-Means method
        let startDate = new Date();
        cv.kmeans(samples, k, labels, criteria, attempts, flags, centers);
        console.log('kmeans:', new Date() - startDate, 'ms');
        
        // Convert Float to UInt8
        const floatToUint8 = x => Math.min(Math.max(Math.round(x), 0), 255);
        const ui8Centers = new Uint8Array(k * 3);
        for(let c = 0; c < k; c += 1) {
            for(let z = 0; z < 3; z += 1) {
                ui8Centers[c * 3 + z] = floatToUint8(centers.floatAt(c, z));
            }
        }        
        centers.delete();
        
        // Create dst from labels and centers
        startDate = new Date();
        for(let y = 0; y < dst.rows; y += 1) {
            for(let x = 0; x < dst.cols; x += 1) {
                const p = (y * dst.cols + x) * 4;
                const c = labels.data[p];
                for(let z = 0; z < 3; z += 1) {                    
                    dst.data[p + z] = ui8Centers[c * 3 + z];
                }
                dst.data[p + 3] = src.data[p + 3];
            }
        }
        labels.delete();
        console.log('after kmeans:', new Date() - startDate, 'ms');
        return dst;
    }

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163605/83dcfecd-57ae-4625-8a51-3736893f18c0.png)

## 参考リンク
<a href="https://answers.opencv.org/question/206557/how-can-you-use-k-means-clustering-to-posterize-an-image-using-opencv-javascript/">How can you use K-Means clustering to posterize an image using opencv javascript?</a>