使用LaserScanSpeckleFilter过滤激光雷达噪点

之前一直在用indigo版本的 laser_filters ,最近更新了melodic版本的,发现多了 LaserScanSpeckleFilter 这个插件,试用之后发现效果还不错,分享给大家。

参数

  • filter_type (int)
    滤波算法选择
    • 0:基于距离的滤波(连续点的距离)
    • 1:基于半径离群点搜索的欧几里得距离滤除
  • max_range (double)
    只考虑比这个范围小的数据
  • max_range_difference (double)
    • 连续点的最大距离差(当filter_type为0)
    • 点之间的最大欧几里得距离差(当filter_type为1)
  • filter_window (double)
    滤波窗口

代码阅读

阅读 speckle_filter.hspeckle_filter.cpp ,发现主要函数为 update()checkWindowValid() ,滤波时会执行 update() 函数,而 update() 中使用 checkWindowValid() 判断激光点是否需要滤除。根据 filter_type 选择的不同,会调用不同的 checkWindowValid() 函数。下面继续查看 checkWindowValid() 函数。

  • filter_type 0:基于距离的滤波(连续点的距离)

    class DistanceWindowValidator : public WindowValidator
    {
        virtual bool checkWindowValid(const sensor_msgs::LaserScan& scan, size_t idx, size_t window, double max_range_difference)
        {
            const float& range = scan.ranges[idx];
            if (range != range) {
                return false;
            }
    
            for (size_t neighbor_idx_nr = 1; neighbor_idx_nr < window; ++neighbor_idx_nr)
            {
                size_t neighbor_idx = idx + neighbor_idx_nr;
                if (neighbor_idx < scan.ranges.size())  // Out of bound check
                {
                    const float& neighbor_range = scan.ranges[neighbor_idx];
                    if (neighbor_range != neighbor_range || fabs(neighbor_range - range) > max_range_difference)
                    {
                    return false;
                    }
                }
            }
            return true;
        }
    };
    

    这里原理比较简单,对于意外来源的随机噪声也是够用的,原理如下图所示:
    在这里插入图片描述

  • filter_type 1:基于半径离群点搜索的欧几里得距离滤波

    class RadiusOutlierWindowValidator : public WindowValidator
    {
    virtual bool checkWindowValid(const sensor_msgs::LaserScan& scan, size_t idx, size_t window, double max_distance)
    {
        int num_neighbors = 0;
        const float& r1 = scan.ranges[idx];
        float r2 = 0.;
    
        // Look around the current point until either the window is exceeded
        // or the number of neighbors was found.
        for (int y = -(int)window; y < (int)window + 1 && num_neighbors < (int)window; y++)
        {
            int j = idx + y;
            r2 = scan.ranges[j];
    
            if (j < 0 || j >= static_cast<int>(scan.ranges.size()) || idx == j || std::isnan(r2))
            {  // Out of scan bounds or itself or infinity
                continue;
            }
    
            // Explanation:
            //
            // Distance between two points:
            // d² = (x2 - x1)² + (y2 - y1)²
            //
            // Substitute x with r * cos(phi) and y with r * sin(phi):
            // d² = (r2 * cos(phi2) - r1 * cos(phi1))² + (r2 * sin(phi2) - r1 * sin(phi1))²
            //
            // Apply binomial theorem:
            // d² = ((r2² * cos(phi2)² + r1² * cos(phi1)² - 2 * r1 * r2 * cos(phi1) * cos(phi2)) +
            //      ((r2² * sin(phi2)² + r1² * sin(phi1)² - 2 * r1 * r2 * sin(phi1) * sin(phi2))
            //
            // Merge sums:
            // d² = r2² * (cos(phi2)² + sin(phi2)²) + r1² * (cos(phi1)² + sin(phi1)² -
            //      2 * r1 * r2 * (cos(phi1) * cos(phi2) + sin(phi1) * sin(phi2))
            //
            // Apply cos² + sin² = 1:
            // d² = r2² + r1² - 2 * r1 * r2 * (cos(phi1) * cos(phi2) + sin(phi1) * sin(phi2))
            //
            // Note the following:
            // cos(phi1) * cos(phi2) = 1/2 * (cos(phi1 - phi2) + cos(phi1 + phi2))
            // sin(phi1) * sin(phi2) = 1/2 * (cos(phi1 - phi2) - cos(phi1 + phi2))
            //
            // cos(phi1) * cos(phi2) + sin(phi1) * sin(phi2) = cos(phi1 - phi2)
            //
            // Finally, phi1 - phi2 is our included_angle.
    
            const float d = sqrt(
                    pow(r1,2) + pow(r2,2) -
                    (2 * r1 * r2 * cosf(y * scan.angle_increment)));
    
    
            if (d <= max_distance)
            {
                num_neighbors++;
            }
        }
    
        // consider the window to be the number of neighbors we need
        if (num_neighbors < window)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    };
    

    没看代码之前,我以为这里是用PCL库的 RadiusOutlier removal 实现的,阅读代码后发现这里实现的是比较巧妙的,如下图所示:
    在这里插入图片描述

    这里还有个问题是如何求 d ,代码中注释部分给出了解析法,其实可以用余弦定理或者使用几何法自行证明,已知两边长度及夹角,求第三边长度。( c 2 = a 2 + b 2 − 2 a b cos ⁡ C c^2=a^2+b^2-2ab\cos{C} c2=a2+b22abcosC

测试结果

scan_filter_chain:
- name: speckle_filter
  type: laser_filters/LaserScanSpeckleFilter
  params:
    filter_type: 0
    max_range: 2.0
    max_range_difference: 0.1
    filter_window: 2

过滤前可以看到有一处随机噪点,如下图所示:
在这里插入图片描述

过滤后噪点已被移除。
在这里插入图片描述


参考

官方wiki
插件源码

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值