Skip to content

Repository files navigation

mruby-gpu

mruby から Raspberry Pi 5 の GPU(VideoCore VII / Vulkan Compute Shader)を叩く mrbgem。
ベクトル・行列演算 / カメラキャプチャ / GPU 顔検出 / 2 層 MLP の学習と推論 まで mruby スクリプトで書けます。

🎤 RubyKaigi 2026 Lightning Talk"mruby-gpu: The Joy of Talking to Hardware in Ruby"


ドキュメント

ファイル内容
docs/PERFORMANCE.mdボトルネック分析と高速化アイデア
docs/GPU_VS_CPU_ANALYSIS.mdGPU vs CPU 推論の比較調査・なぜ V3D で遅いのか

機能

クラス概要
GPUVulkan Compute Shader によるベクトル演算・行列演算
CameraV4L2 経由のカメラキャプチャ(USB / CSI カメラ対応)
FaceDetectorNCNN + Vulkan による GPU 顔検出(UltraFace-slim モデル)
SkinDetector肌色検出による人数カウント(学習モデル不要・CPU 軽量)
DisplaySDL2 ウィンドウへのリアルタイム表示・枠描画

推論モデル・検出器

mruby-gpu は 3 種類の検出器を提供します。外部ライブラリに乗るもの完全自作のルールベースmruby 上で学習から推論まで組むもの がそれぞれ含まれています。

#検出器推論エンジンモデル出自外部依存
1FaceDetectorNCNN (Tencent)UltraFace-slim (Linzaer)libncnn + Vulkan / NEON
2SkinDetector自作ロジックベースなし(libc のみ)
3MNIST MLP自作 mruby + Compute Shader自分で学習なし(自前 shader のみ)

FaceDetector — NCNN + UltraFace-slim(外部)

Tencent の軽量推論エンジン NCNN に、Linzaer 氏の 1MB 級顔検出モデルを載せた構成。use_gpu: で Vulkan(GPU) / NEON(CPU) を切り替え可能。

SkinDetector — 完全自作・学習不要

RGB → YCbCr 変換 → 肌色色域の閾値判定 → グリッド化 → flood-fill で連結領域抽出。学習モデルゼロ、libc のみで動く超軽量実装。

③ MNIST MLP — mruby + 自作 Compute Shader で学習から推論まで

外部 ML フレームワーク一切なし。2 層 MLP(784 → 128 ReLU → 10)を mruby スクリプトで組み、GPU.matmul / add / relu(すべて自前 Vulkan compute shader)で実行。


動作環境

  • ボード: Raspberry Pi 5
  • OS: Ubuntu 24.04 (arm64)
  • GPU: VideoCore VII(V3D 7.1)— Vulkan 1.2 対応
  • カメラ: USB Webカメラ(UVC準拠、例: Logitech C920)

依存関係のインストール

# Vulkan / SDL2 / ビルドツール
sudo apt install -y libvulkan-dev vulkan-tools libsdl2-dev cmake build-essential git
# NCNN (GPU 推論フレームワーク) — ソースからビルド
git clone --depth=1 https://github.com/Tencent/ncnn.git ~/ncnn
cd~/ncnn
git submodule update --init --depth=1
mkdir build &&cd build
cmake \
-DNCNN_VULKAN=ON \
-DNCNN_BUILD_EXAMPLES=OFF \
-DNCNN_BUILD_TOOLS=OFF \
-DNCNN_BUILD_BENCHMARK=OFF \
-DNCNN_BUILD_TESTS=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
..
make -j1 # メモリ不足防止のため -j1 推奨(RAM 2GB 環境)
sudo make install

注意: Raspberry Pi 5 (RAM 2GB) では -j4 でビルドするとフリーズします。
事前にスワップを追加してください:

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

セットアップ

1. モデルのダウンロード

cd /home/ubuntu/work/mruby-gpu
mkdir -p models
wget "https://raw.githubusercontent.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/master/ncnn/data/version-slim/slim_320.param" \
-O models/ultraface-slim.param
wget "https://raw.githubusercontent.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/master/ncnn/data/version-slim/slim_320.bin" \
-O models/ultraface-slim.bin

2. シェーダーのコンパイル

cd shader
make
cd ..

3. mruby のビルド

cd /home/ubuntu/work/mruby
rake MRUBY_CONFIG=gpu

ビルドが成功すると build/host/bin/mruby が生成されます。


LT デモ用コマンド

