Wiki
影像基本操作
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
在獲取影像後,首先需要了解處理影像的基本操作,例如對影像顏色的分離,畫素的改變,影像的拉伸與旋轉,甚至需要在影像中新增一些基礎的形狀,並進行簡單處理。因此,本章重點介紹OpenCV 4中提供的影像基本操作,包括彩色空間的介紹、畫素操作、影像形狀的改變、繪製幾何圖形以及生成影像金字塔等。
1.影像顏色空間
通過紅、綠、藍3種顏色不同比例的混合能夠讓影像展現出五彩斑斕的顏色,這種模型稱為RGB顏色模型。RGB顏色模型是最常見的顏色模型之一,常用於表示和顯示影像。為了能夠表示3種顏色的混合,影像以多通道的形式分別儲存某一種顏色的紅色分量、綠色分量和藍色分量。除RGB顏色模型外,影像的顏色模型還有YUV、HSV等模型,分別表示影像的亮度、色度、飽和度等分量。瞭解影像顏色空間對分割擁有顏色區分特徵的影像具有重要的幫助,例如提取影像中的紅色物體可以通過比較影像紅色通道的畫素值來實現。
1.1.顏色模型與轉換
本小節將介紹幾種OpenCV 4中能夠互相轉換的常見顏色模型,例如RGB模型、HSV模型、Lab模型、YUV模型及GRAY模型,並介紹這幾種模型之間的數學轉換關係、OpenCV 4中提供的這幾種模型之間的變換函式。
1. RGB顏色模型
前面對於RGB顏色模型已經有所介紹,該模型的命名方式是採用3種顏色的英文首字母,分別是紅色(Red)、綠色(Green)和藍色(Blue)。雖然該顏色模型的命名方式是紅色在前,但是在OpenCV中卻是相反的順序,第一個通道是藍色(B)分量,第二個通道是綠色(G)分量,第三個通道是紅色(R)分量。根據儲存順序的不同,OpenCV 4中提供了這種順序的反序格式,用於儲存第一個通道是紅色分量的影像,但是這兩種格式影像的顏色空間是相同的,顏色空間模型如圖3-1所示。3個通道對於顏色描述的範圍是相同的,因此RGB顏色模型的空間構成是一個立方體。在RGB顏色模型中,所有的顏色都是由這3種顏色通過不同比例的混合得到,如果3種顏色分量都為0,則表示為黑色,如果3種顏色的分量相同且都為最大值,則表示為白色。每個通道都表示某一種顏色0~1的過程,不同位數的影像表示將這個顏色變化過程細分成不同的層級,例如8UC3格式的影像每個通道將這個過程量化成256個等級,分別由0~255表示。在這個模型的基礎上增加第四個通道即為RGBA模型,第四個通道表示顏色的透明度,當沒有透明度需求的時候,RGBA模型就會退化成RGB模型。

2. YUV顏色模型
YUV模型是電視訊號系統所採用的顏色編碼方式。這3個變數分別表示畫素的亮度(Y)、紅色分量與亮度的訊號差值(U)、藍色與亮度的差值(V)。這種顏色模型主要用於影片和影像的傳輸,該模型的產生與電視機的發展歷程密切相關。由於彩色電視機在黑白電視機發明之後才產生,因此用於彩色電視機的影片訊號需要能夠相容黑白電視機。彩色電視機需要3個通道的資料才能顯示彩色,而黑白電視機只需要一個通道的資料,因此,為了使影片訊號能夠相容彩色電視機與黑白電視機,將RGB編碼方式轉變成YUV的編碼方式,其Y通道是影像的亮度,黑白電視只需要使用該通道就可以顯示黑白影片影像,而彩色電視機通過將YUV編碼轉成RGB編碼方式,便可以在彩色電視機中顯示彩色影像,較好地解決了同一個影片訊號相容不同型別電視機的問題。RGB模型與YUV模型之間的轉換關係如式(3-1)所示,其中RGB取值範圍均為0~255。

