使用Python进行二维图像的三维重建

2D图像的三维重建是从一组2D图像中创建对象或场景的三维模型的过程。这个技术广泛应用于计算机视觉、机器人技术和虚拟现实等领域。

在本文中,我们将解释如何使用Python执行从2D图像到三维重建的过程。我们将使用TempleRing数据集作为示例,逐步演示这个过程。该数据集包含了在对象周围的一个环上采样的阿格里真托(Agrigento)“Dioskouroi神庙”复制品的47个视图。

三维重建的关键概念

在深入了解如何使用Python从2D图像执行三维重建的详细步骤之前,让我们首先回顾一些与这个主题相关的关键概念。

深度图

深度图是一幅图像,其中每个像素代表摄像机和场景中相应点之间的距离。深度图常用于计算机视觉和机器人技术中,用于表示场景的三维结构。

有许多不同的方法可以从2D图像计算深度图,包括立体对应、结构光和飞行时间等。在本文中,我们将使用立体对应来从示例数据集计算深度图。

Point Cloud

点云是表示对象或场景形状的三维空间中的一组点。点云常用于计算机视觉和机器人技术中,用于表示场景的三维结构。

一旦我们计算出代表场景深度的深度图,我们可以使用它来计算一个三维点云。这涉及使用有关摄像机内部和外部参数的信息,将深度图中的每个像素投影回三维空间。

网格

网格是一个由顶点、边和面连接而成的表面表示。网格常用于计算机图形学和虚拟现实中,用于表示对象或场景的形状。

一旦我们计算出代表对象或场景形状的三维点云,我们可以使用它来生成一个网格。这涉及使用诸如Marching Cubes或Poisson表面重建等算法,将表面拟合到点云上。

逐步实现

现在我们已经回顾了与2D图像的三维重建相关的一些关键概念,让我们看看如何使用Python执行这个过程。我们将使用TempleRing数据集作为示例,逐步演示这个过程。下面是一个执行Temple Ring数据集中图像的三维重建的示例代码:

安装库:

pip install numpy scipy

导入库:

#importing libraries 
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os

加载TempleRing数据集的图像:

# Directory containing the dataset images
dataset_dir = '/content/drive/MyDrive/templeRing'
# Initialize the list to store images
images = []# Attempt to load the grayscale images and store them in the list
for i in range(1, 48):  # Assuming images are named templeR0001.png to templeR0047.png
    img_path = os.path.join(dataset_dir, f'templeR{i:04d}.png')
    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
    if img is not None:
        images.append(img)
    else:
        print(f"Warning: Unable to load 'templeR{i:04d}.png'")# Visualize the input images
num_rows = 5  # Specify the number of rows
num_cols = 10  # Specify the number of columns
fig, axs = plt.subplots(num_rows, num_cols, figsize=(15, 8))# Loop through the images and display them
for i, img in enumerate(images):
    row_index = i // num_cols  # Calculate the row index for the subplot
    col_index = i % num_cols   # Calculate the column index for the subplot
    axs[row_index, col_index].imshow(img, cmap='gray')
    axs[row_index, col_index].axis('off')# Fill any remaining empty subplots with a white background
for i in range(len(images), num_rows * num_cols):
    row_index = i // num_cols
    col_index = i % num_cols
    axs[row_index, col_index].axis('off')plt.show()

03c47c3cc1e07dc9864795f4449246cf.jpeg

解释:这段代码加载灰度图像序列,将它们排列在网格布局中,并使用matplotlib显示它们。

为每个图像计算深度图:

# Directory containing the dataset images
dataset_dir = '/content/drive/MyDrive/templeRing'
# Initialize the list to store images
images = []# Attempt to load the grayscale images and store them in the list
for i in range(1, 48):  # Assuming images are named templeR0001.png to templeR0047.png
    img_path = os.path.join(dataset_dir, f'templeR{i:04d}.png')
    img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
    if img is not None:
        images.append(img)
    else:
        print(f"Warning: Unable to load 'templeR{i:04d}.png'")# Initialize the list to store depth maps
