DEV Community

Cover image for Build a Simple Raspberry Pi Snapshot Monitor with a USB Camera
Yoshida.T
Yoshida.T

Posted on

Build a Simple Raspberry Pi Snapshot Monitor with a USB Camera

What is Miniviz?

Miniviz is a service that allows you to easily store, visualize, and receive alerts for IoT data and images.
It is well-suited for PoC projects and learning purposes.

In this guide, we’ll build a simple surveillance-camera-like system using a Raspberry Pi and a USB camera.

https://miniviz.net

Part #1 (Monitoring temperature and humidity with ESP32):
https://dev.to/yoshidataisei/how-to-send-esp32-sensor-data-to-miniviz-for-real-time-visualizationminiviz-1-59na

What we will do

We will connect a Raspberry Pi to a USB camera and send images to Miniviz.

:::note info
Image upload is available only with the Pro plan.
:::

What you need

  • Raspberry Pi
  • Using VS Code Remote SSH is convenient
  • USB camera
  • Miniviz Project ID and Token

Connecting the Raspberry Pi and USB Camera

Connect the USB camera and run the lsusb command.

pi@raspberrypi:~ $ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
Bus 001 Device 003: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
Bus 001 Device 004: ID 0424:7800 Microchip Technology, Inc. (formerly SMSC)
Bus 001 Device 007: ID 0411:02da BUFFALO INC. (formerly MelCo., Inc.) USB 2.0 Camera // これがUSBカメラ
Enter fullscreen mode Exit fullscreen mode

You can check the USB camera device file.

$ ls /dev/video*
/dev/video0  /dev/video10  /dev/video12  /dev/video14  /dev/video16  /dev/video20  /dev/video22  /dev/video31
/dev/video1  /dev/video11  /dev/video13  /dev/video15  /dev/video18  /dev/video21  /dev/video23
Enter fullscreen mode Exit fullscreen mode

Checking the image captured by the camera

$ sudo apt-get install fswebcam
$ fswebcam -r 640x480 --no-banner image.jpg
Enter fullscreen mode Exit fullscreen mode

The image quality isn’t great, but it works.
(Open the image via GUI or download it locally using scp, etc.)

image.png

(((Yes… my room is really cold…)))

Sending camera images to Miniviz

Overall steps:

  1. Obtain your Project ID and Token
  2. Call the Image API and try sending a sample image
  3. If it works, send actual camera images using the API

1. Obtain your Project ID and Token

Get your Project ID and Token from the Miniviz dashboard.
Create a project → then retrieve the Project ID and Token.

image.png

2. Call the Image API to send a sample image

First, try sending the sample image you captured earlier.
API: Send an image in the request body

POST https://api.miniviz.net/api/project/{project_id}/image?token={token}
Enter fullscreen mode Exit fullscreen mode

Request body

{
    "timestamp": 1717587812345,
    "label_key": "raspberry_pi_cam",
    "image_name": "camera.jpg",
    "image_base64": "base64_encoded_image_data"
}
Enter fullscreen mode Exit fullscreen mode

Sending a sample image (Python)

#!/usr/bin/env python3
"""
figure image to miniviz
"""
import requests
import base64
import os
from datetime import datetime, timezone

# Settings
PROJECT_ID = "PROJECT_ID"
TOKEN = "TOKEN"
API_URL = "https://api.miniviz.net"
IMAGE_PATH = "image.jpg" # The captured image, or any sample image
LABEL_KEY = "raspberry_pi_cam"

# Encode image as base64
with open(IMAGE_PATH, "rb") as f:
    image_data = f.read()
image_base64 = base64.b64encode(image_data).decode('utf-8')

# Send request
url = f"{API_URL}/api/project/{PROJECT_ID}/image"
payload = {
    "timestamp": int(datetime.now(timezone.utc).timestamp() * 1000),
    "label_key": LABEL_KEY,
    "image_name": os.path.basename(IMAGE_PATH),
    "image_base64": image_base64
}

try:
    response = requests.post(url, json=payload, params={"token": TOKEN})
    response.raise_for_status()
    print("✅ Send successful")
    print(response.json())
