news 2026/8/29 11:01:35

深入解析Carla中的坐标系转换:从世界坐标到像素坐标的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解析Carla中的坐标系转换:从世界坐标到像素坐标的实战指南

1. 为什么坐标转换是自动驾驶仿真的“基本功”

如果你正在用Carla做自动驾驶仿真,想把一个路牌、一个行人,或者你自己车辆的未来轨迹,准确地画在相机拍到的图像上,那你一定会遇到一个绕不开的难题:坐标转换。这听起来像是个纯理论的数学问题,但在我实际做项目的过程中,发现它恰恰是很多仿真效果“失真”或者“对不上”的罪魁祸首。你可能会遇到这样的情况:明明代码逻辑看起来没问题,世界坐标也拿到了,但转换到图像上,那个点要么飞到了画面外,要么位置完全错乱,根本没法用。

这个问题背后的核心,就是世界坐标系、相机坐标系和像素坐标系这三者之间的“语言”不通。想象一下,世界坐标系是上帝视角的全局地图,用米(meter)来描述一个物体在仿真世界中的绝对位置。相机坐标系则是以相机镜头为中心的小天地,它的Z轴指向相机正前方(或正后方,这是个关键坑点!)。而像素坐标系是我们最终看到的二维图像,单位是像素(pixel)。你的目标,就是把一个三维世界中的点(比如X=10米, Y=20米, Z=1米),经过一系列“翻译”,变成它在二维照片上的位置(比如u=640像素, v=360像素)。

这个过程在自动驾驶的感知算法测试、传感器数据融合、可视化调试中至关重要。比如,你想验证激光雷达检测到的障碍物,是否和相机“看到”的物体是同一个,就必须把激光雷达的点云从世界坐标投影到图像像素坐标上,看看它们是否重合。又或者,你想在图像上可视化车辆的预测轨迹,也需要这个转换。很多朋友一开始会直接去网上找代码片段,但如果不理解其中的原理和Carla的一些特殊设定,很容易掉进坑里,调试起来一头雾水。接下来,我就结合自己踩过的坑和实战经验,带你一步步打通这个关键环节。

2. 第一步:理解Carla中的世界坐标与相机外参

2.1 如何获取世界坐标系下的点

在Carla里,获取任何物体(车辆、行人、交通标志)的世界坐标都非常直接。每个Actor都有一个get_transform()方法,返回的Transform对象包含了locationrotationlocation就是一个carla.Location对象,其x,y,z属性就是在Carla世界坐标系下的坐标值(单位:米)。

import carla # 假设你已经连接到了客户端并有了世界对象world vehicle_list = world.get_actors().filter('vehicle.*') my_vehicle = vehicle_list[0] if vehicle_list else None if my_vehicle: # 获取车辆的全局变换(位置和旋转) vehicle_transform = my_vehicle.get_transform() # 提取世界坐标 world_x = vehicle_transform.location.x world_y = vehicle_transform.location.y world_z = vehicle_transform.location.z # 提取朝向(欧拉角,单位:度) pitch = vehicle_transform.rotation.pitch yaw = vehicle_transform.rotation.yaw roll = vehicle_transform.rotation.roll print(f"车辆世界坐标: ({world_x:.2f}, {world_y:.2f}, {world_z:.2f})") print(f"车辆朝向: pitch={pitch:.1f}, yaw={yaw:.1f}, roll={roll:.1f}")

这就拿到了我们转换的起点:一个三维点P_w = (world_x, world_y, world_z)。但请注意,Carla的世界坐标系遵循右手定则:X轴向前(北),Y轴向右(东),Z轴向上。这个要记牢,后面和相机坐标系对比时会用到。

2.2 关键的陷阱:get_transform().get_matrix()不是直接可用的外参矩阵

这是第一个,也是最大的一个坑。很多教程或博客会告诉你,相机的外参矩阵(即从世界坐标到相机坐标的变换矩阵)可以直接用camera.get_transform().get_matrix()获得,或者对其求逆来获得。实测下来,这是不准确的,会直接导致转换结果错误。

