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.EqualizeHist — ヒストグラムを平坦化してコントラストを改善する。
CalcHistで分布を確認してから適用する流れが定番 - Cv2.Normalize —
CalcHistの生カウント値を可視化・比較向けにスケーリングする - Cv2.CompareHist — 2つのヒストグラムの類似度を比較する(画像検索などに利用)
- Cv2.MatchTemplate の使い方【OpenCV/C#(OpenCvSharp)】〜テンプレートマッチングで部分画像を検索する〜 — ヒストグラム分析と組み合わせて対象領域を絞り込む用途でも活用できます
まとめ
Cv2.CalcHist は画像の輝度・色分布を数値で把握するための基本関数です。mask で ROI を限定したり、複数チャンネルを個別に集計したりと応用の幅が広いです。出力が float 型であること・ranges が半開区間であることを押さえておけば、現場での実装でほぼハマりません。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/APIを開発しています。SDK は C#/.NET 8 に正式対応しており、OpenCvSharp を使うプロジェクトからそのまま組み込めます。
→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

