カラー画像のぼかし処理するアプリ
元画像
補正の有無での違いはこの画像では感じられないねえ
ぼかし処理での色の補正
ぼかし処理でググっていたらこんな記事を見かけた
なんでも普通のぼかし処理をすると色の境界で不自然な色になってしまうってことらしい
試した
境界がはっきりしている赤と緑の画像
これを普通にぼかし処理すると
2倍に拡大
それでもこういう極端な色がくっきり分かれている画像はめったにないから
普通のぼかし処理でも問題ないのかも
これに似たような現象はグラデーション作成のときにあったガンマ補正の有無
gogowaten.hatenablog.com
これに近い感じ、ガンマ補正したほうが自然なグラデーションになっていると感じるんだけど、グラデーション作成で
ググるとガンマ補正しないほうが圧倒的
ぼかし処理x5
左が補正無し、右があり
おなじに見える
7色でパターンを使ったディザリング画像を
ぼかし処理x1
やっぱり補正ありのほうがいいねえ
65536色グラデーションを4色誤差拡散ディザリングにした画像
これをぼかし処理x3
こう変な画像だと補正ありの良さが出る
これなら処理時間の短い補正無しでいいかな
デザイン画面
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace _20190422_ぼかし処理カラー
{
<summary>
</summary>
public partial class MainWindow : Window
{
string ImageFileFullPath;
BitmapSource MyBitmapOrigin;
byte[] MyPixelsOrigin;
byte[] MyPixels;
public MainWindow()
{
InitializeComponent();
this.Drop += MainWindow_Drop;
this.AllowDrop = true;
}
<summary>
</summary>
<param name="pixels"></param>
<param name="width"></param>
<param name="height"></param>
<returns></returns>
private (byte[] pixels, BitmapSource bitmap) Filter上下左右(byte[] pixels, int width, int height)
{
byte[] filtered = new byte[pixels.Length];
int stride = width * 3;
for (int y = 1; y < height - 1; y++)
{
for (int x = 1; x < width - 1; x++)
{
int p = x * 3 + y * stride;
for (int i = 0; i < 3; i++)
{
filtered[p + i] = (byte)GetAverage(p + i);
}
}
}
int GetAverage(int p)
{
int total = 0;
total += pixels[p - stride];
total += pixels[p - 3];
total += pixels[p];
total += pixels[p + 3];
total += pixels[p + stride];
return total / 5;
}
return (filtered, BitmapSource.Create(
width, height, 96, 96, PixelFormats.Rgb24, null, filtered, stride));
}
<summary>
</summary>
<param name="pixels"></param>
<param name="width"></param>
<param name="height"></param>
<returns></returns>
private (byte[] pixels, BitmapSource bitmap) Filter上下左右補正あり(byte[] pixels, int width, int height)
{
var sw = new Stopwatch();
sw.Start();
byte[] filtered = new byte[pixels.Length];
int stride = width * 3;
for (int y = 1; y < height - 1; y++)
{
for (int x = 1; x < width - 1; x++)
{
int p = x * 3 + y * stride;
for (int i = 0; i < 3; i++)
{
filtered[p + i] = (byte)GetNewValue(p + i);
}
}
}
int GetNewValue(int p)
{
double total = 0;
total += Math.Pow(pixels[p - stride], 2);
total += Math.Pow(pixels[p - 3], 2);
total += Math.Pow(pixels[p], 2);
total += Math.Pow(pixels[p + 3], 2);
total += Math.Pow(pixels[p + stride], 2);
return (int)Math.Sqrt(total / 5);
}
sw.Stop();
MyTextBlock.Text = $"time = {sw.Elapsed.TotalSeconds.ToString("0.000")}秒";
return (filtered, BitmapSource.Create(
width, height, 96, 96, PixelFormats.Rgb24, null, filtered, stride));
}
<summary>
</summary>
<param name="v"></param>
<param name="acceptable"></param>
<returns></returns>
private double MySqrt1(double v, double acceptable = 0.0001)
{
double x = 127.5;
double x2;
while (true)
{
x2 = (x + (v / x)) / 2.0;
if (Math.Abs(x2 - x) <= acceptable) break;
x = x2;
}
return x2;
}
<summary>
</summary>
<param name="v"></param>
<param name="acceptable"></param>
<returns></returns>
private int MySqrtInt(int v, int acceptable = 1)
{
int x = 127;
int x2;
while (true)
{
x2 = (x + (v / x)) >> 1;
if (Math.Abs(x2 - x) <= acceptable) break;
x = x2;
}
return x2;
}
#region その他
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.Rgb24, 96, 96);
if (bitmap == null)
{
MessageBox.Show("画像ファイルじゃないみたい");
}
else
{
MyPixels = pixels;
MyPixelsOrigin = pixels;
MyBitmapOrigin = bitmap;
MyImage.Source = bitmap;
MyImageOrigin.Source = bitmap;
ImageFileFullPath = filePath[0];
}
}
private void SaveImage(BitmapSource source)
{
var saveFileDialog = new Microsoft.Win32.SaveFileDialog();
saveFileDialog.Filter = "*.png|*.png|*.bmp|*.bmp|*.tiff|*.tiff";
saveFileDialog.AddExtension = true;
saveFileDialog.FileName = System.IO.Path.GetFileNameWithoutExtension(ImageFileFullPath) + "_";
saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(ImageFileFullPath);
if (saveFileDialog.ShowDialog() == true)
{
BitmapEncoder encoder = new BmpBitmapEncoder();
if (saveFileDialog.FilterIndex == 1)
{
encoder = new PngBitmapEncoder();
}
else if (saveFileDialog.FilterIndex == 2)
{
encoder = new BmpBitmapEncoder();
}
else if (saveFileDialog.FilterIndex == 3)
{
encoder = new TiffBitmapEncoder();
}
encoder.Frames.Add(BitmapFrame.Create(source));
using (var fs = new System.IO.FileStream(saveFileDialog.FileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
{
encoder.Save(fs);
}
}
}
<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 Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int aa = Panel.GetZIndex(MyImage);
Panel.SetZIndex(MyImageOrigin, aa + 1);
}
private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
int aa = Panel.GetZIndex(MyImage);
Panel.SetZIndex(MyImageOrigin, aa - 1);
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
MyImage.Source = MyBitmapOrigin;
MyPixels = MyPixelsOrigin;
}
private void Button_Click_3(object sender, RoutedEventArgs e)
{
if (MyImage.Source == null) { return; }
SaveImage((BitmapSource)MyImage.Source);
}
#endregion
private void Button_Click(object sender, RoutedEventArgs e)
{
if (MyPixels == null) { return; }
(byte[] pixels, BitmapSource bitmap) = Filter上下左右(
MyPixels, MyBitmapOrigin.PixelWidth, MyBitmapOrigin.PixelHeight);
MyImage.Source = bitmap;
MyPixels = pixels;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (MyPixels == null) { return; }
(byte[] pixels, BitmapSource bitmap) = Filter上下左右補正あり(
MyPixels, MyBitmapOrigin.PixelWidth, MyBitmapOrigin.PixelHeight);
MyImage.Source = bitmap;
MyPixels = pixels;
}
}
}
昨日のグレースケールと同じ処理で、新しい値は自身と上下左右の合計の平均値、違うのはカラーになったからRGBそれぞれで計算するところ、PixelFormatsはRgb24専用
新しい値の計算は自身と上下左右の値それぞれを2乗して合計、それの平均値の
平方根
(上^2+左^2+自身^2+右^2+下^2)/5
値
上 100
左 20
自身 50
右 200
下 80
のとき自身の新しい値は109
(100^2+20^2+50^2+200^2+80^2)/5=11860
処理時間を色補正無しに比べるとかなり長くなる
2000x1500の画像で
無し 一瞬
あり 4秒強
ローカル関数便利
今回のGetAverageやGetNewValueがそれ
ある1つのメソッドでしか使わない専用のものを、一つにまとめておけるのがいいねえ
書き方も普通のメソッドと変わらないのもいい
ローカル関数っていうんだなあ
参照したところ
色補正の計算式はここから
操作は昨日のと同じ、ファイルドロップで画像表示、ボタンでぼかし処理、押せば押すほどぼやける、画像クリックで元の画像との切り替えて比較できる
関連記事