Baslerカメラを使った画像取得方法: PyPylonの活用


## Baslerカメラを使った画像取得方法: PyPylonの活用



### 1. はじめに

この記事は、以下の記事の内容を再構成したものです。

https://pythonforthelab.com/blog/getting-started-with-basler-cameras/

Baslerは、高品質なカメラを提供することで知られるメーカーであり、その製品は顕微鏡画像取得やコンピュータビジョン、監視カメラ用途など幅広い領域で使用されています。これらのカメラは、強力なソフトウェア開発キット（SDK）であるPylonと共に提供され、開発者がカメラを容易に統合し、カスタマイズできるよう設計されています。

特にPythonユーザーにとって、BaslerはPyPylonというPythonバインディングを提供しています。これは、Pylon SDKのPythonラッパーであり、Python環境でBaslerカメラを操作するための便利なツールです。本記事では、PyPylonを使用してBaslerカメラから画像を取得する方法を詳しく説明します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/00462a00-ed5f-4784-be9c-d42c533c47fc.png)

このチュートリアルを通じて、以下の内容を学ぶことができます。

- PyPylonのインストールとセットアップ
- カメラの接続と認識
- 画像の取得とパラメータ設定
- 高度な設定とコールバックの活用

これにより、Pythonを使用してBaslerカメラを操作し、プロジェクトに組み込むための基本的なスキルを習得できます。

### 2. PyPylonのインストール

BaslerカメラをPython環境で操作するためには、PyPylonパッケージをインストールする必要があります。PyPylonは、Pylon SDKのPythonバインディングであり、カメラからの画像取得や制御をPythonで行うためのインターフェースを提供します。以下では、PyPylonのインストール手順と注意点について説明します。

#### PyPylonのインストール手順

1. **Python環境の準備**

   まず、Pythonがインストールされていることを確認してください。Python 3.xを推奨します。`pip`が使える状態であることも確認してください。

2. **PyPylonのインストール**

   PyPylonは`pip`を使って簡単にインストールできます。以下のコマンドをターミナルまたはコマンドプロンプトで実行してください。

   ```bash
   pip install pypylon
   ```

   これにより、PyPylonと必要な依存関係が自動的にインストールされます。

3. **Pylonソフトウェアスイートのインストール**

   PyPylonのみではなく、Pylon Viewerを含むPylonソフトウェアスイートのインストールも推奨されます。Pylon Viewerを使用することで、カメラの動作確認や設定変更をGUI上で行うことができます。

   - [Baslerの公式サイト](https://www.baslerweb.com/en/sales-support/downloads/software-downloads/)からPylonソフトウェアスイートをダウンロードしてください。
   - オペレーティングシステムとアーキテクチャに適したバージョンを選択してください。

4. **カメラドライバのインストール**

   カメラを接続する前に、必要なドライバがインストールされていることを確認してください。通常、Pylonソフトウェアスイートに含まれているドライバで十分です。

#### インストール時のトラブルシューティング

- **パッケージの不整合**

  PyPylonのバージョンが最新のPylon SDKと一致しない場合、インストール中や実行中にエラーが発生することがあります。この場合は、PyPylonのコードをGitHubからダウンロードし、ローカルでインストールすることが推奨されます。

  ```bash
  git clone https://github.com/basler/pypylon.git
  cd pypylon
  python setup.py install
  ```

- **ライブラリの読み込みエラー**

  インストール後に`ImportError: cannot import name '_pylon'`というエラーが出る場合は、Pylonのライブラリが正しくインストールされていない可能性があります。この場合、Pylonのインストールディレクトリを確認し、`PYLON_ROOT`環境変数を設定してください。

  ```bash
  export PYLON_ROOT=/opt/pylon
  python setup.py install
  ```

- **USBポートへのアクセス権限**

  LinuxシステムでUSBカメラを使用する場合、USBポートへのアクセス権限が必要なことがあります。この場合、適切なアクセス権限を設定するか、rootユーザーでプログラムを実行してください。

PyPylonとPylon Viewerのインストールが完了したら、カメラから画像を取得する準備が整いました。次の章では、接続されたカメラを認識し、使用する方法について説明します。

### 3. カメラの接続と認識

BaslerカメラをPyPylonで操作するためには、まずコンピュータに接続されたカメラを認識し、使用したいカメラを選択する必要があります。この章では、接続されたカメラを認識する方法と、特定のカメラを選択する手順を説明します。

#### 接続されたカメラの認識

PyPylonを使用してコンピュータに接続されたカメラのリストを取得するには、以下のコードを実行します。これにより、すべての利用可能なカメラを列挙し、それらの基本情報を表示します。

```python
from pypylon import pylon

# トランスポートレイヤーインスタンスを取得
tl_factory = pylon.TlFactory.GetInstance()

# 接続されたカメラを列挙
devices = tl_factory.EnumerateDevices()

# 接続されたカメラのリストを表示
for device in devices:
    print(f"カメラ名: {device.GetFriendlyName()}")
```

このコードでは、PyPylonの`TlFactory`クラスを使用して、コンピュータに接続されたすべてのカメラを列挙しています。`EnumerateDevices`メソッドは、接続されたカメラの情報を含むリストを返します。この情報には、カメラのフレンドリーネーム（ユーザーにわかりやすい名前）が含まれています。

#### カメラの識別情報

カメラを選択する際には、フレンドリーネーム以外の識別情報も利用できます。たとえば、シリアル番号やフルネームを取得することで、特定のカメラを指定できます。

```python
for device in devices:
    print(f"フルネーム: {device.GetFullName()}")
    print(f"シリアル番号: {device.GetSerialNumber()}")
```

- `GetFullName()`: 接続されたカメラの詳細な名前を取得します（例: `2676:ba05:3:2:10`）。
- `GetSerialNumber()`: カメラのシリアル番号を取得します（例: `40063823`）。

#### 特定のカメラを選択

複数のカメラが接続されている場合、特定のカメラを選択する必要があります。通常、1台のカメラのみを使用する場合は`CreateFirstDevice`メソッドを利用して最初に見つかったカメラを選択しますが、特定のシリアル番号を持つカメラを選ぶこともできます。

```python
# 最初のカメラを選択する場合
camera = pylon.InstantCamera(tl_factory.CreateFirstDevice())

# シリアル番号で特定のカメラを選択する場合
for device in devices:
    if device.GetSerialNumber() == "40063823":
        camera = pylon.InstantCamera(tl_factory.CreateDevice(device))
        break
```

`InstantCamera`オブジェクトを作成し、選択したカメラをアタッチすることで、カメラを操作する準備が整います。



### 4. 画像の取得

Baslerカメラを使用して画像を取得することは、カメラ操作の基本であり、最も重要なステップです。この章では、PyPylonを使用してカメラから画像を取得する方法を具体的に説明します。

#### InstantCameraオブジェクトの作成とアタッチ

画像を取得するためには、まず`InstantCamera`オブジェクトを作成し、使用したいカメラをアタッチします。これは、カメラのライフサイクル管理や画像取得を容易にするためのオブジェクトです。

```python
from pypylon import pylon

# トランスポートレイヤーインスタンスを取得
tl_factory = pylon.TlFactory.GetInstance()

# InstantCameraオブジェクトの作成
camera = pylon.InstantCamera()

# 最初に見つかったデバイスをアタッチ
camera.Attach(tl_factory.CreateFirstDevice())
```

このコードでは、`TlFactory`を利用して最初に見つかったカメラデバイスを取得し、`InstantCamera`にアタッチしています。

#### 画像の取得手順

画像を取得する基本的な手順は、カメラを開き、指定したフレーム数の画像を取得し、結果を処理するというものです。以下のコードは、1フレームの画像を取得する例です。

```python
# カメラを開く
camera.Open()

# 1フレームの画像を取得
camera.StartGrabbing(1)

# 結果を取得
grab = camera.RetrieveResult(2000, pylon.TimeoutHandling_Return)

# 取得が成功した場合
if grab.GrabSucceeded():
    img = grab.GetArray()
    print(f'画像のサイズ: {img.shape}')

# カメラを閉じる
camera.Close()
```

- **`camera.Open()`**: カメラを開き、デバイスとの通信を確立します。
- **`camera.StartGrabbing(1)`**: 取得するフレーム数を指定し、画像取得を開始します。この場合は1フレームを取得します。
- **`camera.RetrieveResult(2000, pylon.TimeoutHandling_Return)`**: 画像取得結果を指定したタイムアウト内に取得します。タイムアウトは2000ミリ秒（2秒）です。
- **`grab.GrabSucceeded()`**: 取得が成功したかどうかを確認します。
- **`grab.GetArray()`**: 取得した画像データをNumPy配列として取得します。

画像取得が完了したら、必ず`camera.Close()`でカメラを閉じ、リソースを解放してください。

#### エラー処理とタイムアウト

画像取得時にタイムアウトが発生した場合や、取得が成功しなかった場合のエラー処理も重要です。タイムアウトを短くしすぎると、露光時間よりも短い場合にエラーが発生します。以下のコードは、エラー処理を含む画像取得の例です。

```python
try:
    grab = camera.RetrieveResult(2000, pylon.TimeoutHandling_ThrowException)
    if grab.GrabSucceeded():
        img = grab.GetArray()
    else:
        print(f"エラー: {grab.GetErrorCode()} - {grab.GetErrorDescription()}")
except Exception as e:
    print(f"例外が発生しました: {e}")
finally:
    camera.Close()
```

- **`TimeoutHandling_ThrowException`**: タイムアウトが発生した場合に例外をスローします。
- **例外処理**: `try`ブロックで画像取得を試み、例外が発生した場合は`except`ブロックで処理します。

### 5. 取得パラメータの変更

カメラから画像を取得する際、露光時間やフレームレートなどの取得パラメータを適切に設定することが重要です。PyPylonでは、これらのパラメータを簡単に変更でき、特定のアプリケーション要件に合わせてカメラの動作をカスタマイズできます。この章では、基本的なパラメータの変更方法を説明します。

#### 露光時間の変更

露光時間は、画像取得時に最もよく変更されるパラメータの一つです。露光時間は、カメラセンサーが光を感知する時間を制御し、画像の明るさに直接影響します。PyPylonを使用して露光時間を設定するには、次のようにします。

```python
# カメラを開く
camera.Open()

# 露光時間を設定（単位はマイクロ秒）
camera.ExposureTime.SetValue(50000)

# 設定した露光時間を確認
print(f'露光時間: {camera.ExposureTime.GetValue()} µs')

# カメラを閉じる
camera.Close()
```

上記のコードでは、露光時間を50,000マイクロ秒（50ミリ秒）に設定しています。`SetValue`メソッドを使用して露光時間を指定し、`GetValue`メソッドで現在の設定値を確認できます。

#### 露光時間の制約と確認

露光時間を設定する際には、そのカメラがサポートする範囲内で設定する必要があります。サポートされている最小値、最大値、および単位を確認する方法は以下の通りです。

```python
# カメラを開く
camera.Open()

# 露光時間の単位、最小値、最大値を取得
unit = camera.ExposureTime.GetUnit()
min_val = camera.ExposureTime.GetMin()
max_val = camera.ExposureTime.GetMax()

print(f'露光時間の単位: {unit}')
print(f'露光時間の範囲: {min_val} - {max_val} {unit}')

# カメラを閉じる
camera.Close()
```

#### その他のパラメータの設定

露光時間のほかにも、カメラの動作を制御するためのパラメータは多く存在します。例えば、フレームレートやゲインなどがあります。これらのパラメータも同様の方法で設定できます。

```python
# カメラを開く
camera.Open()

# フレームレートの設定
camera.AcquisitionFrameRateEnable.SetValue(True)
camera.AcquisitionFrameRate.SetValue(30.0)
print(f'フレームレート: {camera.AcquisitionFrameRate.GetValue()} FPS')

# ゲインの設定
camera.Gain.SetValue(10.0)
print(f'ゲイン: {camera.Gain.GetValue()}')

# カメラを閉じる
camera.Close()
```

このように、カメラの動作を細かく制御することで、特定のアプリケーションや環境に最適な設定を行うことができます。



### 6. 連続画像取得（フリーランモード）

カメラを使用する際、単一の画像ではなく、連続して複数の画像を取得したい場合があります。フリーランモードを使用すると、カメラが新しいフレームを取得し続ける状態を維持できます。この章では、PyPylonを使用して連続的に画像を取得する方法を説明します。

#### フリーランモードの設定

フリーランモードでは、カメラが連続して画像を取得し続けるため、取得戦略を`GrabStrategy_OneByOne`に設定します。この戦略は、取得されたフレームが順次処理されることを保証します。

```python
import time
from pypylon import pylon

# トランスポートレイヤーインスタンスを取得
tl_factory = pylon.TlFactory.GetInstance()

# カメラのインスタンスを作成
camera = pylon.InstantCamera(tl_factory.CreateFirstDevice())

# カメラを開く
camera.Open()

# フリーランモードでの画像取得を開始
camera.StartGrabbing(pylon.GrabStrategy_OneByOne)

# 取得したフレーム数のカウンタ
frame_count = 0

# フレームの取得開始
print('画像取得を開始します')
start_time = time.time()

# カメラがグラビング中かどうかを確認
while camera.IsGrabbing():
    # 結果を取得
    grab = camera.RetrieveResult(100, pylon.TimeoutHandling_ThrowException)

    # 取得が成功した場合
    if grab.GrabSucceeded():
        frame_count += 1

    # 100フレーム取得したら停止
    if frame_count == 100:
        break

print(f'{frame_count}フレームを{time.time() - start_time:.0f}秒で取得しました')

# カメラを閉じる
camera.Close()
```

このコードでは、カメラが画像を取得し続け、`IsGrabbing`メソッドを使ってカメラがデータを取得している状態を確認しています。`RetrieveResult`メソッドは、100ミリ秒のタイムアウトを設定してフレームを取得します。

#### 取得の停止

連続取得を停止するには、カメラを閉じるか、明示的に`StopGrabbing`メソッドを呼び出します。以下に、取得の停止方法を示します。

```python
# 取得中の画像数をリセット
frame_count = 0

# フレームの取得開始
print('画像取得を開始します')
start_time = time.time()

# カメラがグラビング中かどうかを確認
while camera.IsGrabbing():
    # 結果を取得
    grab = camera.RetrieveResult(100, pylon.TimeoutHandling_ThrowException)

    # 取得が成功した場合
    if grab.GrabSucceeded():
        frame_count += 1

    # 100フレーム取得したら停止
    if frame_count == 100:
        camera.StopGrabbing()
        break

print(f'{frame_count}フレームを{time.time() - start_time:.0f}秒で取得しました')

# カメラを閉じる
camera.Close()
```

このコードでは、100フレーム取得した時点で`camera.StopGrabbing()`を呼び出して画像取得を停止しています。

#### フリーランモードの応用

フリーランモードを活用することで、リアルタイムの画像処理や連続監視、データストリーミングといった用途に応用できます。フレームを取得するたびに、必要に応じて画像を保存したり、分析を行ったりすることができます。





### 7. 高度な設定とコールバック

カメラを使用して連続して画像を取得する際、特定のイベントに対してアクションをトリガーする必要があることがあります。PyPylonでは、コールバックを使用して、画像が取得されたときやカメラの状態が変化したときに特定の関数を実行することができます。この章では、コールバックの設定方法と活用例について説明します。

#### コールバックの設定

コールバックを設定するためには、特定のイベントハンドラを定義し、カメラに登録します。これにより、イベントが発生したときに自動的に関数が呼び出されます。以下に、カメラのライフサイクルイベントに対するコールバックを設定する例を示します。

```python
from pypylon import pylon

# カメラのイベントハンドラを定義
class CameraEventHandler(pylon.ConfigurationEventHandler):
    def OnAttach(self, camera):
        print(f'カメラが接続されました: {camera.GetDeviceInfo().GetModelName()}')

    def OnOpen(self, camera):
        print('カメラが開かれました')

    def OnClose(self, camera):
        print('カメラが閉じられました')

    def OnGrabStarted(self, camera):
        print('画像取得が開始されました')

    def OnGrabStopped(self, camera):
        print('画像取得が停止されました')

# カメラのインスタンスを作成
camera = pylon.InstantCamera(pylon.TlFactory.GetInstance().CreateFirstDevice())

# イベントハンドラを登録
camera.RegisterConfiguration(CameraEventHandler(), pylon.RegistrationMode_Append, pylon.Cleanup_Delete)

# カメラを開き、画像取得を開始
camera.Open()
camera.StartGrabbing(pylon.GrabStrategy_OneByOne)

# グラビングループ
while camera.IsGrabbing():
    grab = camera.RetrieveResult(100, pylon.TimeoutHandling_ThrowException)
    if grab.GrabSucceeded():
        # 取得した画像を処理
        img = grab.GetArray()
        # 何らかの画像処理を実行
    else:
        print(f"エラー: {grab.GetErrorCode()} - {grab.GetErrorDescription()}")

    # 条件に応じて取得を停止
    camera.StopGrabbing()
    break

camera.Close()
```

上記の例では、`ConfigurationEventHandler`クラスを拡張してカメラのイベントを処理するコールバックを定義しています。`OnAttach`、`OnOpen`、`OnClose`などのイベントメソッドをオーバーライドすることで、各イベント発生時に特定のアクションを実行します。

#### 画像イベントのハンドリング

画像が取得されたときのイベントを処理するために、`ImageEventHandler`を利用することもできます。以下に、画像取得時のイベントを処理する例を示します。

```python
class ImageEventHandler(pylon.ImageEventHandler):
    def OnImageGrabbed(self, camera, grabResult):
        print(f'画像が取得されました: {camera.GetDeviceInfo().GetModelName()}')

        # 取得が成功した場合の処理
        if grabResult.GrabSucceeded():
            img = grabResult.GetArray()
            # 画像を保存または処理する
            print(f"画像のサイズ: {img.shape}")
        else:
            print(f"エラー: {grabResult.GetErrorCode()} - {grabResult.GetErrorDescription()}")

# イベントハンドラを登録
camera.RegisterImageEventHandler(ImageEventHandler(), pylon.RegistrationMode_Append, pylon.Cleanup_Delete)
```

#### コールバックの利点

コールバックを利用することで、カメラのイベントや画像取得に対するリアルタイムの反応を可能にします。これにより、画像が取得されたタイミングで自動的に処理を行う、異常を検知した際にアラートを発する、特定の条件が満たされたときにデータを保存するといった高度な処理を実現できます。



### 8. バッファ管理とパフォーマンスの最適化

カメラから連続して画像を取得する際には、バッファ管理が重要です。バッファとは、画像データを一時的に保持するメモリ領域のことで、カメラからの画像データを効率的に処理するために使用されます。適切にバッファを管理することで、画像のドロップやパフォーマンスの低下を防ぐことができます。この章では、PyPylonでのバッファ管理とパフォーマンス最適化の手法について説明します。

#### バッファの動作

PyPylonでは、画像データはバッファに保存されます。各バッファは、取得された画像を保持し、プログラムがそれを処理するまで待機します。バッファ管理は、特に高フレームレートや大容量データを扱う場合に重要です。

##### バッファの管理とリリース

取得した画像データは、`RetrieveResult`メソッドを使用してバッファから取得されます。取得後は、バッファを再利用可能な状態にするためにリリースする必要があります。

```python
# 結果を取得
grab = camera.RetrieveResult(100, pylon.TimeoutHandling_Return)

if grab and grab.GrabSucceeded():
    img = grab.GetArray()
    # 画像の処理を実行

    # バッファをリリース
    grab.Release()
```

バッファをリリースすることで、Pylonがそのバッファを再利用できるようになり、メモリの効率的な利用が可能になります。

#### バッファのサイズ設定

バッファのサイズは、カメラが生成するデータ量に応じて調整する必要があります。デフォルトでは、Pylonは10個のバッファを作成しますが、必要に応じて増やすことができます。

```python
# バッファの最大数を設定
camera.MaxNumBuffer = 20
```

バッファ数を増やすことで、一度に保持できるフレーム数が増え、処理速度が追いつかない場合でも画像のドロップを防ぐことができます。ただし、システムのメモリ使用量が増加するため、環境に応じて適切な設定を行う必要があります。

#### 取得戦略の選択

取得戦略は、バッファから画像データをどのように取得するかを決定します。`GrabStrategy_OneByOne`は画像を順次取得する方法ですが、他の戦略も存在します。

```python
# 最新の画像を常に取得する戦略を設定
camera.StartGrabbing(pylon.GrabStrategy_LatestImageOnly)
```

この戦略を使用すると、最新の画像のみを処理し、過去の画像をスキップすることができます。リアルタイム性が重要な場合や、処理能力が限定されている場合に有効です。

#### パフォーマンスの最適化

- **バッファサイズの調整**: 使用するバッファの数やサイズを適切に設定し、システムのメモリを効率的に使用します。
- **画像処理の効率化**: 画像処理のアルゴリズムを最適化し、処理速度を向上させます。
- **非同期処理**: 画像取得と処理を非同期で行うことで、CPUとI/Oの効率を最大化します。



### 9. トラブルシューティングと次のステップ

PyPylonを使用してBaslerカメラを操作する際に直面する可能性のある問題を理解し、それに対する対処法を知ることは重要です。また、さらなる応用を考える際には、PyPylonの提供する追加機能についても理解しておくことが役立ちます。

#### トラブルシューティング

**1. インストールエラー**

- **問題**: `ImportError: cannot import name '_pylon'`というエラーが発生する場合。

  - **対策**: Pylon SDKとPyPylonのバージョンが一致しているか確認し、インストールディレクトリが正しいかをチェックします。必要に応じて、環境変数`PYLON_ROOT`を正しく設定します。

  ```bash
  export PYLON_ROOT=/opt/pylon
  ```

- **問題**: ライブラリのバージョン不一致。

  - **対策**: 使用しているPylon SDKのバージョンとPyPylonパッケージが対応していることを確認し、必要に応じて公式サイトから最新のバージョンをインストールします。

**2. 画像取得エラー**

- **問題**: `TimeoutException`が発生し、画像の取得に失敗する。

  - **対策**: タイムアウト値を適切に設定し、露光時間よりも長い時間に設定します。ネットワークの遅延や処理の負荷も考慮してください。

  ```python
  camera.RetrieveResult(5000, pylon.TimeoutHandling_ThrowException)
  ```

- **問題**: 画像がドロップされる。

  - **対策**: バッファのサイズを増やしてメモリの余裕を持たせます。また、画像処理の効率を向上させ、ボトルネックを解消します。

**3. 接続エラー**

- **問題**: カメラが認識されない。
  - **対策**: USBケーブルや電源を確認し、デバイスマネージャでカメラが認識されているかを確認します。必要に応じてドライバを再インストールします。

#### 次のステップ

PyPylonとBaslerカメラを使いこなすための次のステップとして、以下の高度なトピックに進むことをお勧めします。

**1. トリガーモードの活用**

- トリガーモードを使用して、外部信号やソフトウェアコマンドで画像取得を制御することができます。これにより、特定のイベントに基づいて正確なタイミングで画像を取得することが可能になります。

```python
camera.TriggerMode.SetValue("On")
camera.TriggerSource.SetValue("Software")
camera.TriggerSoftware()
```

**2. 複数カメラの操作**

- PyPylonを使用して、複数のカメラを同時に操作する方法を学びます。これにより、複数視点からのデータ取得や同期撮影が可能になります。

**3. 画像処理の自動化**

- OpenCVやScikit-Imageなどのライブラリと組み合わせて、取得した画像の自動処理や分析を行います。これにより、リアルタイムの画像解析システムを構築できます。

**4. Pylon Viewerの活用**

- Pylon Viewerを使用して、カメラの全パラメータを確認し、最適な設定を見つけることができます。GUIを活用して、パラメータの実験や最適化を行うと便利です。

### 10. まとめ

PyPylonは、BaslerカメラをPython環境で柔軟に操作するための強力なツールです。これを活用することで、画像取得プロセスを効率化し、様々なアプリケーションでの使用が可能になります。この記事を通じて、基本的な使い方をマスターし、次のステップへと進むための基盤を築くことができました。



### 関連資料

pypylon basic code 

https://github.com/basler/pypylon/blob/master/samples/grab.py