From 80792bcbdce7a77862ec3d6f04896d8958d92a76 Mon Sep 17 00:00:00 2001 From: Astatin3 <77305074+Astatin3@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:42:47 -0600 Subject: [PATCH] Attempt to add undistortion --- main.py | 142 +++++++++++++++++++++++++++++++---------------------- output.jpg | Bin 0 -> 24291 bytes 2 files changed, 83 insertions(+), 59 deletions(-) create mode 100644 output.jpg diff --git a/main.py b/main.py index 91b2366..31d2a58 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,69 @@ from freenect2 import Device, FrameType import psutil -import cv2 +from PIL import Image import open3d as o3d import numpy as np -from threading import Thread -from copy import deepcopy +def depth_to_points(depth_map): + height, width = depth_map.shape + x_coords, y_coords = np.meshgrid(np.arange(width), np.arange(height)) + + points = np.stack((x_coords, y_coords, depth_map), axis=-1) + points = points.reshape(-1, 3) + + return points + +def remove_invalid_points(points): + mask = points[:, 2] != 0 + points = points[mask] + + return points, mask + + +def undistort_points(points, params): + fx = params.fx + fy = params.fy + cx = params.cx + cy = params.cy + k1 = params.k1 + k2 = params.k2 + k3 = params.k3 + p1 = params.p1 + p2 = params.p2 + + # Normalize points + x = (points[:, 0] - cx) / fx + y = (points[:, 1] - cy) / fy + + # Compute radial distances + r2 = x ** 2 + y ** 2 + r4 = r2 ** 2 + r6 = r2 ** 3 + + # Compute radial distortion + radial_distortion = (1 + k1 * r2 + k2 * r4 + k3 * r6) + + # Compute tangential distortion + xy = x * y + xy2 = 2 * xy + x2 = x ** 2 + y2 = y ** 2 + tangential_distortion_x = p1 * xy2 + p2 * (r2 + 2 * x2) + tangential_distortion_y = p1 * (r2 + 2 * y2) + p2 * xy2 + + # Undistort points + undistorted_x = (x - tangential_distortion_x) / radial_distortion + undistorted_y = (y - tangential_distortion_y) / radial_distortion + + # Reproject points + undistorted_points = np.zeros_like(points) + undistorted_points[:, 0] = undistorted_x * fx + cx + undistorted_points[:, 1] = undistorted_y * fy + cy + undistorted_points[:, 2] = points[:, 2] # Preserve depth values + + return undistorted_points + voxel_size = 0.005 @@ -17,82 +74,49 @@ reconstruction = o3d.geometry.PointCloud() reconstruction.points = o3d.utility.Vector3dVector(np.random.random((5, 3))) vis.add_geometry(reconstruction) +import numpy as np threads = [] -def parse_frame(depth_frame, color_frame): - - global reconstruction - - - undistorted, registered, big_depth = device.registration.apply(depth_frame, - color_frame, - with_big_depth=True) - - - - - - cv2.imshow("1", np.array(registered.to_image())) - # points = device.registration.get_points_xyz_array(undistorted) - # points = points[~np.isnan(points).any(axis=2)] - # points = points.reshape(-1, 3) / 5 - - # print(points.shape) - - - # if np.isnan(points).any(): - # print("nan") - # return - # - # reconstruction.points = o3d.utility.Vector3dVector(points) - # vis.update_geometry(reconstruction) - - - print("123") - # global threads - # threads.pop(thread_index) running = True device = Device() -depth_frame = None color_frame = None n_cpus = psutil.cpu_count() + with device.running(): for type_, frame in device: - if type_ == FrameType.Depth: - depth_frame = frame - elif type_ == FrameType.Color: - color_frame = frame + if type_ == FrameType.Color: + img = frame.to_image() + img = img.resize((424,512)) + color_frame = np.array(img) + color_frame = color_frame[:, :, :3] + width, height = color_frame.shape[:2] + color_frame = color_frame.reshape(width * height, 3) - if depth_frame is not None and \ + elif type_ == FrameType.Depth and \ color_frame is not None: - # - if len(threads) > 100: - depth_frame = None - color_frame = None - continue - undistorted, registered, big_depth = device.registration.apply(depth_frame, - color_frame, - with_big_depth=True) + depth_map = frame.to_array() + depth_map /= 5 + # ir_image /= ir_image.max() + # ir_image = np.sqrt(ir_image) - cv2.imshow('Grayscale', np.array(registered.to_image())) - # cv2.imshow("1", np.array(registered.to_image())) - # thread = Thread(target=parse_frame, args=(depth_frame, color_frame)) - # threads.append(thread) - # thread.start() - # - # print(len(threads)) - # - # depth_frame = None - # color_frame = None + points = depth_to_points(depth_map) + points, mask = remove_invalid_points(points) + points = undistort_points(points, device.ir_camera_params) + + points /= 512 + + reconstruction.points = o3d.utility.Vector3dVector(points) + # reconstruction.colors = o3d.utility.Vector3dVector(color_frame[mask]) + vis.update_geometry(reconstruction) if not vis.poll_events(): break diff --git a/output.jpg b/output.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1ebab7b4bd42771ca650da1c5aa85cde46159a7 GIT binary patch literal 24291 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