Turn Your Jetson Nano Into a Smart Home Security System with AI Detection

Turn Your Jetson Nano Into a Smart Home Security System with AI Detection

The Jetson Nano hits that sweet spot between "toy computer" and "industrial powerhouse" that makes it perfect for home security projects. With 128 CUDA cores and 4GB of RAM, it's got enough horsepower to run real AI models while sipping power like a Raspberry Pi. I've been running one as my primary security system for two years now, and honestly, it's been more reliable than any commercial solution I've tried.

Here's the thing about commercial security systems: they're either dumb as rocks or they phone home to some cloud service that costs $30/month. The Jetson Nano gives you the best of both worlds — actual AI processing happening locally, with no monthly fees and complete control over your data.

What You'll Need

  • Jetson Nano Developer Kit (4GB model recommended)
  • USB webcam or CSI camera module
  • MicroSD card (64GB minimum, Class 10 or better)
  • 5V 4A power supply (don't cheap out here)
  • Ethernet connection (WiFi dongles work but are flaky under load)

The camera choice matters more than you'd think. USB webcams are plug-and-play but introduce latency. CSI cameras like the Raspberry Pi camera module v2 give you direct access to the image sensor with lower latency, but you'll need to mess with device trees. For a first build, go USB — you can always upgrade later.

Setting Up the Base System

Start with the official JetPack image. Don't get fancy with Ubuntu 20.04 or other distros unless you enjoy spending weekends debugging CUDA driver issues. The JetPack image has everything pre-configured and tested.

# After flashing and initial setup, update everything
sudo apt update && sudo apt upgrade -y

# Install essential packages
sudo apt install python3-pip python3-dev cmake build-essential
sudo apt install libopencv-dev python3-opencv
sudo apt install git curl wget

# Install PyTorch for Jetson
wget https://nvidia.box.com/shared/static/fjtbno0vpo676a25cgvuqc1wty0fkkg6.whl -O torch-1.10.0-cp36-cp36m-linux_aarch64.whl
pip3 install torch-1.10.0-cp36-cp36m-linux_aarch64.whl

The PyTorch installation is crucial because most modern AI models expect it. The pre-compiled wheel from NVIDIA is optimized for the Jetson's ARM architecture and CUDA setup. Don't try to compile from source unless you have a weekend to burn.

Building the Detection Pipeline

For object detection, I recommend starting with YOLOv5. It's fast, accurate enough for security applications, and has excellent documentation. More importantly, it runs well on the Jetson Nano's limited memory.

# Clone YOLOv5 repository
git clone https://github.com/ultralytics/yolov5
cd yolov5
pip3 install -r requirements.txt

The beauty of YOLOv5 is that it comes pre-trained on COCO dataset, which includes people, cars, pets, and other objects you'd want to detect around your home. You can use it out of the box, but I'll show you how to fine-tune it for your specific setup.

Here's a basic detection script that captures frames from your camera and runs inference:

import cv2
import torch
import time
from pathlib import Path

# Load YOLOv5 model
model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
model.eval()

# Configure for Jetson optimization
if torch.cuda.is_available():
    model = model.cuda()
    model.half()  # Use FP16 for speed

# Initialize camera
cap = cv2.VideoCapture(0)  # USB camera
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 15)

# Detection classes we care about for security
security_classes = ['person', 'car', 'truck', 'motorcycle', 'bicycle']

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    # Run inference
    results = model(frame)
    
    # Process detections
    detections = results.pandas().xyxy[0]
    
    for _, detection in detections.iterrows():
        if detection['name'] in security_classes and detection['confidence'] > 0.5:
            # Draw bounding box
            x1, y1, x2, y2 = int(detection['xmin']), int(detection['ymin']), \
                           int(detection['xmax']), int(detection['ymax'])
            
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv2.putText(frame, f"{detection['name']}: {detection['confidence']:.2f}",
                       (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            
            # Trigger alert logic here
            print(f"Security alert: {detection['name']} detected with confidence {detection['confidence']:.2f}")
    
    # Display frame (remove for headless operation)
    cv2.imshow('Security Camera', frame)
    
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

This basic script gives you real-time object detection, but it's missing the "smart" part. Real security systems need to understand context — is this person supposed to be here? Is this car in my driveway or on the street?

Adding Intelligence with Zone Detection

The game-changer is implementing detection zones. Instead of alerting on every person or car that appears in frame, you define specific areas where detection matters. Think of it like invisible tripwires.

import numpy as np
from shapely.geometry import Point, Polygon

class SecurityZone:
    def __init__(self, name, coordinates, alert_classes):
        self.name = name
        self.polygon = Polygon(coordinates)
        self.alert_classes = alert_classes
        self.last_alert = 0
        self.cooldown = 30  # seconds between alerts
    
    def check_detection(self, detection):
        # Calculate center point of detection
        center_x = (detection['xmin'] + detection['xmax']) / 2
        center_y = (detection['ymin'] + detection['ymax']) / 2
        point = Point(center_x, center_y)
        
        # Check if detection is in zone and should trigger alert
        if (self.polygon.contains(point) and 
            detection['name'] in self.alert_classes and
            time.time() - self.last_alert > self.cooldown):
            
            self.last_alert = time.time()
            return True
        return False

# Define security zones (coordinates are pixel positions in your camera frame)
zones = [
    SecurityZone("driveway", [(100, 200), (500, 200), (500, 400), (100, 400)], 
                 ['person', 'car']),
    SecurityZone("front_door", [(200, 50), (400, 50), (400, 300), (200, 300)], 
                 ['person']),
    SecurityZone("backyard", [(0, 300), (640, 300), (640, 480), (0, 480)], 
                 ['person'])
]

Zone detection transforms your system from a noisy motion detector into something actually useful. I set up zones for my driveway, front walkway, and back gate. The system ignores people walking on the sidewalk but alerts when someone enters my property.

Implementing Smart Notifications

Nobody wants their phone buzzing every time a delivery truck drives by. Smart notifications require context and filtering. Here's how I handle it:

import smtplib
import requests
import json
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from datetime import datetime

class NotificationManager:
    def __init__(self, config):
        self.email_config = config.get('email', {})
        self.webhook_url = config.get('webhook_url', None)
        self.min_confidence = config.get('min_confidence', 0.6)
        
    def send_alert(self, detection_data, frame_image):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        # Create alert message
        message = f"Security Alert: {detection_data['class']} detected in {detection_data['zone']} at {timestamp}"
        
        # Send email with image
        if self.email_config:
            self._send_email(message, frame_image)
            
        # Send to Discord/Slack webhook
        if self.webhook_url:
            self._send_webhook(message, detection_data)
    
    def _send_email(self, message, image):
        msg = MIMEMultipart()
        msg['From'] = self.email_config['sender']
        msg['To'] = self.email_config['recipient']
        msg['Subject'] = "Security System Alert"
        
        # Add text
        msg.attach(MIMEText(message, 'plain'))
        
        # Encode and attach image
        _, buffer = cv2.imencode('.jpg', image)
        img_data = buffer.tobytes()
        img = MIMEImage(img_data)
        img.add_header('Content-Disposition', 'attachment', filename='alert.jpg')
        msg.attach(img)
        
        # Send email
        try:
            server = smtplib.SMTP(self.email_config['smtp_server'], 587)
            server.starttls()
            server.login(self.email_config['username'], self.email_config['password'])
            server.send_message(msg)
            server.quit()
            print("Email alert sent successfully")
        except Exception as e:
            print(f"Failed to send email: {e}")
    
    def _send_webhook(self, message, data):
        payload = {
            "content": message,
            "embeds": [{
                "title": "Security Detection",
                "description": f"Detected {data['class']} in {data['zone']}",
                "color": 16711680,  # Red
                "fields": [
                    {"name": "Confidence", "value": f"{data['confidence']:.2f}", "inline": True},
                    {"name": "Zone", "value": data['zone'], "inline": True}
                ]
            }]
        }
        
        try:
            requests.post(self.webhook_url, json=payload)
            print("Webhook alert sent successfully")
        except Exception as e:
            print(f"Failed to send webhook: {e}")

Performance Optimization

The Jetson Nano isn't a supercomputer. Getting consistent performance requires some tuning. Here are the optimizations that made the biggest difference in my setup:

Model Selection: YOLOv5s (small) runs at about 8-10 FPS on the Nano. YOLOv5n (nano) gets you 15+ FPS but with lower accuracy. For security, I prefer the extra accuracy — missing a detection is worse than slightly lower framerate.

Input Resolution: Don't feed 1080p frames to your model. Resize to 640x480 or even 416x416. The accuracy hit is minimal, but the performance gain is massive.

# Optimize inference settings
model.conf = 0.5  # Confidence threshold
model.iou = 0.4   # NMS IOU threshold
model.max_det = 50  # Maximum detections per image

# Enable TensorRT optimization (requires TensorRT installation)
try:
    model = torch.jit.script(model)
    print("TensorRT optimization enabled")
except:
    print("Running without TensorRT optimization")

Memory Management: The Nano only has 4GB of RAM, shared between system and GPU. Monitor memory usage and implement frame skipping if needed:

import psutil

def check_system_resources():
    memory = psutil.virtual_memory()
    if memory.percent > 85:
        print("High memory usage, skipping frame")
        return False
    return True

# In your main loop
if check_system_resources():
    results = model(frame)
else:
    # Skip inference this frame
    continue

Handling Edge Cases

Real-world deployment means dealing with stuff that never happens in tutorials. Here's what I learned the hard way:

Lighting Changes: Your model will struggle with dawn/dusk transitions and shadows. Consider training on your own dataset with images from different times of day, or implement automatic exposure adjustment.

False Positives: Shadows, reflections, and even swaying trees can trigger detections. Implement temporal filtering — require detections to persist for multiple frames before alerting.

Network Reliability: WiFi dongles on the Nano are notoriously unreliable under sustained load. Use ethernet when possible, or implement local storage for critical alerts.

# Temporal filtering example
class DetectionTracker:
    def __init__(self, required_frames=3):
        self.required_frames = required_frames
        self.recent_detections = []
    
    def add_detection(self, detections):
        self.recent_detections.append(detections)
        if len(self.recent_detections) > self.required_frames:
            self.recent_detections.pop(0)
    
    def is_stable_detection(self, current_detection):
        if len(self.recent_detections) < self.required_frames:
            return False
        
        # Check if similar detection exists in recent frames
        for frame_detections in self.recent_detections:
            for detection in frame_detections:
                if self._similar_detection(current_detection, detection):
                    return True
        return False

Going Beyond Basic Detection

Once you have the basics working, there are some interesting enhancements that make the system genuinely smart. Face recognition lets you distinguish between family members and strangers. Vehicle recognition can identify your own cars versus unknown vehicles.

I've also integrated mine with Home Assistant to control lights and other smart devices. When someone approaches the front door, the porch light automatically turns on. It's these small touches that make the system feel professional.

The total cost for this setup is under $200, compared to $500+ for comparable commercial systems that lock you into monthly subscriptions. More importantly, everything runs locally — no cloud dependencies, no privacy concerns, and it keeps working even when your internet goes down.

The Jetson Nano's sweet spot is exactly this kind of application: real AI processing with enough performance for practical use, but simple enough that you can understand and modify every component. It's embedded development at its best — powerful enough to be useful, constrained enough to keep you focused on what matters.