换相机或改分辨率之后,深度图看着仍然“能用”,但把深度投影成点云就会发现整体位置偏移、量出来的距离也不稳。多数情况下,问题不在深度模型本身,而在推理时用的相机内参和标定文件不是同一组:标定文件记录的焦距和主点还是旧相机、旧分辨率的值,而输入图像已经变了。要判断 LingBot-Depth 2.0 的深度值是否可比,顺序通常是先把内参与畸变参数对齐,再看深度质量;否则调模型、换权重都只是在错位的坐标系上做微调。
同一场景换相机或改输入分辨率后,深度图目视正常但投影位置偏移,优先怀疑内参没有同步更新。建议先打印标定文件与实际送入模型的内参,逐项核对 fx、fy、cx、cy 和图像宽高;分辨率变化时按比例换算内参,保持宽高比裁剪时同步修正 cx、cy;再用棋盘格或直线场景确认畸变已在进入模型前校正,最后用平面场景验证投影后的像素偏差。边界在于:这些步骤只能证明参数一致和投影自洽,不能替代对深度绝对精度的评估。
核对模型拿到的是哪一组内参
先要确认一件事:推理管线读的是标定文件里的参数,还是运行时配置、默认值或某处硬编码的参数。常见坑是标定文件已经更新,但模型初始化用的是另一份 config;或者标定文件按原始分辨率标定,输入却已经被 resize 过。需要逐项核对的字段至少包括:fx、fy、cx、cy,以及标定时对应的图像宽高 width、height。
做法很简单,把标定值打印出来,与实际传入模型的参数做一次逐项比对。下面是一个与具体 SDK 无关的骨架,替换成你项目里读取配置的函数即可:
import numpy as np
# 1. 从标定文件读取(示例:按你的标定格式替换)
calib = load_calibration("camera_calib.yaml")
K_file = calib.intrinsics # 3x3
W_file, H_file = calib.width, calib.height
fx_f, fy_f = K_file[0, 0], K_file[1, 1]
cx_f, cy_f = K_file[0, 2], K_file[1, 2]
# 2. 从推理配置读取
cfg = load_inference_config()
K_cfg = cfg.camera_matrix # 3x3
W_cfg, H_cfg = cfg.input_width, cfg.input_height
fx_c, fy_c = K_cfg[0, 0], K_cfg[1, 1]
cx_c, cy_c = K_cfg[0, 2], K_cfg[1, 2]
print(f"fx : file={fx_f:.4f} cfg={fx_c:.4f} diff={fx_f-fx_c:.4f}")
print(f"fy : file={fy_f:.4f} cfg={fy_c:.4f} diff={fy_f-fy_c:.4f}")
print(f"cx : file={cx_f:.4f} cfg={cx_c:.4f} diff={cx_f-cx_c:.4f}")
print(f"cy : file={cy_f:.4f} cfg={cy_c:.4f} diff={cy_f-cy_c:.4f}")
print(f"size: file={W_file}x{H_file} cfg={W_cfg}x{H_cfg}")
assert K_file.shape == (3, 3) and K_cfg.shape == (3, 3)
如果 fx、fy 的差异在千分位以上,或 cx、cy 差出几十个像素,基本可以判断两套参数不一致。宽高不一致时更要先解决分辨率来源问题,再谈别的。这里不需要追求精确到小数点后多少位,重点是排除“整体偏移”这类数量级的错误。
分辨率缩放后重算内参
把输入从原始分辨率缩放到模型要求的尺寸、却不改内参,深度投影就会按缩放比例错位:放大多少倍,坐标也偏多少倍。换算方式是按比例缩放 fx、fy、cx、cy。设原图为 W0×H0,目标为 W1×H1,则:
sx = W1 / W0
sy = H1 / H0
fx1 = fx0 * sx
fy1 = fy0 * sy
cx1 = cx0 * sx
cy1 = cy0 * sy
如果做了 letterbox 或保持宽高比的居中裁剪,缩放比例对两个方向是同一个值,但 cx、cy 还要加上裁剪或 padding 的偏移量。以先按比例缩放到 W1×H1'、再上下补齐到 H1 为例:
scale = min(W1 / W0, H1 / H0)
new_w, new_h = round(W0 * scale), round(H0 * scale)
pad_x = (W1 - new_w) / 2.0
pad_y = (H1 - new_h) / 2.0
fx1 = fx0 * scale
fy1 = fy0 * scale
cx1 = cx0 * scale + pad_x
cy1 = cy0 * scale + pad_y
如果只是裁剪而不缩放,则需要减去裁剪起点:cx1 = cx0 - crop_x,cy1 = cy0 - crop_y。缩放和裁剪都会改变深度到图像的投影关系,所以这两步要么都做、要么都不做,不能只改输入尺寸。
确认畸变在进入模型前已校正
另一个容易混淆的问题是:深度值看着对不上,到底是模型深度不准,还是输入图本来就是歪的。畸变残留时,直线在图像里是弯的,深度模型在弯曲区域算出来的几何关系也不可靠,投影之后误差会随位置变化,而不是整体平移。
可以用棋盘格或带明显直线的场景自查:
- 拍一张包含棋盘格或长直线的图,尽量让直线靠近画面边缘,因为畸变在边缘更明显。
- 用 undistort 校正后看直线是否变直;如果校正前后直线位置几乎没变,说明图里几乎没有畸变或已经校正过。
- 把校正后的图和原始图分别送入模型,比较同一条直线上两个点的深度投影是否落在同一直线方向。
对照方式也很直接:一次送原始图,一次送已经校正的图,其余参数保持不变,观察投影点的偏移方向是否一致。如果原始图偏移呈明显的径向弯曲、校正图偏移基本一致,说明畸变是主要来源。需要结合具体镜头和标定质量确认,广角镜头在边缘的畸变通常更明显。
# 通用校正骨架,参数替换为你的标定值
undistorted = cv2.undistort(
image, K, dist_coeffs, None, new_K
)
# 注意:如果用了 new_K,K 也要换成 new_K 再送入模型
关键点是:送入模型的内参,必须与送入模型的图像是同一套校正状态。校正后不改内参,等于又引入了一次错位。
用平面场景验证投影位置
参数对不对,最终要落到可观察的证据上。一个低成本的办法是拍一个明显是平面的场景,比如地面加一堵墙、桌面加背景,把深度投影回原图或投影到世界平面,看前景和背景是否落在同一平面。
投影骨架大致如下,使用与输入图像一致的内参:
# uv: 像素坐标 (N,2),d: 对应深度 (N,)
# K: 与当前输入图像一致的内参
fx, fy, cx, cy = K[0,0], K[1,1], K[0,2], K[1,2]
x = (uv[:,0] - cx) * d / fx
y = (uv[:,1] - cy) * d / fy
z = d
points_cam = np.stack([x, y, z], axis=1)
# 若已有外参 R, t,可再投到世界坐标
points_world = points_cam @ R.T + t
把投影点叠加回原图时,看地面、墙面这类平面上的采样点是否落在一条直线上:如果一条水平直线上各点的世界 z 差异很小,说明投影自洽;如果前后点分层、越靠边缘偏得越多,可能还是畸变或内参没对齐。记录偏差像素数时,不必追求精确统计,可以先取少量人工标注点,量一下投影点和实际位置的像素差,并把数值记下来留作后续对比。
把内参配置写进实验记录
后续每次对比都要基于同一组参数,否则一次看不见的配置变化就能把结果搅乱。实验记录里至少保留这些字段:
- 输入分辨率(宽 × 高)
- fx、fy、cx、cy
- 畸变系数(k1、k2、p1、p2、k3 等,按实际使用的数量列出)
- 是否做了校正、校正时使用的 new_K
- 图像缩放或裁剪方式及对应比例
- 模型版本与输入尺寸要求
替换相机之后,建议按顺序重跑这几步:先重新标定并确认标定文件是最新的;再核对推理配置是否指向这组新参数;然后重新计算分辨率缩放后的内参;最后用平面场景重新验证一次投影位置,记录偏差像素数,和旧记录对比。这样即使后面想追查某次结果异常,也能回到“那次用的是哪组内参”这个确定的问题上。