3. HSV顏色模型
HSV是色度(Hue)、飽和度(Saturation)和亮度(Value)的簡寫,通過名字也可以看出該模型通過這3個特性對顏色進行描述。色度是色彩的基本屬性,就是平時常說的顏色,例如紅色、藍色等;飽和度是指顏色的純度,飽和度越高色彩越純和越豔,飽和度越低,色彩則逐漸地變灰和變暗,飽和度的取值範圍是0~100%;亮度是顏色的明亮程度,其取值範圍由0到計算機中允許的最大值。由於色度、飽和度和亮度的取值範圍不同,因此其顏色空間模型用錐形表示,如圖3-2所示。相比於RGB模型3個顏色分量與最終顏色聯絡不直觀的缺點,HSV模型更加符合人類感知顏色的方式:顏色、深淺及亮暗。

4. Lab顏色模型
Lab顏色模型彌補了RGB模型的不足,是一種裝置無關和基於生理特徵的顏色模型。在模型中,L表示亮度(Luminosity),a和b是兩個顏色通道,兩者的取值區間都是-128~127,其中a通道數值由小到大對應的顏色是從綠色變成紅色,b通道數值由小到大對應的顏色是由藍色變成黃色。Lab顏色模型構成的顏色空間是一個球形,如圖3-3所示。

5. GRAY顏色模型
GRAY模型並不是一個彩色模型,而是一個灰度影像的模型,其命名使用的是英文單詞gray的全字母大寫。灰度影像只有單通道,灰度值根據影像位數不同由0到最大依次表示由黑到白,例如8UC1格式中,由黑到白被量化為256個等級,通過0~255表示,其中255表示白色。彩色影像具有顏色豐富、資訊含量大的特性,但是灰度圖在影像處理中依然具有一定的優勢。例如,灰度影像具有相同尺寸相同壓縮格式所佔容量小、易於採集、便於傳輸等優點。常用的RGB模型轉成灰度圖的方式如式(3-2)所示。

