OpenCV/C++ Mat の基礎② 〜行列演算〜

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

OpenCV/C++ Mat の基礎② 〜行列演算〜

cv::Mat は画像データをそのまま行列として扱えるため、四則演算・型変換・コピーといった操作が直感的に書けます。本記事では実務でよく使う 演算・clone/copyTo・convertTo・reshape を、完全なサンプルコードで解説します。

最小の結論コードは次のとおりです。

cv::Mat a = cv::Mat::ones(3, 3, CV_32FC1) * 2.0f;
cv::Mat b = a.clone();          // ディープコピー
cv::Mat c;
a.convertTo(c, CV_8UC1, 127.0); // float → uchar にスケーリング変換

動作環境

項目 バージョン
OpenCV 4.x(4.5 以降推奨)
言語 C++17
g++ -std=c++17 main.cpp `pkg-config --cflags --libs opencv4` -o main

基本の使い方

まず cv::Mat 同士の四則演算・スカラー演算を確認します。

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

int main()
{
    // 3x3 の float 行列を生成
    cv::Mat a = (cv::Mat_<float>(3, 3) <<
        1, 2, 3,
        4, 5, 6,
        7, 8, 9);

    cv::Mat b = (cv::Mat_<float>(3, 3) <<
        9, 8, 7,
        6, 5, 4,
        3, 2, 1);

    cv::Mat add_result  = a + b;          // 要素ごとの加算
    cv::Mat sub_result  = a - b;          // 要素ごとの減算
    cv::Mat mul_result  = a.mul(b);       // 要素ごとの乗算(行列積ではない)
    cv::Mat matmul      = a * b;          // 行列積(線形代数の意味)
    cv::Mat scale       = a * 2.0f;       // スカラー倍

    std::cout << "a + b =\n" << add_result  << "\n\n";
    std::cout << "a - b =\n" << sub_result  << "\n\n";
    std::cout << "a.mul(b) =\n" << mul_result << "\n\n";
    std::cout << "a * b (行列積) =\n" << matmul << "\n\n";
    std::cout << "a * 2.0f =\n" << scale     << "\n";

    return 0;
}

実行すると、各演算結果が行列形式で標準出力に表示されます。

a + b =
[10, 10, 10;
 10, 10, 10;
 10, 10, 10]

a - b =
[-8, -6, -4;
 -2, 0, 2;
 4, 6, 8]

a.mul(b) =
[9, 16, 21;
 24, 25, 24;
 21, 16, 9]

a * b (行列積) =
[30, 24, 18;
 84, 69, 54;
 138, 114, 90]

a * 2.0f =
[2, 4, 6;
 8, 10, 12;
 14, 16, 18]

ポイント:

  • + - は要素ごとの演算
  • *行列積。要素ごとに掛けたいときは必ず mat.mul() を使う
  • スカラー値との演算は * 2.0f のように直接書ける

引数と戻り値

cv::Mat::mul

引数 説明
m InputArray 掛け合わせる行列(サイズ・型が一致する必要あり)
scale double 追加のスケーリング係数(省略時 1.0)
戻り値 cv::MatExpr 演算式オブジェクト(cv::Mat に代入して使う)

cv::Mat::clone

戻り値 説明
cv::Mat データを完全にコピーした独立した新しい Mat

cv::Mat::copyTo

引数 説明
m OutputArray コピー先の Mat
mask InputArray コピーするピクセルを指定するマスク(省略可)

cv::Mat::convertTo

引数 説明
m OutputArray 変換後の出力 Mat
rtype int 出力の型(CV_8UC1 など)。-1 で型変更なし
alpha double スケーリング係数(省略時 1.0)
beta double シフト量(省略時 0.0)。 出力 = alpha × 入力 + beta

cv::Mat::reshape

引数 説明
cn int 新しいチャンネル数。0 で変更なし
rows int 新しい行数。0 で変更なし
戻り値 cv::Mat データを共有する再配置済み Mat(コピーではない)

実践例

① clone / copyTo — ディープコピーと浅いコピーの違い

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

int main()
{
    cv::Mat src = (cv::Mat_<uchar>(2, 2) << 10, 20, 30, 40);

    // 浅いコピー(ヘッダのみ): dst_shallow は src のデータを共有
    cv::Mat dst_shallow = src;

    // ディープコピー: データが完全に独立する
    cv::Mat dst_clone = src.clone();

    // copyTo も独立したデータを作る
    cv::Mat dst_copy;
    src.copyTo(dst_copy);

    // 元データを書き換えて影響を確認
    src.at<uchar>(0, 0) = 255;

    std::cout << "src(0,0)         = " << (int)src.at<uchar>(0,0)         << "\n"; // 255
    std::cout << "dst_shallow(0,0) = " << (int)dst_shallow.at<uchar>(0,0) << "\n"; // 255(影響あり)
    std::cout << "dst_clone(0,0)   = " << (int)dst_clone.at<uchar>(0,0)   << "\n"; // 10(独立)
    std::cout << "dst_copy(0,0)    = " << (int)dst_copy.at<uchar>(0,0)    << "\n"; // 10(独立)

    return 0;
}

