欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RANSAC 激光雷达地面检测 (1)

程序员文章站 2022-07-14 21:12:17
...

RANSAC 激光雷达地面检测 (1)

(感谢前辈)转自:https://zhuanlan.zhihu.com/p/71177362


用随机抽样一致性方法做地面检测,主要思路就是不断的随机抽取三个点构建一个平面方程,如果这个平面包含了足够多的点,那么就认为它是地面;

代码如下,比较高兴的一点是经过优化后,python的这个代码居然跑的比 c++ 这部分的代码还要快了,只要20ms 就能找到平面,矩阵运算真心牛。

import numpy as np
import time
import random
import math

def my_ransac_v3(data,
              distance_threshold=0.3,
              P=0.99,
              sample_size=3,
              max_iterations=10000,
              ):
    """

    :param data:  n*3
    :param sample_size:
    :param P :
    :param distance_threshold:
    :param max_iterations:
    :return:
    """
    # np.random.seed(12345)
    random.seed(12345)
    max_point_num = -999
    i = 0
    K = 10
    L_data = len(data)
    R_L = range(L_data)


    while i < K:
        # 随机选3个点  np.random.choice 很耗费时间,改为random模块
        # s3 = np.random.choice(L_data, sample_size, replace=False)
        s3 = random.sample(R_L, sample_size)

        if abs(data[s3[0],1] - data[s3[1],1]) < 3:
            continue



        # 计算平面方程系数
        coeffs = estimate_plane(data[s3,:], normalize=False)
        if coeffs is None:
            continue
        # 法向量的模, 如果系数标准化了就不需要除以法向量的模了
        r = np.sqrt(coeffs[0]**2 + coeffs[1]**2 + coeffs[2]**2 )
        # 计算每个点和平面的距离,根据阈值得到距离平面较近的点数量
        d = np.divide(np.abs(np.matmul(coeffs[:3], data.T) + coeffs[3]) , r)
        # d = abs(np.matmul(coeffs[:3], data.T) + coeffs[3]) / r
        d_filt = np.array(d < distance_threshold)

        near_point_num = np.sum(d_filt,axis=0)


        if near_point_num > max_point_num:
            max_point_num = near_point_num
            # TODO 这是是否还有一个重新拟合模型的步骤,
            #  有的话怎么用多个点求一个平面
            best_model = coeffs
            best_filt = d_filt

            # 最大迭代次数K,是P的函数, 因为迭代次数足够多,就一定能找到最佳的模型,
            # P是模型提供理想需要结果的概率,也可以理解为模型的点都是内点的概率?
            # P = 0.99
            # 当前模型,所有点中随机抽取一个点,它是内点的概率
            w = near_point_num / L_data
            # np.power(w, 3)是随机抽取三个点,都是内点的概率
            # 1-np.power(w, 3),就是三个点至少有一个是外点的概率,
            # 也就是得到一个坏模型的概率;
            # 1-P 代表模型永远不会选出一个3个点都是内点的集合的概率,
            # 也就是至少有一个外点;
            # K 就是得到的需要尝试多少次才会得到当前模型的理论次数
            # TODO 完善对该理论最大论迭代次数的理解
            wn = np.power(w, 3)
            p_no_outliers = 1.0 - wn
            # sd_w = np.sqrt(p_no_outliers) / wn
            K = (np.log(1-P) / np.log(p_no_outliers)) #+ sd_w
        # print('# K:', i, K, near_point_num)

        i += 1

        if i > max_iterations:
            print(' RANSAC reached the maximum number of trials.')
            break

    print('took iterations:', i+1, 'best model:', best_model,
          'explains:', max_point_num)
    return np.argwhere(best_filt).flatten(), best_model



def estimate_plane(xyz, normalize=True):
    """
    已知三个点,根据法向量求平面方程;
    返回平面方程的一般形式的四个系数
    :param xyz:  3*3 array
    x1 y1 z1
    x2 y2 z2
    x3 y3 z3
    :return: a b c d

      model_coefficients.resize (4);
      model_coefficients[0] = p1p0[1] * p2p0[2] - p1p0[2] * p2p0[1];
      model_coefficients[1] = p1p0[2] * p2p0[0] - p1p0[0] * p2p0[2];
      model_coefficients[2] = p1p0[0] * p2p0[1] - p1p0[1] * p2p0[0];
      model_coefficients[3] = 0;
      // Normalize
      model_coefficients.normalize ();
      // ... + d = 0
      model_coefficients[3] = -1 * (model_coefficients.template head<4>().dot (p0.matrix ()));

    """
    vector1 = xyz[1,:] - xyz[0,:]
    vector2 = xyz[2,:] - xyz[0,:]

    # 共线性检查 ; 0过滤
    if not np.all(vector1):
        # print('will divide by zero..', vector1)
        return None
    dy1dy2 = vector2 / vector1
    # 2向量如果是一条直线,那么必然它的xyz都是同一个比例关系
    if  not ((dy1dy2[0] != dy1dy2[1])  or  (dy1dy2[2] != dy1dy2[1])):
        return None


    a = (vector1[1]*vector2[2]) - (vector1[2]*vector2[1])
    b = (vector1[2]*vector2[0]) - (vector1[0]*vector2[2])
    c = (vector1[0]*vector2[1]) - (vector1[1]*vector2[0])
    # normalize
    if normalize:
        # r = np.sqrt(a ** 2 + b ** 2 + c ** 2)
        r = math.sqrt(a ** 2 + b ** 2 + c ** 2)
        a = a / r
        b = b / r
        c = c / r
    d = -(a*xyz[0,0] + b*xyz[0,1] + c*xyz[0,2])
    # return a,b,c,d
    return np.array([a,b,c,d])

优化速度的核心有两点,一是点到平面距离用矩阵运算代替循环计算; 二是随机抽样用random 模块,而不是np.random,numpy 的随机模块不知道为什么这么慢;

这个方法找平面的缺点有两个,一是会把墙壁当作地面,二是对于斜坡无能为力,我试了下 kitti 数据,大概100张里面会出现1张第一种情况,会出现10来张第二种情况;

正常检测:

RANSAC 激光雷达地面检测 (1)
误检测墙壁:RANSAC 激光雷达地面检测 (1)
漏掉斜坡:RANSAC 激光雷达地面检测 (1)
后续我会分享怎么去进一步改进这个方法,让它克服这两个缺点。