6. 不同顏色模型間的互相轉換
針對影像不同顏色模型之間的相互轉換,OpenCV 4提供了cvtColor()函式用於實現轉換功能,該函式的原型在程式碼清單3-1中給出。
程式碼清單3-1 cvtColor()函式原型
void cv::cvtColor(InputArray src,
OutputArray dst,
int code,
int dstCn = 0);
- src: 待轉換顏色模型的原始影像。
- dst: 轉換顏色模型後的目標影像。
- code: 顏色空間轉換的標誌,如由RGB空間到HSV空間。常用標誌及含義在表3-1中給出。
- dstCn: 目標影像中的通道數。如果引數為0,則從src和code中自動匯出通道數。
函式用於將影像從一個顏色模型轉換為另一個顏色模型,前兩個引數用於輸入待轉換影像和轉換顏色空間後的目標影像,第三個引數用於宣告該函式具體的轉換模型空間,常用的標誌在表3-1中給出,讀者可以自行查閱OpenCV 4的教程以瞭解詳細的標誌。第四個引數在一般情況下不需要特殊設定,使用預設引數即可。 需要注意的是該函式變換前後的影像取值範圍,由於8位無符號影像的畫素為0~255,16位無符號影像的畫素為0~65535,而32位浮點影像的畫素為0~1,因此一定要注意目標影像的畫素範圍。線上性變換的情況下,範圍問題不需要考慮,目標影像的畫素不會超出範圍。如果在非線性變換的情況下,那麼應將輸入RGB影像歸一化到適當的範圍以內來獲得正確的結果,例如將8位無符號影像轉成32位浮點影像,需要先將影像畫素通過除以255縮放到0~1範圍內,以防止產生錯誤結果。
注意:如果轉換過程中添加了alpha通道(RGB模型中第四個通道,表示透明度),則其值將設定為相應通道範圍的最大值:CV_8U為255,CV_16U為65535,CV_32F為1。
表3-1 cvtColor()函式顏色模型轉換常用標誌引數
| 標誌引數 | 簡記 | 作用 |
|---|---|---|
COLOR_BGR2BGRA |
0 | 對RGB影像新增alpha通道 |
COLOR_BGR2RGB |
4 | 彩色影像通道顏色順序的更改 |
COLOR_BGR2GRAY |
10 | 彩色影像轉成灰度影像 |
COLOR_GRAY2BGR |
8 | 灰度影像轉成彩色影像(偽彩色) |
COLOR_BGR2YUV |
82 | RGB顏色模型轉成YUV顏色模型 |
COLOR_YUV2BGR |
84 | YUV顏色模型轉成RGB顏色模型 |
COLOR_BGR2HSV |
40 | RGB顏色模型轉成HSV顏色模型 |
COLOR_HSV2BGR |
54 | HSV顏色模型轉成RGB顏色模型 |
COLOR_BGR2Lab |
44 | RGB顏色模型轉成Lab顏色模型 |
COLOR_Lab2BGR |
56 | Lab顏色模型轉成RGB顏色模型 |
為了直觀地感受同一張影像在不同顏色空間中的樣子,在程式碼清單3-2中給出了前面幾種顏色模型互相轉換的程式,執行結果如圖3-4所示。需要說明的是,Lab顏色模型具有負數,而通過imshow()函式顯示的影像無法顯示負數,因此在結果中給出了利用Image Watch外掛顯示影像在Lab模型中的樣子。在程式中,為了防止轉換後出現數值越界的情況,我們先將CV_8U型別轉成CV_32F型別後再進行顏色模型的轉換。
程式碼清單3-2 myCvColor.cpp 影像顏色模型互相轉換
#include "chapter3_1_cvclolor/inc/CvColor.hpp"
#include <cstdio>
#include <opencv2/opencv.hpp>
int opencv_function9(void)
{
const std::string & file_name = std::string(MEDIA_PATH) + "林星阑L.jpg";
cv::Mat img = cv::imread(file_name);
if(img.empty() == true)
{
std::cout << "请确认图像文件是否正确,请检查输入格式" << std::endl;
return 1;
}
else
{
std::cout << "图像成功读取!" << std::endl;
}
cv::Mat img32;
cv::Mat gray,HSV,YUV,Lab;
img.convertTo(img32,CV_32F,1.0/255); //缩放因子:1.0/255指将现在的图像的范围转换为目标图像的范围需要乘以的因数
cv::cvtColor(img32,HSV,cv::COLOR_BGR2HSV);
cv::cvtColor(img32,YUV,cv::COLOR_BGR2YUV);
cv::cvtColor(img32,Lab,cv::COLOR_BGR2Lab);
cv::cvtColor(img32,gray,cv::COLOR_BGR2GRAY);
cv::namedWindow("原图BGR",cv::WINDOW_FREERATIO);
cv::namedWindow("HSV",cv::WINDOW_FREERATIO);
cv::namedWindow("YUV",cv::WINDOW_FREERATIO);
cv::namedWindow("Lab",cv::WINDOW_FREERATIO);
cv::namedWindow("gray",cv::WINDOW_FREERATIO);
cv::imshow("原图BGR",img32);
cv::imshow("HSV",HSV);
cv::imshow("YUV",YUV);
cv::imshow("Lab",Lab);
cv::imshow("gray",gray);
cv::waitKey(0);
return 0;
}

程式中我們利用了OpenCV 4中Mat類自帶的資料型別轉換函式convertTo(),在平時使用影像資料時也會經常遇到不同資料型別轉換的問題,因此下面詳細介紹該轉換函式的使用方式,在程式碼清單3-3中給出了該函式的原型。
程式碼清單3-3 convertTo()函式原型
void cv::Mat::convertTo(OutputArray m,
int rtype,
double alpha = 1,
double beta = 0);
- m: 轉換型別後輸出的影像。
- rtype: 轉換影像的資料型別。
- alpha: 轉換過程中的縮放因子。
- beta: 轉換過程中的偏置因子。
該函式用來實現將已有影像轉換成指定資料型別的影像,第一個引數用於輸出轉換資料型別後的影像,第二個引數用於宣告轉換後圖像的資料型別。第三個引數與第四個引數用於宣告兩個資料型別間的轉換關係,具體轉換形式如式(3-3)所示。

