Bi-Cubic调整大小的实现

时间:2013-07-14 14:15:40

标签: c++ bicubic

我一直在尝试为内存位图编写Bi-Cubic resize算法。我熟悉双三次插值的工作原理,并且我使用Wikipedia articleexisting implementations作为编码我自己版本的指南。

以下是我的简单实现。这里,bmap是包含位图数据的vector,而get_subpixel只是将位图视为由X x Y x Channel像素组成的3D数组的函数,并返回单个指定坐标处的子像素。

std::vector<unsigned char> bicubic_resize(
    std::vector<unsigned char>& bmap, std::size_t bmap_width, std::size_t bmap_height, 
    std::size_t channels, std::size_t dest_width, std::size_t dest_height)
{
    std::vector<unsigned char> out(dest_width * dest_height * 3);

    const double tx = double(bmap_width) / dest_width;
    const double ty = double(bmap_height) / dest_height;
    const std::size_t row_stride = dest_width * channels;
    unsigned char C[5] = { 0 };

    for (unsigned i = 0; i < dest_height; ++i)
    {
        for (unsigned j = 0; j < dest_width; ++j)
        {
            const int x = int(tx * j);
            const int y = int(ty * i);
            const double dx = tx * j - x;
            const double dy = ty * i - y;

            for (int k = 0; k < 3; ++k)
            {
                for (int jj = 0; jj < 4; ++jj)
                {
                    const int idx = y - 1 + jj;
                    unsigned char a0 = get_subpixel(bmap, idx, x, k);
                    unsigned char d0 = get_subpixel(bmap, idx, x - 1, k) - a0;
                    unsigned char d2 = get_subpixel(bmap, idx, x + 1, k) - a0;
                    unsigned char d3 = get_subpixel(bmap, idx, x + 2, k) - a0;
                    unsigned char a1 = -1.0 / 3 * d0 + d2 - 1.0 / 6 * d3;
                    unsigned char a2 = 1.0 / 2 * d0 + 1.0 / 2 * d2;
                    unsigned char a3 = -1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3;
                    C[jj] = a0 + a1 * dx + a2 * dx * dx + a3 * dx * dx * dx;

                    d0 = C[0] - C[1];
                    d2 = C[2] - C[1];
                    d3 = C[3] - C[1];
                    a0 = C[1];
                    a1 = -1.0 / 3 * d0 + d2 -1.0 / 6 * d3;
                    a2 = 1.0 / 2 * d0 + 1.0 / 2 * d2;
                    a3 = -1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3;
                    out[i * row_stride + j * channels + k] = a0 + a1 * dy + a2 * dy * dy + a3 * dy * dy * dy;
                }
            }
        }
    }

    return out;
}

此代码适用于某些目标尺寸。例如,如果原始位图为 500 X 366 ,且目标尺寸为 250 x 183 ,则算法运行正常:

原始
enter image description here
调整大小
enter image description here

但是,对于某些其他目的地尺寸,例如 100 x 73 ,目标图片会失真:
enter image description here

我一直在查看插值代码,我看不出我做错了什么。

我很感激任何提示,建议或答案。

1 个答案:

答案 0 :(得分:3)

除了混合浮点和整数运算之外,我怀疑你的某些中间值会出现数值上溢/下溢。

一个简单的解决方法是保持一致并始终使用浮点。现在你有:

unsigned char C[5] = { 0 };

for (unsigned i = 0; i < dest_height; ++i)
{
    for (unsigned j = 0; j < dest_width; ++j)
    {
        const int x = int(tx * j);
        const int y = int(ty * i);
        const double dx = tx * j - x;
        const double dy = ty * i - y;

        for (int k = 0; k < 3; ++k)
        {
            for (int jj = 0; jj < 4; ++jj)
            {
                const int idx = y - 1 + jj;
                unsigned char a0 = get_subpixel(bmap, idx, x, k);
                unsigned char d0 = get_subpixel(bmap, idx, x - 1, k) - a0;
                unsigned char d2 = get_subpixel(bmap, idx, x + 1, k) - a0;
                unsigned char d3 = get_subpixel(bmap, idx, x + 2, k) - a0;
                unsigned char a1 = -1.0 / 3 * d0 + d2 - 1.0 / 6 * d3;
                unsigned char a2 = 1.0 / 2 * d0 + 1.0 / 2 * d2;
                unsigned char a3 = -1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3;
                C[jj] = a0 + a1 * dx + a2 * dx * dx + a3 * dx * dx * dx;

                d0 = C[0] - C[1];
                d2 = C[2] - C[1];
                d3 = C[3] - C[1];
                a0 = C[1];
                a1 = -1.0 / 3 * d0 + d2 -1.0 / 6 * d3;
                a2 = 1.0 / 2 * d0 + 1.0 / 2 * d2;
                a3 = -1.0 / 6 * d0 - 1.0 / 2 * d2 + 1.0 / 6 * d3;
                out[i * row_stride + j * channels + k] = a0 + a1 * dy + a2 * dy * dy + a3 * dy * dy * dy;
            }
        }
    }
}

