Ed Baker FLS ARCS

Picture of Ed Baker.

Interdisciplinary researcher using sensor networks and acoustics to monitor biodiversity and environments.

GitHub | CV | Media | Social

Latest publications

All publications

Google Scholar

Co-authorship Cloud

Latest blog posts

All blog posts

Talks

All talks

Notes

All notes

Some thoughts on

Rotating a Pi Camera Feed 90° with ws-camerad

If you have mounted a camera sideways — on a doorframe, along a corridor, or inside a narrow enclosure — every application that reads the feed receives a landscape image where you need a portrait one. You could rotate the image in each consumer application individually, but that approach scales poorly when multiple processes need the same corrected feed, and some consumers (e.g. web browsers) offer no rotation controls at all.

This guide walks through using ws-camerad with the v4l2loopback kernel module to rotate the feed once at the source and present the corrected output as a standard V4L2 device. The end result is a virtual camera at /dev/video10 (or any device number you choose) that any application — OpenCV, FFmpeg, OBS, a browser — can open and read as if it were a physical camera, receiving pre-rotated frames with no additional work.

Prerequisites

You will need:

  • A Raspberry Pi - ideally 4 or 5 running Raspberry Pi OS (64-bit recommended)
  • A connected CSI or USB camera
  • ws-camerad installed (build instructions)

Why 90° Rotation Requires Special Handling

Before diving in, it helps to understand why this is not a one-line configuration change in the camera driver. The Raspberry Pi’s ISP (Image Signal Processor) natively supports horizontal and vertical flips, so 0° and 180° rotations are free — the hardware handles them during its normal processing pass. A 90° or 270° rotation, however, is a transpose: rows become columns, and a 1280×960 frame becomes 960×1280. The ISP cannot change the output geometry this way, so the rotation must happen in software after frame capture.

ws-camerad handles this transparently. When you set rotation = 90 in the configuration, the daemon applies a NEON SIMD–optimized rotation to every frame before distributing it to downstream consumers, including the virtual camera output. The rotation happens once per frame regardless of how many consumers are attached.

Step 1: Install the v4l2loopback Kernel Module

The v4l2loopback module creates virtual V4L2 devices. One process writes frames to a device; other processes open it for capture as though it were a physical camera.

sudo apt update
sudo apt install v4l2loopback-dkms v4l2loopback-utils

Step 2: Load the Module and Create Virtual Devices

Load the module with one or more virtual devices. The video_nr parameter controls which /dev/videoN paths are assigned:

sudo modprobe v4l2loopback devices=1 video_nr=10 card_label="Rotated Camera"

This creates /dev/video10. Verify it exists:

v4l2-ctl --list-devices

You should see an entry for “Rotated Camera” pointing to /dev/video10.

To create multiple virtual devices (for example, one at full resolution and one downsampled):

sudo modprobe v4l2loopback devices=2 video_nr=10,11 \
    card_label="Virtual Camera 1,Virtual Camera 2"

Step 3: Configure ws-camerad

Edit the ws-camerad configuration file (typically /etc/ws/camerad/ws-camerad.conf) to enable rotation and virtual camera output:

[camera]
width = 1280
height = 960
framerate = 30
rotation = 90

[virtual_camera.0]
device = /dev/video10
enabled = true

The rotation = 90 setting tells the daemon to rotate every frame 90° clockwise before distribution. The [virtual_camera.0] section tells it to write the rotated frames to /dev/video10.

With a 1280×960 source and 90° rotation, the virtual camera will output frames at 960×1280.

Optional: Multiple Virtual Cameras at Different Resolutions

If a virtual camera’s width and height are set to values smaller than the rotated output, ws-camerad downsamples the YUV420 data before writing. This is useful for providing a lower-resolution feed for a secondary consumer without any additional processing on the consumer side:

[virtual_camera.0]
device = /dev/video10
enabled = true
# Full rotated resolution: 960×1280

[virtual_camera.1]
device = /dev/video11
width = 480
height = 640
enabled = true
# Downsampled to 480×640

Up to 8 virtual camera devices are supported.

Step 4: Start the Daemon

sudo systemctl start ws-camerad

Or run it directly for testing:

ws-camerad -c /etc/ws/camerad/ws-camerad.conf

You should see log output confirming the rotation and virtual camera initialization:

FrameRotator: 1280x960 -> 960x1280 (90°)
Initialized 1 virtual camera output(s)
v4l2loopback output ready: /dev/video10

Step 5: Consume the Rotated Feed

The virtual camera at /dev/video10 is now a standard V4L2 capture device. Any application that can open a webcam can read it:

Preview with FFplay:

ffplay /dev/video10

Record with FFmpeg:

ffmpeg -f v4l2 -i /dev/video10 -c:v libx264 rotated.mp4

Read in OpenCV (Python):

import cv2

cap = cv2.VideoCapture(10)
ret, frame = cap.read()
print(f"Frame shape: {frame.shape}")  # (1280, 960, 3)
cap.release()

Read in OpenCV (C++):

cv::VideoCapture cap(10);
cv::Mat frame;
cap >> frame;
// frame.rows = 1280, frame.cols = 960

No rotation code is needed on the consumer side. The frames arrive already correctly oriented.

Step 6: Make It Persistent Across Reboots

By default, the v4l2loopback module must be reloaded after every reboot. To make it permanent:

echo "v4l2loopback" | sudo tee /etc/modules-load.d/v4l2loopback.conf
echo "options v4l2loopback devices=1 video_nr=10 card_label=\"Rotated Camera\"" \
    | sudo tee /etc/modprobe.d/v4l2loopback.conf

If ws-camerad is installed as a systemd service (default), it will start automatically and begin writing to the virtual camera on boot.