为什么?我们需要仔细看Carla的文档和源码逻辑。get_matrix()方法返回的是一个4x4的变换矩阵,它描述的是:以该物体(比如相机)自身的变换为参考系A,到世界坐标系B的变换。但这里有一个至关重要的隐含假设:参考系A的坐标轴方向与世界坐标系B的坐标轴方向是完全对齐的。

然而,相机的坐标系定义是特殊的。在计算机视觉和图形学中,标准的相机坐标系通常定义为:原点在相机光心,Z轴指向相机正前方(观察方向),X轴向右,Y轴向下(右手坐标系)或Y轴向上(左手坐标系,取决于库)。Carla内部使用的相机模型,其光轴方向是沿着相机的正前方

get_transform()得到的旋转,是描述这个相机Actor在世界中的朝向(偏航Yaw、俯仰Pitch、滚动Roll)。get_matrix()在构建矩阵时,并没有将相机自身的“前-右-上”轴对应到标准的“X-Y-Z”轴上。简单来说,camera.get_transform().get_matrix()给出的是“一个位置和朝向与相机相同的虚拟物体,但其自身坐标系轴方向与世界轴一致”的变换矩阵。

所以,我们不能直接使用这个矩阵或其逆矩阵作为世界到相机坐标的变换。我们需要一个额外的轴对齐旋转矩阵来修正这个差异。

2.3 构建正确的世界到相机坐标变换矩阵

正确的变换步骤应该是这样的:

  1. 获取相机的变换并计算其“原始”变换矩阵cam_matrix = camera.get_transform().get_matrix()。这个矩阵是将点从相机局部坐标系(轴与世界对齐版)变换到世界坐标系
  2. 计算世界到“轴对齐版相机坐标系”的变换:我们需要从世界坐标变换到那个“轴对齐版相机坐标系”,所以要对cam_matrix求逆。matrix_world_to_cam_axes_aligned = np.linalg.inv(cam_matrix)
  3. 施加轴对齐修正:现在,我们需要把点从“轴对齐版相机坐标系”转换到真正的相机坐标系(Z轴朝前)。这需要一个固定的旋转矩阵R_axes_correction

那么,这个修正矩阵R_axes_correction到底是什么?这取决于Carla内部是如何定义相机坐标系轴的。经过大量测试和社区验证,常见的两种可能是:

  • 情况A:相机坐标系为:X轴向右,Y轴向下,Z轴向前(符合OpenCV等库常用的相机模型,但Y轴向下是左手系?这里需要仔细辨别)。对应的修正矩阵可能是一个将世界轴(X前,Y右,Z上)映射到(X右,Y下,Z前)的旋转。
  • 情况B:相机坐标系为:X轴向右,Y轴向上,Z轴向后(即光轴指向相机后方,这是某些图形API如OpenGL的惯例)。

原始文章里提到了两个矩阵,正是对应这两种情况。根据我和其他开发者的经验,在Carla的默认相机设置下,更常使用的是第二种情况(Z轴向后)。对应的修正矩阵是一个3x3的旋转矩阵:

R_correction = [[0, -1, 0], [0, 0, 1], [-1, 0, 0]]

这个矩阵的作用是什么?我们来手动乘一下看看效果:假设一个点在“轴对齐版相机坐标系”下的坐标是(x_a, y_a, z_a),其中x_a指向前(世界X),y_a指向右(世界Y),z_a指向上(世界Z)。乘以这个修正矩阵后:

  • 新的X坐标 = 0*x_a + (-1)y_a + 0z_a =-y_a。这意味着新的X轴对应了原来轴对齐坐标系的负Y轴(即向右)。
  • 新的Y坐标 = 0x_a + 0y_a + 1*z_a =z_a。这意味着新的Y轴对应了原来轴对齐坐标系的Z轴(即向上)。
  • 新的Z坐标 = (-1)x_a + 0y_a + 0*z_a =-x_a。这意味着新的Z轴对应了原来轴对齐坐标系的负X轴(即向后)。

于是,新的坐标系就变成了:X右,Y上,Z后。这正是我们需要确认的相机坐标系定义。

因此,完整的世界坐标点P_w到相机坐标点P_c的变换公式为:P_c = R_correction @ (matrix_world_to_cam_axes_aligned @ P_w)这里@表示矩阵乘法,P_wP_c需要是齐次坐标(3D点增加一个维度1,变成4维向量)或只取旋转平移部分。在实际计算中,我们通常将R_correction合并到4x4变换矩阵的左上角3x3部分。

