|
|
/* |
|
|
* Camera.cpp |
|
|
* |
|
|
* Copyright (c) 2014-2015 SEACAVE |
|
|
* |
|
|
* Author(s): |
|
|
* |
|
|
* cDc <cdc.seacave@gmail.com> |
|
|
* |
|
|
* |
|
|
* This program is free software: you can redistribute it and/or modify |
|
|
* it under the terms of the GNU Affero General Public License as published by |
|
|
* the Free Software Foundation, either version 3 of the License, or |
|
|
* (at your option) any later version. |
|
|
* |
|
|
* This program is distributed in the hope that it will be useful, |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
* GNU Affero General Public License for more details. |
|
|
* |
|
|
* You should have received a copy of the GNU Affero General Public License |
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
* |
|
|
* |
|
|
* Additional Terms: |
|
|
* |
|
|
* You are required to preserve legal notices and author attributions in |
|
|
* that material or in the Appropriate Legal Notices displayed by works |
|
|
* containing it. |
|
|
*/ |
|
|
|
|
|
#include "Common.h" |
|
|
#include "Camera.h" |
|
|
|
|
|
using namespace MVS; |
|
|
|
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
|
|
const Camera Camera::IDENTITY(Matrix3x3::eye(), Point3(REAL(0), REAL(0), REAL(0))); |
|
|
|
|
|
Camera::Camera(const Matrix3x4& _P, bool bUpdate/*=true*/) |
|
|
: |
|
|
P(_P) |
|
|
{ |
|
|
if (bUpdate) |
|
|
DecomposeP(); |
|
|
} // Camera |
|
|
Camera::Camera(const Matrix3x3& _R, const Point3& _C, bool bUpdate/*=true*/) |
|
|
: |
|
|
CameraIntern(_R, _C) |
|
|
{ |
|
|
if (bUpdate) |
|
|
ComposeP_RC(); |
|
|
} // Camera |
|
|
Camera::Camera(const Matrix3x3& _K, const Matrix3x3& _R, const Point3& _C, bool bUpdate/*=true*/) |
|
|
: |
|
|
CameraIntern(_K, _R, _C) |
|
|
{ |
|
|
if (bUpdate) |
|
|
ComposeP(); |
|
|
} // Camera |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
Camera& Camera::operator= (const CameraIntern& camera) |
|
|
{ |
|
|
K = camera.K; |
|
|
R = camera.R; |
|
|
C = camera.C; |
|
|
return *this; |
|
|
} |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
void Camera::ComposeP_RC() |
|
|
{ |
|
|
AssembleProjectionMatrix(R, C, P); |
|
|
} // ComposeP_RC |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
void Camera::ComposeP() |
|
|
{ |
|
|
AssembleProjectionMatrix(K, R, C, P); |
|
|
} // ComposeP |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
void Camera::DecomposeP_RC() |
|
|
{ |
|
|
DecomposeProjectionMatrix(P, R, C); |
|
|
} // DecomposeP_RC |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
void Camera::DecomposeP() |
|
|
{ |
|
|
DecomposeProjectionMatrix(P, K, R, C); |
|
|
} // DecomposeP |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
void Camera::Transform(const Matrix3x3& _R, const Point3& _t, const REAL& _s) |
|
|
{ |
|
|
R = R * _R.t(); |
|
|
C = _R*C*_s+_t; |
|
|
ComposeP(); |
|
|
} // Transform |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
REAL Camera::PointDepth(const Point3& X) const |
|
|
{ |
|
|
return P(2,0)*X.x + P(2,1)*X.y + P(2,2)*X.z + P(2,3); |
|
|
} // PointDepth |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
bool Camera::IsInFront(const Point3& X) const |
|
|
{ |
|
|
return (PointDepth(X)>0); |
|
|
} // IsInFront |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
REAL Camera::DistanceSq(const Point3& X) const |
|
|
{ |
|
|
const Point3 ray = X-C; |
|
|
return (SQUARE(ray.x)+SQUARE(ray.y)+SQUARE(ray.z)); |
|
|
} // Distance |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
// decomposition of projection matrix into KR[I|-C]: internal calibration ([3,3]), rotation ([3,3]) and translation ([3,1]) |
|
|
// (comparable with OpenCV: normalized cv::decomposeProjectionMatrix) |
|
|
void MVS::DecomposeProjectionMatrix(const PMatrix& P, KMatrix& K, RMatrix& R, CMatrix& C) |
|
|
{ |
|
|
// extract camera center as the right null vector of P |
|
|
const Vec4 hC(P.RightNullVector()); |
|
|
C = CMatrix(hC[0],hC[1],hC[2]) * INVERT(hC[3]); |
|
|
// perform RQ decomposition |
|
|
RQDecomp3x3<REAL>(cv::Mat(3,4,cv::DataType<REAL>::type,const_cast<REAL*>(P.val))(cv::Rect(0,0, 3,3)), K, R); |
|
|
// normalize calibration matrix |
|
|
K *= INVERT(K(2,2)); |
|
|
// ensure positive focal length |
|
|
if (K(0,0) < 0) { |
|
|
ASSERT(K(1,1) < 0); |
|
|
NEGATE(K(0,0)); |
|
|
NEGATE(K(1,1)); |
|
|
NEGATE(K(0,1)); |
|
|
NEGATE(K(0,2)); |
|
|
NEGATE(K(1,2)); |
|
|
(TMatrix<REAL,2,3>&)R *= REAL(-1); |
|
|
} |
|
|
ASSERT(R.IsValid()); |
|
|
} // DecomposeProjectionMatrix |
|
|
void MVS::DecomposeProjectionMatrix(const PMatrix& P, RMatrix& R, CMatrix& C) |
|
|
{ |
|
|
#ifndef _RELEASE |
|
|
KMatrix K; |
|
|
DecomposeProjectionMatrix(P, K, R, C); |
|
|
ASSERT(K.IsEqual(Matrix3x3::IDENTITY, 1e-5)); |
|
|
#endif |
|
|
// extract camera center as the right null vector of P |
|
|
const Vec4 hC(P.RightNullVector()); |
|
|
C = CMatrix(hC[0],hC[1],hC[2]) * INVERT(hC[3]); |
|
|
// get rotation |
|
|
const cv::Mat mP(3,4,cv::DataType<REAL>::type,const_cast<REAL*>(P.val)); |
|
|
mP(cv::Rect(0,0, 3,3)).copyTo(R); |
|
|
ASSERT(R.IsValid()); |
|
|
} // DecomposeProjectionMatrix |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// assemble projection matrix: P=KR[I|-C] |
|
|
void MVS::AssembleProjectionMatrix(const KMatrix& K, const RMatrix& R, const CMatrix& C, PMatrix& P) |
|
|
{ |
|
|
// compute temporary matrices |
|
|
cv::Mat mP(3,4,cv::DataType<REAL>::type,const_cast<REAL*>(P.val)); |
|
|
cv::Mat M(mP, cv::Rect(0,0, 3,3)); |
|
|
cv::Mat(K * R).copyTo(M); //3x3 |
|
|
mP.col(3) = M * cv::Mat(-C); //3x1 |
|
|
} // AssembleProjectionMatrix |
|
|
void MVS::AssembleProjectionMatrix(const RMatrix& R, const CMatrix& C, PMatrix& P) |
|
|
{ |
|
|
Eigen::Map<Matrix3x3::EMat,0,Eigen::Stride<4,0> >(P.val) = (const Matrix3x3::EMat)R; |
|
|
Eigen::Map<Point3::EVec,0,Eigen::Stride<0,4> >(P.val+3) = ((const Matrix3x3::EMat)R) * (-((const Point3::EVec)C)); |
|
|
} // AssembleProjectionMatrix |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// compute the focus of attention of a set of cameras; only cameras |
|
|
// that have the focus of attention in front of them are considered |
|
|
Point3 MVS::ComputeCamerasFocusPoint(const CameraArr& cameras, const Point3* pInitialFocus) |
|
|
{ |
|
|
// set initial focus point |
|
|
Point3 focus; |
|
|
if (pInitialFocus == NULL) { |
|
|
// initialize focus point to the average camera center |
|
|
focus = Point3::ZERO; |
|
|
for (const Camera& camera: cameras) |
|
|
focus += camera.C; |
|
|
focus /= cameras.size(); |
|
|
// compute average distance to the cameras |
|
|
REAL avgDist = 0; |
|
|
for (const Camera& camera: cameras) |
|
|
avgDist += SQRT(camera.DistanceSq(focus)); |
|
|
avgDist /= cameras.size(); |
|
|
// move focus point to the average distance in front of first camera |
|
|
focus = cameras.front().C + cameras.front().Direction()*avgDist; |
|
|
} else |
|
|
focus = *pInitialFocus; |
|
|
// compute focus point as the point in front of the active cameras, |
|
|
// closest to the view direction of each camera |
|
|
uint32_t numActive = 0; |
|
|
Point3::EVec smp(Point3::EVec::Zero()); |
|
|
Matrix3x3::EMat smTm(Matrix3x3::EMat::Zero()); |
|
|
for (const Camera& camera: cameras) { |
|
|
if (!camera.IsInFront(focus)) |
|
|
continue; |
|
|
// https://en.wikipedia.org/wiki/Line<EFBFBD>line_intersection#In_more_than_two_dimensions |
|
|
const Point3::EVec dir = camera.Direction(); |
|
|
const Matrix3x3::EMat m(Matrix3x3::EMat::Identity() - dir * dir.transpose()); |
|
|
const Matrix3x3::EMat mTm(m.transpose() * m); |
|
|
const Point3::EVec s = mTm * Point3::EVec(camera.C); |
|
|
smp += s; |
|
|
smTm += mTm; |
|
|
++numActive; |
|
|
} |
|
|
if (numActive >= 2) |
|
|
focus = smTm.inverse() * smp; |
|
|
return focus; |
|
|
} // ComputeCamerasFocusPoint |
|
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
|
|
|
|
namespace MVS { |
|
|
|
|
|
namespace RECTIFY { |
|
|
|
|
|
// compute the ROIs for the two images based on the corresponding points |
|
|
void GetImagePairROI(const Point3fArr& points1, const Point3fArr& points2, const Matrix3x3& K1, const Matrix3x3& K2, const Matrix3x3& R1, const Matrix3x3& R2, const Matrix3x3& invK1, const Matrix3x3& invK2, AABB2f& roi1h, AABB2f& roi2h) |
|
|
{ |
|
|
ASSERT(!points1.empty() && points1.size() && points2.size()); |
|
|
|
|
|
// compute rectification homography (from original to rectified image) |
|
|
const Matrix3x3 H1(K1 * R1 * invK1); |
|
|
const Matrix3x3 H2(K2 * R2 * invK2); |
|
|
|
|
|
// determine the ROIs in rectified images |
|
|
roi1h.Reset(); roi2h.Reset(); |
|
|
FOREACH(i, points1) { |
|
|
Point2f xh; |
|
|
const Point3f& x1 = points1[i]; |
|
|
ProjectVertex_3x3_2_2(H1.val, x1.ptr(), xh.ptr()); |
|
|
roi1h.InsertFull(xh); |
|
|
const Point3f& x2 = points2[i]; |
|
|
ProjectVertex_3x3_2_2(H2.val, x2.ptr(), xh.ptr()); |
|
|
roi2h.InsertFull(xh); |
|
|
} |
|
|
} |
|
|
|
|
|
void SetCameraMatricesROI(const AABB2f& roi1h, const AABB2f& roi2h, cv::Size& size1, cv::Size& size2, Matrix3x3& K1, Matrix3x3& K2) |
|
|
{ |
|
|
// set the new image sizes such that they are equal and contain the entire ROI |
|
|
const Point2f size1h(roi1h.GetSize()); |
|
|
const Point2f size2h(roi2h.GetSize()); |
|
|
const int maxSize(MAXF(size1.width+size2.width, size1.height+size2.height)/2); |
|
|
size1.width = size2.width = MINF(ROUND2INT(MAXF(size1h.x,size2h.x)), maxSize); |
|
|
size1.height = size2.height = MINF(ROUND2INT(MAXF(size1h.y,size2h.y)), maxSize); |
|
|
|
|
|
// set the new camera matrices such that the ROI is centered |
|
|
const Point2f center1h(roi1h.GetCenter()); |
|
|
const Point2f center2h(roi2h.GetCenter()); |
|
|
K1(0,2) += size1.width /2-center1h[0]; |
|
|
K1(1,2) += size1.height/2-center1h[1]; |
|
|
K2(0,2) += size2.width /2-center2h[0]; |
|
|
K2(1,2) += size2.height/2-center2h[1]; |
|
|
} |
|
|
|
|
|
} // namespace RECTIFY |
|
|
|
|
|
} // namespace MVS |
|
|
|
|
|
// Compute stereo rectification homographies that transform two images, |
|
|
// such that corresponding pixels in one image lie on the same scan-line in the other image; |
|
|
// note: this function assumes that the two cameras are already undistorted |
|
|
REAL Camera::StereoRectify(const cv::Size& size1, const Camera& camera1, const cv::Size& size2, const Camera& camera2, Matrix3x3& R1, Matrix3x3& R2, Matrix3x3& K1, Matrix3x3& K2) |
|
|
{ |
|
|
// compute relative pose |
|
|
RMatrix poseR; |
|
|
CMatrix poseC; |
|
|
ComputeRelativePose(camera1.R, camera1.C, camera2.R, camera2.C, poseR, poseC); |
|
|
|
|
|
// compute the average rotation between the first and the second camera |
|
|
const Point3 r(poseR.GetRotationAxisAngle()); |
|
|
reinterpret_cast<RMatrix&>(R2).SetFromAxisAngle(r*REAL(-0.5)); |
|
|
R1 = R2.t(); |
|
|
|
|
|
// compute the translation, such that it coincides with the X-axis |
|
|
int idx(0); |
|
|
Point3 t(R2 * (poseR*(-poseC))); |
|
|
#if 0 |
|
|
const Point3 unitX(t.x<0?-1:1, 0, 0); |
|
|
const Point3 axis(t.cross(unitX)); |
|
|
const REAL normAxis(norm(axis)); |
|
|
if (normAxis > EPSILONTOLERANCE<REAL>()) { |
|
|
ASSERT(t.dot(unitX) >= 0); |
|
|
const REAL angle(ACOS(ComputeAngle(t.ptr(), unitX.ptr()))); |
|
|
const RMatrix Rx(axis*angle/normAxis); |
|
|
#else |
|
|
const REAL nt(norm(t)); |
|
|
//idx = (ABS(t.x) > ABS(t.y) ? 0 : 1); |
|
|
Point3 axis(Point3(0,0,1).cross(t)); |
|
|
const REAL na(norm(axis)); |
|
|
if (na < EPSILONTOLERANCE<REAL>()) |
|
|
return 0; |
|
|
{ |
|
|
const Point3 w(normalized(t.cross(axis))); |
|
|
RMatrix Rx; |
|
|
for (int i = 0; i < 3; ++i) { |
|
|
Rx(idx,i) = -t[i]/nt; |
|
|
Rx(idx^1,i) = -axis[i]/na; |
|
|
Rx(2,i) = w[i]*(1-2*idx); // if idx == 1 -> opposite direction |
|
|
} |
|
|
#endif |
|
|
R1 = Rx * R1; |
|
|
R2 = Rx * R2; |
|
|
t = R2 * (poseR*(-poseC)); |
|
|
} |
|
|
ASSERT(ISEQUAL(-t.x, norm(camera2.C-camera1.C)) && ISZERO(t.y) && ISZERO(t.z)); |
|
|
|
|
|
// determine the intrinsic calibration matrix; |
|
|
// vertical focal length must be the same for both images to keep the epipolar constraint |
|
|
K1 = Matrix3x3::IDENTITY; |
|
|
K1(0,0) = (camera1.K(0,0)+camera2.K(0,0))/2; |
|
|
K1(1,1) = (camera1.K(1,1)+camera2.K(1,1))/2; |
|
|
K2 = K1; |
|
|
// set ROI to contain the whole transformed image |
|
|
for (int k = 0; k < 2; k++) { |
|
|
const Matrix3x3 H(k == 0 ? K1*R1*camera1.GetInvK() : K2*R2*camera2.GetInvK()); |
|
|
Point2f ptMin(std::numeric_limits<float>::max()); |
|
|
for (int i = 0; i < 4; i++) { |
|
|
const Point2f pt( |
|
|
(float)((i%2)*size1.width), |
|
|
(float)((i<2?0:1)*size1.height) |
|
|
); |
|
|
Point2f ptH; |
|
|
ProjectVertex_3x3_2_2(H.val, pt.ptr(), ptH.ptr()); |
|
|
ptMin.x = MINF(ptMin.x,ptH.x); |
|
|
ptMin.y = MINF(ptMin.y,ptH.y); |
|
|
} |
|
|
Matrix3x3& KNew(k == 0 ? K1 : K2); |
|
|
KNew(0,2) = -ptMin.x; |
|
|
KNew(1,2) = -ptMin.y; |
|
|
} |
|
|
K1(idx^1,2) = K2(idx^1,2) = MAXF(K1(idx^1,2), K2(idx^1,2)); |
|
|
return t.x; |
|
|
} // StereoRectify |
|
|
|
|
|
// see: "A compact algorithm for rectification of stereo pairs", A. Fusiello, E. Trucco, and A. Verri, 2000 |
|
|
REAL Camera::StereoRectifyFusiello(const cv::Size& size1, const Camera& camera1, const cv::Size& size2, const Camera& camera2, Matrix3x3& R1, Matrix3x3& R2, Matrix3x3& K1, Matrix3x3& K2) |
|
|
{ |
|
|
// compute relative pose |
|
|
RMatrix poseR; |
|
|
CMatrix poseC; |
|
|
ComputeRelativePose(camera1.R, camera1.C, camera2.R, camera2.C, poseR, poseC); |
|
|
|
|
|
// new x axis (baseline, from C1 to C2) |
|
|
const Point3 v1(camera2.C-camera1.C); |
|
|
// new y axes (orthogonal to old z and new x) |
|
|
const Point3 v2(camera1.Direction().cross(v1)); |
|
|
// new z axes (no choice, orthogonal to baseline and y) |
|
|
const Point3 v3(v1.cross(v2)); |
|
|
|
|
|
// new extrinsic (translation unchanged) |
|
|
RMatrix R; |
|
|
R.SetFromRowVectors(normalized(v1), normalized(v2), normalized(v3)); |
|
|
|
|
|
// new intrinsic (arbitrary) |
|
|
K1 = camera1.K; K1(0,1) = 0; |
|
|
K2 = camera2.K; K2(0,1) = 0; |
|
|
K1(1,1) = K2(1,1) = (camera1.K(1,1)+camera2.K(1,1))/2; |
|
|
|
|
|
// new rotations |
|
|
R1 = R*camera1.R.t(); |
|
|
R2 = R*camera2.R.t(); |
|
|
|
|
|
#if 0 |
|
|
// new projection matrices |
|
|
PMatrix P1, P2; |
|
|
AssembleProjectionMatrix(K1, R, camera1.C, P1); |
|
|
AssembleProjectionMatrix(K2, R, camera2.C, P2); |
|
|
|
|
|
// rectifying image transformation |
|
|
#if 0 |
|
|
const Matrix3x3 H1((PMatrix::EMat(P1).leftCols<3>()*(PMatrix::EMat(camera1.P).leftCols<3>().inverse())).eval()); |
|
|
const Matrix3x3 H2((PMatrix::EMat(P2).leftCols<3>()*(PMatrix::EMat(camera2.P).leftCols<3>().inverse())).eval()); |
|
|
#else |
|
|
const Matrix3x3 H1(K1*R*camera1.R.t()*camera1.GetInvK()); |
|
|
const Matrix3x3 H2(K2*R*camera2.R.t()*camera2.GetInvK()); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
const Point3 t(R2 * (poseR*(-poseC))); |
|
|
ASSERT(ISEQUAL(-t.x, norm(v1)) && ISZERO(t.y) && ISZERO(t.z)); |
|
|
return t.x; |
|
|
} // StereoRectifyFusiello |
|
|
|
|
|
// adjust rectified camera matrices such that the entire area common to both source images is contained in the rectified images; |
|
|
// as long as the focal-length on x and the skewness are equal in both cameras |
|
|
// the point-scale is also equal in both images; |
|
|
// note however that the surface-scale can still be different (most likely is) |
|
|
// as it depends on its (unknown) normal too; |
|
|
// - points1 and points2: contain the pairs of corresponding pairs of image projections and their depth |
|
|
// - size1 and size2: input the size of the source images, output the size of the rectified images (rectified image sizes are equal) |
|
|
void Camera::SetStereoRectificationROI(const Point3fArr& points1, cv::Size& size1, const Camera& camera1, const Point3fArr& points2, cv::Size& size2, const Camera& camera2, const Matrix3x3& R1, const Matrix3x3& R2, Matrix3x3& K1, Matrix3x3& K2) |
|
|
{ |
|
|
ASSERT(!points1.empty() && points1.size() && points2.size()); |
|
|
|
|
|
#if 1 |
|
|
// ignore skewness |
|
|
K1(0,1) = K2(0,1) = 0; |
|
|
#else |
|
|
// set same skewness |
|
|
K1(0,1) = K2(0,1) = (K1(0,1)+K2(0,1))/2; |
|
|
#endif |
|
|
|
|
|
// set same focal-length on x too |
|
|
K1(0,0) = K2(0,0) = (K1(0,0)+K2(0,0))/2; |
|
|
ASSERT(ISEQUAL(K1(1,1), K2(1,1))); |
|
|
|
|
|
// determine the ROIs in rectified images |
|
|
AABB2f roi1h, roi2h; |
|
|
RECTIFY::GetImagePairROI(points1, points2, K1, K2, R1, R2, camera1.GetInvK(), camera2.GetInvK(), roi1h, roi2h); |
|
|
|
|
|
// set the new camera matrices such that the ROI is centered |
|
|
RECTIFY::SetCameraMatricesROI(roi1h, roi2h, size1, size2, K1, K2); |
|
|
} // SetStereoRectificationROI |
|
|
/*----------------------------------------------------------------*/
|
|
|
|