任何业余摄影师都能告诉你,极端的后处理总是很好的。一种这样的技术被称为"微型假象“。
其目的是使一幅图像看起来像一张小型化的玩具版的照片。这对从中等/高角度拍摄到地面的照片效果最好,目标高度的变化很小,但对其他图像的效果不同。
挑战:拍摄一张照片,并应用一种微型伪造算法。有许多方法可以做到这一点,但就这一挑战而言,归根结底是:
必须实施这三项修改,不允许进行任何其他改进/改动。没有修剪,锐化,白平衡调整,什么都没有。
Image.blur() )example-outputs子文件夹中看到每个输出的示例。请注意,这些是完整的10 on图像直接从相机,所以你有很多像素的工作。另一个可以是你选择的任何图像。显然,尝试选择可自由使用的图像。此外,包括原始图像或链接到它的比较。例如,从这个图像中:

您可以输出如下内容:

作为参考,上面的例子是在GIMP中处理的,带有角盒型梯度高斯模糊,饱和度+80,对比度+20。(我不知道GIMP使用什么单位)
要获得更多的灵感,或者想更好地了解您想要实现的目标,请查看本站或这一个。您还可以搜索miniature faking和tilt shift photography作为示例。
这是一场人气竞赛。选民们,投票给你觉得最好看的条目,同时坚持目标。
澄清什么函数是不允许的,这不是我的意图禁止数学函数。这是我的意图,禁止图像处理功能。是的,有一些重叠,但像FFT,卷积,矩阵数学等,在许多其他领域是有用的。您不应该使用简单的图像和模糊的函数。如果你找到了一种合适的方法来制造一个模糊,那就是公平的游戏。
发布于 2014-04-27 01:38:17
以下是Java中的一个基本参考实现。它在高角度拍摄时效果最好,而且效率很低。
模糊是一个非常基本的方框模糊,所以它在相同的像素上的循环远远超过必要。对比和饱和度也可以合并成一个循环,但是大部分时间都在模糊上,所以它不会从中获得太多的收益。尽管如此,它在2MP以下的图像上运行得相当快。10 to图像需要一些时间才能完成。
模糊质量可以很容易地通过使用基本的任何东西,除了一个扁平的盒子模糊。对比/饱和算法完成了他们的工作,所以没有真正的抱怨。
这个计划中没有真正的情报。它使用恒定的因素,模糊,饱和度,和对比。我绕着它玩,以找到快乐的媒介设置。因此,有些场景做得不太好。例如,它将对比度/饱和度拉大,使得颜色相似的大面积图像(如天空)分裂成彩色波段。
使用起来很简单;只需将文件名作为唯一的参数传入即可。它在PNG中输出,而不管输入文件是什么。
从dropbox选择的
为了便于张贴,这些第一批图片被缩小了。单击该图像以查看完整大小。
之后:

在此之前:

之后:

