M5stickVによるトイカメラ

# はじめに
今回，ちょっとしたお出かけをすることになり，重くて面倒な一眼レフではなく，手軽なカメラが欲しくなりアマゾンで検索．いやいや，M5stickV持ってんじゃん．(積んじゃってるけど)

"無い物は作ろう"の精神は大切です．

という訳で，手軽なトイカメラを作ったのでここに書いておきます．お家でM5stickVが積んである方はぜひお試しください．

![toycam_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478299/c307f475-87f4-9121-9d90-0c1a4c1e36a0.jpeg)



# 目的
お出かけ時に使えるトイカメラをM5stickVで作る

# 先駆者のありがたいデータ
何をやるにも大抵のことは誰かが先にやってくれています．過去のデータを使わない手はありません．というか今回のトイカメラは実際のところ次の記事を見たことから始めたものです．
> https://qiita.com/deckeye/items/dc27c3bbe7ae9e724c35

今回のトイカメラはこの記事を参考にしてます．なので最初はこの記事の通りにコードをmicroSDに書き込んで動作させてみました．

デジカメとしての基本的な動作（センサが捉えた画像をLCDに表示し保存）はこの時点で完成していますね．では何が足りていないのかと言うと，画像の連続保存とプレビュー機能，だと思います．画像の保存については参考記事の最後の章で触れられていますね．プレビューについては今回の目的上，撮った写真をすぐに見たい時もあると思うので実装します．

# 実装する機能と実装方法
今回は次の2つの機能を実装します．

- 画像の連続保存
- プレビュー機能<br>


この2つの機能はmicroSDにデータを保存し，そのデータを読んで表示する，という同じような機能となっています．
M5stickVはMaixPyによる開発が可能で，MaixPyではuosモジュールによりディレクトリ操作ができます．今回追加する2つの機能はuosモジュールによるmicroSD上のデータの操作により実装できます．

# uosモジュールによるディレクトリ操作
MaixPyのuosモジュールについてのドキュメントは以下のページに書かれています．
> https://maixpy.sipeed.com/en/libs/standard/uos.html

今回使用するのは，chdir，getcwd，mkdir，listdirの4つです．この4つの関数によりmicroSDカード上のデータを操作します．
<br>
まず画像連続保存について説明します．

起動直後に現在のディレクトリをgetcwdにより取得し，chdirにより確実に/sdに移動します．（確認せずchdirしてもよいのだが．．．）

次に画像を保存するディレクトリとしてpicturesをmkdirにより用意します．2回目以降の起動では既にpicturesが存在しているはずなので，その場合はディレクトリは用意されずに元のpicturesを使用します．

最後にpicturesにchdirで入り，中に保存されている画像の枚数をlistdirにより取得します．
この枚数の数え上げにより，画像保存時に元のデータを上書きすることなく，新たなファイル名で保存することができます．

プレビュー機能もlistdirのリストにより実装されています．

プレビュー時にはpictures内のファイル名リストをlistdirにより取得し，これを逆順にforで回して画像を表示していきます．画像はresizeにより小さくし，並べて表示できるようにしています．が，小さすぎてあまり見えません．．．

# コード
兎にも角にも上であげた2つの機能の実装はできたので，ここにコードを貼り付けておきます．

```python:boot.py
import sensor, image, time, lcd, uos
from Maix import GPIO
from fpioa_manager import fm, board_info


lcd.init(freq=15000000)
lcd.rotation(2)

# directory check
dirData = uos.getcwd()

# move to sd
if dirData != "/sd":
    uos.chdir("/sd")

# directory "pictures" is for saving pictures
dirList = uos.listdir()
dirFlag = 0
savingDir = "pictures"
for i in dirList:
    if i == savingDir:
        dirFlag = 1

# if there's no directory "pictures", make the "pictures" directory
if dirFlag == 0:
    uos.mkdir(savingDir)

# picNum is a counting variable for picture saving part
uos.chdir("/sd/pictures")
picNum = len(uos.listdir())

# run the sensor
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
clock = time.clock()
sensor.run(1)


# button settings
fm.register(board_info.BUTTON_A, fm.fpioa.GPIO1)
button_a = GPIO(GPIO.GPIO1, GPIO.IN, GPIO.PULL_UP)
fm.register(board_info.BUTTON_B, fm.fpioa.GPIO2)
button_b = GPIO(GPIO.GPIO2, GPIO.IN, GPIO.PULL_UP)
# led setting
fm.register(board_info.LED_R, fm.fpioa.GPIO0)
led_r = GPIO(GPIO.GPIO0, GPIO.OUT)
fm.register(board_info.LED_G, fm.fpioa.GPIO4)
led_g = GPIO(GPIO.GPIO4, GPIO.OUT)
fm.register(board_info.LED_B, fm.fpioa.GPIO3)
led_b = GPIO(GPIO.GPIO3, GPIO.OUT)
ledFlag = 0
led_r.value(1)
led_g.value(1)
led_b.value(1)
# variables for button chattering
countButA = 0
countButB = 0


#preview
def previewPic():
    dirName = "/sd/pictures/"
    listname = uos.listdir()
    countNum = 1
    posX = 12
    posY = 20

    preview = image.Image()
    for j in (reversed(listname)):
        prevList = dirName + j
        preview.draw_image(image.Image(prevList).resize(36,27), posX, posY)

        posX += 36
        if countNum%6 == 0:
            posX = 12
            posY += 27
        if countNum == 25:
            posX = 12
            posY = 20
            coutNum = 1
            break
        countNum += 1
        lcd.display(preview)
    countButAinP = 0
    countButBinP = 0
    while(True):
        if button_a.value() == 0:
            countButAinP += 1
            time.sleep(0.1)
        if button_b.value() == 0:
            countButBinP += 1
            time.sleep(0.1)
        if countButBinP >= 3 or countButAinP >= 3:
            break

# light the led
def litLed(ledFlag):
    ledFlag = not ledFlag
    led_r.value(ledFlag)
    led_g.value(ledFlag)
    led_b.value(ledFlag)
    return ledFlag

# main loop
while(True):
    img = sensor.snapshot()         # Take a picture and return the image.
    tmp = img.resize(180,135)       # Resize the image
    lcd.display(tmp)                # Display on LCD

    # count the number for button A
    if button_a.value() == 0:
        countButA += 1
        time.sleep(0.1)

    # button b has two functions
    if button_b.value() == 0:
        while(True):
            if button_b.value() == 1:
                # if button B plessed longer, light the led
                if countButB >= 10:
                    ledFlag = litLed(ledFlag)
                    countButB = 0
                # if button B plessed not so longer, preview mode
                if countButB >=5:
                    ledFlag = litLed(0)     # before entering preview, off the led
                    previewPic()
                    countButB = 0
                countButB = 0
                break
            countButB += 1
            time.sleep(0.1)

    # image saving part
    if countButA >= 2:
        filename = "/sd/pictures/{0:0=8}.jpg".format(picNum)
        img.save(filename)
        picNum += 1
        lcd.draw_string(10,115,filename, lcd.WHITE, lcd.BLACK)
        countButA = 0
        time.sleep(0.6)


```

