0

我试图了解图像重采样方法是如何工作的。我已经阅读/观看了几页/视频,我想我明白了。但是,我找不到任何关于如何实现它的工作示例。所以我想我应该从基础开始:一维上的最近邻重采样。

这很简单,我想我明白了。JSFiddle 演示

function resample() {

    var widthScaled   = Math.round(originalPixels.width * scaleX);
    var sampledPixels = context.createImageData(widthScaled, originalPixels.height);

    for (var i = 0; i < sampledPixels.data.length; i+=4) {

        var position  = index2pos(sampledPixels, i);
        var origPosX  = Math.floor(position.x / scaleX);
        var origColor = getPixel(originalPixels, origPosX, position.y);

        setPixel(sampledPixels, position.x, position.y, origColor);
    }

    loadImage(context, sampledPixels);
}

接下来,我继续进行线性插值。以为这也很简单,但我遇到了问题。首先,如何处理最后一个像素(标记为红色)?它只有一个相邻像素。其次,与 Photoshop 相比,我的结果太锐利了。我的方法有缺陷,还是 PS 做了一些额外的工作?JSFiddle 演示

在此处输入图像描述

function resample() {

    var sampledPixels = context.createImageData(originalPixels.width * scaleX, originalPixels.height);

    for (var i = 0; i < sampledPixels.data.length; i+=4) {

        var position  = index2pos(sampledPixels, i);
        var origPosX  = position.x / scaleX;

        var leftPixelPosX  = Math.floor(origPosX);
        var rightPixelPosX = Math.ceil(origPosX);

        var leftPixelColor  = getPixel(originalPixels, leftPixelPosX, position.y);
        var rightPixelColor = getPixel(originalPixels, rightPixelPosX, position.y);

        var weight = origPosX % 1;
        var color  = mix(leftPixelColor[0], rightPixelColor[0], weight);
            color  = [color, color, color, 255];

        setPixel(sampledPixels, position.x, position.y, color);
    }

    loadImage(context, sampledPixels);
}

function mix(x, y, a) {
    return x * (1 - a) + y * a;
}
4

1 回答 1

0

像素的线性插值

过滤的方式并没有真正的对与错,因为结果是主观的,结果的质量取决于你,是否足够好,或者你觉得还有改进的余地。

还有各种各样的过滤方法,最近邻、线性、双线性、多项式、样条、Lanczos ......并且每种方法都可以有很多变化。还有一些因素,比如过滤输出格式是什么;屏幕,打印,视频。质量优先于速度还是内存效率。无论如何,当硬件为您实时完成时,为什么要升级。

看起来您具有正确的线性过滤基础知识

更新更正。线性和双线性是指同一类型的插值,双线性是2D,线性是1D

处理最后一个像素

在丢失像素的情况下,有几种选择,

  • 假设颜色继续,所以只需复制最后一个像素。
  • 假设下一个像素是背景、边框颜色或一些预定义的边缘颜色。
  • 环绕到另一侧的像素(平铺地图的最佳选择)
  • 如果您知道有背景图像,请使用其像素
  • 只需删除最后一个像素(图像大小将小 1 个像素)

PS结果

对我来说,PhotoShop 结果看起来像是一种双线性过滤形式,尽管它应该保持原始像素颜色,所以使用了一些更复杂的东西。在不知道方法是什么的情况下,您将很难匹配它。

获得最佳结果的光谱

良好的过滤将找到特定点的频谱并根据该信息重建丢失的像素。

如果您认为一行像素不是值而是体积,那么一行像素会产生波形。任何复杂的波形都可以分解为一组更简单的基本纯音(频率)。然后,您可以通过在特定点添加所有频率来获得良好的近似值。

使用这种方法的滤波器通常用傅里叶或 FFT(快速傅里叶变换)表示,并且需要比标准线性插值进行大量处理。

RGB 值代表什么。

每个红色、绿色和蓝色通道代表该通道强度/亮度的平方根。(这是一个接近的通用近似值)因此,当您进行插值时,您需要转换为正确的值,然后进行插值,然后再转换回对数值。

正确插值

