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ķ。用户界面的代码仍然很小。