フィルタ; 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グラフィックおよびその入出力
フィルタ
グレースケール
ガウシアンフィルタ
バイラテラルフィルタ
モーションブラーフィルタ
微分フィルタ
ソーベルフィルタ
鮮鋭化フィルタ