Cv2.CalcHist の使い方【OpenCV/C#(OpenCvSharp)】〜ヒストグラムを計算する〜

OpenCV for C#
📌 準備: OpenCV の環境構築がまだの方はこちら → C++ 環境構築ガイド / C#(OpenCvSharp)セットアップ

Cv2.CalcHist の使い方【OpenCV/C#(OpenCvSharp)】〜ヒストグラムを計算する〜

Cv2.CalcHist は画像のピクセル輝度値の分布(ヒストグラム)を計算する関数です。露出の確認・色分析・ヒストグラム平坦化の前処理など、画像処理の基礎的な工程で必ず登場します。最小構成は以下のとおりです。

Cv2.CalcHist(new[] { src }, new[] { 0 }, null, hist, 1, new[] { 256 }, new[] { new Rangef(0, 256) });

動作環境

  • OpenCvSharp4(OpenCV 4.x 系)
  • NuGet: OpenCvSharp4 + OpenCvSharp4.runtime.win(Windows 実行時)
  • .NET 8 コンソールアプリ(トップレベルステートメント)

環境構築がまだの方は環境構築ガイドを先にどうぞ。


基本の使い方

グレースケール画像のヒストグラムを計算し、棒グラフ風に可視化して保存するサンプルです。

using OpenCvSharp;

// --- 入力画像の読み込み(グレースケール)---
using var src = Cv2.ImRead("input.jpg", ImreadModes.Grayscale);
if (src.Empty()) throw new Exception("画像の読み込みに失敗しました");

// --- ヒストグラム計算 ---
using var hist = new Mat();
// チャンネル 0, マスクなし, 1次元, ビン数 256, 輝度レンジ 0〜255
Cv2.CalcHist(
    images: new[] { src },
    channels: new[] { 0 },
    mask: null,
    hist: hist,
    dims: 1,
    histSize: new[] { 256 },
    ranges: new[] { new Rangef(0, 256) }
);

// --- 正規化(可視化用に高さ 200px に収める)---
using var histNorm = new Mat();
Cv2.Normalize(hist, histNorm, 0, 200, NormTypes.MinMax);

// --- ヒストグラムを折れ線で描画 ---
int width = 512;
int height = 240;
using var canvas = new Mat(height, width, MatType.CV_8UC3, Scalar.All(255));

float xScale = (float)width / 256;
for (int i = 1; i < 256; i++)
{
    // 1ビン前との間に線を引く
    Cv2.Line(
        canvas,
        new Point((int)((i - 1) * xScale), height - (int)histNorm.At<float>(i - 1)),
        new Point((int)(i * xScale),       height - (int)histNorm.At<float>(i)),
        Scalar.Black, 1
    );
}

Cv2.ImWrite("histogram.png", canvas);
Console.WriteLine("histogram.png を保存しました");

実行すると histogram.png に輝度分布の折れ線グラフが出力されます。

histogram.png を保存しました

行ごとの解説

処理 ポイント
ImRead(..., Grayscale) グレースケールで読み込むことで channels: new[] { 0 } のみで済む
CalcHist の images Mat[] で渡す。複数枚を一括処理することも可能
dims: 1 1次元ヒストグラム。2次元(2チャンネル同時)も指定可
histSize: new[] { 256 } ビン数。粗くしたい場合は 64 や 128 でもよい
ranges: new[] { new Rangef(0, 256) } ピクセル値の範囲。256 は含まれない(半開区間)
Cv2.Normalize CalcHist の出力は生のカウント値なので、描画前に正規化する

引数と戻り値

Cv2.CalcHist(
    Mat[]    images,    // 入力画像の配列
    int[]    channels,  // ヒストグラムを計算するチャンネルインデックスの配列
    Mat?     mask,      // 計算対象マスク(null = 全領域)
    Mat      hist,      // 出力ヒストグラム(Mat)
    int      dims,      // ヒストグラムの次元数
    int[]    histSize,  // 各次元のビン数
    Rangef[] ranges     // 各次元のピクセル値範囲
);
引数 型 説明
images Mat[] 入力画像配列。同じサイズ・ビット深度が必要
channels int[] 使用するチャンネルのインデックス(BGR なら 0=B, 1=G, 2=R)
mask Mat? 計算領域を限定するマスク。null で全画素
hist Mat 出力先 Mat(float 型の多次元配列として格納)
dims int ヒストグラムの次元数(1〜3)
histSize int[] 各次元のビン数。dims と要素数を合わせる
ranges Rangef[] 各次元の値範囲(半開区間 [start, end))

