如何使用 OpenGL 检测二值图像中的角点?

信息处理 图像处理 计算机视觉
2021-12-24 05:18:55

我有二进制 160x120 图像,例如:

原始图像

我想检测那些白色斑点的角落。它们以前被数学形态学封闭,因此不应该有任何内角。在这种特定情况下,我想要 16 个角,例如:

角点检测示例

我的第一次尝试是使用一些 OpenCV 函数,如goodFeaturesToTrackFAST,但它们特别慢(而且 FAST 非常不稳定)。我的想法是在 GPU 上进行这样的计算,因为我的源图像来自它。我在网上寻找有关如何编写此类着色器的想法(我使用的是 OpenGL ES 2.0),但没有发现任何具体的内容。知道如何启动这样的算法吗?

4个回答

我只是碰巧在使用 Harris 角点检测的 OpenGL ES 2.0 上实现了类似的东西,虽然我还没有完全完成,但我想我会分享我目前拥有的基于着色器的实现。我已将其作为基于 iOS 的开源框架的一部分完成,因此如果您对某些特定步骤的工作方式感到好奇,可以查看代码。

为此,我使用以下步骤:

  • 使用 RGB 值与向量 (0.2125, 0.7154, 0.0721) 的点积将图像降低到其亮度值。
  • 通过从当前像素的左右和上方和下方的像素中减去红色通道值来计算 X 和 Y 导数。然后我将 x 导数的平方存储在红色通道中,将 Y 导数的平方存储在绿色通道中,并将 X 和 Y 导数的乘积存储在蓝色通道中。片段着色器如下所示:

    precision highp float;
    
    varying vec2 textureCoordinate;
    varying vec2 leftTextureCoordinate;
    varying vec2 rightTextureCoordinate;
    
    varying vec2 topTextureCoordinate; 
    varying vec2 bottomTextureCoordinate;
    
    uniform sampler2D inputImageTexture;
    
    void main()
    {
     float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
     float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
     float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
     float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
    
     float verticalDerivative = abs(-topIntensity + bottomIntensity);
     float horizontalDerivative = abs(-leftIntensity + rightIntensity);
    
     gl_FragColor = vec4(horizontalDerivative * horizontalDerivative, verticalDerivative * verticalDerivative, verticalDerivative * horizontalDerivative, 1.0);
    }
    

    其中变量只是每个方向上的偏移纹理坐标。我在顶点着色器中预先计算了这些以消除依赖纹理读取,这在这些移动 GPU 上是出了名的慢。

  • 对此衍生图像应用高斯模糊。我使用了分离的水平和垂直模糊,并利用硬件纹理过滤来进行九次模糊,每次只有五次纹理读取。我在这个 Stack Overflow 答案中描述了这个着色器

  • 使用模糊的输入导数值运行实际的 Harris 角点检测计算。在这种情况下,我实际上使用的是 Alison Noble 在她的博士论文中描述的计算。论文“图像表面的描述”。处理此问题的着色器如下所示:

    varying highp vec2 textureCoordinate;
    
    uniform sampler2D inputImageTexture;
    
    const mediump float harrisConstant = 0.04;
    
    void main()
    {
     mediump vec3 derivativeElements = texture2D(inputImageTexture, textureCoordinate).rgb;
    
     mediump float derivativeSum = derivativeElements.x + derivativeElements.y;
    
     // This is the Noble variant on the Harris detector, from 
     // Alison Noble, "Descriptions of Image Surfaces", PhD thesis, Department of Engineering Science, Oxford University 1989, p45.     
     mediump float harrisIntensity = (derivativeElements.x * derivativeElements.y - (derivativeElements.z * derivativeElements.z)) / (derivativeSum);
    
     // Original Harris detector
     //     highp float harrisIntensity = derivativeElements.x * derivativeElements.y - (derivativeElements.z * derivativeElements.z) - harrisConstant * derivativeSum * derivativeSum;
    
     gl_FragColor = vec4(vec3(harrisIntensity * 10.0), 1.0);
    }
    
  • 执行局部非最大抑制并应用阈值以突出显示通过的像素。我使用以下片段着色器对中心像素附近的八个像素进行采样,并确定它是否是该分组中的最大值:

    uniform sampler2D inputImageTexture;
    
    varying highp vec2 textureCoordinate;
    varying highp vec2 leftTextureCoordinate;
    varying highp vec2 rightTextureCoordinate;
    
    varying highp vec2 topTextureCoordinate;
    varying highp vec2 topLeftTextureCoordinate;
    varying highp vec2 topRightTextureCoordinate;
    
    varying highp vec2 bottomTextureCoordinate;
    varying highp vec2 bottomLeftTextureCoordinate;
    varying highp vec2 bottomRightTextureCoordinate;
    
    void main()
    {
        lowp float bottomColor = texture2D(inputImageTexture, bottomTextureCoordinate).r;
        lowp float bottomLeftColor = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
        lowp float bottomRightColor = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
        lowp vec4 centerColor = texture2D(inputImageTexture, textureCoordinate);
        lowp float leftColor = texture2D(inputImageTexture, leftTextureCoordinate).r;
        lowp float rightColor = texture2D(inputImageTexture, rightTextureCoordinate).r;
        lowp float topColor = texture2D(inputImageTexture, topTextureCoordinate).r;
        lowp float topRightColor = texture2D(inputImageTexture, topRightTextureCoordinate).r;
        lowp float topLeftColor = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
    
        // Use a tiebreaker for pixels to the left and immediately above this one
        lowp float multiplier = 1.0 - step(centerColor.r, topColor);
        multiplier = multiplier * 1.0 - step(centerColor.r, topLeftColor);
        multiplier = multiplier * 1.0 - step(centerColor.r, leftColor);
        multiplier = multiplier * 1.0 - step(centerColor.r, bottomLeftColor);
    
        lowp float maxValue = max(centerColor.r, bottomColor);
        maxValue = max(maxValue, bottomRightColor);
        maxValue = max(maxValue, rightColor);
        maxValue = max(maxValue, topRightColor);
    
        gl_FragColor = vec4((centerColor.rgb * step(maxValue, centerColor.r) * multiplier), 1.0);
    }
    

