How to Set Up OpenCV Machine Vision on a Raspberry Pi
Introduction
A Raspberry Pi with a camera module and OpenCV is one of the most accessible entry points into computer vision. For under $100, you get a system that can detect objects, read QR codes, measure dimensions, monitor for motion, and guide automated processes. This guide covers the complete setup from a fresh Raspberry Pi OS install through a working OpenCV application with a real-time object detection pipeline.
What You Need
- Raspberry Pi 4 (4GB or 8GB) or Raspberry Pi 5
- Raspberry Pi Camera Module 3 or HQ Camera
- MicroSD card 32GB or larger
- Raspberry Pi OS (64-bit Bookworm recommended)
- Power supply (USB-C, 3A minimum)
- Monitor, keyboard, mouse for initial setup (or use SSH headless)
- Good lighting for your subject area
Step 1: Install Raspberry Pi OS and Enable Camera
- Flash Raspberry Pi OS 64-bit to the MicroSD using Raspberry Pi Imager.
- Before first boot, create a file named ssh (no extension) in the boot partition to enable SSH.
- Create a wpa_supplicant.conf file with your Wi-Fi credentials for headless setup.
- Boot the Pi. Find the IP on your router or use Angry IP Scanner.
- SSH in: ssh [email protected] (or use the IP)
Enable the camera interface:
- sudo raspi-config
- Interface Options > Camera > Enable
- Interface Options > I2C > Enable (for some camera functions)
- Finish and reboot: sudo reboot
Verify camera detection:
libcamera-helloThis should open a preview window (or run without error over SSH). If it fails, check the camera ribbon cable connection and orientation.
Step 2: Install OpenCV and Dependencies
OpenCV is available through pip, but building from source on Pi takes hours. Use the pre-built wheels instead.
# Update system sudo apt update && sudo apt upgrade -y # Install system dependencies sudo apt install -y libcamera-dev libcamera-apps python3-pip python3-numpy python3-matplotlib libatlas-base-dev libjasper-dev libqtgui4 libqt4-test libhdf5-dev libhdf5-serial-dev libhdf5-103 libqtgui4 libqtwebkit4 libqt4-test python3-pyqt5 libjpeg-dev libpng-dev libtiff-dev libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgtk-3-dev # Install OpenCV via pip pip3 install opencv-python --break-system-packages # Verify installation python3 -c "import cv2; print(cv2.__version__)"Note: The --break-system-packages flag is needed on Bookworm because pip is restricted from modifying system Python. Alternatively, use a virtual environment:
python3 -m venv ~/opencv_env source ~/opencv_env/bin/activate pip install opencv-python numpy matplotlibStep 3: Capture Images with the Camera
The Pi Camera Module 3 uses the libcamera stack. There are two ways to get images into OpenCV:
Method 1: Picamera2 (Recommended for Pi cameras)
pip3 install picamera2 --break-system-packagesCapture test image:
from picamera2 import Picamera2 import cv2 picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": "RGB888", "size": (640, 480)}) picam2.configure(config) picam2.start() frame = picam2.capture_array() cv2.imwrite("test_capture.jpg", frame) picam2.stop()Method 2: OpenCV VideoCapture (for USB cameras or with V4L2 loopback)
import cv2 cap = cv2.VideoCapture(0) # Device 0 for USB camera cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) ret, frame = cap.read() if ret: cv2.imwrite("usb_capture.jpg", frame) cap.release()For the Pi Camera Module 3, Picamera2 is the native interface and provides better performance than VideoCapture with V4L2.
Step 4: Build a Real-Time Object Detection Pipeline
This pipeline captures frames, converts to grayscale, applies Gaussian blur, detects edges with Canny, finds contours, and draws bounding boxes around detected objects.
from picamera2 import Picamera2 import cv2 import numpy as np # Initialize camera picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": "RGB888", "size": (640, 480)}) picam2.configure(config) picam2.start() # Minimum contour area to filter noise MIN_AREA = 500 try: while True: # Capture frame frame = picam2.capture_array() # Convert to grayscale gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) # Gaussian blur to reduce noise blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Canny edge detection edges = cv2.Canny(blurred, 50, 150) # Find contours contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Draw bounding boxes on original frame output = frame.copy() for i, cnt in enumerate(contours): area = cv2.contourArea(cnt) if area > MIN_AREA: x, y, w, h = cv2.boundingRect(cnt) cv2.rectangle(output, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.putText(output, f"Obj {i}: {int(area)}px", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) # Display cv2.imshow("Object Detection", output) # Exit on 'q' key if cv2.waitKey(1) & 0xFF == ord('q'): break finally: picam2.stop() cv2.destroyAllWindows()Run this over SSH with X11 forwarding, or save frames to disk instead of displaying. For headless operation, remove cv2.imshow() and save annotated frames with cv2.imwrite().
Step 5: QR Code and Barcode Reading
OpenCV includes QR code detection. This is useful for inventory tracking, part identification, and machine-readable job sheets.
import cv2 from picamera2 import Picamera2 picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": "RGB888", "size": (640, 480)}) picam2.configure(config) picam2.start() # Create QR detector detector = cv2.QRCodeDetector() try: while True: frame = picam2.capture_array() # Detect and decode data, bbox, _ = detector.detectAndDecode(frame) if data: print(f"QR Code detected: {data}") # Draw bounding box if bbox is not None: bbox = bbox.astype(int) for i in range(len(bbox[0])): pt1 = tuple(bbox[0][i]) pt2 = tuple(bbox[0][(i+1) % len(bbox[0])]) cv2.line(frame, pt1, pt2, (0, 255, 0), 2) cv2.putText(frame, data, (int(bbox[0][0][0]), int(bbox[0][0][1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow("QR Scanner", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break finally: picam2.stop() cv2.destroyAllWindows()Step 6: Color-Based Object Tracking
For sorting, counting, or positioning by color:
import cv2 import numpy as np from picamera2 import Picamera2 picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": "RGB888", "size": (640, 480)}) picam2.configure(config) picam2.start() # Define color range in HSV (example: red objects) # Red wraps around in HSV, so we need two ranges lower_red1 = np.array([0, 100, 50]) upper_red1 = np.array([10, 255, 255]) lower_red2 = np.array([170, 100, 50]) upper_red2 = np.array([180, 255, 255]) try: while True: frame = picam2.capture_array() hsv = cv2.cvtColor(frame, cv2.COLOR_RGB2HSV) # Create mask for red mask1 = cv2.inRange(hsv, lower_red1, upper_red1) mask2 = cv2.inRange(hsv, lower_red2, upper_red2) mask = cv2.bitwise_or(mask1, mask2) # Find contours on mask contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) output = frame.copy() for cnt in contours: if cv2.contourArea(cnt) > 300: x, y, w, h = cv2.boundingRect(cnt) cv2.rectangle(output, (x, y), (x+w, y+h), (0, 0, 255), 2) cx, cy = x + w//2, y + h//2 cv2.circle(output, (cx, cy), 5, (255, 0, 0), -1) cv2.putText(output, f"({cx},{cy})", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) cv2.imshow("Color Tracking", output) if cv2.waitKey(1) & 0xFF == ord('q'): break finally: picam2.stop() cv2.destroyAllWindows()Use a color picker tool or the HSV range finder at opencv.org to determine the correct HSV values for your target color.
Step 7: Dimension Measurement with Calibration
To measure real-world dimensions from camera pixels, you need a calibration step:
import cv2 import numpy as np from picamera2 import Picamera2 # Calibration: place a known-size object (e.g., credit card: 85.6 x 54 mm) # in the scene and measure its pixel dimensions KNOWN_WIDTH_MM = 85.6 KNOWN_WIDTH_PX = 320 # Measure this from a calibration image PIXELS_PER_MM = KNOWN_WIDTH_PX / KNOWN_WIDTH_MM picam2 = Picamera2() config = picam2.create_preview_configuration(main={"format": "RGB888", "size": (640, 480)}) picam2.configure(config) picam2.start() try: while True: frame = picam2.capture_array() gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) _, thresh = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) output = frame.copy() for cnt in contours: area = cv2.contourArea(cnt) if area > 1000: x, y, w, h = cv2.boundingRect(cnt) real_w = w / PIXELS_PER_MM real_h = h / PIXELS_PER_MM cv2.rectangle(output, (x, y), (x+w, y+h), (0, 255, 0), 2) label = f"{real_w:.1f} x {real_h:.1f} mm" cv2.putText(output, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow("Dimension Measurement", output) if cv2.waitKey(1) & 0xFF == ord('q'): break finally: picam2.stop() cv2.destroyAllWindows()Step 8: Headless Operation and Automation
For production use, the Pi should run without a monitor:
Systemd service for auto-start:
# Create service file sudo tee /etc/systemd/system/vision.service > /dev/nullRelated Guides
- Build a Raspberry Pi Pan-Tilt Camera Tracking Rig with OpenCV Face and Motion Tracking
- How to Run a Timelapse Camera with Raspberry Pi
- Raspberry Pi AI Camera Module (IMX500): On-Sensor Machine Learning Without an Accelerator HAT
- Building a Raspberry Pi Timelapse Camera
- How to Control GPIO Pins on Raspberry Pi with Python
- Automated Plant Watering System with Raspberry Pi
- Controlling GPIO Outputs with Python — LED, Relay, and Buzzer
- Raspberry Pi GPIO: Complete Beginner Guide with Python Examples