Demos
以sfm_bench数据为例介绍。
运行命令:
python demo.py --imagedir=data/sfm_bench/rgb --calib=calib/eth.txt
脚本文件:demo.py
参数:
--imagedir=data/sfm_bench/rgb (指定数据集的目录)
--calib=calib/eth.txt (相机参数数据文件)
分析
1、data/sfm_bench/rgb
1540481046.653665.png:
2、calib/tum3.txt
726.21081542969 726.21081542969 359.2048034668 202.47247314453
fx = 726.21081542969
fy = 726.21081542969
cx = 359.2048034668
cy = 202.47247314453
3、demo.py
最后看看demo.py文件
首先看main函数:
'''
节选main()主函数
'''
if __name__ == '__main__':
# 1.解析参数
parser = argparse.ArgumentParser()
parser.add_argument("--imagedir", type=str, help="path to image directory")
parser.add_argument("--calib", type=str, help="path to calibration file") # 校准文件
parser.add_argument("--t0", default=0, type=int, help="starting frame")
parser.add_argument("--stride", default=3, type=int, help="frame stride")
parser.add_argument("--weights", default="droid.pth")
parser.add_argument("--buffer", type=int, default=512)
parser.add_argument("--image_size", default=[240, 320])
parser.add_argument("--disable_vis", action="store_true")
parser.add_argument("--beta", type=float, default=0.3, help="weight for translation / rotation components of flow")
parser.add_argument("--filter_thresh", type=float, default=2.4, help="how much motion before considering new keyframe")
parser.add_argument("--warmup", type=int, default=8, help="number of warmup frames")
parser.add_argument("--keyframe_thresh", type=float, default=4.0, help="threshold to create a new keyframe")
parser.add_argument("--frontend_thresh", type=float, default=16.0, help="add edges between frames whithin this distance")
parser.add_argument("--frontend_window", type=int, default=25, help="frontend optimization window")
parser.add_argument("--frontend_radius", type=int, default=2, help="force edges between frames within radius")
parser.add_argument("--frontend_nms", type=int, default=1, help="non-maximal supression of edges")
parser.add_argument("--backend_thresh", type=float, default=22.0)
parser.add_argument("--backend_radius", type=int, default=2)
parser.add_argument("--backend_nms", type=int, default=3)
parser.add_argument("--upsample", action="store_true")
parser.add_argument("--reconstruction_path", help="path to saved reconstruction")
args = parser.parse_args()
args.stereo = False
# 创建多线程
# 新的多进程时使用spawn启动方法。这通常在使用PyTorch进行多进程训练或并行计算时使用,可以确保每个子进程都有独立的内存空间,避免共享状态带来的潜在问题。
torch.multiprocessing.set_start_method('spawn')
droid = None
# need high resolution depths
if args.reconstruction_path is not None:
args.upsample = True
tstamps = []
# 2.通过image_stream函数读取数据集,并使用tqdm包进行可视化进度
print("\n开始跟踪......")
image_counter = 0
for (t, image, intrinsics) in tqdm(image_stream(args.imagedir, args.calib, args.stride)):
image_counter += 1
# 2.1 判断是否到了采样点
# 当图像i < 用户设置的t0,表示还没有达到采样的时间,则继续
if t < args.t0:
continue
# 2.2 判断是否可视化
if not args.disable_vis:
show_image(image[0])
# 2.3 实例化Droid模型
if droid is None:
args.image_size = [image.shape[2], image.shape[3]]
droid = Droid(args) # 实例化模型
#print(droid)
# 2.4 开始跟踪建图
#调用模型中的track函数,对每一帧t中的图像image,按照内参矩阵intrinsics进行跟踪,获取轨迹
droid.track(t, image, intrinsics=intrinsics)
print("共计{}帧图像",image_counter)
# 3. 如果用户需要重建,则将结果通过save_reconstruction()进行重建
if args.reconstruction_path is not None:
save_reconstruction(droid, args.reconstruction_path)
# 4. 对跟踪结果进行评估
print("\n开始BA操作....")
traj_est = droid.terminate(image_stream(args.imagedir, args.calib, args.stride))
print("The shape of traj_est is:",np.shape(traj_est))
print("traj_est = ",traj_est)
print("demo running is over ... ")
image_stream(imagedir, calib, stride)函数
'''
imagedir:图片路径
calib:校准文件(参数文件)
stride:采样步幅
'''
def image_stream(imagedir, calib, stride):
""" image generator """
# 加载相机参数文件calib,并用" "进行分割
calib = np.loadtxt(calib, delimiter=" ")
# 读取的数据取前四列
fx, fy, cx, cy = calib[:4]
# 构建内参矩阵K
K = np.eye(3)
K[0,0] = fx
K[0,2] = cx
K[1,1] = fy
K[1,2] = cy
# 按照stride步进行视频帧采样
image_list = sorted(os.listdir(imagedir))[::stride]
# 对于采样之后的图像,帧进行循环遍历
for t, imfile in enumerate(image_list):
# 首先去读内一张图像
image = cv2.imread(os.path.join(imagedir, imfile))
# 图像畸变校正
# calib文件格式为:458.654 457.296 367.215 248.375 -0.28340811 0.07395907 0.00019359 1.76187114e-05
# 如果calib长度>4的话,说明文件中有畸变参数。需要根据畸变参数进行校准
if len(calib) > 4:
image = cv2.undistort(image, K, calib[4:])
# 获取原始图像的尺寸数据
h0, w0, _ = image.shape
# 将图片变成384 * 512,并且保持宽高比例不变
h1 = int(h0 * np.sqrt((384 * 512) / (h0 * w0)))
w1 = int(w0 * np.sqrt((384 * 512) / (h0 * w0)))
# 利用cv2.resize函数将图片裁剪成固定尺寸
image = cv2.resize(image, (w1, h1))
# 通过裁剪操作,确保图像的高度和宽度是8的倍数
image = image[:h1-h1%8, :w1-w1%8]
# 调整图像的维度,最终的张量形状将变为(通道数, 高度, 宽度),方便计算
image = torch.as_tensor(image).permute(2, 0, 1)
# 将内参转化成张量
intrinsics = torch.as_tensor([fx, fy, cx, cy])
#因为上面进行了裁剪,所以相机内参数据需要根据裁剪比例进行缩放
intrinsics[0::2] *= (w1 / w0)
intrinsics[1::2] *= (h1 / h0)
# 返回的值作为一个迭代元组,包含三个元素:t、image[None] 和 intrinsics。
yield t, image[None], intrinsics
save_reconstruction(droid, reconstruction_path)
def save_reconstruction(droid, reconstruction_path):
from pathlib import Path
import random
import string
# 获取调用该函数时,droid.video.counter记录的次数
t = droid.video.counter.value
# 获取不同对象,索引0到索引t-1的元素的元素
tstamps = droid.video.tstamp[:t].cpu().numpy()
images = droid.video.images[:t].cpu().numpy()
disps = droid.video.disps_up[:t].cpu().numpy()
poses = droid.video.poses[:t].cpu().numpy()
intrinsics = droid.video.intrinsics[:t].cpu().numpy()
# 确保reconstructions路径存在
Path("reconstructions/{}".format(reconstruction_path)).mkdir(parents=True, exist_ok=True)
# 在reconstructions/路径下存放tstamps数据,并使用npy格式
np.save("reconstructions/{}/tstamps.npy".format(reconstruction_path), tstamps)
# 在reconstructions/路径下存放images数据,并使用npy格式
np.save("reconstructions/{}/images.npy".format(reconstruction_path), images)
# 在reconstructions/路径下存放disps数据,并使用npy格式
np.save("reconstructions/{}/disps.npy".format(reconstruction_path), disps)
# 在reconstructions/路径下存放poses数据,并使用npy格式
np.save("reconstructions/{}/poses.npy".format(reconstruction_path), poses)
# 在reconstructions/路径下存放intrinsics数据,并使用npy格式
np.save("reconstructions/{}/intrinsics.npy".format(reconstruction_path), intrinsics)
实验结果
1、debug输出
这里可以看出:traj_est的输出就是一个220*7的矩阵。其中220表示220帧图像,7代表位姿向量(4维旋转+3维平移)。
2、视频帧界面
3、俯视图
4、正视图
5、侧视图
但是原作者的工程中自带的demo没有结束功能,一直停在“Global BA Iteration #12”的位置,也没有进行评估操作。