此过程会从您的对象生成一个角点图,如下所示:

拐角地图

基于非最大抑制和阈值处理,以下点被识别为角点:

已识别的角落

通过为该过滤器设置适当的阈值,它可以识别该图像中的所有 16 个角,尽管它确实倾向于将角放置在对象的实际边缘内一个像素左右。

在 iPhone 4 上,这种角点检测可以在来自相机的 640x480 帧视频上以 20 FPS 的速度运行,而 iPhone 4S 可以轻松地以 60+ FPS 的速度处理该尺寸的视频。对于这样的任务,这应该比 CPU-bound 处理快很多,尽管现在读回点的过程是 CPU-bound 并且比它应该的慢一点。

如果你想看到它的实际效果,你可以获取我的框架的代码并运行它附带的 FilterShowcase 示例。那里的 Harris 角点检测示例在来自设备摄像头的实时视频上运行,尽管正如我提到的,角点的回读当前发生在 CPU 上,这确实减慢了速度。为此,我也正在转向基于 GPU 的进程。

您正在处理什么尺寸的图像?以什么帧速率?在什么硬件上?根据我的经验,FAST 很漂亮,嗯,很快。

我还看到 FAST 用作 ROI 检测器,在确定的 ROI 上运行 goodFeaturesToTrack 以提供更好的稳定性,而不会在整个图像上运行 gFTT 的惩罚。

Harris”角点检测器也可能非常快,因为它由非常简单的操作组成(例如每个像素没有 sqrt()!) - 不如 gFTT 稳定,但可能比 FAST 更稳定。

(在 GPU 实现方面,谷歌搜索gpu corner似乎提供了相当多的链接,但我不知道它们有多合适——我倾向于在 FPGA 中实现。)

众所周知,像 Shi-Tomasi 和 Moravec 这样的“鲁棒”角检测器速度很慢。在这里检查它们 - http://en.wikipedia.org/wiki/Corner_detection FAST 可能是唯一足够好的轻量级角检测器。您可以通过进行非最大抑制来提高 FAST - 选择具有最佳“角点”分数的 FAST 输出(有几种直观的计算方法,包括 Shi-Tomasi 和 Moravec 作为角点分数)您还可以从几个 FAST 检测器中进行选择 -从 FAST-5 到 FAST-12 和 FAST_ER(最后一个可能对于移动设备来说太大了)另一种方法是生成 FAST - 从作者站点获取 FAST 代码生成器并在一组可能的图像上对其进行训练。 http://www.edwardrosten.com/work/fast.html

不是真正特定于 GPU 的,但Steve Smith 的 SUSAN 算法适用于角点检测。

该算法非常简单,如C 中的源代码所示。