except requests.exceptions.HTTPError as e:
    print(f"❌ Error: HTTP {e.response.status_code}")
    print(e.response.text)
except Exception as e:
    print(f"❌ Error: {e}")
Enter fullscreen mode Exit fullscreen mode

Checking on Miniviz

image.png

When you run Preview…

image.png

3. Sending camera images periodically

Once the sample code works, you can start sending camera images at regular intervals.

Code

The full code is provided at the end of this document.

Viewing images in the Database

You can check the uploaded data from the Database menu.
All sent images are stored in the database.

If nothing appears here, the data transmission failed.
Please check the logs on your device.

image.png

Displaying images in Visualize

You can create graphs from the Visualize menu.
You can customize the graph type and how the data is displayed.

image.png

image.png

Sample Code (Periodic Sending)

#!/usr/bin/env python3
"""
Raspberry Pi USB Camera to Miniviz
"""
import requests
import base64
import os
import subprocess
import time
from datetime import datetime, timezone

# Miniviz configuration
PROJECT_ID = "PROJECT_ID"
TOKEN = "TOKEN"
API_URL = "https://api.miniviz.net"
LABEL_KEY = "raspberry_pi_cam"

# USB Camera configuration
DEVICE = "/dev/video0"
RESOLUTION = "640x480"
IMAGE_PATH = "image.jpg"

# Send interval (seconds)
SEND_INTERVAL = 60  # 1 minute

def capture_image():
    """Capture image with USB camera"""
    cmd = [
        "fswebcam",
        "-d", DEVICE,
        "-r", RESOLUTION,
        "--no-banner",
        "-S", "5",
        IMAGE_PATH
    ]
    print("[Info] Capturing image...")
    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        print(f"[Error] Capture failed: {result.stderr}")
        return False

    print("[Info] Image captured successfully")
    return True

def encode_image_to_base64(image_path):
    """Encode image file to base64"""
    with open(image_path, "rb") as f:
        image_data = f.read()
    return base64.b64encode(image_data).decode('utf-8')

def send_image_to_miniviz(image_path):
    """Send image to Miniviz API"""
    url = f"{API_URL}/api/project/{PROJECT_ID}/image"

    # Encode image to base64
    image_base64 = encode_image_to_base64(image_path)

    # Request payload
    payload = {
        "timestamp": int(datetime.now(timezone.utc).timestamp() * 1000),
        "label_key": LABEL_KEY,
        "image_name": os.path.basename(image_path),
        "image_base64": image_base64
    }

    try:
        response = requests.post(url, json=payload, params={"token": TOKEN})
        response.raise_for_status()
        print("[Info] Send successful")
        print(response.json())
        return True
    except requests.exceptions.HTTPError as e:
        print(f"[Error] HTTP {e.response.status_code}")
        print(e.response.text)
        return False
    except Exception as e:
        print(f"[Error] {e}")
        return False

def cleanup_image(image_path):
    """Delete sent image file (to save disk space)"""
    try:
        if os.path.exists(image_path):
            os.remove(image_path)
            print(f"[Info] Cleaned up: {image_path}")
    except Exception as e:
        print(f"[Warning] Failed to delete {image_path}: {e}")

def main():
    """Main process"""
    # Capture image with USB camera
    if not capture_image():
        print("[Error] Failed to capture image")
        return

    # Send to Miniviz
    success = send_image_to_miniviz(IMAGE_PATH)

    # Delete image file only on success (to save disk space)
    if success:
        cleanup_image(IMAGE_PATH)

if __name__ == "__main__":
    print("Starting miniviz image send test (press Ctrl+C to stop)")
    try:
        while True:
            main()
            time.sleep(SEND_INTERVAL)
    except KeyboardInterrupt:
        print("\n[Info] Stopped by user")
Enter fullscreen mode Exit fullscreen mode

Lastly

You can also keep the video locally and send only snapshots, which can still be quite practical.
As a more advanced use case, combining images with AI to trigger notifications could be an interesting approach.

Top comments (0)