前面的主題都環繞在開發環境的建置,大部分都著重在編譯器的設定、環境變數的設定等等,非常的枯燥。現在我們終於要寫出第一個OpenCV程式了。但在撰寫程式之前,有一個基本的觀念你絕對不可以漏掉,那就是影像的組成。
像素被視為影像的核心元素,每張影像都是由無數個像素所組成的,每個像素代表著影像中的一個點,它擁有特定的座標位置和色彩值。色彩值可以是灰度值(在灰階影像中)或是紅、綠、藍三種色彩通道的值(在RGB彩色影像中)。
以一張512x512的Lenna圖為例,影像被切割成了512x512個像素,每個像素代表著影像中的一個小區域。這些像素的排列和色彩值的組合形成了整個影像的外觀。
影像的尺寸是指影像的寬度和高度,通常使用像素作為單位來表示。例如,一張影像的尺寸可以表示為「寬度 x 高度」,像是「1920 x 1080」,這代表著該影像的寬度為1920像素,高度為1080像素。
色彩模式定義了影像中每個像素的顏色表示方式。常見的色彩模式包括RGB(紅綠藍)、CMYK(青、品紅、黃、黑)和灰階(Gray scale)等。
以一張RGB彩色影像為例,實際上它是由三個不同的色彩通道所組成。每個像素都包含了這三個通道(通常稱為R、G和B通道)的數值,分別代表紅色、綠色和藍色的強度。通過調整這三個通道的數值,我們可以創造出各種不同的顏色和色調,這樣就構成了整張彩色影像。
你可以想像成這三個通道像是調色盤上的三種顏料,通過混合不同比例的顏料,我們可以創造出多種不同的顏色。下圖展示了Lenna影像的三個顏色通道分別長怎樣,我們可以將它們分開,然後重新組合,從而形成一張完整的彩色照片。
除了RGB色彩模式外,還存在其他色彩模式,例如HSV、HSL、CMYK...等等。
位元深度是指每個像素所使用的位元數,用來表示該像素的顏色資訊。位元深度決定了每個像素可以表示的不同顏色數量,從而影響了影像的色彩細膩度。較高的位元深度能夠呈現更多色彩細節,但相對的也需要更多的記憶體和儲存空間。
常見的位元深度包括:
cv::Mat
其實是一個矩陣,只不過在這裡被用來存放影像的像素值,這個變數具有以下的成員變數和函數可以呼叫,取得矩陣的屬性。
img.rows
:影像的高度,以像素為單位。img.cols
:影像的寬度,以像素為單位。img.type()
取得矩陣元素類型,例如CV_16SC3或16位有符號3通道數量。img.depth()
影像的深度。下表列出了所有位元深度的列舉(Enum)。img.channels()
影像的通道數。這表示影像的顏色通道數量,通常是 3(紅、綠、藍)。代表常數 | 值 | 深度 |
---|---|---|
CV_8U | 0 | 8-bit 無號整數 |
CV_8S | 1 | 8-bit 有號整數 |
CV_16U | 2 | 16-bit 無號整數 |
CV_16S | 3 | 16-bit 有號整數 |
CV_32S | 4 | 32-bit 有號整數 |
CV_32F | 5 | 32-bit 浮點數 |
CV_64F | 6 | 64-bit 浮點數 |
函式imread
從指定的檔案載入影像並回傳Mat,可以把Mat當成一個三通道的矩陣。
cv::Mat img = cv::imread("C:\\Users\\vince\\Downloads\\Lenna.png",cv::IMREAD_UNCHANGED);
filename
:要讀取的圖片檔案的路徑。可以是相對路徑或絕對路徑。flags
:一個整數值,代表了讀取影像的方式和特性。這個參數可以使用不同flag來指定如何讀取影像。例如,你可以使用 cv::IMREAD_COLOR
來讀取一張彩色影像,使用 cv::IMREAD_GRAYSCALE
來讀取一張灰階影像,或是使用其他旗標進行不同的操作。cv::imshow
用於在視窗中顯示影像。
cv::imshow("merged", dst);
"merged"
:影像的視窗的標題。這個標題將顯示在視窗的標題欄中,以識別不同的顯示視窗。以這行程式碼為例,視窗標題為"merged"。dst
:要顯示的影像,是一個cv::Mat
。建立一個 channels
向量,用於儲存分離後的顏色通道影像。接著使用 cv::split
函式將輸入的彩色影像 img
分離成三個顏色通道,並將這些通道影像存儲在 channels
向量中。
vector<cv::Mat> channels;
cv::split(img,channels);
在這段程式碼中,首先建立了一個名為 dst
的 cv::Mat
物件用來儲存重新合併後的彩色影像。接下來,使用 cv::merge
函式將之前分開的三個顏色通道 channels
合併起來,形成一張完整的彩色影像,並將合併後的結果儲存在 dst
中。
最後,使用 cv::imshow
函式將合併後的彩色影像顯示出來。
cv::Mat dst;
cv::merge(channels, dst);
cv::imshow("merged", dst);
cv::utils::logging::setLogLevel
設定OpenCV的日誌級別為LOG_LEVEL_SILENT
,使OpenCV不輸出任何日誌訊息。cv::imread
讀取一張影像,保持影像的原始通道數和深度。printf
函式輸出影像的大小、類型、深度和通道數等資訊。cv::split
函式將讀取的彩色影像分成三個顏色通道,分別存儲在channels
向量中。convertSplitChannel
,將channels
向量中的每個通道轉換為其原始通道的顏色,然後使用cv::imshow
函式來顯示藍、綠和紅通道的影像。但其實convertSplitChannel
不是必需的,會這樣寫只是讓你更容易理解影像的組成。cv::merge
函式將藍、綠和紅通道再次合併成一張彩色影像,並存儲在dst
變數中。cv::imshow
函式顯示合併後的彩色影像,標題為"merged"。cv::waitKey(0)
:等待使用者按下鍵盤按鈕,並持續顯示影像,直到使用者關閉視窗。如果沒有加這行的話視窗會在程式結束時直接關閉。#include "opencv_color_split.h"
#include "opencv2/opencv.hpp"
#include <opencv2/core/utils/logger.hpp>h
using namespace std;
cv::Mat convertSplitChannel(int channel, cv::Mat single_channel);
int main()
{
cv::utils::logging::setLogLevel(cv::utils::logging::LogLevel::LOG_LEVEL_SILENT);
// 讀取圖片
cv::Mat img = cv::imread("C:\\Users\\vince\\Downloads\\Lenna.png",cv::IMREAD_UNCHANGED);
printf("圖片大小 高度:%dpx 寬度:%dpx\n", img.rows, img.cols);
printf("圖片類型:%d\n",img.type());
printf("圖片深度:%d\n",img.depth());
printf("圖片通道數:%d\n",img.channels());
vector<cv::Mat> channels;
// 將分成三個顏色通道
cv::split(img,channels);
// 顯示Blue Channel
cv::imshow("Blue Channel", convertSplitChannel(0,channels.at(0)));
// 顯示Green Channel
cv::imshow("Green Channel", convertSplitChannel(1,channels.at(1)));
// 顯示Red Channel
cv::imshow("Red Channel", convertSplitChannel(2,channels.at(2)));
cv::Mat dst;
// 合併三個通道,重建彩色圖
cv::merge(channels, dst);
cv::imshow("merged", dst);
cv::waitKey(0);
return 0;
}
// 將每個通道轉換成其通道的顏色
cv::Mat convertSplitChannel(int channel,cv::Mat single_channel) {
vector<cv::Mat> dst_channels;
for (int i = 0;i < 3;i++) {
if(i==channel)
dst_channels.push_back(single_channel);
else
dst_channels.push_back(cv::Mat::zeros(single_channel.rows, single_channel.cols, CV_8UC1));
}
cv::Mat dst;
cv::merge(dst_channels, dst);
return dst;
}