【C言語】平均化フィルタによるPGM画像の平滑化

<!--
:::note alert
コードに問題があります．修正をお待ちください．
:::
-->


# はじめに

C言語の学習として，グレースケール画像に対して平均化フィルタによる平滑化処理を行う課題が出た.
平均化フィルタのプログラムは，他のフィルタ処理にも応用が利くため残しておきたい．

# 平均化フィルタとは

平均化フィルタとは，周囲の画素の平均をとるフィルタのことである．
画素の値を周囲の画素の平均にすることによって，周囲の画素との値のギャップが減り，結果的に画像がぼやけたような画像になる．

画像 $A = [a_{ij}]$ から画像 $B = [b_{ij}]$ が得られるとすると，
```math
b_{ij} = \frac{1}{9} \{ a_{ij} + (a_{i-1j-1} + a_{ij-1} + a_{i+1j-1} + a_{i+1j} + a_{i+1j+1} + a_{ij+1} + a_{i-1j+1} + a_{i-1j}) \}
```
( )で囲われた部分が，周囲の画素である．

# 【コード】C言語で平均化フィルタを実現する
以下は，自分なりに作成した最終版である(いただいたコメントを参考に修正しました).

```c: averagingFilter.c
    /*
   
    ・
    ・ 画像を読み込む処理
    ・

    */

    /*
    ！画像の画素は，int型2次元配列image[ySize][xSize]で参照できるものとする．
    */    
    int temp[xSize];

    for (int y = 0; y < ySize; ++y)
    {
        int saveresult[xSize];

        for (int x = 0; x < xSize; ++x)
        {
            int sum = image[y][x];
            int numOfNeighbors = 1;

            for (int i = 0; i < 8; ++i)
            {
                //周囲の画素を左下から反時計回りにチェック
                int neighbors[8][2] = {
                    {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}};
                int nextRow = y + neighbors[i][0];
                int nextCol = x + neighbors[i][1];

                //参照する値が，画素の座標として取りうる値かチェック
                if (nextRow >= 0 && nextRow < ySize && nextCol >= 0 && nextCol < xSize)
                {
                    sum += image[nextRow][nextCol];
                    numOfNeighbors++;
                }
            }
            saveresult[x] = sum / numOfNeighbors;
        }

        int lastLineTrue = 0;
        
        //1行目の計算が終わったときのみスキップされる
        if (y > 0)
        {
            //1行手前に, tempに格納しておいた計算結果を代入
            for (int x = 0; x < xSize; ++x)
            {
                image[y - 1][x] = temp[x];
            }

            //最終行は計算結果をすぐ代入
            if (y == ySize-1)
            {
                for (int x = 0; x < xSize; ++x)
                {
                    image[y][x] = saveresult[x];
                    lastLineTrue = 1;
                }
            }
        }

        //最終行はこの処理は要らないためスキップ
        if (!lastLineTrue)
        {
            for (int x = 0; x < xSize; ++x)
            {
                temp[x] = saveresult[x];
            }
        }
    }

    /* 
    
    ・
    ・ 画像を出力する処理
    ・
    
    */
```

## 説明

一行の端から端まで計算を行い，終わったら次の行へ...というプログラムになっている．

どの画素に対しても周囲を参照するように書かれているため，取りうる値かどうかのチェックが必要．例えば，左上の画素(image[0][0])の周囲には画素が存在しない部分がある．そこを参照しようとするとエラーが起こる．

平均の計算には**元の値**を用いるため，1行目の計算が終わった段階で1行目に結果を代入してしまうと，2行目の計算で1行目を参照する際に元の値が参照できなくなる．それを防ぐために，$n$行目の計算結果は,$n+1$行目の計算が終わった段階で代入されるようになっている．最終行はこの処理では行えないため，別で記述している．

このプログラムでは，画素が存在しない部分は加算しない($0$とみなす)ようにしているが，
そもそも画像の端点は計算を行わないというやり方や，

https://jp.mathworks.com/help/curvefit/smoothing-data.html

周りに同じ画像を並べた画像に対して処理を行い，最後に真ん中の画像を参照することで，画素が存在しない部分を補う方法もあるらしい．

https://qiita.com/ikeponsu/items/ccc346e08335a29a55e7

平均化フィルタ以外にも，周囲の画素の値を用いて計算を行うフィルタ処理はいくつかある. 例えば，平均化フィルタ以外の平滑化フィルタ[^1] や，エッジ検出にも用いられる．

それらのプログラムは，このプログラムの計算部分を変えるだけで実現することができる．

## 2次元配列に格納ではダメなのか

image[ySize][xSize]と同じ大きさの2次元配列をもう一つ定義して，そこに計算結果を代入していくのではダメなのか．

結論から言うと，ダメではない．ただ，ローカル配列として定義しようとするとスタック・オーバーフローとなる．

課題で与えられたPGM画像は960×1280で，ローカル配列として2つ確保するには大きすぎたため，コアダンプが出力された．

https://qiita.com/ryo_manba/items/f5354835859957527aff


課題に取り組む段階でこのコアダンプの原因が分からず悩まされた．そのため，1次元配列を用いて無理やり実現することにした．

2次元配列を用いたプログラムは，計算結果を格納するだけのシンプルなコードである．しかし，さんざん苦しんで作ったのであえて1次元配列を用いた方を最終版として残したい．

# 関連
pタイル法による2値化にも取り組みました.

https://qiita.com/Shi-haMoon/items/17fd15e720586b1eaa8a

[^1]: ガウシアンフィルタ，メディアンフィルタ等...