I am attempting to create a camera unit with the below hardware.
Raspberry Pi 5 (8GB)
2x Raspberry Pi Camera Module 3 (Wide) - mounted on a T-Bar with around 40mm spacing and 0 degrees of tilt (optimum spacing and angle to be determined once stitching is functional).
Eventually will add a SSD and an AI Processing Chip
First step for me is to stitch the two video feeds together for which I have put together the below code (with some help from the internet).
The output though is highly unstable, with obvious ghosting of features in the background and any movement is chaotic/blurred/ghosted. It also comes out as a very low framerate (not sure on figure, but it's very jolty and not at all smooth).
Is there a better way to do this? I just want a single panoramic video feed with the two cameras side-by-side to cover the whole pitch.
Raspberry Pi 5 (8GB)
2x Raspberry Pi Camera Module 3 (Wide) - mounted on a T-Bar with around 40mm spacing and 0 degrees of tilt (optimum spacing and angle to be determined once stitching is functional).
Eventually will add a SSD and an AI Processing Chip
First step for me is to stitch the two video feeds together for which I have put together the below code (with some help from the internet).
Code:
import subprocessimport numpy as npimport cv2# Frame size and overlapWIDTH, HEIGHT = 960, 540OVERLAP = 100 # pixels overlap for stitchingdef read_frame(pipe, width, height): """Read one frame from pipe (libcamera-vid YUV420 output).""" # YUV420 size: width * height * 1.5 size = int(width * height * 1.5) raw = pipe.stdout.read(size) if len(raw) < size: return None # Convert YUV420 to BGR for OpenCV yuv = np.frombuffer(raw, dtype=np.uint8).reshape((int(height * 1.5), width)) bgr = cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR_I420) return bgrdef stitch_images(img1, img2, overlap): """Simple horizontal blend stitching with overlap.""" height, width, _ = img1.shape blended_width = width * 2 - overlap blended = np.zeros((height, blended_width, 3), dtype=np.uint8) # Left part from img1 (excluding overlap) blended[:, :width - overlap] = img1[:, :width - overlap] # Right part from img2 (excluding overlap) blended[:, width:] = img2[:, overlap:] # Blend the overlap region for x in range(overlap): alpha = x / overlap blended[:, width - overlap + x] = ( (1 - alpha) * img1[:, width - overlap + x] + alpha * img2[:, x] ).astype(np.uint8) return blendeddef main(): # libcamera-vid command for camera 0 cmd0 = [ "libcamera-vid", "--camera", "0", "--width", str(WIDTH), "--height", str(HEIGHT), "--codec", "yuv420", "--nopreview", "--timeout", "0", # Keep streaming indefinitely "-o", "-" ] # libcamera-vid command for camera 1 cmd1 = [ "libcamera-vid", "--camera", "1", "--width", str(WIDTH), "--height", str(HEIGHT), "--codec", "yuv420", "--nopreview", "--timeout", "0", # Keep streaming indefinitely "-o", "-" ] # Start both libcamera-vid subprocesses pipe0 = subprocess.Popen(cmd0, stdout=subprocess.PIPE) pipe1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE) try: while True: frame0 = read_frame(pipe0, WIDTH, HEIGHT) frame1 = read_frame(pipe1, WIDTH, HEIGHT) if frame0 is None or frame1 is None: print("Frame read failed or stream ended") break stitched = stitch_images(frame0, frame1, OVERLAP) cv2.imshow("Stitched", stitched) if cv2.waitKey(1) & 0xFF == ord("q"): break finally: pipe0.terminate() pipe1.terminate() cv2.destroyAllWindows()if __name__ == "__main__": main()Is there a better way to do this? I just want a single panoramic video feed with the two cameras side-by-side to cover the whole pitch.
Statistics: Posted by longtail — Mon May 19, 2025 12:14 pm