depth_maps = []# Create a StereoBM object with your preferred parameters
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)# Loop through the images to calculate depth maps
for img in images:
    # Compute the depth map
    disparity = stereo.compute(img, img)    # Normalize the disparity map for visualization
    disparity_normalized = cv2.normalize(
        disparity, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)    # Append the normalized disparity map to the list of depth maps
    depth_maps.append(disparity_normalized)# Visualize all the depth maps
num_rows = 5  # Specify the number of rows
num_cols = 10  # Specify the number of columns
fig, axs = plt.subplots(num_rows, num_cols, figsize=(15, 8))for i, depth_map in enumerate(depth_maps):
    row_index = i // num_cols  # Calculate the row index for the subplot
    col_index = i % num_cols   # Calculate the column index for the subplot
    axs[row_index, col_index].imshow(depth_map, cmap='jet')
    axs[row_index, col_index].axis('off')# Fill any remaining empty subplots with a white background
for i in range(len(depth_maps), num_rows * num_cols):
    row_index = i // num_cols
    col_index = i % num_cols
    axs[row_index, col_index].axis('off')plt.show()

解释:这段代码负责使用Stereo Block Matching(StereoBM)算法从一系列立体图像中计算深度图。它遍历灰度立体图像列表,并为每一对相邻图像计算深度图。

可视化每个图像的深度图:

# Initialize an accumulator for the sum of depth maps
sum_depth_map = np.zeros_like(depth_maps[0], dtype=np.float64)
# Compute the sum of all depth maps
for depth_map in depth_maps:
    sum_depth_map += depth_map.astype(np.float64)
# Calculate the mean depth map by dividing the sum by the number of depth maps
mean_depth_map = (sum_depth_map / len(depth_maps)).astype(np.uint8)
# Display the mean depth map
plt.figure(figsize=(8, 6))
plt.imshow(mean_depth_map, cmap='jet')
plt.title('Mean Depth Map')
plt.axis('off')
plt.show()

输出:

2707fa661da54abeb2386179e16b6ecb.jpeg

解释:这段代码通过累加深度图来计算平均深度图。然后,通过将总和除以深度图的数量来计算平均值。最后,使用jet颜色图谱显示平均深度图以进行可视化。

从平均深度图计算三维点云

# Initialize an accumulator for the sum of depth maps
sum_depth_map = np.zeros_like(depth_maps[0], dtype=np.float64)
# Compute the sum of all depth maps
for depth_map in depth_maps:
    sum_depth_map += depth_map.astype(np.float64)# Calculate the mean depth map by dividing the sum by the number of depth maps
mean_depth_map = (sum_depth_map / len(depth_maps)).astype(np.uint8)# Display the mean depth map
plt.figure(figsize=(8, 6))
plt.imshow(mean_depth_map, cmap='jet')
plt.title('Mean Depth Map')
plt.axis('off')
plt.show()

1b32de06ed9913c45fa573d4f5631e8c.jpeg

解释:这段代码通过对深度图进行累加来计算平均深度图。然后,通过将总和除以深度图的数量来计算平均值。最后,使用Jet颜色映射来可视化显示平均深度图。

计算平均深度图的三维点云

#converting into point cloud 
points_3D = cv2.reprojectImageTo3D(mean_depth_map.astype(np.float32), np.eye(4))

解释:该代码将包含点云中点的三维坐标,并且您可以使用这些坐标进行三维重建。

点云生成网格

安装库

!pip install numpy scipy

导入库

#importing libraries 
from scipy.spatial import Delaunay
from skimage import measure
from skimage.measure import marching_cubes

生成网格

verts, faces, normals, values = measure.marching_cubes(points_3D)

解释:该代码将Marching Cubes算法应用于3D点云以生成网格。它返回定义结果3D网格的顶点、面、顶点法线和标量值。

可视化网格

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_trisurf(verts[:, 0], verts[:, 1], verts[:, 2], triangles=faces)
plt.show()

输出:

f151ba53e9d647b918eed7586832aa31.png

解释:该代码使用matplotlib可视化网格。它创建一个3D图并使用ax.plot_trisurf方法将网格添加到其中。

