尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
KITTI数据集3D目标检测可视化:坐标系变换与点云投影全解析
KITTI数据集在3D目标检测里的地位基本等同于视觉领域的Hello World。入门第一篇聊完怎么下载和解析数据格式之后这次我打算把可视化这件事彻底讲透。不是教你怎么点一个可视化按钮而是把KITTI数据集每一帧背后的四类文件、三个坐标系、两个矩阵变换一次讲穿再给你一套可以直接跑的Python示例代码。只有当激光雷达点云、标注框和原始图像真正叠在同一个画面里时你才会理解3D目标检测在解决什么问题在三维空间里定位目标、估计朝向、判断运动趋势。这篇就是围绕这个目标来的。1. 可视化之前先认清KITTI一帧数据里的四张底牌很多初学者拿到KITTI数据集后第一反应是直接找可视化工具结果工具装了一堆图还是画不出来。原因很简单你还没搞清楚这一帧数据由哪些文件组成、每个文件里存的到底是什么。先把底牌摸清楚后面所有代码都会变得顺理成章。1.1 一帧数据里的四类文件KITTI数据集里同一个编号比如000000对应着同一时刻采集的四类文件缺一不可文件类型路径示例格式在可视化里扮演的角色左相机彩色图像image_2/000000.pngPNG图片最终投影结果要叠在这张图上激光雷达点云velodyne/000000.binfloat32数组N行4列3D场景的主体提供空间位置目标标签label_2/000000.txt文本一行一个目标提供3D框参数尺寸、位置、朝向标定参数calib/000000.txt文本若干矩阵坐标系之间互相变换的桥梁先说点云。velodyne目录下的.bin文件不是普通文本而是按float32排列的二进制数据每四个float组成一个点分别是x、y、z和反射强度。解压读取之后你会得到一个N行4列的数组。KITTI每帧点云大概有12万个点左右但真正落在有效范围内的只占一部分后面会讲到怎么裁剪。再说标签。label_2目录下的.txt文件每一行描述一个目标按空格分隔成15个字段。这是可视化里信息密度最高的文件我建议你把它的每一个字段都背下来。标定文件很多人一开始不重视直到画出来的框和图像对不上才开始翻文档。calib目录下的.txt文件里存了多个矩阵我们这一篇只用其中三个但理解它们之间的关系很关键我会在第2节专门展开。稍微提醒一句不要试图直接双击打开.bin文件也别用记事本去看它本来就是给程序读的。我自己当年就是栽在用文本编辑器打开bin文件这一步上出来的全是乱码。1.2 标签文件里最关键的两个块2D框和3D框KITTI标签的15个字段从可视化角度拆开看其实可以分成五个信息块字段示例值含义可视化时是否需要typeCar目标类别用来决定要不要画、用什么颜色画truncated0.00目标被图像边缘截断的比例0到1可用来过滤严重截断的对象occluded0遮挡等级0-3越大挡得越厉害可用来过滤严重遮挡的对象alpha-0.20观察角即目标朝向与相机视线方向的夹角3D框不需要2D显示偶尔用bbox712.40 143.00 810.73 307.922D像素框左、上、右、下用来和投影结果做对比验证dimensions1.48 1.63 3.893D尺寸高、宽、长米画3D框的核心参数location2.15 1.70 15.35底部中心在相机坐标系下的x、y、z画3D框的位置依据rotation_y1.61目标绕相机y轴的朝向角画3D框的方向依据这里有两个非常容易混淆的点。第一个是dimensions的顺序。KITTI里规定的是height、width、length也就是高、宽、长。很多人在读代码时习惯性地以为是长、宽、高结果画出来的框长宽互换一辆车看起来像被横过来放。这个错误非常隐蔽因为框的位置是对的、高度是对的只有长和宽对不上不仔细看根本发现不了。第二个是location的含义。KITTI官方给的说明是3D object location in camera coordinates很多人就当成物体中心坐标用了。但实际在官方devkit的角点计算里它是当作底部中心坐标来用的——8个角点中底面4个点的y坐标是0顶面4个点的y坐标是-h。如果你把location当成中心整个框会整体上移半个车身高度看起来像浮在半空。这点后面会专门展开讲。还有一个容易忽略的点alpha和rotation_y是两个不同的角。alpha是观测角跟相机视线方向有关rotation_y才是物体在三维空间里的全局朝向。画3D框时只跟rotation_y有关跟alpha无关。2D检测里有时候会用到alpha3D可视化里基本可以无视它。1.3 同一辆车在三个坐标系里长在不同位置KITTI可视化的本质就是把来自不同坐标系的信息统一到同一个参考系里再绘制。一辆真实世界里的车在KITTI数据里其实有三种长相在图像里它是一块2D像素区域由u、v两个坐标表示在激光雷达点云里它是一团分布在三维空间里的反射点由x、y、z表示且原点在激光雷达传感器上在标签文件里它是一个三维长方体由尺寸、底部中心坐标和朝向角表示且原点在相机光心上。同一辆车在三种坐标系统里被记录成完全不同的数字。可视化的任务就是把这三个数字集合放进同一个画布里。你可以选择把点云搬到相机坐标系也可以选择把3D框搬到激光雷达坐标系但必须二选一不能混着画。打个比方你手里有三份关于同一个人的描述——一份是照片一份是他的身高体重一份是他在房间里的位置。你要做的是把这三份信息拼成一个完整的人物模型。如果位置用的是房间坐标系身高体重用的却是另一个标尺那拼出来的人肯定是扭曲的。坐标系不统一之前一切可视化都是空谈。2. 坐标系变换可视化的地基错一步全盘皆输如果只让我选一节作为这篇博文的重点我一定选这一节。KITTI可视化的所有疑难杂症80%都能追溯到坐标系变换没做对。2.1 三个坐标系的基本定义先建立画面感。KITTI里经常打交道的有三个坐标系坐标系原点轴方向常见用途Velodyne激光雷达坐标系激光雷达传感器中心x朝前、y朝左、z朝上点云的原始坐标系相机坐标系相机光心x朝右、y朝下、z朝前标签location所在的坐标系图像像素坐标系图像左上角u朝右、v朝下最终画框的坐标系注意相机坐标系里y轴是朝下的这一点跟我们在数学课上学的右手坐标系完全相反。很多人在KITTI里被绕晕根源就是忘记了相机坐标的y是向下的。再补充一个容易忽视的点激光雷达坐标系的x朝前跟相机坐标系的z朝前是两个不同的前。从激光雷达到相机x要变成zy要变成-x方向这个转变不是简单换一下变量名而是通过一个旋转加平移矩阵完成的。2.2 标定文件里真正要关心的三个矩阵KITTI的calib文件里会有P0、P1、P2、P3、R0_rect、Tr_velo_to_cam这几个矩阵。如果你只想做单目图像的3D框可视化盯住下面三个就够了P2是一个3x4矩阵负责把相机坐标系下的点投影到图像平面。它融合了相机的内参焦距、光心偏移和相机2相对于参考相机的外参旋转加平移。这个矩阵是KITTI官方在数据采集时就标定好的我们不需要自己算直接拿过来用。Tr_velo_to_cam是一个3x4矩阵负责把激光雷达坐标系下的点变换到相机坐标系。同理这也是设备安装时标定出来的包含了激光雷达和相机之间的旋转关系和平移关系。R0_rect是一个3x3矩阵负责把参考相机坐标系下的点做一次旋转矫正消除双目相机校正过程中产生的坐标系偏差。它非常接近单位矩阵但绝不能忽略否则投影结果会有几个像素的偏差。我的建议是在代码里把这三个矩阵从calib文件里解析出来单独存成变量名不要随手把它们连成一个4x4的大矩阵。分步写虽然啰嗦但每一步的类型和维度都很清晰调错时也容易定位。2.3 一条公式打通所有坐标Lidar到Camera再到Pixel把激光雷达点投影到图像平面上整个过程就一条链式公式P_img P2 · R0_rect · Tr_velo_to_cam · [x_velo, y_velo, z_velo, 1]^T其中[x_velo, y_velo, z_velo]是激光雷达点坐标最后这一步必须补1是为了做齐次坐标变换。Tr_velo_to_cam是3x4矩阵正好需要4维输入R0_rect是3x3的旋转矫正P2是3x4的投影矩阵。投影结果是一个3维齐次坐标最后要除以z分量才能得到真正的像素坐标u P_img[0] / P_img[2] v P_img[1] / P_img[2]为什么除z因为P2里同时包含了焦距和光心参数乘完以后得到的是深度未归一化的坐标只有除以z才能模拟相机小孔成像的透视效果。这个除法是透视投影的关键一步漏掉它点云投影到图像上就是乱七八糟的一片。这里有一个非常常见的坑点云bin文件的第4列是反射强度不是齐次坐标里的1。如果直接把整行丢进去乘位置就错了。必须单独构造一列1再把反射强度放到一边去。用代码写就是import numpy as np def load_velodyne(filepath): pts np.fromfile(filepath, dtypenp.float32).reshape(-1, 4) return pts # N x 4 (x, y, z, reflectance) def load_calib(filepath): lines [line.rstrip().split() for line in open(filepath)] calib {} for line in lines: key line[0].rstrip(:) values np.array(line[1:], dtypenp.float32) calib[key] values calib[P2] calib[P2].reshape(3, 4) calib[R0_rect] calib[R0_rect].reshape(3, 3) calib[Tr_velo_to_cam] calib[Tr_velo_to_cam].reshape(3, 4) return calib def project_lidar_to_image(pts, calib): N len(pts) pts_homo np.hstack([pts[:, :3], np.ones((N, 1))]) # N x 4 pts_cam calib[Tr_velo_to_cam] pts_homo.T # 3 x N pts_rect calib[R0_rect] pts_cam # 3 x N pts_rect_homo np.vstack([pts_rect, np.ones((1, N))]) # 4 x N pts_img calib[P2] pts_rect_homo # 3 x N pts_img pts_img / pts_img[2, :] # 归一化 return pts_img[:2, :].T # N x 2 (u, v)2.4 反方向变换3D框从相机坐标回到点云坐标标签文件里的3D框是在相机坐标系下的而点云是在激光雷达坐标系下的。要在3D空间里同时画点云和3D框必须让两者处于同一个坐标系。我推荐的做法是把点云从激光雷达坐标系转到相机坐标系因为3D框的角点本身就在相机坐标系里点云转过去之后两者直接对齐不需要对框做任何额外变换。有时候你想把3D框画在原始点云坐标系里那就需要求Tr_velo_to_cam的逆矩阵。注意Tr_velo_to_cam是3x4矩阵求逆前要扩展成4x4齐次矩阵def get_cam_to_velo_matrix(calib): T np.eye(4) T[:3, :4] calib[Tr_velo_to_cam] T_inv np.linalg.inv(T) return T_inv # 4x4无论是哪种方案核心原则只有一个画在同一个画面里的所有东西必须共享同一个坐标系。这一条说得再重要也不为过。3. 三张可视化图从散点云图到带框目标视图理论铺垫完毕现在进入真正动手的阶段。这一节我会带你画出三种图原始点云图、带3D框的点云场景图、以及3D框投影到RGB图像上的效果图。第三种是KITTI可视化里最经典的画面也是很多人入门时最想得到的结果。3.1 第一张图Open3D载入原始点云如果你还没装Open3D先装一下。它是目前最顺手的3D点云可视化库加载KITTI点云只要几行代码。import numpy as np import open3d as o3d pts np.fromfile(velodyne/000000.bin, dtypenp.float32).reshape(-1, 4) # 先做ROI裁剪只保留有效区域 mask (pts[:, 0] 0) (pts[:, 0] 70) \ (np.abs(pts[:, 1]) 40) \ (pts[:, 2] -2) (pts[:, 2] 1) pts pts[mask] pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(pts[:, :3]) # 按高度着色能看到地面和车辆的空间层次 z pts[:, 2] z_norm (z - z.min()) / max(z.max() - z.min(), 1e-6) colors np.stack([z_norm, 1 - z_norm, np.full_like(z_norm, 0.4)], axis1) pcd.colors o3d.utility.Vector3dVector(colors) o3d.visualization.draw_geometries([pcd])为什么做ROI裁剪KITTI激光雷达的有效感知范围大约在车前70米左右更远的点非常稀疏全画出来只会拉低可视化的可读性。我这里用的范围是前方0到70米、左右各40米、高度从-2米到1米你可以根据自己关心的区域调整。有一点要注意KITTI的激光雷达安装在车顶约1.73米高度处它的z轴朝上所以路面的z坐标大约是负1.73米。如果你把z范围下限设置成0地面会被全部切掉看不到路面参照物。3.2 第二张图在点云场景里画出3D检测框画完原始点云后下一步就是在场景里叠加3D标注框。先把标签文件解析出来为每个目标计算8个角点。def load_label(filepath): objects [] for line in open(filepath): parts line.strip().split( ) obj { type: parts[0], truncated: float(parts[1]), occluded: int(parts[2]), alpha: float(parts[3]), bbox: np.array(parts[4:8], dtypenp.float32), dimensions: np.array(parts[8:11], dtypenp.float32), # h, w, l location: np.array(parts[11:14], dtypenp.float32), # 底部中心 rotation_y: float(parts[14]) } objects.append(obj) return objects def compute_box_3d(obj): h, w, l obj[dimensions] # 高、宽、长顺序务必正确 x, y, z obj[location] ry obj[rotation_y] # 底面4个角点的y为0顶面4个角点的y为-h x_corners [l/2, l/2, -l/2, -l/2, l/2, l/2, -l/2, -l/2] y_corners [0, 0, 0, 0, -h, -h, -h, -h] z_corners [w/2, -w/2, -w/2, w/2, w/2, -w/2, -w/2, w/2] R np.array([ [np.cos(ry), 0, np.sin(ry)], [0, 1, 0], [-np.sin(ry), 0, np.cos(ry)] ]) corners_3d R np.array([x_corners, y_corners, z_corners]) corners_3d np.array([x, y, z])[:, None] return corners_3d.T # 8 x 3相机坐标系然后把点云也转到相机坐标系保证两者在同一个空间里pts_homo np.hstack([pts[:, :3], np.ones((len(pts), 1))]) pts_cam (calib[Tr_velo_to_cam] pts_homo.T).T # N x 3 pcd.points o3d.utility.Vector3dVector(pts_cam[:, :3]) # 画3D框 lines [[0,1],[1,2],[2,3],[3,0], [4,5],[5,6],[6,7],[7,4], [0,4],[1,5],[2,6],[3,7]] colors [[1,0,0] for _ in range(len(lines))] for obj in objects: if obj[type] not in [Car, Pedestrian, Cyclist]: continue corners compute_box_3d(obj) line_set o3d.geometry.LineSet() line_set.points o3d.utility.Vector3dVector(corners) line_set.lines o3d.utility.Vector2iVector(lines) line_set.colors o3d.utility.Vector3dVector(colors) o3d.visualization.draw_geometries([pcd, line_set])这段代码里有一个奇淫巧技如果发现Open3D里的线框太细、看不清可以不用LineSet改成用create_cylinder把每一条边画成圆柱体粗线框在截图和演示时效果好得多。缺点是代码会多几行但换来的是画面清晰度提升值得。如果你发现自己画的框都歪歪扭扭、飘在空中不用急着怀疑代码极大概率是location的理解出了问题第4节会专门讲。3.3 第三张图3D框投影到2D图像这是最出效果的一张图把3D框的8个角点投影到图像上用彩色线条把角点连起来覆盖在原始RGB图片上。import cv2 def project_box_to_image(obj, calib): corners compute_box_3d(obj) # 8 x 3相机坐标系 corners_rect calib[R0_rect] corners.T # 3 x 8 corners_rect_homo np.vstack([corners_rect, np.ones((1, 8))]) corners_img calib[P2] corners_rect_homo # 3 x 8 corners_img corners_img / corners_img[2, :] # 除以深度 return corners_img[:2, :].T # 8 x 2 img cv2.imread(image_2/000000.png) edges [(0,1),(1,2),(2,3),(3,0), (4,5),(5,6),(6,7),(7,4), (0,4),(1,5),(2,6),(3,7)] for obj in objects: if obj[type] not in [Car, Pedestrian, Cyclist]: continue pts2d project_box_to_image(obj, calib) for i, j in edges: pt1 tuple(pts2d[i].astype(int)) pt2 tuple(pts2d[j].astype(int)) cv2.line(img, pt1, pt2, (0, 255, 0), 2) cv2.imshow(KITTI 3D box, img) cv2.waitKey(0)当你第一次跑出这张图看到汽车轮廓被一个绿色的三维线框稳稳框住时之前所有关于坐标系的困惑都会瞬间落地。这里还要解释一个现象3D框投影到图像后它跟标签文件里的2D bbox并不会完全重合。2D bbox是人工标注的最大外接矩形而3D框是物体的三维包围盒投影两者的边缘往往会差一点点。如果相差超过几个像素那大概率是标定矩阵或者坐标系出了问题。3.4 附加BEV鸟瞰图为什么选择x-y平面BEV鸟瞰图在3D目标检测里至关重要因为很多检测模型就是基于BEV特征做学习的。用matplotlib就能画一个简易版。import matplotlib.pyplot as plt # 在激光雷达坐标系下做BEVx为前向y为左侧z为高度 plt.figure(figsize(8, 8)) plt.scatter(pts[:, 0], pts[:, 1], s0.2, cpts[:, 2], cmapjet) plt.axis(equal) # 3D框从相机坐标转到velodyne坐标 T_cam_to_velo get_cam_to_velo_matrix(calib) for obj in objects: corners compute_box_3d(obj) corners_homo np.hstack([corners, np.ones((8, 1))]) corners_velo (T_cam_to_velo corners_homo.T).T # 8 x 3 plt.plot(corners_velo[[0,1,2,3,0], 0], corners_velo[[0,1,2,3,0], 1], r-) plt.plot(corners_velo[[4,5,6,7,4], 0], corners_velo[[4,5,6,7,4], 1], b-) plt.gca().invert_yaxis() plt.show()BEV图到底取x-y平面还是x-z平面经常有人迷糊。我的建议是看你当前数据在哪个坐标系激光雷达坐标系的俯视平面是x-y因为z是高度相机坐标系的俯视平面是x-z因为y是高度。没有哪个更正统只有当前坐标系下哪两个轴才是地面平面这一条判断标准。4. 验证与排查框画出来不对十有八九是这几个问题可视化代码跑通只是第一步画出来的东西对不对还需要一整套验证方法。这一节我把自己踩过、帮别人排查过的典型问题集中整理出来希望能帮你少走弯路。4.1 用投影结果做自检3D框和2D bbox的关系拿到投影图后第一件事就是做自检把标签文件里的2D bbox也画上去跟3D投影框对比。for obj in objects: left, top, right, bottom obj[bbox].astype(int) cv2.rectangle(img, (left, top), (right, bottom), (255, 0, 0), 2)正常情况下3D框的投影和2D矩形应该基本贴合尤其是物体底边部分。2D框是目标在图像中的实际像素范围3D框是三维长方体投影所以3D投影框的上边沿有时候会比2D框略低或略高这是因为二维标注框通常包含了一部分背景。如果两个框完全重合反而要怀疑是不是某个环节恰好互补了错误。如果两个框差得离谱第一步检查是不是用错了P矩阵。calib文件里有P0到P3四个投影矩阵P2对应左相机彩色图。很多人一不小心用了P0对应灰度相机cam0结果投影位置整体偏移。这种错误最常见于复制粘贴代码时。4.2 框飘在半空或陷入地底location是底部中心这个坑我前面提过两次现在专门展开讲因为它真的太容易踩了。KITTI官方devkit计算3D框角点时y方向的8个角点用的是[0, 0, 0, 0, -h, -h, -h, -h]也就是说location的y是底部中心的y坐标不是立体中心的y坐标。底面四个角点的y等于0顶面四个角点的y等于-h。如果你错误地把location当成中心角点就会变成[h/2, h/2, h/2, h/2, -h/2, -h/2, -h/2, -h/2]框整体上移h/2看起来就像汽车悬空了一样。如果出现框全部飘在路面以上半个车身的位置不用怀疑就是这个原因。反过来如果你把location当成顶面中心框就会陷入地面。这种情况不太常见但在一部分自动转写代码的工程里遇到过。怎么验证在点云视图里打开地面网格。如果你看到框的下底面刚好贴合点云中的路面点说明location用对了如果框的下底面悬在路面之上或者陷进路面之下说明y方向的基准搞错了。4.3 车头转了方向rotation_y与维度顺序这类问题通常表现为框的位置对、高度对但长宽反了或者朝向反了。维度顺序问题如果把h、w、l记成了h、l、w那画出来的框形状就是一个横躺着的不规则长方体。比如一辆车真实尺寸是长3.9米、宽1.6米你如果长宽互换在俯视图里会看到框的形状明显不像车。检测方法很简单找一帧停着的车对比框的长宽比和图像里车的长宽比是否一致。rotation_y方向问题这个稍微复杂。KITTI的camera坐标系里y轴向下所以绕y轴旋转的正方向跟我们在黑板上画坐标系时的直觉相反这就导致很多人画的框朝向跟实际车头朝向差了180度或90度。解决这个问题的土办法是我自己一直用到现在的一种方式拿一个你知道朝向的样本帧把rotation_y分别改成原值和负值看一看哪个方向跟图像里车的朝向吻合。一旦确定了正负后续所有帧都用同一个符号规则。不要试图用死记正角向左、负角向右这类口诀因为不同数据集版本、不同代码库的处理方式可能不同靠样本对齐建立直觉才是最稳的。另外注意KITTI里rotation_y的取值范围是-pi到pi如果你的代码里对角度做了取模操作要小心正负边界问题。某些库会把-pi直接处理成pi导致朝向反向。4.4 图像宽高和P2不一致导致投影偏移KITTI图像看起来都是1242x375左右但细心的同学可能会发现不同帧的图片宽高其实略有差异——有的是1242像素宽有的是1238或1241。这个差异来自双目相机矫正时的裁剪。更关键的是每一帧的calib文件里P2矩阵的最后几个参数也不是完全相同的。如果你图省事只从某一帧拷贝一份P2用在整个数据集上那么大部分帧投影偏差不大但在图像边缘区域的目标可能出现几个像素到十几个像素的偏移。我的建议是每处理一帧图像就读一次它自己的calib文件不要缓存成全局变量。这点开销完全可以忽略但它能避免很多莫名其妙的对不齐问题。5. 我现在完整的可视化工作流和一些值得长期使用的小习惯当你把上面所有环节跑通之后剩下的就是工程化的问题了。这篇最后一节我把自己的标准工作流和几个小习惯分享出来不一定适合所有人但值得你参考。5.1 一个脚本吃穿所有帧我日常做KITTI可视化不会为每帧写一遍左手代码。我会把代码组织成三个模块数据解析模块负责读bin、读calib、读label可视化模块负责三类图点云图、投影图、BEV图主脚本负责循环帧目录把结果输出到指定文件夹。# 伪主流程示意批处理逻辑 for frame_id in range(0, 200): base f000000[:-len(str(frame_id))] str(frame_id) calib load_calib(fcalib/{base}.txt) objects load_label(flabel_2/{base}.txt) pts load_velodyne(fvelodyne/{base}.bin) # 生成投影图 render_image_projection(base, calib, objects) # 生成BEV图 render_bev(base, calib, objects, pts)这套代码跑完后我通常会随机抽看10%的帧确认没有出现大面积框漂移。一个比较实用的习惯是在输出图上把frame_id和目标数量打印在左上角这样回查时一眼就能定位是哪一帧出的问题。5.2 聊聊批处理、固定视角和导出视频如果你想把可视化结果做成视频两个细节值得注意一是Open3D的交互窗口不适合在批处理里长期运行。更好的做法是每帧渲染之后直接保存图像或者用headless模式渲染。我自己更常用的是先批量生成投影图再用cv2.VideoWriter把PNG序列拼成视频这样既可以用Open3D细看单帧又能用视频看整体效果。import cv2 out cv2.VideoWriter(kitti_vis.avi, cv2.VideoWriter_fourcc(*MJPG), 10, (1242, 375)) for i in range(200): img cv2.imread(fvis_overlay/{i:06d}.png) out.write(img) out.release()二是对比不同帧时务必固定视角。Open3D的draw_geometries默认视角是自动计算的连续看多帧会被镜头乱飘搞晕。在代码里指定视角参数可以把每帧的观察角度锁死这样运动目标的位置关系才有可比性。5.3 给刚入门朋友的三条建议第一条先在一帧上把流程跑通再谈批处理。我见过太多人一上来就整目录循环结果每一帧都报错最后连问题出在哪儿都找不到。单帧调试的时间成本远低于批处理排错。第二条把compute_box_3d里的每个参数手动改一改建立几何直觉。改h看框变高改ry看框转向改location的y看框上下移动。这种破坏性实验会让你对这些参数的理解深刻程度远超读十遍文档。第三条不要盲信网上文章里给出的calib常量。每个KITTI子集下载包的calib内容不完全一致你从某篇博客复制来的P2可能对应的是另一个采集日期或另一个相机。永远从你要处理的那一帧自己的calib文件里读取矩阵数据。KITTI可视化这条路说难也难说简单也简单。难在坐标系绕来绕去简单在只要你把握住统一坐标系这个核心原则再复杂的问题都能拆解成一步步验证。我自己从第一行可视化代码到现在摔过的大坑几乎都集中在location基准、维度顺序和矩阵用法这三件事上。希望这篇能帮你直接把这三个坑填平省下几个通宵的时间。
RELATED

