A lightweight TCP-based camera streaming framework for transmitting video from a Raspberry Pi 5 (Pi Camera or USB Webcam) to a laptop for real-time computer vision applications.
The Raspberry Pi is responsible only for image acquisition and transmission. All image processing, object detection, tracking, visual servoing, and control algorithms are executed on the laptop.
Camera_streaming_rpi/
├── camera.py
├── capture_rpi.py
├── README.md
└── stream.py
Camera abstraction layer.
Supported camera sources:
- Raspberry Pi Camera Module (Picamera2)
- USB Webcam
The class automatically selects the available camera source and provides a unified OpenCV-compatible interface.
Streaming server running on the Raspberry Pi.
Responsibilities:
- Acquire frames from the camera
- JPEG compress frames
- Stream frames through a TCP socket
Streaming client running on the laptop.
Responsibilities:
- Wait for Raspberry Pi connectivity
- Launch
stream.pyremotely through SSH - Connect to the streaming server
- Receive and decode image frames
- Display the live video stream
Raspberry Pi Camera / USB Webcam
│
▼
camera.py
│
▼
stream.py
│
TCP Socket
│
Ethernet Link
│
▼
capture_rpi.py
│
▼
OpenCV / Tracking /
Detection Pipeline
The Raspberry Pi performs only image acquisition and transmission.
All image processing is performed on the laptop.
- Raspberry Pi 5
- Raspberry Pi OS or Ubuntu 24.04
- Raspberry Pi Camera Module or USB Webcam
- Linux system
- Python 3
- OpenCV
- NumPy
Clone the repository on both systems:
git clone <repository-url>Install Python dependencies:
pip install opencv-python numpyFor Raspberry Pi Camera support:
pip install picamera2This project have two network configuration
- Over the ethernet streaming
- Wireless using Rpi as Access point
This project assumes a direct Ethernet connection between the Raspberry Pi and the laptop.
Laptop <------ Ethernet ------> Raspberry Pi 5
The following IP addresses are used throughout the project:
Laptop IP : 192.168.10.1
Raspberry Pi IP: 192.168.10.2
Subnet Mask : 255.255.255.0
Port : 9999
This project uses a static IPv4 address on the Raspberry Pi Ethernet interface.
Before configuring a static address, verify that there are no conflicting network configurations generated by cloud-init or NetworkManager.
List all Netplan configuration files:
ls -l /etc/netplan/Display all Netplan configuration contents:
sudo grep -R . /etc/netplan/Ubuntu Desktop installations may contain multiple Netplan files generated by:
- NetworkManager
- cloud-init
- Desktop networking tools
Conflicting configurations can cause:
- SSH sessions to freeze
- Ethernet disconnects
- NetworkManager reconnect loops
- Loss of communication between laptop and Raspberry Pi
If a file such as:
/etc/netplan/50-cloud-init.yaml
contains an Ethernet configuration for eth0, disable it:
sudo mv /etc/netplan/50-cloud-init.yaml \
/etc/netplan/50-cloud-init.yaml.bakPrevent cloud-init from recreating network configurations:
sudo mkdir -p /etc/cloud/cloud.cfg.d
echo "network: {config: disabled}" | \
sudo tee /etc/cloud/cloud.cfg.d/99-disable-network-config.cfgCreate a dedicated Netplan configuration:
sudo nano /etc/netplan/10-eth0-static.yamlContents:
network:
version: 2
renderer: NetworkManager
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses:
- 192.168.10.2/24Apply the configuration:
sudo netplan generate
sudo netplan applyVerify:
ip addr show eth0Expected:
inet 192.168.10.2/24
Display the merged Netplan configuration:
sudo netplan getEnsure that eth0 appears only once and contains:
eth0:
dhcp4: false
dhcp6: false
addresses:
- 192.168.10.2/24Do not mix:
dhcp4: trueand
dhcp4: falsefor the same Ethernet interface.
Identify the Ethernet interface:
ip linkTypical Ethernet interface names:
eno1
enp3s0
enp88s0
eth0
Assign a static IPv4 address:
sudo ip addr add 192.168.10.1/24 dev <ethernet_interface>Example:
sudo ip addr add 192.168.10.1/24 dev enp88s0Verify:
ip addr show <ethernet_interface>Expected:
inet 192.168.10.1/24
From the laptop:
ping 192.168.10.2From the Raspberry Pi:
ping 192.168.10.1Successful communication confirms that the Ethernet link is correctly configured.
If SSH becomes unresponsive, verify that the Raspberry Pi is still reachable:
ping 192.168.10.2If the ping fails, inspect NetworkManager logs:
journalctl -u NetworkManager -bLook for messages similar to:
dhcp4 (eth0): canceled DHCP transaction
policy: auto-activating connection
These messages usually indicate conflicting Ethernet configurations.
Display the active address:
ip addr show eth0Display the merged Netplan configuration:
sudo netplan getSearch for conflicting DHCP settings:
sudo grep -R dhcp4 /etc/netplan/For a static Ethernet configuration, only:
dhcp4: falseshould be present for eth0.
Verify that only a single Netplan configuration manages eth0.
Check:
sudo grep -R . /etc/netplan/If both of the following exist for eth0:
dhcp4: trueand
dhcp4: falseremove the conflicting configuration and reapply Netplan:
sudo netplan generate
sudo netplan applyVerify SSH access from the laptop:
ssh <username>@192.168.10.2Example:
ssh aryan@192.168.10.2Enable passwordless SSH:
ssh-copy-id aryan@192.168.10.2This allows capture_rpi.py to automatically launch the streaming server.
This document describes how to configure a Raspberry Pi 5 running Ubuntu 24.04 LTS as:
- A permanent WiFi Access Point (
DroneNet) - A camera streaming server
- A robotics companion computer
while simultaneously receiving internet access from a laptop through Ethernet.
Final architecture:
Internet
│
CU-Wireless
│
Laptop
│
Ethernet
192.168.10.1
│
▼
Raspberry Pi 5
192.168.10.3
┌───────────┐
│ eth0 │
└─────┬─────┘
│
Internet Access
┌───────────┐
│ wlan0 │
└─────┬─────┘
│
DroneNet AP
192.168.4.1
│
Camera Streaming / SSH / ROS2 / PX4
On the Raspberry Pi:
iw list | grep -A 15 "Supported interface modes"Verify that:
* AP
appears in the output.
Create a new NetworkManager connection:
sudo nmcli connection add \
type wifi \
ifname wlan0 \
con-name DroneAP \
autoconnect yes \
ssid DroneNetConfigure the connection as an Access Point:
sudo nmcli connection modify DroneAP \
802-11-wireless.mode ap \
802-11-wireless.band aThe a band enables 5 GHz operation.
Choose a password with at least 8 characters.
Example:
sudo nmcli connection modify DroneAP \
wifi-sec.key-mgmt wpa-psk \
wifi-sec.psk "YourPasswordHere"Assign a fixed address to the AP:
sudo nmcli connection modify DroneAP \
ipv4.method shared \
ipv4.addresses 192.168.4.1/24Prevent the Pi from automatically joining other WiFi networks.
Example:
sudo nmcli connection modify CU-Wireless connection.autoconnect no
sudo nmcli connection modify "CU-Wireless 1" connection.autoconnect nosudo nmcli connection up DroneAPVerify:
ip addr show wlan0Expected:
inet 192.168.4.1/24
Reboot:
sudo rebootVerify that:
DroneNet
appears automatically without running any commands.
Edit:
sudo nano /etc/netplan/01-network-manager-all.yamlConfiguration:
network:
version: 2
renderer: NetworkManager
ethernets:
eth0:
dhcp4: false
addresses:
- 192.168.10.3/24
routes:
- to: default
via: 192.168.10.1
nameservers:
addresses:
- 8.8.8.8
- 1.1.1.1Apply:
sudo netplan applyVerify:
ip addr show eth0Expected:
inet 192.168.10.3/24
Configure the laptop Ethernet adapter manually.
Example:
Interface : enp88s0
Address : 192.168.10.1
Netmask : 255.255.255.0
Gateway : (leave blank)
Verify:
ip addr show enp88s0Expected:
inet 192.168.10.1/24
Verify:
sudo sysctl net.ipv4.ip_forwardExpected:
net.ipv4.ip_forward = 1
If disabled:
sudo sysctl -w net.ipv4.ip_forward=1Make permanent:
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.confAllow Ethernet clients to use the laptop's WiFi connection.
Replace:
wlp0s20f3
with your WiFi interface name if different.
Add NAT:
sudo iptables -t nat -A POSTROUTING \
-o wlp0s20f3 \
-j MASQUERADEAllow forwarding:
sudo iptables -A FORWARD \
-i enp88s0 \
-o wlp0s20f3 \
-j ACCEPTAllow return traffic:
sudo iptables -A FORWARD \
-i wlp0s20f3 \
-o enp88s0 \
-m state --state RELATED,ESTABLISHED \
-j ACCEPTInstall:
sudo apt update
sudo apt install iptables-persistent -ySave current rules:
sudo netfilter-persistent saveVerify after reboot:
sudo iptables -t nat -L -n -vVerify routing:
ip routeExpected:
default via 192.168.10.1 dev eth0
Test internet:
ping 8.8.8.8Test DNS:
ping google.comUpdate packages:
sudo apt updateSearch for:
DroneNet
Connect using the configured password.
Verify:
ping 192.168.4.1SSH:
ssh aryan@192.168.4.1wlan0
IP: 192.168.4.1
Mode: Access Point
SSID: DroneNet
eth0
IP: 192.168.10.3
Gateway: 192.168.10.1
DNS:
8.8.8.8
1.1.1.1
WiFi
Internet Connection
Ethernet
IP: 192.168.10.1
NAT Enabled
IP Forwarding Enabled
Power on Raspberry Pi:
DroneNet starts automatically
Connect laptop to:
DroneNet
Use:
ssh aryan@192.168.4.1Camera streaming:
PI_IP = "192.168.4.1"Install packages on Pi:
sudo apt update
sudo apt install ...through the Ethernet connection without modifying the DroneNet network.
The Camera class automatically selects the available camera source.
Priority:
- Raspberry Pi Camera Module
- USB Webcam
Example:
from camera import Camera
camera = Camera(
width=640,
height=480
)Reading frames:
ret, frame = camera.read()Returned images are OpenCV-compatible BGR images.
This allows the remainder of the code to remain independent of camera hardware.
On the Raspberry Pi:
python3 stream.pyExpected output:
Streaming server started on 0.0.0.0:9999
Waiting for client...
Once connected:
Client connected: ('192.168.10.1', XXXXX)
The server continuously:
- Captures images from the camera
- Compresses images using JPEG
- Sends frames through a TCP socket
On the laptop:
python3 capture_rpi.pyThe client automatically:
- Waits for the Raspberry Pi to become reachable
- Starts
stream.pyremotely through SSH - Connects to the streaming server
- Receives compressed image frames
- Decodes images using OpenCV
- Displays the live video stream
Press:
ESC
to terminate the application.
The received frame can be directly used with:
- OpenCV
- Object Detection
- Object Tracking
- Visual Servoing
- SLAM
- Custom Computer Vision Pipelines
Example:
ret, frame = stream.read()
outputs = tracker.track(frame)No modifications to the streaming layer are required.
Current configuration:
Resolution : 640 × 480
JPEG Quality : 80
Transport : TCP
Port : 9999
This configuration is suitable for:
- Object Tracking
- Object Detection
- Visual Servoing
- SLAM Visualization
- Remote Camera Monitoring
Image resolution and JPEG quality can be modified in:
camera.py
stream.py
depending on the desired image quality and available network bandwidth.
Verify SSH is running:
sudo systemctl status sshEnable SSH:
sudo systemctl enable ssh
sudo systemctl start sshVerify connectivity:
ping 192.168.10.2Verify the streaming server is running:
netstat -tlnp | grep 9999Expected:
0.0.0.0:9999
List Raspberry Pi cameras:
rpicam-still --list-camerasList USB cameras:
v4l2-ctl --list-devicesIf the client is closed unexpectedly, the server may display:
BrokenPipeError
ConnectionResetError
This is normal behavior and simply indicates that the client disconnected.
The server automatically returns to:
Waiting for client...
and is ready for the next connection.
- Connect Raspberry Pi and laptop via Ethernet.
- Verify connectivity:
ping 192.168.10.2- Verify SSH access:
ssh aryan@192.168.10.2- Run:
python3 capture_rpi.py-
The client automatically:
- Waits for Raspberry Pi connectivity
- Launches the streaming server
- Connects to the video stream
-
Begin image processing on the laptop.
- H.264/H.265 hardware encoding
- ROS 2 integration
- Multi-camera streaming support
This project is released under the MIT License.
You are free to use, modify, and distribute this software for both academic and commercial purposes, provided that the original copyright notice and license are included.
See the LICENSE file for the full license text.