Mat::convertTo の使い方【OpenCV/C++】〜型変換とスケーリング〜

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

Mat::convertTo の使い方【OpenCV/C++】〜型変換とスケーリング〜

Mat::convertTo は、Mat の要素型(CV_8U、CV_32F など)を変換しつつ、スケール係数とオフセットを同時に適用できるメソッドです。画像処理パイプラインでは「整数型 ↔ 浮動小数点型」の変換が頻繁に発生するため、ほぼ全プロジェクトで使うことになります。

最小の呼び出しはこれだけです。

src.convertTo(dst, CV_32F, 1.0 / 255.0);

動作環境

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

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


基本の使い方

完全プログラム

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

int main()
{
    // グレースケールで読み込み
    cv::Mat src = cv::imread("input.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // CV_8U → CV_32F に変換し、値を 0〜1 の範囲にスケーリング
    cv::Mat dst32f;
    src.convertTo(dst32f, CV_32F, 1.0 / 255.0);

    // 確認: 型と値の範囲を出力
    double minVal, maxVal;
    cv::minMaxLoc(dst32f, &minVal, &maxVal);
    std::cout << "変換後の型 (depth): " << dst32f.depth()     // 5 = CV_32F
              << "\n最小値: " << minVal
              << "\n最大値: " << maxVal
              << std::endl;

    // CV_32F → CV_8U に戻す(0〜1 の値を 0〜255 に戻す)
    cv::Mat dst8u;
    dst32f.convertTo(dst8u, CV_8U, 255.0);

    cv::imwrite("output.png", dst8u);
    return 0;
}

実行結果

変換後の型 (depth): 5
最小値: 0
最大値: 1

行ごとの解説

箇所 説明
src.convertTo(dst32f, CV_32F, 1.0 / 255.0) 型を CV_32F に変換しつつ、各画素値に 1/255 を掛ける
cv::minMaxLoc(...) 変換後の値域が正しいかを即確認するための実務的なデバッグ手順
dst32f.convertTo(dst8u, CV_8U, 255.0) CV_32F → CV_8U へ戻す。255 倍してスケールを元に戻す

引数と戻り値

void Mat::convertTo(
    OutputArray m,       // 出力先 Mat
    int         rtype,   // 出力の型(CV_8U, CV_16U, CV_32F, CV_64F など)
    double      alpha,   // スケール係数(省略時 = 1.0)
    double      beta     // オフセット(省略時 = 0.0)
) const;

各画素の変換式は次のとおりです。

dst(x, y) = saturate_cast<rtype>( src(x, y) * alpha + beta )
引数 型 説明
m OutputArray 出力先 Mat。サイズ・チャンネル数は入力と同じ
rtype int 出力の深度(型)。-1 を指定すると型変換なしでスケーリングのみ実行
alpha double 各画素値に掛けるスケール係数(省略可、デフォルト 1.0)
beta double スケール後に加算するオフセット(省略可、デフォルト 0.0)

戻り値はありません(void)。出力は第1引数 m に書き込まれます。

⚠️ saturate_cast により、型の範囲を超えた値は自動でクランプされます(例: -1 → 0、300 → 255)。


実践例

実践例 1: 差分画像を符号付きで扱う(CV_8U → CV_16S)

CV_8U どうしの差分は負の値が 0 にクランプされてしまいます。CV_16S(符号付き16bit)に変換してから差分を取ることで、負の差分も保持できます。

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

int main()
{
    cv::Mat a = cv::imread("frame1.png", cv::IMREAD_GRAYSCALE);
    cv::Mat b = cv::imread("frame2.png", cv::IMREAD_GRAYSCALE);
    if (a.empty() || b.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // CV_8U → CV_16S に変換(符号付きで差分を計算するため)
    cv::Mat a16s, b16s;
    a.convertTo(a16s, CV_16S);
    b.convertTo(b16s, CV_16S);

    // 符号付き差分
    cv::Mat diff = a16s - b16s;

    double minVal, maxVal;
    cv::minMaxLoc(diff, &minVal, &maxVal);
    std::cout << "差分の最小値: " << minVal
              << " / 最大値: " << maxVal << std::endl;

    // 表示用に CV_8U に正規化して戻す
    cv::Mat diffVis;
    diff.convertTo(diffVis, CV_8U, 1.0, 128.0); // オフセット 128 で中間色を中心に
    cv::imwrite("diff_vis.png", diffVis);

    return 0;
}

cv::absdiff は絶対値差分のみですが、この方法なら正負どちらの差分も扱えます。符号付き差分が必要な場合のイディオムとして覚えておくと便利です(cv::absdiff の使い方【OpenCV/C++】〜差分画像を作る〜も参照)。


実践例 2: DNN 推論の前処理(正規化 + チャンネル変換)

深層学習モデルへの入力でよく使われるパターンです。CV_8UC3 の画像を CV_32FC3 の [0, 1] 正規化 Mat に変換します。

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

int main()
{
    cv::Mat img = cv::imread("photo.jpg");
    if (img.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // BGR CV_8UC3 → CV_32FC3、値域を 0〜1 に正規化
    cv::Mat blob;
    img.convertTo(blob, CV_32F, 1.0 / 255.0);

    std::cout << "blob の型: "    << blob.type()     // 21 = CV_32FC3
              << "\nblob のサイズ: " << blob.size()
              << std::endl;

    // ここから cv::dnn::blobFromImage などで推論入力を作成する
    return 0;
}

cv::dnn::blobFromImages が内部で同様の処理をしていますが、カスタム前処理が必要な場合は自前で convertTo を呼ぶことになります。また、画像のブレンドや加算処理と組み合わせる場面も多いので、cv::add / addWeighted の使い方【OpenCV/C++】〜画像の加算・ブレンド合成〜も参考にしてください。


つまずきポイント

⚠️ rtype に CV_32F ではなく CV_32FC3 を指定してしまう

rtype に指定するのは 深度(depth)のみ です。チャンネル数は入力から自動で引き継がれます。

// ✅ 正しい: チャンネル数は自動
img.convertTo(dst, CV_32F, 1.0 / 255.0);

// ❌ 意図通りに動かないケースあり(OpenCV 4.x 時点では内部で depth のみ参照されるが混乱を招く)
img.convertTo(dst, CV_32FC3, 1.0 / 255.0);

CV_32FC3 のような type ではなく、CV_32F・CV_16S・CV_8U のような depth 定数を渡すのが正しい使い方です。


⚠️ CV_32F → CV_8U 変換で画像が真っ黒になる

CV_32F の値域が [0.0, 1.0] のまま CV_8U に変換すると、saturate_cast によって全画素が 0 または 1 にクランプされ、ほぼ真っ黒になります。

// ❌ 0〜1 の float 値をそのまま CV_8U に変換 → 全画素 0 か 1
dst32f.convertTo(dst8u, CV_8U);

// ✅ alpha=255 を指定して値域を 0〜255 に戻す
dst32f.convertTo(dst8u, CV_8U, 255.0);

変換前後の値域を常に意識することが重要です。


⚠️ alpha=-1 と rtype=-1 の混同

rtype に -1 を指定すると型変換なし(入力と同じ型)でスケーリングのみ実行されます。alpha に -1 を渡すと符号反転になります。引数の順序は (m, rtype, alpha, beta) なので、位置引数の取り違えに注意してください。

// rtype=-1: 型変換なし、alpha=2.0 でコントラスト 2 倍
src.convertTo(dst, -1, 2.0);

関連する関数


まとめ

  • Mat::convertTo は型変換とスケーリングを1行で行える基本メソッドです。
  • rtype には深度定数(CV_32F、CV_8U など)を渡し、チャンネル数は自動継承されます。
  • 変換前後の値域([0, 255] ↔ [0.0, 1.0] など)を常に意識して alpha を設定することが、バグを防ぐ最重要ポイントです。

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

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

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

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