这段代码从Temple Ring数据集加载图像,并使用块匹配(block matching)进行每个图像的深度图计算,然后通过平均所有深度图来计算平均深度图,并使用它来计算每个像素的三维点云。最后,它使用Marching Cubes算法从点云生成网格并进行可视化。

结果比较

# importing the libraries
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Create a figure with two subplots
fig, axs = plt.subplots(1, 2, figsize=(10, 5))# Visualize the original image in the first subplot
axs[0].imshow(images[0], cmap='gray')
axs[0].axis('off')
axs[0].set_title('Original')# Visualize the reconstructed mesh in the second subplot
ax = fig.add_subplot(1, 2, 2, projection='3d')
ax.plot_trisurf(verts[:, 0], verts[:, 1], verts[:, 2], triangles=faces)
ax.set_title('Reconstructed')# Show the figure
plt.show()

ed0badbd09152e13a56cf81dd247bdf9.jpeg

解释:在此代码中,使用matplotlib创建了包含两个子图的图形。在第一个图中,显示了来自数据集的原始图像。在第二个图中,使用3D三角形表面图可视化了重建的3D网格。

方法2

以下是执行来自TempleRing数据集图像的3D重建的另一个示例代码:

引入模块:

import cv2
import numpy as np
import matplotlib.pyplot as plt
from google.colab.patches import cv2_imshow

加载两个Temple Ring数据集图像:

# Load the PNG images (replace with your actual file paths)
image1 = cv2.imread('/content/drive/MyDrive/templeRing/templeR0001.png')
image2 = cv2.imread('/content/drive/MyDrive/templeRing/templeR0002.png'

解释:该代码使用OpenCV的cv2.imread函数从TempleRing数据集加载两个图像。

转换为灰度图:

# Convert images to grayscale
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

该代码使用OpenCV将两个图像转换为灰度图像。它们以单通道表示,其中每个像素的值表示其强度,并且没有颜色通道。

查找SIFT关键点和描述符:

# Initialize the SIFT detector
sift = cv2.SIFT_create()
# Detect keypoints and compute descriptors for both images
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)

该代码使用尺度不变特征变换(SIFT)算法在两个图像中查找关键点和描述符。它使用OpenCV的cv2.SIFT_create()函数创建一个SIFT对象,并调用其detectAndCompute方法来计算关键点和描述符。

使用FLANN匹配器匹配描述符:

# Create a FLANN-based Matcher object
flann = cv2.FlannBasedMatcher({'algorithm': 0, 'trees': 5}, {})
# Match the descriptors using KNN (k-nearest neighbors)
matches = flann.knnMatch(des1, des2, k=2)

解释:该代码使用Fast Library for Approximate Nearest Neighbors(FLANN)匹配器对描述符进行匹配。它使用OpenCV的cv2.FlannBasedMatcher函数创建FLANN匹配器对象,并调用其knnMatch方法来找到每个描述符的k个最近邻。

使用Lowe的比率测试筛选出好的匹配项

# Apply Lowe's ratio test to select good matches
good_matches = []
for m, n in matches:
    if m.distance < 0.7 * n.distance:
        good_matches.append(m)

解释:该代码使用Lowe的比率测试筛选出好的匹配项。它使用最近邻和次近邻之间距离比的阈值来确定匹配是否良好。

提取匹配的关键点

# Extract matched keypoints
src_pts = np.float32(
    [kp1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
dst_pts = np.float32(
    [kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)

解释:该代码从两组关键点中提取匹配的关键点,这些关键点将用于估算对齐两个图像的变换。这些关键点的坐标存储在'src_pts'和'dst_pts'中。

使用RANSAC找到单应矩阵

# Find the homography matrix using RANSAC
H, _ = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)

在这段代码中,它使用RANSAC算法基于匹配的关键点计算描述两个图像之间的变换的单应矩阵。单应矩阵后来可以用于拉伸或变换一个图像,使其与另一个图像对齐。

使用单应矩阵将第一个图像进行变换

# Perform perspective transformation to warp image1 onto image2
height, width = image2.shape[:2]
result = cv2.warpPerspective(image1, H, (width, height))
# Display the result
cv2_imshow(result)

28ca168db5a14696349025da381f0a8a.jpeg

解释:该代码使用单应矩阵和OpenCV的cv2.warpPerspective函数将第一个图像进行变换。它指定输出图像的大小足够大,可以容纳两个图像,然后呈现结果图像。

显示原始图像和重建图像

# Display the original images and the reconstructed image side by side
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4))
ax1.imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB))
ax1.set_title('Image 1')
ax1.axis('off')
ax2.imshow(cv2.cvtColor(image2, cv2.COLOR_BGR2RGB))
ax2.set_title('Image 2')
ax2.axis('off')
ax3.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
ax3.set_title('Reconstructed Image')
ax3.axis('off')
plt.show()