以下を順番に実行すると LT のストーリーに沿ったデモになります。

cd /home/ubuntu/work/mruby-gpu
MRUBY=/home/ubuntu/work/mruby/build/host/bin/mruby

Step 1: GPU で一発計算(mruby から GPU を叩けることを見せる)

$MRUBY -e 'GPU.init("shader"); a = GPU.array([1.0, 2.0, 3.0]); b = GPU.array([10.0, 20.0, 30.0]); puts GPU.add(a, b).head(3).inspect'
[11.0, 22.0, 33.0]

Step 2: 100 万要素の GPU ベクトル加算(GPU の並列計算力を見せる)

$MRUBY demo/vector_add.rb
1,000,000 elements x 5 runs Avg: 5.06 ms

Step 3: カメラ + 顔認識 — GPU モード(GPU 推論を見せる)

DISPLAY=:0 $MRUBY demo/face_demo.rb fast gpu

/dev/dri の権限エラーが出る場合(video / render グループ未反映時):

sg video -c "sg render -c 'DISPLAY=:0 /home/ubuntu/work/mruby/build/host/bin/mruby demo/face_demo.rb fast gpu'"

→ ~6 FPS で動く。「GPU で顔認識が動いた!」

Step 4: カメラ + 顔認識 — CPU モード(適材適所を見せる)

DISPLAY=:0 $MRUBY demo/face_demo.rb fast cpu

権限エラーが出る場合:

sg video -c "sg render -c 'DISPLAY=:0 /home/ubuntu/work/mruby/build/host/bin/mruby demo/face_demo.rb fast cpu'"

~30 FPS で動く。 「小さなモデルは CPU NEON が得意。使い分けられるのが mruby-gpu の強み。」

ESC キーまたはウィンドウの × ボタンで停止します。

モード一覧

引数1引数2速度検出距離用途
fastcpu30 FPS~3mデモ・プレゼン
fastgpu6 FPS~3mGPU 推論デモ
countcpu20 FPS~7m会場の人数カウント
countgpu1 FPS~7mGPU + タイル処理

タイル処理の仕組み(count モード):
640×480 の画像を 320×240 の5タイルに分割して各タイルを個別に推論することで、
実効解像度を2倍にして遠距離の小さな顔(17px → 34px)を検出可能にしています。

[全体] + [左上] + [右上] + [左下] + [右下] + [中央] = 6回推論 / フレーム
↑ 各タイルは元画像の1/4領域 = モデル入力と同サイズで2倍ズーム相当

操作方法

操作動作
ESC キーデモを終了してサマリーを表示
ウィンドウの × ボタンデモを終了してサマリーを表示
Ctrl+C(ターミナル)強制終了(サマリーは表示されません)

終了時のサマリー表示例

====================================================
終了サマリー
総フレーム数 : 300
最大同時検出 : 5 人
平均処理時間 : 165.0 ms/frame (6.1 FPS)
====================================================
  • 最大同時検出: セッション中にカメラ映像内に映っていた最大人数

ターミナル出力(30フレームごと)

Frame #30 | 2 人検出 | 163.6ms (6.1 FPS) | 最大検出: 2 人
[0] (120, 80) 140x200 score=0.99
[1] (380, 60) 130x190 score=0.97

パフォーマンス(Raspberry Pi 5 実測値)

処理fast モードcount モード
V4L2 キャプチャ~2ms~2ms
YUYV → RGB 変換 (C)~2ms~2ms
UltraFace 推論 (NCNN + Vulkan)~160ms × 1~160ms × 6
NMS・枠描画・表示<2ms<5ms
合計~165ms (6.1 FPS)~970ms (1.0 FPS)
検出距離~3m~7m

初回フレームのみ Vulkan シェーダーコンパイルで約 200ms かかります。


mruby API リファレンス

GPU モジュール

GPU.init("shader")# シェーダーディレクトリを指定して初期化GPU.backend# => "Vulkan"GPU.device_name# => "V3D 7.1.10.2"# ベクトル演算a=GPU.array([1.0,2.0,3.0])b=GPU.fill(3,1.0)c=GPU.add(a,b)# => GPU::Bufferc.head(3)# => [2.0, 3.0, 4.0]# 行列演算c=GPU.matmul(a,b,M,K,N)

Camera クラス

