【问题标题】:OpenCV FAST corner detection SSE implementation walkthroughOpenCV FAST 角点检测 SSE 实现演练
【发布时间】:2015-09-02 02:44:39
【问题描述】:

有人可以帮助我了解 OpenCV 中 FAST 角点检测的 SSE 实现吗?我了解算法,但不了解实现。有人可以帮我看一下代码吗?

代码很长,先谢谢了。

我使用的是 OpenCV 2.4.11,代码如下:

__m128i delta = _mm_set1_epi8(-128);
__m128i t = _mm_set1_epi8((char)threshold);
__m128i m0, m1;
__m128i v0 = _mm_loadu_si128((const __m128i*)ptr);

我觉得下面跟阈值检查有关系,但是不能理解delta

的使用
__m128i v1 = _mm_xor_si128(_mm_subs_epu8(v0, t), delta);
v0 = _mm_xor_si128(_mm_adds_epu8(v0, t), delta);

现在它检查相邻的 4 个像素,但是再次,delta 有什么用?

__m128i x0 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[0])), delta);
__m128i x1 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[4])), delta);
__m128i x2 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[8])), delta);
__m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[12])), delta);
m0 = _mm_and_si128(_mm_cmpgt_epi8(x0, v0), _mm_cmpgt_epi8(x1, v0));
m1 = _mm_and_si128(_mm_cmpgt_epi8(v1, x0), _mm_cmpgt_epi8(v1, x1));
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x1, v0), _mm_cmpgt_epi8(x2, v0)));
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x1), _mm_cmpgt_epi8(v1, x2)));
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x2, v0), _mm_cmpgt_epi8(x3, v0)));
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x2), _mm_cmpgt_epi8(v1, x3)));
m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x3, v0), _mm_cmpgt_epi8(x0, v0)));
m1 = _mm_or_si128(m1, _mm_and_si128(_mm_cmpgt_epi8(v1, x3), _mm_cmpgt_epi8(v1, x0)));
m0 = _mm_or_si128(m0, m1);

在这里它检查相邻像素的连续性。 (对吧?)

int mask = _mm_movemask_epi8(m0);
if( mask == 0 )
    continue;

这对我来说是另一个难题。为什么要向左移动 8 个字节?我假设 ma​​sk 告诉我角候选的位置,但为什么是 8 个字节?

if( (mask & 255) == 0 )
{
    j -= 8;
    ptr -= 8;
    continue;
}

此时我放弃了……

__m128i c0 = _mm_setzero_si128(), c1 = c0, max0 = c0, max1 = c0;
for( k = 0; k < N; k++ )
{
    __m128i x = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(ptr + pixel[k])), delta);
    m0 = _mm_cmpgt_epi8(x, v0);
    m1 = _mm_cmpgt_epi8(v1, x);

    c0 = _mm_and_si128(_mm_sub_epi8(c0, m0), m0);
    c1 = _mm_and_si128(_mm_sub_epi8(c1, m1), m1);

    max0 = _mm_max_epu8(max0, c0);
    max1 = _mm_max_epu8(max1, c1);
}

max0 = _mm_max_epu8(max0, max1);
int m = _mm_movemask_epi8(_mm_cmpgt_epi8(max0, K16));

for( k = 0; m > 0 && k < 16; k++, m >>= 1 )
    if(m & 1)
    {
        cornerpos[ncorners++] = j+k;
        if(nonmax_suppression)
            curr[j+k] = (uchar)cornerScore<patternSize>(ptr+k, pixel, threshold);
    }

