画像処理画像処理
フィルタ; Filter

概要
フィルタは画像処理を行う際の変換である。ピクセルごとに行う色フィルタと、ピクセルの周辺領域を参照する空間フィルタがある。
空間フィルタは重み付けが非負の平滑フィルタ、重み付けに正と負を含む差分フィルタ、複数ピクセルの最大値、最小値、中央値などを採用する統計フィルタにさらに分類される。

色フィルタ
座標\(x, y\)におけるピクセル\(g_{x, y}\)の変換後のピクセル\({g'}_{x, y}\)は以下で与えられる。

\(\quad \displaystyle {g'}_{x y} = \Phi( g_{x y} ) \)

空間フィルタ
空間フィルタは重み\(w_{i j}\)とピクセル\(g_{x y}\)の重み付け平均を行う。

フィルタサイズ2の空間フィルタ
\(\quad \displaystyle \boldsymbol{w} = \begin{bmatrix} w_{-2 \ -2} & w_{-1 \ -2} & w_{0 \ -2} & w_{+1 \ -2} & w_{+2 \ -2} \\ w_{-2 \ -1} & w_{-1 \ -1} & w_{0 \ -1} & w_{+1 \ -1} & w_{+2 \ -1} \\ w_{-2 \ 0} & w_{-1 \ 0} & w_{0 \ 0} & w_{+1 \ 0} & w_{+2 \ 0} \\ w_{-2 \ +1} & w_{-1 \ +1} & w_{0 \ +1} & w_{+1 \ +1} & w_{+2 \ +1} \\ w_{-2 \ +2} & w_{-1 \ +2} & w_{0 \ +2} & w_{+1 \ +2} & w_{+2 \ +2} \end{bmatrix}\)

なお、領域外の重みは0として扱う。
\( \quad s_{x y} = \begin{cases} 1 \quad (x, y) \ is \ InArea \\ 0 \quad (x, y) \ isnot \ InArea \end{cases} \)

平滑フィルタ
座標\(x, y\)におけるピクセル\(g_{x y}\)の変換後のピクセル\({g'}_{x y}\)は以下で与えられる。

\(\quad \displaystyle {g'}_{x y} = \frac{ s_{x+i \ y+j} \cdot w_{i j} \cdot g_{x y}}{ \displaystyle \sum_{i j} s_{x+i \ y+j} \cdot w_{i j} } \)

領域外を考慮しない場合は以下の式で与えられる。
\(\quad \displaystyle {g'}_{x y} = \frac{ w_{i j} \cdot g_{x y}}{ \displaystyle \sum_{i j} w_{i j} } \)

差分フィルタ
座標\(x, y\)におけるピクセル\(g_{x y}\)の変換後のピクセル\({g'}_{x y}\)は以下で与えられる。

\(\quad \displaystyle {g'}_{x y} = \left| \displaystyle \sum_{i j} w^{+}_{i j} \right| \frac{ s_{x+i \ y+j} \cdot w^{+}_{i j} \cdot g_{x y}}{ \displaystyle \sum_{i j} s_{x+i \ y+j} \cdot w^{+}_{i j} } + \left| \displaystyle \sum_{i j} w^{-}_{i j} \right| \frac{ s_{x+i \ y+j} \cdot w^{-}_{i j} \cdot g_{x y}}{ \displaystyle \sum_{i j} s_{x+i \ y+j} \cdot w^{-}_{i j} } \)

\(\quad \displaystyle w^{+}_{i j} = \begin{cases} w_{i j} & \quad w_{i j} \gt 0 \\ 0 & \quad w_{i j} \leq 0 \end{cases} \)
\(\quad \displaystyle w^{-}_{i j} = \begin{cases} w_{i j} & \quad w_{i j} \lt 0 \\ 0 & \quad w_{i j} \geq 0 \end{cases} \)

領域外を考慮しない場合は以下の式で与えられる。
\(\quad \displaystyle {g'}_{x y} = w_{i j} \cdot g_{x y} \)

フィルタリング ソースコード

namespace ColorField {

    /// <summary>フィルタリング</summary>
    public static class Filtering {
        
        /// <summary>重み平均</summary>
        public static RGB WeightedAverage(GraphicRGB graphic, int x, int y, ValueSpace v) {
            int w = graphic.Width, h = graphic.Height;
            RGB[,] graph = graphic.Graph;

            RGB sum = RGB.Zero;
            double div = 0;

            int min_x = Math.Max(-x, -v.Size), max_x = Math.Min(w - x - 1, v.Size);
            int min_y = Math.Max(-y, -v.Size), max_y = Math.Min(h - y - 1, v.Size);
                        
            for(int dx, dy = min_y, px, py = y + dy; dy <= max_y; dy++, py++) {
                for(dx = min_x, px = x + dx; dx <= max_x; dx++, px++) {
                    sum += graph[px, py] * v[dx, dy];
                    div += v[dx, dy];
                }
            }

            return (div > 0) ? (sum / div) : graph[x, y];
        }

        /// <summary>色フィルタ</summary>
        public static GraphicRGB ColorFiltering(GraphicRGB graphic, Func<RGB, RGB> filter_func) {
            int w = graphic.Width, h = graphic.Height;
            GraphicRGB new_graphic = new GraphicRGB(w, h);
            RGB[,] graph = graphic.Graph, new_graph = new_graphic.Graph;

            for(int x, y = 0; y < h; y++) {
                for(x = 0; x < w; x++) {
                    new_graph[x, y] = filter_func(graph[x, y]);
                }
            }

            return new_graphic;
        }

        /// <summary>平滑フィルタ</summary>
        public static GraphicRGB SmoothFiltering(GraphicRGB graphic, FilterAverage filter, bool is_apply_alpha = false) {
            int w = graphic.Width, h = graphic.Height;
            GraphicRGB new_graphic = new GraphicRGB(w, h);
            RGB[,] graph = graphic.Graph, new_graph = new_graphic.Graph;

            int size = filter.Size;

            for(int x, y = 0; y < h; y++) {
                for(x = 0; x < w; x++) {
                    if(x >= size && x < w - size && y >= size && y < h - size) {
                        RGB sum = RGB.Zero;

                        for(int dx, dy = -size, px, py = y + dy; dy <= size; dy++, py++) {
                            for(dx = -size, px = x + dx; dx <= size; dx++, px++) {
                                sum += graph[px, py] * filter[dx, dy];
                            }
                        }

                        new_graph[x, y] = sum;
                    }
                    else {
                        RGB sum = RGB.Zero;
                        double div = 0;

                        int min_x = Math.Max(-x, -size), max_x = Math.Min(w - x - 1, size);
                        int min_y = Math.Max(-y, -size), max_y = Math.Min(h - y - 1, size);
                        
                        for(int dx, dy = min_y, px, py = y + dy; dy <= max_y; dy++, py++) {
                            for(dx = min_x, px = x + dx; dx <= max_x; dx++, px++) {
                                sum += graph[px, py] * filter[dx, dy];
                                div += filter[dx, dy];
                            }
                        }

                        new_graph[x, y] = (div > 0) ? (sum / div) : graph[x, y];
                    }
                }
            }

            return new_graphic;
        }

        /// <summary>平滑フィルタ(色距離重み付き)</summary>
        public static GraphicRGB SmoothWithColorWeightFiltering(GraphicRGB graphic, FilterAverage filter, Func<double, double> crdist_weight, bool is_apply_alpha = false) {
            int w = graphic.Width, h = graphic.Height;
            GraphicRGB new_graphic = new GraphicRGB(w, h);
            RGB[,] graph = graphic.Graph, new_graph = new_graphic.Graph;

            int size = filter.Size;

            for(int x, y = 0; y < h; y++) {
                for(x = 0; x < w; x++) {
                    RGB c1 = graph[x, y];
                    double sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
                    double div_r = 0, div_g = 0, div_b = 0, div_a = 0;

                    int min_x = Math.Max(-x, -size), max_x = Math.Min(w - x - 1, size);
                    int min_y = Math.Max(-y, -size), max_y = Math.Min(h - y - 1, size);
                        
                    for(int dx, dy = min_y, px, py = y + dy; dy <= max_y; dy++, py++) {
                        for(dx = min_x, px = x + dx; dx <= max_x; dx++, px++) {
                            double weight;
                            RGB c2 = graph[px, py], dc = c1 - c2;
                            
                            weight = filter[dx, dy] * crdist_weight(dc.R * dc.R);
                            sum_r += weight * c2.R;
                            div_r += weight;
                            
                            weight = filter[dx, dy] * crdist_weight(dc.G * dc.G);
                            sum_g += weight * c2.G;
                            div_g += weight;
                            
                            weight = filter[dx, dy] * crdist_weight(dc.B * dc.B);
                            sum_b += weight * c2.B;
                            div_b += weight;

                            if(is_apply_alpha) {
                                weight = filter[dx, dy] * crdist_weight(dc.A * dc.A);
                                sum_a += weight * c2.A;
                                div_a += weight;
                            }
                        }
                    }

                    if(is_apply_alpha) {
                        new_graph[x, y] = new RGB(sum_r / div_r, sum_g / div_g, sum_b / div_b, sum_a / div_a);
                    }
                    else {
                        new_graph[x, y] = new RGB(sum_r / div_r, sum_g / div_g, sum_b / div_b, graph[x, y].A);
                    }
                }
            }

            if(!is_apply_alpha) {
                for(int x, y = 0; y < h; y++) {
                    for(x = 0; x < w; x++) {
                        new_graph[x, y].A = graph[x, y].A;
                    }
                }
            }

            return new_graphic;
        }

        /// <summary>差分フィルタ</summary>
        public static GraphicRGB SubtractFiltering(GraphicRGB graphic, FilterSubtractive filter, bool is_apply_alpha = false) {
            int w = graphic.Width, h = graphic.Height;
            GraphicRGB new_graphic = new GraphicRGB(w, h);
            RGB[,] graph = graphic.Graph, new_graph = new_graphic.Graph;

            int size = filter.Size;

            for(int x, y = 0; y < h; y++) {
                for(x = 0; x < w; x++) {
                    if(x >= size && x < w - size && y >= size && y < h - size) {
                        RGB sum = RGB.Zero;

                        for(int dx, dy = -size, px, py = y + dy; dy <= size; dy++, py++) {
                            for(dx = -size, px = x + dx; dx <= size; dx++, px++) {
                                sum += graph[px, py] * filter[dx, dy];
                            }
                        }

                        new_graph[x, y] = sum;
                    }
                    else {
                        RGB sum_positive = RGB.Zero, sum_negative = RGB.Zero;
                        double div_positive = 0, div_negative = 0;

                        int min_x = Math.Max(-x, -size), max_x = Math.Min(w - x - 1, size);
                        int min_y = Math.Max(-y, -size), max_y = Math.Min(h - y - 1, size);
                                                
                        for(int dx, dy = min_y, px, py = y + dy; dy <= max_y; dy++, py++) {
                            for(dx = min_x, px = x + dx; dx <= max_x; dx++, px++) {
                                if(filter[dx, dy] > 0) {
                                    sum_positive += graph[px, py] * filter[dx, dy];
                                    div_positive += filter[dx, dy];
                                    continue;
                                }
                                if(filter[dx, dy] < 0) {
                                    sum_negative += graph[px, py] * filter[dx, dy];
                                    div_negative -= filter[dx, dy];
                                    continue;
                                }
                            }
                        }

                        if(div_positive > 0 && div_negative > 0) {
                            new_graph[x, y] = sum_positive / div_positive * filter.PlusSum + sum_negative / div_negative * filter.MinusSum;
                        }
                        else {
                            new_graph[x, y] = graph[x, y] * (filter.PlusSum - filter.MinusSum);
                        }
                    }
                }
            }

            if(!is_apply_alpha) {
                for(int x, y = 0; y < h; y++) {
                    for(x = 0; x < w; x++) {
                        new_graph[x, y].A = graph[x, y].A;
                    }
                }
            }

            return new_graphic;
        }
    }
}

空間フィルタ ソースコード

namespace ColorField {

    /// <summary>空間フィルタ基本クラス</summary>
    public abstract class Filter : ValueSpace, ICloneable {

        /// <summary>コンストラクタ</summary>
        public Filter(int size, double[,] filter) : base(size, filter) {
        }

        /// <summary>コンストラクタ</summary>
        public Filter(ValueSpace value_space) : this(value_space.Size, value_space.ValueTable) {
        }

        /// <summary>コンストラクタ</summary>
        public Filter(int size, Func<int, int, double> filter_func) : base(size) {
            for(int dx, dy = -size; dy <= size; dy++) {
                for(dx = -size; dx <= size; dx++) {
                    this[dx, dy] = filter_func(dx, dy);
                }
            }
        }
    }

    /// <summary>平均フィルタ</summary>
    public class FilterAverage : Filter {

        /// <summary>コンストラクタ</summary>
        public FilterAverage(ValueSpace value_space) : base(value_space) {
            Validate();
            Normalize();
        }

        /// <summary>コンストラクタ</summary>
        public FilterAverage(int size, double[,] filter) : base(size, filter) {
            Validate();
            Normalize();
        }

        /// <summary>コンストラクタ</summary>
        public FilterAverage(int size, Func<int, int, double> filter_func) : base(size, filter_func) {
            Validate();
            Normalize();
        }

        /// <summary>有効か判定</summary>
        protected void Validate() {
            for(int dx, dy = -Size; dy <= Size; dy++) {
                for(dx = -Size; dx <= Size; dx++) {
                    if(this[dx, dy] < 0) {
                        throw new ArgumentException();
                    }
                }
            }
        }

        /// <summary>正規化</summary>
        protected void Normalize() {
            double sum = 0, inv_sum;

            for(int dx, dy = -Size; dy <= Size; dy++) {
                for(dx = -Size; dx <= Size; dx++) {
                    sum += this[dx, dy];
                }
            }

            if(!(sum > 0)) {
                throw new DivideByZeroException();
            }

            inv_sum = 1.0 / sum;

            for(int dx, dy = -Size; dy <= Size; dy++) {
                for(dx = -Size; dx <= Size; dx++) {
                    this[dx, dy] *= inv_sum;
                }
            }
        }

        /// <summary>インデクサ</summary>
        public override double this[int dx, int dy] {
            get {
                return ValueTable[Size + dx, Size + dy];
            }
        }

        /// <summary>クローン</summary>
        public override object Clone() {
            FilterAverage f = new FilterAverage(Size, ValueTable);
            return f;
        }
    }

    /// <summary>差分フィルタ</summary>
    public class FilterSubtractive : Filter {
        
        /// <summary>コンストラクタ</summary>
        public FilterSubtractive(ValueSpace value_space) : base(value_space) {
            CountNormalizeConst();
        }

        /// <summary>コンストラクタ</summary>
        public FilterSubtractive(int size, double[,] filter) : base(size, filter) {
            CountNormalizeConst();
        }

        /// <summary>コンストラクタ</summary>
        public FilterSubtractive(int size, Func<int, int, double> filter_func) : base(size, filter_func) {
            CountNormalizeConst();
        }

        /// <summary>正の重みの和</summary>
        public double PlusSum {
            get; private set;
        }

        /// <summary>負の重みの和</summary>
        public double MinusSum {
            get; private set;
        }

        /// <summary>重みの和を計算</summary>
        private void CountNormalizeConst() {
            double plus_sum = 0, minus_sum = 0;

            for(int dx, dy = -Size; dy <= Size; dy++) {
                for(dx = -Size; dx <= Size; dx++) {
                    if(this[dx, dy] > 0) {
                        plus_sum += this[dx, dy];
                        continue;
                    }
                    if(this[dx, dy] < 0) {
                        minus_sum -= this[dx, dy];
                        continue;
                    }
                }
            }

            PlusSum = plus_sum;
            MinusSum = minus_sum;
        }

        /// <summary>インデクサ</summary>
        public override double this[int dx, int dy] {
            get {
                return ValueTable[Size + dx, Size + dy];
            }
        }
    }
}


値空間 ソースコード

namespace ColorField {

    /// <summary>値空間</summary>
    public class ValueSpace : ICloneable{
        #region Constructor

        /// <summary>コンストラクタ</summary>
        public ValueSpace(int size) {
            this.Size = size;
            this.ValueTable = new double[2 * size + 1, 2 * size + 1];
        }

        /// <summary>コンストラクタ</summary>
        public ValueSpace(int size, double[,] ValueTable) {
            if(ValueTable.GetLength(0) != ValueTable.GetLength(1) || ValueTable.GetLength(0) != 2 * size + 1) {
                throw new ArgumentException(nameof(ValueTable));
            }

            this.Size = size;
            this.ValueTable = (double[,])ValueTable.Clone();
        }

        /// <summary>コンストラクタ</summary>
        public ValueSpace(int size, Func<int, int, double> func) : this(size){
            for(int dx, dy = -Size; dy <= Size; dy++) {
                for(dx = -Size; dx <= Size; dx++) {
                    this[dx, dy] = func(dx, dy);
                }
            }
        }

        #endregion


        #region Property

        /// <summary>サイズ</summary>
        public int Size {
            get; private set;
        }

        /// <summary>値</summary>
        public double[,] ValueTable {
            get; private set;
        }

        /// <summary>配列サイズ</summary>
        public int TableSize => ValueTable.GetLength(0);

        #endregion


        #region Indexer

        /// <summary>インデクサ</summary>
        public virtual double this[int dx, int dy] {
            get {
                return ValueTable[Size + dx, Size + dy];
            }

            set {
                ValueTable[Size + dx, Size + dy] = value;
            }
        }

        #endregion


        #region Operators

        /// <summary>要素ごとの積</summary>
        public static ValueSpace operator *(ValueSpace v1, ValueSpace v2) {
            if(v1.TableSize != v2.TableSize) {
                throw new ArgumentException("Mismatch Size");
            }

            int table_size = v1.TableSize;

            var new_ValueTable = new double[table_size, table_size];

            for(int x, y = 0; y < table_size; y++) {
                for(x = 0; x < table_size; x++) {
                    new_ValueTable[x, y] = v1.ValueTable[x, y] * v2.ValueTable[x, y];
                }
            }

            return new ValueSpace(v1.Size, new_ValueTable);
        }

        /// <summary>要素ごとの和</summary>
        public static ValueSpace operator +(ValueSpace v1, ValueSpace v2) {
            if(v1.TableSize != v2.TableSize) {
                throw new ArgumentException("Mismatch Size");
            }

            int table_size = v1.TableSize;

            var new_ValueTable = new double[table_size, table_size];

            for(int x, y = 0; y < table_size; y++) {
                for(x = 0; x < table_size; x++) {
                    new_ValueTable[x, y] = v1.ValueTable[x, y] + v2.ValueTable[x, y];
                }
            }

            return new ValueSpace(v1.Size, new_ValueTable);
        }

        /// <summary>要素ごとの差</summary>
        public static ValueSpace operator -(ValueSpace v1, ValueSpace v2) {
            if(v1.TableSize != v2.TableSize) {
                throw new ArgumentException("Mismatch Size");
            }

            int table_size = v1.TableSize;

            var new_ValueTable = new double[table_size, table_size];

            for(int x, y = 0; y < table_size; y++) {
                for(x = 0; x < table_size; x++) {
                    new_ValueTable[x, y] = v1.ValueTable[x, y] - v2.ValueTable[x, y];
                }
            }

            return new ValueSpace(v1.Size, new_ValueTable);
        }

        /// <summary>単項プラス</summary>
        public static ValueSpace operator +(ValueSpace v) {
            return (ValueSpace)v.Clone();
        }

        /// <summary>単項マイナス</summary>
        public static ValueSpace operator -(ValueSpace v) {
            int table_size = v.TableSize;

            var new_ValueTable = new double[table_size, table_size];

            for(int x, y = 0; y < table_size; y++) {
                for(x = 0; x < table_size; x++) {
                    new_ValueTable[x, y] = -v.ValueTable[x, y];
                }
            }

            return new ValueSpace(v.Size, new_ValueTable);
        }

        #endregion


        #region Function

        /// <summary>点対称化</summary>
        public void PointSymmetrizate(Func<double, double, double> func) {
            for(int dx = 1, dy = 0; dx <= Size; dx++) {
                this[dx, dy] = this[-dx, -dy] = func(this[dx, dy], this[-dx, -dy]);
            }

            for(int dx, dy = 1; dy <= Size; dy++) {
                for(dx = -Size; dx <= Size; dx++) {
                    this[dx, dy] = this[-dx, -dy] = func(this[dx, dy], this[-dx, -dy]);
                }
            }
        }

        #endregion


        #region ICloneable

        /// <summary>クローン</summary>
        public virtual object Clone() {
            return new ValueSpace(Size, ValueTable);
        }

        #endregion

    }
}

関連項目
RGB空間
RGBグラフィックおよびその入出力
フィルタ
グレースケール
ガウシアンフィルタ
バイラテラルフィルタ
モーションブラーフィルタ
微分フィルタ
ソーベルフィルタ
鮮鋭化フィルタ

ライブラリライブラリ
確率統計確率統計
線形代数線形代数
幾何学幾何学
最適化最適化
微分方程式微分方程式
画像処理画像処理
補間補間
機械学習機械学習
クラスタリングクラスタリング
パズルゲーム・パズル
未分類未分類