Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import cv2 | |
| import numpy as np | |
| import plotly.express as px | |
| st.set_page_config(layout="wide") | |
| st.title("2D to 3D Reconstruction Demo") | |
| # SIFT detector | |
| sift = cv2.SIFT_create(nfeatures=5000) | |
| def match_and_reconstruct(img1, img2): | |
| gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) | |
| gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) | |
| kp1, des1 = sift.detectAndCompute(gray1, None) | |
| kp2, des2 = sift.detectAndCompute(gray2, None) | |
| if des1 is None or des2 is None: | |
| return None | |
| # Brute Force Matcher | |
| bf = cv2.BFMatcher(cv2.NORM_L2) | |
| matches = bf.knnMatch(des1, des2, k=2) | |
| # Lowe Ratio Test | |
| good = [] | |
| for m, n in matches: | |
| if m.distance < 0.85 * n.distance: | |
| good.append(m) | |
| if len(good) < 12: | |
| return None | |
| pts1 = np.float32([kp1[m.queryIdx].pt for m in good]) | |
| pts2 = np.float32([kp2[m.trainIdx].pt for m in good]) | |
| # ---- Approximate Camera Intrinsics ---- | |
| h, w = gray1.shape | |
| focal = 0.8 * w | |
| K = np.array([ | |
| [focal, 0, w / 2], | |
| [0, focal, h / 2], | |
| [0, 0, 1] | |
| ]) | |
| # Essential Matrix | |
| E, mask = cv2.findEssentialMat( | |
| pts1, | |
| pts2, | |
| K, | |
| method=cv2.RANSAC, | |
| prob=0.999, | |
| threshold=3.0 | |
| ) | |
| if E is None: | |
| return None | |
| # Recover Pose | |
| _, R, t, mask_pose = cv2.recoverPose(E, pts1, pts2, K) | |
| # Projection matrices | |
| P1 = K @ np.hstack((np.eye(3), np.zeros((3, 1)))) | |
| P2 = K @ np.hstack((R, t)) | |
| # Triangulation | |
| pts1_norm = pts1.T | |
| pts2_norm = pts2.T | |
| points_4d = cv2.triangulatePoints(P1, P2, pts1_norm, pts2_norm) | |
| points_3d = points_4d[:3] / points_4d[3] | |
| return points_3d.T | |
| # Upload images | |
| uploaded1 = st.file_uploader("Upload First Image", type=["jpg", "png"]) | |
| uploaded2 = st.file_uploader("Upload Second Image", type=["jpg", "png"]) | |
| if uploaded1 and uploaded2: | |
| file_bytes1 = np.asarray(bytearray(uploaded1.read()), dtype=np.uint8) | |
| file_bytes2 = np.asarray(bytearray(uploaded2.read()), dtype=np.uint8) | |
| img1 = cv2.imdecode(file_bytes1, 1) | |
| img2 = cv2.imdecode(file_bytes2, 1) | |
| st.image(img1, caption="Image 1", use_column_width=True) | |
| st.image(img2, caption="Image 2", use_column_width=True) | |
| with st.spinner("Reconstructing 3D points..."): | |
| points_3d = match_and_reconstruct(img1, img2) | |
| if points_3d is not None: | |
| fig = px.scatter_3d( | |
| x=points_3d[:, 0], | |
| y=points_3d[:, 1], | |
| z=points_3d[:, 2], | |
| ) | |
| fig.update_layout( | |
| margin=dict(l=0, r=0, b=0, t=0) | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| else: | |
| st.error("Not enough good matches found. Try images with more texture and slight viewpoint difference.") | |