ImageBufferを対象にした画像フィルター
ImageFilters.cs
using ImageBuffer = Maywork.WPF.Helpers.ImageBufferHelper.ImageBuffer;
namespace Maywork.WPF.Helpers;
public static class ImageFilters
{
// グレースケール化
public static ImageBuffer ToGray(this ImageBuffer src)
{
int width = src.Width;
int height = src.Height;
if (src.Channels == 1)
{
return src;
}
var dst = ImageBufferHelper.Create(width, height, 1);
byte[] sp = src.Pixels;
byte[] dp = dst.Pixels;
Parallel.For(0, height, y =>
{
int si = y * src.Stride;
int di = y * width;
for (int x = 0; x < width; x++)
{
byte b = sp[si];
byte g = sp[si + 1];
byte r = sp[si + 2];
int gray = (299 * r + 587 * g + 114 * b) / 1000;
dp[di] = (byte)gray;
si += src.Channels;
di++;
}
});
return dst;
}
// 2値化
public static ImageBuffer ToBinary(this ImageBuffer src, byte threshold = 128)
{
int width = src.Width;
int height = src.Height;
var dst = ImageBufferHelper.Create(width, height, 1);
byte[] sp = src.Pixels;
byte[] dp = dst.Pixels;
Parallel.For(0, height, y =>
{
int si = y * src.Stride;
int di = y * width;
for (int x = 0; x < width; x++)
{
int gray;
if (src.Channels == 1)
{
gray = sp[si];
}
else
{
byte b = sp[si];
byte g = sp[si + 1];
byte r = sp[si + 2];
gray = (299 * r + 587 * g + 114 * b) / 1000;
}
dp[di] = (byte)(gray >= threshold ? 255 : 0);
si += src.Channels;
di++;
}
});
return dst;
}
// 細線化
public static ImageBuffer Thinning(this ImageBuffer src)
{
int width = src.Width;
int height = src.Height;
if (src.Channels != 1)
throw new Exception("Binary image required");
var dst = src.Clone();
byte[] p = dst.Pixels;
bool changed;
do
{
changed = false;
List<int> remove = new();
// step 1
for (int y = 1; y < height - 1; y++)
{
int row = y * width;
for (int x = 1; x < width - 1; x++)
{
int i = row + x;
if (p[i] == 0) continue;
int p2 = p[i - width] > 0 ? 1 : 0;
int p3 = p[i - width + 1] > 0 ? 1 : 0;
int p4 = p[i + 1] > 0 ? 1 : 0;
int p5 = p[i + width + 1] > 0 ? 1 : 0;
int p6 = p[i + width] > 0 ? 1 : 0;
int p7 = p[i + width - 1] > 0 ? 1 : 0;
int p8 = p[i - 1] > 0 ? 1 : 0;
int p9 = p[i - width - 1] > 0 ? 1 : 0;
int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
if (B < 2 || B > 6) continue;
int A =
(p2 == 0 && p3 == 1 ? 1 : 0) +
(p3 == 0 && p4 == 1 ? 1 : 0) +
(p4 == 0 && p5 == 1 ? 1 : 0) +
(p5 == 0 && p6 == 1 ? 1 : 0) +
(p6 == 0 && p7 == 1 ? 1 : 0) +
(p7 == 0 && p8 == 1 ? 1 : 0) +
(p8 == 0 && p9 == 1 ? 1 : 0) +
(p9 == 0 && p2 == 1 ? 1 : 0);
if (A != 1) continue;
if (p2 * p4 * p6 != 0) continue;
if (p4 * p6 * p8 != 0) continue;
remove.Add(i);
}
}
foreach (var i in remove)
{
p[i] = 0;
changed = true;
}
remove.Clear();
// step 2
for (int y = 1; y < height - 1; y++)
{
int row = y * width;
for (int x = 1; x < width - 1; x++)
{
int i = row + x;
if (p[i] == 0) continue;
int p2 = p[i - width] > 0 ? 1 : 0;
int p3 = p[i - width + 1] > 0 ? 1 : 0;
int p4 = p[i + 1] > 0 ? 1 : 0;
int p5 = p[i + width + 1] > 0 ? 1 : 0;
int p6 = p[i + width] > 0 ? 1 : 0;
int p7 = p[i + width - 1] > 0 ? 1 : 0;
int p8 = p[i - 1] > 0 ? 1 : 0;
int p9 = p[i - width - 1] > 0 ? 1 : 0;
int B = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
if (B < 2 || B > 6) continue;
int A =
(p2 == 0 && p3 == 1 ? 1 : 0) +
(p3 == 0 && p4 == 1 ? 1 : 0) +
(p4 == 0 && p5 == 1 ? 1 : 0) +
(p5 == 0 && p6 == 1 ? 1 : 0) +
(p6 == 0 && p7 == 1 ? 1 : 0) +
(p7 == 0 && p8 == 1 ? 1 : 0) +
(p8 == 0 && p9 == 1 ? 1 : 0) +
(p9 == 0 && p2 == 1 ? 1 : 0);
if (A != 1) continue;
if (p2 * p4 * p8 != 0) continue;
if (p2 * p6 * p8 != 0) continue;
remove.Add(i);
}
}
foreach (var i in remove)
{
p[i] = 0;
changed = true;
}
} while (changed);
return dst;
}
// 反転
public static ImageBuffer Invert(this ImageBuffer src)
{
int width = src.Width;
int height = src.Height;
var dst = src.Clone();
byte[] p = dst.Pixels;
Parallel.For(0, height, y =>
{
int i = y * src.Stride;
for (int x = 0; x < width; x++)
{
p[i] = (byte)(255 - p[i]);
i++;
}
});
return dst;
}
// ぼかし処理
public static ImageBuffer Blur(this ImageBuffer src, int kernelSize = 3)
{
if (kernelSize < 3) kernelSize = 3;
if (kernelSize > 15) kernelSize = 15;
if (kernelSize % 2 == 0) kernelSize++;
int width = src.Width;
int height = src.Height;
int ch = src.Channels;
if (ch != 1 && ch != 3)
throw new Exception("Unsupported channel format (1 or 3 only)");
var dst = ImageBufferHelper.Clone(src);
byte[] sp = src.Pixels;
byte[] dp = dst.Pixels;
int stride = src.Stride;
int radius = kernelSize / 2;
Parallel.For(0, height, y =>
{
for (int x = 0; x < width; x++)
{
int[] sum = new int[ch];
for (int ky = -radius; ky <= radius; ky++)
{
int yy = y + ky;
if (yy < 0) yy = 0;
if (yy >= height) yy = height - 1;
int sy = yy * stride;
for (int kx = -radius; kx <= radius; kx++)
{
int xx = x + kx;
if (xx < 0) xx = 0;
if (xx >= width) xx = width - 1;
int si = sy + (xx * ch);
for (int c = 0; c < ch; c++)
{
sum[c] += sp[si + c];
}
}
}
int di = y * stride + (x * ch);
int area = kernelSize * kernelSize;
for (int c = 0; c < ch; c++)
{
dp[di + c] = (byte)(sum[c] / area);
}
}
});
return dst;
}
}
/*
// 使い方
string file = @"sample.png";
BitmapSource dst = ImageHelper.Load(file)
.ToBuffer()
.ToBinary()
.Invert()
.Thinning()
.Invert()
.ToBitmapSource();
Image1.Source = dst;
*/
Download
Blur()の実行例


コメント