关于球面投影SphericalProjector的介绍以及代码开发

球面投影的几何背景

什么是球面投影?

球面投影将 2D 图像中的像素点(通常是平面)映射到一个虚拟的球面上,再将球面上的角度(经度、纬度)展开到平面图上。它是广角图像拼接、全景图生成中常用的投影方法。

与圆柱投影(Cylinder Projection)不同的是,球面投影在水平与垂直两个方向都考虑了非线性映射,适合处理超大视角的图像。

球面投影的示例代码:

struct CV_EXPORTS_W_SIMPLE ProjectorBase

{

void setCameraParams(InputArray K = Mat::eye(3, 3, CV_32F),

InputArray R = Mat::eye(3, 3, CV_32F),

InputArray T = Mat::zeros(3, 1, CV_32F));

float scale; // 缩放因子

float k[9]; // 相机内参矩阵K(3x3)

float rinv[9]; // 旋转矩阵R的逆(R^{-1})

float r_kinv[9]; // 矩阵乘积 R * K^{-1}

float k_rinv[9]; // 矩阵乘积 K * R^{-1}

float t[3]; // 平移向量T

};

这段代码定义了一个基础投影器结构体 ProjectorBase,用于封装相机参数并提供统一的数据表示。它包含一个 setCameraParams 方法,可用于设置相机的内参矩阵 K、旋转矩阵 R 和位移向量 T,并预计算多个常用矩阵(如 R⁻¹R * K⁻¹K * R⁻¹)以提高后续图像投影效率。结构体中的变量 scale 表示图像投影缩放比例,而 krinvr_kinvk_rinv 等数组是将矩阵展平成 float 数组以便在图像变换计算中快速访问。该结构通常作为球面、柱面等具体投影器的基类使用。

  • 核心作用:存储相机参数和预计算的变换矩阵,为后续的投影变换(如球面投影)提供数学基础。
  • 矩阵存储方式 :所有 3x3 矩阵均以行优先 方式展开为一维数组(9 个float),便于高效计算。

void ProjectorBase::setCameraParams(InputArray _K, InputArray _R, InputArray _T)

{

Mat K = _K.getMat(), R = _R.getMat(), T = _T.getMat();

CV_Assert(K.size() == Size(3, 3) && K.type() == CV_32F);

CV_Assert(R.size() == Size(3, 3) && R.type() == CV_32F);

CV_Assert((T.size() == Size(1, 3) || T.size() == Size(3, 1)) && T.type() == CV_32F);

Mat_<float> K_(K);

k[0] = K_(0,0); k[1] = K_(0,1); k[2] = K_(0,2);

k[3] = K_(1,0); k[4] = K_(1,1); k[5] = K_(1,2);

k[6] = K_(2,0); k[7] = K_(2,1); k[8] = K_(2,2);

Mat_<float> Rinv = R.t();

rinv[0] = Rinv(0,0); rinv[1] = Rinv(0,1); rinv[2] = Rinv(0,2);

rinv[3] = Rinv(1,0); rinv[4] = Rinv(1,1); rinv[5] = Rinv(1,2);

rinv[6] = Rinv(2,0); rinv[7] = Rinv(2,1); rinv[8] = Rinv(2,2);

Mat_<float> R_Kinv = R * K.inv();

r_kinv[0] = R_Kinv(0,0); r_kinv[1] = R_Kinv(0,1); r_kinv[2] = R_Kinv(0,2);

r_kinv[3] = R_Kinv(1,0); r_kinv[4] = R_Kinv(1,1); r_kinv[5] = R_Kinv(1,2);

r_kinv[6] = R_Kinv(2,0); r_kinv[7] = R_Kinv(2,1); r_kinv[8] = R_Kinv(2,2);

Mat_<float> K_Rinv = K * Rinv;

k_rinv[0] = K_Rinv(0,0); k_rinv[1] = K_Rinv(0,1); k_rinv[2] = K_Rinv(0,2);

k_rinv[3] = K_Rinv(1,0); k_rinv[4] = K_Rinv(1,1); k_rinv[5] = K_Rinv(1,2);

k_rinv[6] = K_Rinv(2,0); k_rinv[7] = K_Rinv(2,1); k_rinv[8] = K_Rinv(2,2);

Mat_<float> T_(T.reshape(0, 3));

t[0] = T_(0,0); t[1] = T_(1,0); t[2] = T_(2,0);

}

