기음#
반복적 인 박스 블러를 수행하는 대신, 나는 길을 가고 가우시안 블러를 작성하기로 결정했습니다. 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)를 차지한다. 그럼에도 불구하고 사용자 인터페이스의 코드는 매우 작습니다.