cv::LUT の使い方【OpenCV/C++】〜ルックアップテーブルで階調変換〜
cv::LUT は、256エントリのルックアップテーブル(LUT)を使って画像の各画素値を一括変換する関数です。ガンマ補正・コントラスト強調・ポスタリゼーションなど、ピクセル単位の非線形変換を高速に適用できます。
最小の呼び出し形はこうなります。
cv::LUT(src, lut, dst);
動作環境
- OpenCV 4.x
- コンパイル例:
g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)
環境構築がまだの方は環境構築ガイドを先にどうぞ。
基本の使い方
まずガンマ補正を例に、cv::LUT の基本動作を確認します。
#include <opencv2/opencv.hpp>
#include <cmath>
#include <iostream>
int main()
{
// 入力画像の読み込み
cv::Mat src = cv::imread("input.jpg");
if (src.empty()) {
std::cerr << "画像を読み込めませんでした" << std::endl;
return 1;
}
// ガンマ値(1.0 より小さいと明るく、大きいと暗くなる)
const double gamma = 0.5;
// 256エントリの LUT を作成(CV_8UC1 の 1×256 行列)
cv::Mat lut(1, 256, CV_8UC1);
for (int i = 0; i < 256; ++i) {
// ガンマ補正: out = (i/255)^gamma * 255
double val = std::pow(i / 255.0, gamma) * 255.0;
lut.at<uchar>(0, i) = cv::saturate_cast<uchar>(val);
}
// LUT を適用して階調変換
cv::Mat dst;
cv::LUT(src, lut, dst);
cv::imwrite("output_gamma.jpg", dst);
std::cout << "出力完了: output_gamma.jpg" << std::endl;
return 0;
}
実行結果
出力完了: output_gamma.jpg
output_gamma.jpg を確認すると、ガンマ 0.5 の補正が適用され、元画像より全体的に明るく変換された画像が保存されます。
コードのポイント
| 行 | 説明 |
|---|---|
cv::Mat lut(1, 256, CV_8UC1) |
LUT は 1行256列・8ビット1チャンネル が必須形式 |
std::pow(i / 255.0, gamma) * 255.0 |
0〜1 に正規化して累乗し、255 に戻す |
cv::saturate_cast<uchar>(val) |
計算結果が 0〜255 に収まるよう丸め処理 |
cv::LUT(src, lut, dst) |
src の各画素値を lut のインデックスとして参照し dst に書き出す |
カラー画像(CV_8UC3)を渡した場合、全チャンネルに同じ LUT が適用されます。チャンネルごとに異なる変換をしたい場合は後述の実践例を参照してください。
引数と戻り値
void cv::LUT(InputArray src, InputArray lut, OutputArray dst)
| 引数 | 型 | 説明 |
|---|---|---|
src |
InputArray |
入力画像。CV_8U(8ビット符号なし)のみ対応 |
lut |
InputArray |
1行256列の変換テーブル。チャンネル数は 1 または src と同じ |
dst |
OutputArray |
出力画像。src と同サイズ、lut のチャンネル数に応じた型 |
戻り値: なし(void)
⚠️ src が CV_8U 以外(例: CV_16U)の場合、実行時エラー(assertion failure)が発生します。16ビット画像には別途 cv::normalize などで正規化してから使うか、手動でループ処理を実装してください。
実践例
実践例 1: コントラスト強調(S字トーンカーブ)
明部と暗部のコントラストを強調する S字カーブは、LUT で簡潔に表現できます。
#include <opencv2/opencv.hpp>
#include <cmath>
#include <iostream>
int main()
{
cv::Mat src = cv::imread("input.jpg");
if (src.empty()) {
std::cerr << "画像を読み込めませんでした" << std::endl;
return 1;
}
// シグモイド関数でS字カーブLUTを生成
cv::Mat lut(1, 256, CV_8UC1);
const double alpha = 10.0; // 急峻さ(大きいほどコントラストが強い)
for (int i = 0; i < 256; ++i) {
double x = (i - 128.0) / 128.0; // -1〜1 に正規化
double s = 1.0 / (1.0 + std::exp(-alpha * x)); // シグモイド
lut.at<uchar>(0, i) = cv::saturate_cast<uchar>(s * 255.0);
}
cv::Mat dst;
cv::LUT(src, lut, dst);
cv::imwrite("output_scurve.jpg", dst);
std::cout << "出力完了: output_scurve.jpg" << std::endl;
return 0;
}
実践例 2: チャンネルごとに異なる LUT を適用(ホワイトバランス補正)
LUT のチャンネル数を 3 にすると、B/G/R それぞれ異なる変換を一度の呼び出しで適用できます。
#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{
cv::Mat src = cv::imread("input.jpg");
if (src.empty()) {
std::cerr << "画像を読み込めませんでした" << std::endl;
return 1;
}
// 3チャンネルLUT(各チャンネルで独立した変換)
cv::Mat lut(1, 256, CV_8UC3);
for (int i = 0; i < 256; ++i) {
// B: そのまま, G: 1.1倍, R: 0.9倍(例: 温かみを下げる補正)
lut.at<cv::Vec3b>(0, i)[0] = cv::saturate_cast<uchar>(i * 1.0); // B
lut.at<cv::Vec3b>(0, i)[1] = cv::saturate_cast<uchar>(i * 1.1); // G
lut.at<cv::Vec3b>(0, i)[2] = cv::saturate_cast<uchar>(i * 0.9); // R
}
cv::Mat dst;
cv::LUT(src, lut, dst);
cv::imwrite("output_wb.jpg", dst);
std::cout << "出力完了: output_wb.jpg" << std::endl;
return 0;
}
3チャンネル LUT を使うことで、cv::split → 各チャンネル処理 → cv::merge の手間を省けます。処理速度も SIMD が効く cv::LUT 内部実装のほうが有利です。
つまずきポイント
⚠️ src が CV_8U でないと assertion failure
cv::LUT は CV_8U 入力専用 です。グレースケール変換後の画像が CV_8UC1 になっているか、カラー画像が CV_8UC3 になっているかを必ず確認してください。
// NG: 16ビット画像を直接渡すとクラッシュする
cv::Mat src16(480, 640, CV_16UC1);
cv::LUT(src16, lut, dst); // assertion failure!
⚠️ LUT のサイズは必ず 1×256
よくあるミスは cv::Mat lut(256, 1, CV_8UC1) のように行・列を逆にすること。cv::LUT は 1行×256列 を要求しており、256行×1列 では実行時エラーになります。
cv::Mat lut_ng(256, 1, CV_8UC1); // NG: 行列が逆
cv::Mat lut_ok(1, 256, CV_8UC1); // OK
⚠️ カラー画像に1チャンネルLUTを渡したときの挙動
1チャンネル LUT をカラー画像(CV_8UC3)に渡すと、全チャンネルに同じ変換が適用されます。これ自体は正しい動作ですが、「チャンネルごとに変えたつもりが全部同じになっている」というバグの原因になりがちです。チャンネル独立変換が必要な場合は CV_8UC3 の LUT を使ってください。
関連する関数
-
cv::normalize の使い方【OpenCV/C++】〜画像・行列を正規化する〜
LUT 適用前に画素値のレンジを揃えたいときに組み合わせて使います。 -
cv::split / cv::merge の使い方【OpenCV/C++】〜チャンネルを分解・結合する〜
チャンネルごとに LUT を分けて適用する古典的な方法。3チャンネル LUT が使えない状況ではこちら。 -
cv::minMaxLoc の使い方【OpenCV/C++】〜最大値・最小値と位置を求める〜
LUT 設計前に画素値の分布範囲を確認するのに便利です。 -
cv::equalizeHist… ヒストグラム平坦化。LUT を自動生成するラッパーとも言えます。内部的には LUT ベースの変換を行っています。
まとめ
cv::LUT は 256エントリのテーブルを用意するだけで、ガンマ補正・トーンカーブ・ホワイトバランスなどあらゆる画素値変換を高速に適用できます。入力は CV_8U 限定・LUT サイズは 1×256 という制約さえ押さえれば、ループで同等処理を書くより速く・安全です。チャンネル独立変換には3チャンネル LUT を活用してください。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。
→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード
