关于球面投影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
表示图像投影缩放比例,而 k
、rinv
、r_kinv
和 k_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 的 K
和 R
,3x1 或 1x3 的 T
,数据类型为 CV_32F
)。随后将矩阵数据以逐元素的形式复制到结构体的 float 数组中(如 k
、rinv
等)。此外,它计算了 R * K⁻¹
与 K * R⁻¹
,分别存储在 r_kinv
和 k_rinv
中,为后续图像投影变换(如前向和反向映射)提供高效的线性变换支持。这种设计可在图像缝合或投影过程中大幅降低重复矩阵运算的开销。
这段函数的核心目的是:
-
解析相机的内外参数
-
预计算常用的变换矩阵(K⁻¹、R⁻¹、R×K⁻¹、K×R⁻¹)
-
将结果缓存到
float[]
数组中,加快后续几何投影计算速度
这也是 OpenCV 中 RotationWarperBase
或 SphericalWarper
等投影器运行时的前提配置步骤。
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
转换为像素单位。这种前向映射常用于将图像像素投影到球面上,例如图像拼接或全景图生成中的几何校正步骤。
步骤:
- 通过矩阵
r_kinv
将输入的平面点(x, y)
转换到相机坐标系中的3D点(x_, y_, z_)
。这个矩阵是旋转矩阵的逆和内参矩阵的逆的组合。 - 计算经度角(u):
- 使用
atan2f(x_, z_)
计算经度(方位角),并乘以缩放因子scale
。
- 使用
- 计算纬度角(v):
- 首先计算点相对于球心的仰角。公式中,
w = y_ / ||P||
(即点在相机坐标系中的 y 分量除以模长),这相当于sin(φ)
,其中 φ 是与 y 轴相关的角度。 - 使用
acos(w)
得到纬度角,然后调整为v = scale * (π - acos(w))
,使得 v 从0到π(通常球面投影纬度范围是[-π/2, π/2],这里转换为[0, π]以符合图像坐标习惯)。
- 首先计算点相对于球心的仰角。公式中,
- 处理
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。该函数常用于生成图像投影反向映射表,在图像拼接、全景重建等应用中至关重要。
步骤:
- 将输入的
u
,v
还原为弧度值(除以缩放因子scale
)。 - 从球面坐标重建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(θ)
其中 φ 是纬度角,θ 是经度角。
- 使用矩阵
k_rinv
(旋转矩阵和内参矩阵的组合的逆)将3D点变换回平面点。 - 进行透视除法(若点在相机前方,
z>0
),得到归一化平面坐标(x/z, y/z)
。 - 若点位于相机后方(
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 图像拼接模块中的核心接口之一,主要用于处理图像在不同相机姿态下的旋转投影变换。它为各种具体的投影方式(如球面投影、柱面投影)提供统一的接口封装,包括点的前向/反向投影(warpPoint
和 warpPointBackward
)、图像投影与反投影(warp
和 warpBackward
)、构建映射表(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
作为具体的投影器(如 SphericalProjector
、CylindricalProjector
)实例,通过封装和调用 projector_
中定义的投影逻辑(如 mapForward
、mapBackward
),实现图像点的正向/反向映射、构建映射表、执行图像投影、计算变换后的图像区域等功能。该类为各种具体投影器提供统一的实现基础,既具备通用性,也便于在 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
,构建两个映射表 xmap
和 ymap
,分别对应每个目标图像像素在源图像上的横纵坐标。该函数首先设置投影器参数,然后通过 detectResultRoi
计算投影后图像的边界区域,接着为映射表分配内存,并遍历该区域的每个像素,使用投影器的 mapBackward
方法将目标像素反投影回源图像坐标系,并记录结果到 xmap
和 ymap
中。最终返回投影图像的边界矩形 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();
}
这段模板函数 warp
是 RotationWarperBase<P>
类中的图像正向投影实现,它根据给定的源图像 src
、相机内参 K
、旋转矩阵 R
以及插值方式 interp_mode
和边界处理方式 border_mode
,对输入图像进行仿射或旋转投影变换。函数内部首先通过 buildMaps
构建反向映射表 xmap
和 ymap
,确定目标图像的像素在源图像中的对应位置,然后创建目标图像 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
推断源图像的边界,并构造反向映射表 xmap
和 ymap
:对于目标图中每个像素 (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);
}
这段代码 detectResultRoiByBorder
是 RotationWarperBase
模板类的一个成员函数,用于估算投影变换后的目标图像区域的最小包围矩形(ROI),但它只考虑了图像边界上的像素点,因此精度可能略低于完全遍历图像像素的方法 detectResultRoi
。函数通过对源图像的四条边(顶边、底边、左边、右边)进行 mapForward
投影变换,计算变换后所有边缘点的最小(左上)和最大(右下)坐标,并据此构造 ROI 区域 dst_tl
到 dst_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()
获取一个初始边界框,然后进一步考虑图像在球面投影下的可视范围,并根据投影中心方向是否朝上/下修正边界框。
关键步骤如下:
-
使用
detectResultRoiByBorder
得到初步的左上 (dst_tl
) 和右下 (dst_br
) 投影边界点。 -
提取旋转矩阵的逆矩阵中与 Y 轴方向相关的向量,判断相机朝向。
-
根据相机是否朝上 (
y > 0
) 或朝下 (-y > 0
),计算相机视角对应图像坐标,判断这些方向是否在图像有效区域内。 -
若在图像范围内,则更新投影区域上下边界
tl_vf
和br_vf
,使其完整包含可能的投影角度范围,如[0, π*scale]
。 -
最终将浮点结果转换为整数坐标,输出目标矩形区域。
这段逻辑主要用于在球面投影中修正 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)表,即 xmap
和 ymap
。其核心目的是:计算球面投影后的目标图像坐标映射表,以便后续使用 OpenCV 的 remap()
函数进行图像变换。
其逻辑可分为两种路径:
-
OpenCL 加速路径(条件编译启用
HAVE_OPENCL
):-
检查是否启用 OpenCL 加速(
ocl::isOpenCLActivated()
)。 -
创建 OpenCL kernel
"buildWarpSphericalMaps"
(定义在 OpenCV 的warpers_oclsrc
源中)。 -
若 kernel 成功加载:
-
设置相机内参
K
和旋转矩阵R
。 -
调用
detectResultRoi()
获取投影图像的边界框。 -
分配
xmap
和ymap
,并将投影参数打包为 OpenCL 参数传给 kernel。 -
运行 kernel,根据线程模型自动生成 warp map。
-
若成功执行,返回投影矩形区域。
-
-
-
回退到 CPU 路径(当没有 OpenCL 或 kernel 加载失败):
-
调用基类
RotationWarperBase<SphericalProjector>::buildMaps()
使用 CPU 实现生成xmap
和ymap
。
-
该方法优先尝试使用 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
构建重映射表 uxmap
和 uymap
,然后使用 OpenCV 的 remap
函数将图像投影到球面坐标系下,生成输出图像 dst
。该方法的核心作用是:基于球面模型将图像从透视投影变换到球面投影,并输出映射后的图像及其左上角在全局投影图像中的位置。