iT邦幫忙

2023 iThome 鐵人賽

DAY 6
0

一、 介紹

前面的主題都環繞在開發環境的建置,大部分都著重在編譯器的設定、環境變數的設定等等,非常的枯燥。現在我們終於要寫出第一個OpenCV程式了。但在撰寫程式之前,有一個基本的觀念你絕對不可以漏掉,那就是影像的組成。

二、原理

1. 影像的組成

https://ithelp.ithome.com.tw/upload/images/20230916/20161732mms58PK2op.png

1) 像素(Pixel)

像素被視為影像的核心元素,每張影像都是由無數個像素所組成的,每個像素代表著影像中的一個點,它擁有特定的座標位置和色彩值。色彩值可以是灰度值(在灰階影像中)或是紅、綠、藍三種色彩通道的值(在RGB彩色影像中)。

以一張512x512的Lenna圖為例,影像被切割成了512x512個像素,每個像素代表著影像中的一個小區域。這些像素的排列和色彩值的組合形成了整個影像的外觀。

2) 影像尺寸(Dimensions)

影像的尺寸是指影像的寬度和高度,通常使用像素作為單位來表示。例如,一張影像的尺寸可以表示為「寬度 x 高度」,像是「1920 x 1080」,這代表著該影像的寬度為1920像素,高度為1080像素。

3) 色彩模式(Color Mode)

色彩模式定義了影像中每個像素的顏色表示方式。常見的色彩模式包括RGB(紅綠藍)、CMYK(青、品紅、黃、黑)和灰階(Gray scale)等。

以一張RGB彩色影像為例,實際上它是由三個不同的色彩通道所組成。每個像素都包含了這三個通道(通常稱為R、G和B通道)的數值,分別代表紅色、綠色和藍色的強度。通過調整這三個通道的數值,我們可以創造出各種不同的顏色和色調,這樣就構成了整張彩色影像。

你可以想像成這三個通道像是調色盤上的三種顏料,通過混合不同比例的顏料,我們可以創造出多種不同的顏色。下圖展示了Lenna影像的三個顏色通道分別長怎樣,我們可以將它們分開,然後重新組合,從而形成一張完整的彩色照片。

除了RGB色彩模式外,還存在其他色彩模式,例如HSV、HSL、CMYK...等等。

4) 位元深度(Bit Depth)

位元深度是指每個像素所使用的位元數,用來表示該像素的顏色資訊。位元深度決定了每個像素可以表示的不同顏色數量,從而影響了影像的色彩細膩度。較高的位元深度能夠呈現更多色彩細節,但相對的也需要更多的記憶體和儲存空間。

常見的位元深度包括:

  • 8位位元深度: 每個像素使用8個位元(或1個位元組)來表示顏色。這種深度下,可以表示的顏色數量有2^8 = 256種,通常用於灰階影像。
  • 16位位元深度: 每個像素使用16個位元(紅色佔5個位元,藍色佔5個位元,綠色佔6個位元)來表示顏色。這種深度下,可以表示更多的顏色變化,通常用於灰階影像或彩色影像。
  • 24位位元深度: 每個像素使用24個位元(3個通道,每個通道8位元)來表示顏色。這是最常見的深度之一,用於表示彩色影像,每個顏色通道可以有256個不同的強度值,從而總共可以表示16,777,216種不同的顏色。

https://ithelp.ithome.com.tw/upload/images/20230916/20161732l6RB2eRkY8.png

https://ithelp.ithome.com.tw/upload/images/20230916/20161732106cBbOjQH.png

三、 程式碼

1. 逐行解釋

1) 存放影像的變數 cv::Mat

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 浮點數

2) 讀取影像

函式imread從指定的檔案載入影像並回傳Mat,可以把Mat當成一個三通道的矩陣。

cv::Mat img = cv::imread("C:\\Users\\vince\\Downloads\\Lenna.png",cv::IMREAD_UNCHANGED);
  1. filename:要讀取的圖片檔案的路徑。可以是相對路徑或絕對路徑。
  2. flags:一個整數值,代表了讀取影像的方式和特性。這個參數可以使用不同flag來指定如何讀取影像。例如,你可以使用 cv::IMREAD_COLOR 來讀取一張彩色影像,使用 cv::IMREAD_GRAYSCALE 來讀取一張灰階影像,或是使用其他旗標進行不同的操作。

3) 顯示影像

cv::imshow用於在視窗中顯示影像。

cv::imshow("merged", dst);
  • "merged":影像的視窗的標題。這個標題將顯示在視窗的標題欄中,以識別不同的顯示視窗。以這行程式碼為例,視窗標題為"merged"。
  • dst:要顯示的影像,是一個cv::Mat

4) 分離顏色通道

建立一個 channels 向量,用於儲存分離後的顏色通道影像。接著使用 cv::split 函式將輸入的彩色影像 img 分離成三個顏色通道,並將這些通道影像存儲在 channels 向量中。

vector<cv::Mat> channels;
cv::split(img,channels);

5) 合併通道

在這段程式碼中,首先建立了一個名為 dstcv::Mat 物件用來儲存重新合併後的彩色影像。接下來,使用 cv::merge 函式將之前分開的三個顏色通道 channels 合併起來,形成一張完整的彩色影像,並將合併後的結果儲存在 dst 中。

最後,使用 cv::imshow 函式將合併後的彩色影像顯示出來。

cv::Mat dst;
cv::merge(channels, dst);
cv::imshow("merged", dst);

2. 完整程式碼

  1. 設定日誌級別:使用cv::utils::logging::setLogLevel設定OpenCV的日誌級別為LOG_LEVEL_SILENT,使OpenCV不輸出任何日誌訊息。
  2. 讀取影像:使用cv::imread讀取一張影像,保持影像的原始通道數和深度。
  3. 顯示影像資訊:使用printf函式輸出影像的大小、類型、深度和通道數等資訊。
  4. 分割通道:使用cv::split函式將讀取的彩色影像分成三個顏色通道,分別存儲在channels向量中。
  5. 顯示各通道:使用函式convertSplitChannel,將channels向量中的每個通道轉換為其原始通道的顏色,然後使用cv::imshow函式來顯示藍、綠和紅通道的影像。但其實convertSplitChannel不是必需的,會這樣寫只是讓你更容易理解影像的組成。
  6. 合併通道:使用cv::merge函式將藍、綠和紅通道再次合併成一張彩色影像,並存儲在dst變數中。
  7. 顯示合併後的影像:使用cv::imshow函式顯示合併後的彩色影像,標題為"merged"。
  8. 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;
}

3. 輸出結果

https://ithelp.ithome.com.tw/upload/images/20230916/20161732PoMy7nYMqq.png


上一篇
【Day5】使用Visual Studio建立你的第一個OpenCV專案
下一篇
【Day7】使用OpenCV將彩色圖片灰階化
系列文
圖解C++影像處理與OpenCV應用:從基礎到高階,深入學習超硬核技術!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言