通過轉換公式可以知道,該轉換方式就是將原有資料進行線性轉換,並按照指定的資料型別輸出。根據其轉換規則可以知道,該函式不但能夠實現不同資料型別之間的轉換,而且能夠實現在同一種資料型別中的線性變換。我們在程式碼清單3-2中給出了CV_8U型別和CV_32F型別之間互相轉換的示例,其他型別之間的互相轉換與此類似,這裡不再贅述,讀者可以自行探索,通過實踐體會該函式的使用方法。
1.2.多通道分離與合併
在影像顏色模型中,不同的分量存放在不同的通道中,如果我們只需要顏色模型的某一個分量,例如只需要處理RGB影像中的紅色通道,那麼可以將紅色通道從3個通道的資料中分離出來再進行處理,這種方式可以減少資料所佔據的記憶體,加快程式的執行速度。同時,當我們分別處理完多個通道後,需要將所有通道合併在一起重新生成RGB影像。針對影像多通道的分離與混合,OpenCV 4中提供了split()函式和merge()函式用於滿足這些需求。
1. 多通道分離函式split()
OpenCV 4中針對多通道分離函式split()有兩種過載原型,在程式碼清單3-4中給出了這兩種函式原型。
程式碼清單3-4 split()函式原型
void cv::split(const Mat &src,
Mat *mvbegin);
void cv::split(InputArray m,
OutputArrayOfArrays mv);
- mvbegin: 分離後的單通道影像,為陣列形式,陣列大小需要與影像的通道數相同。
- m: 待分離的多通道影像。
- mv: 分離後的單通道影像,為向量(vector)形式。
該函式主要是用於將多通道的影像分離成若干單通道的影像,兩個函式原型中不同之處在於,前者第二個引數輸出的是Mat型別的陣列,其陣列的長度需要與多通道影像的通道數相等並且提前定義;第二種函式原型的第二個引數輸出的是一個vector<Mat>容器,不需要知道多通道影像的通道數。雖然兩個函式原型輸入引數的型別不同,但通道分離的原理是相同的。
2. 多通道合併函式merge()
OpenCV 4中針對多通道合併函式merge()也有兩種過載原型,在程式碼清單3-5中給出了這兩種原型。 程式碼清單3-5 merge()函式原型
void cv::merge(const Mat *mv,
size_t count,
OutputArray dst
)
void cv::merge(InputArrayOfArrays mv,
OutputArray dst);
- mv(第一種過載原型引數):需要合併的影像陣列,其中每個影像必須擁有相同的尺寸和資料型別。
- count:輸入的影像陣列的長度,其數值必須大於0。
- mv(第二種過載原型引數):需要合併的影像向量(vector),其中每個影像必須擁有相同的尺寸和資料型別。
- dst:合併後輸出的影像,與mv[0]具有相同的尺寸和資料型別,通道數等於所有輸入影像的通道數總和。
該函式主要用於將多個影像合併成一個多通道影像,該函式也具有兩種不同的函式原型,每一種函式原型都與split()函式相對應,兩種原型分別輸入陣列形式的影像資料和向量(vector)形式的影像資料,在輸入陣列形式資料的原型中,還需要輸入陣列的長度。合併函式的輸出結果是一個多通道的影像,其通道數目是所有輸入影像通道數目的總和。這裡需要說明的是,用於合併的影像並非都是單通道的,也可以是多個通道數目不相同的影像合併成一個通道更多的影像。雖然這些影像的通道數目可以不相同,但是需要所有影像具有相同的尺寸和資料型別。
3. 影像多通道分離與合併例程
為了使讀者更加熟悉影像多通道分離與合併的操作,同時加深對影像不同通道作用的理解,在程式碼清單3-6中實現了影像的多通道分離與合併的功能。程式中用兩種函式原型分別分離了RGB影像和HSV影像,為了驗證merge()函式可以合併多個通道不相同的影像,程式中分別用兩種函式原型合併了多個不同通道的影像,合併後圖像的通道數為5,不能通過imshow()函式顯示,我們用Image Watch外掛在看合併的結果。由於RGB的3個通道分離結果顯示時都是灰色且相差不大,因此圖3-5沒有給出其分離後的結果,只是給出合併後顯示為綠色的合併影像,同時給出HSV分離結果,其他結果讀者可以通過自行執行程式檢視。 程式碼清單3-6 mySplitAndMerge.cpp 實現影像分離與合併
#include "chapter3_1_cvclolor/inc/SplitAndMerge.hpp"
#include <cstdio>
#include <opencv2/opencv.hpp>
bool Mat_Arr_Split_merge(const std::string &file_name);
bool Vector_Split_merge(const std::string &file_name);
int opencv_function10(void)
{
Mat_Arr_Split_merge(std::string(MEDIA_PATH) + "林星阑L.jpg");
// Vector_Split_merge(std::string(MEDIA_PATH) + "林星阑L.jpg");
return 0;
}
bool Mat_Arr_Split_merge(const std::string &file_name)
{
cv::Mat img = cv::imread(file_name);
if(img.empty() == false)
{
printf("成功读取图片!");
}
else
{
printf("无法读取图片,请确定图片文件是否存在,输入格式是否正确!");
return false;
}
cv::Mat imgs[3];
cv::Mat result[2];
cv::split(img,imgs);
cv::namedWindow("RGB-B通道",cv::WINDOW_FREERATIO);
cv::namedWindow("RGB-G通道",cv::WINDOW_FREERATIO);
cv::namedWindow("RGB-R通道",cv::WINDOW_FREERATIO);
cv::imshow("RGB-B通道",imgs[0]);
cv::imshow("RGB-G通道",imgs[1]);
cv::imshow("RGB-R通道",imgs[2]);
imgs[2] = img; //改变图像通道数量
cv::merge(imgs,3,result[0]);
// cv::namedWindow("合并图像结果0",cv::WINDOW_FREERATIO);
// cv::imshow("合并图像结果0",result[0]); //imshow最多显示4个通道,需要Image Watch插件
cv::Mat zero = cv::Mat::zeros(img.rows,img.cols,CV_8UC1); //一个通道的0矩阵
imgs[0] = zero;
imgs[2] = zero;
cv::merge(imgs,3,result[1]);
cv::namedWindow("合并图像结果1",cv::WINDOW_FREERATIO);
cv::imshow("合并图像结果1",result[1]); //显示合并结果
cv::waitKey(0);
cv::destroyAllWindows();
return true;
}
bool Vector_Split_merge(const std::string &file_name)
{
cv::Mat img = cv::imread(file_name);
if(img.empty() == false)
{
printf("成功读取图片!");
}
else
{
printf("无法读取图片,请确定图片文件是否存在,输入格式是否正确!");
return false;
}
cv::Mat HSV;
cv::Mat result;
cv::cvtColor(img,HSV,cv::COLOR_RGB2HSV);
std::vector<cv::Mat> imgv;
cv::split(HSV,imgv);
cv::namedWindow("HSV-H通道",cv::WINDOW_FREERATIO);
cv::namedWindow("HSV-S通道",cv::WINDOW_FREERATIO);
cv::namedWindow("HSV-V通道",cv::WINDOW_FREERATIO);
cv::imshow("HSV-H通道",imgv.at(0));
cv::imshow("HSV-S通道",imgv.at(1));
cv::imshow("HSV-V通道",imgv.at(2));
imgv.push_back(HSV); //将imgv中的图像通道变成不一致
cv::merge(imgv,result);
// cv::namedWindow("合并图像结果2",cv::WINDOW_FREERATIO);
// cv::imshow("合并图像结果2",result); //imshow最多显示4个通道,需要Image Watch插件
cv::waitKey(0);
cv::destroyAllWindows();
return true;
}

2.影像畫素操作處理
在對影像的不同通道有所瞭解之後,接下來將對每個通道內影像畫素的相關操作進行介紹。關於畫素的相關概念,在前面已經有所瞭解,例如在CV_8U的影像中,畫素取值範圍由黑到白被分成了256份,灰度值為0~255來表示這個變化的過程。因此,畫素灰度值的大小表示的是某個位置畫素的亮暗程度,同時灰度值的變化程度也表示了影像紋理的變化程度,因此,瞭解畫素的相關操作是瞭解影像內容的第一步。
2.1.影像畫素統計
我們可以將數字影像理解成一定尺寸的矩陣,矩陣中每個元素的大小表示了影像中每個畫素的亮暗程度,因此,統計矩陣中的最大值就是尋找影像中灰度值最大的畫素,計算平均值就是計算影像畫素平均灰度,可以用來表示影像整體的亮暗程度。因此,針對矩陣資料的統計工作在影像畫素中同樣具有一定的意義和作用。在OpenCV 4中集成了求取影像畫素最大值、最小值、平均值、均方差等眾多用於統計的函式,下面詳細介紹這些功能的相關函式。