0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M5stack Timer Camera X の camera を CircuitPython で動かしてみる

Last updated at Posted at 2024-02-11

はじめに

CAMERAはmicropython本家に対応してないがCircuitPythonなら対応してるみたいなので、M5stack Timer Camera Xで動かしてみた記録です。注意点は以下。

・M5stack Timer Camera XはESP32チップなのでCIRCUITPYドライブが使えません(https://learn.adafruit.com/welcome-to-circuitpython/the-circuitpy-drive#boards-without-circuitpy-3134472)

・M5stack Timer Camera X はcameraがOV3660なので、デフォルト設定だと色がいまいちです。(https://learn.adafruit.com/capturing-camera-images-with-circuitpython/overview)

使用機材

・M5stack Timer Camera X (https://www.switch-science.com/products/6742)
・CircuitPython 9.0.0-beta.0 (https://circuitpython.org/board/m5stack_timer_camera_x/)

1.M5stack Timer Camera XにCircuitPythonをインストール

CIRCUITPYドライブが使えないので、Web serial かesptools.pyの二択です。

Web Serial でインストールするなら Edge , chrome , Opera で https://circuitpython.org/board/m5stack_timer_camera_x/ を開いて「OPEN INSTALLER」のボタンを押します。今回は最新のCircuitPython 9.0.0-beta.0をインストールしました。

2. Thonny Editor をインストール

CIRCUITPYドライブが使えないので

3. WifiとWeb Workflow(WEB_API) の設定

M5stack Timer Camera X 上の /settings.toml でCIRCUITPY_WIFI_SSIDとCIRCUITPY_WIFI_PASSWORDを(Thonny Editorで)編集して再起動すればWifiに接続できます。

(追記)Web Workflow(WEB_API)は後述のhttpサーバと衝突しないようにCIRCUITPY_WEB_API_PORTを80以外(例えば8080)に設定します。

/settings.toml
# To auto-connect to Wi-Fi
CIRCUITPY_WIFI_SSID="yourwifissid"
CIRCUITPY_WIFI_PASSWORD="yourpassword"

# To enable modifying files from the web. Change this too!
# Leave the User field blank when you type the password into the browser.
CIRCUITPY_WEB_API_PASSWORD="passw0rd"

CIRCUITPY_WEB_API_PORT=8080

4.httpサーバ インストール

LCDがないので、簡単な表示のためにHTTP サーバを立ち上げて画像表示させます。

adafruit_httpserver というライブラリがあるのでこれをインストールしますが、CIRCUITPYドライブが使えないのでCircupコマンド使えません

https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer/releases からダウンロード&展開して(Thonny Editor か WebWorkflowで)M5stack Timer camera X 上の/libへコピーします。

そのあとテスト用にexamples/httpserver_static_files_serving.pyを M5stack Timer camera X 上の /code.py にコピーします。

/code.py
# SPDX-FileCopyrightText: 2023 Michał Pokusa
#
# SPDX-License-Identifier: Unlicense


import socketpool
import wifi

from adafruit_httpserver import Server, MIMETypes


MIMETypes.configure(
    default_to="text/plain",
    # Unregistering unnecessary MIME types can save memory
    keep_for=[".html", ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico"],
    # If you need to, you can add additional MIME types
    register={".foo": "text/foo", ".bar": "text/bar"},
)

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)

# You don't have to add any routes, by default the server will serve files
# from it's root_path, which is set to "/static" in this example.

# If you don't set a root_path, the server will not serve any files.

server.serve_forever(str(wifi.radio.ipv4_address))

/static/index.html をつくります。あとでcamera.bmp を表示するために使います。

/static/index.html
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache">
        <title></title>
    </head>
    <body>
        <img src=camera.bmp>
    </body>
</html>

適当な画像を /static/camera.bmp にコピーしたあと、M5stack Timer Camera X を再起動して、IPアドレスにhttpアクセスして表示されていれば動作確認OKです。

5. espcamera 動作確認

cameraからのテスト信号(カラーバー)を出力させ、RGB565のbitmapでファイルに保存した後、先程のhttpserverで表示させて確認します。

・cameraの初期化はほとんど固定です
・pixel_format=espcamera.PixelFormat.RGB565 で出力をRGB565BMPにしてます。

・解像度 frame_size は(BMP生成が早くなるよう解像度の一番小さな)R96X96にしてます。

・カラーバーの出力のために cam.colorbar=True にします。

・httpserverの表示部はそのままexamples/httpserver_static_files_serving.pyをコピーして使用します。

・/static/index.html も先程つくったままです。

・BMPファイルは碧色工房さんのBMPファイルフォーマットを参考にしました。

/code.py
#
# take a picture
#

import board
import espcamera

cam = espcamera.Camera(
    data_pins=board.D,
    pixel_clock_pin=board.PCLK,
    vsync_pin=board.VSYNC,
    href_pin=board.HREF,
    i2c=board.SSCB_I2C(),
    external_clock_pin=board.XCLK,
    reset_pin=board.RESET,
    pixel_format=espcamera.PixelFormat.RGB565,
    frame_size=espcamera.FrameSize.R96X96
    )

cam.colorbar=True # Test 

cam.reconfigure()
bitmap=cam.take(1)
print(type(bitmap))


#
# save RGB565BMP file
#

def RGB565toBMPfile(filename , bitmap):

    import struct
    import ulab.numpy as np


    output_file=open( filename  ,"wb" )

    # _bytes_per_row(width)
    pixel_bytes = 3* bitmap.width
    padding_bytes = (4 - (pixel_bytes % 4)) % 4
    bytes_per_row = pixel_bytes + padding_bytes

    filesize = 66 + bitmap.height * bytes_per_row


    # _write_bmp_header(output_file, filesize)
    output_file.write(bytes("BM", "ascii"))
    output_file.write(struct.pack("<I", filesize))
    output_file.write(b"\00\x00")
    output_file.write(b"\00\x00")
    output_file.write(struct.pack("<I", 66))


    #  _write_dib_header(output_file, width, height)
    output_file.write(struct.pack("<I", 40))
    output_file.write(struct.pack("<I", bitmap.width))
    output_file.write(struct.pack("<I", bitmap.height))
    output_file.write(struct.pack("<H", 1))
    output_file.write(struct.pack("<H", 16))  #bits per pixel
    for _ in range(24):
        output_file.write(b"\x00")

    output_file.write(b"\x00\xF8\x00\x00")  #RED mask
    output_file.write(b"\xE0\x07\x00\x00")  #GREEN mask
    output_file.write(b"\x1F\x00\x00\x00")  #BLUE mask

    #  _write_pixels(output_file, bitmap)
    swapped = np.frombuffer(bitmap, dtype=np.uint16)
    swapped.byteswap(inplace=True)
    output_file.write(swapped)


    output_file.close()

    return



print('Saving bitmap')

RGB565toBMPfile('/static/camera.bmp'  , bitmap )



#
# https://docs.circuitpython.org/projects/httpserver/en/latest/examples.html#id6
#
import socketpool
import wifi

from adafruit_httpserver import Server, MIMETypes


MIMETypes.configure(
    default_to="text/plain",
    # Unregistering unnecessary MIME types can save memory
    keep_for=[".html", ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico"],
    # If you need to, you can add additional MIME types
    register={".foo": "text/foo", ".bar": "text/bar"},
)

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)

# You don't have to add any routes, by default the server will serve files
# from it's root_path, which is set to "/static" in this example.

# If you don't set a root_path, the server will not serve any files.

server.serve_forever(str(wifi.radio.ipv4_address))

/static/index.html
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache">
        <title></title>
    </head>
    <body>
        <img src=camera.bmp>
    </body>
</html>

これで M5stack Timer Camera X のIPアドレスにアクセスしてカラーバーが見えたら動作確認OKです。(https://photos.app.goo.gl/ABUchkJuyR8g29aC9)

6.最後に

先程のソースコードでカラーバー出力をOFF(cam.colorbar=False)にしたら、cameraの画像がみえます。

解像度の設定やcameraの設定はここにありますのでお好みで設定してください。初期値はいまいちでした・・

/code.py
#
# take a picture
#

import board
import espcamera

cam = espcamera.Camera(
    data_pins=board.D,
    pixel_clock_pin=board.PCLK,
    vsync_pin=board.VSYNC,
    href_pin=board.HREF,
    i2c=board.SSCB_I2C(),
    external_clock_pin=board.XCLK,
    reset_pin=board.RESET,
    pixel_format=espcamera.PixelFormat.RGB565,
    frame_size=espcamera.FrameSize.R96X96
    )

cam.colorbar=False # Test 

cam.reconfigure()
bitmap=cam.take(1)
print(type(bitmap))


#
# save RGB565BMP file
#

def RGB565toBMPfile(filename , bitmap):

    import struct
    import ulab.numpy as np


    output_file=open( filename  ,"wb" )

    # _bytes_per_row(width)
    pixel_bytes = 3* bitmap.width
    padding_bytes = (4 - (pixel_bytes % 4)) % 4
    bytes_per_row = pixel_bytes + padding_bytes

    filesize = 66 + bitmap.height * bytes_per_row


    # _write_bmp_header(output_file, filesize)
    output_file.write(bytes("BM", "ascii"))
    output_file.write(struct.pack("<I", filesize))
    output_file.write(b"\00\x00")
    output_file.write(b"\00\x00")
    output_file.write(struct.pack("<I", 66))


    #  _write_dib_header(output_file, width, height)
    output_file.write(struct.pack("<I", 40))
    output_file.write(struct.pack("<I", bitmap.width))
    output_file.write(struct.pack("<I", bitmap.height))
    output_file.write(struct.pack("<H", 1))
    output_file.write(struct.pack("<H", 16))  #bits per pixel
    for _ in range(24):
        output_file.write(b"\x00")

    output_file.write(b"\x00\xF8\x00\x00")  #RED mask
    output_file.write(b"\xE0\x07\x00\x00")  #GREEN mask
    output_file.write(b"\x1F\x00\x00\x00")  #BLUE mask

    #  _write_pixels(output_file, bitmap)
    swapped = np.frombuffer(bitmap, dtype=np.uint16)
    swapped.byteswap(inplace=True)
    output_file.write(swapped)


    output_file.close()

    return



print('Saving bitmap')

RGB565toBMPfile('/static/camera.bmp'  , bitmap )



#
# https://docs.circuitpython.org/projects/httpserver/en/latest/examples.html#id6
#
import socketpool
import wifi

from adafruit_httpserver import Server, MIMETypes


MIMETypes.configure(
    default_to="text/plain",
    # Unregistering unnecessary MIME types can save memory
    keep_for=[".html", ".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".ico"],
    # If you need to, you can add additional MIME types
    register={".foo": "text/foo", ".bar": "text/bar"},
)

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)

# You don't have to add any routes, by default the server will serve files
# from it's root_path, which is set to "/static" in this example.

# If you don't set a root_path, the server will not serve any files.

server.serve_forever(str(wifi.radio.ipv4_address))

/static/index.html
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache">
        <title></title>
    </head>
    <body>
        <img src=camera.bmp>
    </body>
</html>

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?