这段代码实现了 ProjectorBase 类中的 setCameraParams 函数,用于初始化和预处理相机的内参矩阵 K、旋转矩阵 R 以及平移向量 T。函数首先检查输入矩阵的尺寸和类型是否符合要求(3x3 的 KR,3x1 或 1x3 的 T,数据类型为 CV_32F)。随后将矩阵数据以逐元素的形式复制到结构体的 float 数组中(如 krinv 等)。此外,它计算了 R * K⁻¹K * R⁻¹,分别存储在 r_kinvk_rinv 中,为后续图像投影变换(如前向和反向映射)提供高效的线性变换支持。这种设计可在图像缝合或投影过程中大幅降低重复矩阵运算的开销。

这段函数的核心目的是:

  • 解析相机的内外参数

  • 预计算常用的变换矩阵(K⁻¹、R⁻¹、R×K⁻¹、K×R⁻¹)

  • 将结果缓存到 float[] 数组中,加快后续几何投影计算速度

这也是 OpenCV 中 RotationWarperBaseSphericalWarper 等投影器运行时的前提配置步骤。

struct CV_EXPORTS_W_SIMPLE SphericalProjector : ProjectorBase

{

CV_WRAP void mapForward(float x, float y, float &u, float &v);

CV_WRAP void mapBackward(float u, float v, float &x, float &y);

};

SphericalProjector 继承自 ProjectorBase(一个投影器基类)。

  • 它包含两个方法:
    • mapForward: 将输入平面坐标 (x, y) 映射到球面坐标 (u, v)
    • mapBackward: 将球面坐标 (u, v) 映射回平面坐标 (x, y)
  • CV_WRAP 是 OpenCV 的宏,用于支持 Python 绑定(如 Python 接口)。

inline

void SphericalProjector::mapForward(float x, float y, float &u, float &v)

{

float x_ = r_kinv[0] * x + r_kinv[1] * y + r_kinv[2];

float y_ = r_kinv[3] * x + r_kinv[4] * y + r_kinv[5];

float z_ = r_kinv[6] * x + r_kinv[7] * y + r_kinv[8];

u = scale * atan2f(x_, z_);

float w = y_ / sqrtf(x_ * x_ + y_ * y_ + z_ * z_);

v = scale * (static_cast<float>(CV_PI) - acosf(w == w ? w : 0));

}

这段代码是 SphericalProjector 类中的 mapForward 函数,用于将二维图像坐标 (x, y) 映射到球面投影坐标 (u, v)。首先通过预计算的矩阵 r_kinv = R * K⁻¹ 将图像坐标变换到相机坐标系下的三维方向向量 (x_, y_, z_)。然后,使用球面坐标变换:u 表示水平方向的角度(由 atan2f(x_, z_) 得到),v 表示垂直方向的角度(通过向量与 y 轴夹角的反余弦得到),并结合缩放因子 scale 转换为像素单位。这种前向映射常用于将图像像素投影到球面上,例如图像拼接或全景图生成中的几何校正步骤。

步骤

  1. 通过矩阵 r_kinv 将输入的平面点 (x, y) 转换到相机坐标系中的3D点 (x_, y_, z_)。这个矩阵是旋转矩阵的逆和内参矩阵的逆的组合。
  2. 计算经度角(u):
    • 使用 atan2f(x_, z_) 计算经度(方位角),并乘以缩放因子 scale
  3. 计算纬度角(v):
    • 首先计算点相对于球心的仰角。公式中,w = y_ / ||P||(即点在相机坐标系中的 y 分量除以模长),这相当于 sin(φ),其中 φ 是与 y 轴相关的角度。
    • 使用 acos(w) 得到纬度角,然后调整为 v = scale * (π - acos(w)),使得 v 从0到π(通常球面投影纬度范围是[-π/2, π/2],这里转换为[0, π]以符合图像坐标习惯)。
  4. 处理 NaN:当分母为零时 w 可能是 NaN,使用 w == w ? w : 0 进行判断(如果 w 是 NaN,则用 0 代替)。

inline

void SphericalProjector::mapBackward(float u, float v, float &x, float &y)

{

u /= scale;

v /= scale;

float sinv = sinf(static_cast<float>(CV_PI) - v);

float x_ = sinv * sinf(u);

float y_ = cosf(static_cast<float>(CV_PI) - v);

float z_ = sinv * cosf(u);

float z;

x = k_rinv[0] * x_ + k_rinv[1] * y_ + k_rinv[2] * z_;

y = k_rinv[3] * x_ + k_rinv[4] * y_ + k_rinv[5] * z_;

z = k_rinv[6] * x_ + k_rinv[7] * y_ + k_rinv[8] * z_;

if (z > 0) { x /= z; y /= z; }

else x = y = -1;

}

这段代码是 SphericalProjector 类中的 mapBackward 函数,用于将球面投影坐标 (u, v) 反变换为图像平面坐标 (x, y)。首先将 (u, v) 除以缩放因子 scale,还原为单位球坐标下的角度;然后根据球面坐标公式,将角度转换为三维向量 (x_, y_, z_) 表示空间方向。接着通过预计算的变换矩阵 k_rinv = K * R⁻¹ 将该方向向量投影回图像平面,得到 (x, y, z)。最后执行透视除法(x/z, y/z)得到标准图像坐标。如果 z ≤ 0,说明方向指向相机背后,不可见,函数将 (x, y) 设为 -1。该函数常用于生成图像投影反向映射表,在图像拼接、全景重建等应用中至关重要。

步骤

  1. 将输入的 u, v 还原为弧度值(除以缩放因子 scale)。
  2. 从球面坐标重建3D点:
    • sinv = sin(π - v) = sin(v)(因为 v 是正向映射中计算为 π - φ,所以这里 π - v 就是 φ)。
    • 构建点:(x_, y_, z_) = (sinv * sin(u), cos(φ), sinv * cos(u))。注意这里 y_ 直接是 cos(π - v) = -cos(v)?但正向映射中 w = y_ / ||P|| 相当于 sin(φ)。这里实际上是:
      • x_ = sin(φ) * sin(θ)
      • y_ = cos(φ) (因为 φ 是与 y 轴的夹角)
      • z_ = sin(φ) * cos(θ)
        其中 φ 是纬度角,θ 是经度角。
  3. 使用矩阵 k_rinv(旋转矩阵和内参矩阵的组合的逆)将3D点变换回平面点。
  4. 进行透视除法(若点在相机前方,z>0),得到归一化平面坐标 (x/z, y/z)
  5. 若点位于相机后方(z<=0),则返回 (-1, -1) 表示无效点。

class CV_EXPORTS RotationWarper

{

public:

virtual ~RotationWarper() {}

/** @brief 投影图像中的像素点

@param pt 输入源图像中的点

@param K 相机内参矩阵

@param R 相机旋转矩阵

@return 投影后的点(例如球面、柱面上的位置)

*/

virtual Point2f warpPoint(const Point2f &pt, InputArray K, InputArray R) = 0;

/** @brief 将投影点反向映射回图像坐标

@param pt 投影后的点

@param K 相机内参矩阵

@param R 相机旋转矩阵

@return 反向映射回原图像的点

*/

#if CV_VERSION_MAJOR == 4

virtual Point2f warpPointBackward(const Point2f& pt, InputArray K, InputArray R)

{

CV_UNUSED(pt); CV_UNUSED(K); CV_UNUSED(R);

CV_Error(Error::StsNotImplemented, "");

}

#else

virtual Point2f warpPointBackward(const Point2f& pt, InputArray K, InputArray R) = 0;

#endif

/** @brief 构建投影映射表(map),用于图像重映射

@param src_size 输入图像尺寸

@param K 相机内参矩阵

@param R 相机旋转矩阵

@param xmap 输出的 x 方向映射表

@param ymap 输出的 y 方向映射表

@return 投影图像的最小外接矩形区域(ROI)

*/

virtual Rect buildMaps(Size src_size, InputArray K, InputArray R, OutputArray xmap, OutputArray ymap) = 0;

/** @brief 对图像进行投影变换

@param src 输入图像

@param K 相机内参矩阵

@param R 相机旋转矩阵

@param interp_mode 插值方式(如双线性、最近邻)

@param border_mode 边缘扩展模式(如边缘复制、常量填充)

@param dst 输出变换后的图像

@return 变换后图像的左上角坐标

*/

virtual Point warp(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode,

CV_OUT OutputArray dst) = 0;

/** @brief 对图像进行反向投影变换

@param src 已投影后的图像

@param K 相机内参矩阵

@param R 相机旋转矩阵

@param interp_mode 插值方式

@param border_mode 边缘扩展模式

@param dst_size 反向投影图像的尺寸

@param dst 输出的反向变换图像

*/

virtual void warpBackward(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode,

Size dst_size, CV_OUT OutputArray dst) = 0;

/**

@brief 获取投影图像的 ROI 区域(外接矩形)

@param src_size 输入图像尺寸

@param K 相机内参矩阵

@param R 相机旋转矩阵

@return 投影图像的最小外接矩形区域

*/

virtual Rect warpRoi(Size src_size, InputArray K, InputArray R) = 0;

/// 获取球面/柱面投影缩放因子

virtual float getScale() const { return 1.f; }

/// 设置缩放因子

virtual void setScale(float) {}

};

这段代码定义了一个抽象类 RotationWarper,是 OpenCV 图像拼接模块中的核心接口之一,主要用于处理图像在不同相机姿态下的旋转投影变换 。它为各种具体的投影方式(如球面投影、柱面投影)提供统一的接口封装,包括点的前向/反向投影(warpPointwarpPointBackward)、图像投影与反投影(warpwarpBackward)、构建映射表(buildMaps)、计算投影区域(warpRoi),以及设置缩放比例(setScale)。该类通过纯虚函数设计,要求派生类根据具体投影模型实现对应功能,是 OpenCV 中实现多视角图像配准和拼接(如全景图)的基础组件。

/** @brief Base class for rotation-based warper using a detail::ProjectorBase_ derived class.

*/

template <class P>

class CV_EXPORTS_TEMPLATE RotationWarperBase : public RotationWarper

{

public:

Point2f warpPoint(const Point2f &pt, InputArray K, InputArray R) CV_OVERRIDE;

Point2f warpPointBackward(const Point2f &pt, InputArray K, InputArray R) CV_OVERRIDE;

Rect buildMaps(Size src_size, InputArray K, InputArray R, OutputArray xmap, OutputArray ymap) CV_OVERRIDE;

Point warp(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode,

OutputArray dst) CV_OVERRIDE;

void warpBackward(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode,

Size dst_size, OutputArray dst) CV_OVERRIDE;

Rect warpRoi(Size src_size, InputArray K, InputArray R) CV_OVERRIDE;

float getScale() const CV_OVERRIDE{ return projector_.scale; }

void setScale(float val) CV_OVERRIDE { projector_.scale = val; }

protected:

// Detects ROI of the destination image. It's correct for any projection.

virtual void detectResultRoi(Size src_size, Point &dst_tl, Point &dst_br);

// Detects ROI of the destination image by walking over image border.

// Correctness for any projection isn't guaranteed.

void detectResultRoiByBorder(Size src_size, Point &dst_tl, Point &dst_br);

P projector_;

};

这段代码定义了一个模板基类 RotationWarperBase<P>,是 RotationWarper 接口的具体实现框架,适用于基于旋转变换的图像投影操作。它以模板参数 P 作为具体的投影器(如 SphericalProjectorCylindricalProjector)实例,通过封装和调用 projector_ 中定义的投影逻辑(如 mapForwardmapBackward),实现图像点的正向/反向映射、构建映射表、执行图像投影、计算变换后的图像区域等功能。该类为各种具体投影器提供统一的实现基础,既具备通用性,也便于在 OpenCV 图像拼接中扩展不同的投影模型。它将相机内参 K、旋转矩阵 R 封装为投影器的参数,使图像在全景拼接、视角变换等任务中能实现精准的几何变换。

template <class P>

Point2f RotationWarperBase<P>::warpPoint(const Point2f &pt, InputArray K, InputArray R)

{

projector_.setCameraParams(K, R);

Point2f uv;

projector_.mapForward(pt.x, pt.y, uv.x, uv.y);

return uv;

}

这段代码是 RotationWarperBase 模板类中 warpPoint 方法的实现,它接收一个图像点 pt,相机内参矩阵 K 和旋转矩阵 R,首先通过 projector_ 设置这些相机参数,然后调用其 mapForward 方法将输入图像点 (x, y) 投影到目标图像坐标 (u, v),最终返回变换后的点 uv。该函数实现了图像点在相机旋转作用下的前向几何映射,是图像配准和拼接中关键的投影步骤。

template <class P>

Point2f RotationWarperBase<P>::warpPointBackward(const Point2f& pt, InputArray K, InputArray R)

{

projector_.setCameraParams(K, R);

Point2f xy;

projector_.mapBackward(pt.x, pt.y, xy.x, xy.y);

return xy;

}

这段代码是模板类 RotationWarperBase<P>warpPointBackward 函数的实现,它用于执行图像坐标的反向投影 操作。函数接受一个图像点 pt(通常是投影图像中的坐标),以及相机的内参矩阵 K 和旋转矩阵 R。它首先调用 projector_ 设置相机参数,然后通过 projector_mapBackward 方法将该点 (u, v) 映射回原始图像中的点 (x, y),最终返回这个反投影后的点。该函数常用于图像反向映射(如反变形或从输出图像推回源图像坐标),在图像拼接和图像重建中尤为重要。

template <class P>

Rect RotationWarperBase<P>::buildMaps(Size src_size, InputArray K, InputArray R, OutputArray _xmap, OutputArray _ymap)

{

projector_.setCameraParams(K, R);

Point dst_tl, dst_br;

detectResultRoi(src_size, dst_tl, dst_br);

_xmap.create(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F);

_ymap.create(dst_br.y - dst_tl.y + 1, dst_br.x - dst_tl.x + 1, CV_32F);

Mat xmap = _xmap.getMat(), ymap = _ymap.getMat();

float x, y;

for (int v = dst_tl.y; v <= dst_br.y; ++v)

{

for (int u = dst_tl.x; u <= dst_br.x; ++u)

{

projector_.mapBackward(static_cast<float>(u), static_cast<float>(v), x, y);

xmap.at<float>(v - dst_tl.y, u - dst_tl.x) = x;

ymap.at<float>(v - dst_tl.y, u - dst_tl.x) = y;

}

}

return Rect(dst_tl, dst_br);

}

