Arduino UNO Qを使った基板外観検査機製作に挑戦 Part 4: アプリケーションとしての完成

本シリーズではArduino UNO Qと組込み機械学習エンジン「Edge Impulse」を使用してプリント基板の外観検査システムの構築を目指しています。
このシリーズの記事では、Arduino UNO Q (066-5593)と組込み用機械学習エンジンEdge Impulseを使ったAI/MLのモデル学習により、プリント基板製造工程の外観チェックをAI化させる事を目的にしています。
パート1では、想定するユースケース、およびUNO Qのハード構成や高度なプロトタイピングを実現する開発環境App Labについて紹介しました。
パート2では、Uno Q等のハード部材をセットアップし、Arduino開発環境App LabとEdge Impulseの起動と実行、そしてモデル学習用の基板の準備を行いました。
パート3ではサンプル画像から形状データを取得し、それを使って機械学習モデルを学習させて、認識パフォーマンスをテストして改善に取り組みました。
この最終回では、カスタム学習済みのMLモデルを使用し、物理的な入出力を制御したアプリケーションの完成を目指します。
なお一般的に機械学習 (ML) は人工知能 (AI) の一部と見なされており、このシリーズ記事中は両方の用語が同じ意味で使用される場合があります。
パフォーマンス改善

前回の記事では、基板上のSOT23部品の認識モデルの学習を実施し、F1スコア97.8%、テスト精度96.67%を達成するところまでを説明しました。あの後、Edge Impulse Studioのライブ分類機能(Live Classification)を色々と使い込んでみて、UNO Qのライブ映像フィードでモデルを試してみました。これがうまくいけば、検証の追加、新しいサンプル、異なる照明でのテストで役に立ちそうです。

ライブ分類を使用した副産物として、新たにいくつかの画像も収集する事ができました。Studio上でこれらの画像にもラベル付けすることで、画像総数は149枚から167枚に増えました。その際、以前はラベル付けから除外していた、部分的に切り取られたSOT23部品にもラベルを付けました。最後に、以前と同じパラメータでモデルトレーニングを再実行したところ、F1スコアは99.2%を、テスト精度は100%を達成しました。

これもトレーニングデータの重要性が示されています。この先もパフォーマンス改善を目指場合、最初に検討すべきポイントはここでしょう。
オプション設定

Edge Impulse Studioのデプロイオプションには以下のようないくつかの選択肢があります。
・C++ライブラリ
・Dockerコンテナ
・Arduino UNO Q用にコンパイルされたモデルバイナリ
・etc

また、edge-impulse-linux-runneを実行することでデバイス上でのパフォーマンスを素早くテストすることも可能です。これは、既に設定されたプロジェクトに接続し、モデルが存在していなければビルド・ダウンロードを行い、最終的にこのスクリプトを実行します。その後、JSONが出力され、各オブジェクトごとにラベル、信頼度、座標が割り当てられます。
8つのSOT23トランジスタをフルに実装した基板を顕微鏡にセットした場合のJSON出力は以下の通りでした。
[{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":56,"y":160},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":136,"y":160},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":208,"y":160},{"height":8,"label":"SOT23","value":0.98828125,"width":16,"x":280,"y":168},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":136,"y":216},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":208,"y":216},{"height":24,"label":"SOT23","value":0.99609375,"width":16,"x":280,"y":216},{"height":8,"label":"SOT23","value":0.99609375,"width":16,"x":56,"y":224}]
SOT23トランジスタが1つだけ欠けている別の基板に交換すると・・・、
[{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":56,"y":160},{"height":16,"label":"SOT23","value":0.99609375,"width":8,"x":136,"y":160},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":208,"y":160},{"height":8,"label":"SOT23","value":0.609375,"width":8,"x":280,"y":160},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":56,"y":216},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":208,"y":216},{"height":16,"label":"SOT23","value":0.99609375,"width":16,"x":280,"y":216}]
想定通り、認識できたSOT23部品は7つでした。

ウェブブラウザでUNO Qを接続することで、デバイス上でリアルタイムの分類映像フィードを取得できます。edge-impulse-linux-runnerを起動すると、WebSocket URIも端末に出力でき、各種ウェブアプリケーションとの連携も可能となりました。
プロジェクト設定

まず、edge-impulse-linux-runnerが以前にダウンロードした場所(.ei-linux-runner/models)から、App Labで使用される標準の場所(.arduino-bricks/ei-models)にEdge Impulse Model(EIMファイル)をコピーして、プロジェクトの設定を開始しました。また、この機会にモデルの名前をより分かりやすいものに変更しました。

