How to grab, save and filter depth images with Intel RealSense in Python

Based on contributions by Mathijs.

Once pyrealsense2 is installed (see How to install Intel RealSense librealsense and pyrealsense2 on a Raspberry Pi 5 (Ubuntu) if you’re on a Raspberry Pi), this how-to shows how to grab a depth frame from an Intel RealSense camera, convert it to a normalized, saveable image, and apply Intel’s own depth filters.

What you need

  • An Intel RealSense camera and pyrealsense2 installed.
  • pip install opencv-python numpy matplotlib pyexiv2
    • pyexiv2 is used to attach metadata to the saved image; on Linux it needs the exiv2 development headers available to build/install (for example sudo apt install libexiv2-dev).

Steps

1. Grab a depth frame and save it

import cv2
import matplotlib.pyplot as plt
import numpy as np
from pyexiv2 import Image
import pyrealsense2 as rs

# Create a pipeline object. This owns the handle to the streaming camera.
pipeline = rs.pipeline()
pipeline.start()

depth = None
i = 0
while not depth:
    i += 1
    frames = pipeline.wait_for_frames()
    depth = frames.get_depth_frame()

print(f"Grabbing frame took this many tries: {i}")

depth_data = depth.as_frame().get_data()
np_image = np.asanyarray(depth_data)  # raw sensor values — do not use this directly for real distances
height, width = np_image.shape
cv2.imwrite("testimg.png", np_image)

# Calculate the real-world distance (in meters) for every pixel
distarray = np.empty([height, width], dtype=np.float64)
for y in range(height):
    for x in range(width):
        distarray[y, x] = depth.get_distance(x, y)

# Don't call pipeline.stop() before all operations on the depth frame are done —
# it will break get_distance().
pipeline.stop()

max_dist = np.max(distarray)
print(f"max_dist = {max_dist}")

# Normalize the distance array to the full 16-bit range
normalize = lambda t: t / max_dist * 65535  # 16-bit int!
vfunc = np.vectorize(normalize)
normalized_distarray = vfunc(distarray)

# Convert to uint16 to drop the decimals
img = normalized_distarray.astype(np.uint16)
plt.imshow(img, cmap="gray")

# Save the image and store max_dist as metadata — you need it later to convert
# pixel values back into real distances.
filename = "normalized.png"
cv2.imwrite(filename, img)

with Image(filename) as img_meta:
    img_meta.modify_xmp({"Xmp.dc.max_dist": f"{max_dist}"})

# Read the metadata back to confirm it was written
with Image(filename) as img_meta:
    data = img_meta.read_xmp()

print(data)

The nested per-pixel loop that fills distarray is simple but slow for larger images, since it calls into the SDK once per pixel from Python. If this becomes a bottleneck, look into pyrealsense2’s bulk/vectorized distance conversion (via the depth frame’s scale) instead of looping pixel by pixel.

2. Apply depth filters

Intel provides a worked example of RealSense depth filtering on GitHub: depth_filters.ipynb. Download it and run it in Jupyter Notebook.

If you want to apply the filters directly to live frames from a RealSense camera (instead of from a file), use this modified version that reads from the camera: depth_filters.ipynb (10.0 KB)

Troubleshooting

  • get_distance() returns wrong or zero values after calling pipeline.stop(): make sure pipeline.stop() is called only after you’re completely done reading distances from the depth frame, not before.
  • Metadata step fails: confirm pyexiv2’s underlying exiv2 library is actually installed on your system, not just the Python package.

Related


Rewritten and consolidated (Sept 2026) from the original student how-to’s: Howto grab, save and filter images with librealsense.