在此之前:

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class MiniFake {
int maxBlur;
int maxDist;
int width;
int height;
public static void main(String[] args) {
if(args.length < 1) return;
new MiniFake().run(args[0]);
}
void run(String filename){
try{
BufferedImage in = readImage(filename);
BufferedImage out = blur(in);
out = saturate(out, 0.8);
out = contrast(out, 0.6);
String[] tokens = filename.split("\\.");
String outname = tokens[0];
for(int i=1;i<tokens.length-1;i++)
outname += "." + tokens[i];
ImageIO.write(out, "png", new File(outname + "_post.png"));
System.out.println("done");
} catch (Exception e){
e.printStackTrace();
}
}
BufferedImage contrast(BufferedImage in, double level){
BufferedImage out = copyImage(in);
long lumens=0;
for(int x=0;x<width;x++)
for(int y=0;y<height;y++){
int color = out.getRGB(x,y);
lumens += lumen(getR(color), getG(color), getB(color));
}
lumens /= (width * height);
for(int x=0;x<width;x++)
for(int y=0;y<height;y++){
int color = out.getRGB(x,y);
int r = getR(color);
int g = getG(color);
int b = getB(color);
double ratio = ((double)lumen(r, g, b) / (double)lumens) - 1d;
ratio *= (1+level) * 0.1;
r += (int)(getR(color) * ratio+1);
g += (int)(getG(color) * ratio+1);
b += (int)(getB(color) * ratio+1);
out.setRGB(x,y,getColor(clamp(r),clamp(g),clamp(b)));
}
return out;
}
BufferedImage saturate(BufferedImage in, double level){
BufferedImage out = copyImage(in);
for(int x=0;x<width;x++)
for(int y=0;y<height;y++){
int color = out.getRGB(x,y);
int r = getR(color);
int g = getG(color);
int b = getB(color);
int brightness = Math.max(r, Math.max(g, b));
int grey = (int)(Math.min(r, Math.min(g,b)) * level);
if(brightness == grey)
continue;
r -= grey;
g -= grey;
b -= grey;
double ratio = brightness / (double)(brightness - grey);
r = (int)(r * ratio);
g = (int)(g * ratio);
b = (int)(b * ratio);
out.setRGB(x, y, getColor(clamp(r),clamp(g),clamp(b)));
}
return out;
}
BufferedImage blur(BufferedImage in){
BufferedImage out = copyImage(in);
int[] rgb = in.getRGB(0, 0, width, height, null, 0, width);
for(int i=0;i<rgb.length;i++){
double dist = Math.abs(getY(i)-(height/2));
dist = dist * dist / maxDist;
int r=0,g=0,b=0,p=0;
for(int x=-maxBlur;x<=maxBlur;x++)
for(int y=-maxBlur;y<=maxBlur;y++){
int xx = getX(i) + x;
int yy = getY(i) + y;
if(xx<0||xx>=width||yy<0||yy>=height)
continue;
int color = rgb[getPos(xx,yy)];
r += getR(color);
g += getG(color);
b += getB(color);
p++;
}
if(p>0){
r /= p;
g /= p;
b /= p;
int color = rgb[i];
r = (int)((r*dist) + (getR(color) * (1 - dist)));
g = (int)((g*dist) + (getG(color) * (1 - dist)));
b = (int)((b*dist) + (getB(color) * (1 - dist)));
} else {
r = in.getRGB(getX(i), getY(i));
}
out.setRGB(getX(i), getY(i), getColor(r,g,b));
}
return out;
}
BufferedImage readImage(String filename) throws IOException{
BufferedImage image = ImageIO.read(new File(filename));
width = image.getWidth();
height = image.getHeight();
maxBlur = Math.max(width, height) / 100;
maxDist = (height/2)*(height/2);
return image;
}
public BufferedImage copyImage(BufferedImage in){
BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = out.getGraphics();
g.drawImage(in, 0, 0, null);
g.dispose();
return out;
}
static int clamp(int c){return c<0?0:c>255?255:c;}
static int getColor(int a, int r, int g, int b){return (a << 24) | (r << 16) | (g << 8) | (b);}
static int getColor(int r, int g, int b){return getColor(0xFF, r, g, b);}
static int getR(int color){return color >> 16 & 0xFF;}
static int getG(int color){return color >> 8 & 0xFF;}
static int getB(int color){return color & 0xFF;}
static int lumen(int r, int g, int b){return (r*299)+(g*587)+(b*114);}
int getX(int pos){return pos % width;}
int getY(int pos){return pos / width;}
int getPos(int x, int y){return y*width+x;}
}发布于 2014-04-27 20:54:03
而不是做任何迭代框模糊,我决定一路写一个高斯模糊。当使用大型内核时,GetPixel调用确实减慢了它的速度,但是除非我们正在处理一些较大的图像,否则转换方法使用LockBits是不值得的。
下面是一些示例,它们使用了我设置的默认调优参数(我没有经常使用调优参数,因为它们似乎在测试映像中运行得很好)。
为测试用例提供..。


又一个..。


又一个..。


从代码来看,饱和度和对比度的增加应该是相当直接的。我在HSL空间中这样做,并将其转换回RGB。
二维高斯核是根据指定的n大小生成的,其中:
EXP(-((x-x0)^2/2+(y-y0)^2/2)/2)在分配了所有内核值之后,...and实现了规范化。注意,A=sigma_x=sigma_y=1。
为了找出应用内核的位置,我使用了一个模糊的权重,计算方法是:
SQRT([COS(PI*x_norm)^2 + COS(PI*y_norm)^2]/2)...which给出了一个不错的回应,本质上是创建了一个椭圆的值,这些值被保护起来,使其不受逐渐消失的模糊的影响。带通滤波器与其他方程(可能是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;
}
}
}发布于 2014-04-27 19:22:46
使用快速运行平均双向方块模糊,以足够快运行多次传球,模拟高斯模糊。模糊是一个椭圆梯度,而不是双线,以及。
在视觉上,它对大型图像的效果最好。有一个更黑暗、更粗俗的主题。我很高兴在适当大小的图像上模糊的结果,这是相当渐进的,很难分辨它的“起点”。
对整数或双数数组进行的所有计算(用于HSV)。
期望文件路径作为参数,将文件输出到以“miniaturized.png”为后缀的相同位置,也会在JFrame中显示输入和输出,以便立即查看。
(点击查看大型版本,它们会更好)


