WPFアプリでBaslerカメラの静止画を表示する【pylon SDK / C# / .NET 8】
「コードは動くけど、GUIでプレビューできるところまで一気に行きたい」。
本記事はその最初の一歩として、WPF で “Connect / Disconnect / Snap” を備えた最小ビューアを作ります。
ボタンを押すだけで1枚撮影し、画面の Image に表示されるところまでを丁寧に解説します。
ゴール
-
Connect/Disconnect/Snapの3ボタンで操作できる -
Snapで取得した1枚をImageに表示 - 終了時に確実に切断・解放(リソースリーク防止)
構成
これまでの記事で作成した BaslerCameraSample(クラスライブラリ) (Basler pylon SDKでソフトウェアトリガー撮影を行う(C# / .NET対応))をそのまま使い、
新しく WPF アプリ BaslerGUISample を追加して参照に設定します。
View(XAML)
3つのボタンとプレビュー用 Image を並べます。
<Window x:Class="BaslerGUISample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BaslerGUISample"
mc:Ignorable="d"
Title="BaslerGUISample" Height="450" Width="450" Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Button Content="Connect" Width="80" Margin="4,0" Command="{Binding ConnectCommand}"/>
</Grid>
<Grid Grid.Row="0" Grid.Column="1">
<Button Content="Disconnect" Width="80" Margin="4,0" Command="{Binding DisconnectCommand}"/>
</Grid>
<Grid Grid.Row="0" Grid.Column="2">
<Button Content="Snap" Width="80" Margin="4,0" Command="{Binding SnapCommand}"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
<Image x:Name="PreviewImage" Source="{Binding CurrentFrame}" Stretch="UniformToFill"/>
</Grid>
</Grid>
</Window>
XAMLデザイナ上での見た目は次の通り。
コードビハインド側では
DataContext = new MainViewModel();を設定し、
Window_Closingで必ず切断するようにします(後述)。
ViewModel
-
BindableBase/DelegateCommandは任意の実装でOK(本稿では中身は省略) - これまで作ってきた
BaslerCameraSampleを使って接続・撮影・切断 -
CurrentFrame(BitmapSource)をImage.Sourceにバインド - 接続時にUIが固まるようなら、
Connectの非同期化も検討
using BaslerSamples;
// 筆者は`BindableBase`、`DelegateCommand`を`Common`で実装しています。
// お使いの環境に合わせて実装してください。
// using Common;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media.Imaging;
namespace BaslerGUISample.ViewModels
{
public partial class MainViewModel : BindableBase
{
private readonly BaslerCameraSample _cameraService = new();
public MainViewModel()
{
ConnectCommand = new DelegateCommand(Connect, () => !IsConnected);
DisconnectCommand = new DelegateCommand(Disconnect, () => IsConnected);
SnapCommand = new DelegateCommand(Snap, () => IsConnected);
Connect(); // 起動時に自動接続する場合はここで
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set
{
SetProperty(ref _isConnected, value);
}
}
private BitmapSource? _currentFrame;
public BitmapSource? CurrentFrame
{
get => _currentFrame;
set => SetProperty(ref _currentFrame, value);
}
public DelegateCommand ConnectCommand { get; }
public DelegateCommand DisconnectCommand { get; }
public DelegateCommand SnapCommand { get; }
public void Connect()
{
IsConnected = _cameraService.Connect();
}
/// <summary>
/// カメラ接続を切断します。
/// </summary>
public void Disconnect()
{
_cameraService.Disconnect();
IsConnected = _cameraService.IsConnected;
}
public void Snap()
{
try
{
CurrentFrame = _cameraService.SnapToBitmapSource();
}
catch (InvalidOperationException ex)
{
// カメラ未接続時にメッセージボックスを表示。
MessageBox.Show($"Failed to snap. Error: {ex.Message}");
}
}
}
}
(参考)MainWindow のコードビハインド
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel();
DataContext = _viewModel;
}
/// <summary>
/// ウィンドウが閉じられる前にカメラ接続を切断します。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_viewModel.Disconnect();
}
BaslerCameraSample に切断機能を追加
これまで接続中心だったので、確実に解放する Disconnect() を用意し、アプリ終了時に呼び出します。
public void Disconnect()
{
if (Camera != null)
{
if (IsGrabbing) StopGrabbing();
if (Camera.IsOpen) Camera.Close();
Camera.Dispose();
Camera = null;
Console.WriteLine("Camera disconnected.");
}
}
実装のポイント / よくあるハマりどころ
-
UIスレッド更新
連続撮影(イベント駆動)に進むと、UIが固まらないようにするため、ImageGrabbedは別スレッドで発火します。
その場合はApplication.Current.Dispatcher.Invokeなどで UIスレッドに戻してCurrentFrameを更新 してください(次回以降で実装予定) -
Stretch の違い
UniformToFillは余白なく表示・一部トリミングあり、Uniformは全体表示・余白あり。用途に合わせて選択 -
例外の扱い
接続や撮影に失敗した場合は、MessageBoxなどでユーザーに分かる形で通知すると親切
まとめ
- WPF の 最小ビューア(Connect / Disconnect / Snap / 画像表示)を実装した
- MVVM で View と制御ロジックを分離、
Image.SourceにBitmapSourceをバインド - 終了時の
Disconnect()でリソースを確実に解放 - 連続撮影やイベント駆動表示へ進む下地が整った(次回以降で発展)
次回予告
次の記事では、イベント駆動で連続取得しながらUIを非同期更新する方法を解説します。
キュー+非同期保存や OpenCV 表示とも連携し、実務投入できるビューアへ発展させます。
👨💻 筆者について
@MilleVision
産業用カメラ・画像処理システムの開発に関する情報を発信中。
pylon SDK × C# の活用シリーズを連載しています。
🛠 サンプルコード完全版のご案内
Qiita記事のサンプルをまとめた C#プロジェクト(単体テスト付き) を BOOTH で配布しています。
本記事で省略した BindableBase や DelegateCommand の実装も同梱。
- 撮影・露光・ゲイン・フレームレート・ROI・イベント駆動撮影など主要機能を網羅
- 単体テスト同梱で動作確認や学習がスムーズ
- 記事更新に合わせてアップデート予定

