LoginSignup
0
0

More than 1 year has passed since last update.

M5UnitVで顔検出してM5StickCPlusのLCDに表示する

Last updated at Posted at 2022-08-18

はじめに

IMG_4104.jpeg

セットアップ

写真のようにM5UnitVとM5StickCPlusをGroveケーブルで繋ぐ。

IMG_4105.jpeg

前回同様に、以下を参考にさせて頂きました。ありがとうございます。

顔検出

  • Sipeedの解説ページはこちら。
  • Yolo v2で作ったモデルをK210向けのkmodelに変換した face_model_at_0x300000.kfpkg が提供されているので、これを使う。
    • kflash_guiを使ってフラッシュメモリに書き込むか、SDカードに格納して読み込む方法の2つがある。
    • 自分はkflash_guiを使った。
    • フラッシュメモリに書き込んだ場合は、 task = kpu.load(0x300000) でメモリ領域のオフセットを指定してモデルをロードする。

スクリーンショット 2022-08-18 10.43.37.png

モデルはここから入手可能。

スクリーンショット 2022-08-18 10.52.22.png

ちなみに、今回ちゃんとドキュメントを読み込んでみたらこんな解説があるのを知った。

スクリーンショット 2022-08-18 10.59.18.png

要約すると、

  • MaixPyには2種類のイメージバッファがあって、使い分けている。
    • 1つはLCD用のRGB565で、もう一つはRGB888というKPU(AI処理)用
  • カメラでキャプチャすると、自動的に2箇所のバッファに画像がコピーされるが、それ以外の場合は手動でやる必要がある。
    • 例えば、モデルが要求する画像サイズがLCDと異なる場合には、img = img.resize((224, 224))みたいな感じで一旦resizeし、img.pix_to_ai() を実行する必要がある。

ふーん。なるほど。
ただ、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
          }
        }
    }
}

動作イメージ

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