cam=Camera.open("/dev/video0",640,480)# デバイス, 幅, 高さcam.width# => 640cam.height# => 480yuyv=cam.capture# YUYV raw bytes (String)rgb=cam.capture_rgb# RGB888 bytes (String)rgb=Camera.yuyv_to_rgb(yuyv,640,480)# YUYV→RGB 変換のみcam.close

FaceDetector クラス

detector=FaceDetector.new("models/ultraface-slim",use_gpu: true)faces=detector.detect_rgb(rgb,width,height,threshold: 0.6)# faces => Array of Hash# 各要素: { x: Float, y: Float, w: Float, h: Float, score: Float }faces.eachdo |f|
puts"x=#{f[:x].round} y=#{f[:y].round} w=#{f[:w].round} h=#{f[:h].round}"puts"score=#{f[:score].round(2)}"end
パラメータ説明デフォルト
use_gpu:Vulkan GPU 推論を使用true
threshold:検出スコアの閾値(0.0〜1.0)0.7
  • 座標 (x, y) は検出枠の左上角(ピクセル単位、元画像サイズ基準)
  • score が 1.0 に近いほど確信度が高い

Display クラス

disp=Display.open(640,480,"ウィンドウタイトル")# RGB888 文字列をウィンドウに表示disp.show(rgb,width,height)# 検出枠を描画した新しい RGB 文字列を返す(元のデータは変更しない)rgb=disp.draw_rect(rgb,width,height,x,y,w,h,# 枠の位置とサイズ(Integer)r,g,b)# 枠の色 (0〜255)# ウィンドウ閉じる / ESC イベントを確認breakifDisplay.poll_quitdisp.close

ファイル構成

mruby-gpu/
├── src/ # C/C++ ソース
│ ├── gpu_internal.h # GPU 内部共有ヘッダ
│ ├── gpu_vulkan.c # Vulkan 初期化・コンピュートディスパッチ
│ ├── gpu_buffer.c # GpuBuffer 作成・アクセス・I/O
│ ├── gpu_ops.c # mruby メソッド定義・gem init/final
│ ├── mrb_camera.c # Camera クラス (V4L2)
│ ├── mrb_face.cpp # FaceDetector クラス (NCNN + Vulkan)
│ ├── mrb_display.c # Display クラス (SDL2)
│ ├── v4l2_capture.c # V4L2 カメラ低レベル実装
│ └── v4l2_capture.h
├── shader/ # Compute Shader
│ ├── add.comp / .spv # ベクトル加算
│ ├── matmul.comp / .spv # 行列積
│ ├── rgb_convert.comp # YUYV→RGBA GPU 変換
│ ├── draw_rect.comp # 枠描画 GPU シェーダー
│ └── Makefile
├── models/ # NCNN モデル
│ ├── ultraface-slim.param
│ └── ultraface-slim.bin
├── examples/ # デモ・サンプルスクリプト
│ ├── demo.rb # GPU 基本演算デモ
│ ├── face_demo.rb # 顔認識デモ (LT 本番用)
│ ├── mnist.rb # MNIST 推論
│ └── train.rb # MNIST 学習
├── bench/ # ベンチマーク
│ ├── basic.rb # 基本ベンチマーク
│ └── scaling.rb # スケーリングベンチマーク
├── test/ # テスト
│ └── test_gpu.rb # GPU 基本テスト
├── tools/ # データ準備・スタンドアロンテスト
├── docs/ # ドキュメント
│ └── PERFORMANCE.md
├── mrbgem.rake # gem ビルド設定
└── README.md # このファイル

トラブルシューティング

SDL_Init failed エラー
環境変数 DISPLAY が設定されていません。

DISPLAY=:0 mruby demo/face_demo.rb

カメラが開けない (Camera.open failed)
デバイスを確認してください。

v4l2-ctl --list-devices # 接続中のカメラ一覧# /dev/video0 以外の場合は demo/face_demo.rb の Camera.open 行を修正

顔が検出されない

  • カメラに顔を向けてください
  • threshold: を下げてみてください(例: threshold: 0.4
  • 照明が暗すぎる場合は明るい場所で試してください

ビルド時に undefined reference to ncnn エラー
NCNN がインストールされていません。「依存関係のインストール」を参照してください。

Raspberry Pi がフリーズする(NCNN ビルド時)
メモリ不足です。スワップを追加して -j1 でビルドしてください(前述の手順を参照)。

About

The Joy of Talking to Hardware in Ruby — Raspberry Pi 5 GPU (Vulkan Compute Shader) bindings for mruby, with face detection and MLP training/inference.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages