[画像処理] アルゴリズムから画像処理を知ろう -フィルタ処理編-

# はじめに
前回の画像処理の続編になります。
今回はフィルタ処理を説明していきます。前回より面白いんじゃないかと。

https://qiita.com/T_ksy/items/7cbff8f50890de6316f4

# 動作環境
PC : M4 Macbook Pro(Sequoia 15.1)
コンパイラ : g++
c++ : 17
cmake : 3.31.6
OpenCV : 4.11.0_*
エディタ：VScode

# ソースコード

https://github.com/kiiisy/ips_c_sample

# フィルタ処理とは
フィルタ処理とは入力画像の画素値だけではなく、その周辺の画素値も利用して出力値を求める処理を指します。

$$P_o=\sum_{i=1}^{n} F_iP_i $$
$$P_iは入力画素、P_oは出力画素、F_iはフィルタ$$

:::note info
フィルタのことをカーネル、オペレータとも言う
:::

演算イメージ

![フィルタ処理の演算.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/4049d222-0747-4c7a-9bbf-de8a774d7eeb.png)

上記の演算を全ての画素に対して行い、新しい画素値を求めます。
要は畳み込み演算をしています。
イメージでは3×3のフィルタとしていますが、5×5や9×9などもあります。
その場合の計算方法は全く同じです。ただ単に範囲が広くなるだけです。

演算はシンプルでわかりやすいのですが、1点注意しないといけないことがあります。
境界(端っこの画素)の扱いです。

![境界処理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/14183a43-38fa-489f-85f2-dfccc9c9ddd0.png)

この場合どう処理するかですが、いくつかの手段があります。

<table>
<caption>境界処理の手段
<thead>
<tr><th>方法<th>内容<th>メリット<th>デメリット
<tbody>
<tr><tr><td>リピート<td>端の画素を繰り返し使用<td>計算が簡単<td>若干ボヤけるかも
<tr><tr><td>ミラー<td>端の画素を反射して使用<td>境界が自然に見える<td>計算が増える
<tr><tr><td>ゼロパディング<td>境界を0にする<td>計算が簡単<td>端が黒くなるかも
<tr><tr><td>なにもしない<td>端は処理しない<td>計算が簡単<td>斜線が出るかも
<tr><tr><td>非対称フィルタ(L字/T字)<td>有効な画素のみ使用する形に変える<td>境界が自然に見える<td>計算が複雑
</table>

手軽に実装したいため、今回はリピートにします。

# 画像処理
先ほどの説明でフィルタ処理のイメージはできたかと思いますので、実際に処理していきましょう。

### 平滑化フィルタ
効果：ピンぼけしたような画像になる
フィルタ：以下画像
![平滑化フィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/ed40d4c9-cac7-4f38-8a96-b8c96ee21b90.png)

##### ソースコード
```c++
void ImageProcessor::equalizationFilter(Mat inImg, int32_t height, int32_t width, int32_t filterCoeff, Mat outImg)
{
    int32_t filterSize = (2 * filterCoeff + 1) * (2 * filterCoeff + 1);
    int32_t redSum, greenSum, blueSum;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            redSum = greenSum = blueSum = 0;

            // fiterCoeff x filterCoeffのフィルタ処理
            for (int32_t yy = -filterCoeff; yy <= filterCoeff; yy++) {
                for (int32_t xx = -filterCoeff; xx <= filterCoeff; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値の総和
                    redSum += pix[RED];
                    greenSum += pix[GREEN];
                    blueSum += pix[BLUE];
                }
            }
            // 画素値の平均化
            uint8_t red   = static_cast<uint8_t>(std::clamp(redSum / filterSize, 0, 255));
            uint8_t green = static_cast<uint8_t>(std::clamp(greenSum / filterSize, 0, 255));
            uint8_t blue  = static_cast<uint8_t>(std::clamp(blueSum / filterSize, 0, 255));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue, green, red);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・今回はフィルタサイズを任意にユーザー側で変えられるようにしています
・フィルタ係数は全て1になるため、フィルタを使用せずに純粋に画素値を総和してフィルタサイズで割っています
・範囲外対策としてclamp()関数でガードしています

##### 結果

元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後　※3×3は効果がわかりにくいため5×5にしています
![平滑化フィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/e66f0f82-f909-4fa6-a8d0-5c42df7637e0.png)

### 加重平均フィルタ
効果：元の色(中心画素)をできるだけ残して平滑化を行う
フィルタ：以下画像
![加重平均フィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/f7efea71-91d7-4d89-ae45-ea0a37b6e2e1.png)

##### ソースコード
```c++
void ImageProcessor::weightedAverageFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter[3][3] = {
        {1, 2, 1},
        {2, 8, 2},
        {1, 2, 1}
    };  // 加重平均フィルタ
    int32_t filterSum = std::reduce(&filter[0][0], &filter[0][0] + 3 * 3);
    int32_t redSum, greenSum, blueSum;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            redSum = greenSum = blueSum = 0;

            // 3x3のフィルタ処理(-1から1までの範囲)
            for (int32_t yy = -1; yy <= 1; yy++) {
                for (int32_t xx = -1; xx <= 1; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値とフィルタの積和
                    redSum += filter[yy + 1][xx + 1] * pix[RED];
                    greenSum += filter[yy + 1][xx + 1] * pix[GREEN];
                    blueSum += filter[yy + 1][xx + 1] * pix[BLUE];
                }
            }
            // 画素値の正規化、0~255に収める
            uint8_t red   = static_cast<uint8_t>(std::clamp(redSum / filterSum, 0, 255));
            uint8_t green = static_cast<uint8_t>(std::clamp(greenSum / filterSum, 0, 255));
            uint8_t blue  = static_cast<uint8_t>(std::clamp(blueSum / filterSum, 0, 255));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue, green, red);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・加重平均フィルタ(2次元配列)を使用し、積和演算を行います

