-
Notifications
You must be signed in to change notification settings - Fork 0
Implement Camera Intrinsics & compute ray direction in camera frame #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4a186e1
8167ce2
b59aaf2
bf69733
f4982ed
cc09b1b
d26d491
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| #include <cstdint> | ||
| #include <cuda/std/ranges> | ||
| #include <cuda_runtime.h> | ||
|
|
||
| #include "camera.cuh" | ||
| #include "geometry.cuh" | ||
| #include "utils.cuh" | ||
|
|
||
| CUDA_CALLABLE Vec3D Intrinsics::get_ray_direction(uint32_t px, uint32_t py) const { | ||
| auto x = (static_cast<float>(px) - cx) / fx; | ||
| auto y = (static_cast<float>(py) - cy) / fy; | ||
| return Vec3D{x, y, -1.0f}; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,36 @@ | ||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
| #include <cuda_runtime.h> | ||
|
|
||
| #include "geometry.cuh" | ||
| #include "utils.cuh" | ||
|
|
||
| struct Intrinsics { | ||
| uint32_t height; | ||
| uint32_t width; | ||
| uint32_t height; // in x direction | ||
| uint32_t width; // in y direction | ||
| float fx; | ||
| float fy; | ||
| float cx; | ||
| float cy; | ||
| float near; | ||
| float far; | ||
|
Comment on lines
-12
to
-13
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These variables are not used in the current implementation, though we can always add them back later if needed |
||
|
|
||
| // Returns the direction of the ray going through pixel (px, py) in camera frame. | ||
| // For efficiency, this function does not check if the pixel is within bounds. | ||
| CUDA_CALLABLE Vec3D get_ray_direction(uint32_t px, uint32_t py) const; | ||
|
|
||
| // Returns a 2D array of ray directions in camera frame in the specified pixel range | ||
| // and store them in the provided buffer. By default, the full image is used | ||
| template <MemoryLocation location> | ||
| CUDA_CALLABLE Array2D<Vec3D, location>& get_ray_directions(Array2D<Vec3D, location> buffer, | ||
| uint32_t px_start = 0, | ||
| uint32_t px_end = UINT32_MAX, | ||
| uint32_t py_start = 0, | ||
| uint32_t py_end = UINT32_MAX) const { | ||
| for (auto i = max(0, px_start); i < min(height, px_end); ++i) { | ||
| for (auto j = max(0, py_start); j < min(width, py_end); ++j) { | ||
| buffer[i][j] = get_ray_direction(j, i); | ||
| } | ||
| } | ||
| return buffer; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -141,3 +141,12 @@ def compute_normals(camera_rays, depth_py_px, eps=1e-20): | |
| norms = nan_ddiff / (eps + jnp.linalg.norm(nan_ddiff, axis=1, keepdims=True)) | ||
|
|
||
| return norms | ||
|
|
||
|
|
||
| def get_camera_rays( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, I see that you took their code and made it into a GT function here |
||
| fx: float, fy: float, cx: float, cy: float, pixel_list: np.ndarray[int] | ||
| ) -> np.ndarray[np.float32]: | ||
| K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float32) | ||
| camera_rays = (pixel_list - K[:, 2]) / np.diag(K) | ||
| camera_rays[:, -1] = -1 | ||
| return camera_rays | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| #include <cmath> | ||
| #include <cstdint> | ||
| #include <cuda_runtime.h> | ||
| #include <gtest/gtest.h> | ||
| #include <thrust/device_vector.h> | ||
| #include <thrust/host_vector.h> | ||
|
|
||
| #include "core/camera.cuh" | ||
| #include "core/geometry.cuh" | ||
| #include "core/utils.cuh" | ||
|
|
||
| namespace test_camera_gpu { | ||
|
|
||
| // CUDA kernel to call get_ray_directions on device with multiple threads | ||
| // Each thread processes one row of the image | ||
| __global__ void get_ray_directions_kernel(Intrinsics intrinsics, | ||
| Array2D<Vec3D, MemoryLocation::DEVICE> ray_buffer) { | ||
| uint32_t row_start = threadIdx.x * 2; | ||
| uint32_t row_end = max(row_start + 2, intrinsics.height); | ||
| uint32_t col_start = threadIdx.y * 2; | ||
| uint32_t col_end = max(col_start + 2, intrinsics.width); | ||
| intrinsics.get_ray_directions(ray_buffer, row_start, row_end, col_start, col_end); | ||
| } | ||
|
|
||
| } // namespace test_camera_gpu | ||
|
|
||
| // Test get_ray_directions on GPU (device) | ||
| TEST(CameraTest, GetRayDirectionsDevice) { | ||
| // Create a small camera intrinsics | ||
| Intrinsics intrinsics{4, 6, 100.0f, 100.0f, 3.0f, 2.0f}; | ||
|
|
||
| // Create Array2D buffer on device | ||
| thrust::device_vector<Vec3D> data(intrinsics.height * intrinsics.width); | ||
| Array2D<Vec3D, MemoryLocation::DEVICE> ray_buffer(data.data(), intrinsics.height, | ||
| intrinsics.width); | ||
|
|
||
| // Launch kernel with multiple threads -- divide into 2x2 tiles | ||
| test_camera_gpu:: | ||
| get_ray_directions_kernel<<<1, dim3(intrinsics.height / 2, intrinsics.width / 2)>>>( | ||
| intrinsics, ray_buffer); | ||
| CUDA_CHECK(cudaGetLastError()); | ||
| CUDA_CHECK(cudaDeviceSynchronize()); | ||
|
|
||
| // Copy data back to host for sanity check | ||
| thrust::host_vector<Vec3D> ray_data = data; | ||
|
|
||
| // Sanity check: adjacent rays should be different | ||
| constexpr float eps = 1e-6f; | ||
| for (auto i = 0; i < data.size() - 1; ++i) { | ||
| auto diff = ray_data[i + 1] - ray_data[i]; | ||
| float diff_mag = sqrtf(dot(diff, diff)); | ||
| EXPECT_GT(diff_mag, eps) << "Adjacent rays are too similar at index " << i; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,7 +75,6 @@ static std::vector<ConfidenceCase> confidence_cases() { | |
|
|
||
| TEST(GpuConfidenceTest, ConfidenceMultipleValuesGPU_AllTypes) { | ||
| using test_float = float; | ||
| constexpr float rtol = 1e-6F; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My compiler has been complaining about this unused variable for a while lol |
||
|
|
||
| auto sizes = confidence_test_sizes(); | ||
| std::mt19937 master_gen(MASTER_SEED); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import numpy as np | ||
| import pytest | ||
|
|
||
| from genmetaballs.core import Intrinsics | ||
| from genmetaballs.fmb.utils import get_camera_rays | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def rng() -> np.random.Generator: | ||
| return np.random.default_rng(0) | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def intrinsics() -> Intrinsics: | ||
| return Intrinsics(height=480, width=640, fx=500.0, fy=520.0, cx=320.0, cy=240.0) | ||
|
|
||
|
|
||
| def test_get_ray_direction_in_camera_frame(intrinsics: Intrinsics): | ||
| # selet a few random pixels to test | ||
| pixel_list = np.array( | ||
| [ | ||
| [0, 0], | ||
| [intrinsics.width - 1, 0], | ||
| [0, intrinsics.height - 1], | ||
| [intrinsics.width - 1, intrinsics.height - 1], | ||
| [intrinsics.width // 2, intrinsics.height // 2], | ||
| ] | ||
| ) | ||
|
|
||
| reference_rays = get_camera_rays( | ||
| intrinsics.fx, | ||
| intrinsics.fy, | ||
| intrinsics.cx, | ||
| intrinsics.cy, | ||
| np.concatenate([pixel_list, np.zeros((pixel_list.shape[0], 1))], axis=1), | ||
| ) | ||
|
|
||
| for (pixel_x, pixel_y), reference_ray in zip(pixel_list, reference_rays, strict=True): | ||
| ray_direction = intrinsics.get_ray_direction(pixel_x, pixel_y) | ||
| assert np.allclose( | ||
| [ray_direction.x, ray_direction.y, ray_direction.z], | ||
| reference_ray, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this (px, py) convention? is this the same as (i,j)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it does not matter, as long as we know that camera looks at bottom left as origin and Y is upwards while X is rightwards, it should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup,
(px, py)is essentially the 2D coordinate in the pixel space