WFPでvideoWindowを使ったビデオ表示
概要
このプログラムは、WPFアプリケーションでTheImagingSource社のカメラを制御・表示しています。主な処理は、カメラの設定読み込み、映像の取得、画像表示、そして画像の反転や回転処理です。ボタン操作で保存されたカメラ設定ファイルを読み込み、ビデオフォーマットに応じたピクセル形式(RGB24、RGB64、Y16など)を自動で判別し、FrameQueueSinkを通してカメラ映像をリアルタイムで受信・表示します。取得したフレームはWriteableBitmap形式 に変換され、必要に応じてRGB64対応の表示処理も行われます。
サンプルプログラム
Software | IC Imaging Control 3.5, Visual Studio™ 2022 |
---|---|
サンプル(C#) | IC_WPF_cs_3.5.zip |
サンプルプログラムの外観
解説
初期化
public MainWindow()
{
InitializeComponent();
_rotateFlipFilter = _ic.FrameFilterCreate("Rotate Flip", "");
_ic.DeviceFrameFilters.Add(_rotateFlipFilter);
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.RGB32, 5);
}
IこのコードはWPFのMainWindow初期化処理です。InitializeComponent()でUIを初期化し、回転・反転用のフィルターRotate Flipを作成してカメラ映像に適用します。続いて、画像受信用のFrameQueueSinkをRGB32形式で設定し、カメラからのフレームを受け取れるようにします。
画像の受信と表示処理
private FrameQueuedResult ShowBuffer(IFrameQueueBuffer buffer)
{
videoWindow1.UpdateImage(buffer);
//画像を保存(64bit対応)
//TIS.Imaging.FrameExtensions.SaveAsTiff(buffer, "RGB64.tiff");
return FrameQueuedResult.ReQueue;
}
この関数ShowBufferは、カメラからフレームが届いたときに呼び出されるコールバック処理です。引数bufferは1フレーム分の画像データを表しており、それをvideoWindow1.UpdateImage()を使ってWPFの画面に表示します。処理後、FrameQueuedResult.ReQueueを返すことで、このバッファを再利用し、次のフレームも連続して受け取れるようにします。これにより、リアルタイムなライブ映像表示が可能になります。
デバイス選択時のフォーマット判定とSinkの切り替え
// デバイス選択ボタンがクリックされたときの処理
private void btnSelectDevice_Click(object sender, RoutedEventArgs e)
{
// カメラのライブ映像を一時停止
_ic.LiveStop();
// デバイス設定ダイアログの代わりに、保存された設定ファイルを読み込む
//_ic.ShowDeviceSettingsDialog();
_ic.LoadShowSaveDeviceState("lastSelectedDeviceState.xml");
// 有効なデバイスが選択されていれば処理を継続
if (_ic.DeviceValid)
{
// 現在のビデオフォーマット文字列を取得(例:RGB24(640x480))
string videoformat = _ic.VideoFormat;
// "(" の位置を探して、その前の文字列(カラーフォーマットの種類)を抽出
int index = videoformat.IndexOf('(');
string beforeParenthesis = "";
if (index >= 0)
{
beforeParenthesis = videoformat.Substring(0, index).Trim(); // 空白除去
}
else
{
Console.WriteLine("( が見つかりませんでした。");
}
// カラーフォーマットに応じて Sink(画像受け取り方式)を切り替える
if (beforeParenthesis == "RGB24")
{
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.RGB24, 5);
}
else if (beforeParenthesis == "RGB32")
{
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.RGB32, 5);
}
else if (beforeParenthesis == "RGB64")
{
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.RGB64, 5);
}
else if (beforeParenthesis == "Y16")
{
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.Y16, 5);
}
else if (videoformat == "Y800")
{
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.Y800, 5);
}
else
{
// 未知のフォーマットの場合は RGB24 をデフォルトとして使用
_ic.Sink = new FrameQueueSink(ShowBuffer, MediaSubtypes.RGB24, 5);
}
// カメラのライブ映像を再開
_ic.LiveStart();
}
}
このコードは、WPFアプリで[Select Devide...]ボタンが押されたときに実行される処理です。まず、カメラのライブ映像を一時停止し、保存されている設定ファイル(lastSelectedDeviceState.xml)を読み込んで前回使用したカメラ設定を復元します。その後、現在のビデオフォーマット(例:RGB24(640x480))から「RGB24」などのカラーフォーマットを抽出し、使用するMediaSubtypeに応じたFrameQueueSinkを設定します。これにより、カメラからの画像データを適切なカラーフォーマットで取り込めるようになります。最後にLiveStart()を呼び、再びライブ映像の表示を開始します。カラーフォーマットに応じて処理を切り替えることで、RGB24・RGB64・モノクロ8bit・モノクロ16bitなどの様々なカラーフォーマットに対応しています。
チェックボックスとRotate Flipフィルターの連動
private void chkRotateFlipH_Checked(object sender, RoutedEventArgs e)
{
_rotateFlipFilter.SetBoolParameter("Flip H", chkRotateFlipH.IsChecked.Value);
}
private void chkRotateFlipH_Unchecked(object sender, RoutedEventArgs e)
{
_rotateFlipFilter.SetBoolParameter("Flip H", chkRotateFlipH.IsChecked.Value);
}
private void chkFlipRotateV_Checked(object sender, RoutedEventArgs e)
{
_rotateFlipFilter.SetBoolParameter("Flip V", chkRotateFlipV.IsChecked.Value);
}
private void chkFlipRotateV_Unchecked(object sender, RoutedEventArgs e)
{
_rotateFlipFilter.SetBoolParameter("Flip V", chkRotateFlipV.IsChecked.Value);
}
「水平方向の反転(Flip H)」「垂直方向の反転(Flip V)」を制御するチェックボックスのイベントハンドラです。
例えば、SetBoolParameter("Flip H", true/false)により、回転フィルターの水平反転機能をON/OFFしています。チェックを入れても外しても、現在の値(IsChecked.Value)をそのままフィルターに適用し、チェックボックスとの連動するようにしています。
WPFアプリケーション上に表示するための処理
// カメラから取得した画像フレームを WPF の Image に表示する処理
public void UpdateImage(TIS.Imaging.IFrame buffer)
{
// 現在のスレッドが UI スレッドか確認
if (display.Dispatcher.CheckAccess())
{
// UI スレッドであれば、直接描画処理を実行
DoUpdateImage(buffer);
}
else
{
// UI スレッド以外からの呼び出しの場合
// すでに非同期描画処理が進行中であれば処理をスキップ(多重更新防止)
if ((_updateImageOperation != null) && (_updateImageOperation.Status != System.Windows.Threading.DispatcherOperationStatus.Completed))
{
return;
}
// UI スレッドに非同期で描画処理を依頼
_updateImageOperation = display.Dispatcher.BeginInvoke(new Action(() => DoUpdateImage(buffer)));
}
}
// UIスレッド上での非同期更新操作の状態を保持する変数
private System.Windows.Threading.DispatcherOperation _updateImageOperation = null;
// 実際の画像更新処理を行う関数(UIスレッド上で実行される)
private void DoUpdateImage(TIS.Imaging.IFrame buffer)
{
// フレームのサイズとピクセルフォーマットを取得
int bufferWidth = buffer.FrameType.Width;
int bufferHeight = buffer.FrameType.Height;
PixelFormat bufferFormat = PixelFormatFromFrameType(buffer.FrameType);
// フレームバッファのポインタを取得
IntPtr ptr = buffer.GetIntPtr();
// 現在の WriteableBitmap が null または サイズ・フォーマットが異なる場合は再作成
if ((_source == null)
|| (_source.PixelWidth != bufferWidth)
|| (_source.PixelHeight != bufferHeight)
|| (_source.Format != bufferFormat))
{
if (buffer.FrameType.BitsPerPixel == 64)
{
// RGB64(16bit × 4ch)の場合は専用の変換処理で WriteableBitmap を生成
_source = ConvertRgb64ToWriteableBitmap(ptr, bufferWidth, bufferHeight);
}
else
{
// 通常フォーマット(例:RGB24)の場合は WriteableBitmap を作成してバッファをコピー
_source = CreateBackBuffer(bufferWidth, bufferHeight, bufferFormat);
CopyImageBufferToWritableBitmap(buffer, _source);
}
// WPFの Image コントロールに表示
display.Source = _source;
}
else
{
// バッファサイズとフォーマットが同じであれば、既存の WriteableBitmap に上書きするだけでOK
CopyImageBufferToWritableBitmap(buffer, _source);
}
// オーバーレイ描画用のアドーナーがある場合は再描画を要求
if (_overlayAdorner != null)
{
_overlayAdorner.InvalidateVisual();
}
// 画像が上下逆かどうかの情報を記録(フリップ描画の補正に使用)
IsSourceBottomUp = buffer.FrameType.IsBottomUp;
// FlipH / FlipV の状態に基づいて描画の反転を更新
UpdateFlip();
}
このコードは、The Imaging Controlで取得したカメラ映像フレーム(IFrame)をWPFアプリケーション上に表示するための処理を行います。UpdateImageメソッドは、フレームをWPFのImageコントロールに表示する前処理であり、現在のスレッドがUIスレッドかどうかをDispatcher.CheckAccess()で確認します。UIスレッドでなければ、非同期にDoUpdateImageを呼び出します。重複更新を防ぐため、未完了の更新操作があれば処理をスキップします。
DoUpdateImageは、画像サイズやフォーマットが変わった場合に新たなWriteableBitmapを生成し、RGB64形式の場合は専用の変換処理(ConvertRgb64ToWriteableBitmap)を用いてWPFの表示できる形式(RGB24)に変換します。既存のサイズ・形式と一致する場合は、バッファだけを更新して高速化しています。オーバーレイ表示の再描画を行い、上下反転の補正情報IsSourceBottomUpをUpdateFlip()に反映させることで、映像表示と描画が正しく整合されます。
映像表示のためにRGB64からRGB24に変換
public static WriteableBitmap ConvertRgb64ToWriteableBitmap(IntPtr bufferPtr, int width, int height)
{
int pixelCount = width * height;
int srcBytesPerPixel = 8; // R16 G16 B16 A16 = 16bit × 4ch
int dstBytesPerPixel = 3; // 8bit RGB
int dstStride = ((width * dstBytesPerPixel + 3) / 4) * 4; // 4バイトアライメント
byte[] srcBuffer = new byte[pixelCount * srcBytesPerPixel];
byte[] dstBuffer = new byte[dstStride * height];
// TISバッファ(RGB64)をコピー
Marshal.Copy(bufferPtr, srcBuffer, 0, srcBuffer.Length);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int i = y * width + x;
int srcIndex = i * srcBytesPerPixel;
int dstIndex = y * dstStride + x * dstBytesPerPixel;
// 各チャンネルの上位8bitを取得(リトルエンディアン)
ushort r16 = (ushort)(srcBuffer[srcIndex + 0] | (srcBuffer[srcIndex + 1] << 8));
ushort g16 = (ushort)(srcBuffer[srcIndex + 2] | (srcBuffer[srcIndex + 3] << 8));
ushort b16 = (ushort)(srcBuffer[srcIndex + 4] | (srcBuffer[srcIndex + 5] << 8));
dstBuffer[dstIndex + 0] = (byte)(b16 >> 8); // B
dstBuffer[dstIndex + 1] = (byte)(g16 >> 8); // G
dstBuffer[dstIndex + 2] = (byte)(r16 >> 8); // R
}
}
// WPFのWriteableBitmapを作成
WriteableBitmap wb = new WriteableBitmap(
width,
height,
96, 96, // DPI
PixelFormats.Rgb24,
null);
wb.WritePixels(new Int32Rect(0, 0, width, height), dstBuffer, dstStride, 0);
return wb;
}
この関数は、IC Imaging Controlから得られるRGB64形式(16bit×4チャンネル:R16G16B16A16)の画像バッファを、WPFで扱える8bit×3チャンネル(RGB24)に変換して表示するための WriteableBitmap を作成します。
まず、ネイティブメモリ(IntPtr)の画像バッファをMarshal.CopyでC#のバイト配列にコピーし、各画素ごとにR・G・Bの上位8bit(16bit→8bit)を抽出して新たなバッファに変換します。変換後はWriteableBitmap.WritePixelsを使ってWPFに描画可能な画像オブジェクトを生成して返しています。
この処理によって画像の表示が遅くなることがありますので表示を早くしたい場合にはRGB24やRGB32のカラーフォーマットをあらかじめ設定しておいてください。
画像上で上下・左右反転(Rotateを使用せず)
// 映像の上下反転を制御する依存関係プロパティ(WPFバインディング対応)
// デフォルト値は false(反転なし)
// 値が変更されると再描画が発生し、FlipChanged が呼ばれる
public static readonly DependencyProperty FlipVProperty =
DependencyProperty.Register("FlipV", typeof(Boolean), typeof(VideoWindow),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(FlipChanged)
));
// 映像の左右反転を制御する依存関係プロパティ(WPFバインディング対応)
// デフォルト値は false(反転なし)
// 値が変更されると再描画が発生し、FlipChanged が呼ばれる
public static readonly DependencyProperty FlipHProperty =
DependencyProperty.Register("FlipH", typeof(Boolean), typeof(VideoWindow),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(FlipChanged)
));
// 上下反転状態にアクセスするための CLRプロパティ(XAMLやコードから使用可能)
public bool FlipV
{
get
{
return (bool)GetValue(FlipVProperty); // 現在の値を取得
}
set
{
SetValue(FlipVProperty, value); // 値を設定(FlipChangedが自動で呼ばれる)
}
}
// 左右反転状態にアクセスするための CLRプロパティ(XAMLやコードから使用可能)
public bool FlipH
{
get
{
return (bool)GetValue(FlipHProperty); // 現在の値を取得
}
set
{
SetValue(FlipHProperty, value); // 値を設定(FlipChangedが自動で呼ばれる)
}
}
// FlipV または FlipH のプロパティ値が変更されたときに呼ばれるコールバック関数
// 実際の反転処理(描画の更新)を行う UpdateFlip を呼び出す
static void FlipChanged(DependencyObject dep, DependencyPropertyChangedEventArgs e)
{
var vw = dep as VideoWindow; // プロパティが変更された VideoWindow を取得
vw.UpdateFlip(); // 反転状態を描画に反映
}
このコードは、WPFで画像の上下・左右反転を切り替えるための仕組みを実装したものです。WPFでは通常のプロパティとは別に「依存関係プロパティ(DependencyProperty)」という特殊な仕組みを使うことで、UIのXAMLとの連携や自動再描画が可能になります。ここでは FlipV(上下反転)と FlipH(左右反転)という2つのプロパティを定義し、どちらも false(反転なし)を初期値としています。値が変更されるとFlipChanged関数が自動で呼ばれ、反転状態を画面に反映するUpdateFlip()が実行されます。これにより、ユーザーがチェックボックスなどを使って反転を切り替えた際、即座に映像が反映される仕組みになっています。