You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenCV技术咨询:如何用四台鱼眼摄像头生成车辆360°鸟瞰图

解决牛津机器人汽车数据集360°鸟瞰图生成的逆透视变换问题

要生成车辆周围360°鸟瞰图,手动选getPerspectiveTransform的源/目标点不是最优方案——因为你的相机(尤其是180°FOV的鱼眼相机)是倾斜安装的,单应性变换无法覆盖大视野的透视畸变。正确的做法是结合相机内外参,通过投影模型逆推图像点到鸟瞰平面的映射,以下是具体步骤:

1. 先定义鸟瞰图的物理尺度与坐标系

目标是800x800分辨率的鸟瞰图,先明确其物理意义:

  • 设定覆盖范围:比如车辆周围±20m(前后左右),对应0.05m/像素(20m ÷ 400像素),即每像素代表实际0.05米。
  • 坐标系映射:
    • 鸟瞰图中心(400,400)对应车辆地面投影中心;
    • 鸟瞰图向上像素对应车辆前方,向右像素对应车辆右侧;
    • 地面物理坐标(车辆坐标系:X向前、Y向左、Z向上)转鸟瞰像素坐标的公式:
      u_bev = 400 + int(Y / 0.05)  # Y为车辆坐标系下的横向坐标,正方向是左侧,所以符号需调整
      v_bev = 400 - int(X / 0.05)  # X为车辆坐标系下的纵向坐标,正方向是前方
      

2. 获取相机外参(核心前提)

牛津机器人汽车数据集会提供每个相机相对于车辆的外参(旋转矩阵R、平移向量T),表示相机坐标系到车辆坐标系的变换:

  • R:相机坐标系(X向右、Y向下、Z向前)到车辆坐标系的旋转关系;
  • T:相机中心在车辆坐标系下的位置(比如安装高度T[2]通常在1-1.5m左右)。

如果没有直接提供外参,可通过数据集内的标定板、已知GPS坐标的地面点(如车道线、路缘石)结合内参求解。

3. 基于相机投影模型生成映射(替代手动选点)

对每个去畸变后的相机图像,通过逆投影计算每个像素对应的鸟瞰图位置,步骤如下:

3.1 图像点转相机归一化坐标

利用相机内参矩阵K,将图像像素(u,v)转换为相机坐标系下的射线方向:

import numpy as np
# 以单目左相机内参为例
fx, fy = 400.0, 400.0
cx, cy = 500.107605, 511.461426
K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float32)

# 图像点转归一化相机坐标
uv_hom = np.array([u, v, 1])
cam_normalized = np.linalg.inv(K) @ uv_hom
cam_normalized /= cam_normalized[2]  # 得到(x,y,1)的射线方向

3.2 计算射线与地面的交点

通过外参将射线转换到车辆坐标系,求解与地面(Z=0)的交点:

# 示例外参(需替换为数据集实际值)
R = np.array([[0, 0, 1], [-1, 0, 0], [0, -1, 0]], dtype=np.float32)  # 相机向左安装的旋转矩阵
T = np.array([0.0, 1.5, 1.2], dtype=np.float32)  # 相机中心在车辆坐标系的位置:横向1.5m、高度1.2m

# 射线在车辆坐标系下的方向
cam_norm_vehicle = R @ cam_normalized
# 计算射线与地面Z=0的交点参数t
t = -T[2] / cam_norm_vehicle[2]
if t <= 0:
    # 射线向上(如天空区域),不与地面相交,跳过
    continue
# 得到地面点的车辆坐标
X = cam_norm_vehicle[0] * t + T[0]
Y = cam_norm_vehicle[1] * t + T[1]

3.3 转换为鸟瞰图像素坐标

用第一步定义的尺度,将地面物理坐标转为鸟瞰图像素位置,然后用cv2.remap完成图像变换。

4. 多相机拼接与融合

对每个相机生成对应的局部鸟瞰图后,处理重叠区域:

  • 加权融合:对重叠区域的像素取加权平均值(权重可根据相机距离该区域的远近调整);
  • 优先级覆盖:比如用立体相机的图像覆盖前后单目相机的重叠区域(立体相机分辨率更高)。

若必须用单应性变换(临时方案)

如果暂时无法获取外参,可尝试:

  • 在图像中找到至少4个已知物理坐标的地面点(比如数据集标注的GPS点、标定板位置);
  • 这些点的去畸变图像坐标作为源点,对应的鸟瞰图像素坐标(按物理尺度计算)作为目标点;
  • 用cv2.getPerspectiveTransform计算单应性矩阵,再用cv2.warpPerspective变换图像。
    但此方法仅适合局部小范围,大FOV鱼眼相机的边缘区域畸变无法通过单应性修正。

核心代码示例

import cv2
import numpy as np

# 鸟瞰图参数
BEV_SIZE = (800, 800)
PIX_PER_METER = 20  # 20像素/米,即0.05米/像素
BEV_CENTER = (BEV_SIZE[0]//2, BEV_SIZE[1]//2)

def create_bev_mapping(img_size, K, R, T):
    """生成图像到鸟瞰图的映射表"""
    map_x = np.zeros((img_size[0], img_size[1]), dtype=np.float32)
    map_y = np.zeros((img_size[0], img_size[1]), dtype=np.float32)
    for v in range(img_size[0]):
        for u in range(img_size[1]):
            # 图像点转归一化相机坐标
            uv_hom = np.array([u, v, 1], dtype=np.float32)
            cam_norm = np.linalg.inv(K) @ uv_hom
            cam_norm /= cam_norm[2]
            
            # 计算地面交点
            cam_norm_vehicle = R @ cam_norm
            t = -T[2] / cam_norm_vehicle[2]
            if t <= 0:
                map_x[v, u] = -1
                map_y[v, u] = -1
                continue
            X = cam_norm_vehicle[0] * t + T[0]
            Y = cam_norm_vehicle[1] * t + T[1]
            
            # 转鸟瞰像素坐标
            u_bev = BEV_CENTER[0] + int(Y * PIX_PER_METER)
            v_bev = BEV_CENTER[1] - int(X * PIX_PER_METER)
            if 0 <= u_bev < BEV_SIZE[0] and 0 <= v_bev < BEV_SIZE[1]:
                map_x[v, u] = u_bev
                map_y[v, u] = v_bev
            else:
                map_x[v, u] = -1
                map_y[v, u] = -1
    return map_x, map_y

# 处理单目左相机图像
img_left = cv2.imread("undistorted_left.jpg")
map_x, map_y = create_bev_mapping(img_left.shape[:2], K, R, T)
bev_left = np.zeros((BEV_SIZE[1], BEV_SIZE[0], 3), dtype=np.uint8)
cv2.remap(img_left, map_x, map_y, cv2.INTER_LINEAR, dst=bev_left)

# 同理处理其他相机后拼接融合
# ...

内容的提问来源于stack exchange,提问作者J. Random Luser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 05:42:54