ビニング処理の実装 ― 画質×速度×帯域の実用バランス【Basler pylon × C#】
産業用カメラで何か検査したいとき、最初は低解像度・高フレームレートで粗合わせして、
その後高解像度に切り替えて分解能を上げたいことがしばしばあります。
そういった場合にはセンサビニング(ビニング) を活用します。
ビニング処理は解像度が下がるもののROIのように視野を減らすことなく、フレームレートを上げられます。
ビニングの意味と効果
ビニングは、センサ上で隣接画素(例:2×2や3×3)をアナログ的/センサ内部でまとめる処理です。
代表的なモードは以下:
- Average:N画素の平均を1画素に。解像度は $1/h$ × $1/v$ へ縮小
- Sum:N画素の和を1画素に(センサのフルウェルやダイナミックレンジに影響し得る)
帯域・速度:画素数が $1/(h·v)$ になるため、GigE等の転送帯域と処理負荷が減る → FPSや安定性が改善しやすい。
S/N:無相関ノイズを仮定すると、Average の標準偏差は 1/√N に低減(後述の数式)。
似た概念:デシメーション
読み出しの間引き(Decimation)は画素を捨てる処理。S/Nは基本的に変わらない(平均していないため)。
「画素数だけ減らして解像度と帯域だけ下げたい」ならデシメーション、「S/Nも改善したい」ならビニングが有利。
ゴール
- ビニングのまとめ量を設定するメソッドを追加
- FPS/帯域/ノイズの変化を単体テストで比較
-
(諸事情で次回持越し)Binning 1×1 / 2×2をワンクリック切替するUI
✅ 使用環境
| 項目 | 内容 |
|---|---|
| カメラ | Basler acA2500-14gm |
| SDK | pylon Camera Software Suite |
| 言語 | C# / .NET 8 / WPF |
コード
これまでの内容に追加していきます。実装自体は単純ですが、ビニング非対応の機種もあるため、ケアしておきます。
Baslerカメラ × pylon SDK 制御シリーズまとめ
/// <summary>
/// ビニングがサポートされているかどうかを確認します。
/// </summary>
/// <returns></returns>
public bool IsBinningSupported()
{
if (Camera == null || !IsConnected) return false;
try
{
var parameters = Camera!.Parameters;
return parameters.Contains(PLCamera.BinningHorizontal)
&& parameters.Contains(PLCamera.BinningVertical)
&& parameters[PLCamera.BinningHorizontal].IsWritable
&& parameters[PLCamera.BinningVertical].IsWritable;
}
catch { return false; }
}
/// <summary>
/// 現在のビニング設定を取得します。
/// </summary>
/// <returns>ビニング設定のタプル</returns>
public (int H, int V) GetBinning()
{
if (!IsBinningSupported()) return (1, 1);
long h = GetPLCameraParameter(PLCamera.BinningHorizontal);
long v = GetPLCameraParameter(PLCamera.BinningVertical);
return ((int)h, (int)v);
}
/// <summary>
/// ビニング設定を適用します。
/// </summary>
/// <param name="h"></param>
/// <param name="v"></param>
/// <returns></returns>
public bool SetBinning(int h, int v)
{
if (!IsConnected || !IsBinningSupported()) return false;
bool wasGrabbing = IsGrabbing;
if (wasGrabbing) StopGrabbing(); // 取得停止中に設定するのが安全
SetPLCameraParameter(PLCamera.BinningHorizontal, h);
SetPLCameraParameter(PLCamera.BinningVertical, v);
// ROIをセンサー全体にリセット
ResetRoiToFullSafe();
// 取得中の場合は再開
if (wasGrabbing) StartGrabbing();
return true;
}
private bool ResetRoiToFullSafe()
{
var p = Camera!.Parameters;
return SetROI(0, 0, (int)GetPLCameraParameter(PLCamera.WidthMax), (int)GetPLCameraParameter(PLCamera.HeightMax));
}
参考:
DecimationHorizontal/Vertical(読み出し間引き)という別ノードがある機種もあります。本稿ではセンサビニングを扱います。
ビニングとノイズ(S/Nの基礎)
実行例を紹介する前に、AverageビニングとSumビニングにおける出力とノイズの関係を示しておきます。
- センサ出力を画素 $x_i = s_i + n_i$ とし、$n_i$ は平均0・分散 $\sigma^2$ の無相関ノイズと仮定します
- ビニングにより $N=h\times v$ 画素を束ねるとき:
(A) Average ビニング
$$
y_{\text{avg}} = \frac{1}{N}\sum_{i=1}^{N} x_i
= \frac{1}{N}\sum s_i + \frac{1}{N}\sum n_i
$$
ノイズ分散:
$$
\sigma_{\text{avg}}^2 =\mathrm{Var}\left(\frac{1}{N}\sum n_i\right)
= \frac{1}{N^2}\sum \mathrm{Var}(n_i)
= \frac{N\sigma^2}{N^2} = \frac{\sigma^2}{N}
$$
標準偏差:
$$
\sigma_{\text{avg}} = \frac{\sigma}{\sqrt{N}}
$$
→ S/Nは $\sqrt{N}$ 倍 改善(信号が局所的に一定のとき)。
(B) Sum ビニング
$$
y_{\text{sum}} = \sum_{i=1}^{N} x_i = \sum s_i + \sum n_i
$$
ノイズ分散:
$$
\sigma_{\text{sum}}^2 =\mathrm{Var}\left(\sum n_i\right)
= \sum \mathrm{Var}(n_i)
= N\sigma^2
$$
標準偏差:
$$
\sigma_{\text{sum}} = \sqrt{N}\sigma
$$
以上より、Sumビニングでノイズは$\sqrt{N}$倍になりますが、信号がN画素分となるためS/Nは改善 します。ただしフルウェル容量やA/Dレンジに注意が必要です。
実カメラでは**固定パターンノイズ(FPN)**や相関成分、アナログ前段のゲイン設定等が入るため、理想通りにならない場合があります。傾向として「2×2で約1/√4=1/2のノイズ」程度を目安に。
実行例
テストコードは以下の通りです。
ビニングを設定した後画像を取得し、平均輝度とフレームレートを調べます。
[TestMethod()]
public void SetBinningTest()
{
// 事前条件: カメラへ接続していること。
if (!_baslerCameraSample.IsConnected)
_baslerCameraSample.Connect();
// ビニング非対応のカメラではテストを不成立(不完了)とする
if (!_baslerCameraSample.IsBinningSupported())
{
Assert.Inconclusive("このカメラはビニングをサポートしていません。");
return;
}
// 元の設定を退避
var original = _baslerCameraSample.GetBinning();
Console.WriteLine($"Original Binning: H={original.H}, V={original.V}");
// まずは同じ値を再設定して成功すること(冪等性の確認)
var setSame = _baslerCameraSample.SetBinning(original.H, original.V);
Assert.IsTrue(setSame, "同一ビニング値の再設定に失敗しました。");
var same = _baslerCameraSample.GetBinning();
Assert.AreEqual(original.H, same.H);
Assert.AreEqual(original.V, same.V);
// 変更可能なビニング値をいくつか候補から探索し、変更できる値で検証
// 一般的な候補: 1x1, 2x2, 3x3, 4x4 (カメラによりサポート状況は異なる)
var candidates = new (int H, int V)[] { (1, 1), (2, 2), (3, 3), (4, 4) };
(int H, int V)? changedTo = null;
foreach (var c in candidates)
{
// 設定を試み、成功した最初の候補で検証を行う
if (_baslerCameraSample.SetBinning(c.H, c.V))
{
var now = _baslerCameraSample.GetBinning();
Console.WriteLine($"Changed Binning: H={now.H}, V={now.V}");
Assert.AreEqual(c.H, now.H, "設定した BinningHorizontal が反映されていません。");
Assert.AreEqual(c.V, now.V, "設定した BinningVertical が反映されていません。");
changedTo = c;
// 変更後の画像を取得して情報を表示
var binningSnap = _baslerCameraSample.Snap();
using var binningMat = binningSnap.ToMat();
Console.WriteLine($"Image Size with Binning {c.H}x{c.V}: Width={binningMat.Width}, Height={binningMat.Height}");
var avg = binningMat.Mean();
var framerate = _baslerCameraSample.GetResultingFrameRate();
// ビニングにより平均輝度とフレームレートが変化していることを確認
Console.WriteLine($"Image Brightness: Mean={avg[0]:F2}, ResultingFrameRate={framerate:F2}");
binningMat.ImWrite($"SetBinningTest_h{c.H}_v{c.V}.bmp");
// もとの値と同じなら他のパラメータも試す。
if (c.H == original.H && c.V == original.V)
continue;
else
break;
}
}
// いずれの候補にも変更できなかった場合は不成立(不完了)
if (changedTo is null)
{
Assert.Inconclusive("このカメラでは候補のビニング値(1/2/3/4)へ変更できませんでした。");
}
// テスト後は元のビニングに戻しておく
var restored = _baslerCameraSample.SetBinning(original.H, original.V);
Assert.IsTrue(restored, "元のビニング値への復帰に失敗しました。");
var back = _baslerCameraSample.GetBinning();
Assert.AreEqual(original.H, back.H);
Assert.AreEqual(original.V, back.V);
}
実行結果
以下のような出力を得られます。
| 1x1 | 2x2 |
|---|---|
![]() |
![]() |
Changed Binning: H=1, V=1
Image Size with Binning 1x1: Width=2592, Height=1944
Image Brightness: Mean=99.09, ResultingFrameRate=14.59
Changed Binning: H=2, V=2
Image Size with Binning 2x2: Width=1296, Height=972
Image Brightness: Mean=198.25, ResultingFrameRate=24.20
ビニングにより、画像サイズが小さくなり、フレームレートが向上していることが分かります。
また、acA2500-14gmはSumビニングのため、輝度も向上しています。
Sumビニングしか選べないと、ビニングを設定するたびに輝度値を調整しないといけないため、注意が必要です。
脱線:思ったより暗い?(縦Average×横Sumビニングの組み合わせ)
結論はマニュアルを読もう ということだけなので、読み飛ばしても構いません。
自分が使っているカメラのビニングモードを知りたいとき、皆様はどうしていますか?
筆者はまず、カメラに接続し、Binning HorizontalのFeature Documentationを読むことにしました。するとacA2500-14gmでは以下のように記載がありました。
Binning Horizontal
Number of adjacent horizontal pixels to be summed.
Number of adjacent horizontal pixels to be summed. Their charges will be summed and reported out of the camera as a single pixel.
Binning Verticalについても同様の記載があり、Sumビニングであることが予想されます。
となると、先ほどのSumビニングの理論からすると、4画素まとめればセンサ出力(≒輝度値)は4倍近くになってもいいはずです。
しかし、2x2ビニング時の実際の平均輝度は2倍程度であり、何かおかしいです。
そこで、USER’S MANUAL FOR GigE CAMERASのp.272を見ると、acA2500-14gmはVertical Binning ModeがAveraging, Summing or a combinationでHorizontal Binning ModeはSumming(settable)となっていました。
さらにacA2500-14gmのイメージセンサMT9P031のデータシートを見たところ、行方向は平均のみ、列方向はAverageまたはSumを選択可能だが低照度ではSumを推奨と記載されているように見えます。
これらの情報を組み合わせると、縦はAverageビニング、横はSumビニングであるということが推察され、その場合2x2ビニング時に輝度値が約2倍になることが妥当であるとわかりました。
マニュアル、大事ですね。
よくある事例
画がぼけた/エッジが甘い
Averageビニングは情報の平均化で高周波が落ちます。シャープ感が欲しいならデシメーションも比較検討。後段でアンシャープ等の軽処理を入れる選択も。
画面が真っ黒/色がおかしい
カラー画像の場合は、ビニングにより色が壊れる場合もあります。その場合はPixelFormatやBayer変換の順序を見直してください。ビニングはセンサ側で行われるため、後段のカラー処理との組み合わせで見え方が変わることもあります。
ROIが設定できなくなった
ビニング変更でWidth/Height/OffsetのInc/範囲が変わるため、先にビニング → その後ROIの順で適用。
まとめ & 次回予告
- ビニングの設定により、解像度を落としてフレームレート向上
- ビニングのモードにより、処理が異なる。
- 次回はGUIを用いたビニング設定の切り替えを実装します。(GUIまでやる予定でしたが、脱線内容の調査に時間がかかり持ち越しです)
👨💻 筆者について
@MilleVision
産業用カメラ・画像処理システムの開発に関する情報を発信中。
pylon SDK × C# の活用シリーズを連載しています。
🛠 サンプルコード完全版のご案内
Qiita記事のサンプルをまとめた C#プロジェクト(単体テスト付き) を BOOTH で配布しています。
- 撮影・露光・ゲイン・フレームレート・ROI・イベント駆動撮影など主要機能を網羅
- WPFへの実装もフォロー
- 単体テスト同梱で動作確認や学習がスムーズ
- 記事更新に合わせてアップデート予定
先日、初めてお買い求めいただけました。ありがとうございます。

