Raspberry Piをデジタルスチルカメラにする

               公開日:2016/11/30

はじめに

2016年11月、Raspberry Pi2にカメラモジュールとシャッターボタンを接続して、Pythonスクリプトでデジタルスチルカメラにしました。

RaspberryPi+Camera

RaspberryPi+Camera

カメラの接続は、「Raspberry Piにカメラを接続」を参照してください。


Pythonスクリプトでカメラ入力を行うために、PicameraのWEBサイトを参考にしました。

Picamera WEB

Picamera WEB

Pythonライブラリのインストール

PicameraのPythonライブラリをインストールします。

sudo apt-get update
sudo apt-get install python-picamera python3-picamera

OSバージョンを下記に示します。ファームウェアバージョンは#930です。

pi@pi2note:~ $ uname -a
Linux pi2note 4.4.34-v7+ #930 SMP Wed Nov 23 15:20:41 GMT 2016 armv7l GNU/Linux

pi@pi2note:~ $ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 8.0 (jessie)
Release:        8.0
Codename:       jessie

デジタルスチルカメラの仕様

デジタルスチルカメラの仕様を下記のように決めました。

  • カメラのプレビュー表示をする
  • シャッターボタンを押すと静止画をキャプチャして保存します
  • 一定時間、シャッターボタンを押さないとプログラムを終了します
  • シャッターボタンを長押しすると、次回は1回だけ連写モードとなります

電子工作編

シャッターボタン

シャッターボタンは、タクトスイッチを使用します。 スイッチ状態をGPIOの17番ピンに入力する回路図を下記に示します。GPIOは内部にプルアップ抵抗とプルダウン抵抗を内蔵しており、設定により有効にすることができます。この接続では、内蔵プルダウン抵抗を有効にして使用します。

スイッチ入力の回路図

スイッチ入力の回路図

  • スイッチがONの時は、3.3Vの電圧がかかりGPIOに「1」が入力されます。プルダウン抵抗があるので、3.3VとGNDがショートすることはありません
  • スイッチがOFFの時は、プルダウン抵抗によりGND方向につながりGPIOに「0」が入力されます

 

キャプチャLED

静止画をキャプチャしている間、LEDを点灯させます。

GPIOの18ピンの出力でLEDを点灯、または消灯する回路図を下記に示します。

LED出力の回路図

LED出力の回路図

 

  • GPIO出力を「1」にすると、GPIO出力の電圧は3.3Vとなり電流が抵抗とLEDに流れてLEDが点灯します
  • GPIO出力を「0」にすると、GPIO出力の電圧は0.0Vとなり電流が流れないのでLEDは消灯します

回路の接続方法

ブレッドボードで回路を接続する方法を下記に示します。ブレッドボードの左右に配置されている赤色と青色の2列は縦1列がつながっています。青色側をマイナス、赤色側をプラスとして使用します。

電子回路の接続

電子回路の接続

LEDの出力回路は右側半分で接続しています。GPIOの18ピンの出力を抵抗(330Ω)とLEDに接続します。LEDは極性がありますので足の長い方のアノード側をプラス側、カソード側をマイナス側に接続します。

スイッチ状態の入力回路は左側半分で接続しています。3.3VとGPIOの17番ピンをタクトスイッチに接続します。

ソフトウェア編

/home/pi/MyProgram に、デジタルスチルカメラのプログラムを作成します。

pi@pi2note:~/MyProgram $cat camera.py 
#!/usr/bin/env python
# -*- coding: utf-8 -*-

u""" 静止画撮影カメラ

 シャッターボタンを押すと静止画をキャプチャして保存します
 一定時間、シャッターボタンを押さないと終了します
 シャッターボタンを長押しすると、次回は1回だけ連写モードとなります 
"""
__author__  = 'Yuki'
__version__ = '0.1'

#
import RPi.GPIO as GPIO
import datetime
import time
from picamera import PiCamera
from threading import (Event, Thread)

# Const
IMG_W = 1920      # Still Image Width
IMG_H = 1080      # Still Image Height

TMOUT_PUSH = 30   # TimeOut Push (second)
TMOUT_PULL = 5    # TimeOut Pull (second)

SEQ_NUM = 10      # Number of Continuous Shooting

CAP_LED = 18        # GPIO Capture Led
CAP_SHT = 17        # GPIO Capture Shutter

PATH_SAVE = "/home/pi/exMyData/Picture/"     # Save Full Path

#
req_event_push = Event()
req_event_pull = Event()

