picamera2を用いたカメラ画像の取得とLcdディスプレイへの表示
カメラで取得した映像を、LCDディスプレイに表示させる方法を考えます。
picamera21を用いたカメラ画像の取得方法と、取得した画像をLcdディスプレイに表示するための処理について考えます。
画像のリサイズに関する比較を行い、処理速度の違いについても考えます。
環境
以下は、今回使用している、システム情報とpicamera2の情報です。
$ uname -a
Linux raspberrypi 5.15.76-v7l+ #1597 SMP Fri Nov 4 12:14:58 GMT 2022 armv7l GNU/Linux
$ pip show picamera2
Name: picamera2
Version: 0.3.7
Summary: The libcamera-based Python interface to Raspberry Pi cameras, based on the original Picamera library
Home-page: https://github.com/RaspberryPi/picamera2
Author: Raspberry Pi & Raspberry Pi Foundation
Author-email: picamera2@raspberrypi.com
License: BSD 2-Clause License
Location: /usr/local/lib/python3.9/dist-packages
Requires: piexif, simplejpeg, numpy, PiDNG, v4l2-python3, python-prctl, pillow
Required-by:
Arducamのカメラ2を使用してます。最大の解像度は "4656 × 3496" です。
Picamera2でカメラ画像を取得する
Picamera2では画像の取得方法が複数あります。
はじめに簡単な例として、Picamera2.capture_fileを使用し、カメラから画像を取得する例を示します。
import time
from picamera2 import Picamera2
camera = Picamera2()
config_capture = camera.create_still_configuration()
camera.configure(config_capture)
camera.start()
time.sleep(2)
camera.capture_file("sample.png")
ファイル名からフォーマットを取得し、画像を保存しています。
Picamera2では保存する際に、PILを用いるため、JPEG, PNG, GIFがサポートされるようです。
(ちなみにPicamera2.capture_fileはio.BytesIO()を使って、取得した画像をメモリ上のバイナリデータとして扱うこともできます。→ おまけ)
処理時間の比較
上記例ではカメラ画像をファイルとして保存していますが、普通にデータとして扱いたい場合もあります。
取得した後に、どのように使用するかによって、使い分けることになります。
以下に各関数を用いた際の処理時間をまとめました。
time.time()を使って計測し、1000回実行時の平均値を採用しています。
| 関数名 | 処理時間 [s] | 返り値 |
|---|---|---|
| capture_buffers | 0.255 | [<class 'numpy.ndarray'>], <class 'dict'> (1dimとメタデータ) |
| capture_array | 0.607 | <class 'numpy.ndarray'> (2dim) |
| capture_image | 0.701 | <class 'PIL.Image.Image'> |
| capture_file(".jpg") | 1.260 | <class 'dict'> (メタデータ) |
| capture_file(".png") | 5.391 | <class 'dict'> (メタデータ) |
処理時間は圧倒的にPicamera2.capture_buffersが速いことがわかります。
Picamera2.capture_arrayではHelpers.make_arrayにより変換処理がされています。
Picamera2.capture_imageではその変換されたarrayをPILイメージに変換しています。
ということで、Helpers.make_arrayが、処理のボトルネックになっていそうです。
Picamera2.capture_imageを使うケースが多いかと思います。
しかし、写真を保存し、あとで利用する等であれば、Picamera2.capture_buffersを使って取得した値をpickleで保存するのがメタです。
buffersから復元する例を最後に記載します。→ おまけ
画像表示処理の検討
LCDディスプレイに表示するために、PILのイメージを準備することを想定した場合、必要な処理は以下になります。
- PILイメージを生成する。
- 画像をLCDディスプレイのサイズにリサイズする。
この2つの処理を出来る限り素早く行うことが必要になります。
そもそもフルの解像度で撮ったものを小さなLCDに出力する必要は全くありませんが、"4656 × 3496"のフルサイズで撮ったものをリサイズして"160 × 128"にします。
パターン1: Image.fromarray
とても単純な方法です。
Picamera2.capture_imageから得た画像をリサイズします。
import time
from picamera2 import Picamera2
picam2 = Picamera2()
capture_config =
picam2.configure(capture_config)
picam2.start()
time.sleep(2)
s_sum = 0
for _ in range(N := 1000):
s = time.time()
img = picam2.capture_image()
img.resize((160, 128))
s_sum += (time.time()-s)
print(s_sum/N)
パターン2: cv2
OpenCVを使用してリサイズを行ってから、イメージを生成します。
import time
import cv2
from picamera2 import Picamera2
from PIL import Image
picam2 = Picamera2()
capture_config = picam2.create_still_configuration()
picam2.configure(capture_config)
picam2.start()
time.sleep(2)
s_sum = 0
for _ in range(N := 1000):
s = time.time()
img = picam2.capture_array()
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
img = cv2.resize(img, (160, 128))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img)
s_sum += (time.time()-s)
print(s_sum/N)
※ OpenCVのライセンスはライセンスの制約があるため注意が必要です。
処理時間
上記2パターンの出力結果は以下になります。
| パターン | 処理時間 [s] |
|---|---|
| パターン1 | 0.927 |
| パターン2 | 0.600 |
一般的にPILのイメージのリサイズよりもcv2のリサイズの方が速いと言われています。 今回の結果も割とそれを反映していると考えられます。
イメージサイズが大きい状態での比較なので、顕著に差が出ていそうです。
次にできる限り、処理時間が短くなるように解像度とセンサーサイズを変更します。
the resolution of the sensor output
デフォルトではmain=sizeを選択すると、画角が決まってしまいます。
そこでraw=をのsizeを使用して画角を最大で固定し、出力サイズをディスプレイサイズに変更します。
capture_config = picam2.create_still_configuration(
main={"size": (160, 128)}, raw={"size": picam2.sensor_resolution})
計測した結果を示します。最初に比べればだいぶ速くなりました。
| パターン | 処理時間 [s] |
|---|---|
| パターン3 | 0.286 |
ディスプレイ表示に必要な時間を0秒だとしたら、4fps程度になりました。 (ディスプレイは最大60fps程度です。。)
他に良いアイディアが思いつくまではこれで行こうと思います。
おまけ
io.BytesIO()
io.BytesIO()を使用する場合はフォーマットがないためformat=を指定してあげる必要があります。
data = io.BytesIO()
camera.capture_file(data, format='png')Picamera2.capture_buffers
buffersからイメージを作成するに際し、picamera2.request.Helpersを使用します。
まずpickleなどでbuffersを保存します。
with open(f"buffers.pkl", "wb") as f:
pickle.dump(buffers, f)
Helpersを使ってみます。
import time
from picamera2 import Picamera2
from picamera2.request import Helpers
camera = Picamera2()
capture_config = camera.create_still_configuration()
camera.configure(capture_config)
camera.start()
time.sleep(1)
h = Helpers(camera)
with open("buffers.pkl", "rb") as tf:
(b, ), meta = pickle.load(tf)
img = h.make_image(b, capture_config['main'])
h.save(img, meta, 'sample.png')
saveする際に、Helpersに引数としてPicamera2を渡しています。
saveする時にカメラ情報をいくつか使用するため、しかたないです。
掻い潜れますが、とりあえずはカメラがないケースでの現像はないのでおいておきます。
(追記)
make_arrayやmake_imageするだけなら実際はPicamera2()を使用しません。
そのため、ソースコードを参考に変換することは容易です。
ただ、私はrawで撮影し、カメラとPicamera2無しで現像することにしました。
→ Picamera2を使用したRAW撮影と現像処理の方法
picam2.helpers.make_imageを使って生成する方がスマートかと思います。exampleがGitHubにありました。
picamera2/examples/capture_helpers.py - raspberrypi/picamera2 - GitHub
参考文献
picamera2は、Pythonのライブラリで、Raspberry Pi上でカメラシステムを使用する際に利用されます。基本的にはリボンケーブルで接続されるカメラを想定しているようです。Picamera2はドキュメントがPDFにより提供されています。 raspberrypi/picamera2 - GitHub PDF: The Picamera2 Library