function interpolateLinear(pos,c1,c2){ // pos 0-1, c1,c2 are objects {r,g,b}
    return {
       r : Math.sqrt((c2.r * c2.r + c1.r * c1.r) * pos + c1.r * c1.r),
       g : Math.sqrt((c2.g * c2.g + c1.g * c1.g) * pos + c1.g * c1.g),
       b : Math.sqrt((c2.b * c2.b + c1.b * c1.b) * pos + c1.b * c1.b),
    };
}

需要注意的是,绝大多数数字处理软件都不能正确插值。这部分是由于开发人员对输出格式的无知(为什么我会尽可能地谈论它),部分是由于遵守那些仅仅为了显示图像而苦苦挣扎的旧计算机,更不用说处理它了(尽管我不买那个借口)。

HTML5也不例外,几乎在所有插值中都错误地插值了像素值。这会产生暗带,其中存在强烈的色调对比和较暗的总亮度,用于放大和缩小的图像。一旦您注意到错误,它将永远让您烦恼,因为今天的硬件很容易胜任这项工作。

为了说明不正确的插值有多糟糕,下图显示了正确的(顶部)和使用 SVG 过滤器(底部)插值的 canvas 2D API。

在此处输入图像描述

二维线性插值(双线性)

通过依次执行每个轴来完成沿两个轴的插值。首先沿 x 轴插值,然后沿 y 轴插值。您可以将其作为 2 道工序或单道工序来执行。

以下函数将在任何子像素坐标处进行插值。此功能不是为速度而构建的,并且有很大的优化空间。

// Get pixel RGBA value using bilinear interpolation.
// imgDat is a imageData object, 
// x,y are floats in the original coordinates
// Returns the pixel colour at that point as an array of RGBA
// Will copy last pixel's colour
function getPixelValue(imgDat, x,y, result = []){ 
    var i;
    // clamp and floor coordinate
    const ix1 = (x < 0 ? 0 : x >= imgDat.width ? imgDat.width - 1 : x)| 0;
    const iy1 = (y < 0 ? 0 : y >= imgDat.height ? imgDat.height - 1 : y | 0;
    // get next pixel pos
    const ix2 = ix1 === imgDat.width -1 ? ix1 : ix1 + 1;
    const iy2 = iy1 === imgDat.height -1 ? iy1 : iy1 + 1;
    // get interpolation position 
    const xpos = x % 1;
    const ypos = y % 1;
    // get pixel index
    var i1 = (ix1 + iy1 * imgData.width) * 4;
    var i2 = (ix2 + iy1 * imgData.width) * 4;
    var i3 = (ix1 + iy2 * imgData.width) * 4;
    var i4 = (ix2 + iy2 * imgData.width) * 4;

    // to keep code short and readable get data alias
    const d = imgDat.data;

    for(i = 0; i < 3; i ++){
        // interpolate x for top and bottom pixels
        const c1 = (d[i2] * d[i2++] - d[i1] * d[i1]) * xpos + d[i1] * d[i1 ++];
        const c2 = (d[i4] * d[i4++] - d[i3] * d[i3]) * xpos + d[i3] * d[i3 ++];

        // now interpolate y
        result[i] = Math.sqrt((c2 - c1) * ypos + c1);
    }

    // and alpha is not logarithmic
    const c1 = (d[i2] - d[i1]) * xpos + d[i1];
    const c2 = (d[i4] - d[i3]) * xpos + d[i3];
    result[3] = (c2 - c1) * ypos + c1;
    return result;
}


 const upScale = 4;
 // usage
 const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
 const imgData2 = ctx.createImageData(ctx.canvas.width * upScale, ctx.canvas.height * upScale);
 const res = new Uint8ClampedArray(4);
 for(var y = 0; y < imgData2.height; y++){
     for(var x = 0; x < imgData2.width; x++){
         getPixelValue(imgData,x / upScale, y / upScale, res);
         imgData2.data.set(res,(x + y * imgdata2.width) * 4);
     }
 }

示例高档画布 8 倍

该示例使用上述函数将测试图案放大 8 倍。显示三个图像。原来的 64 x 8 然后,使用对数双线性插值计算 upscale,然后使用 canvas 标准 API drawImage 来进行 upScale(使用默认插值,双线性)。

// helper functions create canvas and get context
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"),c.width = w,c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w,h), c.ctx = c.getContext("2d"), c);
// iterators
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; 
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };

const upScale = 8;
var canvas1 = CImageCtx(64,8);
var canvas2 = CImageCtx(canvas1.width * upScale, canvas1.height * upScale);
var canvas3 = CImageCtx(canvas1.width * upScale, canvas1.height * upScale);


    // imgDat is a imageData object, 
    // x,y are floats in the original coordinates
    // Returns the pixel colour at that point as an array of RGBA
    // Will copy last pixel's colour
    function getPixelValue(imgDat, x,y, result = []){ 
        var i;
        // clamp and floor coordinate
        const ix1 = (x < 0 ? 0 : x >= imgDat.width ? imgDat.width - 1 : x)| 0;
        const iy1 = (y < 0 ? 0 : y >= imgDat.height ? imgDat.height - 1 : y) | 0;
        // get next pixel pos
        const ix2 = ix1 === imgDat.width -1 ? ix1 : ix1 + 1;
        const iy2 = iy1 === imgDat.height -1 ? iy1 : iy1 + 1;
        // get interpolation position 
        const xpos = x % 1;
        const ypos = y % 1;
        // get pixel index
        var i1 = (ix1 + iy1 * imgData.width) * 4;
        var i2 = (ix2 + iy1 * imgData.width) * 4;
        var i3 = (ix1 + iy2 * imgData.width) * 4;
        var i4 = (ix2 + iy2 * imgData.width) * 4;

        // to keep code short and readable get data alias
        const d = imgDat.data;

        // interpolate x for top and bottom pixels
        for(i = 0; i < 3; i ++){
            const c1 = (d[i2] * d[i2++] - d[i1] * d[i1]) * xpos + d[i1] * d[i1 ++];
            const c2 = (d[i4] * d[i4++] - d[i3] * d[i3]) * xpos + d[i3] * d[i3 ++];

            // now interpolate y
            result[i] = Math.sqrt((c2 - c1) * ypos + c1);
        }

        // and alpha is not logarithmic
        const c1 = (d[i2] - d[i1]) * xpos + d[i1];
        const c2 = (d[i4] - d[i3]) * xpos + d[i3];
        result[3] = (c2 - c1) * ypos + c1;
        return result;
    }
     const ctx = canvas1.ctx;    
     var cols = ["black","red","green","Blue","Yellow","Cyan","Magenta","White"];
     doFor(8,j => eachOf(cols,(col,i) => {ctx.fillStyle = col; ctx.fillRect(j*8+i,0,1,8)}));
     eachOf(cols,(col,i) => {ctx.fillStyle = col; ctx.fillRect(i * 8,4,8,4)});
     
     const imgData = ctx.getImageData(0, 0, canvas1.width, canvas1.height);
     const imgData2 = ctx.createImageData(canvas1.width * upScale, canvas1.height * upScale);
     const res = new Uint8ClampedArray(4);
     for(var y = 0; y < imgData2.height; y++){
         for(var x = 0; x < imgData2.width; x++){
             getPixelValue(imgData,x / upScale, y / upScale, res);
             imgData2.data.set(res,(x + y * imgData2.width) * 4);
         }
     }
     canvas2.ctx.putImageData(imgData2,0,0);
     function $(el,text){const e = document.createElement(el); e.textContent = text; document.body.appendChild(e)};
   
     document.body.appendChild(canvas1);
     $("div","Next Logarithmic upscale using linear interpolation * 8");
     document.body.appendChild(canvas2);
     canvas3.ctx.drawImage(canvas1,0,0,canvas3.width,canvas3.height);
     document.body.appendChild(canvas3);
     $("div","Previous Canvas 2D API upscale via default linear interpolation * 8");
     $("div","Note the overall darker result and dark lines at hue boundaries");
     
canvas { border : 2px solid black; }

于 2017-09-16T01:30:54.350 回答