この前の
ガウスぼかしと同じように
カーネル(マスク、オペレータ)があって
この2つがよく使われるみたい、左が上下左右の4近傍、右が斜めも入れた8近傍
中心の注目
ピクセルと、その周りとの輝度差が大きいほど、結果も大きく(白く)なってエッジ(輪郭)になって、
差が全くなければプラスマイナス0(黒)
最大差
中心輝度が0で周りが255のとき
4近傍だと、255*4-0*4=1020
8近傍だと、255*8-0*8=2040
輝度の最大は255だからそれ以上でも切り捨てになるけど、8近傍のほうがちょっとした差でも大きな差になって現れてくるってことかな
8近傍のとき上下左右の重みを増して2にしてみると
差を絶対値で取ると
輝度差でエッジを表現するなら、-100も100も同じ100差なんだから絶対値で計算すればいいのかと思ったら、そうでもないようで場合によりけりみたい
エクセル方眼紙で確認
元画像は黒(0)と白(255)の間に中間の灰色(128)が1
ピクセルあるけど
普通に離れて見た場合は灰色は見えなくて黒と白が隣接しているように見えるはず
輝度にマイナスはないから-127は0(黒)に置き換えられるので
正確さでいったら絶対値で取ったほうかなあ
白と灰色とのエッジと、灰色と黒のエッジの2つが出てきたほうが自然だと思う
でも実際の画像で見ると絶対値の方はブレたように見えるから不自然なんだよねえ
これも絶対値じゃない方は1
ピクセル幅で太さが変わっていないから自然に見える
今度は逆に絶対値じゃないほうが2重線になって不自然
5x5の
カーネル
どれもエッジって感じがしないかなあ、これなら
3x3の
カーネルのほうがいいと思った、使いみちによるのかも
画像のぼかし処理にはザラザラしたノイズを抑える効果もあるけど、輪郭(エッジ)もぼやけてしまう
今回のエッジ抽出を使ってエッジ以外をぼかし処理すれば、エッジを残したままノイズだけを抑えた画像ができるかも
見た目的にきれいなエッジが欲しい時は絶対値じゃないほうだなあ
でもエッジを残してのぼかしは絶対値の方だと思うんだよねえ
エクセル方眼紙でいろいろ試してみた
クリック注意
参照したところ
<summary>
</summary>
<param name="pixels"></param>
<param name="width"></param>
<param name="height"></param>
<param name="absolute"></param>
<returns></returns>
private (byte[] pixels, BitmapSource bitmap) Filterラプラシアン(byte[] pixels, int width, int height, bool absolute = false)
{
byte[] filtered = new byte[pixels.Length];
int stride = width;
for (int y = 1; y < height - 1; y++)
{
for (int x = 1; x < width - 1; x++)
{
int p = x + y * stride;
int total = 0;
total += pixels[p - stride];
total += pixels[p - 1];
total += pixels[p + 1];
total += pixels[p + stride];
total -= pixels[p] * 4;
if (absolute)
{
total = Math.Abs(total);
}
total = total < 0 ? 0 : total > 255 ? 255 : total;
filtered[p] = (byte)total;
}
}
return (filtered, BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, filtered, width));
}
基本はこのまえの
ガウスぼかしと全く同じ、外周の1
ピクセルは0で埋めるのも同じ
違うのは
カーネルの数値だけだから、輝度にかける数値が違うだけ
8近傍
private (byte[] pixels, BitmapSource bitmap) Filterラプラシアン8近傍(byte[] pixels, int width, int height, bool absolute = false)
{
byte[] filtered = new byte[pixels.Length];
int stride = width;
int total;
int begin = stride + 1;
int end = pixels.Length - stride - 1;
for (int i = begin; i < end; i++)
{
total = 0;
total += pixels[i - stride - 1];
total += pixels[i - stride];
total += pixels[i - stride + 1];
total += pixels[i - 1];
total += pixels[i + 1];
total += pixels[i + stride - 1];
total += pixels[i + stride];
total += pixels[i + stride + 1];
total -= pixels[i] * 8;
if (absolute) total = Math.Abs(total);
total = total < 0 ? 0 : total > 255 ? 255 : total;
filtered[i] = (byte)total;
}
return (filtered, BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, filtered, width));
}
forを1個減らしてみた、こっちのほうが速いはずだけど誤差程度
5x5の
カーネル、中心が-24でそれ以外は1の固定
private (byte[] pixels, BitmapSource bitmap) Filterラプラシアン5x5近傍(byte[] pixels, int width, int height, bool absolute = false)
{
byte[] filtered = new byte[pixels.Length];
int stride = width;
int total;
int diff1 = -stride * 2 - 2;
int diff2 = -stride - 2;
int diff3 = -2;
int diff4 = stride - 2;
int diff5 = stride * 2 - 2;
for (int y = 2; y < height - 2; y++)
{
for (int x = 2; x < width - 2; x++)
{
int p = y * stride + x;
total = 0;
for (int z = 0; z < 5; z++)
{
total += pixels[p + diff1 + z];
total += pixels[p + diff2 + z];
total += pixels[p + diff3 + z];
total += pixels[p + diff4 + z];
total += pixels[p + diff5 + z];
}
total -= pixels[p];
total -= pixels[p] * 24;
if (absolute) total = Math.Abs(total);
total = total < 0 ? 0 : total > 255 ? 255 : total;
filtered[p] = (byte)total;
}
}
return (filtered, BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, filtered, width));
}
forをできるだけ使いたくないけど5x5だと参照ピクセルが25個もあってさすがに長くなるから一番内側の5個以外はforで
private (byte[] pixels, BitmapSource bitmap) Filterラプラシアン5x5近傍2(int[,] weight, byte[] pixels, int width, int height, bool absolute = false)
{
byte[] filtered = new byte[pixels.Length];
int stride = width;
int total;
for (int y = 2; y < height - 2; y++)
{
for (int x = 2; x < width - 2; x++)
{
int p = y * stride + x;
total = 0;
for (int i = 0; i < 5; i++)
{
int pp = p + stride * (i - 2);
for (int j = 0; j < 5; j++)
{
total += pixels[pp + (j - 2)] * weight[i, j];
}
}
if (absolute) total = Math.Abs(total);
total = total < 0 ? 0 : total > 255 ? 255 : total;
filtered[p] = (byte)total;
}
}
return (filtered, BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, filtered, width));
}
さっきの固定だった
カーネルを指定できるようにしたもの
ここは素直にforを使って書いた
これを使っているところは
private void Button_Click_11(object sender, RoutedEventArgs e)
{
if (MyPixels == null) { return; }
int[,] weight = {
{ -1, -4, -7, -4, -1 },
{ -4, 0, 8, 0, -4 },
{ -7, 8, 32, 8, -7 },
{ -4, 0, 8, 0, -4 },
{ -1, -4, -7, -4, -1 } };
(byte[] pixels, BitmapSource bitmap) = Filterラプラシアン5x5近傍2(
weight,
MyPixels,
MyBitmapOrigin.PixelWidth,
MyBitmapOrigin.PixelHeight,
(bool)CheckBoxAbsolute.IsChecked);
MyImage.Source = bitmap;
MyPixels = pixels;
}
画像の読み込み
<summary>
</summary>
<param name="filePath"></param>
<param name="pixelFormat"></param>
<param name="dpiX"></param>
<param name="dpiY"></param>
<returns></returns>
private (byte[] array, BitmapSource source) MakeBitmapSourceAndByteArray(string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
byte[] pixels = null;
BitmapSource source = null;
try
{
using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
var bf = BitmapFrame.Create(fs);
var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0);
int w = convertedBitmap.PixelWidth;
int h = convertedBitmap.PixelHeight;
int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
pixels = new byte[h * stride];
convertedBitmap.CopyPixels(pixels, stride, 0);
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception)
{
}
return (pixels, source);
}
この部分はいつもと同じ
//画像ファイルドロップ時の処理
private void MainWindow_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; }
string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop);
var (pixels, bitmap) = MakeBitmapSourceAndByteArray(filePath[0], PixelFormats.Gray8, 96, 96);
if (bitmap == null)
{
MessageBox.Show("画像ファイルじゃないみたい");
}
else
{
MyPixels = pixels;
MyPixelsOrigin = pixels;
MyBitmapOrigin = bitmap;
MyImage.Source = bitmap;
MyImageOrigin.Source = bitmap;
ImageFileFullPath = filePath[0];
}
}
画像ファイルドロップで表示、ボタンで変換、表示画像クリックで元の画像と切り替え
関連記事
1年後