M5StickCplus買いました。最初やりたかったのはBLEをプログラムして、ChromeのWebBluetoothから接続してみたかったから。BLE通してジャイロ情報送って簡単なアプリ作って。この時点でPlusじゃなくても良かったなと思ったけど、せっかく結構きれいなLCDが付いてるんで、カメラ接続してみたくなり、UnitV AI Camera買ってみた。
なぜかカメラだけのHatがないんですよね。M5CameraとかTimerCameraになってしまう。UnitVはM5StickVから色々取り除いた廉価版。AI処理用のKPUというのが入っているので簡単なモデルなら動くらしい。いずれやってみよう。
ここでは、StickCにCamera映像を描画することが第一目的だが、せっかくなのでUnitVに標準で入っている顔認識(tiny yolo v2)もついでにやってみたという内容。
まずはUnitV側のプログラム(MicroPython)
import time
import KPU as kpu
import sensor
import struct
from fpioa_manager import fm
from machine import UART
fm.register(35, fm.fpioa.UART1_TX, force=True)
fm.register(34, fm.fpioa.UART1_RX, force=True)
uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=4096)
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.run(1)
model = kpu.load(0x300000) # Load Model File from Flash
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987,
5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
kpu.init_yolo2(model, 0.5, 0.3, 5, anchor)
def transfer(img):
header = struct.pack('>BBH', 0, 0, img.size())
uart.write(header)
uart.write(img)
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
bbox = kpu.run_yolo2(model, img)
if bbox:
for i in bbox:
img.draw_rectangle(i.rect(), thickness=4)
transfer(img.resize(160, 120).compress(10))
print(clock.fps())
UnitVとStickCとは4Pinケーブルで接続してシリアル通信するので、UARTの設定をする。次にカメラ(sensor)の設定、KPUの設定、ループ内でカメラ>yolo処理>転送の流れ。
ポイントとしては、QVGAのまま転送すると超遅いので160x120にリサイズして、それをJPEG圧縮して転送している。だいたい1.5kbytesくらいになるので5fps位になる。
単純にカメラ処理だけなら10fps。KPU処理入れると8fpsくらい。転送が一番やばい。この時点で素直にM5StickVにしとけばと思ったが、遊びだしこの不自由さがまたたまらない!
最初からQQVGA(160x120)でやればと思ったが、yolo2がそのサイズを受け付けなかった。
そして以下がStickCplus側のプログラム
#include <M5StickCPlus.h>
#include <esp_timer.h>
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_STDIO
#define STBI_ONLY_JPEG
#include "stb_image.h"
void *buffer = NULL;
TFT_eSprite canvas = TFT_eSprite(&M5.Lcd);
void *decode(void *buffer, int length, int *w, int *h)
{
int comp = 3;
stbi_uc *image = stbi_load_from_memory((stbi_uc *)buffer, length, w, h, &comp, comp);
uint8_t *src = (uint8_t *)image;
uint16_t *dst = (uint16_t *)image;
for (int i = 0; i < *w * *h; i++)
{
const uint8_t r = *src++;
const uint8_t g = *src++;
const uint8_t b = *src++;
*dst++ = canvas.color565(r, g, b);
}
return image;
}
void setup()
{
M5.begin();
M5.Axp.ScreenBreath(13);
M5.Lcd.setRotation(3);
Serial2.begin(115200, SERIAL_8N1, 32, 33);
canvas.createSprite(M5.Lcd.width(), M5.Lcd.height());
canvas.setSwapBytes(false);
canvas.setTextSize(2);
size_t size = 4 * 1024;
buffer = malloc(size);
M5.Lcd.printf("alloc: %d", size);
}
void loop()
{
static int64_t t0 = 0;
if (Serial2.available() && Serial2.read() == 0 && Serial2.read() == 0)
{
int64_t t1 = esp_timer_get_time();
int size = (Serial2.read() << 8) + Serial2.read();
size = Serial2.readBytes((uint8_t *)buffer, (size_t)size);
int64_t t2 = esp_timer_get_time();
int w, h;
void *image = decode(buffer, size, &w, &h);
int64_t t3 = esp_timer_get_time();
canvas.pushImage(canvas.width() - w, (canvas.height() - h) / 2, w, h, (uint16_t *)image);
free(image);
int64_t t4 = esp_timer_get_time();
canvas.setCursor(0, 0);
canvas.printf("S %d\n", size);
canvas.printf("R %d\n", (t2 - t1) / 1000);
canvas.printf("D %d\n", (t3 - t2) / 1000);
canvas.printf("I %d\n", (t4 - t3) / 1000);
canvas.printf("T %d\n", (t3 - t0) / 1000);
if (t0)
{
int ms = (t1 - t0) / 1000;
canvas.printf("F %d\n", ms);
canvas.printf(" %.1f\n", 1000.0f / ms);
}
canvas.pushSprite(0, 0);
t0 = t1;
}
}
色々書いてあるけど、やっていることは受信したらcanvasへ描画するだけ。
ポイントは、jpegデコードに
nothing/stbのstb_image.hを使っていること。
M5ライブラリ内にも実はJpegデコードがあるがなぜかコメントアウトされている。無理やり復活させるのも気持ち悪いので、昔からよく利用しているstb_imageを使うことにした。
実はstbの前に、
picojpeg 使ってやろうとした。メモリが少ない組み込み系ではこっちがいいかなと思った。PCでテストする分には問題なかったが実際組み込んでみると何故かMCU単位で変になる。UnitVのJpegエンコードとの相性かもしれない。
stb_imageは問題なかったし、パフォーマンスもstbの方が若干良かった。
UnitVのmicropython(OpenMV)には_threadが一応実装されている?ので、カメラとYOLOをメイン。転送をスレッド化すれば並列処理されていいのでは?と思った。
で実際に_threadで転送部を回して、queueがないので代わりにuheapqを介してイメージデータをスレッドに渡すようにしてみた。安定させるためにメイン、スレッドともにtime.sleep_ms(1)が必要だった。がしかし、スピードは変わらなかった。。。
内部実装がただのコルーチンなのではないかと思った。それぞれにsleep入れないと処理が回らないのもそれでうなずける。コルーチンのyield的なものが必要なのだろうと。
0 件のコメント:
コメントを投稿