COLMAP+OPENMVS进行三维重建
1、COLMAP安装
只需要在windows下安装已经编译好的可执行文件就行,还可以使用命令行,但是命令行不太好用,用UI界面转换比较方便。
github地址:
colmap/README.md at main · colmap/colmap · GitHub
将解压的COLMAP的根目录添加到系统环境变量中,可以使用cmd或者powershell完成
2、OPENMVS安装
OpenMVS GitHub网站
GitHub - cdcseacave/openMVS: open Multi-View Stereo reconstruction library
第一种方法,在Ubuntu中,最快捷的方法就是使用docker镜像,省去安装各种库的麻烦,前提是拉取docker镜像时需要使用国内镜像,使用代理也不太管用。
具体步骤:
1、ubuntu下创建一个/etc/docker/daemon.json文件。填入以下信息
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com",
"https://docker.mirrors.ustc.edu.cn",
"https://docker.nju.edu.cn",
"https://do.nark.eu.org"
]
}
重启docker服务,使用纯ubuntu系统可以执行
systemctl restart docker
使用wsl子系统,由于子系统中systemctl 不可用,使用service可以重启。
service docker restart
2、拉取openmvs镜像
docker pull openmvs/openmvs-ubuntu
第二种方法,下载编译好的windows版本,将解压的openmvs根目录添加到系统环境变量中可以在CMD或者powershell中通过命令行执行openmvs的运行。
Releases · cdcseacave/openMVS (github.com)
3、COLMAP重建得到稠密点云
新建一个文件夹,在其中新建images文件夹,以及一个Database.db的空文件,注意文件路径不要出现中文。images文件夹中放入需要重建的图片,图片越多重建时间越长,图片过少可能会重建失败.
自建数据集要求:
尽量使用单反相机或专业数码相机进行数据采集,如果要用手机进行采集,请使用单摄像头的手机进行数据采集。
尽量选择纹理丰富的外界环境进行数据采集,避免玻璃围墙、瓷砖和打蜡地板等强反光材料环境
尽量选择光照明亮,且光照条件变化不剧烈的环境,最好选择室内环境。如室内客厅,开启客厅大灯进行灯光补偿。
尽量围绕重建物体或环境采集较多的影像,且在采集过程中控制快门速度,避免模糊。
打开colmap程序(COLMAP.bat),点击File-New Project,在Database栏点击open,选择刚才新建的Database.db文件,在Images栏选择刚才新建的存放图片的images文件夹,点击save保存。
点击Processing-Feature Extraction,默认参数即可,点击Extract,等待程序运行完毕可看到右侧有log信息输出。
点击Processing-Feature matching,默认参数即可,点击Run,等待程序运行完毕可看到右侧有log信息输出。
点击Reconstruncion-Start reconstruction,等待执行完毕,即可得到重建的结果。
在之前images的同级目录下,新建一个文件夹dense。点击Reconstruncion-Dense reconstrction,点击select,选择刚才新建的dense文件夹用来保存稠密点云结果。依次点击Undistorion、Stereo、Fusion,执行完毕。
在程序中点击File-export model as txt,选择dense文件夹下的sparse文件夹(由于openmvs支支持txt格式的,所以需要将重建的文件转成txt文件) 软件自带的转换工具有bug,使用以下python代码实现转换功能。
import os
import collections
import numpy as np
import struct
import argparse
CameraModel = collections.namedtuple(
"CameraModel", ["model_id", "model_name", "num_params"])
Camera = collections.namedtuple(
"Camera", ["id", "model", "width", "height", "params"])
BaseImage = collections.namedtuple(
"Image", ["id", "qvec", "tvec", "camera_id", "name", "xys", "point3D_ids"])
Point3D = collections.namedtuple(
"Point3D", ["id", "xyz", "rgb", "error", "image_ids", "point2D_idxs"])
class Image(BaseImage):
def qvec2rotmat(self):
return qvec2rotmat(self.qvec)
CAMERA_MODELS = {
CameraModel(model_id=0, model_name="SIMPLE_PINHOLE", num_params=3),
CameraModel(model_id=1, model_name="PINHOLE", num_params=4),
CameraModel(model_id=2, model_name="SIMPLE_RADIAL", num_params=4),
CameraModel(model_id=3, model_name="RADIAL", num_params=5),
CameraModel(model_id=4, model_name="OPENCV", num_params=8),
CameraModel(model_id=5, model_name="OPENCV_FISHEYE", num_params=8),
CameraModel(model_id=6, model_name="FULL_OPENCV", num_params=12),
CameraModel(model_id=7, model_name="FOV", num_params=5),
CameraModel(model_id=8, model_name="SIMPLE_RADIAL_FISHEYE", num_params=4),
CameraModel(model_id=9, model_name="RADIAL_FISHEYE", num_params=5),
CameraModel(model_id=10, model_name="THIN_PRISM_FISHEYE", num_params=12)
}
CAMERA_MODEL_IDS = dict([(camera_model.model_id, camera_model)
for camera_model in CAMERA_MODELS])
CAMERA_MODEL_NAMES = dict([(camera_model.model_name, camera_model)
for camera_model in CAMERA_MODELS])
def read_next_bytes(fid, num_bytes, format_char_sequence, endian_character="<"):
"""Read and unpack the next bytes from a binary file.
:param fid:
:param num_bytes: Sum of combination of {2, 4, 8}, e.g. 2, 6, 16, 30, etc.
:param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
:param endian_character: Any of {@, =, <, >, !}
:return: Tuple of read and unpacked values.
"""
data = fid.read(num_bytes)
return struct.unpack(endian_character + format_char_sequence, data)
def write_next_bytes(fid, data, format_char_sequence, endian_character="<"):
"""pack and write to a binary file.
:param fid:
:param data: data to send, if multiple elements are sent at the same time,
they should be encapsuled either in a list or a tuple
:param format_char_sequence: List of {c, e, f, d, h, H, i, I, l, L, q, Q}.
should be the same length as the data list or tuple
:param endian_character: Any of {@, =, <, >, !}
"""
if isinstance(data, (list, tuple)):
bytes = struct.pack(endian_character + format_char_sequence, *data)
else:
bytes = struct.pack(endian_character + format_char_sequence, data)
fid.write(bytes)
def read_cameras_text(path):
"""
see: src/base/reconstruction.cc
void Reconstruction::WriteCamerasText(const std::string& path)
void Reconstruction::ReadCamerasText(const std::string& path)
"""
cameras = {
}
with open(path, "r") as fid:
while True:
line = fid.readline()
if not line:
break
line = line.strip()
if len(line) > 0 and line[0] != "#":
elems = line.split()
camera_id = int(elems[0])
model = elems[1]
width = int(elems[2])
height = int(elems[3])
params = np.array(tuple(map(float, elems[4:])))
cameras[camera_id] = Camera(id=camera_id, model=model,
width=width, height=height,
params=params)
return cameras
def read_cameras_binary(path_to_model_file):
"""
see: src/base/reconstruction.cc
void Reconstruction::WriteCamerasBinary(const std::string& path)
void Reconstruction::ReadCamerasBinary(const std::string& path)
"""
cameras = {
}
with open(path_to_model_file, "rb") as fid:
num_cameras = read_next_bytes(fid, 8, "Q")[0]
for _ in