picamera2を用いたカメラ画像の取得とLcdディスプレイへの表示

2024-01-12

カメラで取得した映像を、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_fileio.BytesIO()を使って、取得した画像をメモリ上のバイナリデータとして扱うこともできます。→ おまけ)

処理時間の比較

上記例ではカメラ画像をファイルとして保存していますが、普通にデータとして扱いたい場合もあります。

取得した後に、どのように使用するかによって、使い分けることになります。

以下に各関数を用いた際の処理時間をまとめました。 time.time()を使って計測し、1000回実行時の平均値を採用しています。

関数名処理時間 [s]返り値
capture_buffers0.255[<class 'numpy.ndarray'>], <class 'dict'> (1dimとメタデータ)
capture_array0.607<class 'numpy.ndarray'> (2dim)
capture_image0.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のイメージを準備することを想定した場合、必要な処理は以下になります。

この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]
パターン10.927
パターン20.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]
パターン30.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_arraymake_imageするだけなら実際はPicamera2()を使用しません。 そのため、ソースコードを参考に変換することは容易です。 ただ、私はrawで撮影し、カメラとPicamera2無しで現像することにしました。 → Picamera2を使用したRAW撮影と現像処理の方法

picam2.helpers.make_imageを使って生成する方がスマートかと思います。exampleがGitHubにありました。

picamera2/examples/capture_helpers.py - raspberrypi/picamera2 - GitHub

参考文献

1

picamera2は、Pythonのライブラリで、Raspberry Pi上でカメラシステムを使用する際に利用されます。基本的にはリボンケーブルで接続されるカメラを想定しているようです。Picamera2はドキュメントがPDFにより提供されています。 raspberrypi/picamera2 - GitHub PDF: The Picamera2 Library