##### 結果

元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![加重平均フィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/23ee5bd1-8cbc-4398-8a6c-3a165f3fdb4a.png)

### 先鋭化フィルタ
効果：画像の境界部分(色の変化が激しい場所)を鮮明にする
　　　そのためノイズがある場合はそこも強調されてしまう
フィルタ：以下画像
![先鋭化フィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/6e98e819-0d99-4246-add6-80844d1c7f55.png)

##### ソースコード
```c++
void ImageProcessor::sharpeningFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter[3][3] = {
        {0,  -1, 0 },
        {-1, 5,  -1},
        {0,  -1, 0 }
    };  // 先鋭化フィルタ(4近傍)
    int32_t redSum, greenSum, blueSum;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            redSum = greenSum = blueSum = 0;

            // 3x3のフィルタ処理(-1から1までの範囲)
            for (int32_t yy = -1; yy <= 1; yy++) {
                for (int32_t xx = -1; xx <= 1; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値とフィルタの積和
                    redSum += filter[yy + 1][xx + 1] * pix[RED];
                    greenSum += filter[yy + 1][xx + 1] * pix[GREEN];
                    blueSum += filter[yy + 1][xx + 1] * pix[BLUE];
                }
            }
            // 画像の輝度値を0~255に収める
            uint8_t red   = static_cast<uint8_t>(std::clamp(redSum, 0, 255));
            uint8_t green = static_cast<uint8_t>(std::clamp(greenSum, 0, 255));
            uint8_t blue  = static_cast<uint8_t>(std::clamp(blueSum, 0, 255));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue, green, red);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・フィルタが変わっただけなため特になし

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後(ザラザラとした画像になる)
![先鋭化フィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/c8088cf0-ef17-4bad-9cf9-874d5f8c7829.png)

### エッジ検出フィルタ
ここからはフィルタを使用して画像のエッジ検出をやっていきます。

まず、簡単に画像のエッジについて説明します。
エッジとは一言で言うと、物体の輪郭部分(色の変化が激しい)のことです。

![エッジのイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/4d252d46-1bb1-4c5a-83ea-4d3eb72b014d.png)

この写真の例だと、オレンジ色で線を書いている部分が帽子のエッジになります。
他にも目のエッジや顔のエッジ、体のエッジなど全ての物体にエッジは存在しています。

:::note info
例えば漫画とかも全てエッジで表現していますよね。
特に色もついていないですが、線だけで物体を区切って表現していますよね。
でもそれだけで何かを瞬時に判断できるためエッジはすごく重要な情報になります。
:::

では具体的にどうやってエッジを検出するかですが、**微分**をします。
微分と言いつつ、単なる差分のことです。

フィルタ：縦方向のフィルタと横方向のフィルタを使用します
![エッジ検出フィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/e9154923-0821-4a58-9485-66f773da28a2.png)

上記フィルタは中心画素に対して縦方向と横方向で差分をとっています。
なぜ縦/横の2つのフィルタを使用するのかと言うと、エッジには向きがあります。

![エッジ検出縦横イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/6a8a2bba-ae0a-4c1e-bcd1-9a655a4493c2.png)

※もちろん縦方向のみ、横方向のみで使用することも可能です

##### ソースコード

```c++
void ImageProcessor::edgeDetectionFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter1[3][3] = {
        {0, 0, 0 },
        {1, 0, -1},
        {0, 0, 0 }
    };  // 横フィルタ
    int32_t filter2[3][3] = {
        {0, 1,  0},
        {0, 0,  0},
        {0, -1, 0}
    };  // 縦フィルタ
    int32_t rrx, ggx, bbx;
    int32_t rry, ggy, bby;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            rrx = ggx = bbx = 0;
            rry = ggy = bby = 0;

            // 3x3のフィルタ処理
            for (int32_t yy = 0; yy < FILTER_SIZE; yy++) {
                for (int32_t xx = 0; xx < FILTER_SIZE; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy - 1, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx - 1, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値と横フィルタの積和
                    rrx += pix[RED] * filter1[yy][xx];
                    ggx += pix[GREEN] * filter1[yy][xx];
                    bbx += pix[BLUE] * filter1[yy][xx];

                    // 画素値と縦フィルタの積和
                    rry += pix[RED] * filter2[yy][xx];
                    ggy += pix[GREEN] * filter2[yy][xx];
                    bby += pix[BLUE] * filter2[yy][xx];
                }
            }

            // 画素値の平方根
            double red   = sqrt(static_cast<double>(rrx * rrx + rry * rry));
            double green = sqrt(static_cast<double>(ggx * ggx + ggy * ggy));
            double blue  = sqrt(static_cast<double>(bbx * bbx + bby * bby));

            // 画像の輝度値を0~255に収める
            uint8_t red8   = static_cast<uint8_t>(std::clamp(red, 0., 255.));
            uint8_t green8 = static_cast<uint8_t>(std::clamp(green, 0., 255.));
            uint8_t blue8  = static_cast<uint8_t>(std::clamp(blue, 0., 255.));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue8, green8, red8);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・フィルタが2種類になったが、今まで通りそれぞれの積和を求める
