【问题标题】:Gaussian blur in C++C++中的高斯模糊
【发布时间】:2021-05-17 08:37:49
【问题描述】:

我正在尝试在 C++ 中实现高斯模糊。但是,看起来无论半径如何,我总是会得到半径〜3的模糊。我想这可能与 alpha 预乘有关,但我无法弄清楚,我做错了什么。

实现如下:

extern "C" void __cdecl GaussianBlur(unsigned char* bitmapData, 
    int stride, 
    int width, 
    int height, 
    int radius)
{
    // Gaussian kernel

    int diameter = 2 * radius + 1;
    std::shared_ptr<float[]> kernel = generateGaussKernel(diameter);

    // Blur

    auto copy = std::shared_ptr<unsigned char[]>(new unsigned char[height * stride]);

    for (int y = 0; y < height; y++)
        memcpy(copy.get() + y * stride, bitmapData + y * stride, width * BYTES_PER_PIXEL);

    for (int y = 0; y < height; y++)
        for (int x = 0; x < width; x++)
        {
            Color sum;
            float weight = 0.0f;
            int count = 0;

            int xStart = x - radius;
            int xEnd = x + radius;
            int yStart = y - radius;
            int yEnd = y + radius;

            for (int x1 = xStart; x1 <= xEnd; x1++)
                for (int y1 = yStart; y1 <= yEnd; y1++)
                {
                    // Find weight

                    int kernelX = x1 - xStart;
                    int kernelY = y1 - yStart;
                    float kernelValue = kernel[kernelY * diameter + kernelX];

                    // Premultiply alpha

                    Color color;
                    if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height)
                        color = getColor(copy.get(), stride, x1, y1);
                    else
                        color = Color(0);

                    sum.R += (float)(color.R * color.A) * kernelValue;
                    sum.G += (float)(color.G * color.A) * kernelValue;
                    sum.B += (float)(color.B * color.A) * kernelValue;
                    sum.A += color.A * kernelValue;

                    weight += kernelValue;
                    count++;
                }

            if (count > 0)
            {
                Color result;
                result.A = sum.A / weight;

                if (result.A > 0)
                {
                    result.R = ((sum.R / weight) / result.A);
                    result.G = ((sum.G / weight) / result.A);
                    result.B = ((sum.B / weight) / result.A);
                }

                setColor(bitmapData, stride, x, y, result);
            }
        }
}

高斯核生成实现如下:

std::shared_ptr<float[]> generateGaussKernel(int diameter)
{
    float sigma = 1;
    std::shared_ptr<float[]> kernel(new float[diameter * diameter]);
    int mean = diameter / 2;
    float sum = 0.0; // For accumulating the kernel values
    for (int x = 0; x < diameter; ++x)
        for (int y = 0; y < diameter; ++y) {
            kernel[y * diameter + x] = (float)(exp(-0.5 * (pow((x - mean) / sigma, 2.0) + pow((y - mean) / sigma, 2.0))) / (2 * M_PI * sigma * sigma));

            // Accumulate the kernel values
            sum += kernel[y * diameter + x];
        }

    // Normalize the kernel
    for (int x = 0; x < diameter; ++x)
        for (int y = 0; y < diameter; ++y)
            kernel[y * diameter + x] /= sum;

    return kernel;
}

和实用程序(如果相关):

const int BYTES_PER_PIXEL = 4;
const int B_OFFSET = 0;
const int G_OFFSET = 1;
const int R_OFFSET = 2;
const int ALPHA_OFFSET = 3;

inline float getAlpha(unsigned char* bitmap, int stride, int x, int y)
{
    return bitmap[y * stride + x * BYTES_PER_PIXEL + ALPHA_OFFSET] / 255.0f;
}

inline Color getColor(unsigned char* bitmap, int stride, int x, int y)
{
    Color result;
    result.A = bitmap[y * stride + x * BYTES_PER_PIXEL + ALPHA_OFFSET] / 255.0f;
    result.R = bitmap[y * stride + x * BYTES_PER_PIXEL + R_OFFSET];
    result.G = bitmap[y * stride + x * BYTES_PER_PIXEL + G_OFFSET];
    result.B = bitmap[y * stride + x * BYTES_PER_PIXEL + B_OFFSET];

    return result;
}

我做错了什么?


编辑:函数的调用方式:

[DllImport("Animator.Engine.Native.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void GaussianBlur(IntPtr bitmapData,
    int stride,
    int width,
    int height,
    int radius);

//(...)

internal override void Apply(BitmapBuffer framebuffer, BitmapBuffer backBuffer, BitmapBuffer frontBuffer, BitmapBufferRepository repository)
{
    var data = framebuffer.Bitmap.LockBits(new System.Drawing.Rectangle(0, 0, framebuffer.Bitmap.Width, framebuffer.Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    ImageProcessing.GaussianBlur(data.Scan0, 
        data.Stride, 
        data.Width, 
        data.Height, 
        Radius);

        framebuffer.Bitmap.UnlockBits(data);
}

编辑 2

(错误的)效果:


编辑 3

整个项目是开源的,repo链接:https://gitlab.com/spook/Animator.git

如果你想测试它,使用以下示例 xml 运行 Animator.Editor/Animator.Editor 项目:

<Movie>
    <Movie.Config>
        <MovieConfig FramesPerSecond="30" Height="200" Width="200" />
    </Movie.Config>
    
    <Scene Name="Scene1" Background="White">
        <Rectangle Position="0;0" Width="20" Height="20" Brush="Red">
            <Rectangle.Effects>
                <GaussianBlurEffect Radius="7" />
            </Rectangle.Effects>
        </Rectangle>
    </Scene>
</Movie>

【问题讨论】:

  • 你在哪里调用函数GaussianBlur
  • @paladin,P/从 C# 代码调用。
  • 我想说的是,“如果函数GaussianBlur 总是用int radius = 3 调用怎么办?”请先检查一下。
  • @paladin,它不是。我检查了在本机代码中,半径是正确传递的。
  • 这看起来很可疑:float kernelValue = kernel[kernelY * diameter + kernelX];(在你的循环中)

标签: c++ gaussianblur


【解决方案1】:

错误在于生成高斯核的方式:

std::shared_ptr<float[]> generateGaussKernel(int diameter)
{
    float sigma = 1;

具有常数sigma,无论内核大小如何,高斯钟的形状始终相同。只需要将其修复为:

std::shared_ptr<float[]> generateGaussKernel(int diameter)
{
    float sigma = diameter / 4.0f;

...让它工作。

【讨论】:

    猜你喜欢
    • 2018-05-01
    • 1970-01-01
    • 2015-11-05
    • 2017-06-30
    • 1970-01-01
    • 2016-04-30
    • 2011-12-07
    • 2023-03-22
    • 1970-01-01
    相关资源
    最近更新 更多