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::cvtColor や cv::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)を開発しています。顔認証システムへの組み込み実績多数。

