Baslerのpylon SDKでカメラ画像を1枚取得する方法（C# × .NET 8）

# Baslerのpylon SDKでカメラ画像を1枚取得する方法（C# × .NET 8）

Basler社の産業用カメラと、pylon SDKを使って画像を1枚だけ取得し、Bitmap形式で保存するまでの処理を紹介します。

業務で初めてBaslerカメラを扱った際、.NET向けの実例が少なく試行錯誤したため、動作確認済みコードを共有します。

本記事では、
- カメラ接続
- 1枚取得（Snap）
- BitmapSource変換
- 保存（.bmp）

までを扱います。

---

> ⚠️ **ご注意**
>
> 本記事は、Basler社が提供する「pylon Camera Software Suite SDK」を用いて、自作アプリケーションを開発・解説するものです。  
> 記載された内容は筆者が独自に調査・実装したものであり、**Basler社の公式見解やサポートを受けたものではありません**。
>
> 本記事では **Basler SDK本体（DLLやヘッダファイル等）の配布や転載は一切行っておりません**。  
> SDKのご利用には、Basler公式サイトより正規にダウンロード・同意のうえ、ライセンス契約に基づきご利用ください：
>
> 🔗 [pylon SDK ダウンロードページ](https://www.baslerweb.com/ja-jp/products/software/basler-pylon-camera-software-suite/)
>
> また、SDKの利用にあたっては、**商用目的・再配布・高リスク用途などに制限がある場合があります**。詳細は [エンドユーザライセンス契約（EULA）](https://www.baslerweb.com/ja-jp/service/eula/) をご確認ください。
>
> 本記事内のサンプルコード・解説は、学習・参考目的での利用を想定しており、利用者の責任のもとでご活用ください。


---

## ✅ 環境

| 項目     | 内容                  |
|----------|-----------------------|
| SDK      | [pylon Camera Software Suite](https://www.baslerweb.com/ja-jp/products/software/basler-pylon-camera-software-suite/)（Basler公式） |
| 言語     | C# (.NET 8.0-windows) |
| カメラ   | Basler acA2500-14gm   |
| OS       | Windows 10/11         |

---

## 1. カメラへ接続する

まずは、接続用クラスを用意します。最初に見つかったカメラ、または指定した名前のカメラに接続します。カメラ名は`Basler Pylon Viewer`などで確認できます。

```csharp
using System;
using Basler.Pylon;

namespace BaslerSamples
{
    public class BaslerCameraSample
    {
        public Camera? Camera { get; private set; }
        public bool IsConnected => Camera?.IsConnected ?? false;

        public bool Connect()
        {
            var camera = new Camera();
            return ConnectInternal(camera);
        }

        public bool Connect(string cameraName)
        {
            try
            {
                var camera = new Camera(cameraName);
                return ConnectInternal(camera);
            }
            catch (InvalidOperationException e)
            {
                Console.WriteLine($"Failed to create camera. Message: {e.Message}");
                return false;
            }
        }

        private bool ConnectInternal(Camera camera)
        {
            try
            {
                camera.Open();
                if (camera.IsOpen)
                {
                    Console.WriteLine("Camera connected successfully.");
                    Camera = camera;
                    return true;
                }
                else
                {
                    Console.WriteLine("Failed to open camera.");
                    return false;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error connecting to camera: {ex.Message}");
                return false;
            }
        }
    }
}
```

---

## 2. 画像を1枚取得する

`GrabOne()` メソッドを使うことで、カメラから1フレームだけ取得できます。

```csharp
public IGrabResult Snap(int timeoutMs = 5000)
{
    if (Camera == null)
        throw new InvalidOperationException("Camera is not connected.");

    var streamGrabber = Camera.StreamGrabber ?? throw new InvalidOperationException("StreamGrabber is not available.");
    var grabResult = streamGrabber.GrabOne(timeoutMs);

    if (grabResult != null && grabResult.GrabSucceeded)
        return grabResult;
    else
        throw new Exception("Failed to grab image: " + (grabResult?.ErrorDescription ?? "Unknown error"));
}
```

---

## 3. WPFで使える `BitmapSource` に変換

取得した `IGrabResult` を `BitmapSource` に変換すれば、WPFなどで扱いやすくなります。

```csharp
using System.Windows.Media;
using System.Windows.Media.Imaging;

public BitmapSource SnapToBitmapSource(int timeoutMs = 5000)
{
    using IGrabResult grabResult = Snap(timeoutMs);
    PixelDataConverter converter = new();

    var (outputType, pixelFormat, bytesPerPixel) = grabResult.PixelTypeValue switch
    {
        PixelType.RGB8packed => (PixelType.RGB8packed, PixelFormats.Rgb24, 3),
        PixelType.BGR8packed => (PixelType.BGR8packed, PixelFormats.Bgr24, 3),
        _ => (PixelType.Mono8, PixelFormats.Gray8, 1),
    };

    converter.OutputPixelFormat = outputType;

    int width = grabResult.Width;
    int height = grabResult.Height;
    int stride = width * bytesPerPixel;

    byte[] buffer = new byte[stride * height];
    converter.Convert(buffer, grabResult);

    return BitmapSource.Create(width, height, 96, 96, pixelFormat, null, buffer, stride);
}
```

---

## 4. 画像を保存する

取得した `BitmapSource` を `.bmp` ファイルとして保存するには、以下のように `BmpBitmapEncoder` を使用します。

```csharp
public static void SaveBitmap(BitmapSource bitmap, string filePath)
{
    using FileStream fileStream = new(filePath, FileMode.Create);
    var encoder = new BmpBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bitmap));
    encoder.Save(fileStream);
}
```

---

## 5. テストコード（実行例）

以下のように単体テスト内で使うことで、保存動作まで確認できます。

```csharp
static BaslerCameraSample _baslerCameraSample = new BaslerCameraSample();

[TestMethod()]
public void SnapToBitmapSourceTest()
{
    if (!_baslerCameraSample.IsConnected)
        _baslerCameraSample.Connect();

    var result = _baslerCameraSample.SnapToBitmapSource(1234);
    Assert.IsNotNull(result);

    BaslerCameraSample.SaveBitmap(result, "SaveBitmapTest.bmp");
    Assert.IsTrue(File.Exists("SaveBitmapTest.bmp"));
}
```

---

## ✅ 実行結果（保存された画像）

カメラで取得した画像は `"SaveBitmapTest.bmp"` として保存されます。以下は筆者が実際に撮影してみた例です。

![SaveBitmapTest.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4121147/e5f59add-5aab-4f9b-b83e-fcf0c7eb2c30.png)

---

## ✨ おわりに

Basler pylon .NETの記事はあまり見かけませんが、実装が簡単でWindowsアプリにも組み込みやすく、筆者は気に入っています。