python - 沿 3d 线插入点
问题描述
尽管我在R中概述了问题,但欢迎 使用R和Python回答。
假设我们在 x,y,z 中有一组点,它们定义了时间 t 向量上的离散路径(或一组连接的线段)。这些路径在 z 中不是单调的。
path <- data.frame(x = c(245, 233, 270, 400, 380),
y = c(245, 270, 138, 225, 300),
z = c(0, 1.2, 5, 3, 9),
t = 1:5)
plot3D::scatter3D(path$x, path$y, path$z, type = "b", bty = "g",
phi = 0, col = "red", pch = 20, ticktype = "detailed")
如何在 z 中以任意分辨率沿路径进行插值?
例如,假设我想在 z 中保留 1 个单位的分辨率。沿 z 的点为 0、1.2、5、3、9。因此,满足此约束的一种可能解决方案是在 z 中的 1、2、3、4、4、4、5、6、7、8 处进行插值方向,产生下图中的蓝点(标签表示 z 位置):
最终,我想要蓝点的坐标。我们可以通过对每对点 z 依次求解 3d 中的线方程,然后沿每个线段进行插值来强制求解。但是,我想确保我没有遗漏一些现有的实现或巧妙的技巧。
解决方案
我过去purrr::map
只是将它们全部捆绑成一个小标题,但您可以轻松地独立完成它们。唯一的问题是你的x
ory
不是单调的z
。然后你需要做第四个变量来指示行顺序,从那里开始变得更加复杂。
library(dplyr)
library(purrr)
library(plotly)
path3d <- data.frame(
x = c(245, 233, 270, 400, 380),
y = c(245, 270, 138, 225, 300),
z = c(0, 1.2, 3, 5, 9)
)
path3d_interp <- list(
x = approxfun(path3d$z, path3d$x),
y = approxfun(path3d$z, path3d$y)
) %>%
map(~.x(1:8)) %>% as_tibble() %>%
mutate(z = 1:8)
x y z <dbl> <dbl> <int> 1 235 266. 1 2 249. 211. 2 3 270 138 3 4 335 182. 4 5 400 225 5 6 395 244. 6 7 390 262. 7 8 385 281. 8
plot_ly(path3d) %>%
add_paths(x = ~x, y = ~y, z = ~z) %>%
add_markers(
x = ~x, y = ~y, z = ~z,
data = path3d_interp
)
为非单调更新z
:
我想不出一种特别优雅的方法来预测x
,并且y
确切地说,是否每个所需的解决方案都不完全一致z
。我建议的总体大纲是根据您的 time 预测的每个变量创建一个funX
, funY
, and 。然后使用一个非常高分辨率的新值向量并将其子集为. 您永远无法准确找到您正在寻找的值,但您可以将它们近似为所需的任意精度:funZ
t
t
funZ(new_t_values)
path3d <- data.frame(
x = c(245, 233, 270, 400, 380),
y = c(245, 270, 138, 225, 300),
z = c(0, 1.2, 5, 3, 9),
t = 1:5
)
只是为了对这里发生的事情有一个很好的了解t
:
library(ggplot2)
ggplot(path3d) +
geom_path(aes(t, x), color = "blue") +
geom_path(aes(t, y), color = "red") +
geom_path(aes(t, z*50), color = "orange") +
labs(y = "x, y, z*50")
path3d
这是在( x
、y
、z
和)的每一列上循环,并t
为每个变量创建一个线性插值函数t
作为预测变量。
path3d_interp_funs <-
map(path3d, ~approxfun(path3d$t, .x))
全方位检查t
:
现在我们可以在整个范围内制作高分辨率矢量t
,这里有 100 万个元素。您可以根据您的精度要求和您的记忆力允许尽可能多地增加它。
new_t_values <- seq(min(path3d$t), max(path3d$t), length.out = 1e6)
1.000000 1.000004 1.000008 1.000012 1.000016 1.000020 ...
生成的完整路径z
:
现在我们看看范围内z
每个可能的值是什么t
。
z_candidates <- path3d_interp_funs$z(new_t_values)
0.000000e+00 4.800005e-06 9.600010e-06 1.440001e-05 1.920002e-05 2.400002e-05 ...
测试z
最接近您期望的位置z
:
z
所以现在我们取( )的每个期望值1:8
,并询问向量的哪个元素与z_candidates
它的绝对偏差最小。这将返回我们可以用于子集的索引new_t_values
。
t_indices <- map_dbl(1:8, ~which.min(abs(z_candidates-.x)))
208334 302632 750000 434211 500001 875000 916667 958333
健全性检查:这些选择t
的值是否会产生z
您想要的 s?
path3d_interp_funs$z(new_t_values[t_indices])
0.9999994 1.9999958 3.0000020 3.9999986 4.9999960 5.9999970 7.0000060 7.9999910
生成您想要的数据:
因此,让我们遍历每个近似函数,以我们新选择的值评估每个函数t
:
path3d_interp <-
path3d_interp_funs %>%
map(~.x(new_t_values[t_indices])) %>%
as_tibble()
# A tibble: 8 x 4 x y z t <dbl> <dbl> <dbl> <dbl> 1 235. 266. 1.000 1.83 2 241. 242. 2.00 2.21 3 400. 225. 3.00 4.00 4 260. 173. 4.00 2.74 5 270. 138. 5.00 3.00 6 390. 262. 6.00 4.50 7 387. 275. 7.00 4.67 8 383. 287. 8.00 4.83
可视化您的结果
您可以检查以确保这些点确实落在正确的路径上:
ggplot(path3d) +
geom_path(aes(t, x), color = "blue") +
geom_path(aes(t, y), color = "red") +
geom_path(aes(t, z*50), color = "orange") +
geom_point(data = path3d_interp, aes(t, x), color = "blue") +
geom_point(data = path3d_interp, aes(t, y), color = "red") +
geom_point(data = path3d_interp, aes(t, z*50), color = "orange") +
geom_text(data = path3d_interp, aes(t, z*50, label = round(z))) +
labs(y = "x, y, z*50")
并以 3D 形式查看它们:
plot_ly(path3d) %>%
add_paths(x = ~x, y = ~y, z = ~z) %>%
add_markers(
x = ~x, y = ~y, z = ~z,
data = path3d_interp
) %>%
add_text(
x = ~x, y = ~y, z = ~z, text = ~round(z),
data = path3d_interp
)
再次更新:
冒着过于冗长的风险,我想起了这个uniroot
函数,它在这个反解方面做得很好:
t_solutions <- map(1:8,
~uniroot(
function(x) path3d_interp_funs$z(x) - .x,
interval = range(path3d$t)
)
) %>% map_dbl("root")
1.833333 2.210526 2.473684 2.736842 4.333333 4.500000 4.666667 4.833333
但是,您可能会注意到这些解决方案与以前的方法不同!
uniroot
找到了更接近区间极值的解,而不是在函数改变方向的局部区域。但这带来了一个问题,即t
每个所需值可能有多个z
值。所以一个更强大的解决方案:
root_finder <- function(f, zero, range, cuts) {
endpts <- seq(range[1], range[2], length.out = cuts+1)
range_list <- map2(endpts[-(cuts+1)], endpts[-1], c)
safe_root <- possibly(uniroot, otherwise = NULL)
f0 <- function(x) f(x) - zero
map(range_list, ~safe_root(f0, interval = .x, maxiter = 100)$root) %>%
compact() %>%
unlist() %>%
unique()
}
这个函数需要一个函数,一个新的期望“零”uniroot
来求解,一个值范围来测试函数,以及将该范围划分为多少个桶。然后它在每个桶中测试一个解决方案,NULL
如果没有找到则返回。然后它会抛出NULL
s 并删除任何重复项(例如,如果解决方案恰好位于存储桶的边界)。
root_finder(path3d_interp_funs$z, zero = 4, range = range(path3d$t), cuts = 10)
2.736842 3.500000 4.166667
然后你可以遍历所有你想要z
的值来找到t
满足它的值。
t_solutions <- map(
1:8,
~root_finder(path3d_interp_funs$z, zero = .x, range = range(path3d$t), cuts = 100)
) %>% unlist()
1.833333 2.210526 2.473684 4.000000 2.736842 3.500000 4.166667 3.000000 4.333333 4.500000 4.666667 4.833333
然后再次将这些t
值传递给您之前创建的每个函数,您可以为所有这些函数创建一个数据框。
map(path3d_interp_funs, ~.x(t_solutions)) %>%
as_tibble()
x y z t <dbl> <dbl> <dbl> <dbl> 1 235 266. 1. 1.83 2 241. 242. 2. 2.21 3 251. 207. 3. 2.47 4 400 225 3 4 5 260. 173. 4 2.74 6 335 182. 4 3.5 7 397. 238. 4. 4.17 8 270 138 5 3 9 393. 250. 5.00 4.33 10 390 262. 6 4.5 11 387. 275 7. 4.67 12 383. 288. 8.00 4.83
推荐阅读
- python - Anaconda 提示快速关闭
- android - 更改apk文件的快速而肮脏的方法
- ruby-on-rails - 使用 neo4j.rb 检索子图
- javascript - 更换
- c++ - 如何比较字符串(不区分大小写),同时将相同的字符串插入二叉树(区分大小写)?
- java - 为什么我的 HashMap 实现比 Java 的 HashMap 慢很多很多?
- matlab - Matlab `imread` 在某些图像中读取为全零
- javascript - AndroidJs 无法识别为命令
- python - 将python opencv项目中的输入从图像更改为视频
- ios - 如何比较两个线性渐变?SwiftUI