はじめに
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)に設定します。
# 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 にコピーします。
# 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 を表示するために使います。
<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ファイルフォーマットを参考にしました。
#
# 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))
<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の設定はここにありますのでお好みで設定してください。初期値はいまいちでした・・
#
# 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))
<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>
以上