3D Hough变换点云平面检测算法

简介: 3D Hough变换点云平面检测算法

本文的主要目标:1.介绍3D Hough Transform的应用场景,算法思路,算法步骤以及代码。2.对其应用场景进行更进一步分析,与相似用途的算法(RandSAC)进行比较,分析优缺点。

1.适用场景分析:

拟合问题也可以看成是在参数空间内进行的搜索。在我们遇到拟合问题时,我们需要解答的问题通常是以下几个方面中的一个:

已知点集合属于某一个平面(模型),这个模型的参数是多少?

点集中可能存在0到多个平面(模型),找到具体有多少模型实例?

找到点集中的哪些点对应哪些平面(模型)?

针对每一个问题,考虑到噪音的分布、计算的代价,一般都能找到比较适合的解决方法。Hough Tranform(下文称HT)方法,可以用于解决(但不一定是最适用)全部以上问题。在后面的具体分析中,我们会知道那种场景适合用哪种方法解决。

2.算法思路:

2.1“投票”算法

已知点集{p1,…,pn} 中存在平面以及一定数量的噪音,求解最好平面的参数m。拟合问题的解决思路可以用“投票(voting)”来概括:由点 pi向其符合的模型mx投票,得票者最多的模型胜出成为“最好平面”。从理论上来说,这种方法非常通用,不过,n个点的pi与数量不确定的模型之间的组合引发了可穷举性的问题,怎样确定模型的数量?HT解决的就是这个问题。

2.2法向量的转换

平面的方程:Ax+By+Cz+D=0,其中

为平面的法向量。D为原点(0,0,0)到平面的距离(有符号)。我们可以将法向量v⃗ 带入球极坐标系考虑。

在极坐标系下

因为 θ∈[0,2π],ϕ∈[0,2π],所以我们可以通过离散θ,ϕ以及原点到平面的距离ρ来实现对参数空间的离散化枚举。

2.3累加器

直线方程为 : (cosϕsinθ)x+(sinϕsinθ)y+cosθ+ρ=0,对于点pi,其针对所有的 (θ,ϕ)带入方程均可求得对应的ρ,因此每个点pi都可在参数空间形成了对应参数曲面如下图所示:

多个点对应的参数曲面会形成多个交点,其中交点最多的参数(θ,ϕ,ρ)即对应的点数最多的平面。如下图所示:

从实现的角度考虑,我们可以构建一个三维的数组,第三维保存符合平面的点的个数,称为累加器。针对每个点pi,均对其对应的(θ,ϕ,ρ)进行累加。

2.4需要考虑的问题:

以二维HT线检测为例,出于噪声的影响,我们认为的同一条线可能在HT空间上划分为多个(θ,ϕ,ρ) 因而引发其在累加器上的峰值变的模糊(fuzzy),如下图所示。

解决方案是在进行累加值峰值统计时,并不统计每个基本单元的点个数而得到最大值,而是对每个单元的一个邻域进行合并统计,可以帮助解决噪音导致的峰值分散的问题。

第二个问题是,我们认为平面法向量v(x,y,z)与(−x,−y,−z)是等同的,所以在θ,ϕ角度的值域上,我们仅选取半球θ∈[0,π],ϕ∈[0,π]即可。这样也缩小了计算量。同时,因为法向量的这种对称性,在统计累加器个数时,我们也要认为θ=0与θ=π是毗连的,对ϕ亦然。

3.针对具体应用的改进:

在特定的应用中,我们可能对平面的某些特征进行了更进一步的确定。比如说交通标牌,其法向量通常朝向行车方向,结合HT空间(θ,ϕ)的具体含义,我们可以缩小上述参数的值域范围,因此不但可以排除无关平面的“乱入”,而且极大的加速了算法的效率,减小了不小的一笔计算量。

4.分析以及与类似算法进行对比:

HT算法的优点:

所有点都是独立处理的,因此不受离群点的影响。

对噪音有一定的鲁棒性(鲁棒性较其他算法好)

能够进行多个参数实例(比如说多个不同平面)的识别,是HT的独门绝技。

HT的缺点:

计算量极大,计算复杂程度是参数个数的指数倍。

非目标要素会造成伪峰值(这一条在应用得到点云平面检测时并未发现)

参数离散化的步长比较不好选。

同为“投票”算法并且广泛使用的还有RandSAC算法。在拟合算法中,基本的算法还有最小二乘拟合方法。对这三种方法的适用场景、计算量、噪音影响等进行评估,可以得到下表:

5.参考文献:

1.The 3D Hough Transform for Plane Detection in Point Clouds:A Review and a new Accumulator Design

2.Feifei Li: Finding lines: from detection to model fittinig

3.Fitting: the Hough Transform

4.Least Squares. RandSAC.Hough Transform

6.实现代码:

按照参考文献1中的进行实现,文献1中的方位角为θ,俯仰角为ϕ,与本文以及通行的称呼正好相反,请读者在参考时注意区别。