・なぜ平方根をしているかですが、縦方向と横方向の変化量を組み合わせて全体の変化の
　大きさを求めているからです。ここではピタゴラスの定理を使用しています。
　以下の様に考えればわかりやすいかと思います

![エッジ検出の平方根の説明.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/477b1c23-57f4-47f4-be6b-6b2e74c04888.png)

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![エッジ検出.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/100ad61b-d15f-4b16-8e21-289c0417e8b7.png)

### ソーベルフィルタ
効果：画質のノイズを抑えつつ、エッジの検出を行う(水平、垂直方向)
フィルタ：以下画像
![ソーベルフィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/afe11f33-3616-4af7-b1b6-78441b3b99c1.png)

##### ソースコード

```c++
void ImageProcessor::sobelFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter1[3][3] = {
        {1,  2,  1 },
        {0,  0,  0 },
        {-1, -2, -1}
    };  // 横方向のSobelフィルタ
    int32_t filter2[3][3] = {
        {1, 0, -1},
        {2, 0, -2},
        {1, 0, -1}
    };  // 縦方向のSobelフィルタ
    int32_t rrx, ggx, bbx;
    int32_t rry, ggy, bby;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            rrx = ggx = bbx = 0;
            rry = ggy = bby = 0;

            // 3x3のフィルタ処理
            for (int32_t yy = 0; yy < FILTER_SIZE; yy++) {
                for (int32_t xx = 0; xx < FILTER_SIZE; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy - 1, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx - 1, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値と横フィルタの積和
                    rrx += pix[RED] * filter1[yy][xx];
                    ggx += pix[GREEN] * filter1[yy][xx];
                    bbx += pix[BLUE] * filter1[yy][xx];

                    // 画素値と縦フィルタの積和
                    rry += pix[RED] * filter2[yy][xx];
                    ggy += pix[GREEN] * filter2[yy][xx];
                    bby += pix[BLUE] * filter2[yy][xx];
                }
            }

            // 画素値の平方根
            double red   = sqrt(static_cast<double>(rrx * rrx + rry * rry));
            double green = sqrt(static_cast<double>(ggx * ggx + ggy * ggy));
            double blue  = sqrt(static_cast<double>(bbx * bbx + bby * bby));

            // 画像の輝度値を0~255に収める
            uint8_t red8   = static_cast<uint8_t>(std::clamp(red, 0., 255.));
            uint8_t green8 = static_cast<uint8_t>(std::clamp(green, 0., 255.));
            uint8_t blue8  = static_cast<uint8_t>(std::clamp(blue, 0., 255.));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue8, green8, red8);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・フィルタが変わっただけなため特になし

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![ソーベルフィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/36fb7d87-131d-4b77-9c12-dc5f6e940956.png)

### プレウィットフィルタ
効果：ソーベルフィルタをほぼ同じだが、各方向のエッジを均等に検出する
フィルタ：以下画像
![プレウィットフィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/bb2d6155-892a-4670-95ba-44750548aae9.png)

##### ソースコード

```c++
void ImageProcessor::prewittFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter1[3][3] = {
        {1,  1,  1 },
        {0,  0,  0 },
        {-1, -1, -1}
    };  // 横方向のPrewittフィルタ
    int32_t filter2[3][3] = {
        {1, 0, -1},
        {1, 0, -1},
        {1, 0, -1}
    };  // 縦方向のPrewittフィルタ
    int32_t rrx, ggx, bbx;
    int32_t rry, ggy, bby;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            rrx = ggx = bbx = 0;
            rry = ggy = bby = 0;

            // 3x3のフィルタ処理
            for (int32_t yy = 0; yy < FILTER_SIZE; yy++) {
                for (int32_t xx = 0; xx < FILTER_SIZE; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy - 1, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx - 1, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値と横フィルタの積和
                    rrx += pix[RED] * filter1[yy][xx];
                    ggx += pix[GREEN] * filter1[yy][xx];
                    bbx += pix[BLUE] * filter1[yy][xx];

                    // 画素値と縦フィルタの積和
                    rry += pix[RED] * filter2[yy][xx];
                    ggy += pix[GREEN] * filter2[yy][xx];
                    bby += pix[BLUE] * filter2[yy][xx];
                }
            }

            // 画素値の平方根
            double red   = sqrt(static_cast<double>(rrx * rrx + rry * rry));
            double green = sqrt(static_cast<double>(ggx * ggx + ggy * ggy));
            double blue  = sqrt(static_cast<double>(bbx * bbx + bby * bby));

            // 画像の輝度値を0~255に収める
            uint8_t red8   = static_cast<uint8_t>(std::clamp(red, 0., 255.));
            uint8_t green8 = static_cast<uint8_t>(std::clamp(green, 0., 255.));
            uint8_t blue8  = static_cast<uint8_t>(std::clamp(blue, 0., 255.));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue8, green8, red8);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・フィルタが変わっただけなため特になし

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![プレウィットフィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/afc7c22e-4192-4c37-bfbe-9e25f4583a4d.png)

