【画像処理解説】Maxプーリング（Javaソース付き）

#はじめに
画像処理を簡単に解説していきます。
下部にはJavaでのソースコードも載せているのでぜひご参照ください。
不定期にはなりますが、随時更新していく予定です。

#Maxプーリングとは？
前回説明したモザイク処理の一種です。
具体的には、ブロックごとの代表値にブロックの最大値を採用する方法です。
詳しい説明は以下の記事を参照してください。

https://qiita.com/Sigmay/items/9cf8a73525ad2772159a

#アルゴリズム
1. 入力画像をラスタスキャンし、2から5の処理を行う。ただし、移動幅をブロックサイズとする。
2. 平均値計算用の変数を初期化する。
3. ブロックの中をラスタスキャンし、各画素に4の処理を行う。
4. 3のインデックスが画像からはみ出していない場合、画素値を取得し、2の最大値変数と比較して最大値を更新する。
5. ブロックの中をラスタスキャンし、各画素に5で取得した値をセットする。

#Maxプーリング処理結果の例
元画像（カラー画像）
![imori_256x256.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655420/3254b128-8a72-03d3-b380-f01a00e08ace.png)


結果画像（モザイク画像）
![out008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655420/0c47a221-a9f5-1486-2585-1f3f841d5a7d.png)


#ソースコード

```Mosaic.java
public static SImage maxPooling(SImage oriImg, int ksize) {
    // カーネルサイズが偶数なら奇数にする
    if (ksize % 2 == 0)
        ksize++;

    // グレースケール画像の場合
    if (oriImg.channel == 1) {
        // モザイク画像用を生成
        var mosaicImg = new SImage(oriImg.width(), oriImg.height(), 1);

        // 画像をラスタスキャン（カーネルサイズ単位で移動）
        for (int y = ksize / 2; y < oriImg.height() + ksize / 2; y += ksize)
            for (int x = ksize / 2; x < oriImg.width() + ksize / 2; x += ksize) {
                // 最大値用変数の初期化
                int max = 0;

                // カーネル範囲をスキャン
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        // 画像内である場合のみ集計
                        if (x + dx < oriImg.width() && y + dy < oriImg.height())
                            max = Math.max(max, oriImg.getGray(x + dx, y + dy));
                    }

                // カーネル範囲を再スキャン
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        // 画像内である場合のみ最大値をセット
                        if (x + dx < oriImg.width() && y + dy < oriImg.height())
                            mosaicImg.setGray(x + dx, y + dy, max);
                    }
            }
        return mosaicImg;

        // RGBカラー画像の場合
    } else if (oriImg.channel == 3) {
        // モザイク画像用を生成
        var mosaicImg = new SImage(oriImg.width(), oriImg.height(), 3);

        // 画像をラスタスキャン（カーネルサイズ単位で移動）
        for (int y = ksize / 2; y < oriImg.height() + ksize / 2; y += ksize)
            for (int x = ksize / 2; x < oriImg.width() + ksize / 2; x += ksize) {
                // 最大値用変数の初期化
                int[] max = new int[3];

                // カーネル範囲をスキャン
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        // 画像内である場合のみ集計
                        if (x + dx < oriImg.width() && y + dy < oriImg.height()) {
                            int[] rgb = oriImg.getRGB(x + dx, y + dy);
                            for (int i = 0; i < 3; i++)
                                max[i] = Math.max(max[i], rgb[i]);
                        }
                    }

                // カーネル範囲を再スキャン
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        // 画像内である場合のみ平均値をセット
                        if (x + dx < oriImg.width() && y + dy < oriImg.height())
                            mosaicImg.setRGB(x + dx, y + dy, max);
                    }
            }
        return mosaicImg;

        // アルファチャネルを持つ場合
    } else {
        var mosaicImg = new SImage(oriImg.width(), oriImg.height(), 4);

        for (int y = ksize / 2; y < oriImg.height() + ksize / 2; y += ksize)
            for (int x = ksize / 2; x < oriImg.width() + ksize / 2; x += ksize) {
                int[] max = new int[4];

                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        if (x + dx < oriImg.width() && y + dy < oriImg.height()) {
                            int[] rgb = oriImg.getARGB(x + dx, y + dy);
                            for (int i = 0; i < 4; i++)
                                max[i] = Math.max(max[i], rgb[i]);
                        }
                    }

                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        if (x + dx < oriImg.width() && y + dy < oriImg.height())
                            mosaicImg.setARGB(x + dx, y + dy, max);
                    }
            }
        return mosaicImg;
    }
}
```