I am trying to implement 3d reconstruction using opencv however the triangulation result is very weird. I am not able to understand where I am going wrong with this.

Here is my code.
ThreeDReconstruct.py
import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
import glob
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
class ThreeDReconstruct:
def __init__(self) -> None:
self.matches_all=[]
self.keypoints_and_descriptors=[]
self.calib_images=[]
def featureDetection(self):
images=self.calib_images
for i in tqdm(range(len(images)),desc="Features and Descriptors"):
img=images[i]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.SIFT_create()
keypoints = sift.detect(gray, None)
keypoints = sorted(keypoints, key=lambda x: -x.response)[:1000]
keypoints, descriptors = sift.compute(gray, keypoints)
self.keypoints_and_descriptors.append([keypoints,descriptors])
# Matching
# https://stackoverflow.com/questions/42397009/what-values-does-the-algorithm-parametre-take-in-opencvs-flannbasedmatcher-cons
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=4)
search_params = dict(checks=2)
flann = cv2.FlannBasedMatcher(index_params, search_params)
for i in tqdm(range(len(self.keypoints_and_descriptors) - 1),desc="Matching"):
matches = flann.knnMatch(self.keypoints_and_descriptors[i][-1], self.keypoints_and_descriptors[i + 1][-1], k=2)
# Apply ratio test as per Lowe's paper
# https://blog.finxter.com/5-effective-ways-to-implement-flann-based-feature-matching-in-opencv-python/
goodMatches = []
for (m, n) in matches:
if m.distance < 0.7 * n.distance:
goodMatches.append(matches)
self.matches_all.append(goodMatches)
def calibrateCamera(self,images):
# 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*9,3), np.float32)
objp[:,:2] = np.mgrid[0:6,0:9].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.
for i in tqdm(range(len(images)),desc="Camera Calibration"):
img = images[i]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Find the chess board corners
ret, corners = cv2.findChessboardCorners(gray, (6,9), flags=cv2.CALIB_USE_INTRINSIC_GUESS)
# 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(corners2)
self.calib_images.append(img)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
return ret, mtx, dist, rvecs, tvecs
def save_ply_file(self,filename, vertices):
header = """ply
format ascii 1.0
element vertex {}
property float x
property float y
property float z
end_header
""".format(len(vertices))
with open(filename, 'w') as f:
f.write(header)
for vertex in vertices:
f.write('{} {} {}\n'.format(vertex[0], vertex[1], vertex[2]))
example_usage.py
import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
import glob
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from ThreeDReconstruct import ThreeDReconstruct
from tqdm.auto import tqdm
import open3d as o3d
directory = "../images/"
image_files = sorted(glob.glob(directory + "*.jpg"))
images=[]
for image_file in image_files:
image = cv2.imread(image_file)
if image is not None:
images.append(image)
else:
print(f"Unable to read image file: {image_file}")
if len(images)>50:
break
reconstrucClass = ThreeDReconstruct()
x_coords=[]
y_coords=[]
z_coords=[]
ret, mtx, dist, rvecs, tvecs=reconstrucClass.calibrateCamera(images)
reconstrucClass.featureDetection()
for i in tqdm(range(len(reconstrucClass.keypoints_and_descriptors)-1),desc="Triangulation"):
P1 = np.hstack((cv2.Rodrigues(rvecs[i])[0], tvecs[i]))
P2 = np.hstack((cv2.Rodrigues(rvecs[i+1])[0], tvecs[i+1]))
src_pts = np.float32(reconstrucClass.keypoints_and_descriptors[i][-1]).reshape(-1, 1, 2)
dst_pts = np.float32(reconstrucClass.keypoints_and_descriptors[i+1][-1]).reshape(-1, 1, 2)
points_4d=cv2.triangulatePoints(P1,P2,src_pts,dst_pts)
# Convert homogeneous coordinates to 3D coordinates
points_3d=cv2.convertPointsFromHomogeneous(points_4d.T)
x_coords.extend(points_3d[:, 0, 0])
y_coords.extend(points_3d[:, 0, 1])
z_coords.extend(points_3d[:, 0, 2])
print(len(x_coords),len(y_coords),len(z_coords))
# Create 3D plot
reconstrucClass.save_ply_file("wine.ply",list(zip(x_coords,y_coords,z_coords)))
# Create a point cloud
point_cloud = o3d.geometry.PointCloud()
point_cloud.points = o3d.utility.Vector3dVector(zip(x_coords, y_coords, z_coords))
# Create a mesh from the point cloud
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(
point_cloud, alpha=1.0
)
# Compute normals
mesh.compute_vertex_normals()
Save the mesh as an STL file
o3d.io.write_triangle_mesh("output_mesh.stl", mesh)
Here is an example of input image
I tried implementing the functions but I am surely doing something wrong. I have used chess board image for camera calibration in each image to ensure i get correct matrix for each image.
