[ad_1]
Phần 3 trong hướng dẫn về Gaussian Splatting của chúng tôi, cho thấy cách kết xuất các splat trên hình ảnh 2D
Cuối cùng, chúng ta đến giai đoạn hấp dẫn nhất của quá trình phân tán Gaussian: kết xuất! Bước này có thể được coi là quan trọng nhất, vì nó quyết định tính hiện thực của mô hình của chúng ta. Tuy nhiên, nó cũng có thể là đơn giản nhất. Trong phần 1 Và phần 2 trong loạt bài của chúng tôi, chúng tôi đã trình bày cách chuyển đổi các splat thô thành định dạng sẵn sàng để kết xuất, nhưng bây giờ chúng tôi thực sự phải làm việc và kết xuất trên một tập hợp pixel cố định. Các tác giả đã phát triển một công cụ kết xuất nhanh bằng CUDA, có thể hơi khó để làm theo. Do đó, tôi tin rằng sẽ có lợi khi trước tiên hãy xem qua mã trong Python, sử dụng các vòng lặp for đơn giản để rõ ràng hơn. Đối với những người háo hức tìm hiểu sâu hơn, tất cả các mã cần thiết đều có sẵn trên G của chúng tôiitHub.
Hãy thảo luận về cách hiển thị từng pixel riêng lẻ. Từ trước bài báochúng ta có tất cả các thành phần cần thiết: điểm 2D, màu liên quan, hiệp phương sai, thứ tự độ sâu được sắp xếp, hiệp phương sai nghịch đảo trong 2D, giá trị x và y tối thiểu và tối đa cho mỗi splat và độ mờ liên quan. Với các thành phần này, chúng ta có thể kết xuất bất kỳ pixel nào. Với các tọa độ pixel cụ thể, chúng ta lặp qua tất cả các splat cho đến khi đạt đến ngưỡng bão hòa, theo thứ tự độ sâu của splat so với mặt phẳng digicam (chiếu lên mặt phẳng digicam rồi sắp xếp theo độ sâu). Đối với mỗi splat, trước tiên chúng ta kiểm tra xem tọa độ pixel có nằm trong giới hạn được xác định bởi các giá trị x và y tối thiểu và tối đa hay không. Kiểm tra này xác định xem chúng ta có nên tiếp tục kết xuất hay bỏ qua splat đối với các tọa độ này. Tiếp theo, chúng ta tính toán cường độ splat theo chuẩn Gauss tại tọa độ pixel bằng cách sử dụng giá trị trung bình của splat, hiệp phương sai splat và tọa độ pixel.
def compute_gaussian_weight(
pixel_coord: torch.Tensor, # (1, 2) tensor
point_mean: torch.Tensor,
inverse_covariance: torch.Tensor,
) -> torch.Tensor:distinction = point_mean - pixel_coord
energy = -0.5 * distinction @ inverse_covariance @ distinction.T
return torch.exp(energy).merchandise()
Chúng tôi nhân trọng số này với độ mờ của splat để có được một tham số gọi là alpha. Trước khi thêm giá trị mới này vào pixel, chúng tôi cần kiểm tra xem chúng tôi đã vượt quá ngưỡng bão hòa của mình hay chưa. Chúng tôi không muốn một splat đằng sau các splat khác ảnh hưởng đến màu pixel và sử dụng tài nguyên máy tính nếu pixel đã bão hòa. Do đó, chúng tôi sử dụng một ngưỡng cho phép chúng tôi dừng kết xuất khi vượt quá ngưỡng đó. Trong thực tế, chúng tôi bắt đầu ngưỡng bão hòa của mình ở mức 1 rồi nhân nó với min(0.99, (1 — alpha)) để có được một giá trị mới. Nếu giá trị này nhỏ hơn ngưỡng của chúng tôi (0.0001), chúng tôi dừng kết xuất pixel đó và coi như hoàn tất. Nếu không, chúng tôi thêm các màu được cân bằng theo giá trị bão hòa * (1 — alpha) và cập nhật độ bão hòa thành new_saturation = old_saturation * (1 — alpha). Cuối cùng, chúng tôi lặp qua mọi pixel (hoặc mọi ô 16×16 trong thực tế) và kết xuất. Mã hoàn chỉnh được hiển thị bên dưới.
def render_pixel(
self,
pixel_coords: torch.Tensor,
points_in_tile_mean: torch.Tensor,
colours: torch.Tensor,
opacities: torch.Tensor,
inverse_covariance: torch.Tensor,
min_weight: float = 0.000001,
) -> torch.Tensor:
total_weight = torch.ones(1).to(points_in_tile_mean.machine)
pixel_color = torch.zeros((1, 1, 3)).to(points_in_tile_mean.machine)
for point_idx in vary(points_in_tile_mean.form(0)):
level = points_in_tile_mean(point_idx, :).view(1, 2)
weight = compute_gaussian_weight(
pixel_coord=pixel_coords,
point_mean=level,
inverse_covariance=inverse_covariance(point_idx),
)
alpha = weight * torch.sigmoid(opacities(point_idx))
test_weight = total_weight * (1 - alpha)
if test_weight < min_weight:
return pixel_color
pixel_color += total_weight * alpha * colours(point_idx)
total_weight = test_weight
# in case we by no means attain saturation
return pixel_color
Bây giờ chúng ta có thể kết xuất một pixel nên chúng ta có thể kết xuất một mảng hình ảnh hoặc thứ mà tác giả gọi là một ô!
def render_tile(
self,
x_min: int,
y_min: int,
points_in_tile_mean: torch.Tensor,
colours: torch.Tensor,
opacities: torch.Tensor,
inverse_covariance: torch.Tensor,
tile_size: int = 16,
) -> torch.Tensor:
"""Factors in tile needs to be organized so as of depth"""tile = torch.zeros((tile_size, tile_size, 3))
# iterate by tiles for extra environment friendly processing
for pixel_x in vary(x_min, x_min + tile_size):
for pixel_y in vary(y_min, y_min + tile_size):
tile(pixel_x % tile_size, pixel_y % tile_size) = self.render_pixel(
pixel_coords=torch.Tensor((pixel_x, pixel_y))
.view(1, 2)
.to(points_in_tile_mean.machine),
points_in_tile_mean=points_in_tile_mean,
colours=colours,
opacities=opacities,
inverse_covariance=inverse_covariance,
)
return tile
Và cuối cùng chúng ta có thể sử dụng tất cả các ô đó để hiển thị toàn bộ hình ảnh. Lưu ý cách chúng ta kiểm tra để đảm bảo splat thực sự ảnh hưởng đến ô hiện tại (mã x_in_tile và y_in_tile).
def render_image(self, image_idx: int, tile_size: int = 16) -> torch.Tensor:
"""For every tile need to examine if the purpose is within the tile"""
preprocessed_scene = self.preprocess(image_idx)
top = self.photographs(image_idx).top
width = self.photographs(image_idx).widthpicture = torch.zeros((width, top, 3))
for x_min in tqdm(vary(0, width, tile_size)):
x_in_tile = (x_min >= preprocessed_scene.min_x) & (
x_min + tile_size <= preprocessed_scene.max_x
)
if x_in_tile.sum() == 0:
proceed
for y_min in vary(0, top, tile_size):
y_in_tile = (y_min >= preprocessed_scene.min_y) & (
y_min + tile_size <= preprocessed_scene.max_y
)
points_in_tile = x_in_tile & y_in_tile
if points_in_tile.sum() == 0:
proceed
points_in_tile_mean = preprocessed_scene.factors(points_in_tile)
colors_in_tile = preprocessed_scene.colours(points_in_tile)
opacities_in_tile = preprocessed_scene.sigmoid_opacity(points_in_tile)
inverse_covariance_in_tile = preprocessed_scene.inverse_covariance_2d(
points_in_tile
)
picture(x_min : x_min + tile_size, y_min : y_min + tile_size) = (
self.render_tile(
x_min=x_min,
y_min=y_min,
points_in_tile_mean=points_in_tile_mean,
colours=colors_in_tile,
opacities=opacities_in_tile,
inverse_covariance=inverse_covariance_in_tile,
tile_size=tile_size,
)
)
return picture
Cuối cùng thì chúng ta đã có tất cả các thành phần cần thiết, chúng ta có thể dựng hình ảnh. Chúng ta lấy tất cả các điểm 3D từ tập dữ liệu treehill và khởi tạo chúng dưới dạng các splat Gaussian. Để tránh tìm kiếm lân cận gần tốn kém, chúng ta khởi tạo tất cả các biến tỷ lệ là .01 (Lưu ý rằng với phương sai nhỏ như vậy, chúng ta sẽ cần một lượng lớn splat tập trung tại một điểm để có thể nhìn thấy. Phương sai lớn hơn khiến quá trình này khá chậm.). Sau đó, tất cả những gì chúng ta phải làm là gọi render_image với số hình ảnh mà chúng ta đang cố gắng mô phỏng và như bạn có thể thấy, chúng ta sẽ có được một tập hợp thưa thớt các đám mây điểm giống với hình ảnh của chúng ta! (Hãy xem phần thưởng của chúng tôi ở cuối để biết về hạt nhân CUDA tương đương sử dụng công cụ tiện lợi của pyTorch để biên dịch mã CUDA!)
Mặc dù đường truyền ngược không phải là một phần của hướng dẫn này, nhưng cần lưu ý rằng mặc dù chúng ta chỉ bắt đầu với một vài điểm này, nhưng chúng ta sẽ sớm có hàng trăm nghìn splat cho hầu hết các cảnh. Điều này là do việc chia nhỏ các splat lớn (được xác định bởi phương sai lớn hơn trên các trục) thành các splat nhỏ hơn và loại bỏ các splat có độ mờ cực thấp. Ví dụ, nếu chúng ta thực sự khởi tạo tỷ lệ theo giá trị trung bình của ba người hàng xóm gần nhất, chúng ta sẽ có phần lớn không gian được bao phủ. Để có được chi tiết tốt, chúng ta sẽ cần chia nhỏ chúng thành các splat nhỏ hơn nhiều có thể nắm bắt được chi tiết tốt. Chúng cũng cần lấp đầy các khu vực có rất ít gaussian. Chúng gọi hai kịch bản này là tái tạo quá mức và tái tạo dưới mức và xác định cả hai kịch bản bằng các giá trị gradient lớn cho các splat khác nhau. Sau đó, chúng chia nhỏ hoặc sao chép các splat tùy thuộc vào kích thước (xem hình ảnh bên dưới) và tiếp tục quá trình tối ưu hóa.
Mặc dù hướng dẫn này không đề cập đến đường truyền ngược, nhưng điều quan trọng cần lưu ý là chúng ta chỉ bắt đầu với một vài điểm nhưng sẽ sớm có hàng trăm nghìn splat trong hầu hết các cảnh. Sự gia tăng này là do việc chia các splat lớn (có phương sai lớn hơn trên các trục) thành các splat nhỏ hơn và loại bỏ các splat có độ mờ rất thấp. Ví dụ, nếu ban đầu chúng ta đặt tỷ lệ thành giá trị trung bình của ba người hàng xóm gần nhất, thì hầu hết không gian sẽ được bao phủ. Để đạt được chi tiết tốt, chúng ta cần chia các splat lớn này thành các splat nhỏ hơn nhiều. Ngoài ra, cần phải điền vào các khu vực có rất ít Gaussian. Các tình huống này được gọi là tái tạo quá mức và tái tạo dưới mức, được đặc trưng bởi các giá trị gradient lớn cho các splat khác nhau. Tùy thuộc vào kích thước của chúng, các splat được chia nhỏ hoặc sao chép (xem hình ảnh bên dưới) và quá trình tối ưu hóa tiếp tục.
Và đó là phần giới thiệu dễ hiểu về Gaussian Splatting! Bây giờ bạn sẽ có trực giác tốt về chính xác những gì đang diễn ra trong quá trình chuyển tiếp của một cảnh dựng hình Gaussian. Mặc dù hơi khó khăn và không hẳn là mạng nơ-ron, nhưng chỉ cần một chút đại số tuyến tính là chúng ta có thể dựng hình học 3D thành 2D!
Hãy thoải mái để lại bình luận về những chủ đề khó hiểu hoặc nếu tôi hiểu sai điều gì đó và bạn luôn có thể kết nối với tôi trên LinkedIn hoặc Twitter!
[ad_2]
Source link