惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
H
Heimdal Security Blog
S
Secure Thoughts
Help Net Security
Help Net Security
The Hacker News
The Hacker News
T
Threatpost
T
Troy Hunt's Blog
T
Threat Research - Cisco Blogs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
TaoSecurity Blog
TaoSecurity Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Cisco Talos Blog
Cisco Talos Blog
Microsoft Security Blog
Microsoft Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
阮一峰的网络日志
阮一峰的网络日志
Security Latest
Security Latest
Forbes - Security
Forbes - Security
The Last Watchdog
The Last Watchdog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
A
About on SuperTechFans
C
Cybersecurity and Infrastructure Security Agency CISA
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
Google Online Security Blog
Google Online Security Blog
N
Netflix TechBlog - Medium
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
S
Securelist
Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell
G
GRAHAM CLULEY
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏
C
Cisco Blogs
量子位
有赞技术团队
有赞技术团队
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
SegmentFault 最新的问题
T
The Exploit Database - CXSecurity.com
MyScale Blog
MyScale Blog
A
Arctic Wolf
T
Tenable Blog
P
Privacy & Cybersecurity Law Blog

Nemo

再见,2025 Complete ORB-SLAM3 Setup Guide for Jetson Xavier NX with RealSense D455 20250723 再见,2024 Using CertBot for Automatic Secure EMQX Broker Create Your Own GPS Data Publisher Support SSL AGV Dispatching System Technical Documentation Finding Nemo No title 香港賽馬會呈獻系列:黑白——攝影敘事 再见,2023 团团是只猫 Design an FSM for Robot State Machines – Basics of Computer Science Data-driven robot lifespan: Collection 数据驱动的机器人寿命:收集、诊断、预测 Exploring the design space of binary search trees 特首来了 Large language models, explained with a minimum of math and jargon 设计有缓存异步逻辑的监控脚本并测试其资源占用 使用loguru记录串口数据并使用Docker搭建ARM开发环境 在vscode的Dev Container中构建.NET开发环境及使用doxygen和graphviz绘制函数调用图 State or Status? A*算法两种时间复杂度 /A* Algorithm: Two Types of Time Complexity 翻译 || 总结 - Go语言中的空结构体(The empty struct) 再见,2022 从PE工作报告中能读出什么 Give me miles, give me truth AMR调度系统性能优化/AMR Dispatch System Performance Optimization 条件触发AMR避让流程/Conditionally triggered AMR avoidance process Docker实现调度系统整体部署/Docker implementation of dispatching system overall deployment 调度系统中加密算法的使用/增加SM4补0方法/Use of encryption algorithms in scheduling systems/add SM4 complementary 0 method 一篇关于北京四天三夜的攻略 2022 藏疆自驾 策划/招募书 AMR仿真模型/AMR Simulation Model 东东有鱼2022年会分享 使用perf-FlameGraph监控系统性能 Beyond Compare 4删除试用 你的灵魂有香气 Pyinstaller打包Python项目 [转载]Python中的单例模式的几种实现方式的及优化 - Nemo 再见! 2020 1024 UML软件建模 入职优必选一个月 写论文时 那些让你开心的软件 - Nemo 分享一个记录刷题次数的模版 LeetCode-查找表类算法题精析 PyCharm 调用vs 2010 C++库导致提示报错R6034解决方法 MySQL基础教程 多种数据结构的Python实现形式 字节跳动-挑战字符串 选择排序 二分查找和大O表示法 算法中的动态规划问题 高高手课程-青山裕企人像摄影 笔记(内含福利)
使用KD-Tree快速收敛到最近坐标点/Fast convergence to the nearest coordinate point using KD-Tree
Nemo · 2023-05-07 · via Nemo

背景