下面是一个整合的Python代码示例:

import numpy as np import carla def get_world_to_camera_matrix(camera_transform): """ 根据相机变换,计算从世界坐标系到相机坐标系的4x4变换矩阵。 假设相机坐标系为:X右,Y上,Z后。 """ # 获取相机变换矩阵(从相机局部坐标系-轴对齐版 到 世界坐标系) cam_to_world = camera_transform.get_matrix() # 计算世界坐标系 到 相机局部坐标系-轴对齐版 的变换 world_to_cam_axes_aligned = np.linalg.inv(cam_to_world) # 轴对齐修正旋转矩阵 (3x3) # 这个矩阵将点从“轴对齐版相机坐标系”转换到“真实相机坐标系(X右,Y上,Z后)” R_correction = np.array([ [0, -1, 0], [0, 0, 1], [-1, 0, 0] ]) # 构建完整的4x4变换矩阵 # 先初始化一个单位矩阵 world_to_camera = np.identity(4) # 将旋转部分替换为修正后的旋转 world_to_camera[:3, :3] = R_correction @ world_to_cam_axes_aligned[:3, :3] # 平移部分需要先进行轴对齐变换,然后旋转修正只影响方向,平移向量直接取自轴对齐变换的平移部分,但需要被旋转修正? # 更正确的做法:将修正矩阵扩展为4x4,然后与轴对齐变换矩阵相乘。 R_correction_4x4 = np.identity(4) R_correction_4x4[:3, :3] = R_correction world_to_camera = R_correction_4x4 @ world_to_cam_axes_aligned return world_to_camera # 使用示例 camera = world.get_actors().filter('sensor.camera.rgb')[0] camera_transform = camera.get_transform() world_to_cam_mat = get_world_to_camera_matrix(camera_transform) # 假设有一个世界坐标点 P_w (齐次坐标,形状为 (4,), 最后一位为1) P_w = np.array([10.0, 5.0, 1.0, 1.0]) # (x, y, z, 1) # 转换到相机坐标系 P_c_homo = world_to_cam_mat @ P_w # 齐次坐标结果 P_c = P_c_homo[:3] / P_c_homo[3] # 转换为3D笛卡尔坐标 (x_c, y_c, z_c) print(f"相机坐标系下的点: {P_c}")

注意:这个修正矩阵R_correction不是一成不变的。如果你在蓝图属性里修改了相机的安装角度(例如,将相机倒置或侧装),或者你使用了非RGB相机(如深度相机、语义分割相机),可能需要调整这个矩阵。最稳妥的方式是通过一个已知的标定物进行验证。

3. 第二步:从相机坐标到像素坐标——内参估计

当我们成功将一个点从广阔的世界坐标系转换到以相机为中心的相机坐标系P_c = (x_c, y_c, z_c)后,下一步就是把它投影到二维的图像平面上,也就是像素坐标系(u, v)。这一步需要相机的内参矩阵(Intrinsic Matrix)

3.1 Carla相机内参的构成与获取

遗憾的是,Carla没有直接提供一个get_intrinsics()这样的函数。但是,我们可以根据相机蓝图(Blueprint)的属性来估算它。内参矩阵K通常是一个3x3的矩阵,形式如下:

K = [[f_x, 0, c_x], [0, f_y, c_y], [0, 0, 1]]

其中:

  • f_x,f_y:相机在x和y方向上的焦距,单位是像素。它们与物理焦距F(单位:毫米)和传感器像素尺寸有关:f_x = F / dx,f_y = F / dy。在Carla中,我们通常认为f_x = f_y
  • c_x,c_y:主点(Principal Point)坐标,通常是图像的中心,即(图像宽度/2, 图像高度/2)

在Carla相机蓝图的“高级属性”(Advanced Attributes)中,有两个关键参数:

  1. fov(Field of View): 视野角,单位是度。这是对角视野角还是水平视野角?根据文档和测试,Carla的fov通常指的是水平视野角
  2. image_size_ximage_size_y: 生成图像的宽度和高度,单位是像素。