このモデルはEdge Impulse Studioのデプロイメントページからもダウンロード可能です。
この時点でApp Labでプロジェクトを新規作成することもできましたが、私たちのアプリはパート2で紹介したサンプルプロジェクトの「カメラ上のオブジェクトを検出」と似ているので、まずはこれを元に作成しようと思います。

具体的な操作としては、App Labでそのサンプルを開き、右上から「アプリのコピーと編集」を選択し、名前を入力してから「新しい作成」を選択します。

UNO QのターミナルセッションからArduinoAppsディレクトリの内容を調べ、パート2の「App Lab入門」で説明されたファイル構造を確認しました。
これを元に、今回の用途に合わせてカスタマイズしていこうと思います。
アプリケーションのカスタマイズ
name: PCB Inspection
description: This example showcases object detection within a live feed from a USB camera.
ports: []
bricks:
- arduino:video_object_detection: {}
- arduino:web_ui: {}
icon: <emoji>
注:上の<emoji>には実際には絵文字が入りますが、ウェブサイト上で表示ができないため<emoji>と表記しています。
最上位レベルから見てみると、app.yamlファイルがあり、そこにアプリケーション名、説明、使用されるブリックなどが指定されています。上記は、サンプルコードの「Detect Objects on Camera」をコピーした直後の内容です。最初のステップは、説明の部分を「部品実装が正しく行われているかプリント基板をチェックする」と修正することでした。
プロジェクトのREADME.mdファイルも同様に、プロジェクトを切り替える際の混乱を避けるために修正しました。
開発環境のApp Labの構造については、下記記事パート2の「App Lab入門」をご参照ください。

次に、assets/index.htmlに移動し「Video Generic Object Detection」というテキストを「PCB Inspection」に置き換えました。これも分かりやすさを向上させるための手軽で簡単な修正です。
ビデオオブジェクト検出ブリックの設定

まずは、独自に学習させたモデルを使用するようアプリケーションを更新しました。
App Labで「Video Object Detection Brick」を選択し、Brick Configureをクリックすると、上記のように2つの選択肢が表示されます。Edge Impulseで訓練したモデルを使うようブリックを設定するために、app.yaml に戻って更新しました。
name: PCB Inspection
description: Inspect printed circuit boards to confirm correct assembly.
ports: []
bricks:
- arduino:video_object_detection: {
variables: {
EI_OBJ_DETECTION_MODEL: /home/arduino/.arduino-bricks/ei-models/SOT23.eim
}
}
- arduino:web_ui: {}
icon: <emoji>
注:上の<emoji>には実際には絵文字が入りますが、ウェブサイト上で表示ができないため<emoji>と表記しています。
ここでは、先にコピーしたカスタムモデルを指定するためにBrick用の変数を設定します。
ついに成功!

この時点で、アプリを起動してみることにし、App Labから起動しました。
アプリケーション起動が完了し、コンソールのテキストが緑色に変わりました。

ブラウザがUNO Qと繋がり、ウェブインターフェースへの読み込みに成功!アプリケーションがテスト基板にハンダ付けされた7つのSOT23デバイスを正しく識別しました。
しかしなぜかボックス選択ではなく小さな円が表示されました。その理由は、私たちが使っているFOMO(Faster Objects, More Objects)モデルが重心を推論出力として提供しているからです。
アプリケーションでSOT23デバイスを検出できるようになったら、MCUの部分に進みましょう。
物理的な入力と出力の統合

UNO Q MCUを使用するApp Labのサンプル(例えば、このプラットフォーム向けに更新された定番のBlinkサンプルなど)を見てみると、MCUコードが格納されているスケッチディレクトリが追加されていることがわかります。
そこで、最初のステップとしてスケッチディレクトリを作成しました。その中にはsketch.inoファイルが必要で、これは従来のArduinoボードを使ったことがある人なら誰でも知っているファイルです。また、sketch.yamlファイルも必要です。
profiles:
default:
platforms:
- platform: arduino:zephyr
libraries:
- Arduino_RouterBridge (0.2.2)
- dependency: Arduino_RPClite (0.2.0)
- dependency: ArxContainer (0.7.0)
- dependency: ArxTypeTraits (0.3.2)
- dependency: DebugLog (0.8.4)
- dependency: MsgPack (0.4.2)
default_profile: default
sketch.yamlには、RTOS Zephyrが動作するMCUプラットフォームの詳細を始め、依存関係のリストも含まれています。
つまりこれはMCUのパッケージマネージャーのようなものです。ライブラリを個別にダウンロードしてコピーする代わりに、ここにリストを載せれば、App Labが残りを処理します。
#include "Arduino_RouterBridge.h"
int buttonPin = 5;
int redPin = 4;
int greenPin = 7;
int bluePin = 8;
int buzzerPin = 12;
int buttonState;
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
pinMode(buzzerPin, OUTPUT);
Bridge.begin();
Bridge.provide("setTowerLight", setTowerLight);
Bridge.provide("setBuzzer", setBuzzer);
}
void setTowerLight(bool red, bool green, bool blue) {
digitalWrite(redPin, red ? HIGH : LOW);
digitalWrite(greenPin, green ? HIGH : LOW);
digitalWrite(bluePin, blue ? HIGH : LOW);
}
void setBuzzer(bool state) {
digitalWrite(buzzerPin, state ? HIGH : LOW);
}
sketch.inoには、MPUやMCUアプリケーションパーツの統合に便利なリモートプロシージャコール(RPC)サービスを提供するarduinoルーターのヘッダーを含めました。また、いくつかの変数を定義し、setup()関数内で使用予定のIOピンを設定しました。
MPUからMCUへのRPC統合は、Bridge.provide()を使用してPythonからのコールをC++関数にマッピングするだけで簡単に実現しました。上記の機能で、タワーライトの赤・緑・青のLEDを切り替えたり、ブザーのオン・オフを切り替えたりできます。
void loop() {
int reading = digitalRead(buttonPin);
if (reading != lastButtonState) {
// reset the debouncing timer
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// if the button state has changed:
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
Bridge.notify("buttonPressed");
}
}
}
lastButtonState = reading;
}
メインのloop関数では、内部プルアップ抵抗を備えた入力として設定されたIOピンを読み取ります。このピンは産業用のノーマルオープンプッシュボタンを介してグランドに接続されています。スイッチはノイズが多いため、チャタリングのデバウンス処理も組み込みました。
その後、Bridge.notify()を使用して適切な関数を呼び出すことで、ボタン押下とPythonスクリプトを関連付けることができました。
もちろん、これはごく簡単なMCUの制御を例にしています。
私たちが使用しているSTM32は、多様なディスプレイの駆動、あらゆる種類のバスプロトコルの実装、無限のセンサーやアクチュエーターの統合、DSPアルゴリズムの実装などのリアルタイム処理など、はるかに高度な作業に対応しており、それらの際にも上記が活用できるでしょう。
Pythonのアップデート
Python側では、ボタンが押されたらSOT23デバイスの数をカウントし、合格の場合はタワーライトを緑に、不合格の場合は赤に点灯させ、さらにブザーを鳴らすという処理が必要でした。
この記事執筆時点では、ビデオオブジェクト検出ブリックにバグがあり、同じ種類のオブジェクトが複数存在する場合、最後のオブジェクトしか検出されないという問題がありました。しかし、回避策は簡単で、Edge Impulseモデルに直接接続し、前述のJSON出力を解析して、返されたSOT23アイテムの数をカウントすれば解決できました。Arduinoチームはこの問題を迅速に確認しており、この記事の公開時には既に修正版がリリースされている可能性があります。
ここではPythonスクリプトを逐語的に解説するのではなく、主要な部分のみを説明します。詳細については、GitHubリポジトリをご覧ください。
from arduino.app_utils import *
スクリプトの冒頭で、MCUと通信できるように、対応するarduino-routerの機能をインポートする必要がありました。また、Edge Impulseモデルに直接接続するため、JSON解析ライブラリとWebSocketライブラリもインポートしました。
ここで改めて述べておきますが、App Labがバックグラウンドで依存関係を管理してくれるため、ライブラリを手動でインストールする必要はありませんでした。インポートを更新するだけで済みました。
sot23_fitted = 8
checkPCBA = False
私たちは、検出されるべきSOT23デバイスの数を閾値として定義し、ボタンが押されたときにTrueに設定される変数を初期化しました。
def buttonPressed():
global checkPCBA
print("Button pressed!")
checkPCBA = True
次にBridge.provide()を使用し、MCUからのコールをPython関数にマッピングしました。
Bridge.provide("buttonPressed", buttonPressed)
次に、フレーム内のすべてのオブジェクトが検出されたたびにメインループによって実行される関数について説明します。
# Send detections to the UI.
for key, value in detections.items():
# Multiple detections for the same object type will be sent as separate messages
for detection in value:
entry = {
"content": key,
"confidence": detection.get("confidence"),
"timestamp": now
}
ui.send_message("detection", message=entry)
# If requested evaluate the PCBA based on the number of detected SOT23 components
if checkPCBA:
if len(detections.get('SOT23', [])) == sot23_fitted:
Bridge.call("setTowerLight", False, True, False)
time.sleep(3)
Bridge.call("setTowerLight", False, False, False)
else:
Bridge.call("setTowerLight", True, False, False)
Bridge.call("setBuzzer", True)
time.sleep(3)
Bridge.call("setBuzzer", False)
Bridge.call("setTowerLight", False, False, False)
checkPCBA = False
Detect Objects on Cameraサンプルの関数を拡張し、ウェブUIに修正に加え、checkPCBAがTrueであればEdge Impulseモデルが出力する辞書にSOT23がいくつ存在するかをチェックするようにしました。sot23_fittedが一致すれば合格、それ以外は不合格となります。それぞれのケースで、適切なブリッジコールを行い、MCU上でC++関数を呼び出します。その結果、合格は3秒間の緑色ランプが点灯し、不合格の場合は3秒間の赤色ランプ点灯とブザーの鳴動を実行します。
以上です。
実行、デバッグ、起動