这段代码实现了一个模板函数 buildMaps,它是图像旋转投影类 RotationWarperBase<P> 的核心方法之一,主要作用是根据输入图像的尺寸 src_size,相机的内参矩阵 K 和旋转矩阵 R,构建两个映射表 xmapymap,分别对应每个目标图像像素在源图像上的横纵坐标。该函数首先设置投影器参数,然后通过 detectResultRoi 计算投影后图像的边界区域,接着为映射表分配内存,并遍历该区域的每个像素,使用投影器的 mapBackward 方法将目标像素反投影回源图像坐标系,并记录结果到 xmapymap 中。最终返回投影图像的边界矩形 Rect(dst_tl, dst_br),供后续拼接或重采样使用。这个函数在图像缝合、投影转换等视觉任务中非常关键。

template <class P>

Point RotationWarperBase<P>::warp(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode,

OutputArray dst)

{

UMat xmap, ymap;

Rect dst_roi = buildMaps(src.size(), K, R, xmap, ymap);

dst.create(dst_roi.height + 1, dst_roi.width + 1, src.type());

remap(src, dst, xmap, ymap, interp_mode, border_mode);

return dst_roi.tl();

}

这段模板函数 warpRotationWarperBase<P> 类中的图像正向投影实现,它根据给定的源图像 src、相机内参 K、旋转矩阵 R 以及插值方式 interp_mode 和边界处理方式 border_mode,对输入图像进行仿射或旋转投影变换。函数内部首先通过 buildMaps 构建反向映射表 xmapymap,确定目标图像的像素在源图像中的对应位置,然后创建目标图像 dst 的空间,并调用 remap 函数按照映射关系将源图像重新采样到目标图像中。最终返回的是目标图像左上角的坐标 dst_roi.tl(),常用于图像拼接时确定偏移。整个流程适用于基于旋转的图像投影变换,如全景拼接或视图重映射。

template <class P>

void RotationWarperBase<P>::warpBackward(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode,

Size dst_size, OutputArray dst)

{

projector_.setCameraParams(K, R);

Point src_tl, src_br;

detectResultRoi(dst_size, src_tl, src_br);

Size size = src.size();

CV_Assert(src_br.x - src_tl.x + 1 == size.width && src_br.y - src_tl.y + 1 == size.height);

Mat xmap(dst_size, CV_32F);

Mat ymap(dst_size, CV_32F);

float u, v;

for (int y = 0; y < dst_size.height; ++y)

{

for (int x = 0; x < dst_size.width; ++x)

{

projector_.mapForward(static_cast<float>(x), static_cast<float>(y), u, v);

xmap.at<float>(y, x) = u - src_tl.x;

ymap.at<float>(y, x) = v - src_tl.y;

}

}

dst.create(dst_size, src.type());

remap(src, dst, xmap, ymap, interp_mode, border_mode);

}

这段模板函数 warpBackward 实现了图像的反向投影变换 (Backward Warping),即将目标图像坐标反投影到原图像上,从而实现视图重建。函数接受源图像 src,相机内参 K,旋转矩阵 R,插值方式 interp_mode,边界处理方式 border_mode 以及目标图像尺寸 dst_size。它首先设置投影参数,然后通过 detectResultRoi 推断源图像的边界,并构造反向映射表 xmapymap:对于目标图中每个像素 (x, y),通过 mapForward 得到它在源图中的位置 (u, v),并记录偏移。最后调用 remap 进行插值采样,生成重建后的目标图像 dst。此方法常用于图像去畸变、全景图像还原等场景。

template <class P>

Rect RotationWarperBase<P>::warpRoi(Size src_size, InputArray K, InputArray R)

{

projector_.setCameraParams(K, R);

Point dst_tl, dst_br;

detectResultRoi(src_size, dst_tl, dst_br);

return Rect(dst_tl, Point(dst_br.x + 1, dst_br.y + 1));

}

这段模板函数 warpRoi 用于计算输入图像经过旋转投影变换后,在目标图像中的最小包围矩形区域(ROI) 。它首先通过 setCameraParams 设置相机的内参矩阵 K 和旋转矩阵 R,然后调用 detectResultRoi 函数计算变换后图像的左上角 dst_tl 和右下角 dst_br 坐标,最后构造一个 Rect 对象返回表示该区域。注意,这里右下角坐标加了 1,以确保矩形包含所有变换后的像素。这在拼接图像或预分配内存时非常关键。

template <class P>

void RotationWarperBase<P>::detectResultRoi(Size src_size, Point &dst_tl, Point &dst_br)

{

float tl_uf = (std::numeric_limits<float>::max)();

float tl_vf = (std::numeric_limits<float>::max)();

float br_uf = -(std::numeric_limits<float>::max)();

float br_vf = -(std::numeric_limits<float>::max)();

float u, v;

for (int y = 0; y < src_size.height; ++y)

{

for (int x = 0; x < src_size.width; ++x)

{

projector_.mapForward(static_cast<float>(x), static_cast<float>(y), u, v);

tl_uf = (std::min)(tl_uf, u); tl_vf = (std::min)(tl_vf, v);

br_uf = (std::max)(br_uf, u); br_vf = (std::max)(br_vf, v);

}

}

dst_tl.x = static_cast<int>(tl_uf);

dst_tl.y = static_cast<int>(tl_vf);

dst_br.x = static_cast<int>(br_uf);

dst_br.y = static_cast<int>(br_vf);

}

这段模板函数 detectResultRoi 的作用是计算经过投影变换后图像的最小包围矩形(ROI) 。函数通过遍历原始图像 src_size 中的每一个像素点 (x, y),使用 projector_.mapForward 将其投影到目标图像坐标系 (u, v),并记录所有投影点中的最小和最大坐标,以此确定变换后图像的左上角 dst_tl 和右下角 dst_br。这些点组成的矩形就是变换后图像的边界,用于后续图像重映射(remap)或图像拼接等操作。这是计算投影范围的基础步骤之一。

template <class P>

void RotationWarperBase<P>::detectResultRoiByBorder(Size src_size, Point &dst_tl, Point &dst_br)

{

float tl_uf = (std::numeric_limits<float>::max)();

float tl_vf = (std::numeric_limits<float>::max)();

float br_uf = -(std::numeric_limits<float>::max)();

float br_vf = -(std::numeric_limits<float>::max)();

float u, v;

for (float x = 0; x < src_size.width; ++x)

{

projector_.mapForward(static_cast<float>(x), 0, u, v);

tl_uf = (std::min)(tl_uf, u); tl_vf = (std::min)(tl_vf, v);

br_uf = (std::max)(br_uf, u); br_vf = (std::max)(br_vf, v);

projector_.mapForward(static_cast<float>(x), static_cast<float>(src_size.height - 1), u, v);

tl_uf = (std::min)(tl_uf, u); tl_vf = (std::min)(tl_vf, v);

br_uf = (std::max)(br_uf, u); br_vf = (std::max)(br_vf, v);

}

for (int y = 0; y < src_size.height; ++y)

{

projector_.mapForward(0, static_cast<float>(y), u, v);

tl_uf = (std::min)(tl_uf, u); tl_vf = (std::min)(tl_vf, v);

br_uf = (std::max)(br_uf, u); br_vf = (std::max)(br_vf, v);

projector_.mapForward(static_cast<float>(src_size.width - 1), static_cast<float>(y), u, v);

tl_uf = (std::min)(tl_uf, u); tl_vf = (std::min)(tl_vf, v);

br_uf = (std::max)(br_uf, u); br_vf = (std::max)(br_vf, v);

}

dst_tl.x = static_cast<int>(tl_uf);

dst_tl.y = static_cast<int>(tl_vf);

dst_br.x = static_cast<int>(br_uf);

dst_br.y = static_cast<int>(br_vf);

}

这段代码 detectResultRoiByBorderRotationWarperBase 模板类的一个成员函数,用于估算投影变换后的目标图像区域的最小包围矩形(ROI) ,但它只考虑了图像边界上的像素点,因此精度可能略低于完全遍历图像像素的方法 detectResultRoi。函数通过对源图像的四条边(顶边、底边、左边、右边)进行 mapForward 投影变换,计算变换后所有边缘点的最小(左上)和最大(右下)坐标,并据此构造 ROI 区域 dst_tldst_br。这种方法计算量较小,适合对 ROI 精度要求不是很高的场景。

class CV_EXPORTS SphericalWarper : public RotationWarperBase<SphericalProjector>

{

public:

/** @brief Construct an instance of the spherical warper class.

@param scale Radius of the projected sphere, in pixels. An image spanning the

whole sphere will have a width of 2 * scale * PI pixels.

*/

SphericalWarper(float scale) { projector_.scale = scale; }

Rect buildMaps(Size src_size, InputArray K, InputArray R, OutputArray xmap, OutputArray ymap) CV_OVERRIDE;

Point warp(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode, OutputArray dst) CV_OVERRIDE;

protected:

void detectResultRoi(Size src_size, Point &dst_tl, Point &dst_br) CV_OVERRIDE;

};

