Cv2.LUT の使い方【OpenCV/C#(OpenCvSharp)】〜ルックアップテーブルで階調変換〜

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

Cv2.LUT の使い方【OpenCV/C#(OpenCvSharp)】〜ルックアップテーブルで階調変換〜

Cv2.LUT は、ピクセル値を別の値にマッピングする「ルックアップテーブル(LUT)」を画像全体に高速適用する関数です。ガンマ補正・階調反転・コントラスト強調など、数式で表せる任意の階調変換を1行で処理できます。

// 256要素のLUTを作り、画像に適用する基本パターン
using var lut = new Mat(1, 256, MatType.CV_8UC1);
// ... lut に変換テーブルを書き込む ...
Cv2.LUT(src, lut, dst);

動作環境

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

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

基本の使い方

ガンマ補正(γ = 2.2)を LUT で実装する例です。

using OpenCvSharp;

// 入力画像を読み込む(グレースケール)
using var src = Cv2.ImRead("input.png", ImreadModes.Grayscale);
if (src.Empty()) throw new Exception("画像を読み込めませんでした");

// ガンマ補正用LUTを構築(γ=2.2: 暗めに補正)
using var lut = new Mat(1, 256, MatType.CV_8UC1);
var lutIndexer = lut.GetGenericIndexer<byte>();
for (int i = 0; i < 256; i++)
{
    // 入力値を0〜1に正規化し、ガンマ乗算後に0〜255へ戻す
    double normalized = i / 255.0;
    lutIndexer[0, i] = (byte)(Math.Pow(normalized, 2.2) * 255.0);
}

// LUTを適用
using var dst = new Mat();
Cv2.LUT(src, lut, dst);

Cv2.ImWrite("output_gamma.png", dst);
Console.WriteLine("ガンマ補正画像を output_gamma.png に保存しました");

実行すると、output_gamma.png にガンマ補正済みのグレースケール画像が保存されます。

コードの解説

行の処理 説明
new Mat(1, 256, MatType.CV_8UC1) 256要素・1行のLUTテーブルを生成
GetGenericIndexer<byte>() 要素を型安全に読み書きするインデクサを取得
Math.Pow(normalized, 2.2) ガンマ値2.2での階調変換式
Cv2.LUT(src, lut, dst) src の各ピクセル値を lut のインデックスとして変換値を引き、dst へ書き出す

引数と戻り値

Cv2.LUT(Mat src, Mat lut, Mat dst)
引数 型 説明
src Mat 入力画像。CV_8U 型のみ対応
lut Mat 変換テーブル。1×256 の Mat。src がカラーの場合は 1×256×3チャンネル または1チャンネル(全チャンネルに同じ変換が適用される)
dst Mat 出力画像。src と同サイズ・同チャンネル数

戻り値はなし(void)。dst に結果が書き込まれます。

⚠️ src は必ず CV_8U(8bit符号なし整数)でなければなりません。CV_16U や CV_32F には使えません。

実践例

階調反転(ネガ変換)

using OpenCvSharp;

using var src = Cv2.ImRead("input.png", ImreadModes.Grayscale);
if (src.Empty()) throw new Exception("画像を読み込めませんでした");

// i → 255-i のLUTでネガ反転
using var lut = new Mat(1, 256, MatType.CV_8UC1);
var indexer = lut.GetGenericIndexer<byte>();
for (int i = 0; i < 256; i++)
    indexer[0, i] = (byte)(255 - i);

using var dst = new Mat();
Cv2.LUT(src, lut, dst);

Cv2.ImWrite("output_negative.png", dst);
Console.WriteLine("ネガ反転画像を output_negative.png に保存しました");

カラー画像へのガンマ補正(チャンネル共通LUT)

src がカラー(CV_8UC3)の場合でも、LUT を CV_8UC1 の 1×256 にしておくと全チャンネルに同一変換が適用されます。

using OpenCvSharp;

using var src = Cv2.ImRead("input.png", ImreadModes.Color);
if (src.Empty()) throw new Exception("画像を読み込めませんでした");

// γ=0.5: 明るめに補正するLUT
using var lut = new Mat(1, 256, MatType.CV_8UC1);
var indexer = lut.GetGenericIndexer<byte>();
for (int i = 0; i < 256; i++)
    indexer[0, i] = (byte)(Math.Pow(i / 255.0, 0.5) * 255.0);

// カラー画像(CV_8UC3)にそのまま適用可能
using var dst = new Mat();
Cv2.LUT(src, lut, dst);

Cv2.ImWrite("output_bright.png", dst);
Console.WriteLine("明るめ補正画像を output_bright.png に保存しました");

実行すると、BGR 3チャンネルそれぞれに同じガンマ変換が適用された画像が保存されます。

コントラスト強調(S字カーブ)

中間調のコントラストを強調する定番の S字(シグモイド風)LUT です。

using OpenCvSharp;

using var src = Cv2.ImRead("input.png", ImreadModes.Grayscale);
if (src.Empty()) throw new Exception("画像を読み込めませんでした");

using var lut = new Mat(1, 256, MatType.CV_8UC1);
var indexer = lut.GetGenericIndexer<byte>();
for (int i = 0; i < 256; i++)
{
    // 0〜1の範囲でS字カーブ(中間を急峻に、端を緩やかに)
    double x = i / 255.0;
    double s = x < 0.5
        ? 2 * x * x
        : 1 - 2 * (1 - x) * (1 - x);
    indexer[0, i] = (byte)(s * 255.0);
}

using var dst = new Mat();
Cv2.LUT(src, lut, dst);

Cv2.ImWrite("output_scurve.png", dst);
Console.WriteLine("S字カーブ適用画像を output_scurve.png に保存しました");

つまずきポイント

✅ src は CV_8U 専用・型が違うと即エラー

Cv2.LUT に渡せる src は MatType.CV_8U(8bit符号なし整数)系のみです。CV_32F や CV_16U の Mat を渡すと実行時例外が発生します。浮動小数点画像を変換したい場合は、先に ConvertTo で CV_8U に変換してから LUT を適用してください。

// NG: CV_32F の Mat をそのまま渡すと例外
Cv2.LUT(floatMat, lut, dst); // OpenCVException

// OK: 先に CV_8U へ変換する
using var u8 = new Mat();
floatMat.ConvertTo(u8, MatType.CV_8U, 255.0);
Cv2.LUT(u8, lut, dst);

✅ lut の Mat は Dispose を忘れずに

LUT テーブル用に生成した Mat もネイティブメモリを確保します。ループ内で毎回 new Mat() して Dispose を省略すると、メモリリークが積み重なります。必ず using で管理してください。

// NG: using なしでループ処理
var lut = new Mat(1, 256, MatType.CV_8UC1); // Dispose されない

// OK
using var lut = new Mat(1, 256, MatType.CV_8UC1);

✅ カラー画像にチャンネル別LUTを適用したい場合

チャンネルごとに異なる変換をかけたいときは、LUT を CV_8UC3 の 1×256 Mat として各チャンネルの値を個別に設定します。GetGenericIndexer<Vec3b>() を使って R/G/B それぞれのテーブル値を書き込んでください。チャンネルを分離して個別に LUT 適用する方法(Cv2.Split + 各チャンネルに LUT + Cv2.Merge)も確実です。

関連する関数

まとめ

Cv2.LUT は、256要素のテーブルを1度定義するだけで画像全体の階調変換を高速に実行できる関数です。ガンマ補正・ネガ反転・S字カーブなど、任意の変換を数式からテーブルに焼き込んでおけばピクセルごとの計算コストを最小化できます。src が CV_8U 専用である点と、LUT の Mat も using で確実に解放する点を押さえれば、実務で安心して使えます。

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

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

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

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