はじめに
- 以前、M5UnitVをカメラとして使い、M5StackのLCDに表示するってのをやってみた。
- 今回は発展版として、M5UnitVで顔検出した結果をM5StickCPlusのLCDに表示してみた。
セットアップ
写真のようにM5UnitVとM5StickCPlusをGroveケーブルで繋ぐ。
前回同様に、以下を参考にさせて頂きました。ありがとうございます。
- HomeMadeGarbage さんのページがとても参考になります。構成が多少異なるので、適宜読み替えます。
- グラフィックス系はLovyanGFXライブラリを使わせてもらっています。フレームレートが向上します。
顔検出
- Sipeedの解説ページはこちら。
- Yolo v2で作ったモデルをK210向けのkmodelに変換した
face_model_at_0x300000.kfpkg
が提供されているので、これを使う。- kflash_guiを使ってフラッシュメモリに書き込むか、SDカードに格納して読み込む方法の2つがある。
- 自分はkflash_guiを使った。
- フラッシュメモリに書き込んだ場合は、
task = kpu.load(0x300000)
でメモリ領域のオフセットを指定してモデルをロードする。
モデルはここから入手可能。
ちなみに、今回ちゃんとドキュメントを読み込んでみたらこんな解説があるのを知った。
要約すると、
- MaixPyには2種類のイメージバッファがあって、使い分けている。
- 1つはLCD用の
RGB565
で、もう一つはRGB888
というKPU(AI処理)用
- 1つはLCD用の
- カメラでキャプチャすると、自動的に2箇所のバッファに画像がコピーされるが、それ以外の場合は手動でやる必要がある。
- 例えば、モデルが要求する画像サイズがLCDと異なる場合には、
img = img.resize((224, 224))
みたいな感じで一旦resizeし、img.pix_to_ai()
を実行する必要がある。
- 例えば、モデルが要求する画像サイズがLCDと異なる場合には、
ふーん。なるほど。
ただ、M5UnitVのカメラはQVGA(320×240)なので、AI処理用にリサイズすると言ってもそんなに大幅なサイズギャップにはならない気がするけど、ちゃんと使い分けできるのは良いと思った。
M5UnitV側のコード
-
sipeedのリファレンスコードをベースに、UART転送処理を追加。
- 顔検出モデルはあらかじめ
model_addr=0x300000
のフラッシュに書き込んでおく - 小さいLCDでも見やすいようにバウンディングボックスの色と太さを変更
- UnitVカメラのQVGA画像をM5StickCPlusのLCDサイズ(128, 240)に中心部分をクロップしてJpegにしたものを転送
- Jpeg画像のサイズは可変なので、パケットヘッダに画像サイズを仕込んで転送
- 顔検出モデルはあらかじめ
demo_find_face.py
# https://github.com/sipeed/MaixPy_scripts/blob/master/machine_vision/face_find/demo_find_face.py
import sensor, image, time
import KPU as kpu
import gc, sys
from machine import UART
from fpioa_manager import fm
def lcd_show_except(e):
import uio
err_str = uio.StringIO()
sys.print_exception(e, err_str)
err_str = err_str.getvalue()
img = image.Image(size=(224,224))
img.draw_string(0, 10, err_str, scale=1, color=(0xff,0x00,0x00))
def main(model_addr=0x300000, lcd_rotation=0, sensor_hmirror=False, sensor_vflip=False):
# 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)
try:
sensor.reset()
except Exception as e:
raise Exception("sensor reset fail, please check hardware connection, or hardware damaged! err: {}".format(e))
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_hmirror(1)
sensor.set_vflip(1)
sensor.run(1)
anchors = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437, 6.92275, 6.718375, 9.01025)
try:
task = None
task = kpu.load(model_addr)
kpu.init_yolo2(task, 0.5, 0.3, 5, anchors) # threshold:[0,1], nms_value: [0, 1]
while(True):
img = sensor.snapshot()
t = time.ticks_ms()
objects = kpu.run_yolo2(task, img)
t = time.ticks_ms() - t
if objects:
for obj in objects:
img.draw_rectangle(obj.rect(), color=(0,0,255), thickness=3)
img.draw_string(60, 200, "t:%dms" %(t), scale=2)
# crop image for M5StickCPlus
img2 = img.copy((56, 0, 128, 240))
img_buf = img2.compress(quality=50)
# Packetヘッダ
img_size1 = (img2.size()& 0xFF0000)>>16
img_size2 = (img2.size()& 0x00FF00)>>8
img_size3 = (img2.size()& 0x0000FF)>>0
data_packet = bytearray([0xFF,0xD8,0xEA,0x01,img_size1,img_size2,img_size3,0x00,0x00,0x00])
uart.write(data_packet)
uart.write(img_buf)
except Exception as e:
raise e
finally:
if not task is None:
kpu.deinit(task)
if __name__ == "__main__":
try:
main( model_addr=0x300000, lcd_rotation=0, sensor_hmirror=False, sensor_vflip=False)
# main(model_addr="/sd/m.kmodel")
except Exception as e:
sys.print_exception(e)
lcd_show_except(e)
finally:
gc.collect()
M5StickCPlus側のコード
-
#define LGFX_AUTODETECT
のおかげでほぼM5StackのコードのままでM5StickCPlus でも動く。素敵!- 一応、
#include <M5StickCPlus.h>
は必要。
- 一応、
#define LGFX_AUTODETECT
#include <LovyanGFX.hpp>
#include <M5StickCPlus.h>
static LGFX lcd;
typedef struct {
uint32_t length; //Jpegデータサイズ
uint8_t *buf; //Jpegデータバッファ
} jpeg_data_t;
jpeg_data_t jpeg_data;
static const int RX_BUF_SIZE = 100000;
uint8_t rx_buffer[10];
// Packetヘッダ判別用固定バイト列
static const uint8_t packet_begin[3] = { 0xFF, 0xD8, 0xEA };
void setup() {
M5.begin();
// Serial.begin(9600);
// Serial2.begin(115200, SERIAL_8N1, 21, 22); //M5Stack
Serial2.begin(115200, SERIAL_8N1, 32, 33); //M5StickC
jpeg_data.buf = (uint8_t *) malloc(sizeof(uint8_t) * RX_BUF_SIZE);
lcd.init();
lcd.setRotation(0);
lcd.setBrightness(255);
lcd.setColorDepth(16);
lcd.clear();
}
void loop() {
if (Serial2.available()) {
jpeg_data.length = 0;
int rx_size = Serial2.readBytes(rx_buffer, 10);
if (rx_size == 10) {
if ((rx_buffer[0] == packet_begin[0]) && (rx_buffer[1] == packet_begin[1]) && (rx_buffer[2] == packet_begin[2])) {
jpeg_data.length = (uint32_t)(rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
rx_size = Serial2.readBytes(jpeg_data.buf, jpeg_data.length);
lcd.drawJpg(jpeg_data.buf, jpeg_data.length);
// lcd.drawJpg(jpeg_data.buf, jpeg_data.length,0, 0, 128, 240, 0, 0, ::JPEG_DIV_NONE); //StickCPlus:128*240
}
}
}
}
動作イメージ
M5UnitVとM5StickCPlus繋いでYolo v2で顔認識した結果をLCDに出力してみた。
— Nabeshin (@desmoquattro996) August 18, 2022
25ms前後で推論できてますね。#M5Stack #M5UnitV pic.twitter.com/nxjaWiTcNH