官方文檔鏈接:https://docs.opencv.org/4.2.0/d5/d98/tutorial_mat_operations.html
輸入 / 輸出 (Input / Output)
圖像 (Images)
從文件中加載圖像:
cv::Mat img = cv::imread(filename);
如果讀取的是一個 jpg 文件,則默認情況下會創建一個 3 通道圖像。如果需要灰度圖像,則使用:
cv::Mat img = cv::imread(filename, cv::IMREAD_GRAYSCALE);
注意
文件的格式由其內容(前幾個字節)決定。要將圖像保存到文件中,請執行如下操作:
cv::imwrite(filename, img);
注意
文件的格式由其擴展名決定。使用 cv::imdecode 和 cv::imencode 從內存(而不是文件)讀取和寫入圖像。
圖像基本操作 (Basic operations with images)
訪問像素值 (Accessing pixel intensity values)
爲了獲得像素強度值,必須知道圖像的類型和通道數。以下是單通道灰度圖像(8UC1 型) 和像素座標 x 和 y 的示例:
cv::Scalar intensity = img.at<uchar>(y, x);
C++ 版本:intensity.val[0] 存儲着 0 到 255 的一個值。注意 x 和 y 的順序。因爲在 OpenCV 中,圖像用和矩陣相同的結構表示,所以我們對這兩種情況都使用相同的約定:首先是基於 0 的行索引(或 y 座標),然後是基於 0 的列索引(或 x 座標)。或者,也可以使用以下符號(僅 C++):
cv::Scalar intensity = img.at<uchar>(cv::Point(x, y);
現在考慮一下具有 BGR 顏色順序(cv::imread 返回的默認格式) 的 3 通道圖像:
cv::Vec3b intensity = img.at<cv::Vec3b>(y, x);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
可以用同樣的方法來處理浮點圖像(例如,可以通過在 3 通道圖像上運行 Sobel 來獲得這樣的圖像)(基於 C++):
cv::Vec3f intensity = img.at<cv::Vec3f>(y, x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
同樣的方法可用於改變像素值:
img.at<uchar>(y, x) = 128;
例如:
完整代碼:
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/highgui/highgui.hpp>
int main(int argc, char** argv)
{
cv::Mat src = cv::imread(cv::samples::findFile("lena.jpg"), cv::IMREAD_COLOR);
for (int i = 100; i <= 200; ++i)
for (int j = 300; j <= 500; ++j)
{
src.at<cv::Vec3b>(i, j).val[0] = 250;
src.at<cv::Vec3b>(i, j).val[1] = 250;
src.at<cv::Vec3b>(i, j).val[2] = 250;
}
cv::imshow("Output", src);
cv::waitKey(0);
return 0;
}
輸出圖像:
OpenCV 中有一些函數,特別是 calib3d 模塊中的函數,比如 cv::projectPoints,它們以 Mat 的形式獲取二維或三維點的數組。矩陣應正好包含一列,每行對應一個點,矩陣類型應相應爲 32FC2 或 32FC3 。這樣的矩陣可以很容易地從 std::vector 構造:
std::vector<cv::Point2f> points;
// ... fill the array
cv::Mat pointsMat = cv::Mat(points);
可以使用相同的方法 cv::Mat::at 訪問矩陣中的點:
cv::Point2f point = pointsMat.at<cv::Point2f>(i, 0);
內存管理和引用計數 (Memory management and reference counting)
Mat 是一種保持矩陣/圖像特徵(行和列數、數據類型等)和指向數據的指針的結構。因此,可以同時擁有於相同數據對應的多個 Mat 實例。Mat 保存一個引用計數,該計數指明在銷燬 Mat 的特定實例時是否必須釋放數據。這裏是一個創建兩個矩陣而不復制數據的例子:
std::vector<cv::Point3f> points;
// ... fill the array
cv::Mat pointsMat = cv::Mat(points).reshape(1);
結果得到了一個 3 列的 32FC1 矩陣,而不是 1 列的 32FC3 矩陣。pointsMat 使用來自點的數據,並且在銷燬時不會釋放內存。但是,在這個特定的實例中,如果我們需要複製數據,必須確保 points 的生存期比 pointsMat 的生存期長。可以使用 cv::Mat::copyTo 或 cv::Mat::clone:
cv::Mat img = cv::imread("image.jpg");
cv::Mat img1 = img.clone();
每個函數可提供一個空的輸出 Mat。每個實現都爲目標矩陣調用 cv::Mat::create。如果矩陣是空的,則此方法爲其分配數據。如果它不是空的並且具有正確的大小和類型,則該方法不會執行任何操作。但是,如果大小或類型與輸入參數不同,則會釋放(並丟失)數據並重新分配數據。例如:
cv::Mat img = cv::imread("image.jpg");
cv::Mat sobelx;
cv::Sobel(img, sobelx, CV_32F, 1, 0);
輸出圖像
原始操作 (Primitive operations)
矩陣上定義了許多方便的算子。例如,
- 下面是如何從現有的灰度圖像 img 生成黑色圖像的方法:
img = cv::Scalar(0);
輸出圖像:
- 選擇感興趣的區域 (ROI):
cv::Rect r(10, 10, 100, 100);
cv::Mat smallImg = img(r);
輸出圖像:
- 將彩色圖像轉換爲灰度圖像:
cv::Mat img = cv::imread("image.jpg"); // loading a 8UC3 image
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
- 將圖像類型從 8UC1 轉換爲 32FC1:
img.convertTo(dst, CV_32F);
圖像可視化 (Visualizing images)
在開發過程中,查看算法的中間結果是非常有用的。OpenCV 提供了一種可視化圖像的便捷方法。一幅 8U 的圖像可以使用以下方式顯示:
cv::Mat img = cv::imread("image.jpg");
cv::namedWindow("image", cv::WINDOW_AUTOSIZE);
cv::imshow("image", img);
cv::waitKey();
調用 cv::waitKey() 將啓動一個消息傳遞週期,該週期將等待 “圖像” 窗口中的鍵擊。一幅 32F 圖像需要轉換爲 8U 類型,例如:
cv::Mat img = cv::imread("image.jpg");
cv::Mat grey;
cv::cvtColor(img, grey, cv::COLOR_BGR2GRAY);
cv::Mat sobelx;
cv::Soble(grey, sobelx, CV_32F, 1, 0);
double minVal, maxVal;
cv::minMaxLoc(sobelx, &minVal, &maxVal); // find minimum and maximum intensities
cv::Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
cv::namedWindow("image", cv::WINDOW_AUTOSIZE);
cv::imshow("image", draw);
cv::waitKey();
輸出圖像:
注意 (Note)
這裏的 cv::namedWindow 不是必要的,因爲後面緊跟着 cv::imshow。但是,它可以用於更改窗口屬性,或者在使用 cv::createTrackbar 時。