您混合了unsigned charintdouble。这些1.0 / 3中的每一个都将您的8位数据转换为双精度浮点数,然后赋值将其截断。

相反,为什么不直接使用float

float C[5] = { 0 };

for (unsigned i = 0; i < dest_height; ++i)
{
    for (unsigned j = 0; j < dest_width; ++j)
    {
        const float x = float(tx * j);
        const float y = float(ty * i);
        const float dx = tx * j - x;
        const float dy = ty * i - y;

        for (int k = 0; k < 3; ++k)
        {
            for (int jj = 0; jj < 4; ++jj)
            {
                const int idx = y - 1 + jj;
                float a0 = get_subpixel(bmap, idx, x, k);
                float d0 = get_subpixel(bmap, idx, x - 1, k) - a0;
                float d2 = get_subpixel(bmap, idx, x + 1, k) - a0;
                float d3 = get_subpixel(bmap, idx, x + 2, k) - a0;
                float a1 = -(1.0f / 3.0f) * d0 + d2 - (1.0f / 6.0f) * d3;
                float a2 =          0.5f  * d0 +              0.5f *  d2;
                float a3 = -(1.0f / 6.0f) * d0 - 0.5f * d2 + (1.0f / 6.0f) * d3;
                C[jj] = a0 + a1 * dx + a2 * dx * dx + a3 * dx * dx * dx;

                d0 = C[0] - C[1];
                d2 = C[2] - C[1];
                d3 = C[3] - C[1];
                a0 = C[1];
                a1 = -(1.0f / 3.0f) * d0 + d2 -(1.0f / 6.0f) * d3;
                a2 =          0.5f  * d0 +             0.5f  * d2;
                a3 = -(1.0f / 6.0f) * d0 - 0.5f * d2 + (1.0f / 6.0f) * d3;
                out[i * row_stride + j * channels + k] = saturate( a0 + a1 * dy + a2 * dy * dy + a3 * dy * dy * dy );
            }
        }
    }
}

然后定义一个执行此操作的函数saturate

inline unsigned char saturate( float x )
{
    return x > 255.0f ? 255
         : x < 0.0f   ? 0
         :              unsigned char(x);
}

这将解决您的溢出问题,并为您提供更好的精确度和可能更好的性能。

如果您需要进一步提高性能,那么您应该研究定点运算。但就目前而言,我认为上述实施方案更好。

另外,另一个想法是:您应该能够通过预先计算dx * dxdx * dx * dx等来提高效率:

float C[5] = { 0 };

for (unsigned i = 0; i < dest_height; ++i)
{
    for (unsigned j = 0; j < dest_width; ++j)
    {
        const float x = float(tx * j);
        const float y = float(ty * i);
        const float dx = tx * j - x, dx2 = dx * dx, dx3 = dx2 * dx;
        const float dy = ty * i - y, dy2 = dy * dy, dy3 = dy2 * dy;

        for (int k = 0; k < 3; ++k)
        {
            for (int jj = 0; jj < 4; ++jj)
            {
                const int idx = y - 1 + jj;
                float a0 = get_subpixel(bmap, idx, x, k);
                float d0 = get_subpixel(bmap, idx, x - 1, k) - a0;
                float d2 = get_subpixel(bmap, idx, x + 1, k) - a0;
                float d3 = get_subpixel(bmap, idx, x + 2, k) - a0;
                float a1 = -(1.0f / 3.0f) * d0 + d2 - (1.0f / 6.0f) * d3;
                float a2 =          0.5f  * d0 +              0.5f *  d2;
                float a3 = -(1.0f / 6.0f) * d0 - 0.5f * d2 + (1.0f / 6.0f) * d3;
                C[jj] = a0 + a1 * dx + a2 * dx2 + a3 * dx3;

                d0 = C[0] - C[1];
                d2 = C[2] - C[1];
                d3 = C[3] - C[1];
                a0 = C[1];
                a1 = -(1.0f / 3.0f) * d0 + d2 -(1.0f / 6.0f) * d3;
                a2 =          0.5f  * d0 +             0.5f  * d2;
                a3 = -(1.0f / 6.0f) * d0 - 0.5f * d2 + (1.0f / 6.0f) * d3;
                out[i * row_stride + j * channels + k] = saturate( a0 + a1 * dy + a2 * dy2 + a3 * dy3 );
            }
        }
    }
}