首页 > 其他分享 >使用iPhone相机和OpenCV来完成3D重建(第三部分)

使用iPhone相机和OpenCV来完成3D重建(第三部分)

时间:2023-06-14 15:03:15浏览次数:46  
标签:map 匹配 img OpenCV 算法 iPhone 图像 视差 3D


使用iPhone相机和OpenCV来完成3D重建(第三部分)_python

正文字数:4509  阅读时长:2分钟

欢迎来到本教程的第三部分,也是最后一部分关于立体重建的教程。

Posted by Omar Padierna 

url : https://medium.com/@omar.ps16/stereo-3d-reconstruction-with-opencv-using-an-iphone-camera-part-iii-95460d3eddf0

快速回顾:

在第一部分中,我们简要介绍了立体三维重建所需的步骤以及立体重建工作的原理和要点。

在第二部分中,我们分析了一个脚本来计算摄像机矩阵和失真系数。这些都是三维重建过程中相机的固有参数。

一旦我们的相机被校准,我们就可以利用来自同一个物体的一对照片完成重建。在大多数立体声应用程序中,你会发现每张照片都是从两个单独的摄像头拍摄的,如下图所示

使用iPhone相机和OpenCV来完成3D重建(第三部分)_人工智能_02

用于三维重建的典型双摄像头系统

人们这样做的原因是因为两个摄像头在同一高度(比如我们的眼睛)是非常重要的。我们在这个教程中,我们只是使用了手机的摄像头,没有使用这种相机,因此我们不需要进行类似的设置。如果你想制作自己的双摄像头系统以获得更好的效果,那么就你可以去尝试阅读Daniel Lee的博客。

我们仍然需要一对图片来生成视差图。在这种情况下,我让别人给我拍了两张照片,同时小心地水平移动相机,确保没有垂直移动(说起来容易做起来难)。如果用手或是自己动手太困难,你可以用三脚架。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_python_03

我胖乎乎的样子

一旦我们有了照片,我们就要花一些时间开始写一些代码。我们将从加载摄像机矩阵和现实上面得到的图片开始。作为一个友好的提醒,请记住完整的脚本可以在这里(https://github.com/OmarPadierna/3DReconstruction)找到。

#=========================================================
# Stereo 3D reconstruction 
#=========================================================
#Load camera parameters
ret = np.load('./camera_params/ret.npy')
K = np.load('./camera_params/K.npy')
dist = np.load('./camera_params/dist.npy')
#Specify image paths
img_path1 = './reconstruct_this/left2.jpg'
img_path2 = './reconstruct_this/right2.jpg'
#Load pictures
img_1 = cv2.imread(img_path1)
img_2 = cv2.imread(img_path2)
#Get height and width. Note: It assumes that both pictures are the same size. They HAVE to be same size 
h,w = img_2.shape[:2]

然后,基于自由缩放参数,计算出最佳摄像机矩阵。实际上,该算法需要算出一种新的摄像机矩阵,如果我们改变图像大小的话。虽然我们没有实际改变它,但我注意到,通过该算法得到的摄像机矩阵在消除失真方面会得到更好的结果。

#Get optimal camera matrix for better undistortion 
new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(K,dist,(w,h),1,(w,h))
#Undistort images
img_1_undistorted = cv2.undistort(img_1, K, dist, None, new_camera_matrix)
img_2_undistorted = cv2.undistort(img_2, K, dist, None, new_camera_matrix)
#Downsample each image 3 times (because they're too big)
img_1_downsampled = downsample_image(img_1_undistorted,3)
img_2_downsampled = downsample_image(img_2_undistorted,3)

最后,一旦图像没有失真,我们就对他们进行降采样。

降采样有两个功能:1)提高图像处理速度  2)在计算视差图时帮助调整参数

在关于特征匹配算法中,了解图像的大小是非常重要的。这是因为对于我们使用的算法,我们需要指定一个窗口大小。窗口大小越大,相对应的需要计算时间越长。

