Arduino Portenta H7で作る高性能エッジAIカメラ:OpenMV + TensorFlow Lite
部品と用品
| Arduino Portenta H7 (ABX00042) | STM32H747XI: Cortex-M7 480MHz + Cortex-M4 240MHz, 2MB Flash, 1MB RAM | 1 |
| Portenta Vision Shield (ABX00043) | Himax HM01B0 320x320 カメラ、マイクロフォン、microSDスロット、イーサネット | 1 |
| Portenta Breakout Board (ABX00044) - オプション | GPIO拡張、産業用コネクタ | 1 |
| USB-C ケーブル (データ通信対応) | 1 |
このプロジェクトについて
Arduino Portenta H7とVision Shieldを組み合わせ、OpenMVエコシステムとTensorFlow Lite for Microcontrollersを活用した高性能エッジAIカメラを構築します。デュアルコアアーキテクチャを活かし、M7コアでカメラ捕捉・前処理・推論、M4コアでI/O制御・通信・アクチュエータ駆動を並列実行。産業用途レベルの信頼性と性能を備えたコンピュータビジョンデバイスです。
Portenta H7の特長
- デュアルコア: Cortex-M7 (480MHz, FPU, DSP) + Cortex-M4 (240MHz, FPU)。非対称マルチプロセッシング(AMP)対応
- 大容量メモリ: 2MB Flash + 1MB RAM (M7: 512KB TCM + 512KB AXI SRAM, M4: 384KB TCM + 16KB SRAM)
- 高速I/O: カメラインターフェース(DCMI)、イーサネット、USB-HS、CAN-FD、複数UART/SPI/I2C
- セキュリティ: セキュアブート、ハードウェア暗号化、TRNG、セキュアエレメント(ATECC608A)
- OpenMV対応: MicroPythonベースのマシンビジョンフレームワークが公式サポート
Vision Shieldの特長
- Himax HM01B0: 320x320 モノクロ/カラー、グローバルシャッター、120fps
- オンボードMEMSマイクロフォン (PDM)
- microSDカードスロット (データログ・モデル保存用)
- イーサネットPHY (RJ45、PoE対応オプション)
開発環境セットアップ
Option A: OpenMV IDE (推奨・最速)
- OpenMV IDE をダウンロード・インストール
- Portenta H7をUSB接続、IDEで「Connect」→ ファームウェア自動書き込み
- MicroPython + OpenMVライブラリで即座に開発開始
Option B: Arduino IDE + TensorFlow Lite
- Arduino IDE で「Arduino Portenta」ボードパッケージをインストール
- ライブラリマネージャーで「Arduino_TensorFlowLite」「Arduino_OV767X」等をインストール
- C++で推論コード記述(より低レベル制御可能)
実装例1:OpenMV (MicroPython) で物体検出
# main.py - OpenMV on Portenta H7
import sensor, image, time, tf, math, uos, gc
from pyb import LED
# カメラ初期化
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA) # 320x240
sensor.set_windowing((240, 240)) # 正方形クロップ
sensor.skip_frames(time=2000)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
# TensorFlow Liteモデル読み込み (人検出例)
# モデルは https://github.com/armaanpriyadarshan/Person-Detection-OpenMV 等から取得
net = tf.load("person_detection.tflite", load_to_fb=True)
labels = ["no_person", "person"]
clock = time.clock()
red_led = LED(1)
while True:
clock.tick()
img = sensor.snapshot()
# 推論実行
for obj in tf.classify(net, img, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5):
predictions = list(obj.output())
max_idx = predictions.index(max(predictions))
confidence = predictions[max_idx]
if confidence > 0.7 and labels[max_idx] == "person":
# 検出領域描画
img.draw_rectangle(obj.rect(), color=(255, 0, 0), thickness=2)
img.draw_string(obj.x(), obj.y() - 10,
"Person: {:.1f}%".format(confidence * 100),
color=(255, 0, 0), scale=1.5)
red_led.on()
else:
red_led.off()
# FPS表示
img.draw_string(5, 5, "FPS: {:.1f}".format(clock.fps()), color=(0, 255, 0))
# フレームバッファ表示 (OpenMV IDEのフレームバッファビューアで確認)
# 実際のデプロイ時はLCDやMJPEGストリーミングで出力
実装例2:Arduino IDE (C++) でTensorFlow Lite推論 + デュアルコア活用
M7コア側 (メイン・推論)
/* M7 Core: Camera Capture + TensorFlow Lite Inference */
#include
#include
#include
#include
#include
#include "model_data.h" // xxd -i model.tflite > model_data.h で生成
// 共有メモリ領域 (M7-M4間通信用)
__attribute__((section(".shared_mem"))) volatile struct {
int detected_class;
float confidence;
int bbox[4]; // x, y, w, h
uint32_t timestamp;
bool new_data;
} shared_result;
// テンソルアリーナ (推論用作業メモリ)
constexpr int kTensorArenaSize = 256 * 1024;
alignas(16) uint8_t tensor_arena[kTensorArenaSize];
// TFLiteセットアップ
tflite::AllOpsResolver resolver;
const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* input = nullptr;
TfLiteTensor* output = nullptr;
const char* labels[] = {"background", "person", "car", "bike"};
void setup() {
Serial.begin(115200);
bootM4(); // M4コア起動
// カメラ初期化
Camera.begin(CAMERA_RGB565, CAMERA_QVGA, 30);
// TFLiteモデル読み込み
model = tflite::GetModel(g_model_data);
static tflite::MicroInterpreter static_interpreter(model, resolver, tensor_arena, kTensorArenaSize);
interpreter = &static_interpreter;
interpreter->AllocateTensors();
input = interpreter->input(0);
output = interpreter->output(0);
// RPC初期化 (M4との通信)
RPC.begin();
Serial.println("M7: Ready for inference");
}
void loop() {
// フレーム取得
Camera.readFrame(input->data.uint8);
// 推論実行
if (interpreter->Invoke() == kTfLiteOk) {
// 結果解析
float max_conf = 0;
int max_idx = 0;
for (int i = 0; i < output->dims->data[1]; i++) {
float conf = output->data.f[i];
if (conf > max_conf) {
max_conf = conf;
max_idx = i;
}
}
// 共有メモリに結果書き込み (アトミック操作)
__disable_irq();
shared_result.detected_class = max_idx;
shared_result.confidence = max_conf;
shared_result.timestamp = millis();
shared_result.new_data = true;
__enable_irq();
// 高信頼度検出時のみM4にRPC通知
if (max_conf > 0.8 && max_idx != 0) {
RPC.call("onDetection", max_idx, max_conf).as();
}
}
// ターゲットFPS制御
delay(33); // ~30fps
}
M4コア側 (I/O・通信・アクチュエータ)
/* M4 Core: I/O Control + Communication + Actuators */
#include
#include
// M7からの検出通知を受けるRPC関数
void onDetection(int class_id, float confidence) {
// ここでアクチュエータ制御、LED点灯、アラーム出力等
digitalWrite(LEDB, HIGH); // 青色LED点灯
// シリアル/イーサネット/MQTTで外部通知
Serial.printf("M4 ALERT: Class=%d, Conf=%.2f\n", class_id, confidence);
// 例: サーボでカメラ方向追従、ブザー鳴動、リレー制御等
delay(100);
digitalWrite(LEDB, LOW);
}
// RPC関数登録
RPC.bind("onDetection", onDetection);
void setup() {
pinMode(LEDB, OUTPUT);
pinMode(LEDR, OUTPUT);
Serial.begin(115200);
RPC.begin();
Serial.println("M4: I/O Core Ready");
}
void loop() {
// M4側の独立タスク: センサー読み取り、ボタン監視、通信維持等
static uint32_t last_heartbeat = 0;
if (millis() - last_heartbeat > 5000) {
last_heartbeat = millis();
// ハートビート送信、システムヘルスチェック等
}
// 共有メモリポーリング (RPC非同期の補完)
// extern volatile SharedResult shared_result;
// if (shared_result.new_data) { ... }
delay(10);
}
モデルの準備と量子化
- PCでTensorFlow/Kerasでモデル訓練 (MobileNetV2, EfficientNet-Lite等)
- TensorFlow Lite変換 + 量子化 (Post-training quantization / Quantization-aware training)
xxd -i model.tflite > model_data.hでC配列化- Portentaのメモリ制約内に収まるようモデルサイズ調整 (< 1MB推奨)
パフォーマンス指標 (実測値目安)
| モデル | 入力解像度 | 推論時間 (M7) | 精度 (mAP/Acc) | メモリ使用量 |
|---|---|---|---|---|
| MobileNetV2-SSD | 224x224 | ~45ms | 75% mAP (COCO) | ~1.2MB |
| EfficientDet-Lite0 | 320x320 | ~85ms | 78% mAP | ~2.1MB |
| YOLOv4-tiny (TFLite変換) | 320x320 | ~120ms | 80% mAP | ~3.5MB |
| カスタム分類 (4クラス) | 240x240 | ~12ms | 95% Acc | ~300KB |
産業グレードへの拡張
- PoE給電: Vision ShieldのPoEオプションで単一ケーブル配線
- セキュアブート: ファームウェア改ざん防止、IP保護
- 冗長性: デュアルコアでウォッチドッグ相互監視
- ログ・トレーサビリティ: microSDに推論ログ・画像保存、GDPR対応
- OTAアップデート: 差分アップデートでダウンタイム最小化
- エッジクラスタリング: 複数PortentaをMQTT/ROS2で協調動作
トラブルシューティング
| 問題 | 解決策 |
|---|---|
| カメラ初期化失敗 | Vision Shieldのフラットケーブル確認、I2Cアドレス競合チェック |
| M4コアが起動しない | bootM4() 呼び出し確認、M4用リンカスクリプト設定 |
| 推論が遅い/メモリ不足 | モデル量子化(int8)、入力解像度縮小、テンソルアリーナ拡大 |
| RPC通信エラー | 共有メモリセクション属性確認、割り込み無効化区間最小化 |
| 発熱・スロットリング | ヒートシンク装着、フレームレート制限、M4側処理分散 |
製造プロセス
- ボタン:古代の留め具から現代イノベーションへ
- Raspberry Pi、PIRセンサー、MQTTで作るモーション制御AWS IoTボタン
- UnifiedWater v1 - 完全なArduino水質センサーキット
- GSM接続対応ポータブル投票システム「Mobile Vote Box」
- モダンジュークボックス:Raspberry Pi & Arduino LEDミュージックプレーヤー
- PCB試作メーカー選びの完全ガイド:失敗しないための5つの評価基準
- バンドソー最適鋸挽き速度の選定:実践ガイド
- 適切な潜水ポンプの選び方:エキスパートのヒントと主要機能
- 精密加工に立形旋盤センターを選ぶべき理由
- 機器用インストルメントノブの選び方:グリップ・素材・ポインター・強化プラ
- ボタンソケットキャップスクリュー選びの実践ガイド:材質・長さ・ネジ・防犯機能