戻り値はなく、結果は hist に書き込まれます。


実践例

例1: カラー画像の BGRチャンネル別ヒストグラムを保存する

カラー画像の B/G/R それぞれのヒストグラムを計算し、一枚のキャンバスに色分けして描画します。

using OpenCvSharp;

using var src = Cv2.ImRead("input.jpg", ImreadModes.Color);
if (src.Empty()) throw new Exception("画像の読み込みに失敗しました");

int width = 512, height = 300;
using var canvas = new Mat(height, width, MatType.CV_8UC3, Scalar.All(255));

// B=0, G=1, R=2 の順にヒストグラムを計算・描画
(int ch, Scalar color)[] channels = {
    (0, Scalar.Blue),
    (1, Scalar.Green),
    (2, Scalar.Red)
};

float xScale = (float)width / 256;

foreach (var (ch, color) in channels)
{
    using var hist = new Mat();
    Cv2.CalcHist(
        new[] { src },
        new[] { ch },
        null,
        hist,
        1,
        new[] { 256 },
        new[] { new Rangef(0, 256) }
    );

    using var histNorm = new Mat();
    Cv2.Normalize(hist, histNorm, 0, height - 20, NormTypes.MinMax);

    for (int i = 1; i < 256; i++)
    {
        Cv2.Line(
            canvas,
            new Point((int)((i - 1) * xScale), height - (int)histNorm.At<float>(i - 1)),
            new Point((int)(i * xScale),       height - (int)histNorm.At<float>(i)),
            color, 2
        );
    }
}

Cv2.ImWrite("histogram_bgr.png", canvas);
Console.WriteLine("histogram_bgr.png を保存しました");

例2: マスクを使って特定領域だけヒストグラムを計算する

製品検査などで ROI(関心領域)だけを分析したい場面に使います。

using OpenCvSharp;

using var src = Cv2.ImRead("input.jpg", ImreadModes.Grayscale);
if (src.Empty()) throw new Exception("画像の読み込みに失敗しました");

// 画像中央に 200x200 の矩形マスクを作成
using var mask = new Mat(src.Size(), MatType.CV_8UC1, Scalar.Black);
mask[new Rect(src.Width / 2 - 100, src.Height / 2 - 100, 200, 200)].SetTo(Scalar.White);

using var hist = new Mat();
Cv2.CalcHist(
    new[] { src },
    new[] { 0 },
    mask,   // マスクを渡すことで矩形内だけ集計
    hist,
    1,
    new[] { 256 },
    new[] { new Rangef(0, 256) }
);

// ビン 128 付近(中間輝度)のカウントを確認
Console.WriteLine($"輝度128のカウント: {hist.At<float>(128)}");
輝度128のカウント: 312

つまずきポイント

⚠️ hist の型は CV_32F(float)である

CalcHist の出力 Mat は float 型で格納されます。int や byte でアクセスすると値が取れません。値を読み出す際は必ず hist.At<float>(i) を使ってください。

// ❌ 間違い
int count = hist.At<int>(i);

// ✅ 正しい
float count = hist.At<float>(i);

⚠️ ranges の上限は「含まれない」半開区間

new Rangef(0, 256) とした場合、輝度値 256 は含まれません(0〜255 が対象)。8bit 画像では Rangef(0, 256) が正しく、Rangef(0, 255) とすると 255 の画素がカウントされません。

⚠️ Mat の Dispose 漏れに注意

ループ内で CalcHist を呼ぶ際、hist を using で毎回解放しないとネイティブメモリがリークします。上記の例2のように、foreach ブロック内で using var hist = new Mat() と宣言する書き方が安全です。


関連する関数


まとめ

Cv2.CalcHist は画像の輝度・色分布を数値で把握するための基本関数です。mask で ROI を限定したり、複数チャンネルを個別に集計したりと応用の幅が広いです。出力が float 型であること・ranges が半開区間であることを押さえておけば、現場での実装でほぼハマりません。

🛠 画像処理のプロが開発するSDK/API

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/APIを開発しています。SDK は C#/.NET 8 に正式対応しており、OpenCvSharp を使うプロジェクトからそのまま組み込めます。

→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

タイトルとURLをコピーしました