実行すると以下のように出力されます。

src(0,0)         = 255
dst_shallow(0,0) = 255
dst_clone(0,0)   = 10
dst_copy(0,0)    = 10

= による代入はヘッダのコピーのみで、データは共有されます。画像を関数に渡して加工する際に元データを壊さないよう clone()copyTo() を必ず使ってください。


② convertTo — 型変換とスケーリング

画像処理では float 演算後に CV_8U へ戻す操作が頻出します。

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

int main()
{
    // 0.0〜1.0 の float Mat(正規化済みの想定)
    cv::Mat src = (cv::Mat_<float>(2, 3) <<
        0.0f, 0.5f, 1.0f,
        0.25f, 0.75f, 0.9f);

    cv::Mat dst;
    // 0〜1 の float を 0〜255 の uchar に変換
    src.convertTo(dst, CV_8UC1, 255.0);

    std::cout << "src (float):\n" << src << "\n\n";
    std::cout << "dst (uchar, x255):\n" << dst << "\n";

    return 0;
}
src (float):
[0, 0.5, 1;
 0.25, 0.75, 0.9]

dst (uchar, x255):
[0, 127, 255;
 63, 191, 229]

alpha=255.0 を指定することで、0〜1 の float 値が 0〜255 の uchar にスケーリングされます。逆に alpha=1.0/255.0 とすれば uchar → float(0〜1 正規化)になります。


③ reshape — チャンネル数・行数の変更

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

int main()
{
    // 2行 6列 の uchar Mat
    cv::Mat src = (cv::Mat_<uchar>(2, 6) <<
        1, 2, 3, 4, 5, 6,
        7, 8, 9,10,11,12);

    // 3行 4列に再配置(データはコピーしない)
    cv::Mat dst = src.reshape(0, 3); // cn=0(変更なし), rows=3

    std::cout << "src: " << src.rows << "x" << src.cols << "\n";
    std::cout << src << "\n\n";
    std::cout << "dst (reshape to 3 rows): " << dst.rows << "x" << dst.cols << "\n";
    std::cout << dst << "\n";

    return 0;
}
src: 2x6
[1, 2, 3, 4, 5, 6;
 7, 8, 9, 10, 11, 12]

dst (reshape to 3 rows): 3x4
[1, 2, 3, 4;
 5, 6, 7, 8;
 9, 10, 11, 12]

reshape はデータをコピーせずレイアウトだけ変更します。dst への書き込みは src にも反映されることに注意してください。


つまずきポイント

⚠️ * は行列積、要素積ではない

cv::Mat c = a * b; // 行列積(dot product)

「画素ごとに掛けたい」のに * を使うと、線形代数の行列積になります。要素ごとの乗算には必ず a.mul(b) を使ってください。これは最も多い実装ミスの一つです。

⚠️ = 代入と clone() の取り違え

cv::Mat dst = src;        // NG: データ共有(浅いコピー)
cv::Mat dst = src.clone(); // OK: 独立したコピー

cv::Mat= はスマートポインタと同じ参照カウント方式です。関数の引数渡し・STLコンテナへの格納でも同様です。書き換えが必要な場面では clone()copyTo() を徹底してください。

⚠️ convertTo の型不一致による assert

src.convertTo(dst, CV_8UC3, 255.0);

入力が 1チャンネル float なのに出力型を CV_8UC3(3チャンネル)にするとアサーションエラーになります。convertTo はチャンネル数を変えられません。チャンネル数の変換には cv::cvtColorcv::merge を組み合わせてください。


関連する関数

  • cv::add / cv::subtract / cv::multiply — 飽和演算が必要な場合は演算子オーバーロードより安全
  • cv::cvtColor — チャンネル変換(BGR ↔ グレースケールなど)
  • cv::split / cv::merge — チャンネルの分離・結合
  • cv::Mat::row / cv::Mat::col — 特定行・列の参照取得(ヘッダ共有)
  • cv::Mat::operator() / cv::Rect — ROI(部分領域)の切り出し

まとめ

  • 要素積は mul()、行列積は *。混同しないように注意する
  • データを独立させたいときは clone() または copyTo() を使う
  • convertTo は型とスケーリングを同時に指定でき、float ↔ uchar の変換に重宝する

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

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

→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

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