相关推荐

从Sora到DiT:视频生成模型架构迁移与实战指南

从Sora到DiT:视频生成模型架构迁移与实战指南

1. 从Sora说起:为什么视频生成突然集体转向DiT如果你这两年一直在关注视频生成这个赛道,应该会有一种很强烈的感受:2024年之前,大家还在各玩各的,有基于U-Net的扩散模型,有自回归的Transformer方案&#xf…

📅 2026/10/15 14:27:08
C#直连西门子S7-1200/1500 PLC实战:S7NetPlus通信三道关与避坑指南

C#直连西门子S7-1200/1500 PLC实战:S7NetPlus通信三道关与避坑指南

简介:本资源是一套基于C#与S7.NET库实现西门子S7系列PLC通信的完整可运行工程,面向工业自动化领域初/中级开发者、工控软件工程师及高校自动化专业学习者,解决C#环境下PLC数据读写、连接管理与基础调试等核心实践问题。压缩包共41个文件&…

📅 2026/10/15 14:27:08
数字序列99999999999999999988的技术解析与应用

数字序列99999999999999999988的技术解析与应用

1. 数字序列的潜在含义解析这个由纯数字"99999999999999999988"组成的序列看似简单,实则可能包含多重解读角度。作为从业多年的数据分析师,我见过各种数字组合在实际业务场景中的应用,这种特殊序列往往隐藏着特定领域的专业含义。从…