有了水平视野角fov_h(以弧度为单位)和图像宽度W,我们可以估算焦距f_xf_x = (W / 2) / tan(fov_h / 2)由于相机传感器通常是方像素,我们可以假设f_y = f_x

主点坐标简单取图像中心:c_x = W / 2c_y = H / 2

3.2 编写内参估计函数与投影代码

让我们把这些计算封装成函数:

def estimate_camera_intrinsics(camera_bp, image_width, image_height): """ 根据相机蓝图属性和图像尺寸估算内参矩阵K。 假设 fov 是水平视野角。 """ # 获取水平视野角(需要转换为弧度) fov_deg = camera_bp.get_attribute('fov').as_float() fov_rad = np.deg2rad(fov_deg) # 计算焦距 (fx = fy) fx = (image_width / 2.0) / np.tan(fov_rad / 2.0) fy = fx # 假设方形像素 # 计算主点(图像中心) cx = image_width / 2.0 cy = image_height / 2.0 # 构建内参矩阵 K K = np.array([ [fx, 0, cx], [0, fy, cy], [0, 0, 1] ]) return K # 使用示例:在创建相机时估算内参 camera_bp = world.get_blueprint_library().find('sensor.camera.rgb') image_width = 800 image_height = 600 camera_bp.set_attribute('image_size_x', str(image_width)) camera_bp.set_attribute('image_size_y', str(image_height)) # ... 设置其他属性,如fov K = estimate_camera_intrinsics(camera_bp, image_width, image_height) print(f"估算的内参矩阵 K:\n{K}")

现在,结合我们之前得到的世界到相机坐标的变换矩阵world_to_cam_mat和内参矩阵K,就可以实现从世界坐标到像素坐标的完整投影:

def project_world_to_pixel(world_point, world_to_cam_mat, K): """ 将世界坐标系下的一个3D点投影到像素坐标系。 参数: world_point: numpy数组,形状 (3,),世界坐标 (x, y, z)。 world_to_cam_mat: 4x4矩阵,世界到相机坐标的变换矩阵。 K: 3x3矩阵,相机内参。 返回: pixel_coords: 像素坐标 (u, v),如果点在相机后方则返回None。 depth: 点在相机坐标系下的深度(Z值)。 """ # 将世界点转换为齐次坐标 (4,) point_homo = np.array([world_point[0], world_point[1], world_point[2], 1.0]) # 转换到相机坐标系 point_cam_homo = world_to_cam_mat @ point_homo point_cam = point_cam_homo[:3] # (x_c, y_c, z_c) # 检查点是否在相机前方(根据我们的坐标系定义,Z轴向后,所以z_c为负表示在相机前方?) # 实际上,在X右,Y上,Z后的坐标系中,相机“看到”的是Z坐标为负的区域。 # 但为了通用性,我们通常检查投影前的z_c(相机坐标系下的Z值)是否大于0(对于Z向前的坐标系)或小于0(对于Z向后的坐标系)。 # 这里我们假设经过修正后,相机坐标系是Z向后。点要想被看到,其相机坐标系下的Z坐标应为负值。 z_c = point_cam[2] if z_c >= 0: # 如果点在相机平面后方或平面上,无法投影 return None, z_c # 投影到归一化相机平面 (x_n = x_c / z_c, y_n = y_c / z_c) # 注意:因为z_c是负的,所以x_n, y_n的符号与x_c, y_c相反,这符合成像原理。 x_n = point_cam[0] / z_c y_n = point_cam[1] / z_c # 应用内参矩阵,得到像素坐标 pixel_homo = K @ np.array([x_n, y_n, 1.0]) u = pixel_homo[0] v = pixel_homo[1] return (u, v), z_c # 实战使用示例 # 假设我们已经有了 world_to_cam_mat 和 K world_point = np.array([15.0, 3.5, 0.5]) # 世界坐标系下某点,例如地面上的一个点 pixel_coords, depth = project_world_to_pixel(world_point, world_to_cam_mat, K) if pixel_coords is not None: print(f"世界点 {world_point} 投影到像素坐标: ({pixel_coords[0]:.1f}, {pixel_coords[1]:.1f}),深度: {depth:.2f}") # 你可以用OpenCV的cv2.circle等函数在图像上画出这个点 else: print(f"世界点 {world_point} 在相机后方,不可见。")