App Lab IDE経由でアプリケーションを起動する以外にも、arduino-app-cliを使用して同様の操作を行う事ができます。つまり、IDEは非常に便利ですが、それに縛られる必要はなく、Arduino App CLIツールと併せて、お好みのエディタを自由に使用できます。
$ arduino-app-cli app start pcb-inspection
アプリケーションをApp CLIから実行させます。

App Labはブリック機能にDockerコンテナを使用しているため、Docker CLIを使用してコンテナログを表示できます。ただし、App Lab CLIを使用してログを表示することもできます。App Lab CLIの利点は、デバッグ中に追加されたprint文など、Pythonスクリプトからの出力も確認できることです。さらに、スケッチ内でMonitor.print()を使用することで、MCUコードからデバッグ出力を生成することもできます。
$ arduino-app-cli app logs pcb-inspection --all --follow
以下はアプリケーションのログ出力です。

CLIを使って利用可能なアプリケーションを以下のようにリストアップできます:
$ arduino-app-cli app list
ユーザー名としてpcb-inspectionというユーザを見ることができます。

そして、これを自動起動するように設定します
$ arduino-app-cli properties set default user:pcb-inspection
デモ

今回一連の記事で作成したこの作成物一式はEmbedded World 2026のRSブースでデモ展示されます(2026年3月12日に終了)。使用した部品群は以下の通りです。
- Arduino UNO Q (066-5593)
- Arduino 4 Relayシールド (875-0292)
- Fortex デジタル顕微鏡 HD5-MIC USB 2.0 (028-3421)
- RS PRO カラーLED表示灯 (271-2221)
- RS PRO パネル実装プッシュボタン (145-0613)
- RS PRO ABS 汎用エンクロージャー (201-0177)
- RS PRO ACアダプタ(40W 12V DC) (251-9226)
- RS PRO 構造材・フレーム – 30mm profile, 6mm groove
- StarTech.com UNO Q用 USB Cドッキングステーション (236-8297)
- Alfatronix カーチャージャー USB Cソケット (283-6476)
- Werma 圧電ブザー(60 dB, 61 mm) (224-0665)
4 Relayシールドは信号灯のLED赤、緑、青(結果的には未使用)とブザーを駆動するために使っています。このシールドには入力が4つ(アナログ2、デジタル2)があり、それぞれ3ピンのMolex 2.54mmピッチKKコネクタに接続されており、5Vとアース端子も備えています。デジタル入力の一つがテスト実行用のプッシュボタンの入力に使われています。
堅牢なフレームには30x30mmのRS PROアルミフレームが使用され、M6タップ押出端に固定されたコーナーキューブで連結されました。
おわりに
センシングデータの入力とマシン制御用の出力を1台で集約できるArduino UNO Qを「App Lab」「Edge Impulse」と組み合わせて使用することで、産業設備システム上の物理的な入力と出力をリアルタイムで統合し、Linuxに対応した豊富なライブラリ資産を活用し、機械学習を使って個別事象にカスタムトレーニングさせるような高度な産業DXアプリケーションを、手軽に短期間でプロトタイピグすることができました。
今回のシリーズでは産業システムの可能性の一部のみを実現した。これをベースに、例えばQRコードスキャン機能の実装、品質管理システムとの統合、ベルトコンベア等の機械制御機器との連携、それら全てのデータのモデルトレーニングと最適化といった活用が考えられます。
— Andrew Back
コメント