【问题标题】:How can i optimize this erosion filter code?我该如何优化这个侵蚀过滤器代码?
【发布时间】:2019-09-16 14:59:28
【问题描述】:

我已经实现了将侵蚀滤镜应用于图像的函数

void applyErosionFilter(QImage &input, int matrixSize)
{
    int filterOffset = (matrixSize - 1) / 2;
    int byteOffset = 0;

    uchar red, green, blue = 0;
    uchar morphResetValue = 255;

    uchar *data = input.bits();
    int stride = input.bytesPerLine();
    uchar *newdata = new uchar[stride * input.height()];

    int i = 0;
    for (int y = filterOffset; y < input.height() - filterOffset; y++)
    {
        for (int x = filterOffset; x < input.width() - filterOffset; x++)
        {
            byteOffset = y * stride + x * 4;

            red = morphResetValue;
            green = morphResetValue;
            blue = morphResetValue;

            for (int filterY = -filterOffset; filterY <= filterOffset; filterY++)
            {
                for (int filterX = -filterOffset; filterX <= filterOffset; filterX++)
                {
                    i = byteOffset + (filterX * 4) + (filterY * stride);

                    if (data[i] < red)
                        red = data[i];

                    if (data[i + 1] < green)
                        green = data[i + 1];

                    if (data[i + 2] < blue)
                        blue = data[i + 2];
                }
            }

            newdata[byteOffset] = red;
            newdata[byteOffset + 1] = green;
            newdata[byteOffset + 2] = blue;
            newdata[byteOffset + 3] = 255;
        }
    }

    input = input.fromImage(QImage(newdata, input.width(), input.height(), QImage::Format::Format_ARGB32));
    delete [] newdata;
}

它工作得很好,但我一直想知道是否有另一种方法可以更有效地做到这一点,也许有一种方法可以使用 openGL 在 GPU 上执行计算。

【问题讨论】:

  • 这可以显着优化,如果matrixSize(我们称之为N)大于3。首先,您可以将正方形NxN结构元素组合为长度为N的两条线SE。接下来,每条线的 SE 可以在每个像素 O(1) 中计算,与 N 无关。
  • 我能想到的最简单的方法是并行计算多行的newdata 值(即使用多个线程)。您绝对可以使用 OpenCL / OpenGL,但这需要大量样板才能开始。
  • 矩阵大小有多大?
  • @CrisLuengo:给 OP 一个参考是公平的。

标签: c++ qt opengl image-processing optimization


【解决方案1】:

作为一种快速改进,我建议使用多个线程并行计算多行。您也可以使用 OpenCL 或 Cuda 在 GPU 上实现此目的,但这需要大量样板文件。

我已修改您的代码以使用多线程,但我尚未对此进行测试,因为我目前没有在此设备上安装 Qt。但这至少可以提示您从哪里开始。

(顺便说一句,uchar red, green, blue = 0; 仅将 blue 初始化为 0,而 redgreen 保持未初始化)

#include <thread>
#include <vector>
void applyErosionFilter(QImage &input, int matrixSize)
{
    int filterOffset = (matrixSize - 1) / 2;
    int byteOffset = 0;

    uchar morphResetValue = 255;

    uchar *data = input.bits();
    int stride = input.bytesPerLine();
    uchar *newdata = new uchar[stride * input.height()];

    unsigned num_threads = std::thread::hardware_concurrency();
    if (num_threads == 0)
        num_threads = 1;
    std::vector<std::thread> threads;
    int i = 0;
    for (unsigned i = 0; i < num_threads; ++i)
    {
        int start = (input.height() - 2 * filterOffset) * i / num_threads;
        int end = (input.height() - 2 * filterOffset) * (i+1) / num_threads;
        threads.emplace_back([&](int start_line, int end_line){
            unsigned char red = 0;
            unsigned char green = 0;
            unsigned char blue = 0;
            for (int y = start_line; y < end_line; y++)
            {
                for (int x = filterOffset; x < input.width() - filterOffset; x++)
                {
                    byteOffset = y * stride + x * 4;

                    red = morphResetValue;
                    green = morphResetValue;
                    blue = morphResetValue;

                    for (int filterY = -filterOffset; filterY <= filterOffset; filterY++)
                    {
                        for (int filterX = -filterOffset; filterX <= filterOffset; filterX++)
                        {
                            i = byteOffset + (filterX * 4) + (filterY * stride);

                            if (data[i] < red)
                                red = data[i];

                            if (data[i + 1] < green)
                                green = data[i + 1];

                            if (data[i + 2] < blue)
                                blue = data[i + 2];
                        }
                    }

                    newdata[byteOffset] = red;
                    newdata[byteOffset + 1] = green;
                    newdata[byteOffset + 2] = blue;
                    newdata[byteOffset + 3] = 255;
                }
            }
        },
            start, end);
    }
    for (auto &thread : threads)
        thread.join();

    input = input.fromImage(QImage(newdata, input.width(), input.height(), QImage::Format::Format_ARGB32));
    delete [] newdata;
}

【讨论】:

  • 我同意你的观点,如果这段代码是多线程的,它的执行速度会比这快得多,但问题是这个函数是从一个已经使用的循环中调用的多线程。这就是为什么我想利用 GPU 未使用的计算能力
  • @MostafaMahmoud 好的,在这种情况下,我认为 OpenGL/CL 将是最便携的方式。由于您使用的是 Qt,您应该能够为 QOffscreenSurface 创建一个 OpenGL 上下文,从您的 QImage 创建一个纹理并编写一个实现侵蚀过滤器的着色器。然后使用纹理和着色器渲染一个四边形并将结果存储在QOpenGLFramebufferObject 中,该QOpenGLFramebufferObject 转换回QImage。你甚至不需要计算着色器。
  • 我不知道如何编写一个在 OpenGL plus 中实现过滤器的着色器,这不会是一个很长的路要走这么简单的任务吗?无论如何,我想尝试这种方式,所以如果有人可以提供一些代码示例,这将加快我的进度
  • 一般来说,对于这样的过滤器,你会用你的图像作为纹理来渲染一个四边形。在片段着色器中,您将像当前在两个内部循环中那样对纹理进行采样,并将片段颜色设置为结果。这些片段着色器在线有很多示例。 learnopengl.com/Advanced-Lighting/Bloom 例如包含一个片段着色器,它在“高斯模糊”部分下应用高斯模糊。该文章的其余部分与您的问题无关。
【解决方案2】:
  1. 使用多线程:将图像水平切割成带状,并分别处理。我是用 OpenMP 做的,它真的很困难。
  2. 大小为 NxN 的平方结构元素可以分解为大小为 1xN 和 Nx1 的两段(水平和垂直)。因此,您无需对每个像素进行 NxN 测试,而是进行 2xN:N=3 9 vs 6、n=5 25 vs 10 等等。速度要快得多!
  3. 使用已在以下库中实现的算法:SMIL,Matthieu Faessel(基于行比较的 C++ 自动矢量化代码,最快!!!),libmorpho,Marc Van Droogenbroeck(C++ 但有限到 8 位编码,如果我是对的),或者 Christophe Clienti 的 Fulguro(非常适合 SIMD 优化的朗伯算法)。正如您在这些库中看到的那样,它们使用智能方法/算法/架构来快速获得结果。您开发的是课程中讲授的基础知识,以便于理解,但它是最慢的!

【讨论】:

    【解决方案3】:

    谢谢大家。 我找到了我想要的东西,我想和你分享。

    QOpenGLTexture *m_texImageInput;
    QOpenGLShaderProgram *m_shaderComputeH;
    
    void initiateShader()
    {
        if (m_texImageInput)
        {
            delete m_texImageInput;
            m_texImageInput = nullptr;
        }
    
        QImage img(":/image.png");
        m_texImageInput = new QOpenGLTexture(img.convertToFormat(QImage::Format_RGBA8888).mirrored());
    
        if (m_shaderComputeH)
        {
            delete m_shaderComputeH;
            m_shaderComputeH = nullptr;
        }
    
        m_shaderComputeH = new QOpenGLShaderProgram;
        m_shaderComputeH->addShaderFromSourceFile(QOpenGLShader::Compute, ":/csErosionFilter.fsh");
        m_shaderComputeH->link();
    }
    
    QSize getWorkGroups(int workGroupSize, const QSize &imageSize)
    {
        int x = imageSize.width();
        x = (x % workGroupSize) ? (x / workGroupSize) + 1 : (x / workGroupSize);
        int y = imageSize.height();
        y = (y % workGroupSize) ? (y / workGroupSize) + 1 : (y / workGroupSize);
    
        return QSize(x, y);
    }
    
    void executeFilter(int radius)
    {
        QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
    
        // Process input image
        QSize workGroups = getWorkGroups(32, QSize(m_texImageInput->width(), m_texImageInput->height()));
        // Pass 2
        f->glBindImageTexture(0, m_texImageInput->textureId(), 0, 0, 0,  GL_READ_WRITE, GL_RGBA8);
        f->glBindImageTexture(1, m_texImageProcessed->textureId(), 0, 0, 0,  GL_READ_WRITE, GL_RGBA8);
        m_shaderComputeH->bind();
        m_shaderComputeH->setUniformValue("radius", radius);
        f->glDispatchCompute(workGroups.width(), workGroups.height(), 1);
        f->glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
        m_shaderComputeH->release();
        // Compute cleanup
        f->glBindImageTexture(0, 0, 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
        f->glBindImageTexture(1, 0, 0, 0, 0, GL_READ_WRITE, GL_RGBA8);
    }
    

    这是计算着色器

       /// csErosionFilter.fsh
        #version 430 core
        #define COMPUTEPATCHSIZE 32
        layout (local_size_x = COMPUTEPATCHSIZE, local_size_y = COMPUTEPATCHSIZE) in;
        layout(binding=0, rgba8) uniform readonly highp image2D inputImage;
        layout(binding=1, rgba8) uniform writeonly highp image2D resultImage;
        uniform int radius;
        void main()
        {
            ivec2 imgSize = imageSize(resultImage);
            int x = int(gl_GlobalInvocationID.x);
            int y = int(gl_GlobalInvocationID.y);
            if ((x >= imgSize.x) || (y >= imgSize.y))
                return;
    
            vec4 newValue = vec4(1);
            int left   = clamp(x - radius, 0, imgSize.x - 1);
            int right  = clamp(x + radius, 0, imgSize.x - 1);
            int top    = clamp(y - radius, 0, imgSize.y - 1);
            int bottom = clamp(y + radius, 0, imgSize.y - 1);
            for (int iX = left; iX <= right; iX++)
            {
                for (int iY = top; iY <= bottom; iY++)
                {
                    vec4 value = imageLoad(inputImage, ivec2(iX, iY));
                    if(value.x < newValue.x)
                        newValue.x = value.x;
                    if(value.y < newValue.y)
                        newValue.y = value.y;
                    if(value.z < newValue.z)
                        newValue.z = value.z;
                }
            }
    
            imageStore(resultImage, ivec2(x,y), newValue);
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-02
      • 1970-01-01
      • 1970-01-01
      • 2012-09-24
      • 1970-01-01
      • 2012-10-05
      • 2017-07-13
      • 1970-01-01
      相关资源
      最近更新 更多