重建精度
在我们将多图像三维重建为模型时,会产生一张xoy平面的网格
如图,这样的网格就是有序点云模型的基础。有序点云为了还原实际物体的表面形态,将其重建为了一种“点阵”的形式。我们向z轴的负方向看去,如图:
可以看到有序点云就像下棋一样,是呈一行一行,或是一列一列这样排列的。这表明,模型即使是有的地方有点,有的地方没有点,有序点云的每个点依然可以垂直投影到xoy平面精度网格的交点上。
这里的表面有序点云的重建方式其实可以联想到一个surface函数。
[X,Y] = meshgrid(1:0.5:10,1:20);
Z = sin(X) + cos(Y);
surf(X,Y,Z)
其与MATLAB中函数surface()的绘制方法类似,这种三维函数不过是将平面的精度网格直接绘制在了曲面函数上。我们在对三维函数限制一定的网格采样精度后也是可以直接转化为三维点云模型的。
可惜很多实际复杂物体并非能够使用简单的三维函数表示。
有序点云与无序点云
有有序点云自然也存在无序点云,这两类都有典型代表:
有序点云主要是的是现实实物扫描或者图像重建出来的现实表面点云,为了表现物体的表面特征其通常为均匀的一层。无序点云主要是某些数据的特征点集合,这些点云“有聚有散”,多数区域之间的密度差别较大。
拉伸与空洞
在实际的三维重建中,很可能在图像拍摄时由于光照变化:如玻璃平面,水面的反射,和摄像头的像控点不稳定造成图片位置出错。这些情景的图像特征点变少,给空间三维计算照成了困难,最终在三维模型上出现模型的拉伸与空洞。
问
现有一块有序点云如图所示:
图中红点表示此处平面坐标存在一枚点云,其数字表示此点云z轴的高度。这块点云中间有一个大洞,如何能够通过一种方法能够将表面点云中间的缺失点云补全,使得填充的点云能够和空洞周围的点云平滑的连接在一起。
此点云数据的格式应该如下:
point_data = [[1, 1, 2],[1, 2, 1],[1, 3, 2],[1, 4, 3],[1, 5, 2],[1, 6, 1],[1, 7, 2],[1, 8, 3],[1, 9, 5],[1, 10, 7],
[2, 1, 1],[2, 2, 1],[2, 3, 2],[2, 4, 4],[2, 5, 3],[2, 6, 3],[2, 7, 4],[2, 8, 2],[2, 9, 4],[2, 10, 6],
[3, 1, 1],[3, 2, 2],[3, 3, 3],[3, 4, 3],[3, 8, 1],[3, 9, 2],[3, 10, 5],
[4, 1, 2],[4, 2, 3],[4, 3, 2],[4, 9, 6],[4, 10, 4],
[5, 1, 4],[5, 2, 5],[5, 9, 2],[5, 10, 4],
[6, 1, 6],[6, 2, 7],[6, 9, 4],[6, 10, 5],
[7, 1, 5],[7, 2, 6],[7, 9, 3],[7, 10, 7],
[8, 1, 5],[8, 2, 7],[8, 3, 5],[8, 9, 5],[8, 10, 7],
[9, 1, 3],[9, 2, 4],[9, 3, 6],[9, 4, 7],[9, 5, 8],[9, 6, 6],[9, 7, 8],[9, 8, 7],[9, 9, 6],[9, 10, 8],
[10, 1, 5],[10, 2, 5],[10, 3, 7],[10, 4, 6],[10, 5, 4],[10, 6, 5],[10, 7, 6],[10, 8, 8],[10, 9, 7],[10, 10, 9]]
空洞判断
由图中的空洞可知,空洞的特征为两侧有点, 而中间行或者列存在空洞。
如图,在某行或者某一列中,存在两端不为空但是中间为空(存在红色箭头)时,认定其为需要填充的行或者列。
插值法
插值在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点。可以看出,插值本质可能是一种曲线拟合,然后根据曲线来预测确定某个坐标处可能的值。在某些像素精度不太高的图片中,插值也可以用来提升图像的清晰度。
本例中,由于空洞并非离散点稀疏。直接应用均值进行填充,整行或者整列的填充均值数据仅与缺失两端点的数值有关。
逻辑流
1. 导入存在空洞的有序点云
2. 空洞按行或者检测并插值
3. 完成插值空洞填充
完整代码
import numpy as np
import matplotlib.pyplot as plt
def boundary(point_data_line):
boundary_points = []
for i in range(len(point_data_line)):
num_row_pre = 1
for i_row in range(len(point_data_line[i])):
num_row = point_data_line[i][i_row][1]
if num_row != num_row_pre:
boundary_points_first = point_data_line[i][i_row - 1]
boundary_points_last= point_data_line[i][i_row]
boundary_points.append([boundary_points_first,boundary_points_last])
break
num_row_pre += 1
return boundary_points
def fillp(boundary_points):
copy_point_data = []
for i in point_data:
copy_point_data.append(i)
for i in range(len(boundary_points)):
numpoints = boundary_points[i][1][1] - boundary_points[i][0][1] + 1
fill_points = np.linspace(boundary_points[i][0], boundary_points[i][1], numpoints)
for p in fill_points:
copy_point_data.append(list(p))
point_data_fill = copy_point_data
return point_data_fill
def view(point_data,point_data_fill):
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, projection='3d')
ax1.set_xlabel("x")
ax1.set_ylabel("y")
ax1.set_zlabel("z")
for i in range(len(point_data_fill)):
xf = point_data_fill[i][0]
yf = point_data_fill[i][1]
zf = point_data_fill[i][2]
ax1.scatter(xf, yf, zf, color='b', marker='.')
for i in range(len(point_data)):
x = point_data[i][0]
y = point_data[i][1]
z = point_data[i][2]
ax1.scatter(x, y, z, color='gray', marker='o')
plt.show()
return None
if __name__ == '__main__':
point_data = [[1, 1, 2], [1, 2, 1], [1, 3, 2], [1, 4, 3], [1, 5, 2], [1, 6, 1], [1, 7, 2], [1, 8, 3], [1, 9, 5], [1, 10, 7],
[2, 1, 1], [2, 2, 1], [2, 3, 2], [2, 4, 4], [2, 5, 3], [2, 6, 3], [2, 7, 4], [2, 8, 2], [2, 9, 4], [2, 10, 6],
[3, 1, 1], [3, 2, 2], [3, 3, 3], [3, 4, 3], [3, 8, 1], [3, 9, 2], [3, 10, 5],
[4, 1, 2], [4, 2, 3], [4, 3, 2], [4, 9, 6], [4, 10, 4],
[5, 1, 4], [5, 2, 5], [5, 9, 2], [5, 10, 4],
[6, 1, 6], [6, 2, 7], [6, 9, 4], [6, 10, 5],
[7, 1, 5], [7, 2, 6], [7, 9, 3], [7, 10, 7],
[8, 1, 5], [8, 2, 7], [8, 3, 5], [8, 9, 5], [8, 10, 7],
[9, 1, 3], [9, 2, 4], [9, 3, 6], [9, 4, 7], [9, 5, 8], [9, 6, 6], [9, 7, 8], [9, 8, 7], [9, 9, 6], [9, 10, 8],
[10, 1, 5], [10, 2, 5], [10, 3, 7], [10, 4, 6], [10, 5, 4], [10, 6, 5], [10, 7, 6], [10, 8, 8], [10, 9, 7],[10, 10, 9]]
point_data_line = [[[1, 1, 2],[1, 2, 1],[1, 3, 2],[1, 4, 3],[1, 5, 2],[1, 6, 1],[1, 7, 2],[1, 8, 3],[1, 9, 5],[1, 10, 7]],
[[2, 1, 1],[2, 2, 1],[2, 3, 2],[2, 4, 4],[2, 5, 3],[2, 6, 3],[2, 7, 4],[2, 8, 2],[2, 9, 4],[2, 10, 6]],
[[3, 1, 1],[3, 2, 2],[3, 3, 3],[3, 4, 3],[3, 8, 1],[3, 9, 2],[3, 10, 5]],
[[4, 1, 2],[4, 2, 3],[4, 3, 2],[4, 9, 6],[4, 10, 4]],
[[5, 1, 4],[5, 2, 5],[5, 9, 2],[5, 10, 4]],
[[6, 1, 6],[6, 2, 7],[6, 9, 4],[6, 10, 5]],
[[7, 1, 5],[7, 2, 6],[7, 9, 3],[7, 10, 7]],
[[8, 1, 5],[8, 2, 7],[8, 3, 5],[8, 9, 5],[8, 10, 7]],
[[9, 1, 3],[9, 2, 4],[9, 3, 6],[9, 4, 7],[9, 5, 8],[9, 6, 6],[9, 7, 8],[9, 8, 7],[9, 9, 6],[9, 10, 8]],
[[10, 1, 5],[10, 2, 5],[10, 3, 7],[10, 4, 6],[10, 5, 4],[10, 6, 5],[10, 7, 6],[10, 8, 8],[10, 9, 7],[10, 10, 9]]]
boundary_points = boundary(point_data_line)
point_data_fill = fillp( boundary_points)
view(point_data,point_data_fill)
基于横向x轴的均值插值空洞填充如图所示蓝色的点为填补点,灰色的点为原始的点云。
注意:本文仅仅对于此类特定特征点云空洞填补的思路做一个提供,想要套用此思路在其他形式点云的空洞填补上还需要对适应性做出对应的优化。