# 使い方
上のコードをboot.pyとしてmicroSDに書き込み，M5stickVの電源を入れると実行されます．

M5stickVにはボタンが3つありますが，一つは電源ボタンなので，自由に使えるのは2つとなります．今回は参考にさせてもらった記事と同じく，撮影は画面と同じ面にあるボタンAで行います．

プレビューは電源ボタンと反対の面にあるボタンBを軽く長押しすることで使用できます．

ボタンBを長めに長押しすると，カメラ横のLEDを白色に点灯させることができます．が，レンズへの入り込みがあるので微妙なところです．

プレビューに入る時にLEDが点灯している場合は自動でオフになります．
プレビューから元の撮影モードへは，2つのボタンのいずれかを長押しすることで戻れます．

# ハードウェアの改造
M5stickVそのままだと画角が狭く使いにくいので，100均で売られているスマホ用広角レンズを付けてみました．100均のプラスチックは柔らかく加工しやすいので便利ですね．
本体へは両面テープで固定しています．

![toycam_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478299/fcb93856-7cd2-7166-c4ef-f0565603c56f.jpeg)

さらにM5stickVを購入したときに付いてくるマウントの一部にレゴを組み合わせ，首から下げられるようにしておきました．これでいつでも気軽に写真が撮れそう．

# 実写
ここで，このトイカメラで撮影した画像を載せておきます．
高知へのお出かけ時の写真ですね．

![00000090.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478299/6f26ca0b-7ee1-04c9-b276-29889ebba4db.jpeg)
![00000048.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478299/b95bc216-5c2f-2da7-1dec-80ec1e65083c.jpeg)
![00000060.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478299/ee03ade8-ad5d-bac7-6fb6-2415444850c5.jpeg)
![00000080.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478299/fe3a1491-7010-b90f-8a6a-84a4c30c1599.jpeg)

まあ，画質については，うん...こんなもんだよ．

# 改善案
一応改善できそうなところを書いて終わりにしておきます．

- ピントがあってない
<br>これはカメラユニットのレンズを回さないと調節できないので現在は人力オートフォーカス状態です．撮影者の気まぐれでピントが合ったりボケたりします．レンズを機械的に回せるようにすればなんとかなりそうです，が，トイカメラなのでこれぐらいが味があっていいのかもしれません．
- 傾きに対応していない
<br>これについては参考記事の方も言及されています．なんとかしたいところだ．4枚目の写真は縦向きですが，これは取り込み時に人力で編集して縦にしています．M5stickVには加速度センサが内蔵されているので，このセンサの値を利用すれば傾き対応もできそうです．
- 画質わるいよ
<br>M5stickVが採用しているカメラユニット，OV7740の上限が656x488です．これは計算すると，0.32メガピクセルです．1メガ切ってますね．なのでどうしようもないと思います．代替案としてM5unitVではOV2640を使用していることから，1600x1200，約2メガピクセルで撮影できるので，どうしても我慢できない場合はM5unitVを使いましょう．と言ってもあちら側にはLCDとバッテリー付いてないけど．

# まとめ
当初の目的のトイカメラの作製は達成し，お出かけにも使用できました．
いくつか改善できそうな点はありますが，トイカメラとしてはまあこんなもんでいいんじゃないかと思っています．
これで積んであったM5stickVに役目ができたので肩の荷も下りますね．（依然M5unitVは積んである...）
k210が売りにしているkpuを全く利用していないところで胸が痛みますが，しばらくはこの状態で使っていこうと思います．