这段代码定义了一个 SphericalWarper 类,它继承自 RotationWarperBase<SphericalProjector>,用于实现球面图像投影变换 。构造函数中通过 scale 参数设定球面投影的半径,用于控制输出图像的分辨率。该类重载了 buildMaps()warp() 方法,分别用于生成投影映射图(xmap/ymap)和将源图像进行球面投影变换;同时还重载了 detectResultRoi() 方法,用于更精确地计算球面变换后图像的目标区域。这个类是 OpenCV 图像拼接模块中用于将图像映射到球面坐标系的关键组件,常用于处理宽视角全景图像的对齐与合成。

void SphericalWarper::detectResultRoi(Size src_size, Point &dst_tl, Point &dst_br)

{

detectResultRoiByBorder(src_size, dst_tl, dst_br);

float tl_uf = static_cast<float>(dst_tl.x);

float tl_vf = static_cast<float>(dst_tl.y);

float br_uf = static_cast<float>(dst_br.x);

float br_vf = static_cast<float>(dst_br.y);

float x = projector_.rinv[1];

float y = projector_.rinv[4];

float z = projector_.rinv[7];

if (y > 0.f)

{

float x_ = (projector_.k[0] * x + projector_.k[1] * y) / z + projector_.k[2];

float y_ = projector_.k[4] * y / z + projector_.k[5];

if (x_ > 0.f && x_ < src_size.width && y_ > 0.f && y_ < src_size.height)

{

tl_uf = std::min(tl_uf, 0.f); tl_vf = std::min(tl_vf, static_cast<float>(CV_PI * projector_.scale));

br_uf = std::max(br_uf, 0.f); br_vf = std::max(br_vf, static_cast<float>(CV_PI * projector_.scale));

}

}

x = projector_.rinv[1];

y = -projector_.rinv[4];

z = projector_.rinv[7];

if (y > 0.f)

{

float x_ = (projector_.k[0] * x + projector_.k[1] * y) / z + projector_.k[2];

float y_ = projector_.k[4] * y / z + projector_.k[5];

if (x_ > 0.f && x_ < src_size.width && y_ > 0.f && y_ < src_size.height)

{

tl_uf = std::min(tl_uf, 0.f); tl_vf = std::min(tl_vf, static_cast<float>(0));

br_uf = std::max(br_uf, 0.f); br_vf = std::max(br_vf, static_cast<float>(0));

}

}

dst_tl.x = static_cast<int>(tl_uf);

dst_tl.y = static_cast<int>(tl_vf);

dst_br.x = static_cast<int>(br_uf);

dst_br.y = static_cast<int>(br_vf);

}

这段代码是 SphericalWarper::detectResultRoi 的实现,用于精确计算球面投影后图像的目标区域(ROI) 。它首先调用 detectResultRoiByBorder() 获取一个初始边界框,然后进一步考虑图像在球面投影下的可视范围,并根据投影中心方向是否朝上/下修正边界框。

关键步骤如下:

  1. 使用 detectResultRoiByBorder 得到初步的左上 (dst_tl) 和右下 (dst_br) 投影边界点。

  2. 提取旋转矩阵的逆矩阵中与 Y 轴方向相关的向量,判断相机朝向。

  3. 根据相机是否朝上 (y > 0) 或朝下 (-y > 0),计算相机视角对应图像坐标,判断这些方向是否在图像有效区域内。

  4. 若在图像范围内,则更新投影区域上下边界 tl_vfbr_vf,使其完整包含可能的投影角度范围,如 [0, π*scale]

  5. 最终将浮点结果转换为整数坐标,输出目标矩形区域。

这段逻辑主要用于在球面投影中修正 ROI,避免丢失极端朝向下的可视区域,从而保证拼接图像时视角完整、无裁剪。

Rect SphericalWarper::buildMaps(Size src_size, InputArray K, InputArray R, OutputArray xmap, OutputArray ymap)

{

#ifdef HAVE_OPENCL

if (ocl::isOpenCLActivated())

{

ocl::Kernel k("buildWarpSphericalMaps", ocl::stitching::warpers_oclsrc);

if (!k.empty())

{

int rowsPerWI = ocl::Device::getDefault().isIntel() ? 4 : 1;

projector_.setCameraParams(K, R);

Point dst_tl, dst_br;

detectResultRoi(src_size, dst_tl, dst_br);

Size dsize(dst_br.x - dst_tl.x + 1, dst_br.y - dst_tl.y + 1);

xmap.create(dsize, CV_32FC1);

ymap.create(dsize, CV_32FC1);

Mat k_rinv(1, 9, CV_32FC1, projector_.k_rinv);

UMat uxmap = xmap.getUMat(), uymap = ymap.getUMat(), uk_rinv = k_rinv.getUMat(ACCESS_READ);

k.args(ocl::KernelArg::WriteOnlyNoSize(uxmap), ocl::KernelArg::WriteOnly(uymap),

ocl::KernelArg::PtrReadOnly(uk_rinv), dst_tl.x, dst_tl.y, 1/projector_.scale, rowsPerWI);

size_t globalsize[2] = { (size_t)dsize.width, ((size_t)dsize.height + rowsPerWI - 1) / rowsPerWI };

if (k.run(2, globalsize, NULL, true))

{

CV_IMPL_ADD(CV_IMPL_OCL);

return Rect(dst_tl, dst_br);

}

}

}

#endif

return RotationWarperBase<SphericalProjector>::buildMaps(src_size, K, R, xmap, ymap);

}

这段代码实现了 SphericalWarper::buildMaps 方法,用于为球面投影生成图像的重映射(remap)表,即 xmapymap。其核心目的是:计算球面投影后的目标图像坐标映射表 ,以便后续使用 OpenCV 的 remap() 函数进行图像变换。

其逻辑可分为两种路径:

  1. OpenCL 加速路径 (条件编译启用 HAVE_OPENCL):

    • 检查是否启用 OpenCL 加速(ocl::isOpenCLActivated())。

    • 创建 OpenCL kernel "buildWarpSphericalMaps"(定义在 OpenCV 的 warpers_oclsrc 源中)。

    • 若 kernel 成功加载:

      • 设置相机内参 K 和旋转矩阵 R

      • 调用 detectResultRoi() 获取投影图像的边界框。

      • 分配 xmapymap,并将投影参数打包为 OpenCL 参数传给 kernel。

      • 运行 kernel,根据线程模型自动生成 warp map。

      • 若成功执行,返回投影矩形区域。

  2. 回退到 CPU 路径(当没有 OpenCL 或 kernel 加载失败):

    • 调用基类 RotationWarperBase<SphericalProjector>::buildMaps() 使用 CPU 实现生成 xmapymap

该方法优先尝试使用 OpenCL 并行计算提升投影映射生成效率,若不可用则退回常规 CPU 实现。它是球面图像拼接中的关键步骤,用于将图像正确投影到球面坐标系统上,为后续图像缝合打下基础。

Point SphericalWarper::warp(InputArray src, InputArray K, InputArray R, int interp_mode, int border_mode, OutputArray dst)

{

UMat uxmap, uymap;

Rect dst_roi = buildMaps(src.size(), K, R, uxmap, uymap);

dst.create(dst_roi.height + 1, dst_roi.width + 1, src.type());

remap(src, dst, uxmap, uymap, interp_mode, border_mode);

return dst_roi.tl();

}

这段代码是 SphericalWarper::warp 方法的实现,它完成了对输入图像 src 进行球面投影变换的全过程。首先调用 buildMaps 根据相机内参 K 和旋转矩阵 R 构建重映射表 uxmapuymap,然后使用 OpenCV 的 remap 函数将图像投影到球面坐标系下,生成输出图像 dst。该方法的核心作用是:基于球面模型将图像从透视投影变换到球面投影,并输出映射后的图像及其左上角在全局投影图像中的位置

相关推荐
achene_ql7 分钟前
OpenCV C++ 图像处理教程:灰度变换与直方图分析
c++·图像处理·人工智能·opencv·计算机视觉
mortimer36 分钟前
当PySide6遇上ModelScope:一场关于 paraformer-zh is not registered 的调试旅程
人工智能·github·阿里巴巴
Baihai IDP40 分钟前
深度解析 Cursor(逐行解析系统提示词、分享高效制定 Cursor Rules 的技巧...)
人工智能·ai编程·cursor·genai·智能体·llms
神经星星44 分钟前
MIT 团队利用大模型筛选 25 类水泥熟料替代材料,相当于减排 12 亿吨温室气体
人工智能·深度学习·机器学习
Jamence1 小时前
多模态大语言模型arxiv论文略读(125)
论文阅读·人工智能·语言模型·自然语言处理·论文笔记
AI浩1 小时前
TradingAgents:基于多智能体的大型语言模型(LLM)金融交易框架
人工智能·语言模型·自然语言处理
澳鹏Appen1 小时前
对抗性提示:进阶守护大语言模型
人工智能·语言模型·自然语言处理
源图客2 小时前
大语言模型指令集全解析
人工智能·语言模型·自然语言处理
wenzhangli72 小时前
筑牢安全防线:电子文件元数据驱动的 AI 知识库可控管理方案
大数据·人工智能
北京地铁1号线2 小时前
OCRBench:评估多模态大模型的OCR能力
人工智能