提示z_c的正负判断逻辑需要与你定义的相机坐标系一致。上述代码假设Z_c向后,可见点为Z_c < 0。如果你使用不同的修正矩阵导致坐标系定义变化,这个判断条件也需要调整。一个更稳健的做法是,投影后检查像素坐标(u, v)是否在图像边界(0,0)(W, H)之内,但这并不能排除点在相机后方的特殊情况。

4. 第三步:实战整合与效果验证

理论说再多,不如跑一遍代码看看效果。我们来设计一个完整的、可运行的验证实验。这个实验的目标是:在Carla仿真中,让一辆车行驶,并在其搭载的相机图像上,实时绘制出车辆未来几帧的预测位置(用世界坐标表示),从而直观检验我们的坐标转换是否正确。

4.1 实验设置与代码框架

首先,我们需要搭建一个基本的Carla仿真环境,生成一辆带相机的车,并让车沿着一条路径(比如跟随一条预定义的航点)行驶。在每一帧,我们做以下几件事:

  1. 获取车辆当前的世界坐标P_current
  2. 根据车辆当前的速度和朝向,简单预测未来N个时间点(比如对应未来20帧)的位置P_future_list。这里为了简化,我们假设车辆匀速直线运动。
  3. 获取相机当前的图像image和相机变换camera_transform
  4. 对于每一个未来位置点P_future,使用我们前面编写的get_world_to_camera_matrixproject_world_to_pixel函数,计算其对应的像素坐标。
  5. 如果像素坐标在图像范围内,就在图像上用醒目的颜色(比如红色圆点)标记出来。
  6. 显示或保存处理后的图像。

下面是核心循环部分的代码片段:

import carla import numpy as np import cv2 from matplotlib import pyplot as plt # ... 省略Carla客户端连接、世界加载、车辆和相机生成代码 ... # 假设 vehicle, camera 已经创建,camera.listen() 设置了回调函数 # 在相机传感器的回调函数中处理 def camera_callback(image, data_dict): """相机数据回调函数,data_dict中存储了车辆和预测点信息""" # 将原始数据转换为数组 img_array = np.frombuffer(image.raw_data, dtype=np.uint8) img_array = img_array.reshape((image.height, image.width, 4)) # RGBA格式 img_bgr = img_array[:, :, :3] # 取RGB,忽略A img_bgr = cv2.cvtColor(img_bgr, cv2.COLOR_RGB2BGR) # Carla是RGB,OpenCV常用BGR # 获取当前帧的相机变换和车辆变换 camera_transform = data_dict['camera'].get_transform() vehicle_transform = data_dict['vehicle'].get_transform() # 计算世界到相机的变换矩阵 world_to_cam_mat = get_world_to_camera_matrix(camera_transform) # 估算内参 (假设相机参数在创建时已设定,这里直接使用已知的K) # 或者从蓝图重新估算 K = data_dict['camera_intrinsics'] # 获取车辆当前世界坐标和速度(用于简单预测) current_location = vehicle_transform.location current_velocity = data_dict['vehicle'].get_velocity() # 获取速度向量 # 计算未来20帧(假设每帧0.05秒,共1秒)的位置 future_points_world = [] num_future_frames = 20 delta_time = 0.05 for i in range(1, num_future_frames+1): # 简单线性预测:位置 = 当前位置 + 速度 * 时间 future_x = current_location.x + current_velocity.x * i * delta_time future_y = current_location.y + current_velocity.y * i * delta_time future_z = current_location.z # 假设高度不变 future_points_world.append(np.array([future_x, future_y, future_z])) # 将未来点投影到当前图像 for i, world_point in enumerate(future_points_world): pixel_coords, depth = project_world_to_pixel(world_point, world_to_cam_mat, K) if pixel_coords is not None: u, v = int(pixel_coords[0]), int(pixel_coords[1]) # 检查点是否在图像边界内 if 0 <= u < image.width and 0 <= v < image.height: # 根据深度或帧序设置颜色和大小,越远/越未来的点可能颜色越浅或越小 color = (0, 0, 255) # 红色 (BGR) radius = max(3, 5 - i//5) # 越未来的点画得越小 cv2.circle(img_bgr, (u, v), radius, color, -1) # 画实心圆 # 显示图像 cv2.imshow('Camera View with Projected Future Path', img_bgr) cv2.waitKey(1) # 在主循环或设置中,将必要的对象传递给回调函数 data_dict = {'vehicle': vehicle, 'camera': camera, 'camera_intrinsics': K} camera.listen(lambda image: camera_callback(image, data_dict))

