C/C++ OpenCV 矩陣運算詳解 💡
OpenCV 是一個強大的開源計算機視覺和機器學習庫,它提供了豐富的矩陣運算功能,這對于圖像處理和計算機視覺算法至關重要。本文將詳細介紹如何使用 C/C++ 和 OpenCV 進行常見的矩陣運算。
矩陣的創建與初始化
在進行矩陣運算之前,我們首先需要知道如何創建和初始化矩陣。OpenCV 提供了 cv::Mat
類來處理矩陣。
創建矩陣
有多種方法可以創建 cv::Mat
對象:
-
使用構造函數:可以指定行數、列數、數據類型以及可選的初始值。
#include <opencv2/opencv.hpp> #include <iostream>int main() {// 創建一個 3x3 的浮點型矩陣,所有元素初始化為 0cv::Mat matrix1 = cv::Mat::zeros(3, 3, CV_32F);std::cout << "Matrix1:\n" << matrix1 << std::endl;// 創建一個 2x4 的整型矩陣,所有元素初始化為 1cv::Mat matrix2 = cv::Mat::ones(2, 4, CV_8UC1); // 8位無符號單通道std::cout << "Matrix2:\n" << matrix2 << std::endl;// 創建一個具有特定尺寸和類型的矩陣,不初始化cv::Mat matrix3(4, 2, CV_64FC3); // 64位浮點型三通道// 使用 Scalar 初始化cv::Mat matrix4 = cv::Mat(3, 3, CV_32F, cv::Scalar(5.0));std::cout << "Matrix4:\n" << matrix4 << std::endl;// 通過 C/C++ 數組創建double data[] = {1, 2, 3, 4, 5, 6};cv::Mat matrix5 = cv::Mat(2, 3, CV_64F, data);std::cout << "Matrix5:\n" << matrix5 << std::endl;return 0; }
-
cv::Mat::create()
方法:如果矩陣已經存在,此方法會重新分配內存(如果需要)。cv::Mat matrix; matrix.create(4, 4, CV_32F);
-
cv::Mat::eye()
方法:創建單位矩陣。cv::Mat identityMatrix = cv::Mat::eye(3, 3, CV_64F); std::cout << "Identity Matrix:\n" << identityMatrix << std::endl;
訪問矩陣元素
可以使用 at()
方法或者直接通過指針訪問矩陣元素。at()
方法更安全,因為它會進行邊界檢查。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrix = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);std::cout << "Original Matrix:\n" << matrix << std::endl;// 使用 at() 方法訪問和修改元素double& elem = matrix.at<double>(0, 0); // 獲取 (0,0) 處的元素引用elem = 100.0;std::cout << "Modified Matrix (at()):\n" << matrix << std::endl;// 直接通過指針訪問 (效率更高,但不安全)// 注意:需要確保數據類型匹配double* p = matrix.ptr<double>(1); // 獲取第二行的指針p[1] = 200.0; // 修改第二行第二列的元素 (1,1)std::cout << "Modified Matrix (ptr):\n" << matrix << std::endl;return 0;
}
基本的算術運算 ?????
OpenCV 支持對矩陣進行各種基本的算術運算。這些運算可以是矩陣與標量之間的運算,也可以是矩陣與矩陣之間的運算。
矩陣與標量運算
可以直接使用標準的算術運算符:
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(2,2) << 1, 2, 3, 4);double scalar = 5.0;cv::Mat result;// 加法result = matrixA + scalar;std::cout << "MatrixA + Scalar:\n" << result << std::endl;// 減法result = matrixA - scalar;std::cout << "MatrixA - Scalar:\n" << result << std::endl;result = scalar - matrixA;std::cout << "Scalar - MatrixA:\n" << result << std::endl;// 乘法result = matrixA * scalar;std::cout << "MatrixA * Scalar:\n" << result << std::endl;// 除法result = matrixA / scalar;std::cout << "MatrixA / Scalar:\n" << result << std::endl;return 0;
}
矩陣與矩陣運算 (逐元素)
對于加法、減法和逐元素的乘法、除法,可以使用重載的運算符或 OpenCV 提供的函數。
- 加法 (
+
或cv::add()
) - 減法 (
-
或cv::subtract()
) - 逐元素乘法 (
cv::multiply()
) - 逐元素除法 (
cv::divide()
)
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(2,2) << 1, 2, 3, 4);cv::Mat matrixB = (cv::Mat_<double>(2,2) << 5, 6, 7, 8);cv::Mat result;// 加法result = matrixA + matrixB;// 或者 cv::add(matrixA, matrixB, result);std::cout << "MatrixA + MatrixB:\n" << result << std::endl;// 減法result = matrixA - matrixB;// 或者 cv::subtract(matrixA, matrixB, result);std::cout << "MatrixA - MatrixB:\n" << result << std::endl;// 逐元素乘法cv::multiply(matrixA, matrixB, result);// 注意: matrixA * matrixB 是矩陣乘法,而不是逐元素乘法std::cout << "Element-wise multiplication (A .* B):\n" << result << std::endl;// 逐元素除法cv::divide(matrixA, matrixB, result);std::cout << "Element-wise division (A ./ B):\n" << result << std::endl;return 0;
}
矩陣乘法 (標準線性代數乘法)
使用 *
運算符可以執行標準的矩陣乘法 (m x n 矩陣乘以 n x p 矩陣得到 m x p 矩陣)。或者使用 cv::gemm()
函數 (通用矩陣乘法)。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(2,3) << 1, 2, 3, 4, 5, 6);cv::Mat matrixB = (cv::Mat_<double>(3,2) << 7, 8, 9, 10, 11, 12);cv::Mat result;// 矩陣乘法result = matrixA * matrixB;std::cout << "MatrixA * MatrixB (Matrix Multiplication):\n" << result << std::endl;// 使用 cv::gemm()// gemm(src1, src2, alpha, src3, beta, dst, flags)// dst = alpha*src1*src2 + beta*src3cv::Mat matrixC = cv::Mat::zeros(2, 2, CV_64F);cv::gemm(matrixA, matrixB, 1.0, cv::Mat(), 0.0, result, 0); // result = 1.0 * matrixA * matrixBstd::cout << "MatrixA * MatrixB (using gemm):\n" << result << std::endl;return 0;
}
注意: 參與矩陣乘法的兩個矩陣的維度必須兼容 (第一個矩陣的列數等于第二個矩陣的行數)。
其他重要的矩陣運算 ??
OpenCV 還提供了許多其他有用的矩陣運算函數。
轉置 (cv::transpose()
或 Mat::t()
)
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(2,3) << 1, 2, 3, 4, 5, 6);cv::Mat transposedMatrix;cv::transpose(matrixA, transposedMatrix);// 或者 transposedMatrix = matrixA.t();std::cout << "Original MatrixA:\n" << matrixA << std::endl;std::cout << "Transposed MatrixA:\n" << transposedMatrix << std::endl;return 0;
}
逆矩陣 (cv::invert()
或 Mat::inv()
)
只有方陣且非奇異矩陣(行列式不為零)才有逆矩陣。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(2,2) << 4, 7, 2, 6);cv::Mat inverseMatrix;// method 可以是 DECOMP_LU, DECOMP_SVD, DECOMP_CHOLESKY (對于對稱正定矩陣)double det = cv::determinant(matrixA);if (det != 0) {cv::invert(matrixA, inverseMatrix, cv::DECOMP_LU);// 或者 inverseMatrix = matrixA.inv(cv::DECOMP_LU);std::cout << "Original MatrixA:\n" << matrixA << std::endl;std::cout << "Inverse MatrixA:\n" << inverseMatrix << std::endl;// 驗證 A * A_inv = Istd::cout << "A * A_inv:\n" << matrixA * inverseMatrix << std::endl;} else {std::cout << "MatrixA is singular, cannot compute inverse." << std::endl;}return 0;
}
行列式 (cv::determinant()
)
計算方陣的行列式。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(3,3) << 1, 2, 3, 0, 1, 4, 5, 6, 0);double det = cv::determinant(matrixA);std::cout << "MatrixA:\n" << matrixA << std::endl;std::cout << "Determinant of MatrixA: " << det << std::endl;return 0;
}
跡 (cv::trace()
)
計算方陣的跡(主對角線元素之和)。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);cv::Scalar traceValue = cv::trace(matrixA);std::cout << "MatrixA:\n" << matrixA << std::endl;std::cout << "Trace of MatrixA: " << traceValue[0] << std::endl; // trace返回一個Scalarreturn 0;
}
范數 (cv::norm()
)
計算矩陣的范數(例如 L1 范數、L2 范數、無窮范數)。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(1,3) << 3, -4, 0); // 也可以是多維矩陣double normL1 = cv::norm(matrixA, cv::NORM_L1);double normL2 = cv::norm(matrixA, cv::NORM_L2);double normInf = cv::norm(matrixA, cv::NORM_INF);std::cout << "MatrixA: " << matrixA << std::endl;std::cout << "L1 Norm: " << normL1 << std::endl; // |3| + |-4| + |0| = 7std::cout << "L2 Norm: " << normL2 << std::endl; // sqrt(3^2 + (-4)^2 + 0^2) = 5std::cout << "Infinity Norm: " << normInf << std::endl; // max(|3|, |-4|, |0|) = 4cv::Mat matrixB = (cv::Mat_<double>(2,2) << 1, 2, 3, 4);double frobeniusNorm = cv::norm(matrixB, cv::NORM_L2); // 對于矩陣,NORM_L2 是 Frobenius 范數std::cout << "MatrixB:\n" << matrixB << std::endl;std::cout << "Frobenius Norm of MatrixB: " << frobeniusNorm << std::endl;return 0;
}
矩陣操作 🛠?
改變形狀 (Mat::reshape()
)
在不改變數據的情況下改變矩陣的維度。
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = (cv::Mat_<double>(2,3) << 1, 2, 3, 4, 5, 6);std::cout << "Original MatrixA (2x3):\n" << matrixA << std::endl;// 改變為一個通道,3行 (列數自動計算)cv::Mat reshaped1 = matrixA.reshape(1, 3);std::cout << "Reshaped to 3 rows (1 channel):\n" << reshaped1 << std::endl;std::cout << "Reshaped1 channels: " << reshaped1.channels() << ", rows: " << reshaped1.rows << ", cols: " << reshaped1.cols << std::endl;// 改變為 3 通道,2 行 (假設原始數據可以這樣組織)// 注意:reshape(cn, rows) cn是新的通道數// 原始矩陣是單通道,6個元素。如果reshape(3, 2),則變成2行1列,3通道。// (1,2,3) 像素1// (4,5,6) 像素2cv::Mat matrixB = cv::Mat::arange(1, 7).reshape(1, 2); // 2行3列,單通道matrixB = matrixB.reshape(3, 2); // 2行1列,3通道std::cout << "Original MatrixB (2x3, 1 channel then reshaped to 2x1, 3 channels):\n" << matrixB << std::endl;std::cout << "MatrixB value at (0,0) Ch0: " << matrixB.at<cv::Vec3b>(0,0)[0] << std::endl; // 假設是 CV_8UC3// arange 默認是 CV_32Scv::Mat matrixC = (cv::Mat_<int>(1,6) << 1,2,3,4,5,6);std::cout << "Original MatrixC (1x6, 1 channel):\n" << matrixC << std::endl;cv::Mat reshapedC = matrixC.reshape(3, 2); // 2行1列,3通道std::cout << "ReshapedC (2x1, 3 channels):\n" << reshapedC << std::endl;// 訪問 reshapedC.at<cv::Vec3i>(0,0)[0] 等return 0;
}
注意: reshape()
不會復制數據。新的矩陣頭指向原始數據。
合并與拼接 (cv::hconcat()
, cv::vconcat()
)
cv::hconcat()
: 水平拼接矩陣 (列數增加)cv::vconcat()
: 垂直拼接矩陣 (行數增加)
#include <opencv2/opencv.hpp>
#include <iostream>int main() {cv::Mat matrixA = cv::Mat::ones(2, 2, CV_64F);cv::Mat matrixB = cv::Mat::zeros(2, 2, CV_64F);cv::Mat matrixC = cv::Mat::eye(2, 2, CV_64F) * 2;cv::Mat horizontalConcat;cv::hconcat(matrixA, matrixB, horizontalConcat); // 可以傳遞多個矩陣cv::hconcat(horizontalConcat, matrixC, horizontalConcat);std::cout << "Horizontal Concatenation:\n" << horizontalConcat << std::endl;cv::Mat matrixD = cv::Mat::ones(2, 2, CV_64F) * 3;cv::Mat matrixE = cv::Mat::ones(2, 2, CV_64F) * 4;cv::Mat verticalConcat;std::vector<cv::Mat> matrices_to_stack = {matrixD, matrixE};cv::vconcat(matrices_to_stack, verticalConcat); // 可以傳遞一個Mat的vector// 或者 cv::vconcat(matrixD, matrixE, verticalConcat);std::cout << "Vertical Concatenation:\n" << verticalConcat << std::endl;return 0;
}
總結 🎉
OpenCV 提供了非常全面且易于使用的矩陣運算功能。通過 cv::Mat
類及其相關函數,可以高效地執行從基本算術運算到復雜線性代數運算的各種操作。熟練掌握這些運算是進行圖像處理和計算機視覺算法開發的基礎。記得查閱 OpenCV 官方文檔以獲取更詳細的信息和更多高級功能。