球面投影的幾何背景
什么是球面投影?
球面投影將 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?1
、R * K?1
、K * R?1
)以提高后續圖像投影效率。結構體中的變量 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?1
與 K * R?1
,分別存儲在 r_kinv
和 k_rinv
中,為后續圖像投影變換(如前向和反向映射)提供高效的線性變換支持。這種設計可在圖像縫合或投影過程中大幅降低重復矩陣運算的開銷。
這段函數的核心目的是:
-
?解析相機的內外參數
-
?預計算常用的變換矩陣(K?1、R?1、R×K?1、K×R?1)
-
?將結果緩存到
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?1
將圖像坐標變換到相機坐標系下的三維方向向量 (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?1
將該方向向量投影回圖像平面,得到 (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
。該方法的核心作用是:基于球面模型將圖像從透視投影變換到球面投影,并輸出映射后的圖像及其左上角在全局投影圖像中的位置。