在AMR物流机器人的实际使用场景中,要快速确定AMR距离哪个坐标点最近,可以使用空间数据结构和相应的算法,比如k-d树(k-dimensional tree,K:K维空间,D:dimension,KD-Tree即K维度树)作为空间数据结构,它可以高效地搜索多维数据空间。对于二维场景,k-d树即为二维树。以下是详细步骤:

  1. 构建k-d树: 首先,将所有二维码地图上的坐标点插入到k-d树中。在构建k-d树时,每个节点存储一个坐标点,通过轮流比较x轴和y轴坐标将点分配到左子树或右子树。这样可以在O(log n)时间内找到任何给定点的最近邻。

    KD树是一种数据结构,可以用于高效地解决k近邻搜索问题。在构建KD树时,可以使用不同的分割准则和分割顺序,因此可能会产生不同的KD树。

    常用的分割准则包括中位数分割、方差分割、最大值-最小值分割等等,分割顺序可以是轮流选择维度进行分割,也可以根据某种规则选择优先分割哪些维度。不同的分割准则和分割顺序会导致生成的KD树结构不同,但它们的本质都是相同的,都是二叉树结构。因此,KD树的生成方式不是唯一的,但都遵循相同的原理。

  2. 查询最近的坐标点: 当AMR定位完成后,它将有自己的坐标。此时,可以使用k-d树的最近邻搜索算法来找到距离AMR当前位置最近的坐标点。算法首先从根节点开始搜索,沿着k-d树向下移动,直到找到包含目标点的叶子节点。然后,算法向上回溯,检查子树中是否有更接近的点。如果有,将最近点更新为该点。整个过程的时间复杂度为O(log n)。

  3. 数据结构和算法实现: 为了实现k-d树和最近邻搜索算法,可以使用现有的库,如Python的SciPy库,Go的go-kdtree。这些库提供了实现k-d树及相关算法的函数。

KD-Tree的构建

简述过程,例如有以下二维坐标点:

A: (6, 2) B: (4, 1) C: (9, 6) D: (2, 4) E: (5, 7)

则k-d树构建算法如下:

  1. 选择根节点:从x轴(depth = 0)开始,找到中位数点作为根节点。在这个例子中,E(5, 7)是中位数点,将E设为根节点。使用某个坐标值的中位数作为根节点可以在一定程度避免Tree发生偏斜,左侧的点是D(2, 4)和B(4, 1),右侧的点是A(6, 2)和C(9, 6)。

    E (5, 7)
  2. 递归构建左子树:

    现在我们在y轴上找到左侧点的中位数。对点按y坐标排序:B(4, 1), D(2, 4)。中位数是点D(2, 4),因此它将成为左子树的根节点。

    E (5, 7)
    |
    D (2, 4)

    点B(4, 1)位于D的右侧,所以它成为D的右子节点。

       E (5, 7)
        |
       D (2, 4)
          \
           B (4, 1)
    
  3. 构建右子树

    现在我们在y轴上找到右侧点的中位数。对点按y坐标排序:A(6, 2), C(9, 6)。中位数是点A(6, 2),因此它将成为右子树的根节点。

       E (5, 7)
      / \
    D (2, 4) A (6, 2)
    

    点C(9, 6)位于A的右侧,所以它成为A的右子节点。

       E (5, 7)
      / \
    D (2, 4) A (6, 2)
              \
               C (9, 6)
    
  4. 停止条件:递归过程将继续,直到每个子数据集中的点数达到预定义的阈值,或者无法进一步划分为止。这时,子树将成为叶子节点,存储在该叶子节点中的数据点的列表。

应用这个算法,我们得到以下k-d树:

    E (5, 7)
   / \
D (2, 4) A (6, 2)
 \          \
B (4, 1)      C (9, 6)

结果不唯一,A点和C点y值与中位数距离相等,也会有:

E: [5 7]
  D: [2 4]
    B: [4 1]
  C: [9 6]
    A: [6 2]

AMR快速收敛到最近点

假设在这个场景中,有A: (6, 2) B: (4, 1) C: (9, 6) D: (2, 4) E: (5, 7)五个地图点,AMR坐标为(8, 4),则确定最近地图点的过程如下:

  1. 从根节点(E)开始,我们检查目标点与当前节点的X坐标(因为当前节点的深度为0,也就是第一个坐标轴):
    • 目标点的X坐标为8,E节点的X坐标为5,所以目标点在E节点的右子树。
  2. 继续前进到右子树的A节点(深度为1,按Y轴比较坐标):
    • 目标点的Y坐标为4,A节点的Y坐标为2,所以目标点在A节点的右子树。
  3. 继续前进到右子树的C节点(深度为2,按X轴比较坐标):
    • C节点没有子节点,所以搜索到达叶子节点。
  4. 开始回溯搜索路径,计算节点到目标点的距离:
    • 目标点与C节点的距离:sqrt((9-8)^2 + (6-4)^2) = 2.24
    • 将最近距离设置为2.24,最近节点设置为C。
  5. 回溯到A节点:
    • 计算目标点与A节点的距离:sqrt((6-8)^2 + (2-4)^2) = 2.83
    • 当前最近距离为2.24(C节点),2.83 > 2.24,因此A节点不会更新最近距离和最近节点。
  6. 回溯到根节点E:
    • 计算目标点与E节点的距离:sqrt((5-8)^2 + (7-4)^2) = 4.24
    • 当前最近距离为2.24(C节点),4.24 > 2.24,因此E节点不会更新最近距离和最近节点。
    • 由于目标点沿X轴与E节点的距离(8 – 5 = 3)大于当前最近距离(2.24),我们不需要检查E节点的左子树(D节点)
  7. 假如也检查下左子树D?
  8. 进入左子树的D节点(深度为1,按Y轴比较坐标):
    • 目标点的Y坐标为4,D节点的Y坐标为4,所以我们需要检查D节点的两个子树。
  9. 首先检查D节点的左子树(B节点):
    • 计算目标点与B节点的距离:sqrt((4-8)^2 + (1-4)^2) = 5
    • 当前最近距离为2.24(C节点),5 > 2.24,因此B节点不会更新最近距离和最近节点。
  10. 现在回溯到D节点,并检查D节点的右子树(不存在)。
    • 计算目标点与D节点的距离:sqrt((2-8)^2 + (4-4)^2) = 6
    • 当前最近距离为2.24(C节点),6 > 2.24,因此D节点不会更新最近距离和最近节点。
  11. 回溯到根节点E,完成搜索。

找到了距离目标点(8, 4)最近的节点是C(9, 6),距离为2.24。则AMR最近点为C点。

KDTree的Python和Go代码实现

Python代码示例

class Node:
    def __init__(self, point, left=None, right=None):
        self.point = point
        self.left = left
        self.right = right

def build_kdtree(points, depth=0):
    if not points:
        return None

    k = len(points[0])
    axis = depth % k

    points.sort(key=lambda x: x[axis])

    median = len(points) // 2

    return Node(
        points[median],
        left=build_kdtree(points[:median], depth + 1),
        right=build_kdtree(points[median + 1:], depth + 1)
    )

def print_kdtree(node, depth=0):
    if node:
        print(depth * "  " + str(node.point))
        print_kdtree(node.left, depth + 1)
        print_kdtree(node.right, depth + 1)

points = [
    (6, 2),
    (4, 1),
    (9, 6),
    (2, 4),
    (5, 7)
]

kdtree = build_kdtree(points)
print_kdtree(kdtree)

在这个实现中,我们首先定义了一个名为Node的类来表示k-d树的每个节点。该类包含一个表示节点的坐标点的属性point,以及两个分别表示左子树和右子树的属性leftright