如果窗口大小不够大,那么视差将无法正确计算,您将得到一个包含各种噪声的深度图(或不完整的深度图)。这对我们的目标是不利的,所以最好对图像进行降采样。本教程中暂时不讨论用于对图像进行降采样的函数,但它会在完整脚本(https://github.com/OmarPadierna/3DReconstruction/blob/master/Reconstruction/disparity.py)的顶部进行声明。

必须指出的是,通过对图像进行降采样,我们不可避免地会丢失信息,因此我们的深度精度也会受到影响。在我看来,如果深度精度对你很重要,那么你最好使用基于激光或红外传感器来绘制深度图。众所周知,立体深度图并不是十分准确。

一旦图像准备好进行处理,我们就可以使用特征匹配算法。根据《学习opencv3》(http://shop.oreilly.com/product/0636920044765.do)一书,立体匹配的标准典型技术是块匹配。opencv提供了两种块匹配实现:立体块匹配和半全局块匹配(SGBM)。两种算法相似,但有区别。

块匹配的关键是在可视区域重叠的两幅图像之间寻找强匹配点。通俗地说,这意味着算法将在捕获同一对象(即相同的事物)的两张图片中寻找相同的像素。

块匹配侧重于高纹理图像(比如树的图片),而半全局块匹配则侧重于子像素级的匹配和纹理更平滑的图片(比如走廊的图片)。

在本教程中,我们使用SGBM,因为这些照片是在室内拍摄的,而且其中有许多平滑的纹理。该算法有三个重要的步骤需要理解。

如果没有对这些步骤的直观的理解,使用SGBM算法将非常困难,因为它接收到的参数取决于对它正在做什么的理解(即使是表面和肤浅的)。

实际上,该算法有3个步骤:

    1. 预过滤图像,用于归一化亮度,增强纹理

    2. 使用SAD窗口沿水平极线执行相对应的搜索

    3. 后过滤图像,以消除不良的相关匹配。

为了完成亮度归一化并增强纹理操作,我们在图像上运行一个窗口(至少5x5,最大21x21)。修改这个窗口大小的参数在代码中称之为win_size。

然后通过滑动SAD窗口来计算相关性。在继续执行之前,从概念上理解什么是极线是很重要的。OpenCV有一个很好的教程,教你如何编写一些代码来可视化它们。

为了更好地理解极线,我们可以做以下练习。手放在脸中间,闭上左眼。然后做对位操作(即闭上右眼,睁开左眼)。你会注意到你手的位置有轻微的变化。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_人工智能_04

一个我理解下的直观解释

因为你的眼睛处于不同的位置,一只眼睛可以看到另一只眼睛看不到的东西。只睁开一只眼,你就看不见你手上的3D点,因为所有的点都投射到你脸上相同的同一图像平面上(即你看不到背后是什么东西)。

然而,另一只眼睛既可以看到它的相对部分在看什么,也可以看到由于它们之间的分离而隐藏的一些东西。再试一次,用你的眼睛亲眼看看,你会注意到,用一只眼睛你能看到某些东西(尤其是在背景中),而另一只眼睛却看不到。

好吧,那又怎样?好吧,当你改变哪只眼睛睁开,哪只眼睛闭上时,你会无意识地把焦点转移到你感兴趣的东西上(在这个例子中是你的手),你可以通过跟随一条线来实现。这条线被称为“极线”。

通过合并双眼的信息,你就可以对你所看到的东西的三维坐标进行三角测量,这就是你理解深度的方法。

相机的原理是一样的,当你用两个平行的相机拍一张照片(或者在一种情况下,两张照片用同一个相机移动才能够得到时),你知道一张照片将包含另一张沿极线的点。

OpenCV对极线几何有一个更正式(也更好)(https://docs.opencv.org/3.4.4/da/de9/tutorial_py_epipolar_geometry.html)的解释图片寿命。点进去看看不是件坏事。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_python_05

对极几何的解释。紫色的线是兴趣点x所在的极线

为什么极线相关?好吧,因为在对图像进行去失真处理后,极线是水平的,而且由于我们确定兴趣点将沿着极线找到,这样,通过SGBM算法遍历它们,就能可以找到匹配项。

这就是第二步的全部内容。然而,我们需要告诉它在什么程度上视差(即偏移量)是可以接受的。为此,我们必须规定最小和最大的差距。这里的目标是通过减去它们来计算差异的数量,这是一种指定图像中像素可以移动的可接受范围的方法。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_python_06

解释最小和最大差异。布拉德斯基和卡勒的《学习OpenCV3》

最后一步是做一些后处理。在进行特征匹配后,有可能出现误报和假证样本(即错误匹配)。为了纠正这些错误,OpenCV有一个唯一性比率,它是匹配值的阈值。

最后,基于块的匹配可能在目标边界附近存在问题(因为一张图片可以看到“后面”,而另一张则看不到,还记得吗?)这就形成了一个由许多微小差异组成的区域,称为“斑点”。为了保护它们,我们必须设置一个斑点窗口,接受这些“斑点”的区域。 

在SGBM算法的特定情况下,有一个名为disp12MaxDiff的参数,它指定从左到右计算的差异与从右到左计算的差异之间允许的最大差异。

如果差异之间的差异超过该阈值,则像素将被宣布为未知。

如果你想知道更多更好的解释这些算法的内容,建议阅读Gari Bradski和Adrian Kaehler合著的《Learning Open CV 3》一书。它还有c++版本的3D重建。

在代码方面,这意味着我们必须定义SGBM对象并设置参数,然后计算视差,如下所示:

#Set disparity parameters
#Note: disparity range is tuned according to specific parameters obtained through trial and error. 
win_size = 5
min_disp = -1
max_disp = 63 #min_disp * 9
num_disp = max_disp - min_disp # Needs to be divisible by 16
#Create Block matching object. 
stereo = cv2.StereoSGBM_create(minDisparity= min_disp,
 numDisparities = num_disp,
 blockSize = 5,
 uniquenessRatio = 5,
 speckleWindowSize = 5,
 speckleRange = 5,
 disp12MaxDiff = 1,
 P1 = 8*3*win_size**2,#8*3*win_size**2,
 P2 =32*3*win_size**2) #32*3*win_size**2)
#Compute disparity map
print ("\nComputing the disparity  map...")
disparity_map = stereo.compute(img_1_downsampled, img_2_downsampled)


#Show disparity map before generating 3D cloud to verify that point cloud will be usable. 
plt.imshow(disparity_map,'gray')
plt.show()

请注意,这些参数和我拍摄的图片非常匹配。在实践中,这将需要手动微调,并进行大量的尝试和错误。这就是为什么在将视差图转换为点云之前,将其可视化非常方便的原因。

经过多次的尝试和错误,我的视差图最终是这样的。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_计算机视觉_07

我自己的视差图

如你所见,这个视差图在我衬衫的区域有很多死点和斑点。而且,我的嘴不见了,似乎噪声很多。这是因为我没有很好地调整SBGM参数。

当图片被适当地扭曲和SGBM算法被很好地调整,你将得到平滑的视差图,如下所示。这个视差图来自于cones dataset(http://vision.middlebury.edu/stereo/data/)。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_计算机视觉_08

光滑的差距地图

优化视差图的最佳方法是在算法的基础上构建一个GUI,并实时优化视差图,以获得更平滑的图像。在未来我将上传一个GUI,以便实时微调,同时我们将使用这个视差图。

一旦我们计算出视差图,我们就必须得到图像中使用的颜色数组。因为我们减少了图像的采样,所以我们需要得到图像的高度和宽度。

更重要的是我们需要得到变换矩阵。这个矩阵负责将深度和颜色重新投影到三维空间中。opencv的文档中有一个转换矩阵的例子。

大多数例子将使用OpenCV文档中的转换矩阵。在我的情况下,事情并不是那么顺利。环顾四周,我发现了一个更通用的矩阵,我的矩阵就是以这个为基础的。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_算法_09

转换矩阵——来自于Didier Stricker教授

#Generate  point cloud. 
print ("\nGenerating the 3D map...")
#Get new downsampled width and height 
h,w = img_2_downsampled.shape[:2]
#Load focal length. 
focal_length = np.load('./camera_params/FocalLength.npy')
#Perspective transformation matrix
#This transformation matrix is from the openCV documentation, didn't seem to work for me. 
Q = np.float32([[1,0,0,-w/2.0],
    [0,-1,0,h/2.0],
    [0,0,0,-focal_length],
    [0,0,1,0]])
#This transformation matrix is derived from Prof. Didier Stricker's power point presentation on computer vision. 
#Link : https://ags.cs.uni-kl.de/fileadmin/inf_ags/3dcv-ws14-15/3DCV_lec01_camera.pdf
Q2 = np.float32([[1,0,0,0],
    [0,-1,0,0],
    [0,0,focal_length*0.05,0], #Focal length multiplication obtained experimentally. 
    [0,0,0,1]])
#Reproject points into 3D
points_3D = cv2.reprojectImageTo3D(disparity_map, Q2)
#Get color points
colors = cv2.cvtColor(img_1_downsampled, cv2.COLOR_BGR2RGB)
#Get rid of points with value 0 (i.e no depth)
mask_map = disparity_map > disparity_map.min()
#Mask colors and points. 
output_points = points_3D[mask_map]
output_colors = colors[mask_map]
#Define name for output file
output_file = 'reconstructed.ply'
#Generate point cloud 
print ("\n Creating the output file... \n")
create_output(output_points, output_colors, output_file)

实际生成点云的算法与我在OpenCV示例中找到的算法完全相同。它是在实际脚本中声明的,不在本教程的范围之内。本质上,它会重塑颜色和顶点的形状,然后将它们一个一个地堆叠起来。

结果生成的数组被写入一个带有特定头文件的文本文件中,该头文件保存为.ply文件。这个文件可以用meshlab可视化。就我而言,这是我的结果。

使用iPhone相机和OpenCV来完成3D重建(第三部分)_人工智能_10

Point cloud of myself

如您所见,图像看起来有噪声和畸变,与视差图的外观非常相似。根据经验,如果你的视差图看起来含有噪声,那么你的点云就会有点失真。

一个好的视差图会产生这样的结果:

使用iPhone相机和OpenCV来完成3D重建(第三部分)_机器学习_11

平滑视差图的点云

差不多就是这样。你可以通过改进你的拍照方式,你的校准方式和微调SGBM算法中的参数来改善结果。

如果您想要一个更完整的点云,那么您应该在感兴趣的对象周围拍摄几对图像,并将所有三维点连接起来,以获得更密集的点云。

我希望这对你的计算机视觉实验有帮助。


标签:map,匹配,img,OpenCV,算法,iPhone,图像,视差,3D
From: https://blog.51cto.com/u_13530535/6477217

相关文章

  • 使用iPhone相机和OpenCV来完成3D重建(第一部分)
    正文字数:1497 阅读时长:2分钟这个教程将带你使用自己的手机摄像头和图片实现从零开始到点云。Postedby OmarPadierna https://becominghuman.ai/stereo-3d-reconstruction-with-opencv-using-an-iphone-camera-part-i-c013907d1ab5这是一个由3部分组成的系列文章。我注意到,其......
  • 3d模型底模和高模的三条详细区别
    3d模型里底模和高模有哪些区别?在3d建模法线贴图的时候,常常会听到底模和高模两种不同的说法。这两种模型究竟有怎样的区别,有分别有着哪些各异的效果呢?本期,模型云就来为您盘点3d模型底模和高模的区别有哪些。3d模型底模和高模的区别有哪些区别一:面数不同底模又称低模,指的是低精度......
  • Unity3D学习笔记(二)创建地形和漫游
    七月3201212:35上午上一章粗略介绍了一下Unity游戏引擎的概念定义和界面功能,这次就来实践一下。我们的目标是没有蛀牙(误),目标是创建一个地形,上面有山脉和盆地,然后再放置一个人物,以第一人称的视角来漫游、观察我们所创建的世界。 在开始设计游戏之前我们需要先重新......
  • Unity3D学习笔记(一)界面介绍
    六月2020128:05下午从开始学习Unity到现在已经过去近三个月了,期间零零散散地在网上找教程、实例,感觉印象不够深刻。好多知识点不是被忽略了,就是被遗忘了。有幸在六一儿童节的时候发现了3DBuzz的基础视频教程,犹如介绍所言,几乎详细到每个菜单和按钮。为了部落(误),为......
  • opencv 边界填充/数值计算/图像阈值
    边界填充importcv2importmatplotlib.pyplotaspltimportnumpyaspyimg=cv2.imread('C:/Users/59925/Desktop/pytest/pics/minions-s.jpg')#读取文件#边界填充#指定填充边界大小top_size,bottom_size,left_size,right_size=50,50,50,50#函数一样只是填充方法type不......
  • opencv 视频提取
    视频提取importcv2importmatplotlibaspltimportnumpyaspy#cv2.VideoCapture可捕获摄像头用数字控制不同设备,如0,1#如果是视频文件直接指定路径即可。vc=cv2.VideoCapture('C:/Users/59925/Desktop/pytest/video/video_minions.mp4')ifvc.isOpened():#判断图像是......
  • opencv 图片处理/颜色通道提取/截取感兴趣部分图片
    图片处理importcv2#=============================#截取图像某一部分ROI(regionofinterest)mini=cv2.imread('C:/Users/59925/Desktop/pytest/pics/minions-s.jpg')print(mini.shape)cut_mini=mini[0:150,0:150]cv2.imshow('img',cut_mini)#============......
  • Unity3D:Pick and select GameObjects
    推荐:将NSDT场景编辑器加入你的3D工具链3D工具集:NSDT简石数字孪生PickandselectGameObjects可以在Scene视图中或从Hierarchy窗口中选择一个游戏对象。也可以一次选择多个游戏对象。Unity会在Scene视图中突出显示选择的游戏对象及其子项。默认情况下,选择轮廓颜色为橙......
  • ESMap 三维地图在智慧园区三维场景的应用-数字孪生3D可视化服务平台
       近年来,得益于物联网、大数据、云计算、人工智能等新一代信息技术的发展,“数字孪生”概念也被广泛的传播,越来越多的应用于智慧建筑、生产制造、智慧园区、水利水务、健康医疗等诸多领域。 如何运用“数字孪生”概念延长企业生命周期,助力企业从2D管理向3D管理的升级?易景......
  • 3dmax编辑常用按钮添加
    显示按钮,点击带笔的小图标修改按钮......