#!/usr/bin/env python # coding: utf-8 # # EE 046746 - Technion - Computer Vision # # #### Elias Nehme # # ## Tutorial 10 - Camera Calibration and Epipolar Geometry # --- # ## Agenda # --- # * [Camera Model](#-Camera-Model) # * [Camera Calibration](#-Camera-Calibration) # * [Estimating M](#-Estimating-$M$) # * [Chessboard Demo](#-Chessboard-Calibration-in-OpenCV) # * [Homography Quiz](#-Homography-Quiz) # * [Epipolar Geometry](#-Epipolar-Geometry) # * [Essential Matrix](#-Essential-Matrix) # * [Fundamental Matrix](#-Fundamental-Matrix) # * [Fundamental Demo](#-Fundamental-Demo) # * [Recommended Videos](#-Recommended-Videos) # * [Credits](#-Credits) # ## Camera Model # --- # ### The (rearranged) pinhole camera # --- # # # # * A 3D world point $P$ is projected by the camera matrix $M$ to the 2D image point $p$ # ### The (rearranged) pinhole camera # --- # # # ### The (rearranged) pinhole camera # --- # # # # * What is the decomposed structure of $M$? # ### The Camera Matrix # --- # # # # * $M$ is a $3 \times 4$ matrix comprised of two sets of parameters: **Intrinsic** and **Extrinsic**. # ### The Camera Matrix # --- # # # # * How many degress of freedom so far? # * And after switching $f$ with $f_x$ and $f_y$ and adding skew $s$? # ## Camera Calibration # --- # * Estimation of $M$ # * Separating Extrinsic and Intrinsic Parameters # ### Geometric Camera Calibration: Estimating $M$ # --- # # # # * Where did we get such matched points? # ### Estimating $M$ # --- # # * Same trick as in the Homogrpahy tutorial $\rightarrow$ switch to row-wise representation of the unknowns: # $$ \begin{bmatrix} x \\ y \\ w \end{bmatrix} = \begin{bmatrix} m_{11} & m_{12} & m_{13} & m_{14}\\ m_{21} & m_{22} & m_{23} & m_{24} \\ m_{31} & m_{32} & m_{33} & m_{34} \end{bmatrix} # \begin{bmatrix} X \\ Y \\ Z \\ 1 \end{bmatrix}$$ # # * Equivalently # $$ \begin{bmatrix} x \\ y \\ w \end{bmatrix} = \begin{bmatrix} - & m_1^T & - \\ - & m_2^T & - \\ - & m_3^T & - \end{bmatrix} # P$$ # # # ### Estimating $M$ # --- # # * Resulting equation for $x$ and $y$ in heterogeneous coordinates: # $$\tilde{x} = \frac{m_1^T P}{m_3^T P}, \tilde{y} = \frac{m_2^T P}{m_3^T P}$$ # # * Rearranging to solve for $m_i$: # $$ m_1^TP - \tilde{x}m_3^T P = 0$$ # $$ m_2^TP - \tilde{y}m_3^T P = 0$$ # # * What is the dimension of $m_i^T P$? # # # ### Estimating $M$ # --- # # * Rearrange into a matrix for $N_p$ points: # $$ \begin{bmatrix} P_i^T & 0^T & -\tilde{x}_i P_i^T \\ 0^T & P_i^T & -\tilde{y}_i P_i^T \\ . & . & . \\ . & . & . \\ P_{N_p}^T & 0^T & -\tilde{x}_{N_p} P_{N_p}^T \\ 0^T & P_{N_p}^T & -\tilde{y}_{N_p} P_{N_p}^T \end{bmatrix} \begin{bmatrix} | \\ m_1 \\ | \\ m_2 \\ | \\ m_3 \\ | \end{bmatrix} = \begin{bmatrix} 0 \\ 0 \\ . \\ . \\ 0 \\ 0\end{bmatrix} \leftrightarrow Am = 0$$ # # * What are the dimensions? How much points $N_p$ do we need? # # # ### Estimating $M$ # --- # # * boils down to the problem: # $$ \hat{m} = \text{arg}\min\limits_m \| Am \|^2 \ s.t. \|m\|^2 = 1$$ # # * Solution via SVD of $A = U \Sigma V^T$: # # * $\hat{m}$ is the column of V corresponding to the smallest eigen-value. # # * How about separating $M$ to $K \left[R \lvert t\right]$? # # ### Decomposition of M to K, R & t # # * rewrite $M$: # $$ M = K \left[R \lvert t\right] = K \left[R \lvert -Rc\right] = \left[N \lvert -Nc\right]$$ # # * $c$ can be found via SVD of $M$ due to the relation: # $$ Mc = 0$$ # # * Then $N$ can be found and further decomposed into $N = K R $: # * How? using QR decomposition because $K$ is upper triangular and $R$ is orthogonal # # * However.. # * Does not take into account noise, radial distortions, hard to impose prior knowledge (e.g. $f$), etc. # * Solution? # ### Minimize reprojection error # --- # # # # * Where the radial distortion model is: # $$ \lambda = 1 + k_1 r^2 + k_2 r^ 4 + k_3 r^6$$ # ### Minimize reprojection error # --- # * Radial distortion is multiplicative: # $$ x_{rad} = x \left[1 + k_1 r^2 + k_2 r^ 4 + k_3 r^6\right]$$ # $$ y_{rad} = y \left[1 + k_1 r^2 + k_2 r^ 4 + k_3 r^6\right]$$ # # * Usually we also include tangential distortion: # $$ x_{tan} = x + \left[2 p_1 xy + p_2 \left(r^2+2x^2\right)\right]$$ # $$ y_{tan} = y + \left[p_1 \left(r^2 + 2y^2 \right) + 2 p_2 xy\right]$$ # # * We end up with 5 parameters to estimate: # $$ \text{distortion coefficients} = \left[k_1, k_2, k_3, p_1, p_2\right]^T$$ # ## Chessboard Calibration in OpenCV # --- # * Take a notebook and paste a chesspattern # * Capture this pattern from several angles and positions # * Calibrate using OpenCV # ## Chessboard Calibration in OpenCV # --- # * Getting the 3D to 2D points correspondences from a known planar object # * Chessboard has fixed distances between squares known appriori # * Camera static and chessboard moves $\leftrightarrow$ chessboard static and camera moves # * Camera moves $\leftrightarrow$ Extrinsic parameters in each frame change # * Therefore we got the matches of real world points and camera points $\left\{P_i, p_i\right\}_{i=1}^N$! # * $P_i = \left[X_i, Y_i, Z_i=0\right]$, where, $X_i, Y_i$ set by periodicity of the chessbaord # * $p_i = \left[x_i, y_i\right]$, detected corners in the image # In[2]: import numpy as np import cv2 import glob # termination criteria criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0) objp = np.zeros((6*7,3), np.float32) objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2) # Arrays to store object points and image points from all the images. objpoints = [] # 3d point in real world space imgpoints = [] # 2d points in image plane. images = glob.glob('./assets/left*.jpg') for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Find the chess board corners ret, corners = cv2.findChessboardCorners(gray, (7,6), None) # If found, add object points, image points (after refining them) if ret == True: objpoints.append(objp) corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) imgpoints.append(corners) # Draw and display the corners imlast = cv2.drawChessboardCorners(img, (7,6), corners2, ret) cv2.imshow('img', img) cv2.waitKey(500) cv2.destroyAllWindows() # In[11]: # show the last image and the detected corners import matplotlib.pyplot as plt plt.figure(figsize=(13,10)) plt.imshow(imlast) plt.axis('off') plt.title('Calibration Target Corners', fontsize=30) plt.show() # In[3]: # now that we have object points and and image points, we just apply OpenCV builtin function ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) # resulting camera matrix print("M = ") print(repr(mtx)) # distortion coeff. print("distortion coeff = ") print(repr(dist)) # Rodriguez rotation vectors and translation vectors print("Rotation vector 1 = ") print(rvecs[0]) print("Translation vector 1 = ") print(tvecs[0]) print("\n . \n . \n . \n") print("Rotation vector N = ") print(rvecs[-1]) print("Translation vector N = ") print(tvecs[-1]) # In[4]: # let us examine the distortion on a given image img = cv2.imread('./assets/left12.jpg') h, w = img.shape[:2] newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) # undistort dst = cv2.undistort(img, mtx, dist, None, newcameramtx) # crop the image x, y, w, h = roi dst = dst[y:y+h, x:x+w] # plot the image ebfore and after fixing distortion plt.figure(figsize=(18,13)) plt.subplot(1,2,1) plt.imshow(img) plt.axis('off') plt.title('Distorted', fontsize=30) plt.subplot(1,2,2) plt.imshow(dst) plt.axis('off') plt.title('Undistorted', fontsize=30) plt.show() # In[5]: # checking reprojection errors for validation - ideally we should get ~0 mean_error = 0 errs_all = [] for i in range(len(objpoints)): imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist) errs_all.append(np.squeeze(np.sqrt(np.sum((imgpoints[i] - imgpoints2)**2,axis=2)))) # all reprojection errors in absolute pixel values errs_all = np.hstack(errs_all) print("Mean error: {:.4f} px".format(errs_all.mean())) fig = plt.figure(figsize=(12, 7)) ax = fig.add_subplot(1, 1 ,1) ax.hist(errs_all, 20) ax.set_title("Histogram of Reprojection Errors", fontsize=30) ax.set_xlabel('Error in [pixels]', fontsize=30) ax.set_ylabel('Counts', fontsize=30) ax.grid() # ## Homography Quiz # --- # # # ## Homography Quiz # --- # # 1. Prove that there exists a homogrpahy $H$ that satisfies: # $$p_1 \equiv H p_2 $$ # between the 2D points (in homogeneous coordinates) $p_1$ and $p_2$ in the images of a plane $\Pi$ captured by two $3\times 4$ camera projection matrices $M_1$ and $M_2$, respectively. # The symbol $\equiv$ stands for equality $\textit{up to scale}$. # # (Note: A degenerate case happens when the plan $\Pi$ contains both cameras' centers, in which case there are infinite choices of $H$ satisfying the equation. You can ignore this special case in your answer.) # ## Homography Quiz # --- # # * Plane in 3D using homogeneous coordinates is given by: # $$n^T P = 0$$ # Where $n,P$ are homogeneous vectors (4 numbers each) and $n$ is the normal to the plane. # # * Therefore, we can find a basis of 3 vectors $u_1, u_2, u_3$ in $R^4$, such that each point on the plane is given by: # $$ P = \sum\limits_{i=1}^3 \alpha_i u_i $$ # # * The projection of 3D point $P$ to the $j^{th}$ image point $p_j$ is given by: # $$ p_j = M_j P = \sum\limits_{i=1}^3 \alpha_i M_j u_i $$ # ## Homography Quiz # --- # # * If we denote $v_j^i = M_j u_i$ we get: # $$ p_1 = \sum\limits_{i=1}^3 \alpha_i v_1^i $$ # $$ p_2 = \sum\limits_{i=1}^3 \alpha_i v_2^i $$ # # * Hence, the relation between the two points is a $3 \times 3$ matrix satisfying: # # $$ \begin{bmatrix} | & | & | \\ v_1^1 & v_1^2 & v_1^3 \\ | & | & | \end{bmatrix} = \begin{bmatrix} * & * & * \\ * & * & * \\ * & * & * \end{bmatrix} \begin{bmatrix} | & | & | \\ v_2^1 & v_2^2 & v_2^3 \\ | & | & | \end{bmatrix}$$ # ## Homography Quiz # --- # # * Relation to camera calibration: # * Recall that we have 11 degrees of freedom in $M$. # * If all the calibration points are on a plane, we get at most 6 independent equations out of 3 pts. # * Any 4th point will be a linear combination of the previous 3 on the plane. # * Therefore, in estimating $M$ we can't rely on a single image of the chessboard. # ## Homography Quiz # --- # # 2. Prove that there exists a homography $H$ that satisfies the equation $ p_1 = H p_2 $, given two cameras separated by a pure rotation. That is, for camera 1, $p_1 = K_1 \left[I \lvert 0\right]P$, and for camera 2, $p_2 = K_2 \left[R \lvert 0\right]P$. Note that $K_1$ and $K_2$ are the $3 \times 3$ intrinsic matrices of the cameras and are different. $I$ is $3 \times 3$ identity matrix, $0$ is a $3\times 1$ zero vector and $P$ is a point in 3D space. $R$ is the $3\times 3$ rotation matrix of the camera. # ## Homography Quiz # --- # # * Since the last column is zero, we can see that: # $$ \begin{bmatrix} X \\ Y \\ Z \end{bmatrix} = R^{-1} K_2^{-1} p_2$$ # # * Substituting this in the second equation we get: # $$ p_1 = K_1 R^{-1} K_2^{-1} p_2 $$ # # * Therefore, the resulting homography is given by: # # $$ H = K_1 R^{-1} K_2^{-1}$$ # ## Homography Quiz # --- # # * Take away is 2 cameras differing only in rotation can't triangulate! # # # # * Image Source - Prince. # * Remember where this was useful? # * Panorama stitching! (There we did not care about recovering depth) # ## Homography Quiz # --- # # 3. Suppose that a camera is rotating about its center $C$, keeping the intrinsic parameters $K$ constant. Let $H$ be the homography that maps the view from one camera orientation to the view at a second orientation. Let $\theta$ be the angle of rotation between the two. Show that $H^2$ is the homography corresponding to a rotation of $2\theta$. # ## Homography Quiz # --- # # * We have just shown that for such a scenario: # $$H_{2\rightarrow 1} = K_1 R_{\theta}^{-1} K_2^{-1}$$ # $$H_{1\rightarrow 2} = K_2 R_{\theta} K_1^{-1}$$ # # # * Applying the constraint $K_1 = K_2 \equiv K$ gets us: # $$H_{1\rightarrow 2} = K R_{\theta} K^{-1}$$ # # * Applying $H_{1\rightarrow 2}$ twice gets us: # $$H_{1\rightarrow 2}^2 = K R_{\theta} K^{-1} K R_{\theta} K^{-1} = K R_{\theta} R_{\theta} K^{-1}$$ # # * Since $R_{\theta} R_{\theta} = R_{2\theta}$, we indeed get: # $$H_{1\rightarrow 2}^2 = K R_{2\theta} K^{-1}$$ # # Which is a homography that corresponds to a rotation of $2\theta$. # ## Epipolar Geometry # --- # ### Epipolar Lingo # --- # # # # ### Essential Matrix # --- # # # # ### Essential Matrix vs Homography # --- # # # # ### Essential Matrix - Geometric Interpretation # --- # # # # ### Essential Matrix Properties # --- # # # # ### Fundamental Matrix # --- # # # # ### Fundamental Geometric Interpretation # --- # # # # ### Fundamental Matrix Properties # --- # # # # ### Fundamental Matrix Estimation # --- # * Assume we are given 2D to 2D M matched image points: # $$\left\{p_i, p_i'\right\}_{i=1}^M$$ # # * Each cosspondence should satisfy: # $$p_i^T F p_i' = 0 \leftrightarrow \begin{bmatrix} x_i & y_i & 1\end{bmatrix}^T \begin{bmatrix} f_1 & f_2 & f_3 \\ f_4 & f_5 & f_6 \\ f_7 & f_8 & f_9 \end{bmatrix} \begin{bmatrix} x_i' \\ y_i' \\ 1\end{bmatrix} = 0$$ # # * How to solve? # * The 8-point algorithm $\leftrightarrow$ arrange into homogeneous linear equations and SVD.. # ### Fundamental Matrix Estimation # --- # # # # * How much matches needed to solve? # * How much did we need for Homography? # ### Fundamental Matrix Demo # --- # In[6]: # start by detecting features and matching them with SIFT img1 = cv2.imread('./assets/left.jpg',0) #queryimage # left image img2 = cv2.imread('./assets/right.jpg',0) #trainimage # right image sift = cv2.SIFT_create() # find the keypoints and descriptors with SIFT kp1, des1 = sift.detectAndCompute(img1,None) kp2, des2 = sift.detectAndCompute(img2,None) # FLANN parameters FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params,search_params) matches = flann.knnMatch(des1,des2,k=2) pts1 = [] pts2 = [] # ratio test as per Lowe's paper for i,(m,n) in enumerate(matches): if m.distance < 0.8*n.distance: pts2.append(kp2[m.trainIdx].pt) pts1.append(kp1[m.queryIdx].pt) # In[7]: # estimating the fundamental matrix pts1 = np.int32(pts1) pts2 = np.int32(pts2) F, mask = cv2.findFundamentalMat(pts1,pts2,cv2.FM_LMEDS) # We select only inlier points pts1 = pts1[mask.ravel()==1] pts2 = pts2[mask.ravel()==1] # In[8]: # drawing epilines def drawlines(img1,img2,lines,pts1,pts2): ''' img1 - image on which we draw the epilines for the points in img2 lines - corresponding epilines ''' r,c = img1.shape img1 = cv2.cvtColor(img1,cv2.COLOR_GRAY2BGR) img2 = cv2.cvtColor(img2,cv2.COLOR_GRAY2BGR) for r,pt1,pt2 in zip(lines,pts1,pts2): color = tuple(np.random.randint(0,255,3).tolist()) x0,y0 = map(int, [0, -r[2]/r[1] ]) x1,y1 = map(int, [c, -(r[2]+r[0]*c)/r[1] ]) img1 = cv2.line(img1, (x0,y0), (x1,y1), color,1) img1 = cv2.circle(img1,tuple(pt1),5,color,-1) img2 = cv2.circle(img2,tuple(pt2),5,color,-1) return img1,img2 # In[9]: # Find epilines corresponding to points in right image (second image) and # drawing its lines on left image lines1 = cv2.computeCorrespondEpilines(pts2.reshape(-1,1,2), 2,F) lines1 = lines1.reshape(-1,3) img5,img6 = drawlines(img1,img2,lines1,pts1,pts2) # Find epilines corresponding to points in left image (first image) and # drawing its lines on right image lines2 = cv2.computeCorrespondEpilines(pts1.reshape(-1,1,2), 1,F) lines2 = lines2.reshape(-1,3) img3,img4 = drawlines(img2,img1,lines2,pts2,pts1) plt.figure(figsize=(20,10)) plt.subplot(121) plt.imshow(img5) plt.axis('off') plt.title('Left Image', fontsize=30) plt.subplot(122) plt.imshow(img3) plt.axis('off') plt.title('Right Image', fontsize=30) plt.show() # ### Recommended Videos # --- # #### Warning! # * These videos do not replace the lectures and tutorials. # * Please use these to get a better understanding of the material, and not as an alternative to the written material. # # #### Video By Subject # * Epipolar and Essential matrix - William Hoff # * Fundamental Matrix - William Hoff # * The Fundamental Matrix Song - Daniel Wedge # ## Credits # ---- # * EE 046746 Spring 2020 - Dahlia Urbach # * Slides - Elad Osherov (Technion), Simon Lucey (CMU) # * Multiple View Geometry in Computer Vision - Hartley and Zisserman - Chapter 6 # * Least–squares Solution of Homogeneous Equations - Center for Machine Perception - Tomas Svoboda # * Computer vision: models, learning and inference , Simon J.D. Prince - Chapter 15 # * Computer Vision: Algorithms and Applications - Richard Szeliski - Sections 2.1.5, 6.2. , 7.1, 7.2, 11.1 # # * Icons from Icon8.com - https://icons8.com #