输出:

84885befac51176bdd57a076d982f6e6.jpeg

解释:这段代码展示了在一个具有三个子图的单一图形中可视化原始图像和重建图像的过程。它使用matplotlib库显示图像,并为每个子图设置标题和轴属性。

不同的可能方法

有许多不同的方法和算法可用于从2D图像执行3D重建。选择的方法取决于诸如输入图像的质量、摄像机校准信息的可用性以及重建的期望准确性和速度等因素。

一些常见的从2D图像执行3D重建的方法包括立体对应、运动结构和多视图立体。每种方法都有其优点和缺点,对于特定应用来说,最佳方法取决于具体的要求和约束。

结论

总的来说,本文概述了使用Python从2D图像进行3D重建的过程。我们讨论了深度图、点云和网格等关键概念,并使用TempleRing数据集演示了使用两种不同方法逐步进行的过程。我们希望本文能帮助您更好地理解从2D图像进行3D重建以及如何使用Python实现这一过程。有许多可用于执行3D重建的不同方法和算法,我们鼓励您进行实验和探索,以找到最适合您需求的方法。

·  END  ·

HAPPY LIFE

b6791b364b263441b51c0b6b8fb520c8.png

本文仅供学习交流使用,如有侵权请联系作者删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/159389.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot-集成Kafka详解

SpringBoot集成Kafka 1、构建项目 1.1、引入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.5.RELEASE</version> </parent> <dependenci…

机器学习第7天:逻辑回归

文章目录 介绍 概率计算 逻辑回归的损失函数 单个实例的成本函数 整个训练集的成本函数 鸢尾花数据集上的逻辑回归 Softmax回归 Softmax回归数学公式 Softmax回归损失函数 调用代码 参数说明 结语 介绍 作用&#xff1a;使用回归算法进行分类任务 思想&#xff1a;…

某60区块链安全之整数溢出漏洞实战学习记录

区块链安全 文章目录 区块链安全整数溢出漏洞实战实验目的实验环境实验工具实验原理攻击过程分析合约源代码漏洞EXP利用 整数溢出漏洞实战 实验目的 学会使用python3的web3模块 学会以太坊整数溢出漏洞分析及利用 实验环境 Ubuntu18.04操作机 实验工具 python3 实验原理…

GEM5 Garnet DVFS / NoC DVFS教程:ruby.clk_domain ruby.voltage_domain

简介 gem5中的 NoC部分是Garnet实现的&#xff0c;但是Garnet并没有单独的时钟域&#xff0c;而是保持ruby一致&#xff0c;要做noc的DVFS&#xff0c;便是要改ruby的 改电压 #这里只是生成一个随便变量名&#xff0c;存一下值。改是和频率一起的 userssaved_voltage_domain…

鸿蒙开发|鸿蒙系统项目开发前的准备工作

文章目录 鸿蒙项目开发的基本流程介绍鸿蒙项目开发和其他项目有什么不同成为华为开发者-注册和实名认证1.登录官方网站 鸿蒙项目开发的基本流程介绍 直接上图&#xff0c;简单易懂&#xff01; 整个项目的开发通过4个模块进行&#xff1a;开发准备、开发应用、运行调试测试和发…

(六)什么是Vite——热更新时vite、webpack做了什么

vite分享ppt&#xff0c;感兴趣的可以下载&#xff1a; ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录&#xff1a; &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff0…

DevExpress中文教程 - 如何在macOS和Linux (CTP)上创建、修改报表(上)

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 DevExpress Reports — 跨平台报表组件&#x…

Pandas中loc和iloc函数(提取某几列或者行的数据)

loc函数&#xff1a;通过行索引&#xff08;列名、行名&#xff09; 中的具体值来取行数据&#xff08;如取"Index"为"A"的行&#xff09; iloc函数&#xff1a;通过行号&#xff08;数字&#xff09;来取行数据&#xff08;如取第二行的数据&#xff09;…

Vue 路由缓存 防止路由切换数据丢失

在切换路由的时候&#xff0c;如果写好了一丢数据在去切换路由在回到写好的数据的路由去将会丢失&#xff0c;这时可以使用路由缓存技术进行保存&#xff0c;这样两个界面来回换数据也不会丢失 在 < router-view >展示的内容都不会被销毁&#xff0c;路由来回切换数据也…

汇编-间接寻址(处理数组)

直接寻址很少用于数组处理&#xff0c;因为用常数偏移量来寻址多个数组元素时&#xff0c;直接寻址并不实用。取而代之的是使用寄存器作为指针(称为间接寻址(indirect addressing) ) 并控制该寄存器的值。如果一个操作数使用的是间接寻址&#xff0c; 就称之为间接操作数(indie…

Sql Server 2017主从配置之:事务日志传送

使用事务日志传送模式搭建Sql Server 2017主从同步&#xff0c;该模式有一定的延迟&#xff0c;是通过3个不同的定时任务&#xff0c;将主库的日志同步到从库进行恢复来实现数据库同步操作。 环境准备 两台服务器&#xff0c;配置都是8g2核&#xff0c;50g硬盘&#xff0c;操…

【ES6标准入门】JavaScript中的模块Module的加载实现:循环加载和Node加载,非常详细,建议收藏!!!

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript进阶指南 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继…

Git企业开发级讲解(三)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、版本回退1、内容2、演示 二、撤销修改1、情况⼀&#xff1a;对于⼯作区的代码&#xff0c…

基于STM32的无线传感器网络(WSN)通信方案设计与实现

无线传感器网络&#xff08;Wireless Sensor Network&#xff0c;简称WSN&#xff09;是由一组分布式的无线传感器节点组成的网络&#xff0c;用于监测和收集环境中的各类物理信息。本文将基于STM32微控制器&#xff0c;设计并实现一个简单的无线传感器网络通信方案&#xff0c…

Qkeras量化模型-直接搭建模型的量化感知训练

qkeras是谷歌的感知训练量化框架&#xff0c;具有一些功能&#xff1a; 1、支持导入keras模型到qkeras模型&#xff1b; 2、支持剪枝和量化&#xff0c;使用tensorflow lite一起配合&#xff0c;简直不要太好用&#xff1b; 3、支持指定量化函数&#xff0c;量化的bit数目、…

计算机毕业设计选题推荐-人才招聘微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

nacos客户端连接服务端报Client not connected, current status:STARTING

说明&#xff1a; nacos服务端版本&#xff1a;v2.1.2 nacos客户端版本&#xff1a;2.1.2 结果启动项目报错&#xff1a; Client not connected, current status:STARTING 解决&#xff1a; 降低客户端版本至 1.4.1 就Ok了 <dependency><groupId>com.alibaba.naco…

机器学习第5天:多项式回归与学习曲线

文章目录 多项式回归介绍 方法与代码 方法描述 分离多项式 学习曲线的作用 场景 学习曲线介绍 欠拟合曲线 示例 结论 过拟合曲线 示例 ​结论 多项式回归介绍 当数据不是线性时我们该如何处理呢&#xff0c;考虑如下数据 import matplotlib.pyplot as plt impo…

基于SSM的中小型企业财务管理设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

spring cloud openfeign 使用注意点

近期在做项目时给自己挖了一个坑&#xff0c;问题重现如下 使用的组件版本如下 spring boot 2.7.15&#xff0c;对应的 spring cloud 版本为 2021.0.5&#xff0c;其中 spring cloud 适配的 openfeign 版本是 3.1.5。 项目中使用的 feign 接口如下 public interface QueryApi…