From 08f79fef6888b444e5bdc66a9859d1272466e18b Mon Sep 17 00:00:00 2001 From: Bruno Fargnoli Date: Tue, 12 May 2026 17:45:45 +0200 Subject: [PATCH] Improved Projection script --- projection/texture_projection_multiview.py | 63 ++++++++++++++++------ 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/projection/texture_projection_multiview.py b/projection/texture_projection_multiview.py index 788a5d1..3dd2ca8 100644 --- a/projection/texture_projection_multiview.py +++ b/projection/texture_projection_multiview.py @@ -143,7 +143,8 @@ def texture_mesh_with_multiview( max_hole_size: int = 10, use_metallic: bool = True, depth_eps: float = 0.002, - low_poly_mesh: trimesh.Trimesh = None + low_poly_mesh: trimesh.Trimesh = None, + add_alpha_channel: bool = False ): if not (len(images) == len(azimuths) == len(elevations)): raise ValueError("images, azimuths, and elevations must have the same length") @@ -283,7 +284,7 @@ def texture_mesh_with_multiview( # Dilate the foreground mask and inpaint the background band # Scale dilation with image resolution to prevent bilinear bleed - base_res = 1024 + base_res = 2048 scale = max(img_h, img_w) / base_res dilate_px = int(max(5, 5 * scale)) kernel = np.ones((3, 3), np.uint8) @@ -321,7 +322,7 @@ def texture_mesh_with_multiview( cam_depth = cam_depth.permute(2,0,1).unsqueeze(0) # for grid_sample inf_depth = torch.full_like(cam_depth, torch.finfo(cam_depth.dtype).max) cam_depth_occ = torch.where(cam_hit.unsqueeze(0).unsqueeze(0), cam_depth, inf_depth) - cam_depth_occ = -F.max_pool2d(-cam_depth_occ, kernel_size=3, stride=1, padding=1) + cam_depth_occ = -F.max_pool2d(-cam_depth_occ, kernel_size=1, stride=1, padding=0) # Map texels to camera clip space _, _, u_clip, v_clip = project_texels_to_image(tex_pos, right, up, ortho_scale) @@ -363,10 +364,10 @@ def texture_mesh_with_multiview( sampled_hit = F.grid_sample( cam_hit_img, grid_occ, - mode='bilinear', + mode='nearest', padding_mode='zeros', align_corners=False - )[0, 0] > 0.25 + )[0, 0] > 0.10 sampled_depth = F.grid_sample( cam_depth_occ, @@ -460,16 +461,37 @@ def texture_mesh_with_multiview( # color fully. Where it didn't, keep the original texture untouched. # The normal-based weighting already handles per-view confidence during # accumulation, so no additional blending factor is needed here. - blended_rgb = ( - projected_color * confidence3 + - existing_rgb * (1.0 - confidence3) - ) + # blended_rgb = ( + # projected_color * confidence3 + + # existing_rgb * (1.0 - confidence3) + # ) + + # Hard composite: use projected colour only where: + # - the texel lies inside a UV island (uv_hit_mask) + # - the accumulated projection weight is above a tiny fraction of the max weight + conf_threshold = 0.1 + hard_mask = uv_hit_mask & (confidence > conf_threshold) - composite_mask = confidence > 0.01 + # Keep original texture wherever the mask is False + blended_rgb = torch.where(hard_mask.unsqueeze(-1), projected_color, existing_rgb) + + composite_mask = hard_mask # for the reporting print below + + #composite_mask = confidence > 0.01 color_np = (blended_rgb.cpu().numpy() * 255).clip(0, 255).astype(np.uint8) - # Preserve the original alpha everywhere (mesh already has full coverage) - alpha_np = (existing_alpha.squeeze(-1).cpu().numpy() * 255).clip(0, 255).astype(np.uint8) + + # Alpha from projection confidence + conf_np = confidence.cpu().numpy() + + if add_alpha_channel: + uv_mask_np = uv_hit_mask.cpu().numpy() + alpha_np = np.zeros((texture_size, texture_size), dtype=np.uint8) + alpha_np[uv_mask_np] = (conf_np[uv_mask_np] * 255).clip(0, 255).astype(np.uint8) + alpha_np[conf_np <= 0.01] = 0 + else: + # Preserve the original alpha everywhere (mesh already has full coverage) + alpha_np = (existing_alpha.squeeze(-1).cpu().numpy() * 255).clip(0, 255).astype(np.uint8) n_projected = int(composite_mask.sum().item()) print(f" Projected texels: {n_projected} / {texture_size*texture_size}" @@ -488,11 +510,20 @@ def texture_mesh_with_multiview( # Alpha from projection confidence conf_np = confidence.cpu().numpy() - # Slight threshold to remove noise - alpha_mask = conf_np > 0.01 + if add_alpha_channel: + # Use UV mask for proper transparent background + uv_mask_np = uv_hit_mask.cpu().numpy() - alpha_np = (conf_np * 255).clip(0, 255).astype(np.uint8) - alpha_np[~alpha_mask] = 0 + alpha_np = np.zeros((texture_size, texture_size), dtype=np.uint8) + + alpha_np[uv_mask_np] = (conf_np[uv_mask_np] * 255).clip(0, 255).astype(np.uint8) + alpha_np[conf_np <= 0.01] = 0 + else: + # Slight threshold to remove noise + alpha_mask = conf_np > 0.01 + + alpha_np = (conf_np * 255).clip(0, 255).astype(np.uint8) + alpha_np[~alpha_mask] = 0 if fill_holes: print('Filling holes and padding UV seams ...')