#
class FileName:
    u""" ファイル名クラス
    """
    
    def __init__(self,seq):
        u""" コンストラクタ : 連番を初期化 """
        self.seqno = seq
    def getName(self):
        u""" ファイル名を取得して、連番をインクリメント """
        now = datetime.datetime.now()
        file  = PATH_SAVE
        file += now.strftime('%Y%m%d_%H%M%S')+"_"
        file += '%03d' % (self.seqno)
        file += "_%03d"+".jpeg"
        self.seqno += 1
        return file

#
def cb_sw_on(ch):
    u""" SW ON コールバック関数
    
    Pushイベントを通知し、3秒以内にSW OFFするとPullイベントを通知する
    """

    req_event_push.set()           # ---> Set Push Event
    for ix in range(15):
        time.sleep(0.2)            # 0.2秒 × 15回 = 3秒
        sw = GPIO.input(ch)
        if sw == 0:                # IF SW Off
            req_event_pull.set()   # ---> Set Pull Event
            break

#
def camera_main():
    u""" カメラ・メインプログラム 
    
    SW 立ち上がりエッジ検出イベントとコールバック関数を登録    
    PiCameraクラスのインスタンスを生成し、プレビューを開始
    カメラ撮影を繰り返す
    | SW Pushイベント待ち、タイムアウトすればループ終了
    | num回のファイル名リストを生成
    | LED On、カメラ連続キャプチャ、LED Off
    | SW Pullイベント待ち、タイムアウトすれば連写モード(num)
    カメラプレビューを停止 

    """
    
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(CAP_LED,GPIO.OUT)
    GPIO.setup(CAP_SHT,GPIO.IN ,pull_up_down=GPIO.PUD_DOWN)
    
    GPIO.add_event_detect  (CAP_SHT ,GPIO.RISING  ,bouncetime=200 )
    GPIO.add_event_callback(CAP_SHT ,cb_sw_on)
    
    filename = FileName( 0 )                      # Filename Class
    camera = PiCamera(resolution=(IMG_W ,IMG_H) )
    camera.start_preview()
    
    num = 1
    bLoop     = True
    while bLoop:
        bEpush = req_event_push.wait(TMOUT_PUSH)  #  Exit
        else:
            out_file = filename.getName()
            out_file_list =  [out_file % no for no in range(num)] 
            GPIO.output(CAP_LED, 1)
            camera.capture_sequence( out_file_list )
            GPIO.output(CAP_LED, 0)
            #
            bEpull = req_event_pull.wait(TMOUT_PULL)
            req_event_pull.clear()
            num = 1 if bEpull else SEQ_NUM
            time.sleep(0.5)
    #
    camera.stop_preview()
    camera.close()
    
    GPIO.remove_event_detect(CAP_SHT)
    GPIO.cleanup(CAP_SHT)
    GPIO.cleanup(CAP_LED)
#
if __name__=='__main__':
    camera_main()

Pythonコメントによるドキュメント

このPythonスクリプトは、docstring形式でコメントを記述していますのでコメントからドキュメントを生成することができます。

pythonを起動して、対話形式でimport した後に、helpコマンドでドキュメントが出力されます。

pi@pi2note:~/MyProgram $ python
Python 2.7.9 (default, Sep 17 2016, 20:26:04)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import camera
>>> help(camera)

シーケンス図

プログラムをシーケンス図で説明します。

camera シーケンス図

camera シーケンス図

 

  • シャッターSWの立ち上がりイベントによるコールバック関数スレッドを登録
  • カメラのプレビューを開始
  • カメラ処理を繰り返し
  • Pushイベント待ち
  • 一定時間、SWを押さないとイベント待ちがタイムアウトして、カメラ処理の繰り返しを終了
  • SWをONするとコールバック関数スレッドが走り、Pushイベントを通知
  • Pushイベントを受信すると、LED ON → カメラキャプチャ(num枚) → LED OFF
  • Pullイベント待ち
  • コールバック関数スレッドは200msごとにSW状態をポーリングし、3秒以内にSW OFFとなるとPullイベントを通知
  • Pullイベントを受信すると、連写枚数 Num←1として繰り返し
  • 3秒以上SW ONが続くとPullイベントは発生しないので、イベント待ちがタイムアウトして、連写枚数 Numを設定
  • カメラのプレビューを停止

ライブラリのインポート

カメラを使用するのでPiCameraライブラリをインポートします。また、GPIOを使用するのでGPIOライブラリをインポートします。その他に、time,sys,datetime ライブラリもインポートします。イベント通知を行うので、 Event系もインポートします。

定数定義

  • 静止画の解像度定義

IMG_W = 1920 # 静止画 横幅
IMG_H = 1080 # 静止画 高さ

  • イベントのタイムアウト定義

TMOUT_PUSH = 30 # SW Pushイベント
TMOUT_PULL = 5 # SW Pullイベント

  • 連写モードの枚数定義

SEQ_NUM = 10 # Number of Continuous Shooting

  • GPIOポート番号定義

CAP_LED = 18 # GPIO Capture Led
CAP_SHT = 17 # GPIO Capture Shutter

  • 静止画の保存先定義

PATH_SAVE = “/home/pi/exMyData/Picture/” # Save Full Path

FileName ファイル名クラス

ファイル名を管理するFileNameクラスです。
インスタンスを生成する時に引数に連番の初期値を指定します。

filename = FileName( 0 )               

getName()メソッドで、ファイル名を取得して連番をインクリメントします。
ファイル名は「YYYYMMDD_hhmmss_seq_nnn.jpeg」形式で、静止画の保存先フルパスを取得します。

  • 日付 : YYYYMMDD
  • 時刻 : hhmmss
  • 連番 : seq
  • 連写 : nnn
out_file = filename.getName()

カメラ制御部

カメラのインスタンス生成

PiCameraクラスのインスタンスを生成します。

resolutionパラメータを指定して、解像度を指定します。

camera = PiCamera(resolution=(IMG_W ,IMG_H) )

カメラのプレビュー

最初に、カメラのプレビューを開始します。

camera.start_preview()

最後に、カメラのプレビューを停止します。

camera.stop_preview()

カメラの静止画キャプチャ

「capture(ファイル名)」関数で、静止画をキャプチャして指定したファイル名に保存します。
今回は、連写モードを実現するために、連続キャプチャする「capture_sequence(ファイル名リスト)」関数を使用しました。

out_file = filename.getName()
out_file_list =  [out_file % no for no in range(num)] 
camera.capture_sequence( out_file_list )

ファイル名リストは、保存先のファイル名をリストにしたものです。
リストは、下記のように生成することができます。

print ['P%02d.jpeg' % no  for no in range(5)]
['P00.jpeg', 'P01.jpeg', 'P02.jpeg', 'P03.jpeg', 'P04.jpeg']

連写モードでない場合は、range(1)とすることにより1枚だけキャプチャします。

シャッターボタンを押した時に、静止画をキャプチャするようにすれば、デジタルスチルカメラが実現できます。

イベント関係

イベントの定義

シャッターボタンについて、2つのイベントを使います。

  • シャッターボタンを押す : Pushイベント
  • シャッターボタンを放す : Pullイベント
req_event_push = Event()
req_event_pull = Event()

イベント待ちと通知

  • set() 関数で、イベントを通知します
  • wait()関数で、イベントを待ちます
  • clear()関数で、イベントをクリアします

wait()関数の引数にタイムアウト時間を設定して使用します。戻り値は、イベントを受信した場合は True、タイムアウトした場合は Falseとなります。


イベントを通知します

req_event_pull.set()

イベントを受信します。タイムアウトした場合は、bLoopをFalseにしてループを終了させます。

bEpush = req_event_push.wait(TMOUT_PUSH)  
req_event_push.clear()
if not bEpush:
    bLoop = False

GPIO関係

チャンネルの指定方法

2種類のチャンネルの指定モードがあり選択します。

  • GPIO.BOARD : 物理的なGPIOのボード番号(P1から順番にボードに並ぶ番号)
  • GPIO.BCM : GPIOのピン番号
GPIO.setmode(GPIO.BCM)

今回は、GPIOのピン番号を選択します。

チャンネルのモード設定

チャンネルの入出力モードを設定します。

  • CAP_LEDのチャンネルを GPIO.OUT で出力モードにします
  • CAP_SHTのチャンネルを GPIO.IN で入力モードにします

入力モードの時、pull_up_downパラメータを指定することにより、プルアップ抵抗(GPIO.PUD_UP)、またはプルダウン抵抗(GPIO.PUD_DOWN)を有効にすることができます。今回の回路では、GPIO入力チャンネルにプルダウン抵抗を使用します。

GPIO.setup(CAP_LED,GPIO.OUT)
GPIO.setup(CAP_SHT,GPIO.IN ,pull_up_down=GPIO.PUD_DOWN)

LEDの制御

LEDの制御は、GPIO.output()関数でGPIOチャンネルに「1:点灯」/「0:消灯」を出力します。

GPIOに出力して、静止画をキャプチャ中にLEDを点灯します。

  • CAP_LEDチャンネルに「1」を出力してLEDを点灯します
  • 静止画をキャプチャします
  • CAP_LEDチャンネルに「0」を出力してLEDを消灯します
GPIO.output(CAP_LED, 1)
camera.capture_sequence( out_file_list )
GPIO.output(CAP_LED, 0)

シャッターボタンの入力

スイッチの入力は、GPIO.input()関数でGPIOチャンネルから入力します。スイッチ状態のエッジを検出して、イベントを発生させることもできます。

「add_event_detect()」関数で、CAP_SHTチャンネルのエッジ検出イベントを登録します。

検出するエッジの指定は下記3種類です。

  • GPIO.RISING (立ち上がりエッジ)
  • GPIO.FALLING (立ち下がりエッジ)
  • GPIO.BOTH (立ち上がりまたは立ち下がりエッジ)

今回は、シャッターボタンを押すと電気信号がLOWからHIGHになりますので、立ち上がりエッジを検出します。

スイッチ入力を行う時は、チャタリングが発生します。チャタリングとは、オン・オフを切り替える時にオン・オフが細かく繰り返される現象のことです。このため、立ち上がりエッジが何回も発生してしまいます。このチャタリングを防止するために、bouncetime パラメータを指定します。これにより、エッジ検出イベント発生直後からバウンス時間に指定された時間に発生した他のエッジ検出イベントが無視されるため、イベントが複数回発生するのを避けることができます。

「add_event_callback()」関数で、イベントが発生した場合に呼び出すコールバック関数を登録します。コールバック関数は、別スレッドで実行されます。

GPIO.add_event_detect  (CAP_SHT ,GPIO.RISING  ,bouncetime=200 )
GPIO.add_event_callback(CAP_SHT ,cb_sw_on)

このイベント登録により、シャッターボタンの立ち上がりイベントを検出してコールバック関数をスレッドとして実行します。

GPIO入力のイベント検知の解除

シャッターボタンの立ち上がりイベント検知を解除します。

GPIO.remove_event_detect(CAP_SHT)

GPIOの終了処理

cleanup()関数で、使用したチャンネルの終了処理をします。

GPIO.cleanup(CAP_SHT)
GPIO.cleanup(CAP_LED)

コールバック関数部

シャッターボタンの立ち上がりエッジを検出した時にコールバックされるcb_sw_on()関数の中を見てゆきます。

Pushイベント通知

スイッチが押されたので、Pushイベントを通知します。

req_event_push.set()           # ---> Set Push Event

Pullイベント通知

200msごとにスイッチの状態をポーリングします。15回ポーリングしますので、SW ON状態が3秒間継続するか調べます。

for ix in range(15):
    time.sleep(0.2)            # 0.2秒 × 15回 = 3秒
    sw = GPIO.input(ch)
    if sw == 0:                # IF SW Off
        req_event_pull.set()   # ---> Set Pull Event
        break

・SW OFFとなった場合、Pullイベントを通知します。
・SW ON状態が継続した場合は、Pullイベントを発生せずにスレッドが終了します。

デジタルスチルカメラの処理

  • 連写枚数 num を1に設定します。
  • カメラ処理が終了するまでbLoopでループします。
  • Pushイベントを待ちます。タイムアウトした場合は、シャッターボタンを一定時間押されていないのでbLoopをFalseにしてループを終了します。
  • Pushイベントを受信した場合は、静止画をキャプチャします。
  • Pullイベントを待ちます。シャッターボタンを放したタイミングでPullイベントを受信するので、ループします。
  • Pullイベントがタイムアウトした場合は、シャッターボタンの長押しなので、連写モードに切り替えるために 連写枚数 num をSEQ_NUMに設定します。

num = 1
bLoop     = True
while bLoop:
    bEpush = req_event_push.wait(TMOUT_PUSH)  #  Exit
    else:
        out_file = filename.getName()
        out_file_list =  [out_file % no for no in range(num)] 
        GPIO.output(CAP_LED, 1)
        camera.capture_sequence( out_file_list )
        GPIO.output(CAP_LED, 0)
        #
        bEpull = req_event_pull.wait(TMOUT_PULL)
        req_event_pull.clear()
        num = 1 if bEpull else SEQ_NUM
        time.sleep(0.5)

 

num = 1 if bEpull else SEQ_NUM

は、Pythonの表記法のひとつで、if else 文を1行で書けるメリットがあり、下記と同じです。

if bEpull:
    num = 1
else:
    num = SEQ_NUM

最後に

デジタルスチルカメラの試作的なものができました。これをベースにして、動画録画機能やタイマー撮影機能などを追加していきたいと考えています。

 

 

 

このエントリーをはてなブックマークに追加