ラズパイ4デジカメ計画③〜Nodejsでlibcameraを使ったカメラ機能を実装する
※ 当ページには【広告/PR】を含む場合があります。
2024/02/15

ラズパイ4Bでなんちゃってデジタルトイカメラに仕立てる話の3回目です。
前回までで、液晶タッチパネルをセットアップしたり、GPIOピンから電源のオン・オフに関して細かくみていきました。
本回でようやくラズパイ用カメラと本体を接続し、GPIOに接続したスイッチからカメラによる撮影ができるまでの手順を解説します。
ラズパイ用カメラモジュールを接続する
過去にこのブログでも、ラズパイゼロにカメラモジュールを接続する方法を解説したことがあります。
この記事はおおよそ3年前に認めた内容になっていますが、当時のカメラユーティリティソフトであった
libcameraベースのソフトウェアの正式採用と前後し、カメラモジュールV1やV2などの古いカメラモジュール製品は、
レガシーとはいってもとりあえずは現行のOSで動作するようですが、そう遠くない将来で最新のラズパイ製品群で動作できなくなる恐れもあることは頭に入れておかないといけません。
今回利用するのはレガシーカメラの中でも、正規のラズパイ用カメラモジュールではなく、その互換性商品の一つ・「
600x214

価格は1000円弱で、正規品のラズパイカメラモジュールと比較しても非常に安い分、カメラの性能としては劣るものの、決して怪しい商品ではなく、中身は組込みで良く利用されているOmniVision製CMOSイメージセンサ・
「OV5647」
基本的にカメラモジュールは最新のものほど高価な部品になっている傾向にあります。
最近の高性能なカメラモジュールを、いきなり開発途中で動作させていて壊すとショックも大きいです。
ラズパイでカメラを利用した開発を行うのであれば、最初のうちは廉価版のカメラモジュールを使って進めていくとなにかあった際に少し気持ちが安心します。
カメラモジュールの取り付け
能書きはともかく、早速これをそのままカメラポートに取り付けてみましょう。
492x502

ここでは、カメラモジュールの取り付け方向にだけ注意しましょう。
基板にシルクプリントで印字してある
「CAMERA」
端子の挿入方向が反対になっていると、当然デバイスとして認識されないので注意が必要です。
レガシーカメラのセットアップ・動作テスト
先程も述べたように既にV1相当のカメラモジュールは
レガシーカメラ
レガシーカメラ製品はその設定を有効にしないと利用できないため、まずはその設定を有効化していきます。
まずは先程接続したカメラモジュールを
「raspi-config」
$ sudo raspi-config
として設定画面を呼び出し、以下の手順でカメラモジュールを有効化しましょう。
760x890

設定できたらラズパイを再起動します。
この時点ではまだ、カメラモジュールは認識されません。
ヘルパーコマンドから、カメラの利用ステータスを表示させてみると、
$ vcgencmd get_camera
supported=0 detected=0, libcamera interfaces=0
となり、すべてのフラグがなし(
0
ちなみに、Raspberry Pi OS Bullseyeから従来のカメラモジュール製品で利用してきたユーティリティソフト群も非推奨になっているため、当時からある
vcgencmd
それで、どうすればデバイスとして認識してくれるかというと、
/boot/config.txt
$ sudo nano /boot/config.txt
で、このファイルの最後の行らへんに以下を追加します。
#...省略
dtoverlay=ov5647
#...
変更を保存したら、ラズパイを再起動して、デバイスとして認識されているかを確認しましょう。
$ ls /dev/ | grep video
video0
#...
カメラデイズである
video0
また参考までに、
vcgencmd
$ vcgencmd get_camera
supported=1 detected=0, libcamera interfaces=1
というレスポンスが返ってくるようになります。
libcamera以降では、
ラズパイの実機やSSH越しでも良いので、以下のコマンドをターミナルから叩いてみます。
$ libcamera-hello
Preview window unavailable
[0:00:49.368615839] [1674] INFO Camera camera_manager.cpp:297 libcamera v0.0.5+83-bde9b04f
[0:00:49.427213820] [1675] INFO RPI vc4.cpp:437 Registered camera /base/soc/i2c0mux/i2c@1/ov5647@36 to Unicam device /dev/media4 and ISP device /dev/media0
[0:00:49.427555432] [1675] INFO RPI pipeline_base.cpp:1101 Using configuration file '/usr/share/libcamera/pipeline/rpi/vc4/rpi_apps.yaml'
[0:00:49.429376080] [1674] INFO Camera camera.cpp:1033 configuring streams: (0) 1296x972-YUV420
[0:00:49.429980469] [1675] INFO RPI vc4.cpp:565 Sensor: /base/soc/i2c0mux/i2c@1/ov5647@36 - Selected sensor format: 1296x972-SGBRG10_1X10 - Selected unicam format: 1296x972-pGAA
#0 (0.00 fps) exp 33222.00 ag 8.00 dg 1.00
#...
#140 (30.02 fps) exp 33222.00 ag 8.00 dg 1.00
#141 (30.02 fps) exp 33222.00 ag 8.00 dg 1.00
エラーもなくきちんと動作していれば一安心です。
実際に一枚写真を試し撮りしてみましょう。
シンプルな静止画を撮るコマンドは、
$ libcamera-jpeg -n -o test.jpg
[0:01:00.920428999] [1681] INFO Camera camera_manager.cpp:297 libcamera v0.0.5+83-bde9b04f
[0:01:00.966632314] [1682] INFO RPI vc4.cpp:437 Registered camera /base/soc/i2c0mux/i2c@1/ov5647@36 to Unicam device /dev/media4 and ISP device /dev/media0
[0:01:00.966773925] [1682] INFO RPI pipeline_base.cpp:1101 Using configuration file '/usr/share/libcamera/pipeline/rpi/vc4/rpi_apps.yaml'
[0:01:00.968313777] [1681] INFO Camera camera.cpp:1033 configuring streams: (0) 1296x972-YUV420
[0:01:00.968866702] [1682] INFO RPI vc4.cpp:565 Sensor: /base/soc/i2c0mux/i2c@1/ov5647@36 - Selected sensor format: 1296x972-SGBRG10_1X10 - Selected unicam format: 1296x972-pGAA
[0:01:06.120863607] [1681] INFO Camera camera.cpp:1033 configuring streams: (0) 2592x1944-YUV420 (1) 2592x1944-SGBRG10_CSI2P
[0:01:06.125998310] [1682] INFO RPI vc4.cpp:565 Sensor: /base/soc/i2c0mux/i2c@1/ov5647@36 - Selected sensor format: 2592x1944-SGBRG10_1X10 - Selected unicam format: 2592x1944-pGAA
Still capture image received
これでカメラモジュールから画像が問題なく取り込めれば、レガシーカメラの動作試験完了です。
余談〜レガシーカメラはどこまでサポートされる?
前々回の記事でも述べたように、現行の最新版である
Raspberry Pi OS Bookworm(Debian12ベース)
過去のカメラで動作していたアプリケーションを動かし続けようとすると、唯一できる対応策として、現時点でのレガシー版である
Raspberry Pi OS Bullseye(Debian11ベース)
ただし、この
Bullseye
2024年6月まで
それ以降では、公式のイメージからは消えてゆく運命ですので、OSイメージがどこかにアーカイブされたものを使い続けるしかなくなりそうです。
さらに悪いことに、レガシーカメラ機能を用いて市販されていた製品のサポートという点では、
Bullseye
Kernel 5.10
2026年12月
つまり、今後レガシー製品を動かそうとすると、逆に最新のモジュールデバイスが使えないので、新旧織り交ぜながらラズパイを利用することがかなり困難になることが予測されます。
レガシーカメラ製品を多用されていたユーザーからすると、ラズパイでまともにレガシー品を動かせるのも2026年までかな、と頭の隅におかれて利用を継続しておいたほうがよいでしょう。
Nodejsによるカメラ撮影アプリケーションを実装する
ここからは、nodejsでデジカメアプリを実装していきます。
なお、前回セットアップ済である
ラズパイのどこかに適当な作業フォルダを作成し、以下の構成で最低限必要な以下のファイルを準備します。
$ tree
.
├── main.js
├── src
│ ├── camera_ctrl.js
│ └── switch_ctrl.js
└── package.json
npmパッケージのインストール
まずは、
package.json
$ nano package.json
で以下の内容で編集します。
{
"name": "rpi-legacy-camera-app",
"version": "0.0.1",
"description": "RPi legacy camera app for armhf",
"main": "main.js",
"private": true,
"dependencies": {
"pigpio": "^3.3.1"
}
}
大したことはなく、前回に引き続き
pigpio
内容を追加したら、保存して、npmパッケージをインストールします。
$ yarn install
で、
node_modules
yarn.lock
nodejsでカメラ撮影プログラムの実装
最初に
main.js
この
main.js
$ nano main.js
ここでは以下のような内容で編集しましょう。
const fs = require('node:fs');
const path = require('node:path');
const sw = require('./src/switch_ctrl');
//👇Ctrl&Cキーでプロセスを終了させる場合
process.on('SIGINT', () => {
process.exit(0);
});
const sleep = (_ms) => {
return new Promise(resolve => { setTimeout(() => resolve(), _ms)});
}
(async () => {
//👇静止画の保存先としてtmpフォルダを作成
const _dir = path.resolve(__dirname, 'tmp');
if (!fs.existsSync(_dir)) {
console.log('tmpフォルダを作成します。');
fs.mkdirSync(_dir);
}
console.log(`An app is getting started!`);
while(true) {
//👇シャッタースイッチが押されたかを確認
await sw.push();
//👇100msほど時間間隔を空ける
await sleep(100);
}
})();
次にGPIOの機能を盛り込むために
switch_ctrl.js
$ nano src/switch_ctrl.js
このコードは以下のように実装します。
const { execSync } = require('node:child_process');
const camera = require('./camera_ctrl');
const Gpio = require('pigpio').Gpio;
//👇38番ピン(GPIO20)をシャッタースイッチに設定
const button = new Gpio(20, {
mode: Gpio.INPUT,
pullUpDown: Gpio.PUD_UP,
});
const sleep = (_ms) => {
return new Promise(resolve => { setTimeout(() => resolve(), _ms)});
}
const callShutdown = async () => {
execSync(`/sbin/shutdown -h now 'Turn off by GPIO'`);
};
const push = async () => {
const _start = Date.now();
//👇ボタンが押された状態(0)が開始する
if (button.digitalRead() === 0) {
console.log("Button has been pushed!");
while(true) {
//👇ボタンが離された状態(1)に戻るまで待機
if (button.digitalRead() === 1) {
break;
}
await sleep(50);
}
//👇ボタンが押されてから離されるまでの時間[ms]を取得
const _end = Date.now() - _start;
if (_end > 5000) {
//👇ボタンが5秒長押しで電源オフ
console.log("SHUTDONW signal detected! Bye-bye!!");
await callShutdown();
}
else if (_end < 50) {
//まれに入力シグナルのノイズ電圧を誤検出する際の対策
return;
}
else {
//👇ボタン押し5秒未満ならカメラ撮影処理を開始
console.log(`Let me take a picture!`);
await camera.shutter();
}
}
return;
};
exports.push = push;
ここでは
前回からの修正点として、シャッタースイッチを長押し(5秒間)したら"電源OFF"、それより短い時間なら"写真撮影"と、1つのボタンに2つの機能を実装している点です。
最後にカメラコントロールの機能をまとめた
camera_ctrl.js
$ nano src/camera_ctrl.js
このファイルの中身は以下です。
const { execSync } = require('node:child_process');
const path = require('node:path');
let shutter_numb = 0;
const sleep = (_ms) => {
return new Promise(resolve => { setTimeout(() => resolve(), _ms)});
}
const shutter = async () => {
shutter_numb += 1;
//👇保存先はtmpフォルダを指定
const pwd = path.resolve(__dirname, '../tmp');
const output = `${pwd}/${shutter_numb}-${(new Date()).toISOString()}.jpg`;
const disp = 'DISPLAY=:0.0';
const width = 640;
const height = 480;
const timeout = 2000
const cmd = `libcamera-jpeg -o ${output} -f -t ${timeout} --width ${width} --height ${height}`;
await execSync(`${disp} ${cmd}`);
console.log('A picture has been saved.');
};
exports.shutter = shutter;
ここでは
execSync
libcamera-jpeg
libcamera-jpeg
libcamera-jpeg --help
カメラアプリを起動・常駐させる
まずは早速先程のnodejsアプリをラズパイ実機のターミナルから動くことを確認してみましょう。
$ sudo $(which node) main.js
ただしく動いた場合、カメラアプリが以下のような状態でシャッター待ちになります。
480x320

あとは、
tmp
なお、スイッチ5秒押しで電源もOFFになります。
デスクトップメニューに登録して起動する
このままだと、ラズパイを立ち上げ後にわざわざターミナルを起動してコマンドを打たないと使えないのはとても不便です。
せめてデスクトップのスタートメニューからクリックして起動するようにしてみましょう。
まずプロジェクトフォルダに、
start-camera-app.sh
$ nano start-camera-app.sh
でこのスクリプトに以下の内容で編集して保存します。
なお、リソースやコマンドは絶対パスを基本として使います。
ここでは一例として、ユーザー名を
user
/home/user/camera-worker
試されたい場合には、ご自分の環境設定に合わせて修正してください。
#!/bin/bash
export DISPLAY=:0.0
WORK_DIR=/home/user/camera-worker
MY_NODE=/home/user/.nvm/versions/node/v16.20.2/bin/node
sudo $MY_NODE $WORK_DIR/main.js
このスクリプトに実行権を与えておきましょう。
$ sudo chmod +x start-camera-app.sh
スタートメニューに登録するには、
「/usr/share/applications/」
このカメラアプリ用に
node-camera-app.desktop
$ sudo nano /usr/share/applications/node-camera-app.desktop
このファイルは以下のような内容にします。
[Desktop Entry]
Name=node-camera-app
Type=Application
Version=1.0
Comment=libcamera app hosted by nodejs
Exec=/home/user/camera-worker/start-camera-app.sh
Terminal=true
Categories=Utility;
これを保存して、一旦再起動し、デスクトップのスタートメニューを覗いてみましょう。
480x320

すると、
node-camera-app
systemdで常駐デーモン化する
少し高度な設定になりますが、ラズパイが起動したら自動でアプリが立ち上がるようにすると、よりデジカメ感が出ます。
かつてはラズパイの起動時にGUIアプリを登録するやり方は何通りかありましたが、よく利用されきた
「autostart」
代わりにすべてのアプリ自動起動に推奨されるのは、
systemdサービスにすることで、常時バックグラウンドの別プロセスとして管理され、適切なタイミングで起動できるようにすることができる反面、GUIアプリを立ち上げるにはXsesstionやXauthorityなどのLinux特有のディスプレイ機能を理解している必要もあります。
ともかく、早速systemdへサービスとして登録していきます。
systemdサービスの作り方も簡単で、
「/etc/systemd/system/」
$ sudo nano /etc/systemd/system/camera.service
このファイルの中身を以下のようにします。
[Unit]
Description=Camera Launcher
After=multi-user.target
[Service]
User=user
Environment=DISPLAY=:0.0
Environment=XAUTHORITY=/home/user/.Xauthority
ExecStart=lxterminal -e /home/user/camera-worker/start-camera-app.sh
Restart=always
[Install]
WantedBy=multi-user.target
こちらも試される場合には、ユーザー名やリソースの絶対パスが正しいか確認して利用してください。
サービスファイルを保存したら、systemサービスをリロードさせます。
$ sudo systemctl daemon-reload
これでサービス化は完了ですが、一旦手動でカメラアプリが動くかをチェックします。
$ sudo systemctl start camera.service
$ sudo systemctl status camera.service
● camera.service - Camera Launcher
Loaded: loaded (/etc/systemd/system/camera.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2024-02-15 15:02:03 JST; 25min ago
Main PID: 768 (lxterminal)
Tasks: 14 (limit: 1539)
CPU: 1min 49.635s
CGroup: /system.slice/camera.service
├─ 768 lxterminal -e /home/user/camera-worker/start-camera-app.sh
├─1311 /bin/bash /home/user/camera-worker/start-camera-app.sh
├─1317 sudo /home/user/.nvm/versions/node/v16.20.2/bin/node /home/user/camera-worker/main.js
└─1324 /home/user/.nvm/versions/node/v16.20.2/bin/node /home/user/camera-worker/main.js
Feb 15 15:02:03 raspberrypi systemd[1]: Started Camera Launcher.
Feb 15 15:02:05 raspberrypi lxterminal[768]: error: XDG_RUNTIME_DIR not set in the environment.
Feb 15 15:02:09 raspberrypi sudo[1317]: user : TTY=pts/0 ; PWD=/ ; USER=root ; COMMAND=/home/user/.nvm/versions/node/v16.2>
Feb 15 15:02:09 raspberrypi sudo[1317]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
エラーが発生していないことを確認して、サービスを止めます。
$ sudo systemctl stop camera.service
では、ラズパイ起動時に自動で立ち上がるようにこのサービスを有効化します。
$ sudo systemctl enable camera.service
これで、ラズパイを再起動して、無事カメラアプリが自動で起動したらラズパイのソフト面での「デジカメ化」が完了です。
残った課題〜撮りためた画像を取り込む方法
撮った写真を移動する方法はいくつか考えられます。
一番手っ取り早いのが、ラズパイの入ったSDカードをそのまま他のPCのUSBなどに挿して、画像データを移す方法はいかにも「デジカメ」です。
個人的には、せっかくラズパイでSSH接続もできるので、
#!/bin/bash
echo '======= APP DOWNLOADER FROM RPI TO LOCAL MACHINE ======='
source .env
###☆.envファイルの例:
#TARGET_SSH_CLIENT=user
#TARGET_SSH_CIP=192.168.1.10
#WORKER_DIR=camera-worker
echo "TARGET_SSH_CLIENT=$TARGET_SSH_CLIENT"
echo "TARGET_SSH_CIP=$TARGET_SSH_CIP"
echo "WORKER_DIR=$WORKER_DIR"
rsync -avP --delete \
--include="*.jp*g" \
--include="*.png" \
--exclude="*.*" \
--exclude="node_modules/" \
--exclude="src/" \
-e ssh $TARGET_SSH_CLIENT@$TARGET_SSH_CIP:/home/$TARGET_SSH_CLIENT/$WORKER_DIR/tmp/ ./tmp
他にはオンライン接続している環境なら、直接クラウドストレージにも保存できるようなAPIもnodejsで簡単に機能拡張も可能です。
そこらへんはご自身の好みでいろいろと模索してみてください。
蛇足〜サービスの起動時間をちょっと短縮
ラズパイの起動でどのくらい遅いか少し気になるので、アプリ立ち上げまでの時間をチェックしてみます。
$ systemd-analyze time
Startup finished in 3.706s (kernel) + 14.376s (userspace) = 18.082s
graphical.target reached after 14.277s in userspace
おおよそ18秒かかっています。
どこらへんのサービスが重いか内訳も見てみます。
$ systemd-analyze blame
7.972s hciuart.service
7.304s rc-local.service
2.117s e2scrub_reap.service
2.090s dev-mmcblk0p2.device
1.744s raspi-config.service
1.704s rpi-eeprom-update.service
1.501s udisks2.service
1.233s systemd-logind.service
1.157s dhcpcd.service
1.110s ModemManager.service
#...
12ms plymouth-quit-wait.service
一見、ワシャワシャと重要であろうサービスが並んでいて、重いものほど勝手にサービスを無効化しては困るかに見えます。
hciuart.service
ということで、以下の2つのサービスを無効化します。
$ sudo systemctl disable bluetooth.service
$ sudo systemctl disable hciuart.service
再起動後、どのくらい早くなったか確認してみます。
$ systemd-analyze time
Startup finished in 4.109s (kernel) + 14.586s (userspace) = 18.696s
graphical.target reached after 14.497s in userspace
まぁ良いか...どれだけ頑張ってもSDカードの物理特性に因るものが大きく、安いSDカードで起動10秒は切れないようですので、高速化を極めたい方は以下の記事を参考にしてみてください。
まとめ
以上、今回はnodejsでカメラ機能の実装を具体的に解説していきました。
デジカメにしてはアプリの起動までに20秒ほど時間がかかるものの、中身はラズパイですので仕方ありません。
専用のデジカメとは違い、あくまでもなんちゃってデジカメですので、機能性の出来はさほど期待せずにここらへんでソフト面の話は打ち止めさせていただきます。
次回は、カメラの筐体などハード面でいろいろと作成していく予定です。