Get the camera working with the Raspberry Pi libcamera fork; add face detection

Stock libcamera 0.2.0 (Ubuntu) and ROS Jazzy's 0.7.2 both detect the OV5647 but
crash in the Pi IPA when capturing, for every stream format and size. The
Raspberry Pi fork, built into /opt/libcamera-rpi, captures 1296x972 frames.
capture_still.py takes a still with it, and detect_faces.py runs OpenCV YuNet
(Haar fallback) on images. YuNet needs OpenCV 4.8+ and input sides in multiples
of 32, so the venv carries opencv-python-headless 4.10 pinned with numpy<2.

camera_ros still has to be built against the fork.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
This commit is contained in:
2026-10-04 21:32:32 -07:00
co-authored by Claude Sonnet 5.5
parent 2535344f3d
commit 5e48b6fa03
3 changed files with 159 additions and 1 deletions
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Capture one still from the camera with the Raspberry Pi libcamera fork, as a JPEG.
capture_still.py OUT.jpg [--frames N] [--width W --height H]
Runs `cam` from /opt/libcamera-rpi for N frames (so auto-exposure settles) and keeps the last.
Run it with the venv's python so OpenCV is the newer one: ~/venv/bin/python capture_still.py ...
"""
import argparse
import glob
import os
import subprocess
import tempfile
import cv2
import numpy as np
PREFIX = '/opt/libcamera-rpi'
def main():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument('out')
p.add_argument('--frames', type=int, default=45)
p.add_argument('--width', type=int, default=1296)
p.add_argument('--height', type=int, default=972)
a = p.parse_args()
env = dict(os.environ, LD_LIBRARY_PATH=f'{PREFIX}/lib/aarch64-linux-gnu')
with tempfile.TemporaryDirectory() as tmp:
subprocess.run([f'{PREFIX}/bin/cam', '-c', '1', '--stream',
f'pixelformat=RGB888,width={a.width},height={a.height}',
f'--capture={a.frames}', f'--file={tmp}/f-#.bin'],
env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=90)
last = sorted(glob.glob(f'{tmp}/f-*.bin'))[-1]
raw = np.fromfile(last, np.uint8)
stride = raw.size // a.height # rows are padded
img = raw.reshape(a.height, stride)[:, :a.width * 3].reshape(a.height, a.width, 3) # B,G,R in memory
cv2.imwrite(a.out, img)
print(f'{a.out}: {a.width}x{a.height}, mean brightness {img.mean():.1f}/255')
if __name__ == '__main__':
main()