コンピュータービジョン(CV)に欠かせないカメラ。Raspberry Piには多くのカメラモジュールが存在しますが、フォーカスをソフトウェアから操作できると、設置場所の自由度が向上します。
ソフトウェアで制御可能な電動フォーカス機能付き、Raspberry Pi用CSIインターフェイスカメラ "Arducam B0176" の紹介と使い方です。
Arducam B0176 の特徴
- フォーカスが電動、ソフトウェアから 1024 段階で設定可能
- 他のRaspberry Pi向けカメラと同様の形状とネジ穴の位置で、物理的に互換
- ソフトウェアも他のRaspberry Pi向けカメラと同様のCSIインターフェイス
- 500万画素(5MP)で十分な解像度
- 安い! (約1700円)
電動フォーカスの様子はYouTubeを見るのが早いです。(40秒)
Arducam B0176 の情報源
- 商品詳細: https://www.arducam.com/product/raspberry-pi-camera-5mp-autofocus-motorized-focus-camera-b0176/
- ハードウェア仕様やセットアップ方法: https://www.arducam.com/docs/cameras-for-raspberry-pi/native-raspberry-pi-cameras/5mp-ov5647-motorized-focus-camera-modules/
- サンプルコード: https://github.com/ArduCAM/RaspberryPi/tree/master/Motorized_Focus_Camera/
- 販売ページ(Amazon.co.jp): https://www.amazon.co.jp/dp/B07SN8GYGD/
※ 日本語情報がほとんど無いですね。
ミスリードしそうな製品名 "Auto Focus"
商品詳細や販売ページでは "Auto Focus" と書いてありますが、正確には「電動フォーカス」です。
オートフォーカスは OpenCV 等を使って輪郭抽出をしながらソフトウェアでフォーカスを調整して実現しています。ですので、「できない事はないけれど、この製品自体にオートフォーカスが備わっているわけでは無い」のでお気を付けください。
Arducam B0176 の仕組みとセットアップ
対象環境
- Raspberry Pi 4 model B
- Raspberry Pi OS 2021/1/11 (GUIがあるほうが動作確認が楽です)
カメラのセットアップ
Arducam B0176はカメラ画像をCSIで、電動フォーカスはI2Cで動かしています。そのため、Raspberry Pi OSではCameraとI2Cの2つを有効化しておく必要があります。
$ sudo raspi-config nonint do_camera 0
$ sudo raspi-config nonint do_i2c 0
$ sudo sed -i 's/i2c_arm/i2c_vc/' /boot/config.txt
$ sudo systemctl reboot
注意点はI2Cの有効化です。 /boot/config.txt
では dtparam=i2c_vc=on
というエントリーにする必要があります。
raspi-config で有効化した場合は dtparam=i2c_arm=on
設定されます。この状態で電動フォーカスを動かそうとすると Failed to set I2C address
というエラーが発生します。
パラメータの意味は /boot/overlays/README をご覧ください。
再起動後に vcgencmd get_camera
で動作確認をしてください。 supported と detected がそれぞれ 1 になっていれば動作しています。
$ sudo vcgencmd get_camera
supported=1 detected=1
この時点で raspistill
等のコマンドも動作するはずです。
電動フォーカスを動かすライブラリのインストール
電動フォーカスを動かすためのライブラリは libarducam_vcm.soになります。
これをRaspberry Pi にダウンロードし、共有ライブラリとして読み込みます。
$ curl -LO https://github.com/ArduCAM/RaspberryPi/raw/master/Motorized_Focus_Camera/python/lib/libarducam_vcm.so
$ sudo install -m 0644 libarducam_vcm.so /opt/vc/lib/ && rm libarducam_vcm.so
$ sudo ldconfig
sudo ldconfig 、もしくはOSを再起動すれば読み込まれます。
ldconfig -p で libarducam_vcm.so が読み込まれていることが確認できれば完了です。
$ ldconfig -p | grep arducam_vcm
libarducam_vcm.so (libc6,hard-float) => /opt/vc/lib/libarducam_vcm.so
フォーカス操作をしてみる
libarducam_vcm.so を実行するPythonスクリプト(Arducam_B0176_focus_ctl.pyを用意しました。
$ curl -O https://gist.githubusercontent.com/ma2shita/a6ad200bea550f7211636783f17539b9/raw/997f648d3c99fe5c6cb6313275ff3dcc5fe60403/Arducam_B0176_focus_ctl.py
$ chmod u+x Arducam_B0176_focus_ctl.py
$ ./Arducam_B0176_focus_ctl.py -h
usage: Arducam_B0176_focus_ctl.py [-h] --focus {Integer,0 <= N <= 1023}
optional arguments:
-h, --help show this help message and exit
--focus {Integer,0 <= N <= 1023}
Camera focus 0(far)..1023(near)
動作確認ですが、GUIであれば、もう一つターミナルを開いて raspistill -t 0
とプレビューウィンドウを表示させたまま、Arducam_B0176_focus_ctl.pyを動かすとフォーカスの変化がわかりやすいでしょう。
$ Arducam_B0176_focus_ctl.py --focus 0 # 焦点:近い (数センチレベル)
$ Arducam_B0176_focus_ctl.py --focus 1023 # 焦点:遠い
CLIの場合は一度 raspistill -t 0
でカメラを動かしながらArducam_B0176_focus_ctl.py でフォーカスを変更し、改めて raspistill
で画像を取る形になります。
電動フォーカスはカメラの動作中に操作可能です。そのため、動画撮影中でもフォーカス変更ができます。
※逆に、カメラが動作していないと電動フォーカスを動かすことはできません。
仕様
libarducam_vcm.so の利用可能なメソッドは以下の通りです。
- unsigned char vcm_init(void)
- unsigned char vcm_write(unsigned int focus_val)
- focus_val: 0(遠距離) .. 1023(近接)
※ このリファレンスは arducam_vcm.h と Motorized_Focus_Camera_Preview.py の実装から推測と試験の結果です。
調査ノート
GitHubに掲載されているセットアップについて
-
enable_i2c_vc.sh
- このshファイルは
/boot/config.txt
へdtparam=i2c_vc=on
を追加します。ですが、/etc/modules
へi2c-dev
を追加してくれないので、本手順では raspi-config を使うようにしました。
- このshファイルは
-
python-opencv
パッケージのインストールは不要- このパッケージは GitHub のセットアップですが、OpenCVによるオートフォーカスのサンプルを動かすためのものです。電動フォーカスを動かすだけなら不要です。
libarducam_vcm.so のソース
libarducam_vcm.so はヘッダファイルはあるのですが、本体のソースが見当たりません。そのためリファレンスもヘッダファイルや他の実装、そして strings コマンドの結果からのリエンジニアリングです。
Raspberry Pi 上でのlddとfileの結果を残しておきます。
$ file libarducam_vcm.so
libarducam_vcm.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=29831c10685a26166a127333e0413e97f586e1ae, not stripped
$ ldd libarducam_vcm.so
linux-vdso.so.1 (0xbefac000)
/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0xb6f2a000)
libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6eec000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d9e000)
/lib/ld-linux-armhf.so.3 (0xb6f54000)
ライセンスは リポジトリがBSD-3-Clause なので、それに従うことになると思います。
また、現在のフォーカス状態を知る vcm_read()
のような関数は、探す限りは見つけられませんでした。そのため、フォーカスの状況はプログラム側で把握しておく必要があります。
i2cset / i2cget コマンドでの操作
センサーにArduCamにはIMX219を使った電動フォーカスカメラのプロダクトが存在します。そのJetson向けのコードに、i2csetでフォーカスを設定している部分が存在しました。
Arducam B0176でも動作します。
## Focus = 0
$ sudo i2cset -y 0 0x0c 0 0
## Focus = 1023
$ sudo i2cset -y 0 0x0c 63 240
どうやらI2Cアドレスは 0x0c
のようです。フォーカスの値を2バイトで書き込むことで動きました。
また、i2cgetによる現在のフォーカス状況も読み出すことができました。
$ sudo i2cset -y 0 0x0c 63 240
$ sudo i2cget -y 0 0x0c 0x00 w
0xf03f
この方法が正しいのか否かは不明ですの。ご自分の責任範囲でご利用ください。
一応、i2cset / i2cget を補助するPythonスクリプトを置いておきます。
#!/bin/env python3
"""
Number to Focus for Arducam B0176
Focus control for Arducam B0176 "Motorized focus camera for Raspberry Pi"
Copyright (c) 2021 Kohei MATSUSHITA
This software is released under the The 3-Clause BSD License.
https://opensource.org/licenses/BSD-3-Clause
Usage:
$ sudo i2cset -y 0 0x0c $(./focusing.py 0)
$ sudo i2cset -y 0 0x0c $(./focusing.py 512)
$ sudo i2cset -y 0 0x0c $(./focusing.py 1023)
"""
import argparse
import sys
def focusing(val):
value = (val << 4) & 0x3ff0
data1 = (value >> 8) & 0x3f
data2 = value & 0xf0
return (data1, data2)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('focus', metavar='N', type=int, help="Camera focus 0(far)..1023(near)")
args = parser.parse_args()
r = focusing(args.focus)
print("{} {}".format(r[0], r[1]))
sys.exit(0)
#!/bin/python3
"""
Number to Focus for Arducam B0176
Focus control for Arducam B0176 "Motorized focus camera for Raspberry Pi"
Copyright (c) 2021 Kohei MATSUSHITA
This software is released under the The 3-Clause BSD License.
https://opensource.org/licenses/BSD-3-Clause
Usage:
$ sudo i2cget -y 0 0x0c 0x00 w | ./focused.py
<Current Focus 0 to 1023>
"""
def focused(str):
s = str[2:]
u = int(s[:2], 16)
d = int(s[2:], 16) << 8
r = (u + d) >> 4
return r
import sys
if __name__ == "__main__":
str = input()
r = focused(str)
print(r)
sys.exit(0)
あまり派手にフォーカスを動かさない方が良いかもしれない
0 => 1023といった大きな数字の移動をすると、電動フォーカスが「カチッ」と動いている音が聞こえます。物理的な可動部分が存在するため、あまり極端な操作を繰り返すと壊れるかもしれません。
また、埃や湿度も気をつける必要がありそうです。
また、I2Cへのコマンド投入後、一定時間はコマンドを受け付けてくれないことがあるため、リトライを前提にしておいた方が無難です。
あとがき
なんかハマってしまったが、帰ってこられて良かった。
というか、仕様を書いておいて欲しいっすな。
EoT