4.2 如何判断转换是否正确?——常见问题排查

当你运行代码后,可能会看到几种情况:

  1. 点完全不在图像上或集中在某个角落:这很可能是世界到相机坐标的变换矩阵错了。99%的问题出在R_correction修正矩阵上。请再次确认你使用的修正矩阵是否与你的Carla版本和相机设置匹配。尝试使用原始文章提到的另一个矩阵[[0,1,0],[0,0,1],[1,0,0]]看看效果。最根本的调试方法是进行“静态标定”:

    • 在仿真世界中,将一个物体(比如一个车辆或一个行人)放在相机正前方不远处。
    • 记录该物体的世界坐标。
    • 手动计算或目测它应该在图像中出现的大致位置(图像中心)。
    • 运行你的投影代码,看计算出的像素坐标是否在图像中心附近。如果偏差很大,就需要调整修正矩阵。
  2. 点大致位置对,但有系统性偏移或拉伸:这可能是内参矩阵估计不准。检查你的fov是水平视野角还是垂直视野角。Carla文档有时表述模糊,你可以通过实验来反推:将一个已知距离的物体放在相机前,测量它在图像中的像素宽度,利用小孔成像模型反算焦距f_x。此外,确认image_size_ximage_size_y设置是否正确。

  3. 点沿着错误的方向运动:例如,车向前开,投影点却向下或向左移动。这可能是坐标系左右手性轴方向定义混乱导致的。确保你在整个变换链中(世界->相机->像素)使用的坐标系都是同一种手性(通常是右手坐标系),并且清楚每个步骤中每个轴(X, Y, Z)的具体指向。

  4. 深度值z_c判断逻辑导致点被错误剔除:这是最隐蔽的错误之一。如果你的点明明在相机前方,却被if z_c >= 0:语句过滤掉了,说明你对相机坐标系Z轴方向的假设错了。打印出几个已知在相机前方的点的z_c值,看看是正还是负,然后调整判断条件。

4.3 一个更稳健的变换工具函数

为了方便使用和调试,我习惯将整个流程封装成一个类或一组函数。下面是一个更健壮、带调试信息的版本:

class CarlaCoordinateTransformer: def __init__(self, camera_actor, image_width, image_height): self.camera = camera_actor self.image_width = image_width self.image_height = image_height self.K = self._estimate_intrinsics() # 定义修正矩阵(根据你的验证结果选择) self.R_correction = np.array([ [0, -1, 0], [0, 0, 1], [-1, 0, 0] ]) def _estimate_intrinsics(self): """估算内参,同上文""" # ... 实现代码 ... pass def get_current_projection_matrix(self): """获取当前帧下的完整投影矩阵 P = K * [R|t]""" camera_transform = self.camera.get_transform() cam_to_world = camera_transform.get_matrix() world_to_cam_axes = np.linalg.inv(cam_to_world) # 构建4x4修正矩阵 R_corr_4x4 = np.identity(4) R_corr_4x4[:3, :3] = self.R_correction # 世界->相机坐标系变换矩阵 [R|t] world_to_cam = R_corr_4x4 @ world_to_cam_axes # 提取旋转和平移 R = world_to_cam[:3, :3] t = world_to_cam[:3, 3] # 投影矩阵 P = K * [R|t] P = self.K @ np.hstack((R, t.reshape(-1, 1))) return P, R, t def project_points(self, world_points): """ 批量投影世界点到像素坐标。 world_points: numpy数组,形状 (N, 3) 或 (N, 4)(齐次)。 返回: pixel_coords (N, 2), depths (N,), valid_mask (N,) """ P, R, t = self.get_current_projection_matrix() # 确保是世界坐标齐次形式 (N, 4) if world_points.shape[1] == 3: world_points_homo = np.hstack([world_points, np.ones((world_points.shape[0], 1))]) else: world_points_homo = world_points # 转换到相机坐标系并计算深度(这里深度定义为相机坐标系下的Z值) points_cam_homo = (world_points_homo @ world_to_cam.T) # 注意矩阵乘法维度 depths = points_cam_homo[:, 2] # 投影到像素平面 pixels_homo = points_cam_homo @ P.T pixels = pixels_homo[:, :2] / pixels_homo[:, 2:3] # 齐次坐标归一化 # 有效性判断:深度为负(对于Z向后的坐标系)且在图像范围内 valid_mask = (depths < 0) & \ (pixels[:, 0] >= 0) & (pixels[:, 0] < self.image_width) & \ (pixels[:, 1] >= 0) & (pixels[:, 1] < self.image_height) return pixels, depths, valid_mask

