圖像基本操作
在獲取圖像後,首先需要了解處理圖像的基本操作,例如對圖像顏色的分離,像素的改變,圖像的拉伸與旋轉,甚至需要在圖像中添加一些基礎的形狀,並進行簡單處理。因此,本章重點介紹OpenCV 4中提供的圖像基本操作,包括彩色空間的介紹、像素操作、圖像形狀的改變、繪製幾何圖形以及生成圖像金字塔等。
圖像顏色空間
通過紅、綠、藍3種顏色不同比例的混合能夠讓圖像展現出五彩斑斕的顏色,這種模型稱爲RGB顏色模型。RGB顏色模型是最常見的顏色模型之一,常用於表示和顯示圖像。爲了能夠表示3種顏色的混合,圖像以多通道的形式分別存儲某一種顏色的紅色分量、綠色分量和藍色分量。除RGB顏色模型外,圖像的顏色模型還有YUV、HSV等模型,分別表示圖像的亮度、色度、飽和度等分量。瞭解圖像顏色空間對分割擁有顏色區分特徵的圖像具有重要的幫助,例如提取圖像中的紅色物體可以通過比較圖像紅色通道的像素值來實現。
顏色模型與轉換
本小節將介紹幾種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類型之間互相轉換的示例,其他類型之間的互相轉換與此類似,這裏不再贅述,讀者可以自行探索,通過實踐體會該函數的使用方法。
多通道分離與合併
在圖像顏色模型中,不同的分量存放在不同的通道中,如果我們只需要顏色模型的某一個分量,例如只需要處理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:合併後輸出的圖像,與mv0具有相同的尺寸和數據類型,通道數等於所有輸入圖像的通道數總和。
該函數主要用於將多個圖像合併成一個多通道圖像,該函數也具有兩種不同的函數原型,每一種函數原型都與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;
}

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