본격적으로 파이카메라를 제어하는 방법입니다. 다른 여러 프로그래밍 언어로 제어가 가능하겠지만 여기서는 파이썬으로 제어해 보겠습니다. 진행을 위해서는 아래 설정이 미리 갖추어진 상태여야 합니다.

테스트

가급적 X-Window 그래픽 환경에서 해보는게 좋겠습니다.

  • $ startx
  • LXTerminal 실행
  • sudo idle & (파이썬 개발환경 실행)
  • File > New Window

아래 코드를 입력 합니다. 대소문자 주의!

import time
import picamera

with picamera.PiCamera() as camera:
    camera.start_preview()
    time.sleep(5)
    camera.capture('/home/pi/Desktop/image.jpg')
    camera.stop_preview()
  • File > Save 로 저장
  • Run > Run Module 로 스크립트 실행

이상없이 동작되는걸 확인하면 다음과정을 위한 준비가 다 된 것입니다.

버튼으로 사진 촬영

아래와 같이 버튼을 연결합니다.

picamera-gpio-setup

버튼 입력을 확인하기 위해 RPi.GPIO 모듈을 사용합니다. 그리고 이전 소스에서 sleep(5) 함수를 사용한 부분을 GPIO.wait_for_edge() 로 바꿔줍니다. 버튼이 특정 상태가 되길 기다리는 것입니다.

import time
import picamera
import RPi.GPIO as GPIO  # new

GPIO.setmode(GPIO.BCM)  # new
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)  # new

with picamera.PiCamera() as camera:
    camera.start_preview()
    GPIO.wait_for_edge(17, GPIO.FALLING)  # new
    camera.capture('/home/pi/Desktop/image.jpg')
    camera.stop_preview()

/home/pi/Desktop/ 폴더에 image.jpg 파일을 삭제하고 스크립트를 실행합니다. 프리뷰가 시작되면 버튼을 누릅니다. 이미지가 캡쳐되었는지 확인하세요.

셀카용 지연 촬영

버튼을 누르면 5초 뒤 촬영되도록 만들어봅니다. 버튼입력이 감지된 뒤 sleep(5) 만 추가해 주면 되겠죠.

import time
import picamera
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)

with picamera.PiCamera() as camera:
    camera.start_preview()
    GPIO.wait_for_edge(17, GPIO.FALLING)
    time.sleep(5)  # new
    camera.capture('/home/pi/Desktop/image.jpg')
    camera.stop_preview()

앞서와 마찬가지로 이미지 삭제하고 실행해서 테스트 해보세요.

캠코더 촬영

처음 버튼을 누르면 캠코더 촬영을 시작하고, 다시 버튼을 누르면 촬영을 종료합니다.

camera_start_recording() 함수와 camera_stop_recording() 함수로 촬영 시작과 종료를 할 수 있습니다. 물론 2번의 버튼 입력 대기 코드가 필요 하겠지요.

import time
import picamera
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)

with picamera.PiCamera() as camera:
    camera.start_preview()
    GPIO.wait_for_edge(17, GPIO.FALLING)
    camera.start_recording('/home/pi/Desktop/video.h264')
    time.sleep(1)
    GPIO.wait_for_edge(17, GPIO.FALLING)
    camera.stop_recording()
    camera.stop_preview()

마지막 n 초의 영상만 저장

슬슬 복잡해 집니다. Circular buffer 를 이용해서 마지막 n 초 간의 영상만 저장하도록 하는 예제입니다.

picamera.PiCameraCircularIO() 함수를 이용해서 버퍼에 영상을 계속 써둡니다. 촬영 종료를 알리는 버튼이 감지되면 버퍼에 있는 스트림을 파일로 저장하는 방식입니다.

import io
import time
import picamera
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)

with picamera.PiCamera() as camera:
    stream = picamera.PiCameraCircularIO(camera, seconds=20)
    camera.start_preview()
    camera.start_recording(stream, format='h264')
    GPIO.wait_for_edge(17, GPIO.FALLING)
    camera.stop_recording()
    camera.stop_preview()
    for frame in stream.frames:
        if frame.header:
            stream.seek(frame.position)
            break
    with io.open('/home/pi/Desktop/video.h264', 'wb') as output:
        while True:
            data = stream.read1()
            if not data:
                break
            output.write(data)

기존에 생성된 비디오 파일을 삭제하고 스크립트를 실행해서 확인해보세요.

일정간격(Time-Lapse) 촬영

촬영을 하고 일정 시간만큼 sleep() 을 한 뒤 다시 촬영하는 패턴입니다. 이걸 몇 일 동안 반복하도록 놔두면 Time-Lapse 영상이 가능합니다.

import time
import picamera

VIDEO_DAYS = 5
FRAMES_PER_HOUR = 1
FRAMES = FRAMES_PER_HOUR * 24 * VIDEO_DAYS

def capture_frame(frame):
    with picamera.PiCamera() as cam:
        time.sleep(2)
        cam.capture('/home/pi/Desktop/frame%03d.jpg' % frame)

# Capture the images
for frame in range(FRAMES):
    # Note the time before the capture
    start = time.time()
    capture_frame(frame)
    # Wait for the next capture. Note that we take into
    # account the length of time it took to capture the
    # image when calculating the delay
    time.sleep(
        int(60 * 60 / FRAMES_PER_HOUR) - (time.time() - start)
    )

이미지로부터 비딩오 파일을 생성하기 위해서는 FFMPEG 패키지가 필요합니다.

  • sudo apt-get install ffmpeg

설치가 끝나면 아래 경로와 파일 설정이 자신에게 맞는지 확인하고 실행합니다.

  • ffmpeg -y -f image2 -i /home/pi/Desktop/frame%03d.jpg -r 24 -vcodec libx264 -profile high -preset slow /home/pi/Desktop/timelapse.mp4

스톱 모션 촬영

여기까지 보셨다면 이건 쉽겠죠. 버튼 입력을 대기하고 있다가 버튼 눌러지면 촬영하는 패턴을 무한반복 시키면 됩니다.

import picamera
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, GPIO.PUD_UP)

with picamera.PiCamera() as camera:
    camera.start_preview()
    frame = 1
    while True:
        GPIO.wait_for_edge(17, GPIO.FALLING)
        camera.capture('/home/pi/Desktop/frame%03d.jpg' % frame)
        frame += 1
    camera.stop_preview()

마찬가지로 영상으로 만들 때는

  • ffmpeg -y -f image2 -i /home/pi/Desktop/frame%03d.jpg -r 24 -vcodec libx264 -profile high -preset slow /home/pi/Desktop/stop_motion.mp4

보안 촬영

가장 복잡한 예제. 영상에 움직임이 발견되면 마지막 10초 영상을 기록하는 예제입니다.

마지막 n 초의 영상을 기록하는 예제에 모션을 감지하는 루틴이 추가되면 되겠죠. 친절하게도 소스코드 저자가 모션 감지하는 부분은 직접 해보라고 비워뒀네요………

import io
import random
import picamera
from PIL import Image

prior_image = None

def detect_motion(camera):
    global prior_image
    stream = io.BytesIO()
    camera.capture(stream, format='jpeg', use_video_port=True)
    stream.seek(0)
    if prior_image is None:
        prior_image = Image.open(stream)
        return False
    else:
        current_image = Image.open(stream)
        # Compare current_image to prior_image to detect motion. This is
        # left as an exercise for the reader!
        result = random.randint(0, 10) == 0
        # Once motion detection is done, make the prior image the current
        prior_image = current_image
        return result

def write_video(stream):
    # Write the entire content of the circular buffer to disk. No need to
    # lock the stream here as we're definitely not writing to it
    # simultaneously
    with io.open('before.h264', 'wb') as output:
        for frame in stream.frames:
            if frame.header:
                stream.seek(frame.position)
                break
        while True:
            buf = stream.read1()
            if not buf:
                break
            output.write(buf)
    # Wipe the circular stream once we're done
    stream.seek(0)
    stream.truncate()

with picamera.PiCamera() as camera:
    camera.resolution = (1280, 720)
    stream = picamera.PiCameraCircularIO(camera, seconds=10)
    camera.start_recording(stream, format='h264')
    try:
        while True:
            camera.wait_recording(1)
            if detect_motion(camera):
                print('Motion detected!')
                # As soon as we detect motion, split the recording to
                # record the frames "after" motion
                camera.split_recording('after.h264')
                # Write the 10 seconds "before" motion to disk as well
                write_video(stream)
                # Wait until motion is no longer detected, then split
                # recording back to the in-memory circular buffer
                while detect_motion(camera):
                    camera.wait_recording(1)
                print('Motion stopped!')
                camera.split_recording(stream)
    finally:
        camera.stop_recording()

모션을 감지하는 부분 – detect_motion() 은 직접 채워넣어야 제대로 동작합니다.

활용 방법

이정도 코드만 가지고도 다양하게 응용할 수 있습니다.

가장 먼저 생각나는게 라즈베리 파이 카메라 겠죠. 이미 Adafruit 에서 이런 코드를 응용하고 배터리 팩을 달아서 휴대용 카메라를 만든 프로젝트가 공개되어 있습니다.

https://learn.adafruit.com/diy-wifi-raspberry-pi-touch-cam/overview

혹은 CCTV, 보안 서버 등의 용도로 제작이 가능할겁니다. 상상력을 발휘해 보세요.

참고자료 :