C#
反復的なボックスブラーを行う代わりに、私はすべての方法でガウスブラーを作成することにしました。GetPixel
大規模なカーネルを使用した場合の通話は実際にそれを遅く、それが使用する方法を変換するために本当に価値があるではありませんLockBits
、我々はいくつかの大きな画像を処理した場合を除きます。
以下に、設定したデフォルトのチューニングパラメータを使用する例をいくつか示します(テストイメージではうまく機能するように思われたため、チューニングパラメータをあまり使用しませんでした)。
提供されているテストケースの場合...
別の...
別の...
彩度とコントラストの増加は、コードからかなり簡単です。これをHSLスペースで行い、RGBに変換し直します。
2Dガウシアンカーネルはサイズに基づいて生成されるn
と、指定されました:
EXP(-((x-x0)^2/2+(y-y0)^2/2)/2)
...すべてのカーネル値が割り当てられた後に正規化されます。ことに注意してくださいA=sigma_x=sigma_y=1
。
カーネルを適用する場所を把握するために、次の方法で計算されるぼかしの重みを使用します。
SQRT([COS(PI*x_norm)^2 + COS(PI*y_norm)^2]/2)
...適切な応答が得られ、基本的に値の楕円が作成され、ぼかしから保護され、徐々にフェードアウトします。y=-x^2
特定の画像では、他の式(おそらくの変形)と組み合わせたバンドパスフィルターがより効果的に機能する場合があります。テストしたベースケースに対して良好な応答が得られたため、コサインを使用しました。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace FakeMini
{
static class Program
{
static void Main()
{
// Some tuning variables
double saturationValue = 1.7;
double contrastValue = 1.2;
int gaussianSize = 13; // Must be odd and >1 (3, 5, 7...)
// NxN Gaussian kernel
int padding = gaussianSize / 2;
double[,] kernel = GenerateGaussianKernel(gaussianSize);
Bitmap src = null;
using (var img = new Bitmap(File.OpenRead("in.jpg")))
{
src = new Bitmap(img);
}
// Bordering could be avoided by reflecting or wrapping instead
// Also takes advantage of the fact that a new bitmap returns zeros from GetPixel
Bitmap border = new Bitmap(src.Width + padding*2, src.Height + padding*2);
// Get average intensity of entire image
double intensity = 0;
for (int x = 0; x < src.Width; x++)
{
for (int y = 0; y < src.Height; y++)
{
intensity += src.GetPixel(x, y).GetBrightness();
}
}
double averageIntensity = intensity / (src.Width * src.Height);
// Modify saturation and contrast
double brightness;
double saturation;
for (int x = 0; x < src.Width; x++)
{
for (int y = 0; y < src.Height; y++)
{
Color oldPx = src.GetPixel(x, y);
brightness = oldPx.GetBrightness();
saturation = oldPx.GetSaturation() * saturationValue;
Color newPx = FromHSL(
oldPx.GetHue(),
Clamp(saturation, 0.0, 1.0),
Clamp(averageIntensity - (averageIntensity - brightness) * contrastValue, 0.0, 1.0));
src.SetPixel(x, y, newPx);
border.SetPixel(x+padding, y+padding, newPx);
}
}
// Apply gaussian blur, weighted by corresponding sine value based on height
double blurWeight;
Color oldColor;
Color newColor;
for (int x = padding; x < src.Width+padding; x++)
{
for (int y = padding; y < src.Height+padding; y++)
{
oldColor = border.GetPixel(x, y);
newColor = Convolve2D(
kernel,
GetNeighbours(border, gaussianSize, x, y)
);
// sqrt([cos(pi*x_norm)^2 + cos(pi*y_norm)^2]/2) gives a decent response
blurWeight = Clamp(Math.Sqrt(
Math.Pow(Math.Cos(Math.PI * (y - padding) / src.Height), 2) +
Math.Pow(Math.Cos(Math.PI * (x - padding) / src.Width), 2)/2.0), 0.0, 1.0);
src.SetPixel(
x - padding,
y - padding,
Color.FromArgb(
Convert.ToInt32(Math.Round(oldColor.R * (1 - blurWeight) + newColor.R * blurWeight)),
Convert.ToInt32(Math.Round(oldColor.G * (1 - blurWeight) + newColor.G * blurWeight)),
Convert.ToInt32(Math.Round(oldColor.B * (1 - blurWeight) + newColor.B * blurWeight))
)
);
}
}
border.Dispose();
// Configure some save parameters
EncoderParameters ep = new EncoderParameters(3);
ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
ep.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int)EncoderValue.ScanMethodInterlaced);
ep.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int)EncoderValue.RenderProgressive);
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo ici = null;
foreach (ImageCodecInfo codec in codecs)
{
if (codec.MimeType == "image/jpeg")
ici = codec;
}
src.Save("out.jpg", ici, ep);
src.Dispose();
}
// Create RGB from HSL
// (C# BCL allows me to go one way but not the other...)
private static Color FromHSL(double h, double s, double l)
{
int h0 = Convert.ToInt32(Math.Floor(h / 60.0));
double c = (1.0 - Math.Abs(2.0 * l - 1.0)) * s;
double x = (1.0 - Math.Abs((h / 60.0) % 2.0 - 1.0)) * c;
double m = l - c / 2.0;
int m0 = Convert.ToInt32(255 * m);
int c0 = Convert.ToInt32(255*(c + m));
int x0 = Convert.ToInt32(255*(x + m));
switch (h0)
{
case 0:
return Color.FromArgb(255, c0, x0, m0);
case 1:
return Color.FromArgb(255, x0, c0, m0);
case 2:
return Color.FromArgb(255, m0, c0, x0);
case 3:
return Color.FromArgb(255, m0, x0, c0);
case 4:
return Color.FromArgb(255, x0, m0, c0);
case 5:
return Color.FromArgb(255, c0, m0, x0);
}
return Color.FromArgb(255, m0, m0, m0);
}
// Just so I don't have to write "bool ? val : val" everywhere
private static double Clamp(double val, double min, double max)
{
if (val >= max)
return max;
else if (val <= min)
return min;
else
return val;
}
// Simple convolution as C# BCL doesn't appear to have any
private static Color Convolve2D(double[,] k, Color[,] n)
{
double r = 0;
double g = 0;
double b = 0;
for (int i=0; i<k.GetLength(0); i++)
{
for (int j=0; j<k.GetLength(1); j++)
{
r += n[i,j].R * k[i,j];
g += n[i,j].G * k[i,j];
b += n[i,j].B * k[i,j];
}
}
return Color.FromArgb(
Convert.ToInt32(Math.Round(r)),
Convert.ToInt32(Math.Round(g)),
Convert.ToInt32(Math.Round(b)));
}
// Generates a simple 2D square (normalized) Gaussian kernel based on a size
// No tuning parameters - just using sigma = 1 for each
private static double [,] GenerateGaussianKernel(int n)
{
double[,] kernel = new double[n, n];
double currentValue;
double normTotal = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
currentValue = Math.Exp(-(Math.Pow(i - n / 2, 2) + Math.Pow(j - n / 2, 2)) / 2.0);
kernel[i, j] = currentValue;
normTotal += currentValue;
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
kernel[i, j] /= normTotal;
}
}
return kernel;
}
// Gets the neighbours around the current pixel
private static Color[,] GetNeighbours(Bitmap bmp, int n, int x, int y)
{
Color[,] neighbours = new Color[n, n];
for (int i = -n/2; i < n-n/2; i++)
{
for (int j = -n/2; j < n-n/2; j++)
{
neighbours[i+n/2, j+n/2] = bmp.GetPixel(x + i, y + j);
}
}
return neighbours;
}
}
}
GeometricTransformation
、DistanceTransform
、ImageAdd
、ColorNegate
、ImageMultiply
、Rasterize
、およびImageAdjust
。)であっても、このような高いレベルの画像処理機能の助けを借りて、コードが22 Kをとります。それにもかかわらず、ユーザーインターフェイスのコードは非常に小さいです。