Inserimento di oggetti sensibili alla profondità nei video utilizzando Python |  di Berkan Zorlubas |  Agosto 2023

 | Intelligenza-Artificiale

Arriviamo ora alla componente centrale del progetto: l’elaborazione video. Nel mio repository vengono forniti due script chiave: video_processing_utils.py E depth_aware_object_insertion.py. Come implicito nei loro nomi, video_processing_utils.py ospita tutte le funzioni essenziali per l’inserimento degli oggetti, mentre depth_aware_object_insertion.py funge da script principale che esegue queste funzioni su ciascun fotogramma video all’interno di un ciclo.

Una versione ridotta della sezione principale di depth_aware_object_insertion.py è riportato di seguito. In un ciclo for che esegue un numero pari al conteggio dei fotogrammi nel video di input, carichiamo le informazioni in batch della pipeline di calcolo della profondità da cui otteniamo il fotogramma RGB originale e la relativa stima della profondità. Quindi calcoliamo l’inverso della matrice di posa della fotocamera. Successivamente, inseriamo la mesh, la profondità e gli aspetti intrinseci della fotocamera in una funzione denominata render_mesh_with_depth().

for i in tqdm(range(batch_count)):

batch = np.load(os.path.join(BATCH_DIRECTORY, file_names(i)))

# ... (snipped for brevity)