#define  PI 3.141592653
void HoughTransform(const std::vector<Point>& input, double& A, double& B, double& C, double& D)
{
  int n = input.size();
  if (n < 3)
    return;
  double theta_start=0, theta_end=PI;
  double phi_start=0, phi_end=PI;
  //double phi_start = 0.25*PI, phi_end = 0.75*PI;
  double anglestep=PI/90, disstep=0.1;
  boundingbox box;
  calcboundbox(input, box);
  double d_start = -box.diag() / 2.0, d_end = box.diag() / 2.0;
  int thetas = ceil((theta_end - theta_start) / anglestep);
  int phis = ceil((phi_end - phi_start) / anglestep);
  int dises = ceil( box.diag()/disstep);
  int*** cube = new int**[thetas];
  for (int i = 0; i < thetas;++i)
  {
    cube[i] = new int*[phis];
    for (int j = 0; j < phis; ++j)
    {
      cube[i][j] = new int[dises];
      memset(cube[i][j], 0, sizeof(int)*dises);
    }
  }
  //cos(theta)sin(phi)X+sin(theta)sin(phi)Y+cos(phi)Z = D
  Point ptCenter = box.center();
  for (int i = 0; i < n;++i)
  {
    const Point& ptOrigin = input[i];
    Point point = ptOrigin - ptCenter;
    double theta = theta_start;
    for(int j = 0; j < thetas; ++j)
    {
      int** row = cube[j];
      double phi = phi_start;
      for (int k = 0; k < phis; ++k)
      {
        int* col = row[k];
        double sinphi = sin(phi);
        double d = cos(theta)*sinphi*point.x + sin(theta)*sinphi*point.y + cos(phi)*point.z;
        int d_index = floor((d - d_start) / disstep);
        ++(col[d_index]);
        phi += anglestep;
        if (phi > phi_end)
          break;
      }
      theta += anglestep;
      if (theta > theta_end)
        break;
    }
  }//all points
  int buf = 1;
  int maxcount = 0;
  int xmax, ymax, zmax;
  for (int i = 0; i < thetas;++i)
    for (int j = 0; j < phis; ++j)
        for (int k = buf; k < dises - buf;++k)
      {
        int count = 0;
        for (int x = i - buf; x <= i + buf; ++x)
          for (int y = j - buf; y <= j + buf; ++y)
            for (int z = k - buf; z <= k + buf; ++z)
            {
              count += cube[x<0?x+thetas:x%thetas][y<0?y+phis:y%phis][z];
            }
        if (count > maxcount)
        {
          xmax = i;
          ymax = j;
          zmax = k;
          maxcount = count;
        }
      }
  double theta = theta_start + xmax*anglestep;
  double phi = phi_start + ymax*anglestep;
  double d = d_start + zmax*disstep;
  A = cos(theta)*sin(phi);
  B = sin(theta)*sin(phi);
  C = cos(phi);
  D = -d - (A*ptCenter.x + B*ptCenter.y+C*ptCenter.z);
  //std::cout << A << " , " << B << " , " << C << " , "<< D << std::endl;
  //释放cube
  for (int i = 0; i < thetas; ++i)
  {
    int** row = cube[i];
    for (int j = 0; j < phis;++j)
    {
      int* col = row[j];
      delete[] col;
    }
    delete[] row;
  }
  delete[] cube;
}

依赖的 Point 以及 BoundingBox 的实现如下:

class Point
{
public: 
  double x, y, z;
  Point(double ix,double iy,double iz) :
    x(ix), y(iy), z(iz){}
  Point operator-(const Point& pt) const
  {
    return Point(x - pt.x, y - pt.y, z - pt.z);
  }
};
typedef Point Vector;
class boundingbox
{
public:
  double x_min, x_max;
  double y_min, y_max;
  double z_min, z_max;
public:
  double diag() const
  {
    double dx = x_max - x_min;
    double dy = y_max - y_min;
    double dz = z_max - z_min;
    return sqrt(dx*dx + dy*dy + dz*dz);
  }
  boundingbox():
    x_min(std::numeric_limits<double>::max()),
    y_min(std::numeric_limits<double>::max()),
    z_min(std::numeric_limits<double>::max()),
    x_max(-std::numeric_limits<double>::max()),
    y_max(-std::numeric_limits<double>::max()),
    z_max(-std::numeric_limits<double>::max())
  {}
  Point center() const
  {
    return Point((x_max + x_min) / 2.0,(y_min+y_max) / 2.0, (z_min+z_max) / 2.0);
  }
};
void calcboundbox(const std::vector<Point>& input, boundingbox& box)
{
  for (int i = 0, n = input.size(); i < n;++i)
  {
    auto point = input[i];
    if (point.x < box.x_min)
      box.x_min = point.x;
    if (point.y < box.y_min)
      box.y_min = point.y;
    if (point.z < box.z_min)
      box.z_min = point.z;
    if (point.x > box.x_max)
      box.x_max = point.x;
    if (point.y > box.y_max)
      box.y_max = point.y;
    if (point.z > box.z_max)
      box.z_max = point.z;
  }
}

参考原文:http://www.whudj.cn/?p=877

目录
相关文章
|
14天前
|
算法 JavaScript 前端开发
在JavaScript中实现基本的碰撞检测算法,我们通常会用到矩形碰撞检测,也就是AABB(Axis-Aligned Bounding Box)碰撞检测
【6月更文挑战第16天】JavaScript中的基本碰撞检测涉及AABB(轴对齐边界框)方法,常用于2D游戏。`Rectangle`类定义了矩形的属性,并包含一个`collidesWith`方法,通过比较边界来检测碰撞。若两矩形无重叠部分,四个条件(关于边界相对位置)均需满足。此基础算法适用于简单场景,复杂情况可能需采用更高级的检测技术或物理引擎库。
49 6
|
16天前
|
机器学习/深度学习 算法
基于BP神经网络和小波变换特征提取的烟草香型分类算法matlab仿真,分为浓香型,清香型和中间香型
```markdown 探索烟草香型分类:使用Matlab2022a中的BP神经网络结合小波变换。小波分析揭示香气成分的局部特征,降低维度,PCA等用于特征选择。BP网络随后处理这些特征,以区分浓香、清香和中间香型。 ```
|
5天前
|
算法 C++ 计算机视觉
详细解读Canny检测算法与实现
详细解读Canny检测算法与实现
|
10天前
|
机器学习/深度学习 算法 语音技术
基于语音信号MFCC特征提取和GRNN神经网络的人员身份检测算法matlab仿真
**语音识别算法概览** MATLAB2022a中实现,结合MFCC与GRNN技术进行说话人身份检测。MFCC利用人耳感知特性提取语音频谱特征,GRNN作为非线性映射工具,擅长序列学习,确保高效识别。预加重、分帧、加窗、FFT、滤波器组、IDCT构成MFCC步骤,GRNN以其快速学习与鲁棒性处理不稳定数据。适用于多种领域。
|
11天前
|
机器学习/深度学习 算法 计算机视觉
基于ADAS的车道线检测算法matlab仿真
**摘要:** 基于ADAS的车道线检测算法利用Hough变换和边缘检测在视频中识别车道线,判断车道弯曲情况,提供行驶方向信息,并高亮显示。在MATLAB2022a中实现,系统包括图像预处理(灰度化、滤波、边缘检测)、车道线特征提取(霍夫变换、曲线拟合)和车道线跟踪,确保在实时场景中的准确性和稳定性。预处理通过灰度转换减少光照影响,滤波去除噪声,Canny算法检测边缘。霍夫变换用于直线检测,曲线拟合适应弯道,跟踪则增强连续帧的车道线检测。
|
18天前
|
机器学习/深度学习 监控 算法
基于yolov2深度学习网络的昆虫检测算法matlab仿真,并输出昆虫数量和大小判决
YOLOv2算法应用于昆虫检测,提供实时高效的方法识别和定位图像中的昆虫,提升检测精度。核心是统一检测网络,预测边界框和类别概率。通过预测框尺寸估算昆虫大小,适用于农业监控、生态研究等领域。在matlab2022A上运行,经过关键升级,如采用更优网络结构和损失函数,保证速度与精度。持续优化可增强对不同昆虫的检测能力。![image.png](https://ucc.alicdn.com/pic/developer-ecology/3tnl7rfrqv6tw_e760ff6682a3420cb4e24d1e48b10a2e.png)
|
23天前
|
算法 计算机视觉
图像处理之基于采样距离变换算法
图像处理之基于采样距离变换算法
15 0
|
23天前
|
算法 计算机视觉
图像处理之霍夫变换圆检测算法
图像处理之霍夫变换圆检测算法
14 0
|
5天前
|
机器学习/深度学习 自然语言处理 算法
m基于深度学习的OFDM+QPSK链路信道估计和均衡算法误码率matlab仿真,对比LS,MMSE及LMMSE传统算法
**摘要:** 升级版MATLAB仿真对比了深度学习与LS、MMSE、LMMSE的OFDM信道估计算法,新增自动样本生成、复杂度分析及抗频偏性能评估。深度学习在无线通信中,尤其在OFDM的信道估计问题上展现潜力,解决了传统方法的局限。程序涉及信道估计器设计,深度学习模型通过学习导频信息估计信道响应,适应频域变化。核心代码展示了信号处理流程,包括编码、调制、信道模拟、降噪、信道估计和解调。
26 8
|
7天前
|
算法
基于GA遗传优化的混合发电系统优化配置算法matlab仿真
**摘要:** 该研究利用遗传算法(GA)对混合发电系统进行优化配置,旨在最小化风能、太阳能及电池储能的成本并提升系统性能。MATLAB 2022a用于实现这一算法。仿真结果展示了一系列图表,包括总成本随代数变化、最佳适应度随代数变化,以及不同数据的分布情况,如负荷、风速、太阳辐射、弃电、缺电和电池状态等。此外,代码示例展示了如何运用GA求解,并绘制了发电单元的功率输出和年变化。该系统原理基于GA的自然选择和遗传原理,通过染色体编码、初始种群生成、适应度函数、选择、交叉和变异操作来寻找最优容量配置,以平衡成本、效率和可靠性。