我可能需要添加一些更聪明的色调映射或luma保存,因为它可能会变得很暗:


不过,还是很有趣,把它放在一个全新的氛围中。
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public class SceneMinifier {
static final double CONTRAST_INCREASE = 8;
static final double SATURATION_INCREASE = 7;
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("Please specify an input image file.");
return;
}
BufferedImage temp = ImageIO.read(new File(args[0]));
BufferedImage input = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_INT_ARGB);
input.getGraphics().drawImage(temp, 0, 0, null); // just want to guarantee TYPE_ARGB
int[] pixels = ((DataBufferInt) input.getData().getDataBuffer()).getData();
// saturation
double[][] hsv = toHSV(pixels);
for (int i = 0; i < hsv[1].length; i++)
hsv[1][i] = Math.min(1, hsv[1][i] * (1 + SATURATION_INCREASE / 10));
// contrast
int[][] rgb = toRGB(hsv[0], hsv[1], hsv[2]);
double c = (100 + CONTRAST_INCREASE) / 100;
c *= c;
for (int i = 0; i < pixels.length; i++)
for (int q = 0; q < 3; q++)
rgb[q][i] = (int) Math.max(0, Math.min(255, ((rgb[q][i] / 255. - .5) * c + .5) * 255));
// blur
int w = input.getWidth();
int h = input.getHeight();
int k = 5;
int kd = 2 * k + 1;
double dd = 1 / Math.hypot(w / 2, h / 2);
for (int reps = 0; reps < 5; reps++) {
int tmp[][] = new int[3][pixels.length];
int vmin[] = new int[Math.max(w, h)];
int vmax[] = new int[Math.max(w, h)];
for (int y = 0, yw = 0, yi = 0; y < h; y++) {
int[] sum = new int[3];
for (int i = -k; i <= k; i++) {
int ii = yi + Math.min(w - 1, Math.max(i, 0));
for (int q = 0; q < 3; q++)
sum[q] += rgb[q][ii];
}
for (int x = 0; x < w; x++) {
int dx = x - w / 2;
int dy = y - h / 2;
double dist = Math.sqrt(dx * dx + dy * dy) * dd;
dist *= dist;
for (int q = 0; q < 3; q++)
tmp[q][yi] = (int) Math.min(255, sum[q] / kd * dist + rgb[q][yi] * (1 - dist));
if (y == 0) {
vmin[x] = Math.min(x + k + 1, w - 1);
vmax[x] = Math.max(x - k, 0);
}
int p1 = yw + vmin[x];
int p2 = yw + vmax[x];
for (int q = 0; q < 3; q++)
sum[q] += rgb[q][p1] - rgb[q][p2];
yi++;
}
yw += w;
}
for (int x = 0, yi = 0; x < w; x++) {
int[] sum = new int[3];
int yp = -k * w;
for (int i = -k; i <= k; i++) {
yi = Math.max(0, yp) + x;
for (int q = 0; q < 3; q++)
sum[q] += tmp[q][yi];
yp += w;
}
yi = x;
for (int y = 0; y < h; y++) {
int dx = x - w / 2;
int dy = y - h / 2;
double dist = Math.sqrt(dx * dx + dy * dy) * dd;
dist *= dist;
for (int q = 0; q < 3; q++)
rgb[q][yi] = (int) Math.min(255, sum[q] / kd * dist + tmp[q][yi] * (1 - dist));
if (x == 0) {
vmin[y] = Math.min(y + k + 1, h - 1) * w;
vmax[y] = Math.max(y - k, 0) * w;
}
int p1 = x + vmin[y];
int p2 = x + vmax[y];
for (int q = 0; q < 3; q++)
sum[q] += tmp[q][p1] - tmp[q][p2];
yi += w;
}
}
}
// pseudo-lighting pass
for (int i = 0; i < pixels.length; i++) {
int dx = i % w - w / 2;
int dy = i / w - h / 2;
double dist = Math.sqrt(dx * dx + dy * dy) * dd;
dist *= dist;
for (int q = 0; q < 3; q++) {
if (dist > 1 - .375)
rgb[q][i] *= 1 + (Math.sqrt((1 - dist + .125) / 2) - (1 - dist) - .125) * .7;
if (dist < .375 || dist > .375)
rgb[q][i] *= 1 + (Math.sqrt((dist + .125) / 2) - dist - .125) * dist > .375 ? 1 : .8;
rgb[q][i] = Math.min(255, Math.max(0, rgb[q][i]));
}
}
// reassemble image
BufferedImage output = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB);
pixels = ((DataBufferInt) output.getData().getDataBuffer()).getData();
for (int i = 0; i < pixels.length; i++)
pixels[i] = 255 << 24 | rgb[0][i] << 16 | rgb[1][i] << 8 | rgb[2][i];
output.setRGB(0, 0, output.getWidth(), output.getHeight(), pixels, 0, output.getWidth());
// display results
display(input, output);
// output image
ImageIO.write(output, "PNG", new File(args[0].substring(0, args[0].lastIndexOf('.')) + " miniaturized.png"));
}
private static int[][] toRGB(double[] h, double[] s, double[] v) {
int[] r = new int[h.length];
int[] g = new int[h.length];
int[] b = new int[h.length];
for (int i = 0; i < h.length; i++) {
double C = v[i] * s[i];
double H = h[i];
double X = C * (1 - Math.abs(H % 2 - 1));
double ri = 0, gi = 0, bi = 0;
if (0 <= H && H < 1) {
ri = C;
gi = X;
} else if (1 <= H && H < 2) {
ri = X;
gi = C;
} else if (2 <= H && H < 3) {
gi = C;
bi = X;
} else if (3 <= H && H < 4) {
gi = X;
bi = C;
} else if (4 <= H && H < 5) {
ri = X;
bi = C;
} else if (5 <= H && H < 6) {
ri = C;
bi = X;
}
double m = v[i] - C;
r[i] = (int) ((ri + m) * 255);
g[i] = (int) ((gi + m) * 255);
b[i] = (int) ((bi + m) * 255);
}
return new int[][] { r, g, b };
}
private static double[][] toHSV(int[] c) {
double[] h = new double[c.length];
double[] s = new double[c.length];
double[] v = new double[c.length];
for (int i = 0; i < c.length; i++) {
double r = (c[i] & 0xFF0000) >> 16;
double g = (c[i] & 0xFF00) >> 8;
double b = c[i] & 0xFF;
r /= 255;
g /= 255;
b /= 255;
double M = Math.max(Math.max(r, g), b);
double m = Math.min(Math.min(r, g), b);
double C = M - m;
double H = 0;
if (C == 0)
H = 0;
else if (M == r)
H = (g - b) / C % 6;
else if (M == g)
H = (b - r) / C + 2;
else if (M == b)
H = (r - g) / C + 4;
h[i] = H;
s[i] = C / M;
v[i] = M;
}
return new double[][] { h, s, v };
}
private static void display(final BufferedImage original, final BufferedImage output) {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int wt = original.getWidth();
int ht = original.getHeight();
double ratio = (double) wt / ht;
if (ratio > 1 && wt > d.width / 2) {
wt = d.width / 2;
ht = (int) (wt / ratio);
}
if (ratio < 1 && ht > d.getHeight() / 2) {
ht = d.height / 2;
wt = (int) (ht * ratio);
}
final int w = wt, h = ht;
JFrame frame = new JFrame();
JPanel pan = new JPanel() {
BufferedImage buffer = new BufferedImage(w * 2, h, BufferedImage.TYPE_INT_RGB);
Graphics2D gg = buffer.createGraphics();
{
gg.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
@Override
public void paint(Graphics g) {
gg.drawImage(original, 0, 0, w, h, null);
gg.drawImage(output, w, 0, w, h, null);
g.drawImage(buffer, 0, 0, null);
}
};
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pan.setPreferredSize(new Dimension(w * 2, h));
frame.setLayout(new BorderLayout());
frame.add(pan, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
}https://codegolf.stackexchange.com/questions/26317
复制相似问题