在将图像缩小一半时,我试图找出我的最近邻插值有什么问题,我已经被蹲了好几天。我正在使用Rust,但我认为它不像算法那么重要:
/// Interpolate using nearest neighbor.
pub fn nearest(mut src: &mut Image, w2: i32, h2: i32) -> RasterResult<()> {
// Compute scale
let x_scale: f64 = src.width as f64 / w2 as f64;
let y_scale: f64 = src.height as f64 / h2 as f64;
// Create destination image
let mut dest = Image::blank(w2, h2);
// Loop thru destination image's pixels
for y2 in 0..h2 {
for x2 in 0..w2 {
// Get location from src using nearest neighbor
let x1: i32 = ( x2 as f64 * x_scale ).floor() as i32;
let y1: i32 = ( y2 as f64 * y_scale ).floor() as i32;
let pixel = try!(src.get_pixel(x1, y1));
// Apply the pixels
try!(dest.set_pixel(x2, y2, pixel));
}
}
// Update the source image
src.width = dest.width;
src.height = dest.height;
src.bytes = dest.bytes;
Ok(())
}
我的主叫代码:
// Open
let mut image = raster::open(r"test.png").unwrap();
// Resize to half
raster::interpolate::nearest(&mut image, 256, 128).unwrap();
// Save
raster::save(&image, r"out.png").unwrap();
使用我的代码缩小到一半:
这里的比较是Photoshop的
我使用GD进行测试,结果与Photoshop相似。
我的调查让我认为,与其他像素(绿色方块)相比,我对源像素(红色方块)的计算偏差为一个:
我也试过寻找into GD's source code来获取线索,但我并不完全明白它的作用。
为了澄清,我不是在谈论使用不同的插值方法,如双线性和双三次方。我将我的最近邻算法及其结果与Photoshop和GD的最近邻算法的结果进行比较。我不是在问为什么我在Photoshop中的结果很难看,因为我使用了最近邻算法。
(测试图片由Jonas Berlin通过Eric Brasseur提供)
答案 0 :(得分:1)
进行最近邻插值的一种稍微好一点的方法是使插值越过目标像素的中心而不是它们的左上角。在您的代码中,这意味着在计算0.5
和x2
时将y2
添加到x1
和y1
。
let x1: i32 = ( (x2 as f64 + 0.5) * x_scale ).floor() as i32;
let y1: i32 = ( (y2 as f64 + 0.5) * y_scale ).floor() as i32;
当您缩放1/2时,这不一定会产生更好的结果:每个目标像素都是从2x2源区域中选择的,因此选择哪个源像素无关紧要。但是当缩放1/3时,这意味着您选择3x3区域的中心而不是左上角,这是更好的。
Photoshop可能会使用这样的方法,这就是你看到不同结果的原因。
请注意,即使这样可以使测试图像看起来更好地扩展,也可以使用新算法看起来很糟糕的另一个测试图像。通过(1,1)抵消测试图像可能会这样做。
答案 1 :(得分:0)
你的算法没有错。这取决于您选择如何定义坐标。请求的缩放是图像分辨率的一半,这意味着每个目标像素需要考虑四个像素;它们是等距的,覆盖每个目标像素的四分之一,因此“最近”并不是做出选择的有用指标。即使选择是每个像素随机,你仍然可以声称最近的邻居,但这将是一个糟糕的实现,因为它将使用嘈杂的分布。
这种情况下的图像旨在显示实际做出的选择。一些重新缩放程序做出了非凡的选择,特别是某些版本的Microsoft Internet Explorer,它可能导致奇怪的偏见。
例如,在VIPS中,如果我将最近邻居缩减应用于图像,我得到你的结果(偏向于较暗的像素),但如果我首先应用一个像素对角线偏移(通过选择一个奇数偏移区域),我得到Photoshop结果(偏向于较亮的像素)。这仅仅是向上或向下舍入0.5的结果。通过定义子像素坐标所在的位置,即给定像素的坐标位于该像素的角或中心,可以使其更正确。
图像似乎是为了测试不是最近邻,而是用于线性插值的伽马计算。许多图像缩放器错误地认为颜色空间是线性的并且可以使用像素值的平均平均值,但是我们的视觉大致是对数的,因此像sRGB这样的流行颜色空间使用伽马曲线来更好地近似它。