在mac与ubuntu上部署DeepSeek-R1模型 & 7b和32b模型的简单对比测试


在mac与ubuntu上部署DeepSeek-R1模型 & 7b和32b模型的对比测试

笔者的硬件配置

mac端:macbook air m1,16G内存

ubuntu端:i7-13700kf + RTX 4090,32G内存

安装Ollama

访问下面的网站,下载对应操作系统的安装包,根据提示安装。

https://ollama.com/download

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模型的回答

为了解决报错的问题,请按照以下步骤操作:

  1. 检查当前Python环境:确保你使用的是支持图形输出的环境,或者在没有 GUI 的环境中正确配置了matplotlib。

  2. 修改代码以设置合适的backend

    在你的代码中添加以下两行,放在任何导入matplotlib.pyplot之前:

    import matplotlib
    matplotlib.use('Agg')  # 设置为非交互式backend
  3. 显式指定绘图大小(可选)

    如果仍然有问题,可以在创建figure时明确设置大小,确保宽高都是正数:

    fig = plt.figure(figsize=(10, 6))
  4. 处理异常情况

    在绘图之前,添加错误检查,防止负值或零尺寸被传递给绘图函数。

  5. 重新运行代码

    应用上述修改后,再次执行代码,观察是否还出现相同错误。

如果问题依然存在,请进一步核实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)。以下是具体的实现步骤和代码示例:

函数实现思路

  1. 相机坐标系到世界坐标系转换
    首先,需要将相机位置和旋转参数转换为齐次变换矩阵。通过这个矩阵可以将相机坐标系中的点转换到世界坐标系。
  2. 生成视锥体边界点
    根据相机的FOV(水平和垂直)、缓冲距离和其他参数,计算出在相机坐标系中视锥体的边界点。
  3. 变换到世界坐标系
    使用齐次变换矩阵将这些边界点转换到世界坐标系中。
  4. 计算bbox并扩展
    计算这些点的最小和最大值(x, y, z),然后根据缓冲距离扩展bbox。
  5. 更新边界参数
    根据传入的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)

使用说明

  1. 输入参数

    • camera_rotation: 相机旋转矩阵(3x3)。
    • camera_position: 相机位置向量(3D)。
    • buffer_distance: 扩展的距离比例。
    • fov_xfov_y: 水平和垂直视场角,以度为单位。
    • min_x, max_x, min_z, max_z: 初始边界参数。
  2. 输出

    • 返回更新后的边界参数 (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)

注意事项

  1. 单位转换:视场角需要从度转换为弧度。
  2. 相机坐标系方向:如果相机的z轴指向物体,需调整视锥体的方向(如将 min_zmax_z 替换为 -min_z-max_z)。
  3. 扩展距离:buffer_distance 可以根据需求调整,控制bbox的扩展程度。

希望这个实现对您有帮助!

32b模型的回答

步骤说明:

  1. 定义参数
    确定所有输入参数的意义和单位。

  2. 视锥体顶点生成

    • 根据fov_xfov_y计算视锥体底面矩形的宽度和高度。
    • 生成矩形四个顶点在相机坐标系下的坐标。
  3. 应用旋转和平移变换

    • 对每个顶点应用camera_rotation,将其从相机坐标系转换到世界坐标系。
    • 将每个顶点加上camera_position向量,完成平移到世界坐标系的转换。
  4. 计算包围盒

    • 收集所有变换后的顶点和原点(视锥体顶点)在x和z方向上的最小值和最大值。
    • 这些极值构成了包围盒的范围。
  5. 扩展包围盒

    • 根据buffer_distance,向外扩展包围盒的尺寸。这里假设buffer_distance是一个比例,乘以当前包围盒的尺寸进行扩展。
  6. 更新全局范围

    • 将扩展后的包围盒与传入的min_x, max_x, min_z, max_z进行比较,并更新这些值,确保它们包含更大的范围。
  7. 返回结果

    • 返回更新后的四个边界值。

代码实现:

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_xfov_y),计算出视锥体底面矩形在相机坐标系下的四个顶点。假设视锥体的高度为1单位长度。

  • 坐标变换:每个顶点先通过旋转矩阵camera_rotation转换到世界坐标系,然后再应用平移变换,加上相机的位置向量camera_position

  • 包围盒计算:收集所有变换后的顶点和原点(视锥体的顶点)在x和z方向上的最小值和最大值,从而得到包围盒的范围。

  • 扩展包围盒:根据给定的buffer_distance(这里作为比例),向外扩展包围盒的尺寸。计算新的极值,并确保扩展后的包围盒正确覆盖原视锥体区域。

  • 更新全局范围:将扩展后的包围盒与传入的全局边界进行比较,更新这些边界以包含更大的范围。

  • 返回结果:最后,返回更新后的四个边界值min_x, max_x, min_z, 和 max_z


文章作者: Immortalqx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Immortalqx !
评论
  目录