Note

Hello, welcome to the SunFounder Raspberry Pi & Arduino & ESP32 Enthusiasts Community on Facebook! Dive deeper into Raspberry Pi, Arduino, and ESP32 with fellow enthusiasts.

Why Join?

  • Expert Support: Solve post-sale issues and technical challenges with help from our community and team.

  • Learn & Share: Exchange tips and tutorials to enhance your skills.

  • Exclusive Previews: Get early access to new product announcements and sneak peeks.

  • Special Discounts: Enjoy exclusive discounts on our newest products.

  • Festive Promotions and Giveaways: Take part in giveaways and holiday promotions.

👉 Ready to explore and create with us? Click [here] and join today!

4.1 Camera

Introduction

This project demonstrates how to create a simple camera system with a shutter button using the Raspberry Pi Zero. When you press the button, the camera captures a photo, and an LED flashes to indicate the action. This is a great way to get hands-on experience with GPIO control and the Raspberry Pi Camera Module.


What You’ll Need

The following components are required for this project:

COMPONENT INTRODUCTION

PURCHASE LINK

Breadboard

BUY

Jumper Wires

BUY

Resistor

BUY

LED

BUY

Button

BUY

Camera Module

BUY

Fusion HAT+

-

Raspberry Pi

-


Circuit Diagram

Below is the GPIO pin mapping for this project:

../_images/4.1.1_sch.png

Wiring Diagram

  1. To use camera module conveniently, Assemble the Pan-tilt (For Camera) is recommended.

    Note

    Assembling the pan-tilt may obscure some pins, so it is recommended to assemble it only when using the camera, or place it on the outside after assembly.

    ../_images/gimbal_assemble.png
  2. Follow this wiring diagram to set up the circuit:

    ../_images/4.1.1_bb.png

Running the Example

  1. Access the Raspberry Pi Desktop:

  2. Open a Terminal and go to the code folder:

    cd ~/ai-lab-kit/python
    
  3. Run the script to start the camera:

    sudo python3 4.1_Camera.py
    
  4. After running the program, the camera starts working.

    • Press the button to take a photo:

      • The LED lights up.

      • One photo is captured and saved to the Pictures folder (for example, photo_001.jpg, photo_002.jpg).

    • Release the button:

      • The LED turns off.

    • The program keeps running until you press Ctrl + C, then exits cleanly without error messages.

    Note

    QT preview requires a desktop environment. If the preview cannot be started (for example, over SSH), the camera can still capture and save photos normally.


Code

Here is the Python code used for this project:

#!/usr/bin/env python3

import os
import time
import threading
from picamera2 import Picamera2, Preview
from fusion_hat.pin import Pin, Mode, Pull

# Resolve the correct user's home directory (works with sudo)
REAL_USER = os.getenv("SUDO_USER") or os.getlogin()
USER_HOME = f"/home/{REAL_USER}"
PICTURES_DIR = os.path.join(USER_HOME, "Pictures")
os.makedirs(PICTURES_DIR, exist_ok=True)

# Initialize camera
camera = Picamera2()
camera.configure(camera.create_preview_configuration(main={"size": (800, 600)}))

# Photo counter with thread safety
photo_index = 1
photo_lock = threading.Lock()

# Track whether preview was started successfully
preview_started = False

# Initialize LED and button
led = Pin(17, mode=Mode.OUT)
button = Pin(4, mode=Mode.IN, pull=Pull.DOWN)

def take_photo():
   """Capture one photo and increment the index."""
   global photo_index
   with photo_lock:
      filepath = os.path.join(PICTURES_DIR, f"photo_{photo_index:03d}.jpg")
      print(f"\nCapturing: {filepath}")
      camera.capture_file(filepath)
      print("Saved.")
      photo_index += 1

def main():
   global preview_started

   # Start preview only when a GUI display is available (remote SSH often has no DISPLAY)
   preview_started = False
   if os.getenv("DISPLAY"):
      try:
            camera.start_preview(Preview.QT)
            preview_started = True
      except Exception as e:
            preview_started = False
            print(f"Preview start failed (continue without preview): {e}")
   else:
      print("No DISPLAY detected (running headless without preview).")

   camera.start()

   print("Camera is running.")
   print("Press the button to take a photo.")
   print(f"Photos will be saved to: {PICTURES_DIR}")
   print("Press Ctrl+C to exit.\n")

   try:
      while True:
            if button.value():         # Button pressed (HIGH)
               led.on()               # LED on
               take_photo()           # Take photo
               time.sleep(0.3)        # Simple debounce (avoid multiple shots)
               while button.value():  # Wait until button is released
                  time.sleep(0.01)
               led.off()              # LED off after release

            time.sleep(0.01)

   except KeyboardInterrupt:
      print("\nExiting...")

   finally:
      # Turn off LED
      try:
            led.off()
      except Exception:
            pass

      # Stop the camera first
      try:
            camera.stop()
      except Exception:
            pass

      # Stop preview only if it was started
      if preview_started:
            try:
               camera.stop_preview()
            except Exception:
               pass

      try:
            camera.close()
      except Exception:
            pass

if __name__ == "__main__":
   main()

Understanding the Code

  1. Imports and Purpose

    import os
    import time
    import threading
    from picamera2 import Picamera2, Preview
    from fusion_hat.pin import Pin, Mode, Pull
    

    These modules are responsible for:

    • os: handling environment variables and file paths.

    • time: providing delays and keeping the main loop running.

    • threading: ensuring photo capture is thread-safe.

    • Picamera2 and Preview: controlling the Raspberry Pi camera and preview modes.

    • Pin, Mode, and Pull: controlling the LED and reading the push button via Fusion HAT.

  2. Resolving the Save Directory (Works with sudo)

    REAL_USER = os.getenv("SUDO_USER") or os.getlogin()
    USER_HOME = f"/home/{REAL_USER}"
    PICTURES_DIR = os.path.join(USER_HOME, "Pictures")
    os.makedirs(PICTURES_DIR, exist_ok=True)
    

    This section ensures photos are always saved to the real user’s ~/Pictures directory:

    • When the script is run with sudo, SUDO_USER points to the original user.

    • When not using sudo, the current login user is used.

    • The Pictures folder is created automatically if it does not exist.

  3. Camera Initialization and Configuration

    camera = Picamera2()
    camera.configure(camera.create_preview_configuration(main={"size": (800, 600)}))
    

    The camera is initialized and configured for preview and photo capture:

    • A preview resolution of 800 × 600 is used.

    • This configuration works both with and without a visible preview window.

  4. Photo Index and Thread Safety

    photo_index = 1
    photo_lock = threading.Lock()
    
    • photo_index stores the current photo number.

    • photo_lock prevents multiple button presses from triggering overlapping photo captures.

  5. Preview State Tracking

    preview_started = False
    

    This flag records whether a preview window was successfully started, so the program only tries to stop the preview when it actually exists.

  6. LED and Button Setup

    led = Pin(17, mode=Mode.OUT)
    button = Pin(4, mode=Mode.IN, pull=Pull.DOWN)
    
    • The LED is connected to GPIO 17 and configured as an output.

    • The button is connected to GPIO 4 and configured as an input with an internal pull-down resistor.

    • A HIGH signal indicates the button is pressed.

  7. Photo Capture Function

    def take_photo():
       """Capture one photo and increment the index."""
       global photo_index
       with photo_lock:
          filepath = os.path.join(PICTURES_DIR, f"photo_{photo_index:03d}.jpg")
          print(f"\nCapturing: {filepath}")
          camera.capture_file(filepath)
          print("Saved.")
          photo_index += 1
    

    This function:

    • Builds a filename such as photo_001.jpg.

    • Captures an image using the camera.

    • Increments the photo index so the next photo uses a new name.

  8. Conditional Preview for Local and Remote Use

    preview_started = False
    if os.getenv("DISPLAY"):
       try:
             camera.start_preview(Preview.QT)
             preview_started = True
       except Exception as e:
             preview_started = False
             print(f"Preview start failed (continue without preview): {e}")
    else:
       print("No DISPLAY detected (running headless without preview).")
    
    • If a graphical desktop is available, a QT preview window is shown.

    • If running remotely over SSH without a display, the preview is skipped.

    • Photo capture works normally in both cases.

  9. Main Loop and Button Logic

    try:
       while True:
             if button.value():         # Button pressed (HIGH)
                led.on()               # LED on
                take_photo()           # Take photo
                time.sleep(0.3)        # Simple debounce (avoid multiple shots)
                while button.value():  # Wait until button is released
                   time.sleep(0.01)
                led.off()              # LED off after release
    
             time.sleep(0.01)
    

    Inside the main loop:

    • Pressing the button turns on the LED.

    • A photo is captured immediately.

    • The program waits until the button is released to avoid repeated shots.

    • The LED turns off after the button is released.

  10. Clean Exit and Resource Cleanup

    except KeyboardInterrupt:
       print("\nExiting...")
    
    finally:
       # Turn off LED
       try:
             led.off()
       except Exception:
    
    ...
    

    When Ctrl+C is pressed:

    • The LED is turned off.

    • The camera pipeline is stopped.

    • The preview is stopped only if it was started.

    • Camera resources are released cleanly.

    This ensures the program exits without errors, whether running locally with a screen or remotely in headless mode.


Troubleshooting

  1. Photo Not Captured:

    • Cause: The button is not wired correctly or the camera is not initialized.

    • Solution:

      • Ensure the button is connected to GPIO pin 4 and ground.

      • Verify that the camera is properly connected and enabled via raspi-config.

  2. LED Does Not Blink:

    • Cause: Incorrect LED wiring or GPIO configuration.

    • Solution:

      • Ensure the LED is connected to GPIO pin 17 with an appropriate resistor.

      • Test the LED separately to confirm it functions correctly.

  3. Script Crashes with Camera Error:

    • Cause: The camera module is not detected or in use by another process.

    • Solution:

      • Ensure the camera is properly connected and restart the Raspberry Pi.

      • Check for conflicting processes using sudo lsof /dev/video*.


Extendable Ideas

  1. Multiple Photos: Allow multiple photos to be captured in a session, each with a unique filename:

    counter = 0
    camera.capture_file(f'{user_home}/photo_{counter}.jpg')
    counter += 1
    
  2. Video Recording: Extend the functionality to record videos when the button is pressed:

    camera.start_recording(f'{user_home}/my_video.h264')
    time.sleep(10)
    camera.stop_recording()
    
  3. LED Status Indicator: Use the LED to indicate the camera’s readiness or status:

    • Solid light: Ready.

    • Blinking: Capturing a photo.

  4. Photo Gallery Management: Organize captured photos into folders based on date or event.

  5. Timelapse Photography: Capture photos at regular intervals to create a timelapse:

    for i in range(10):
        camera.capture_file(f'{user_home}/timelapse_{i}.jpg')
        time.sleep(5)
    

Conclusion

This project introduces a basic camera setup with a button-triggered shutter mechanism. It combines GPIO control with the Picamera2 library to demonstrate how to create interactive projects. Experiment further to expand its functionality and create more engaging applications.