双立方图像插值算法 - 毛刺

时间:2012-12-23 15:18:55

标签: image image-processing graphics

我正在尝试实现双三次图像插值。我只粘贴了代码的相关部分。我已经跳过了将图像加载到缓冲区并从中读取像素的代码。我有理由相信我的数学是正确的。但是,我似乎在输出中有可怕的伪像。

所有操作都发生在resize方法中。

我希望有经验的图形程序员可以分享他们可能做错的事情。

以下是将输入大小调整为宽度和高度两倍时的输入和输出图像。

input image output image

double Interpolator::interpolate(const double p0, const double p1, const double p2, const double p3, const double x){
    return  (-0.5f*p0+1.5f*p1-1.5f*p2+0.5*p3)*pow(x,3)+
            (p0-2.5f*p1+2.f*p2-0.5f*p3)*pow(x,2)+
            (-0.5f*p0+0.5f*p2)*x+
            p1;
}

bool Image::equals(double a, double b, double threshold){
        if(fabs(a-b)<=threshold)
            return true;
        return false;
}


void Image::interpolate(const Pixel p[], double offset, Pixel& result){
    result.r = Interpolator::interpolate(p[0].r,p[1].r,p[2].r,p[3].r,offset);
    result.g = Interpolator::interpolate(p[0].g,p[1].g,p[2].g,p[3].g,offset);
    result.b = Interpolator::interpolate(p[0].b,p[1].b,p[2].b,p[3].b,offset);
    result.a = Interpolator::interpolate(p[0].a,p[1].a,p[2].a,p[3].a,offset);   
}

void Image::getSamplingCoords(const int nearest,
                              const int max,
                              int coords[]){

    coords[0] = nearest-1;
    if(coords[0]<0)
        coords[0] = nearest;
    coords[1] = nearest;
    coords[2] = nearest+1;
    if(coords[2]>=max)
        coords[2] = nearest;    
    coords[3] = nearest+2;
    //The following check should not be necessary
    //since this is never expected to occur. Nevertheless...
    if(coords[3]>=max)
        coords[3] = nearest;
}

void Image::interpolateAlongY(int x, int y, int yMax, double yOffset, Pixel& result){

    if(equals(yOffset,0.f,ERROR_THRESHOLD)){
        //No interpolation required
        getPixel(x,y,result);
        return;
    }

    int yCoords[4];
    getSamplingCoords(y, yMax, yCoords);    
    Pixel interpolants[4];
    for(int i=0; i<4; ++i){
        getPixel(x, yCoords[i], interpolants[i]);
    }   
    interpolate(interpolants, y, result);
}

void Image::resize(const int newWidth, const int newHeight){
    //Ensure that we have a valid buffer already
    if(buffer==NULL){
        printf("ERROR: Must load an image before resizing it!");
        assert(false);
    }   
    //We first need to create a new buffer with the new dimensions
    unsigned char* newBuffer = new unsigned char[newWidth*newHeight*channelCount];
    for(int j=0; j<newHeight; ++j){
        for(int i=0; i<newWidth; ++i){
            size_t newIndexOffset = (j*newWidth+i)*channelCount;
            //For this pixel in the target image we
            //a) Find the nearest pixel in the source image
            //b) Find the offset from the aforementioned nearest pixel
            int xNear,yNear;
            double xOffset,yOffset;
            double x = ((double)width/(double)newWidth)*i;
            double y = ((double)height/(double)newHeight)*j;    
            xNear = floor(x);
            yNear = floor(y);   
            xOffset = x-xNear;
            yOffset = y-yNear;

            //If offset is 0, we don't need any interpolation
            //we simply need to sample the source pixel and proceed
//          if(equals(xOffset,0.f,ERROR_THRESHOLD) && equals(yOffset,0.f,ERROR_THRESHOLD)){
//              Pixel result;
//              getPixel(xNear, yNear, result);
//              *(newBuffer+newIndexOffset) = result.r;
//              *(newBuffer+newIndexOffset+1) = result.g;
//              *(newBuffer+newIndexOffset+2) = result.b;
//              if(channelCount==4)
//                  *(buffer+newIndexOffset+3) = result.a;
//              continue;
//          }           
            //We make a check that xNear and yNear obtained above
            //are always smaller than the edge pixels at the extremeties
            if(xNear>=width || yNear>=height){
                printf("ERROR: Nearest pixel computation error!");
                assert(false);
            }           
            //Next we find four pixels along the x direction around this
            //nearest pixel
            int xCoords[4];
            getSamplingCoords(xNear,width,xCoords);

            //For each of these sampling xCoords, we interpolate 4 nearest points
            //along Y direction
            Pixel yInterps[4];
            for(int k=0; k<4; k++){
                interpolateAlongY(xCoords[k], yNear, height, yOffset, yInterps[k]);
            }           
            //Finally, the resultant pixel is a cubic interpolation
            //on the 4 obtained pixels above
            Pixel result;
            if(equals(xOffset,0.f,ERROR_THRESHOLD)){
                result.r = yInterps[0].r;
                result.g = yInterps[0].g;
                result.b = yInterps[0].b;
                result.a = yInterps[0].a;
            }else{
                interpolate(yInterps, xOffset, result);
            }
            *(newBuffer+newIndexOffset) = result.r;
            *(newBuffer+newIndexOffset+1) = result.g;
            *(newBuffer+newIndexOffset+2) = result.b;
            if(channelCount==4)
                *(newBuffer+newIndexOffset+3) = result.a;
        }   
    }   
    //Now we can deallocate the memory of our current buffer
    delete [] buffer;
    //Reassign our newly sampled buffer to our own
    buffer = newBuffer;
    //Reset our image dimensions
    height = newHeight;
    width = newWidth;
}

2 个答案:

答案 0 :(得分:4)

interpolateAlongY错了,最后一行是

interpolate(interpolants, y, result);

应该是

interpolate(interpolants, yOffset, result);

此外getSamplingCoords coords[3]中的评论错误。

编辑:

在我查看了JansonD​​的答案之后,我注意到了另外两个问题,if(equals(xOffset,0.f,ERROR_THRESHOLD))的例外情况甚至不必要,当偏移的值接近0时,插值函数做正确的事。

另一方面,当您从相邻像素添加信息时,插值函数可能是低通滤波器,因此您可能会丢失像边缘这样的高频数据的细节。

答案 1 :(得分:1)

除了约瑟夫的回答:

interpolate(interpolants, y, result);

需要参考yOffset,还有:

if(equals(xOffset,0.f,ERROR_THRESHOLD)){
            result.r = yInterps[0].r;
            result.g = yInterps[0].g;
            result.b = yInterps[0].b;
            result.a = yInterps[0].a;

哪个应该使用yInterps[1]

此外,没有任何输出值被钳位,因此锐边可能会出现下溢和溢出。

评论:

if(coords[3]>=max)
    coords[3] = nearest;

这不是唯一的错误,但实际上钳位应该是max-1,而不是nearest,除非你的意思是反映边缘像素而不是重复它们。