实现双三次调整大小 [英] Implementation of Bi-Cubic resize

查看:95
本文介绍了实现双三次调整大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试为内存位图编码Bi-Cubic调整大小算法.我熟悉双三次插值的工作原理,并且已经使用了 Wikipedia文章

I've been attempting to code a Bi-Cubic resize algorithm for in-memory bitmaps. I'm familiar with how bi-cubic interpolation works, and I've used both the Wikipedia article and existing implementations as a guide towards coding my own version.

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

The following is my simple implementation. Here, bmap is a vector containing the bitmap data, and get_subpixel is simply a function that treats the bitmap as a 3D array composed of X x Y x Channel pixels, and returns a single sub-pixel at the specified coordinates.

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 ,则该算法可以正常工作:

This code works perfectly for certain destination sizes. For example, if the original bitmap is 500 X 366, and the destination size is 250 x 183, the algorithm works perfectly:

原始:

调整大小:

Original:

Resized:

但是,对于某些其他目标尺寸,例如 100 x 73 ,目标图像会失真:

However, for certain other destination sizes, such as 100 x 73, the destination image is distorted:

我一直在查看插值代码,但看不到自己在做错什么.

I've been going over the interpolation code, and I can't see what I'm doing incorrectly.

对于任何提示,建议或答案,我将不胜感激.

I'd appreciate any hints, suggestions, or answers.

推荐答案

除了混合浮点数和整数算术之外,我怀疑您的某些中间值还会出现数字上溢/下溢.

Aside from mixing floating point and integer arithmetic, I suspect you're getting numeric overflow / underflow with some of your intermediate values.

一个简单的解决方法是保持一致并在整个过程中使用浮点数.现在您有:

One easy fix is to just be consistent and use floating point throughout. Right now you have:

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位数据转换为双精度浮点数,然后赋值将其截断.

You have a mixture of unsigned char, int and double. Each one of those 1.0 / 3 converts your 8-bit data to double-precision float, and then the assignment truncates it back.

相反,为什么不只在整个范围内使用float?

Instead, why not just use float throughout?

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:

Then define a function saturate that does this:

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

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

That will fix your overflow issues, and give you better precision and likely better performance too.

如果需要进一步提高性能,则应研究定点算法.但就目前而言,我认为上述实施效果更好.

If you need to further improve the performance, then you should look into fixed point arithmetic. But for now, I think the above implementation is better.

另外,还有一个想法:通过预计算dx * dxdx * dx * dx等,您应该能够进一步提高效率:

Also, one other thought: You should be able to get some further efficiencies by precomputing dx * dx, dx * dx * dx, and so on:

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 );
            }
        }
    }
}

这篇关于实现双三次调整大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