📅 2026/10/15 14:22:07
MORE NEWS

更多资讯

📰

批量Excel转CSV工具:pandas+glob+click实战指南

简介:这是一款面向Windows平台开发者的轻量级Excel批量转CSV工具,专为需高效处理大量xls/xlsx报表的办公自动化、数据清洗及ETL预处理场景设计。资源包含完整VS2005工程(含sln/dsw/vcproj等项目文件)、C源码(7个头文件…

📰

C++状态模式在工业控制系统中的应用与实践

1. 状态模式基础认知我第一次接触状态模式是在开发一个工业控制系统的报警模块时。系统需要根据设备的不同运行状态(待机、运行、故障、维护)触发差异化的报警策略,如果直接用if-else堆砌条件判断,代码很快就会变成难以维护的&quo…

📰

火星全球地形渲染实践:基于四叉树与LOD的精细调度

做火星全球地形渲染这件事,一开始我以为最麻烦的是“火星长什么样”,后来才发现真正的硬骨头在“怎么把几十亿个采样点塞进显卡里还能流畅转起来”。四叉树LOD这套组合,就是目前解决全球大尺度地形渲染最务实的一条路。这篇笔记我把整个实现过…

📰

新骆驼IPTV完美版后端源码部署与二次开发实战指南

简介:这份资源是新骆驼IPTV完美版的后端源码与安卓APP源码压缩包,面向具备一定安卓或服务端基础的开发者,用于研究电视直播系统的完整实现,并支持二次开发与功能自定义。包内以后端服务与前端应用代码为主,涵盖服务器管…

📰

Halcon相机标定全流程:标定板选型、内参优化与RMSE排查

简介:面向计算机视觉与机器视觉开发人员,这是Halcon官方参考手册第6章Calibration(标定)的中文直译版,内容无删减,可作为算法工程师与检测设备调试人员理解相机标定原理、参数含义与操作流程的案头资料。手…

📰

制造业桌面云怎么选?4 款主流方案横向对比

数字化转型持续推进,制造车间、研发工位的终端管理难题逐步凸显。传统本地PC硬件老化快,数据分散存储,图纸与生产资料安全管控难度高,运维人员需要往返车间处理各类电脑故障。制造业桌面云依托虚拟化技术,把桌面系统集…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