03 Two-View Geometry
03 Two-View Geometry
https://github.com/mint-lab/3dv_tutorial
An Invitation to 3D Vision:
Two-View Geometry
2
Table of Contents: Two-view Geometry
▪ Planar Homography
▪ Epipolar Geometry
– Epipolar constraint
– Fundamental and essential matrix
▪ Relative Camera Pose Estimation
▪ Triangulation
plane
𝐱
𝐱′
𝐱 ′ = H𝐱
transformation H
shear
rotation composition
translation
perspective
projection
scaling / aspect ratio
4
Planar Homography
Euclidean Transformation Similarity Transformation Affine Transformation Projective Transformation
(a.k.a. Rigid Transform) (a.k.a. Planar Homography)
DOF 3 4 6 8
Transformations
- rotation O O O O
- translation O O O O
- scaling X O O O
- aspect ratio X X O O
- shear X X O O
- perspective projection X X X O
Invariants
- length O X X X
- angle O O X X
- ratio of lengths O O X X
- parallelism O O O X
- incidence O O O O
- cross ratio O O O O
cv.getAffineTransform() cv.getPerspectiveTransform()
cv.estimateRigidTransform() -
OpenCV APIs
- cv.findHomography()
cv.warpAffine() cv.warpPerspective()
Image: Wikipedia 6
Note) Affine Transformation
relative pose R, 𝐭
8
Planar Homography
pts_dst
𝐱 𝑖′ = H𝐱 𝑖
Click
pts_src
9
Planar Homography
if __name__ == '__main__':
img_file = '../data/sunglok_card.jpg'
card_size = (450, 250)
offset = 10
# Load an image
img = cv.imread(img_file)
# Load an image
img = cv.imread(img_file)
if len(pts_src) == 4:
# Calculate planar homography and rectify perspective distortion
H, _ = cv.findHomography(np.array(pts_src), pts_dst)
img_rectify = cv.warpPerspective(img, H, card_size)
cv.destroyAllWindows()
11
Planar Homography
fmatcher = cv.DescriptorMatcher_create('BruteForce-Hamming')
match = fmatcher.match(descriptors1, descriptors2)
_, gray_ref = video.read()
if gray_ref.ndim >= 3:
gray_ref = cv.cvtColor(gray_ref, cv.COLOR_BGR2GRAY)
pts_ref = cv.goodFeaturesToTrack(gray_ref, 2000, 0.01, 10)
14
𝐗
Epipolar Geometry
▪ Epipolar constraint
⊺ image plane
𝐱 ′ F𝐱 = 0 (F: Fundamental matrix on the image plane)
↕ (𝐱 = Kො𝐱)
𝐱 𝐱′
𝐱ො ′⊺ K ′⊺ FKො𝐱 = 0 camera #1
camera #2
↕ (E = K ′⊺ FK)
relative pose R, 𝐭
𝐱ො ′⊺ Eො𝐱 = 0 (E: Essential matrix on the normalized image plane)
𝐱ො 𝐱ො ′
camera #1
camera #2
relative pose R, 𝐭
15
Epipolar Geometry
↓ (ො𝐱 ′ ∙ (𝐭 × 𝐱ො ′ ) = 0)
𝜆ො𝐱 ′ ∙ 𝐭 × Rො𝐱 = 0
0 −𝑡𝑧 𝑡𝑦
↓ (E = 𝐭 × R or E = 𝐭 × R) Note) 𝐭 × = 𝑡𝑧 0 −𝑡𝑥 normalized image plane
−𝑡𝑦 𝑡𝑥 0
𝐱ො ′ Eො𝐱 = 0 𝐱ො 𝐱ො ′
camera #1
camera #2
relative pose R, 𝐭
16
Epipolar Geometry
▪ Epipolar geometry
– Baseline
𝐗
• Distance between two camera centers, 𝐂 and 𝐂′
– Epipolar plane
• Plane generated from three points, 𝐗, 𝐂, and 𝐂′ epipolar plane
image plane
– Epipole (a.k.a. epipolar point)
𝐱 𝐱′
• Projection of other camera centers
𝐂
• 𝐞 = P𝐂′ and 𝐞′ = P 𝐂 ′
camera #1
𝐞 baseline 𝐞′ 𝐂′ camera #2
– e.g. P = K[I|0] and P = K [R|𝐭]
′ ′
17
Epipolar Geometry
▪ Epipolar geometry
– Epipolar line
𝐗
⊺ ⊺
• 𝐱 ′ F𝐱 = 0 → 𝐱 ′ 𝐥′ = 0 where 𝐥′ = F𝐱
– 𝐱 ′ will lie on the line 𝐥′.
epipolar line 𝐥′ = F𝐱
′⊺
• 𝐱 F𝐱 = 0 → 𝐥⊺ 𝐱 = 0 where 𝐥 = F ⊺ 𝐱’ image plane
– 𝐱 will lie on the line 𝐥. 𝐱
• The search space of feature matching is reduced
from a image plane to the epipolar line. camera #1
𝐞 baseline 𝐞′ camera #2
– Note) Every epipolar line intersects at the epipole.
epipole
• F𝐞 = 0
• 𝐞 is the null space of F. Special case) Stereo cameras
epipolar line
⊺
R = I3×3 , 𝐭 = −𝑏, 0, 0 18
Epipolar Geometry
Image #2
Image #2
Image #1
Image #1 19
Epipolar Geometry
if __name__ == '__main__':
# Load two images
img1 = cv.imread('../data/KITTI07/image_0/000000.png', cv.IMREAD_COLOR)
img2 = cv.imread('../data/KITTI07/image_0/000023.png', cv.IMREAD_COLOR)
# Note) `F` is derived from `fundamental_mat_estimation.py`.
F = np.array([[ 3.34638533e-07, 7.58547151e-06, -2.04147752e-03], ...])
# Get a point from a image and draw its correponding epipolar line on the other image
while True:
if len(img1_pts) > 0:
for x, y in img1_pts:
color = (random.randrange(256), random.randrange(256), random.randrange(256))
cv.circle(img1, (x, y), 4, color, -1)
epipolar_line = F @ [[x], [y], [1]] 𝐥′ = F𝐱
draw_straight_line(img2, epipolar_line, color, 2)
img1_pts.clear()
if len(img2_pts) > 0:
for x, y in img2_pts:
color = (random.randrange(256), random.randrange(256), random.randrange(256))
cv.circle(img2, (x, y), 4, color, -1)
epipolar_line = F.T @ [[x], [y], [1]]
draw_straight_line(img1, epipolar_line, color, 2) 𝐥 = F ⊺ 𝐱′
img2_pts.clear()
cv.imshow(wnd2_name, img2) 21
Relative Camera Pose Estimation
22
Relative Camera Pose Estimation: Scale Ambiguity
Reference: Choi et al., Resolving Scale Ambiguity in Monocular Visual Odometry, URAI, 2013 23
Relative Camera Pose Estimation: Scale Ambiguity
1
𝐿
∴𝜌=
𝑙
𝜌
𝑙
𝐿
Reference: Choi et al., Resolving Scale Ambiguity in Monocular Visual Odometry, URAI, 2013 24
Relative Camera Pose Estimation: Relative Pose Ambiguity
Image: Hartley and Zisserman, Multiple View Geometry in Computer Vision (2nd Edition), Cambridge University Press, 2004 25
Relative Camera Pose Estimation
fmatcher = cv.DescriptorMatcher_create('BruteForce-Hamming')
match = fmatcher.match(descriptors1, descriptors2)
26
Relative Camera Pose Estimation
27
Relative Camera Pose Estimation
28
Relative Camera Pose Estimation
Image 𝒏 − 𝟏 Image 𝒏
Relative pose R, 𝐭
29
Relative Camera Pose Estimation
30
Relative Camera Pose Estimation
_, gray_prev = video.read()
if gray_prev.ndim >= 3 and gray_prev.shape[2] > 1:
gray_prev = cv.cvtColor(gray_prev, cv.COLOR_BGR2GRAY)
34
Relative Camera Pose Estimation
35
Relative Camera Pose Estimation
Formulation 1
E = 𝐭 ×R = R + 𝐭𝐧⊺
H
𝑑
Estimation - 5-point algorithm (𝑛 ≥ 5) → k solution - 4-point algorithm (𝑛 ≥ 4) → 1 solution
- cv.findEssentialMat() - cv::findHomography()
Input - (ො𝐱 𝑖 , 𝐱ො 𝑖′ ) [m] on the normalized image plane - (ො𝐱 𝑖 , 𝐱ො 𝑖′ ) [m] on a plane in the normalized image plane
Degenerate - No translational motion - Correspondence not from a single plane
Cases
Decomposition - cv.decomposeEssentialMat() - cv.decomposeHomographyMat() with K = I3×3
to R and 𝐭 - cv.recoverPose()
37
Triangulation
camera #1
camera #2
relative pose R, 𝐭
39
A point cloud: data/box.xyz
Triangulation
▪ Example) Triangulation
f, cx, cy = 1000., 320., 240.
pts0 = np.loadtxt('../data/image_formation0.xyz')[:,:2]
pts1 = np.loadtxt('../data/image_formation1.xyz')[:,:2]
output_file = '../data/triangulation.xyz'
CloudCompare
# Estimate relative pose of two view
F, _ = cv.findFundamentalMat(pts0, pts1, cv.FM_8POINT)
K = np.array([[f, 0, cx], [0, f, cy], [0, 0, 1]])
E = K.T @ F @ K
_, R, t, _ = cv.recoverPose(E, pts0, pts1)
output_file: data/triangulation.xyz
# Write the reconstructed 3D points
np.savetxt(output_file, X)
40
Summary
▪ Planar Homography: 𝐱 𝑖′ = H𝐱 𝑖
– Example) Perspective distortion correction
– Example) Planar image stitching
– Example) 2D video stabilization
⊺
▪ Epipolar Geometry: 𝐱 ′ F𝐱 = 0 (on the image plane), 𝐱ො ′⊺ Eො𝐱 = 0 (on the normalized image plane)
– Example) Epipolar line visualization
▪ Relative Camera Pose Estimation: Finding R and 𝐭 (5 DOF) R, 𝐭
– Solutions) Fundamental matrix: 7/8-point algorithm (7 DOF) ↕ (E = 𝐭 × R)
E
– Solutions) Essential matrix: 5-point algorithm (5 DOF) ↕ (E = K ′⊺ FK)
– Solutions) Planar homography: 4-point algorithm (8 DOF) F
– Example) Fundamental matrix estimation
– Example) Monocular Visual Odometry (Epipolar Version)
▪ Triangulation: Finding 𝐗 (3 DOF)
– Example) Triangulation
41