然后,我们定义了一个名为build_kdtree的递归函数,用于构建k-d树。这个函数接受一个坐标点列表points和当前深度depth作为输入。首先,我们检查points是否为空。如果是,则返回None。接下来,我们根据当前深度选择划分轴,然后将点集按照当前划分轴的坐标值进行排序。然后,我们找到排序后点集的中位数点,并使用它来创建一个新的Node实例。我们递归地为中位数点左侧的点集构建左子树,为中位数点右侧的点集构建右子树。

最后,我们定义了一个名为print_kdtree的函数,用于以树状结构打印k-d树。这个函数接受一个Node实例和当前深度depth作为输入。我们首先打印当前节点的坐标点,然后递归地打印左子树和右子树。

在主程序中,我们使用给定的二维坐标点列表调用build_kdtree函数,构建k-d树。然后,我们使用print_kdtree函数打印k-d树的结构。

scipy工具实现
from scipy.spatial import KDTree

# 定义二维坐标点数据
points = [(2, 3), (5, 7), (8, 1), (4, 5)]

# 构建k-d树
kdtree = KDTree(points)

# 查询最近的坐标点
agv_position = (8, 4)
nearest_distance, nearest_index = kdtree.query(agv_position)
nearest_point = points[nearest_index]

print("最近的坐标点:", nearest_point)

也有blog比较scipy 和sklearn的运算速度,结果是sklearn 更好,可以自己尝试

Go代码示例

Go语言KDTree实现:

package main

import (
    "fmt"
    "math"
    "sort"
)

type Point struct {
    name       string
    coordinates []int
}

type KDNode struct {
    point     *Point
    axis      int
    left      *KDNode
    right     *KDNode
}

func buildKDTree(points []*Point, depth int) *KDNode {
    // 当点集为空时,返回nil
    if len(points) == 0 {
        return nil
    }

    // 计算当前深度的轴
    axis := depth % len(points[0].coordinates)

    // 根据当前轴对点集进行排序
    sort.Slice(points, func(i, j int) bool {
        return points[i].coordinates[axis] < points[j].coordinates[axis]
    })

    // 选择中位数点作为根节点
    median := len(points) / 2

    // 递归地构建左子树和右子树
    left := buildKDTree(points[:median], depth+1)
    right := buildKDTree(points[median+1:], depth+1)

    return &KDNode{
        point: points[median],
        axis:  axis,
        left:  left,
        right: right,
    }
}

func euclideanDistance(p1, p2 []int) float64 {
    sum := 0.0
    for i := 0; i < len(p1); i++ {
        diff := float64(p1[i] - p2[i])
        sum += diff * diff
    }
    return math.Sqrt(sum)
}

func findNearestNeighbor(node *KDNode, target *Point, depth int, best *Point) *Point {
    if node == nil {
        return best
    }

    axis := depth % len(target.coordinates)
    currentDist := euclideanDistance(node.point.coordinates, target.coordinates)
    bestDist := euclideanDistance(best.coordinates, target.coordinates)

    if currentDist < bestDist {
        best = node.point
    }

    var next, other *KDNode
    if target.coordinates[axis] < node.point.coordinates[axis] {
        next, other = node.left, node.right
    } else {
        next, other = node.right, node.left
    }

    best = findNearestNeighbor(next, target, depth+1, best)

    if math.Abs(float64(target.coordinates[axis]-node.point.coordinates[axis])) < bestDist {
        best = findNearestNeighbor(other, target, depth+1, best)
    }

    return best
}

func printKDTree(node *KDNode, depth int) {
    if node == nil {
        return
    }

    indent := ""
    for i := 0; i < depth; i++ {
        indent += "  "
    }

    fmt.Printf("%s%s: %v\n", indent, node.point.name, node.point.coordinates)

    printKDTree(node.left, depth+1)
    printKDTree(node.right, depth+1)
}