使用这个类,你的主循环会变得非常简洁:

transformer = CarlaCoordinateTransformer(camera, image_width, image_height) future_points_world = ... # 形状 (N, 3) pixels, depths, valid = transformer.project_points(future_points_world) for i in range(len(valid)): if valid[i]: u, v = int(pixels[i, 0]), int(pixels[i, 1]) cv2.circle(image, (u, v), 3, (0,255,0), -1)

当你成功地在图像上看到一排沿着车辆运动方向延伸的点时,就证明你的坐标转换链路打通了。这个过程虽然繁琐,但一旦掌握,你就拥有了在Carla仿真中将任意三维信息与二维图像关联起来的核心能力,这对于开发和调试自动驾驶感知算法至关重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 17:11:51

FireRedASR快速上手:音频上传、识别、结果复制,完整操作流程演示

FireRedASR快速上手&#xff1a;音频上传、识别、结果复制&#xff0c;完整操作流程演示 你是不是经常需要把会议录音、采访内容或者课程音频转换成文字&#xff1f;手动听写不仅耗时耗力&#xff0c;还容易出错。今天&#xff0c;我要给你介绍一个能彻底解决这个痛点的工具—…

作者头像 李华
网站建设 2026/7/14 17:11:38

3步实现视频转文字:开源工具Bili2text提升转录效率指南

3步实现视频转文字&#xff1a;开源工具Bili2text提升转录效率指南 【免费下载链接】bili2text Bilibili视频转文字&#xff0c;一步到位&#xff0c;输入链接即可使用 项目地址: https://gitcode.com/gh_mirrors/bi/bili2text 在信息爆炸的时代&#xff0c;视频内容已成…

作者头像 李华
网站建设 2026/7/14 17:11:50

荔枝派Nano全志F1C100s开发板实战:从uboot到buildroot的完整烧录指南

荔枝派Nano全志F1C100s开发板实战&#xff1a;从uboot到buildroot的完整烧录指南 最近有不少朋友开始玩起了那些小巧又便宜的ARM开发板&#xff0c;其中全志F1C100s这颗芯片因为其极致的性价比和相对完整的生态&#xff0c;吸引了不少嵌入式爱好者和初学者的目光。荔枝派Nano作…

作者头像 李华
网站建设 2026/7/14 17:11:48

MFC标签控件CTabCtrl实战:从基础创建到动态标签管理

1. 初识CTabCtrl&#xff1a;你的第一个标签页应用 如果你用过Windows系统的“系统属性”或者任何一个带标签页的软件设置界面&#xff0c;那你对标签控件&#xff08;Tab Control&#xff09;一定不陌生。它就像我们办公桌上的文件夹标签&#xff0c;点击不同的标签&#xff0…

作者头像 李华
网站建设 2026/7/14 17:11:49

KUKA机器人CCU板信号指示灯故障诊断与维护指南

1. 认识你的机器人“心脏”&#xff1a;CCU板与它的“眼睛” 大家好&#xff0c;我是老张&#xff0c;在工业自动化这行摸爬滚打了十几年&#xff0c;跟KUKA机器人打交道的时间比跟我家孩子都长。今天咱们不聊那些复杂的编程和轨迹规划&#xff0c;就说说机器人控制柜里一个特别…

作者头像 李华