### ロバーツフィルタ
効果：エッジの検出を行う(特に斜め方向の検出)
フィルタ：以下画像
![ロバーツフィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/30f9f7c1-7aa5-4f36-9c6d-7ce413773744.png)


##### ソースコード

```c++
void ImageProcessor::robertsFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter1[3][3] = {
        {0, 0, 0 },
        {0, 1, 0 },
        {0, 0, -1}
    };  // 横方向のRobertsフィルタ
    int32_t filter2[3][3] = {
        {0, 0,  0},
        {0, 0,  1},
        {0, -1, 0}
    };  // 縦方向のRobertsフィルタ
    int32_t rrx, ggx, bbx;
    int32_t rry, ggy, bby;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            rrx = ggx = bbx = 0;
            rry = ggy = bby = 0;

            // 3x3のフィルタ処理
            for (int32_t yy = 0; yy < FILTER_SIZE; yy++) {
                for (int32_t xx = 0; xx < FILTER_SIZE; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy - 1, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx - 1, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値と横フィルタの積和
                    rrx += pix[RED] * filter1[yy][xx];
                    ggx += pix[GREEN] * filter1[yy][xx];
                    bbx += pix[BLUE] * filter1[yy][xx];

                    // 画素値と縦フィルタの積和
                    rry += pix[RED] * filter2[yy][xx];
                    ggy += pix[GREEN] * filter2[yy][xx];
                    bby += pix[BLUE] * filter2[yy][xx];
                }
            }

            // 画素値の平方根
            double red   = sqrt(static_cast<double>(rrx * rrx + rry * rry));
            double green = sqrt(static_cast<double>(ggx * ggx + ggy * ggy));
            double blue  = sqrt(static_cast<double>(bbx * bbx + bby * bby));

            // 画像の輝度値を0~255に収める
            uint8_t red8   = static_cast<uint8_t>(std::clamp(red, 0., 255.));
            uint8_t green8 = static_cast<uint8_t>(std::clamp(green, 0., 255.));
            uint8_t blue8  = static_cast<uint8_t>(std::clamp(blue, 0., 255.));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue8, green8, red8);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・フィルタが変わっただけなため特になし

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![ロバーツフィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/af48c11f-bce3-481f-951c-c4b29a9f02ed.png)

### メディアンフィルタ(ノイズ除去フィルタ)
効果：ごま塩ノイズを除去する
フィルタ：3×3や5×5のフィルタを使用し、画素値を並び替え中央値を採用する
![メディアンフィルタイメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/bfdb01a0-4aa2-4628-880a-055bd2f72cfe.png)

##### ソースコード

```c++
void ImageProcessor::medianFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filterSize = 3;
    int32_t red[9], green[9], blue[9];

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            // 3x3のフィルタ処理(-1から1までの範囲)
            int32_t idx = 0;
            for (int32_t yy = -1; yy <= 1; yy++) {
                for (int32_t xx = -1; xx <= 1; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    red[idx]   = pix[RED];
                    green[idx] = pix[GREEN];
                    blue[idx]  = pix[BLUE];
                    idx++;
                }
            }
            // 画素値のソート
            std::sort(red, red + filterSize * filterSize);
            std::sort(green, green + filterSize * filterSize);
            std::sort(blue, blue + filterSize * filterSize);

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue[4], green[4], red[4]);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・sort()関数を使用し、データを並び替える。並び変えたデータの真ん中の値を出力値に置き換える。

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![メディアンフィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/44e494ca-bba9-414f-97ab-bf088e04dd02.png)