func main() {
    points := []*Point{
        {name: "A", coordinates: []int{6, 2}},
        {name: "B", coordinates: []int{4, 1}},
        {name: "C", coordinates: []int{9, 6}},
        {name: "D", coordinates: []int{2, 4}},
        {name: "E", coordinates: []int{5, 7}},
    }

    kdTree := buildKDTree(points, 0)
    printKDTree(kdTree, 0)

    target := &Point{name: "T", coordinates: []int{8, 4}}
    fmt.Printf("\nSearching nearestneighbor for point %s: %v\n", target.name, target.coordinates)
    nearest := findNearestNeighbor(kdTree, target, 0, points[0])
    fmt.Printf("Nearest neighbor: %s: %v\n", nearest.name, nearest.coordinates)

}
go-kdtree包示例
go get -u github.com/hongshibao/go-kdtree
package kdTreePak

import (
    "fmt"

    kdtree "github.com/hongshibao/go-kdtree"
)

type EuclideanPoint struct {
    kdtree.Point
    name string
    coordinates []float64
}

func (p *EuclideanPoint) Dim() int {
    return len(p.coordinates)
}

func (p *EuclideanPoint) GetValue(dim int) float64 {
    return p.coordinates[dim]
}

func (p *EuclideanPoint) Distance(other kdtree.Point) float64 {
    var ret float64
    for i := 0; i < p.Dim(); i++ {
        tmp := p.GetValue(i) - other.GetValue(i)
        ret += tmp * tmp
    }
    return ret
}

func (p *EuclideanPoint) PlaneDistance(val float64, dim int) float64 {
    tmp := p.GetValue(dim) - val
    return tmp * tmp
}

func NewEuclideanPoint(name string,vals ...float64) *EuclideanPoint {
    ret := &EuclideanPoint{name: name}
    for _, val := range vals {
        ret.coordinates = append(ret.coordinates, val)
    }
    return ret
}

func FindNearestNeighbor(target *EuclideanPoint, pointsInfo map[string][]float64) *EuclideanPoint {
    points := make([]kdtree.Point, 0)

    for name, coords := range pointsInfo {
        point := NewEuclideanPoint(name, coords...)
        points = append(points, point)
    }

    tree := kdtree.NewKDTree(points)
    nearest := tree.KNN(target, 1)
    nearestPoint := nearest[0].(*EuclideanPoint)
    fmt.Printf("Nearest neighbor: %s %v\n", nearestPoint.name, nearestPoint.coordinates)
    return nearestPoint
}

// func FindNearestNeighbor() *EuclideanPoint {

//  p1 := NewEuclideanPoint("A", 6, 2)
//  p2 := NewEuclideanPoint("B", 4, 1)
//  p3 := NewEuclideanPoint("C", 9, 6)
//  p4 := NewEuclideanPoint("D", 2, 4)
//  p5 := NewEuclideanPoint("E", 5, 7)

//  points := make([]kdtree.Point, 0)
//  points = append(points, p1)
//  points = append(points, p2)
//  points = append(points, p3)
//  points = append(points, p4)
//  points = append(points, p5)

//  tree := kdtree.NewKDTree(points)

//  target := NewEuclideanPoint("T", 8, 4)
//  nearest := tree.KNN(target, 1)
//  nearestPoint := nearest[0].(*EuclideanPoint)
//  fmt.Printf("Nearest neighbor: %s %v\n", nearestPoint.name, nearestPoint.coordinates)
//  return nearestPoint
// }
package kdTreePak_test

import (
    "testing"
    kdTreePak "kdtree/KDTree_pak"
)

func TestFindNearestNeighbor(t *testing.T) {
    target := kdTreePak.NewEuclideanPoint("T", 8, 4)
    pointsInfo := map[string][]float64{
        "A": {6, 2},
        "B": {4, 1},
        "C": {9, 6},
        "D": {2, 4},
        "E": {5, 7},
    }
    kdTreePak.FindNearestNeighbor(target, pointsInfo)
}

// func TestFindNearestNeighbor(t *testing.T) {

//  result := kdTreePak.FindNearestNeighbor()
//  t.Log(result)
// }