从零开始跑通3DGS教程:(四)修改(缩放、空间变换)colmap生成的sfm结果
写在前面
-
本文内容
本文所属《从零开始跑通3DGS教程》系列文章;
通过colmap进行的sfm的普通方式会丢失场景的物理尺度信息,并且并不在符合一般认知的坐标系下,本文将读取colmap生成的点云和相机pose,将其进行空间变换和缩放之后,重新保存,新保存的结果仍然可以被colmap读取以及进行后续的稠密重建以及3dgs等工作 -
平台/环境
colmap, open3d(python) -
转载请注明出处:
https://blog.csdn.net/qq_41102371/article/details/146251947
目录
- 写在前面
- 系列文章
- 准备
- 代码
- 完
系列文章
-
介绍
从零开始跑通3DGS教程:介绍 -
数据(采集):
从零开始跑通3DGS教程:(一)数据(采集) -
SFM(colmap)计算初始点云和相机pose:
从零开始跑通3DGS教程:(二)SFM(colmap)计算初始点云和相机pose
该步骤将通过structure from motion算法,计算出每张图像的pose,以及整个场景和目标的稀疏点云 -
坐标系与尺度编辑(CloudCompare):
从零开始跑通3DGS教程:(三)坐标系与尺度编辑(CloudCompare) -
修改sfm生成的原始数据
从零开始跑通3DGS教程:(四)修改(缩放、空间变换)colmap生成的sfm结果 -
3DGS训练:
从零开始跑通3DGS教程:(五)3DGS训练 -
Gaussian Model编辑与渲染:
从零开始跑通3DGS教程:(六)Gaussian Model编辑与渲染
准备
open3d
pip install open3d
colmap 读写数据的代码
https://github.com/colmap/colmap/blob/main/scripts/python/read_write_model.py
将原来的模型备份
cd 3dgs_tutorial/pro/truck/sparse
cp -r ./0 ./0_ori
代码
将read_write_model.py放在和modify_colmap.py同级目录下,修改YOUR_DIR;
使用通过cloudcompare得到的变换矩阵和scale,修改对应的trans_mat和scale,代码已中包含每一步的注释
cd 3dgs_tutorial/scripts
python modify_colmap.py
modify_colmap.py
import os
import sys
import copyimport numpy as np
import open3d as o3dabs_path = os.path.abspath(__file__)
sys.path.append(os.path.dirname(abs_path) + "/..")from read_write_model import (read_model,write_model,Point3D,Image,qvec2rotmat,rotmat2qvec,
)def colmapP3d2o3d(points3D):# get xyz, rgbxyz_list = [point.xyz for point in points3D.values()]xyz_array = np.array(xyz_list)rgb_list = [point.rgb for point in points3D.values()]rgb_array = np.array(rgb_list)# convert to Open3D pcdpcd = o3d.geometry.PointCloud()pcd.points = o3d.utility.Vector3dVector(xyz_array)# normalize to [0, 1]pcd.colors = o3d.utility.Vector3dVector(rgb_array / 255.0)# add normals# pcd.normals = o3d.utility.Vector3dVector(np.zeros_like(xyz_array))# o3d.visualization.draw_geometries([pcd])return pcddef o3d2colmapP3d(pcd_o3d, Points3D):# get original xyz, rgbxyz_array = np.asarray(pcd_o3d.points)rgb_array = np.asarray(pcd_o3d.colors) * 255.0# rgbrgb_array = np.round(rgb_array).astype(np.uint8)# convert to Colmap Points3Dfor i, (point_id, point) in enumerate(points3D.items()):new_point3D = Point3D(id=point.id,xyz=xyz_array[i],rgb=rgb_array[i],error=point.error,image_ids=point.image_ids,point2D_idxs=point.point2D_idxs,)points3D[point_id] = new_point3D# return Points3Ddef poses_to_pcd(poses):pcd = o3d.geometry.PointCloud()xyz = np.zeros((len(poses), 3))xyz = [pose[:3, 3] for pose in poses]pcd.points = o3d.utility.Vector3dVector(xyz)pcd.paint_uniform_color([0, 1, 0])return pcddef poses_vis(pose_list, trans_mat=np.eye(4), pcd_scene=o3d.geometry.PointCloud()):poses = copy.deepcopy(pose_list)pose_show = []# pose_show.append(o3d.geometry.TriangleMesh.create_coordinate_frame(2))scale = 0.5pose_cur = o3d.geometry.TriangleMesh.create_coordinate_frame(0.5 * scale)pose_cur.transform(poses[0])pose_show.append(pose_cur)for i in range(1, len(poses)):pose_cur = o3d.geometry.TriangleMesh.create_coordinate_frame(0.2 * scale)pose_cur.transform(poses[i])pose_show.append(pose_cur)# # show neighbor lines# lineset = pcd_tools.create_neighbor_lineset(poses)# pose_show.append(lineset)# show positionspcd = poses_to_pcd(poses)pose_show.append(pcd)pose_show.append(pcd_scene)coor_sfm = o3d.geometry.TriangleMesh.create_coordinate_frame(0.5 * scale)coor_ori = o3d.geometry.TriangleMesh.create_coordinate_frame(1.5 * scale)coor_ori.transform(np.linalg.inv(trans_mat))pose_show.append(coor_sfm)pose_show.append(coor_ori)# pose_show.append(coor_cam)o3d.visualization.draw_geometries(pose_show, "pose_show")if __name__ == "__main__":# ********** you need to modify the dir include data procceed by colmap **********dir_model = "/home/lixin/lixin/code/3dgs_tutorial/pro/truck/"dir_data = f"{dir_model}/sparse/0_ori"dir_data_new = f"{dir_model}/sparse/0"if not os.path.exists(dir_data_new):os.makedirs(dir_data_new)# ********** read data **********cameras, images, points3D = read_model(dir_data, ".bin")# convert colmap points3D to o3d pcdpcd_o3d = colmapP3d2o3d(points3D)o3d.io.write_point_cloud(f"{dir_data_new}/points3D_o3d_ori.ply",pcd_o3d,write_ascii=True,)# ********** modify data **********# scale and transform matrixmat_scale = np.eye(4)trans_mat = np.eye(4) # identity matrixtrans_mat = np.array([[-0.709195, 0.038963, 0.703934, 0.667738],[-0.701549, 0.059836, -0.710104 ,0.452691],[-0.069788 ,-0.997448, -0.015101, 1.136864],[0.000000 ,0.000000, 0.000000, 1.000000]])scale = 1.063829787mat_scale[0, 0] = scalemat_scale[1, 1] = scalemat_scale[2, 2] = scaleprint(mat_scale)print(trans_mat)poses = []for idx, key in enumerate(images):extr = images[key]# get original poseR = qvec2rotmat(extr.qvec)T = np.array(extr.tvec)# transform posepose = np.eye(4)pose[:3, :3] = Rpose[:3, 3] = Tpose = np.linalg.inv(pose)pose = trans_mat @ pose# scale posepose_temp = mat_scale @ posepose[:3, 3] = pose_temp[:3, 3]# save poseposes.append(pose)# update image in colmap datapose_back = np.linalg.inv(pose)image_new = Image(camera_id=images[key].camera_id,id=images[key].id,name=images[key].name,point3D_ids=images[key].point3D_ids,qvec=rotmat2qvec(pose_back[:3, :3]),tvec=pose_back[:3, 3],xys=images[key].xys,)images[key] = image_new# tansform and scale pointspcd_o3d.transform(mat_scale @ trans_mat)# visualize posesposes_vis(poses, pcd_scene=pcd_o3d)print(len(pcd_o3d.points))print(len(pcd_o3d.colors))# save new pcdo3d.io.write_point_cloud(f"{dir_data_new}/points3D.ply",pcd_o3d,write_ascii=True,)# *********** save new data ***********# convert o3d pcd to colmap points3Do3d2colmapP3d(pcd_o3d, points3D)# update colmap datawrite_model(cameras, images, points3D, path=dir_data_new, ext=".bin")# read new datacameras, images, points3D_new = read_model(dir_data_new, ".bin")pcd_o3d_new = colmapP3d2o3d(points3D_new)o3d.visualization.draw_geometries([pcd_o3d_new], "pcd_o3d_new")
然后用cloudcompare重新打开sparse/0/points3D.ply,就会看到尺度和坐标系都变成我们想要的了
完
主要做激光/影像三维重建,配准、分割等常用点云算法,熟悉open3d、pcl等开源点云库,技术交流、咨询可私信