### エンボスフィルタ(おまけ)
効果：エッジを浮き彫りにしたような画像を作る

##### ソースコード

```c++
void ImageProcessor::embossingFilter(Mat inImg, int32_t height, int32_t width, Mat outImg)
{
    int32_t filter[3][3] = {
        {0,  0, 0},
        {-3, 0, 3},
        {0,  0, 0}
    };  // エンボスフィルタ (数値を大きくするとエンボスの強さが増す)
    int32_t redSum, greenSum, blueSum;

    for (int32_t y = 0; y < height; y++) {
        for (int32_t x = 0; x < width; x++) {
            redSum = greenSum = blueSum = 0;

            // 3x3のフィルタ処理(-1から1までの範囲)
            for (int32_t yy = -1; yy <= 1; yy++) {
                for (int32_t xx = -1; xx <= 1; xx++) {
                    // 画像の端の処理 (リピート)
                    int32_t replicateY = std::clamp(y + yy, 0, height - 1);
                    int32_t replicateX = std::clamp(x + xx, 0, width - 1);

                    // 画素取得
                    Vec3b &pix = inImg.at<Vec3b>(replicateY, replicateX);

                    // 画素値とフィルタの積和
                    redSum += filter[yy + 1][xx + 1] * pix[RED];
                    greenSum += filter[yy + 1][xx + 1] * pix[GREEN];
                    blueSum += filter[yy + 1][xx + 1] * pix[BLUE];
                }
            }
            // 画像の輝度値を0~255に収める
            // 128は中間の明るさを示し、6はフィルタの係数の絶対値、それで割ることで画像のコントラストを調整
            uint8_t red   = static_cast<uint8_t>(std::clamp(redSum / 6 + 128, 0, 255));
            uint8_t green = static_cast<uint8_t>(std::clamp(greenSum / 6 + 128, 0, 255));
            uint8_t blue  = static_cast<uint8_t>(std::clamp(blueSum / 6 + 128, 0, 255));

            // 画素の書き込み
            outImg.at<Vec3b>(y, x) = Vec3b(blue, green, red);
        }
    }
}
```

##### ソースコードの説明(ポイントだけ)
・フィルタが変わっただけなため特になし

##### 結果
元画像
![元画像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8a91370c-f542-4d1c-80da-565db4322f63.png)

処理後
![エンボスフィルタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/ae44344c-68cf-4ba3-b776-f053df6328a4.png)

# 最後に
以上でフィルタ処理編を終わります。

また時間があれば勉強して次もなにか書きますね。