空间智能是 AI 的下一个前沿——不只是看懂图片里的内容还要理解三维空间中的物体位置、空间关系、深度感知。cann-recipes-spatial-intelligence 覆盖三大应用3D 目标检测自动驾驶/机器人、NeRF/3DGS神经辐射场/高斯泼溅新型三维重建、SLAM同步定位与地图构建。三个方向的共性输入是图像或视频输出是空间中的几何信息。三类空间智能任务任务输入输出模型核心算子3D 目标检测多视角相机图像目标 3D 边界框BEVFormer / PointPillars视角变换、3D 卷积NeRF 神经辐射场多视角图像 相机位姿场景隐式表示NeRF / 3D Gaussian Splatting体密度查询、MipNeRF360SLAM连续帧图像 IMU相机轨迹 3D 地图ORB-SLAM3 / Direct SLAM光流、特征匹配、BA三类任务在 NPU 上的算子需求完全不同——3D 检测依赖大量矩阵乘和 BEV 视角变换NeRF 依赖体积渲染Ray MarchingSLAM 依赖特征匹配和几何优化。3D 目标检测BEVFormer 的视角变换BEVBird Eye View鸟瞰图感知是自动驾驶的核心——把前视相机的图像转成俯视图上的目标检测。// cann-recipes-spatial-intelligence/kernels/bev_transform.cpp// BEVFormer 的核心Spatial Cross-Attention// 把图像特征投影到 BEV 空间然后做 self-attention__aicore__voidSpatialCrossAttention(GlobalTensorfloatbev_query,// [B, H_bev, W_bev, C]GlobalTensorfloatimage_features,// [B, N_cam, C, H_img, W_img]GlobalTensorfloatcamera_K,// 内参矩阵 [B, N_cam, 3, 3]GlobalTensorfloatcamera_R,// 外参矩阵 [B, N_cam, 3, 3]GlobalTensorfloatcamera_T,// 外参平移向量 [B, N_cam, 3]GlobalTensorfloatgrid,// BEV 网格坐标 [H_bev, W_bev, 3]intB,intN_cam,intC,intH_bev,intW_bev,intH_img,intW_img){// BEV 视角变换从 BEV 坐标反投影到图像坐标// 逻辑// BEV 空间的一个点 (x, y, z0) → 3D 世界坐标 (x, y, 0)// 3D 世界坐标 → 相机坐标T_cam R^-1 × (P_world - T)// 相机坐标 → 像素坐标p K × T_camfor(intb0;bB;b){for(inth0;hH_bev;h){for(intw0;wW_bev;w){// 取 BEV 网格点floatbev_xgrid[h*W_bev*3w*30];floatbev_ygrid[h*W_bev*3w*31];floatbev_z0.0f;// 遍历所有相机LocalTensorfloatattended_features(N_cam*C);for(intcam0;camN_cam;cam){// 3D 点BEV → 世界 → 相机 → 像素// Cube 单元做矩阵乘LocalTensorfloatworld_pt(3);// [x, y, 0]world_pt[0]bev_x;world_pt[1]bev_y;world_pt[2]bev_z;// 逆外参变换相机坐标 R^T × (世界坐标 - T)// R^T 是旋转矩阵的逆因为 R 是正交的LocalTensorfloatcam_pt(3);MatMul(cam_pt,camera_R[b*N_camcam],world_pt);// R^T × P_worldcam_pt[0]-camera_T[b*N_camcam][0];cam_pt[1]-camera_T[b*N_camcam][1];cam_pt[2]-camera_T[b*N_camcam][2];// 检查深度相机朝向方向if(cam_pt[2]0.0f)continue;// 深度为负跳过// 内参变换像素坐标 K × 相机坐标LocalTensorfloatpixel_pt(3);MatMul(pixel_pt,camera_K[b*N_camcam],cam_pt);floatupixel_pt[0]/pixel_pt[2];floatvpixel_pt[1]/pixel_pt[2];// 检查是否在图像范围内if(u0||uW_img||v0||vH_img)continue;// 双线性插值采样图像特征floatfeat_valBilinearSample(image_features[b][cam],u,v);attended_features[cam*C:(cam1)*C]feat_val;}// 对所有相机的特征做加权平均bev_query[b*H_bev*W_bev*Ch*W_bev*Cw*C]Mean(attended_features,N_cam);}}}}BEV 视角变换的计算量随相机数量线性增长——6 个相机就是 6 次矩阵乘和 6 次双线性采样。CUBE 单元并行处理多个相机的投影Vector 单元处理双线性插值。NeRF 的体积渲染NeRFNeural Radiance Fields用隐式表示描述三维场景——给定一个 3D 坐标 (x, y, z) 和观察方向 (θ, φ)输出颜色 (R, G, B) 和体密度 σ。体积渲染Volumetric Rendering沿着射线采样点累积颜色和密度// cann-recipes-spatial-intelligence/kernels/nerf_rendering.cpp// 体积渲染沿射线采样 N_fine 个点计算颜色和密度__aicore__voidNerfVolumeRender(GlobalTensorfloatrgb_map,// [H, W, 3] 渲染图像GlobalTensorfloatdepth_map,// [H, W] 深度图GlobalTensorfloatray_origins,// [H, W, 3] 射线起点GlobalTensorfloatray_dirs,// [H, W, 3] 射线方向归一化GlobalTensorNeRFNetworknetwork,// NeRF 网络权重intH,intW,intN_samples// 采样点数){constfloatt_near0.1f;// 近平面constfloatt_far10.0f;// 远平面for(inty0;yH;y){for(intx0;xW;x){intray_idxy*Wx;float3 origLoadFloat3(ray_origins,ray_idx);float3 dirLoadFloat3(ray_dirs,ray_idx);// 沿射线均匀采样 N 个点floatt_vals[N_samples];for(inti0;iN_samples;i){floattt_near(t_far-t_near)*float(i)/float(N_samples-1);// 添加细粒度扰动分层采样降低方差t(hash(ray_idx*N_samplesi)-0.5f)*(t_far-t_near)/float(N_samples);t_vals[i]clamp(t,t_near,t_far);}// 累积颜色和密度float3 rgb{0,0,0};floattransmittance1.0f;floatdepth0.0f;for(inti0;iN_samples;i){// 射线上的 3D 采样点floatpxorig.xdir.x*t_vals[i];floatpyorig.ydir.y*t_vals[i];floatpzorig.zdir.z*t_vals[i];// NeRF MLP 查询输入 (x, y, z, θ, φ)输出 (R, G, B, σ)float3 rgb_i;floatsigma_i;network.Query(px,py,pz,dir,rgb_i,sigma_i);// Delta t_{i1} - t_ifloatdelta(iN_samples-1)?t_far-t_vals[i]:t_vals[i1]-t_vals[i];// 透射率衰减因子floatalpha1.0f-expf(-sigma_i*delta);floatweightalpha*transmittance;// 累积颜色rgb.xweight*rgb_i.x;rgb.yweight*rgb_i.y;rgb.zweight*rgb_i.z;// 累积深度加权平均depthweight*t_vals[i];// 更新透射率transmittance*(1.0f-alpha);if(transmittance1e-4f)break;// 光线基本被遮挡提前终止}StoreFloat3(rgb_map,ray_idx,rgb);depth_map[ray_idx]depth;}}}NeRF 的计算瓶颈在 MLP 查询每个采样点一次——64 个采样点 × 100 万条射线 6400 万次 MLP 调用。每条射线的采样点之间没有数据依赖适合 NPU 的多核并行。SLAM 的光流和特征匹配SLAM同步定位与地图构建实时估计相机运动和三维环境结构。关键步骤光流跟踪相邻帧的像素运动和特征匹配找两张图之间的对应关系。// cann-recipes-spatial-intelligence/kernels/slam_optical_flow.cpp// 光流计算Lucas-Kanade 算法的 NPU 实现// 假设邻域内所有像素有相同的运动向量__aicore__voidOpticalFlowLK(GlobalTensorfloatflow_u,// 光流水平分量 [H, W]GlobalTensorfloatflow_v,// 光流垂直分量 [H, W]GlobalTensorucharimg1,// 第 1 帧图像GlobalTensorucharimg2,// 第 2 帧图像intH,intW,intwindow_size5// Lucas-Kanade 窗口大小){inthalf_winwindow_size/2;for(intyhalf_win;yH-half_win;y){for(intxhalf_win;xW-half_win;x){// 构建局部邻域的 Ix, Iy, It 矩阵// Ix: 图像 x 方向梯度// Iy: 图像 y 方向梯度// It: 帧间时间梯度floatA[4]{0,0,0,0};// [2x2] 矩阵 A^T AfloatB[2]{0,0};// [2x1] 向量 A^T Bfor(intwy-half_win;wyhalf_win;wy){for(intwx-half_win;wxhalf_win;wx){intx1xwx,y1ywy;intx2x11,y2y1;// x1 方向intx3x1,y3y11;// y1 方向// Sobel 算子计算 Ix 和 IyfloatIx(float(img2[y1][x2])-float(img2[y1][x1]))/2.0f;floatIy(float(img2[y3][x1])-float(img2[y1][x1]))/2.0f;// 时间梯度 ItfloatItfloat(img2[y1][x1])-float(img1[y1][x1]);A[0]Ix*Ix;A[1]Ix*Iy;A[2]Ix*Iy;A[3]Iy*Iy;B[0]-Ix*It;B[1]-Iy*It;}}// 解 2×2 线性方程组 A × [u; v] B// [A[0] A[1]] [u] [B[0]]// [A[2] A[3]] [v] [B[1]]floatdetA[0]*A[3]-A[1]*A[2];if(fabs(det)1e-6f){flow_u[y*Wx](A[3]*B[0]-A[1]*B[1])/det;flow_v[y*Wx](-A[2]*B[0]A[0]*B[1])/det;}else{flow_u[y*Wx]0.0f;flow_v[y*Wx]0.0f;}}}}光流计算的核心是 2×2 矩阵的求逆——Vector 单元的逐元素操作在这里非常高效。窗口内每个像素参与一次累加窗口滑动过程可以复用中间结果。踩坑一NeRF 的射线采样在长序列上显存爆炸NeRF 渲染一张图需要沿每条射线采样 N 个点。1920×1080 的图 × 64 个采样点 1.33 亿个采样点每个采样点调用一次 MLP查询颜色和密度。错误一次性把所有采样点送进 MLP。// 一次性处理所有射线和所有采样点// 1.33 亿个采样点全部加载到 HBM// MLP 查询需要同时保存输入和中间激活值// HBM 占用 1.33亿 × (输入3D坐标3×float MLP中间层~10×float) × 4B ≈ 69 GB// 单卡 HBM 只有 64 GB还要加载网络权重 → 爆显存正确做法按 batch 分批渲染同 batch 的射线共享 MLP 的中间激活值。// 分批渲染每批处理 4096 条射线// 4096 × 64 262144 个采样点// HBM 占用 ≈ 262144 × 40 × 4B 42 MB// 完全适配单卡显存constintRAYS_PER_BATCH4096;intnum_raysH*W;intnum_batches(num_raysRAYS_PER_BATCH-1)/RAYS_PER_BATCH;for(intbatch0;batchnum_batches;batch){intstartbatch*RAYS_PER_BATCH;intendmin(startRAYS_PER_BATCH,num_rays);// 只加载当前 batch 的射线数据LoadRayBatch(ray_origins,ray_dirs,start,end);// 渲染当前 batchRenderBatch(output_rgb,output_depth,start,end);}踩坑二SLAM 的光流在低纹理区域失效Lucas-Kanade 光流假设窗口内所有像素有相同运动——这个假设在低纹理区域白墙、天空完全失效图像梯度为零Ix Iy 0矩阵 A 是奇异的方程无解。修复在低纹理区域跳过光流计算。// 先检测图像的梯度幅值// 梯度幅值小于阈值 → 低纹理区域 → 光流不可靠floatgrad_magsqrt(A[0]A[3]);// Ix² Iy²if(grad_mag1e-4f){// 低纹理区域设置光流为 0 或用附近像素的外插值flow_u[y*Wx]0.0f;flow_v[y*Wx]0.0f;continue;}// 只在纹理丰富的区域计算光流// 非极大值抑制保留响应最强的点踩坑三3D 检测的相机外参在线标定误差BEVFormer 的性能依赖相机外参R, T的标定精度。实际车载环境中相机安装位置随时间漂移热胀冷缩、震动旧的外参逐渐失效。症状BEV 检测的远处目标出现系统性偏移——不是随机误差而是固定方向的偏差。修复在线外参优化Online Extrinsic Calibration。在每帧图像中提取车道线或地面特征用这些特征和上一帧的外参联合优化 R 和 T。// 在线外参优化简化为单参数偏移// 假设外参只有平移偏移T_real T_initial ΔT// ΔT 通过最小化重投影误差得到__aicore__voidOnlineExtrinsicCalib(GlobalTensorfloatdelta_T,// 外参平移偏移 [3]GlobalTensorfloatlane_3d,// 检测到的 3D 车道线点GlobalTensorfloatlane_2d_proj,// 车道线在图像上的投影GlobalTensorfloatcamera_K,// 内参GlobalTensorfloatcamera_R,// 外参旋转已知固定intnum_lanes){// 重投影误差车道线 3D 点投影到图像和检测到的车道线像素的差异// e_i ||project(lane_3d[i], K, R, T_initial ΔT) - lane_2d_proj[i]||²// 高斯-牛顿迭代优化 ΔTfor(intiter0;iter10;iter){float2 total_error{0,0};for(inti0;inum_lanes;i){float3 p_3dlane_3d[i];float2 p_proj;// 用当前 ΔT 做投影float3 T_currT_initialdelta_T;ProjectToImage(p_3d,camera_K,camera_R,T_curr,p_proj);// 误差float2 ep_proj-lane_2d_proj[i];total_errore;}// 更新 ΔT梯度下降方向// 简化ΔT_new ΔT - lr × total_error / num_lanesfloatlr0.01f;delta_T-lr*total_error/float(num_lanes);// 收敛判断if(Length(total_error)0.1f)break;}}在线标定的收敛速度快3-5 次迭代但会消耗一部分计算资源。在资源紧张时可以每 N 帧做一次标定而不是每帧都做。空间智能三条线——3D 检测、NeRF、SLAM——都是自动驾驶和机器人感知的基础能力。三个场景在算子层面的需求各不相同BEVFormer 依赖高效矩阵乘和视角变换NeRF 依赖体渲染Ray MarchingSLAM 依赖光流和特征匹配。但它们的共同点是实时性要求高——自动驾驶的前视感知需要在 50ms 内完成SLAM 需要在 30fps 下跑通。cann-recipes-spatial-intelligence 把这三类场景的优化路径都整理成菜谱。