返回专辑
·Johan·3 分钟阅读

OpenCV remap 的固定点地图:convertMaps 该放在哪一层

预计算去畸变映射后,用 convertMaps 生成 CV_16SC2/CV_16UC1 地图,并核对插值、边界与输出误差。

OpenCV remap 的固定点地图:convertMaps 该放在哪一层

相机内参与分辨率都没变,处理循环里却每帧重新算去畸变坐标。我把 initUndistortRectifyMap 移到初始化阶段后,下一步常被忽略:remap 既能吃浮点地图,也能吃 convertMaps 生成的固定点地图。后者把整数坐标和插值表索引拆开,适合反复使用同一几何映射。

这里的 map 不是“源像素要去哪里”,而是对每个目标像素给出应从源图哪个坐标取样。方向弄反时,输出仍是一张图,但边缘会以很奇怪的方式弯曲,不能只看函数有没有报错。

初始化一次,逐帧只 remap

下面使用 OpenCV 4 的 C++ API。map_xmap_y 是两张 CV_32FC1 浮点地图;转换后 fixed_xyCV_16SC2fixed_fracCV_16UC1

cpp
class Rectifier {
 public:
  Rectifier(
      const cv::Mat& K,
      const cv::Mat& distortion,
      const cv::Mat& R,
      const cv::Mat& new_K,
      cv::Size image_size) {
    cv::Mat map_x;
    cv::Mat map_y;
    cv::initUndistortRectifyMap(
        K, distortion, R, new_K, image_size,
        CV_32FC1, map_x, map_y);

    cv::convertMaps(
        map_x, map_y,
        fixed_xy_, fixed_frac_,
        CV_16SC2,
        false);  // 后续使用非最近邻插值
  }

  cv::Mat operator()(const cv::Mat& frame) const {
    CV_Assert(frame.size() == fixed_xy_.size());
    cv::Mat rectified;
    cv::remap(
        frame, rectified,
        fixed_xy_, fixed_frac_,
        cv::INTER_LINEAR,
        cv::BORDER_CONSTANT);
    return rectified;
  }

 private:
  cv::Mat fixed_xy_;
  cv::Mat fixed_frac_;
};

常见的浮点输入转换是

第一张输出保存取样坐标的整数部分,第二张保存内部插值表的索引。两张单通道 float map 每像素共 字节,固定点组合每像素共 字节;真正的耗时收益仍取决于 OpenCV 构建选项、CPU 和整条流水线,不能从格式直接推导帧率。

nninterpolation=true 只适合后续最近邻取样,此时第二张输出地图不需要生成。若后面却用 INTER_LINEAR,就丢了线性插值需要的小数信息。remap 也不支持 INTER_AREAINTER_LINEAR_EXACT;要换插值方法,先核对该 API 的支持范围。

浮点与固定点要成对对照

我会暂时保留浮点地图,在同一输入上跑两条路径。边界模式和插值参数必须完全相同:

cpp
cv::Mat from_float;
cv::Mat from_fixed;

cv::remap(
    frame, from_float, map_x, map_y,
    cv::INTER_LINEAR, cv::BORDER_CONSTANT);
cv::remap(
    frame, from_fixed, fixed_xy, fixed_frac,
    cv::INTER_LINEAR, cv::BORDER_CONSTANT);

const double max_abs =
    cv::norm(from_float, from_fixed, cv::NORM_INF);
std::cout << "max abs pixel diff: " << max_abs << '\n';

固定点表示会量化坐标,反向转换也不能恢复出完全相同的浮点 map,因此验收应比较输出图,而不是要求 map 按位相等。对项目里实际使用的像素类型和插值方式,先在标定图、图像边缘和高频棋盘格上记录 max_abs 与差分图,再决定容差;不要凭一张自然图“肉眼差不多”就通过。

性能验证同样要把 map 创建排除在逐帧计时之外,并先预热。分别对浮点和固定点路径处理同一批帧,报告中位数和高分位延迟;如果解码或推理占了绝大多数时间,convertMaps 对端到端延迟可能并不明显。

什么时候必须重建地图

只要输入分辨率、内参、畸变参数、校正旋转或新相机矩阵发生变化,旧 map 就失效。相机驱动在运行中切换 binning 或 ROI 时尤其容易踩中:代码还在 remap,几何却已经不是生成 map 时的几何。构造阶段保存这些参数的版本和 image_size,逐帧至少断言尺寸一致。

remap 不能原地操作,源图与目标图要分开。使用 BORDER_TRANSPARENT 时,落在源图之外的目标像素不会被修改,如果目标 Mat 没有预先初始化,就可能留下上一帧残影;去畸变通常用明确的 BORDER_CONSTANT,再根据有效 ROI 裁剪。

这项优化的边界很清楚:地图随标定变化而重建,帧循环只执行取样;正确性用浮点路径对照,收益用实际构建上的逐阶段计时确认。

← 全部文章

johan's blog