1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-28 06:43:01 +04:00
Files
opencv/modules/geometry/src/triangulate.cpp
T
Alexander Smorkalov 59218f9edd Merge pull request #29175 from asmorkalov:as/geometry2
Geometry module #29175

OpenCV Contrib: https://github.com/opencv/opencv_contrib/pull/4129
CI changes: https://github.com/opencv/ci-gha-workflow/pull/313

Continues
- https://github.com/opencv/opencv/pull/28804
- https://github.com/opencv/opencv/pull/29101
- https://github.com/opencv/opencv/pull/29108
- https://github.com/opencv/opencv/pull/28810

Todo for followup PRs:
- [x] Rename doxygen groups
- [x] Fix JS modules layout and whitelists
- [ ] Sort tutorials code/snippets

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
2026-05-31 14:23:15 +03:00

300 lines
12 KiB
C++

/*M///////////////////////////////////////////////////////////////////////////////////////
//
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
// By downloading, copying, installing or using the software you agree to this license.
// If you do not agree to this license, do not download, install,
// copy or use the software.
//
//
// Intel License Agreement
// For Open Source Computer Vision Library
//
// Copyright (C) 2009, Intel Corporation and others, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistribution's of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistribution's in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * The name of Intel Corporation may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/
#include "precomp.hpp"
#include <iostream>
#include <opencv2/core/hal/hal.hpp>
namespace cv {
// correctMatches function is Copyright (C) 2009, Jostein Austvik Jacobsen.
// triangulatePoints function is derived from reconstructPointsFor3View, originally by Valery Mosyagin.
// HZ, R. Hartley and A. Zisserman, Multiple View Geometry in Computer Vision, Cambridge Univ. Press, 2003.
// This method is the same as reconstructPointsFor3View, with only a few numbers adjusted for two-view geometry
void triangulatePoints( InputArray _P1, InputArray _P2,
InputArray _points1, InputArray _points2,
OutputArray _points4D )
{
CV_INSTRUMENT_REGION();
Mat points1 = _points1.getMat(), points2 = _points2.getMat();
int depth1 = points1.depth(), depth2 = points2.depth();
const float *p1f = depth1 == CV_32F ? points1.ptr<float>() : 0;
const float *p2f = depth2 == CV_32F ? points2.ptr<float>() : 0;
const double *p1d = depth1 == CV_64F ? points1.ptr<double>() : 0;
const double *p2d = depth2 == CV_64F ? points2.ptr<double>() : 0;
int pstep1, ystep1, pstep2, ystep2, npoints1, npoints2;
CV_Assert(depth1 == depth2 && (depth1 == CV_32F || depth1 == CV_64F));
if ((points1.rows == 1 || points1.cols == 1) && points1.channels() == 2)
{
npoints1 = points1.rows + points1.cols - 1;
ystep1 = 1;
pstep1 = 2;
}
else
{
npoints1 = points1.cols;
ystep1 = (int)(points1.step/points1.elemSize());
pstep1 = 1;
}
if ((points2.rows == 1 || points2.cols == 1) && points2.channels() == 2)
{
npoints2 = points2.rows + points2.cols - 1;
ystep2 = 1;
pstep2 = 2;
}
else
{
npoints2 = points2.cols;
ystep2 = (int)(points2.step/points2.elemSize());
pstep2 = 1;
}
CV_Assert(npoints1 == npoints2);
_points4D.create(4, npoints1, depth1);
Mat points4D = _points4D.getMat();
Matx<double, 4, 4> matrA;
Matx<double, 4, 4> matrU;
Matx<double, 4, 1> matrW;
Matx<double, 4, 4> matrV;
size_t step4 = 4*sizeof(double);
Matx<double, 3, 4> P1;
Matx<double, 3, 4> P2;
_P1.getMat().convertTo(P1, CV_64F);
_P2.getMat().convertTo(P2, CV_64F);
// Solve system for each point
for( int i = 0; i < npoints1; i++ )
{
// Fill matrix for current point
double x1 = p1f ? (double)p1f[pstep1*i] : p1d[pstep1*i];
double y1 = p1f ? (double)p1f[pstep1*i + ystep1] : p1d[pstep1*i + ystep1];
double x2 = p2f ? (double)p2f[pstep2*i] : p2d[pstep2*i];
double y2 = p2f ? (double)p2f[pstep2*i + ystep2] : p2d[pstep2*i + ystep2];
for(int k = 0; k < 4; k++)
{
matrA(k, 0) = x1*P1(2, k) - P1(0, k);
matrA(k, 1) = y1*P1(2, k) - P1(1, k);
matrA(k, 2) = x2*P2(2, k) - P2(0, k);
matrA(k, 3) = y2*P2(2, k) - P2(1, k);
}
// Solve system for current point
hal::SVD64f(matrA.val, step4, matrW.val, matrU.val, step4, matrV.val, step4, 4, 4, 4);
// Copy computed point
if(depth1 == CV_32F)
{
points4D.at<float>(0, i) = (float)matrV(3, 0);
points4D.at<float>(1, i) = (float)matrV(3, 1);
points4D.at<float>(2, i) = (float)matrV(3, 2);
points4D.at<float>(3, i) = (float)matrV(3, 3);
}
else
{
points4D.at<double>(0, i) = matrV(3, 0);
points4D.at<double>(1, i) = matrV(3, 1);
points4D.at<double>(2, i) = matrV(3, 2);
points4D.at<double>(3, i) = matrV(3, 3);
}
}
}
/*
* The Optimal Triangulation Method (see HZ for details)
* For each given point correspondence points1[i] <-> points2[i], and a fundamental matrix F,
* computes the corrected correspondences new_points1[i] <-> new_points2[i] that minimize the
* geometric error d(points1[i],new_points1[i])^2 + d(points2[i],new_points2[i])^2 (where d(a,b)
* is the geometric distance between points a and b) subject to the epipolar constraint
* new_points2' * F * new_points1 = 0.
*
* _F : 3x3 fundamental matrix
* _points1 : 1xN matrix containing the first set of points
* _points2 : 1xN matrix containing the second set of points
* _newPoints1 : the optimized _points1.
* _newPoints2 : the optimized -points2.
*/
void correctMatches( InputArray _F, InputArray _points1, InputArray _points2,
OutputArray _newPoints1, OutputArray _newPoints2 )
{
CV_INSTRUMENT_REGION();
Mat points1 = _points1.getMat(), points2 = _points2.getMat();
int depth1 = points1.depth(), depth2 = points2.depth();
CV_Assert((depth1 == CV_32F || depth1 == CV_64F) && depth1 == depth2);
CV_Assert(points1.size() == points2.size());
CV_Assert(points1.rows == 1 || points1.cols == 1);
if (points1.channels() != 2)
CV_Error( cv::Error::StsUnmatchedSizes, "The first set of points must contain two channels; one for x and one for y" );
if (points2.channels() != 2)
CV_Error( cv::Error::StsUnmatchedSizes, "The second set of points must contain two channels; one for x and one for y" );
_newPoints1.create(points1.size(), points1.type());
_newPoints2.create(points2.size(), points2.type());
Mat newPoints1 = _newPoints1.getMat(), newPoints2 = _newPoints2.getMat();
Matx33d F, U, Vt;
Matx31d S;
int npoints = points1.rows + points1.cols - 1;
// Make sure F uses double precision
_F.getMat().convertTo(F, CV_64F);
for (int p = 0; p < npoints; ++p) {
// Replace F by T2-t * F * T1-t
double x1, y1, x2, y2;
if (depth1 == CV_32F) {
Point2f p1 = points1.at<Point2f>(p);
Point2f p2 = points2.at<Point2f>(p);
x1 = p1.x; y1 = p1.y;
x2 = p2.x; y2 = p2.y;
} else {
Point2d p1 = points1.at<Point2d>(p);
Point2d p2 = points2.at<Point2d>(p);
x1 = p1.x; y1 = p1.y;
x2 = p2.x; y2 = p2.y;
}
Matx33d T1i(1, 0, x1,
0, 1, y1,
0, 0, 1);
Matx33d T2i(1, 0, x2,
0, 1, y2,
0, 0, 1);
Matx33d TFT = T2i.t()*F*T1i;
// Compute the right epipole e1 from F * e1 = 0
SVDecomp(TFT, S, U, Vt);
double scale = sqrt(Vt(2, 0)*Vt(2, 0) + Vt(2, 1)*Vt(2, 1));
Vec3d e1(Vt(2, 0)/scale, Vt(2, 1)/scale, Vt(2, 2)/scale);
if (e1(2) < 0)
e1 = -e1;
// Compute the left epipole e2 from e2' * F = 0 => F' * e2 = 0
scale = sqrt(U(0, 2)*U(0, 2) + U(1, 2)*U(1, 2));
Vec3d e2(U(0, 2)/scale, U(1, 2)/scale, U(2, 2)/scale);
if (e2(2) < 0)
e2 = -e2;
// Replace F by R2 * F * R1'
Matx33d R1_t(e1(0), -e1(1), 0,
e1(1), e1(0), 0,
0, 0, 1);
Matx33d R2(e2(0), e2(1), 0,
-e2(1), e2(0), 0,
0, 0, 1);
Matx33d RTFTR = R2*TFT*R1_t;
// Set f1 = e1(3), f2 = e2(3), a = F22, b = F23, c = F32, d = F33
double f1 = e1(2);
double f2 = e2(2);
double a = RTFTR(1,1);
double b = RTFTR(1,2);
double c = RTFTR(2,1);
double d = RTFTR(2,2);
// Form the polynomial g(t) = k6*t^6 + k5*t^5 + k4*t^4 + k3*t^3 + k2*t^2 + k1*t + k0
// from f1, f2, a, b, c and d
Vec<double, 7> polynomial(
-a*d*d*b+b*b*c*d,
+f2*f2*f2*f2*d*d*d*d+b*b*b*b+2*b*b*f2*f2*d*d-a*a*d*d+b*b*c*c,
+4*a*b*b*b+4*b*b*f2*f2*c*d+4*f2*f2*f2*f2*c*d*d*d-a*a*d*c+b*c*c*a+4*a*b*f2*f2*d*d-2*a*d*d*f1*f1*b+2*b*b*c*f1*f1*d,
+6*a*a*b*b+6*f2*f2*f2*f2*c*c*d*d+2*b*b*f2*f2*c*c+2*a*a*f2*f2*d*d-2*a*a*d*d*f1*f1+2*b*b*c*c*f1*f1+8*a*b*f2*f2*c*d,
+4*a*a*a*b+2*b*c*c*f1*f1*a+4*f2*f2*f2*f2*c*c*c*d+4*a*b*f2*f2*c*c+4*a*a*f2*f2*c*d-2*a*a*d*f1*f1*c-a*d*d*f1*f1*f1*f1*b+b*b*c*f1*f1*f1*f1*d,
+f2*f2*f2*f2*c*c*c*c+2*a*a*f2*f2*c*c-a*a*d*d*f1*f1*f1*f1+b*b*c*c*f1*f1*f1*f1+a*a*a*a,
+b*c*c*f1*f1*f1*f1*a-a*a*d*f1*f1*f1*f1*c);
// Solve g(t) for t to get 6 roots
double rdata[6*2];
Mat result(6, 1, CV_64FC2, rdata);
solvePoly(polynomial, result);
// Evaluate the cost function s(t) at the real part of the 6 roots
double t_min = DBL_MAX;
double s_val = 1./(f1*f1) + (c*c)/(a*a+f2*f2*c*c);
for (int ti = 0; ti < 6; ++ti) {
Vec2d root_i = result.at<Vec2d>(ti);
double t = root_i(0);
double s = (t*t)/(1 + f1*f1*t*t) + ((c*t + d)*(c*t + d))/((a*t + b)*(a*t + b) + f2*f2*(c*t + d)*(c*t + d));
if (s < s_val) {
s_val = s;
t_min = t;
}
}
// find the optimal x1 and y1 as the points on l1 and l2 closest to the origin
scale = t_min*t_min*f1*f1+1;
Vec3d tmp31(t_min*t_min*f1/scale, t_min/scale, 1);
Vec3d tmp31_2 = T1i*(R1_t*tmp31);
x1 = tmp31_2(0);
y1 = tmp31_2(1);
scale = f2*f2*(c*t_min+d)*(c*t_min+d) + (a*t_min+b)*(a*t_min+b);
tmp31 = Vec3d(f2*(c*t_min+d)*(c*t_min+d)/scale, -(a*t_min+b)*(c*t_min+d)/scale, 1);
tmp31_2 = T2i*(R2.t()*tmp31);
x2 = tmp31_2(0);
y2 = tmp31_2(1);
// Return the points in the matrix format that the user wants
if (depth1 == CV_32F) {
newPoints1.at<Point2f>(p) = Point2f((float)x1, (float)y1);
newPoints2.at<Point2f>(p) = Point2f((float)x2, (float)y2);
} else {
newPoints1.at<Point2d>(p) = Point2d(x1, y1);
newPoints2.at<Point2d>(p) = Point2d(x2, y2);
}
}
}
}