当前位置: 首页 > news >正文

关于球面投影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。该方法的核心作用是:基于球面模型将图像从透视投影变换到球面投影,并输出映射后的图像及其左上角在全局投影图像中的位置

    http://www.lryc.cn/news/571660.html

    相关文章:

  • 分治算法之归并排序
  • webpack+vite前端构建工具 - 3webpack处理js
  • 深入ZGC并发处理的原理
  • 固态硬盘的加装和初始化
  • 电路图识图基础知识-摇臂钻床识图(三十一)
  • 27.自连接
  • 你的下一把量化“瑞士军刀”?KHQuant适用场景全解析【AI量化第32篇】
  • 数据集笔记:宣城轨迹
  • 权重遍历及Delong‘s test | 已完成单调性检验?
  • 键盘 AK35I Pro V2 分析
  • ABP vNext + Azure Application Insights:APM 监控与性能诊断最佳实践
  • 零基础设计模式——总结与进阶 - 1. 设计模式的综合应用
  • 利用cpolar实现Talebook数字图书馆的实时访问
  • ZYNQ学习记录FPGA(五)高频信号中的亚稳态问题
  • VMware vSphere Foundation 9.0 技术手册 —— Ⅰ 安装 ESXi 9.0 (虚拟机)
  • 数据库char字段做trim之后查询很慢的解决方式
  • 需要做一款小程序,用来发券,后端如何进行设计能够保证足够安全?
  • 微信原生小程序转uniapp过程及错误总结
  • 环卫车辆定位与监管:安心联车辆监控管理平台--科技赋能城市环境卫生管理
  • 【力扣 中等 C】2. 两数相加
  • chili3d笔记18 出三视图调整
  • 数据结构——选择题—查漏补缺
  • Could not locate zlibwapi.dll. Please make sure it is in your library path!
  • 功耗高?加密弱?爱普特APT32F1023H8S6单片机 2μA待机+AES硬件加密破局
  • Vue3 + TypeScript 本地存储 localStorage 的用法
  • 【时时三省】(C语言基础)内部函数和外部函数
  • Cornerstone3D 2.x升级调研
  • EPLAN P8 2.9 如何使用.step格式3D文件绘制3D安装布局图
  • 用idea操作git缓存区回退、本地库回退、远程库回退
  • Oracle client 静默安装