# transformation of the mesh with the inverse camera extrinsics
frame_transformation = np.vstack(np.split(extrinsics_data(i),4))
inverse_frame_transformation = np.empty((4, 4))
inverse_frame_transformation(:3, :) = np.concatenate((np.linalg.inv(frame_transformation(:3,:3)),
np.expand_dims(-1 * frame_transformation(:3,3),0).T), axi
inverse_frame_transformation(3, :) = (0.00, 0.00, 0.00, 1.00)
mesh.transform(inverse_frame_transformation)

# ... (snipped for brevity)

image = np.transpose(batch('img_1'), (2, 3, 1, 0))(:,:,:,0)
depth = np.transpose(batch('depth'), (2, 3, 1, 0))(:,:,0,0)

# ... (snipped for brevity)

# rendering the color and depth buffer of the transformed mesh in the image domain
mesh_color_buffer, mesh_depth_buffer = render_mesh_with_depth(np.array(mesh.vertices),
np.array(mesh.vertex_colors),
np.array(mesh.triangles),
depth, intrinsics)

# depth-aware overlaying of the mesh and the original image
combined_frame, combined_depth = combine_frames(image, mesh_color_buffer, depth, mesh_depth_buffer)

# ... (snipped for brevity)

IL render_mesh_with_depth La funzione prende una mesh 3D, rappresentata dai suoi vertici, dai colori dei vertici e dai triangoli, e la esegue il rendering su un fotogramma di profondità 2D. La funzione inizia inizializzando i buffer di profondità e colore per contenere l’output renderizzato. Quindi proietta i vertici della mesh 3D sul fotogramma 2D utilizzando i parametri intrinseci della fotocamera. La funzione utilizza il rendering della linea di scansione per scorrere ciascun triangolo nella mesh, rasterizzandolo in pixel sul fotogramma 2D. Durante questo processo, la funzione calcola le coordinate baricentriche per ciascun pixel per interpolare i valori di profondità e colore. Questi valori interpolati vengono quindi utilizzati per aggiornare i buffer di profondità e colore, ma solo se la profondità interpolata del pixel è più vicina alla fotocamera rispetto al valore esistente nel buffer di profondità. Infine, la funzione restituisce i buffer di colore e profondità come output renderizzato, con il buffer di colore convertito in un formato uint8 adatto alla visualizzazione delle immagini.

def render_mesh_with_depth(mesh_vertices, vertex_colors, triangles, depth_frame, intrinsic):
vertex_colors = np.asarray(vertex_colors)

# Initialize depth and color buffers
buffer_width, buffer_height = depth_frame.shape(1), depth_frame.shape(0)
mesh_depth_buffer = np.ones((buffer_height, buffer_width)) * np.inf

# Project 3D vertices to 2D image coordinates
vertices_homogeneous = np.hstack((mesh_vertices, np.ones((mesh_vertices.shape(0), 1))))
camera_coords = vertices_homogeneous.T(:-1,:)
projected_vertices = intrinsic @ camera_coords
projected_vertices /= projected_vertices(2, :)
projected_vertices = projected_vertices(:2, :).T.astype(int)
depths = camera_coords(2, :)

mesh_color_buffer = np.zeros((buffer_height, buffer_width, 3), dtype=np.float32)

# Loop through each triangle to render it
for triangle in triangles:
# Get 2D points and depths for the triangle vertices
points_2d = np.array((projected_vertices(v) for v in triangle))
triangle_depths = (depths(v) for v in triangle)
colors = np.array((vertex_colors(v) for v in triangle))

# Sort the vertices by their y-coordinates for scanline rendering
order = np.argsort(points_2d(:, 1))
points_2d = points_2d(order)
triangle_depths = np.array(triangle_depths)(order)
colors = colors(order)

y_mid = points_2d(1, 1)

for y in range(points_2d(0, 1), points_2d(2, 1) + 1):
if y < 0 or y >= buffer_height:
continue

# Determine start and end x-coordinates for the current scanline
if y < y_mid:
x_start = interpolate_values(y, points_2d(0, 1), points_2d(1, 1), points_2d(0, 0), points_2d(1, 0))
x_end = interpolate_values(y, points_2d(0, 1), points_2d(2, 1), points_2d(0, 0), points_2d(2, 0))
else:
x_start = interpolate_values(y, points_2d(1, 1), points_2d(2, 1), points_2d(1, 0), points_2d(2, 0))
x_end = interpolate_values(y, points_2d(0, 1), points_2d(2, 1), points_2d(0, 0), points_2d(2, 0))

x_start, x_end = int(x_start), int(x_end)

# Loop through each pixel in the scanline
for x in range(x_start, x_end + 1):
if x < 0 or x >= buffer_width:
continue

# Compute barycentric coordinates for the pixel
s, t, u = compute_barycentric_coords(points_2d, x, y)

# Check if the pixel lies inside the triangle
if s >= 0 and t >= 0 and u >= 0:
# Interpolate depth and color for the pixel
depth_interp = s * triangle_depths(0) + t * triangle_depths(1) + u * triangle_depths(2)
color_interp = s * colors(0) + t * colors(1) + u * colors(2)

# Update the pixel if it is closer to the camera
if depth_interp < mesh_depth_buffer(y, x):
mesh_depth_buffer(y, x) = depth_interp
mesh_color_buffer(y, x) = color_interp

# Convert float colors to uint8
mesh_color_buffer = (mesh_color_buffer * 255).astype(np.uint8)

return mesh_color_buffer, mesh_depth_buffer

Vengono quindi inseriti i buffer di colore e profondità della mesh trasformata combine_frames() funzione insieme all’immagine RGB originale e alla relativa mappa di profondità stimata. Questa funzione è progettata per unire due serie di fotogrammi di immagine e profondità. Utilizza le informazioni sulla profondità per decidere quali pixel nel fotogramma originale devono essere sostituiti dai pixel corrispondenti nel fotogramma mesh renderizzato. Nello specifico, per ogni pixel, la funzione controlla se il valore di profondità della mesh renderizzata è inferiore al valore di profondità della scena originale. In tal caso, il pixel viene considerato “più vicino” alla fotocamera nel fotogramma mesh sottoposto a rendering e i valori dei pixel sia nel fotogramma di colore che in quello di profondità vengono sostituiti di conseguenza. La funzione restituisce i fotogrammi di colore e profondità combinati, sovrapponendo efficacemente la mesh renderizzata alla scena originale in base alle informazioni di profondità.

# Combine the original and mesh-rendered frames based on depth information
def combine_frames(original_frame, rendered_mesh_img, original_depth_frame, mesh_depth_buffer):
# Create a mask where the mesh is closer than the original depth
mesh_mask = mesh_depth_buffer < original_depth_frame

# Initialize combined frames
combined_frame = original_frame.copy()
combined_depth = original_depth_frame.copy()

# Update the combined frames with mesh information where the mask is True
combined_frame(mesh_mask) = rendered_mesh_img(mesh_mask)
combined_depth(mesh_mask) = mesh_depth_buffer(mesh_mask)

return combined_frame, combined_depth

Ecco come mesh_color_buffer, mesh_depth_buffer e il combined_frame assomiglia al primo oggetto, un elefante. Poiché l’oggetto elefante non è occluso da nessun altro elemento all’interno dell’inquadratura, rimane completamente visibile. In posizionamenti diversi si verificherebbe l’occlusione.

(Sinistra) Buffer di colore calcolato della mesh dell’elefante | (Giusto) Buffer di profondità calcolato della mesh dell’elefante | (Metter il fondo a) Telaio combinato

Di conseguenza, ho posizionato la seconda rete, l’auto, sul marciapiede della strada. Ho anche regolato il suo orientamento iniziale in modo che sembri parcheggiato lì. Le immagini seguenti sono corrispondenti mesh_color_buffer, mesh_depth_buffer e il combined_frame per questa maglia.

(Sinistra) Buffer colore calcolato della mesh dell’auto | (Giusto) Buffer di profondità calcolato della mesh dell’auto | (Metter il fondo a) Telaio combinato

Di seguito è riportata la visualizzazione della nuvola di punti con entrambi gli oggetti inseriti. Sono stati introdotti più spazi bianchi a causa delle nuove aree di occlusione che hanno creato nuovi oggetti.

Nuvola di punti generata del primo fotogramma con oggetti inseriti

Dopo aver calcolato le immagini sovrapposte per ciascuno dei fotogrammi video, siamo ora pronti per eseguire il rendering del nostro video.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *