cv::normalize の使い方【OpenCV/C++】〜画像・行列を正規化する〜

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

cv::normalize の使い方【OpenCV/C++】〜画像・行列を正規化する〜

cv::normalize は行列や画像の値を指定した範囲やノルムにスケーリングする関数です。「差分画像を 0〜255 に収めたい」「特徴ベクトルをL2正規化したい」といった場面で頻繁に登場します。

最小構成はこれだけです。

cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8U);

動作環境

  • OpenCV 4.x
  • コンパイル: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)

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


基本の使い方

まずは浮動小数点の行列を 0〜255 の CV_8U に変換する最もシンプルな例です。

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    // 任意の浮動小数点行列(例: 勾配・スコアマップなど)
    cv::Mat src = (cv::Mat_<float>(3, 3) <<
        -50.0f,  10.0f, 200.0f,
         80.0f, -20.0f, 150.0f,
        300.0f,  55.0f,   0.0f);

    cv::Mat dst;
    // NORM_MINMAX: 最小値→0、最大値→255 に線形スケーリング
    cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8U);

    std::cout << "src:\n" << src << "\n\n";
    std::cout << "dst:\n" << dst << "\n";

    return 0;
}

実行すると以下のように表示されます。

src:
[-50, 10, 200;
  80, -20, 150;
 300, 55, 0]

dst:
[  0,  17, 71;
  37,   9, 61;
 100,  30,  14]

src の最小値 -50 → 0、最大値 300 → 255 となり、その間は線形補間されます。


引数と戻り値

引数 型 説明
src InputArray 入力行列
dst OutputArray 出力行列
alpha double NORM_MINMAX では出力の最小値、それ以外ではノルム値
beta double NORM_MINMAX では出力の最大値(それ以外は未使用)
norm_type int 正規化の種類(下表参照)
dtype int 出力の型(-1 で入力と同じ型)
mask InputArray 処理対象ピクセルのマスク(省略可)

norm_type の主な選択肢

定数 動作
cv::NORM_MINMAX 最小・最大値を alpha/beta にスケーリング
cv::NORM_L2 L2ノルムが alpha になるようにスケーリング
cv::NORM_L1 L1ノルムが alpha になるようにスケーリング
cv::NORM_INF 最大絶対値が alpha になるようにスケーリング

戻り値はありません(dst に結果が書き込まれます)。


実践例

実践例1: 差分画像を見やすく可視化する

cv::absdiff で得た差分は値が小さく、そのまま表示しても真っ暗になりがちです。cv::normalize で引き伸ばすと変化箇所が明確になります。

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    cv::Mat img1 = cv::imread("frame1.png", cv::IMREAD_GRAYSCALE);
    cv::Mat img2 = cv::imread("frame2.png", cv::IMREAD_GRAYSCALE);

    if (img1.empty() || img2.empty()) {
        std::cerr << "画像の読み込みに失敗しました\n";
        return 1;
    }

    cv::Mat diff;
    cv::absdiff(img1, img2, diff); // 差分を取る

    cv::Mat vis;
    // 差分値の最小〜最大を 0〜255 に引き伸ばして可視化
    cv::normalize(diff, vis, 0, 255, cv::NORM_MINMAX, CV_8U);

    cv::imwrite("diff_vis.png", vis);
    std::cout << "diff_vis.png を保存しました\n";

    return 0;
}

cv::absdiff の詳細はcv::absdiff の使い方【OpenCV/C++】〜差分画像を作る〜も参照ください。


実践例2: 特徴ベクトルをL2正規化する

マッチングや距離計算の前処理としてベクトルをL2正規化する場面もよくあります。

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    // 1次元の特徴ベクトル(行ベクトル)
    cv::Mat feat = (cv::Mat_<float>(1, 5) << 3.0f, 4.0f, 0.0f, -1.0f, 2.0f);

    cv::Mat norm_feat;
    // L2ノルムが 1.0 になるように正規化
    cv::normalize(feat, norm_feat, 1.0, 0.0, cv::NORM_L2);

    std::cout << "元ベクトル  : " << feat << "\n";
    std::cout << "L2正規化後  : " << norm_feat << "\n";
    std::cout << "L2ノルム確認: " << cv::norm(norm_feat, cv::NORM_L2) << "\n";

    return 0;
}

実行すると以下のように表示されます。

元ベクトル  : [3, 4, 0, -1, 2]
L2正規化後  : [0.5547, 0.7303, 0, -0.1826, 0.3651]
L2ノルム確認: 1

実践例3: マスクを使って特定領域だけ正規化する

マスクを渡すと、マスクが 255 のピクセルだけを対象に最小・最大を計算してスケーリングします。マスク外のピクセルは dst に書き込まれません(dst をゼロ初期化しておくと安全です)。

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    cv::Mat src = cv::imread("input.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました\n";
        return 1;
    }

    // 左半分だけを対象にするマスク
    cv::Mat mask = cv::Mat::zeros(src.size(), CV_8U);
    mask(cv::Rect(0, 0, src.cols / 2, src.rows)) = 255;

    // dst をゼロ初期化してからマスク付き正規化
    cv::Mat dst = cv::Mat::zeros(src.size(), CV_8U);
    cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8U, mask);

    cv::imwrite("masked_norm.png", dst);
    std::cout << "masked_norm.png を保存しました\n";

    return 0;
}

つまずきポイント

⚠️ dtype=-1 のまま整数型に使うと範囲が 0〜1 にならない

NORM_MINMAX で dtype=-1(入力と同じ型)かつ入力が CV_8U の場合、alpha=0/beta=1.0 を指定しても出力は全ピクセルが 0 か 1 になります。CV_8U を 0〜255 に引き伸ばすには beta=255 を指定するか、dtype=CV_8U を明示してください。

// NG: beta=1.0 だと CV_8U では 0 か 1 にしかならない
cv::normalize(src, dst, 0, 1.0, cv::NORM_MINMAX);

// OK
cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8U);

⚠️ マルチチャンネル画像は全チャンネルまとめてスケーリングされる

CV_8UC3 のカラー画像を NORM_MINMAX で正規化すると、3チャンネルの全ピクセルを通じた最小・最大でスケーリングされます。チャンネルごとに独立して正規化したい場合は cv::split で分離してから処理してください。

std::vector<cv::Mat> ch;
cv::split(src, ch);
for (auto& c : ch)
    cv::normalize(c, c, 0, 255, cv::NORM_MINMAX, CV_8U);
cv::merge(ch, dst);

⚠️ L2正規化で入力がゼロベクトルの場合は NaN になることがある

全要素が 0 のベクトルをL2正規化すると除算がゼロ除算になり、結果が NaN や inf になる可能性があります。実務では正規化前にノルムを確認するか、cv::norm で事前チェックしてください。

if (cv::norm(feat, cv::NORM_L2) > 1e-9)
    cv::normalize(feat, feat, 1.0, 0.0, cv::NORM_L2);

関連する関数


まとめ

  • cv::normalize は NORM_MINMAX で値域を 0〜255 に収めるのが最もよく使うパターン。
  • L2正規化や部分マスクにも対応しており、前処理の幅が広い。
  • dtype と beta の指定ミスが典型的なバグ源なので、出力型を明示する習慣をつけると良い。

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

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

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

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