ラズパイ4デジカメ計画③〜Nodejsでlibcameraを使ったカメラ機能を実装する
※ 当ページには【広告/PR】を含む場合があります。
2024/02/15
ラズパイ用カメラモジュールを接続する
「OV5647」
カメラモジュールの取り付け
「CAMERA」
レガシーカメラのセットアップ・動作テスト
レガシーカメラ
「raspi-config」
$ sudo raspi-config
$ vcgencmd get_camera
supported=0 detected=0, libcamera interfaces=0
0
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-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月まで
Bullseye
Kernel 5.10
2026年12月
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
$ 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);
}
})();
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;
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
カメラアプリを起動・常駐させる
$ sudo $(which node) main.js
tmp
デスクトップメニューに登録して起動する
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;
node-camera-app
systemdで常駐デーモン化する
「autostart」
「/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
$ 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
残った課題〜撮りためた画像を取り込む方法
#!/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
蛇足〜サービスの起動時間をちょっと短縮
$ systemd-analyze time
Startup finished in 3.706s (kernel) + 14.376s (userspace) = 18.082s
graphical.target reached after 14.277s in userspace
$ 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
$ 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
まとめ
記事を書いた人
ナンデモ系エンジニア
電子工作を身近に知っていただけるように、材料調達からDIYのハウツーまで気になったところをできるだけ細かく記事にしてブログ配信してます。
カテゴリー