在mac与ubuntu上部署DeepSeek-R1模型 & 7b和32b模型的对比测试
笔者的硬件配置
mac端:macbook air m1,16G内存
ubuntu端:i7-13700kf + RTX 4090,32G内存
安装Ollama
访问下面的网站,下载对应操作系统的安装包,根据提示安装。
macOS
安装完之后,直接打开,状态栏会出来一只小羊驼。
打开mac的终端输入ollama -v
,有如下的输出就说明安装成功了。
Ubuntu
ubuntu通过下面的指令可以很轻松的完成安装。
curl -fsSL https://ollama.com/install.sh | sh
和mac一样,终端输入ollama -v
,有如下的输出就说明安装成功了。
模型的选择与部署
打开下面的网站,根据电脑的配置,选择合适的模型进行部署。
https://ollama.com/library/deepseek-r1:1.5b
macOS
笔者认为mac有16G内存的话,可以先尝试一下7b的模型。
看来M1 16G还是可以顺利运行7b的模型,占用的内存在2-4G之间。
Ubuntu
Ubuntu的内存有点吃紧,担心模型对内存也有要求,笔者给了64G的swap空间,尝试运行32b的模型。
能够顺利运行32b的模型,内存占用不多,显存占了22G。
7b和32b模型的对比测试
笔者准备了几类自己常问ChatGPT的问题,比较一下7b和32b模型。
测试的问题比较少,代码部分内容较多,随机性还挺大的。这里只总结这三个问题的表现:
7b和32b在代码解析上差不太多,个人看不出区别来;
7b在代码报错问题上,只生成了一个解决方案,可能需要多次交互才能顺利解决问题;
32b的结果和ChatGPT比较接近,给了几个解决方案,其中有一个直接能解决问题的;
在代码生成上,需要计算的是相机视锥体,总共是5个点。7b给的代码是错误的,把相机后面不可见的区域也考虑进去了,算了8个点;32b的代码没有问题,计算的是相机视锥体。
下面的内容感兴趣可以看看。
代码解析
笔者使用gaussian splatting中的一段代码来测试这两个模型,下面是发送给这两个模型的内容。
逐行注释下面的代码,并且总结这个函数和对应的公式,使用latex代码写出来。
注意:我希望的是你把注释好的代码放在一起,返回我一整个代码块,然后再总结函数的功能,以及对应的公式。
def _ssim(img1, img2, window, window_size, channel, size_average=True):
mu1 = F.conv2d(img1, window, padding=window_size // 2, groups=channel)
mu2 = F.conv2d(img2, window, padding=window_size // 2, groups=channel)
mu1_sq = mu1.pow(2)
mu2_sq = mu2.pow(2)
mu1_mu2 = mu1 * mu2
sigma1_sq = F.conv2d(img1 * img1, window, padding=window_size // 2, groups=channel) - mu1_sq
sigma2_sq = F.conv2d(img2 * img2, window, padding=window_size // 2, groups=channel) - mu2_sq
sigma12 = F.conv2d(img1 * img2, window, padding=window_size // 2, groups=channel) - mu1_mu2
C1 = 0.01 ** 2
C2 = 0.03 ** 2
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
if size_average:
return ssim_map.mean()
else:
return ssim_map.mean(1).mean(1).mean(1)
对于7b,它的回答是
def _ssim(img1, img2, window, window_size, channel, size_average=True):
# 计算img1的加权均值
mu1 = F.conv2d(img1, window, padding=window_size // 2, groups=channel)
# 计算img2的加权均值
mu2 = F.conv2d(img2, window, padding=window_size // 2, groups=channel)
# 计算img1的平方并加权均值
mu1_sq = mu1.pow(2)
# 计算img2的平方并加权均值
mu2_sq = mu2.pow(2)
# 计算img1和img2乘积的加权均值
mu1_mu2 = mu1 * mu2
# 计算img1的方差
sigma1_sq = F.conv2d(img1 * img1, window, padding=window_size // 2, groups=channel) - mu1_sq
# 计算img2的方差
sigma2_sq = F.conv2d(img2 * img2, window, padding=window_size // 2, groups=channel) - mu2_sq
# 计算img1和img2乘积的方差
sigma12 = F.conv2d(img1 * img2, window, padding=window_size // 2, groups=channel) - mu1_mu2
# 避免分母为零的情况
C1 = 0.01 ** 2
C2 = 0.03 ** 2
# 计算SSIM映射
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
# 返回结果的平均值或通道平均值
if size_average:
return ssim_map.mean()
else:
return ssim_map.mean(1).mean(1).mean(1)
# SSIM公式总结:
# SSIM(img1, img2) = [ (2μ1μ2 + C1)(2σ12 + C2) ] / [ (μ1² + μ2² + C1)(σ1² + σ2² + C2) ]
# 其中:
# μ1和μ2是img1和img2的加权均值
# σ1²和σ2²是img1和img2的加权方差
# σ12是img1和img2乘积的加权协方差
# C1和C2是防止分母为零的常数
# size_average参数决定返回的是整体平均值还是通道级平均值
代码总结:
- 这个函数计算两个图像之间的SSIM(结构相似性)值。
- 它通过滑动窗口计算均值、方差和协方差,并根据预定义的常数C1和C2进行归一化。
- 最终返回的是整体平均值或通道级平均值,取决于size_average参数。
公式总结:
$$
SSIM(img1, img2) = \frac{(2\mu_1\mu_2 + C1)(2\sigma_{12} + C2)}{(\mu_1^2 + \mu_2^2 + C1)(\sigma_1^2 + \sigma_2^2 + C2)}
$$
其中:
- $\mu_1$和$\mu_2$是img1和img2的加权均值
- $\sigma_1^2$和$\sigma_2^2$是img1和img2的加权方差
- $\sigma_{12}$是img1和img2乘积的加权协方差
- $C1 = (0.01)^2$和$C2 = (0.03)^2$是防止分母为零的常数
对于32b,它的回答是
def _ssim(img1, img2, window, window_size, channel, size_average=True):
# 计算第一个图像的局部均值 (Local mean of the first image)
mu1 = F.conv2d(img1, window, padding=window_size // 2, groups=channel)
# 计算第二个图像的局部均值 (Local mean of the second image)
mu2 = F.conv2d(img2, window, padding=window_size // 2, groups=channel)
# 计算两个均值的平方 (Square of means)
mu1_sq = mu1.pow(2)
mu2_sq = mu2.pow(2)
# 计算两个图像均值的乘积 (Product of means)
mu1_mu2 = mu1 * mu2
# 计算第一个图像的方差 (Variance of the first image)
sigma1_sq = F.conv2d(img1 * img1, window, padding=window_size // 2, groups=channel) - mu1_sq
# 计算第二个图像的方差 (Variance of the second image)
sigma2_sq = F.conv2d(img2 * img2, window, padding=window_size // 2, groups=channel) - mu2_sq
# 计算两个图像之间的协方差 (Covariance between images)
sigma12 = F.conv2d(img1 * img2, window, padding=window_size // 2, groups=channel) - mu1_mu2
# 定义常数项,防止除零操作(一般取远小于图像亮度变化范围的值)
C1 = (0.01 * 255)**2
C2 = (0.03 * 255)**2
# 计算SSIM相似度映射图
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / \
((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
# 根据需要返回整体平均值或通道平均值
if size_average:
return ssim_map.mean()
else:
return ssim_map.mean(1).mean(1).mean(1)
函数总结:
该函数实现了结构相似性指数 (Structural Similarity Index, SSIM) 的计算。SSIM是一种衡量两幅图像相似度的指标,考虑了亮度(luminance)、对比度(contrast)和结构(structure)之间的差异。
公式总结:
SSIM的计算公式如下:
$$
SSIM(x, y) = \frac{(2\mu_x\mu_y + C_1)(2\sigma_{xy} + C_2)}{(\mu_x^2 + \mu_y^2 + C_1)(\sigma_x^2 + \sigma_y^2 + C_2)}
$$
其中:
- $\mu_x, \mu_y$ 是图像x和y的局部均值
- $\sigma_x^2, \sigma_y^2$是图像x和y的方差
- $\sigma_{xy}$ 是图像x和y的协方差
- $C_1 = (0.01L)^2, C_2 = (0.03L)^2$是常数项,L是像素的最大值(默认为255)
代码报错问题排查&如何解决报错
这是笔者运行MonoGS的时候遇到的一个问题,下面的代码块是发送给模型的内容。
这个问题已经通过ChatGPT解决,原因是这段代码认为系统没有显示器或图形界面,切换 matplotlib
的后端为无显示后端,例如 Agg
就可以了。(吐槽:matplotlib这里没有创建图形界面,保存图片居然还需要xxxxxx)
下面这段代码,以前都没有报错过,现在突然就报错了。
这是为什么?如何解决报错的问题?
def evaluate_evo(poses_gt, poses_est, plot_dir, label, monocular=False):
## Plot
traj_ref = PosePath3D(poses_se3=poses_gt)
traj_est = PosePath3D(poses_se3=poses_est)
traj_est_aligned = trajectory.align_trajectory(
traj_est, traj_ref, correct_scale=monocular
)
## RMSE
pose_relation = metrics.PoseRelation.translation_part
data = (traj_ref, traj_est_aligned)
ape_metric = metrics.APE(pose_relation)
ape_metric.process_data(data)
ape_stat = ape_metric.get_statistic(metrics.StatisticsType.rmse)
ape_stats = ape_metric.get_all_statistics()
Log("RMSE ATE \[m]", ape_stat, tag="Eval")
with open(
os.path.join(plot_dir, "stats_{}.json".format(str(label))),
"w",
encoding="utf-8",
) as f:
json.dump(ape_stats, f, indent=4)
plot_mode = evo.tools.plot.PlotMode.xy
fig = plt.figure()
报错为
File "slam.py", line 325, in <module>
slam = SLAM(config, save_dir=save_dir)
File "slam.py", line 145, in __init__
self.frontend.run()
File "/home/immortalqx/Projects/MonoGS-noted/utils/slam_frontend.py", line 554, in run
monocular=self.monocular,
File "/home/immortalqx/Projects/MonoGS-noted/utils/eval_utils.py", line 110, in eval_ate
monocular=monocular,
File "/home/immortalqx/Projects/MonoGS-noted/utils/eval_utils.py", line 50, in evaluate_evo
fig = plt.figure()
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/pyplot.py", line 809, in figure
FigureClass=FigureClass, **kwargs)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/pyplot.py", line 325, in new_figure_manager
return _get_backend_mod().new_figure_manager(*args, **kwargs)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backend_bases.py", line 3494, in new_figure_manager
return cls.new_figure_manager_given_figure(num, fig)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backends/_backend_tk.py", line 966, in new_figure_manager_given_figure
manager = cls.FigureManager(canvas, num, window)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backends/_backend_tk.py", line 423, in __init__
self.toolbar = self._get_toolbar()
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backends/_backend_tk.py", line 446, in _get_toolbar
toolbar = NavigationToolbar2Tk(self.canvas, self.window)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backends/_backend_tk.py", line 558, in __init__
command=getattr(self, callback),
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backends/_backend_tk.py", line 680, in _Button
NavigationToolbar2Tk._set_image_for_button(self, b)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/matplotlib/backends/_backend_tk.py", line 659, in _set_image_for_button
image = ImageTk.PhotoImage(im.resize((size, size)), master=self)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/PIL/Image.py", line 2171, in resize
im = im.resize(size, resample, box)
File "/home/immortalqx/package/anaconda3/envs/MonoGS/lib/python3.7/site-packages/PIL/Image.py", line 2193, in resize
return self._new(self.im.resize(size, resample, box))
ValueError: height and width must be > 0
7b模型的回答
通过这种方法,你应该能够解决报错的问题。如果问题仍然存在,请提供更多相关代码或上下文以便进一步诊断。
最终答案:在调用plt.figure()
时,如果没有传递适当的figsize
参数,可能导致绘图区域无效。通过添加合适的figsize
参数(如10x6英寸)可以解决问题:
修改后的代码如下:
def evaluate_evo(poses_gt, poses_est, plot_dir, label, monocular=False):
## Plot
traj_ref = PosePath3D(poses_se3=poses_gt)
traj_est = PosePath3D(poses_se3=poses_est)
traj_est_aligned = trajectory.align_trajectory(
traj_est, traj_ref, correct_scale=monocular
)
## RMSE
pose_relation = metrics.PoseRelation.translation_part
data = (traj_ref, traj_est_aligned)
ape_metric = metrics.APE(pose_relation)
ape_metric.process_data(data)
ape_stat = ape_metric.get_statistic(metrics.StatisticsType rmse)
ape_stats = ape_metric.get_all_statistics()
Log("RMSE ATE \[m\]", ape_stat, tag="Eval")
with open(
os.path.join(plot_dir, "stats_{}.json".format(str(label))),
"w",
encoding="utf-8",
) as f:
json.dump(ape_stats, f, indent=4)
plot_mode = evo.tools.plot.PlotMode.xy
fig = plt.figure(figsize=(10, 6)) # 添加合适的图尺寸
32b模型的回答
为了解决报错的问题,请按照以下步骤操作:
检查当前Python环境:确保你使用的是支持图形输出的环境,或者在没有 GUI 的环境中正确配置了matplotlib。
修改代码以设置合适的backend:
在你的代码中添加以下两行,放在任何导入matplotlib.pyplot之前:
import matplotlib matplotlib.use('Agg') # 设置为非交互式backend
显式指定绘图大小(可选):
如果仍然有问题,可以在创建figure时明确设置大小,确保宽高都是正数:
fig = plt.figure(figsize=(10, 6))
处理异常情况:
在绘图之前,添加错误检查,防止负值或零尺寸被传递给绘图函数。
重新运行代码:
应用上述修改后,再次执行代码,观察是否还出现相同错误。
如果问题依然存在,请进一步核实PIL库的版本以及系统环境的变化,并考虑其他可能的解决方案。
代码补充&生成
笔者不喜欢让AI生成所有的代码,笔者一般是搭建一个框架让AI来补充,并提供一些必要的说明。因此这里对比一下7b和32b模型的代码补充和生成的能力,下面是发送给模型的内容。
我需要补充下面的代码,根据相机旋转camera_rotation、相机平移camera_position、相机的fov_x和fov_y,来计算相机视锥体的bbox,只需要计算2D的bbox,buffer_distance是对bbox扩展的比例。
def visualHull(self, camera_rotation, camera_position, buffer_distance, fov_x, fov_y, min_x, max_x, min_z, max_z):
# 计算相机坐标系下的视锥体
# 将相机坐标系下的视锥体变换到世界坐标系中
# 计算世界坐标系下视锥体的bbox,并且根据buffer_distance进行扩展
# 根据传入的min_x, max_x, min_z, max_z,更新min_x, max_x, min_z, max_z
# 返回更新之后的min_x, max_x, min_z, max_z
return min_x, max_x, min_z, max_z
7b模型的回答
看起来你正在尝试在相机坐标系中定义一个视锥体(视界盒),然后将其变换到世界坐标系中,并根据缓冲距离扩展这个视界盒的边界框(bbox)。以下是具体的实现步骤和代码示例:
函数实现思路
- 相机坐标系到世界坐标系转换
首先,需要将相机位置和旋转参数转换为齐次变换矩阵。通过这个矩阵可以将相机坐标系中的点转换到世界坐标系。 - 生成视锥体边界点
根据相机的FOV(水平和垂直)、缓冲距离和其他参数,计算出在相机坐标系中视锥体的边界点。 - 变换到世界坐标系
使用齐次变换矩阵将这些边界点转换到世界坐标系中。 - 计算bbox并扩展
计算这些点的最小和最大值(x, y, z),然后根据缓冲距离扩展bbox。 - 更新边界参数
根据传入的min_x、max_x等参数,更新这些参数。
实现代码
import numpy as np
def visualHull(camera_rotation, camera_position, buffer_distance, fov_x, fov_y, min_x, max_x, min_z, max_z):
"""
计算相机视锥体的bbox,并根据缓冲距离进行扩展。
输入:
camera_rotation: 相机旋转矩阵(3x3)
camera_position: 相机位置向量(3D)
buffer_distance: 扩展的距离比例
fov_x: 水平视场角(度)
fov_y: 垂直视场角(度)
min_x, max_x, min_z, max_z: 初始边界参数
输出:
更新后的min_x, max_x, min_z, max_z
"""
# 将相机旋转矩阵扩展为齐次变换矩阵
homogeneous_matrix = np.zeros((4, 4))
homogeneous_matrix[:3, :3] = camera_rotation
homogeneous_matrix[3, 3] = 1.0
homogeneous_matrix[:3, 3] = camera_position
# 定义相机坐标系中的视锥体边界点
# 计算水平和垂直的切片范围
h_width = np.tan(np.radians(fov_x / 2)) * (-min_z - buffer_distance)
v_height = np.tan(np.radians(fov_y / 2)) * (max_z + buffer_distance)
# 生成边界点
x_points = np.array([h_width, h_width, -h_width, -h_width]) # x方向的两个端点
z_points = np.array([-min_z, max_z, -min_z, max_z]) * (-1) + buffer_distance # z方向的两个端点
y_points = np.array([v_height, v_height, -v_height, -v_height]) # y方向的两个端点
# 创建所有组合
points = []
for x in x_points:
for z in z_points:
for y in y_points:
point = np.array([x, y, z, 1.0])
world_point = homogeneous_matrix @ point
points.append(world_point[:3]) # 添加变换后的点
# 计算bbox(世界坐标系下的边界)
if not points:
return min_x, max_x, min_z, max_z
world_min_x = np.min([p[0] for p in points])
world_max_x = np.max([p[0] for p in points])
world_min_y = np.min([p[1] for p in points])
world_max_y = np.max([p[1] for p in points])
world_min_z = np.min([p[2] for p in points])
world_max_z = np.max([p[2] for p in points])
# 根据缓冲距离扩展bbox
extended_buffer_distance = buffer_distance / 2.0
new_min_x = world_min_x - extended_buffer_distance
new_max_x = world_max_x + extended_buffer_distance
new_min_y = world_min_y - extended_buffer_distance
new_max_y = world_max_y + extended_buffer_distance
new_min_z = world_min_z - extended_buffer_distance
new_max_z = world_max_z + extended_buffer_distance
# 根据传入的参数更新边界值
min_x_new = max(min_x, new_min_x)
max_x_new = min(max_x, new_max_x)
min_y_new = max(min_y, new_min_y) # 如果需要扩展y方向,可以考虑增加这两个变量
# 返回更新后的参数(假设只关注x和z)
return (min_x_new, max_x_new, min_z_new, max_z_new)
使用说明
输入参数:
camera_rotation
: 相机旋转矩阵(3x3)。camera_position
: 相机位置向量(3D)。buffer_distance
: 扩展的距离比例。fov_x
和fov_y
: 水平和垂直视场角,以度为单位。min_x, max_x, min_z, max_z
: 初始边界参数。
输出:
- 返回更新后的边界参数
(min_x_new, max_x_new, min_z_new, max_z_new)
。
- 返回更新后的边界参数
示例
# 初始化参数
min_x = 0
max_x = 10
min_z = 5
max_z = 20
# 相机参数
camera_rotation = np.array([[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]]) # 无旋转
camera_position = np.array([5.0, 2.0, 15.0]) # 相机位置
# 计算视觉锥体并扩展bbox
buffer_distance = 1.0
fov_x = 60.0
fov_y = 45.0
new_min_x, new_max_x, new_min_z, new_max_z = visualHull(camera_rotation, camera_position, buffer_distance, fov_x, fov_y, min_x, max_x, min_z, max_z)
print("更新后的边界:", new_min_x, new_max_x, new_min_z, new_max_z)
注意事项
- 单位转换:视场角需要从度转换为弧度。
- 相机坐标系方向:如果相机的z轴指向物体,需调整视锥体的方向(如将
min_z
和max_z
替换为-min_z
和-max_z
)。 - 扩展距离:buffer_distance 可以根据需求调整,控制bbox的扩展程度。
希望这个实现对您有帮助!
32b模型的回答
步骤说明:
定义参数:
确定所有输入参数的意义和单位。视锥体顶点生成:
- 根据
fov_x
和fov_y
计算视锥体底面矩形的宽度和高度。 - 生成矩形四个顶点在相机坐标系下的坐标。
- 根据
应用旋转和平移变换:
- 对每个顶点应用
camera_rotation
,将其从相机坐标系转换到世界坐标系。 - 将每个顶点加上
camera_position
向量,完成平移到世界坐标系的转换。
- 对每个顶点应用
计算包围盒:
- 收集所有变换后的顶点和原点(视锥体顶点)在x和z方向上的最小值和最大值。
- 这些极值构成了包围盒的范围。
扩展包围盒:
- 根据
buffer_distance
,向外扩展包围盒的尺寸。这里假设buffer_distance
是一个比例,乘以当前包围盒的尺寸进行扩展。
- 根据
更新全局范围:
- 将扩展后的包围盒与传入的
min_x
,max_x
,min_z
,max_z
进行比较,并更新这些值,确保它们包含更大的范围。
- 将扩展后的包围盒与传入的
返回结果:
- 返回更新后的四个边界值。
代码实现:
import numpy as np
def visualHull(self, camera_rotation, camera_position, buffer_distance, fov_x, fov_y, min_x, max_x, min_z, max_z):
# 计算视锥体底面矩形的宽度和高度(假设距离为1单位长度)
half_width = np.tan(fov_x / 2)
half_height = np.tan(fov_y / 2)
# 视锥体顶点在相机坐标系下
vertices_camera = [
[half_width, -half_height, 1.0],
[half_width, half_height, 1.0],
[-half_width, half_height, 1.0],
[-half_width, -half_height, 1.0]
]
# 将顶点变换到世界坐标系
vertices_world = []
for vertex in vertices_camera:
# 应用旋转
rotated_vertex = np.dot(camera_rotation, vertex)
# 平移到相机位置
world_vertex = rotated_vertex + camera_position
vertices_world.append(world_vertex)
# 计算包围盒的极值(包括视锥体顶点原点)
x_coords = [v[0] for v in vertices_world]
z_coords = [v[2] for v in vertices_world]
current_min_x = min(x_coords)
current_max_x = max(x_coords)
current_min_z = min(z_coords)
current_max_z = max(z_coords)
# 包括视锥体顶点原点
origin = camera_position
x_coords.append(origin[0])
z_coords.append(origin[2])
current_min_x = min(current_min_x, origin[0])
current_max_x = max(current_max_x, origin[0])
current_min_z = min(current_min_z, origin[2])
current_max_z = max(current_max_z, origin[2])
# 扩展包围盒
width = current_max_x - current_min_x
height = current_max_z - current_min_z
expanded_width = width * (1 + buffer_distance)
expanded_height = height * (1 + buffer_distance)
expanded_min_x = current_min_x - (expanded_width - width) / 2
expanded_max_x = current_max_x + (expanded_width - width) / 2
expanded_min_z = current_min_z - (expanded_height - height) / 2
expanded_max_z = current_max_z + (expanded_height - height) / 2
# 更新全局范围
if expanded_min_x < min_x:
min_x = expanded_min_x
if expanded_max_x > max_x:
max_x = expanded_max_x
if expanded_min_z < min_z:
min_z = expanded_min_z
if expanded_max_z > max_z:
max_z = expanded_max_z
return (min_x, max_x, min_z, max_z)
说明:
视锥体生成:根据给定的水平和垂直视场角(
fov_x
和fov_y
),计算出视锥体底面矩形在相机坐标系下的四个顶点。假设视锥体的高度为1单位长度。坐标变换:每个顶点先通过旋转矩阵
camera_rotation
转换到世界坐标系,然后再应用平移变换,加上相机的位置向量camera_position
。包围盒计算:收集所有变换后的顶点和原点(视锥体的顶点)在x和z方向上的最小值和最大值,从而得到包围盒的范围。
扩展包围盒:根据给定的
buffer_distance
(这里作为比例),向外扩展包围盒的尺寸。计算新的极值,并确保扩展后的包围盒正确覆盖原视锥体区域。更新全局范围:将扩展后的包围盒与传入的全局边界进行比较,更新这些边界以包含更大的范围。
返回结果:最后,返回更新后的四个边界值
min_x
,max_x
,min_z
, 和max_z
。