【问题讨论】:

    标签: c performance opencv optimization sse


    【解决方案1】:

    正如 harold 所说,delta 用于进行无符号比较。

    让我们分步描述这个实现:

    1. __m128i x0 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[0])), delta); __m128i x1 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[4])), delta); __m128i x2 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[8])), delta); __m128i x3 = _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(ptr + pixel[12])), delta); m0 = _mm_and_si128(_mm_cmpgt_epi8(x0, v0), _mm_cmpgt_epi8(x1, v0)); m1 = _mm_and_si128(_mm_cmpgt_epi8(v1, x0), _mm_cmpgt_epi8(v1, x1)); m0 = _mm_or_si128(m0, _mm_and_si128(_mm_cmpgt_epi8(x1, v0), _mm_cmpgt_epi8(x2, v0))); ......

    这里不检查 4 个相邻像素。它检查 4 个点,例如,像这样:

    1. 在这里,他们检查这 4 个点的“角条件”是否为真,因为如果不正确,则没有 8 个相邻像素满足“角条件”,因此它不是角像素。如果掩码为零,则意味着向量中的所有像素都不能是角点,因此我们向左移动 16 个像素。
    int mask = _mm_movemask_epi8(m0);
    if( mask == 0 )
        continue;
    
    1. 如果掩码不为零,但前 8 个像素的“角条件”不正确,则它们仅向左移动 8 个像素,以便在下一次迭代时检查剩余像素。
    if( (mask & 255) == 0 )
    {
        j -= 8;
        ptr -= 8;
        continue;
    }
    
    1. 最后一步。在这里,他们计算大于x + thresholdc0 计数器和小于x - thresholdc1 计数器的相邻像素数。

    这里为这种情况生成掩码:

    __m128i x = _mm_xor_si128(_mm_loadu_si128((const __m128i*)(ptr + pixel[k])), delta);
    m0 = _mm_cmpgt_epi8(x, v0);
    m1 = _mm_cmpgt_epi8(v1, x);
    

    请注意,如果向量元素的条件为真,则其值设置为 0xFF 或 -1,因为我们将他视为有符号字符。

    c0 = _mm_and_si128(_mm_sub_epi8(c0, m0), m0); 
    c1 = _mm_and_si128(_mm_sub_epi8(c1, m1), m1);
    

    如果掩码的元素为 -1,则由于减法,它会累积到 c0c1 计数器(例如 c0 - (-1))。但如果它等于零,它们会将计数器重置为零 (_mm_and_si128)。

    比他们需要存储的计数器的最大值:

    max0 = _mm_max_epu8(max0, c0);
    max1 = _mm_max_epu8(max1, c1);
    

    因此它们存储满足“角条件”的最大相邻像素数。

    在这里,它们确定哪些像素实际上是角,哪些不是:

    max0 = _mm_max_epu8(max0, max1);
    int m = _mm_movemask_epi8(_mm_cmpgt_epi8(max0, K16));
    
    for( k = 0; m > 0 && k < 16; k++, m >>= 1 )
        if(m & 1)
        {
            cornerpos[ncorners++] = j+k;
            if(nonmax_suppression)
                curr[j+k] = (uchar)cornerScore<patternSize>(ptr+k, pixel, threshold);
        }
    

    我希望它会有所帮助。对不起,我的英语不好。

    【讨论】:

    • "如果掩码不为零,但前 8 个像素的“角条件”不正确,则它们仅向左移动 8 个像素,以检查下一次迭代的剩余像素。"但是如果前 8 个像素中的第 4 个像素是一个角呢?不应该转移到mask中的第一个0xff吗?
    • 你是对的,计算前导零位并移动到特定数量的像素会更方便,但它可能过于复杂并导致性能下降。我认为这种策略对于 x86/64 CPU 的许多情况都是最佳的。
    【解决方案2】:

    delta 是一个仅设置符号位的掩码。他们使用它是因为他们想要比较大于无符号,但只有一个有符号比较。

    添加 128(或减去它,因为 -128 == 128)并对其进行异或运算(如果您使用字节),因为

    a + b == (a ^ b) + ((a & b) << 1)
    

    如果b 仅设置了最高位,则((a &amp; b) &lt;&lt; 1) 项必须为零(a &amp; b 可以设置最高位,但已移出)。

    然后您可以在下图中看到,减去 128“移位”整个范围,这样有符号比较将给出与原始范围的无符号比较相同的结果。

             |0 ... 127 ... 255|  unsigned
    |-128 ... 0 ... 127|          signed
    

    其他的我不知道,希望其他人能回答。

    【讨论】:

    • 感谢您的解释。但我必须接受@akarsakov 的回答
    猜你喜欢
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-11
    相关资源
    最近更新 更多