概述
POCT-IR圖像識別算法中拋開實驗室相關(guān)的內(nèi)容秋麸,其圖像處理部分都是使用OpenCV來完成。通過學(xué)習(xí)《OpenCV3編程入門》撼嗓,可以更好的在POCT-IR中使用OpenCV,同時也可以對項目中可以提升的點提出一些方案。
通過對這本書的閱讀懒震,深刻地感受到對圖像進行操作歸根結(jié)底就是對圖像中的特定像素點進行處理,最終得到目標(biāo)圖像嗤详。在OpenCV3數(shù)以百計的API法中个扰,有些API以單個像素點作為特征量的變換單位,來修改圖像內(nèi)容葱色,例如:濾波递宅、灰度值變換等。還有一些API以一定范圍內(nèi)的像素點作為特征量的變化單位,來對圖像進行變換办龄,例如邊緣檢測烘绽、仿射變化等。
這篇學(xué)習(xí)筆記以《學(xué)習(xí)OpenCV》書籍中的API為基礎(chǔ)土榴,結(jié)合實踐中的思考诀姚,對OpenCV3進行介紹,加深記憶的同時也希望能夠?qū)χ笫褂肙penCV3的小伙伴起到一定的指引作用玷禽。
全文按照以下三個方面對OpenCV學(xué)習(xí)的內(nèi)容進行整理:
Mat結(jié)構(gòu)赫段、基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)
OpenCV基本API介紹及使用
1. MAT類介紹、基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)
貫穿OpenCV始終的就是Mat類矢赁,通過了解Mat的結(jié)構(gòu)及內(nèi)存分配方式糯笙,將極大的推進我們對OpenCV的學(xué)習(xí)。而一些基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)有助于提高代碼的可讀性和實用性(Point)撩银。
在OpenCV1.0時代,Mat(IplImage*)是存放圖像的數(shù)據(jù)結(jié)構(gòu)额获、基本的圖像容器够庙。在使用的過程中,需要手動管理內(nèi)存空間抄邀,否則會造成內(nèi)存泄漏耘眨。
從OpenCV2.0開始,為了減少手動管理內(nèi)存空間的操作境肾,使用引用計數(shù)的方式將Mat對象進行內(nèi)存管理剔难。
Mat類可以將其分為Header部分和Pointer部分,Header部分主要存放矩陣的大小奥喻,存儲方式偶宫,存儲地址等信息;而Pointer部分主要存儲指向像素值的指針环鲤。在復(fù)制的過程中產(chǎn)生大量計算開銷是復(fù)制Pointer造成的纯趋,所以在Clone過程中只復(fù)制header相關(guān)信息,減少內(nèi)存開銷冷离。
Mat核心成員定義介紹
// 指針结闸,指向存放Pointer的內(nèi)存
uchar* data; int rows, cols;
// 矩陣中每個元素?fù)碛兄档膫€數(shù)
int channels() const;
// 每個元素的位數(shù)
int depth() const;
// 矩陣的維度
int dims;
// 矩陣中每個元素的大小
size_t elemSize() const;
// 矩陣元素的類型
CV_8UC3、CV_16UC2 int type() const;
// 定義了矩陣的布局
size_t step1(int i=0) const;
...
上述部分介紹了Mat的核心成員變量酒朵,通過這些成員變量也大致能夠知道Mat存儲圖像的方式,涉及到如何進行初始化及這些成員變量的運用扎附,在之后的章節(jié)中通過案例及初始化MAT對象對成員變量進一步的認(rèn)識蔫耽。
注:在剛開始閱讀OpenCV相關(guān)文獻的時候,有些文獻會將MAT介紹為結(jié)構(gòu)體,在另一些文章中將Mat介紹為類匙铡,一開始對于各文章中的介紹孰是孰非很難有定論图甜,但是因為其需要手動管理內(nèi)存空間的機制,或者是使用引用計數(shù)的方式管理內(nèi)存鳖眼,根據(jù)OC的值類型和引用類型的相關(guān)知識(結(jié)構(gòu)體是值類型存在棧中系統(tǒng)會自動清理內(nèi)容黑毅、類是引用類型需要運用ARC、MRC機制進行內(nèi)容管理)類比钦讳,確信Mat是一個類矿瘦。如果有更權(quán)威的文獻能夠啪啪打臉,請指正~
寫這篇文章的提綱時愿卒,一直在糾結(jié)需不需要將這部分內(nèi)容放進來缚去,只是關(guān)于Mat初始化并不是很值得放入到文章中。最后考慮加入這部分內(nèi)容琼开,首先確實Mat類是OpenCV核心中的核心易结,其次對筆記之后內(nèi)容得介紹起到承上啟下的作用。
創(chuàng)建方法
UMat(int rows, int cols, int type,const Scalar& s,UMatUsageFlags usageFlags = USAGE_DEFAULT);
Mat mat(2, 2, CV_8UC3, Scalar::all(255, 0, 0));
說明介紹
rows = 2, cols = 2;代表創(chuàng)建一個2*2的矩陣柜候;
type = CV_8UC3搞动;描述為使用8位unsigned char型,每個像素由3個元素組成3通道渣刷。
注 :每個像素最多的通道數(shù)為4鹦肿,除了BGR外加入alpha,而最少為單通道飞主,即灰度值狮惜。
Scalar = Scalar::all(0, 0, 0));描述為每個元素的值為(255, 0, 0),注意不同于常使用的RGB顯示碌识,在OpenCV中采用BGR的方式即第一通道是B碾篡,第二通道是G,第三通道是R筏餐。
Mat mat(2, 2, CV_8UC3, Scalar::all(255, 0, 0));整體描述為創(chuàng)建了一個2*2像素值為藍(lán)色的Mat對象开泽。
更多初始化Mat對象的方法
UMat(int rows, int cols, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT);
UMat(Size size, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT);
//! constucts 2D matrix and fills it with the specified value _s.
UMat(Size size, int type, const Scalar& s, UMatUsageFlags usageFlags = USAGE_DEFAULT);
//! constructs n-dimensional matrix
UMat(int ndims, const int* sizes, int type, UMatUsageFlags usageFlags = USAGE_DEFAULT); UMat(int ndims, const int* sizes, int type, const Scalar& s, UMatUsageFlags usageFlags = USAGE_DEFAULT);
//! copy constructor UMat(const UMat& m);
//! creates a matrix header for a part of the bigger matrix
UMat(const UMat& m, const Range& rowRange, const Range& colRange=Range::all()); UMat(const UMat& m, const Rect& roi);
UMat(const UMat& m, const Range* ranges);
UMat(const UMat& m, const std::vector& ranges);
創(chuàng)建方法
Mat imread(const String& filename,int flags = IMREAD_COLOR);
Mat srcImage = imread("../海賊王2.jpg");
說明介紹
filename指的是文件的絕對路徑,imread對文件格式有一定的要求(bmp魁瞪、jpg穆律、jpeg等)
flags指的是打開文件的方式,通過修改flags的值导俘,直接將圖片進行處理峦耘。例如圖片設(shè)置為單通道IMREAD_GRAYSCALE,或者減少圖片的色值IMREAD_REDUCED_COLOR_2 (色值減少為之前的1/2)等旅薄;
注:將圖片色值減少為原來的1/2的原理是將每個通道上的值除以2取整辅髓,在2,相當(dāng)于去除掉奇數(shù)值。 偽代碼實現(xiàn): outputImage.at(i, j)[0] = outputImage.at(i, j)[0]/2*2; IMREAD_REDUCED_COLOR_4洛口; IMREAD_REDUCED_COLOR_8同理矫付,在損失一定色值的情況下提高存儲效率。
在接下來的筆記中大多數(shù)都是以imread函數(shù)創(chuàng)建Mat對象第焰。
上一節(jié)中對Mat及初始化進行了介紹买优,相信已經(jīng)迫不及待的想要將OpenCV的圖像處理運用到實例中去,在本節(jié)中將會介紹一個色值衰減的案例和畫線的案例挺举,作為切入點來感受一下神奇的OpenCV杀赢。雖然這些例子都相對簡單,但通過這些例子還是可以窺探出在使用OpenCV進行圖像處理時的基本操作步驟:
獲取Mat對象.
在Mat對象中根據(jù)需求尋找特征像素點.
對特征像素點進行修改豹悬,使之獲取新的Mat對象.
重復(fù)2葵陵、3操作獲取新的Mat對象,直到Mat對象符合最終結(jié)果.
輸出圖像.
在1.2節(jié)中對減少圖像色值有了一個簡單的介紹,但imread的flags色值的使用是有些的瞻佛,并不能任意的色值縮小為原來的1/3脱篙,1/5等,所以在本例中會實現(xiàn)將圖片的色值縮小為任意倍數(shù)伤柄。通過本例可以對像素的通道绊困,有更深刻的認(rèn)識。
int main() {
//【1】初始化Mat對象 Mat srcImage = imread("../1.jpg");
????imshow("【原始圖片】", srcImage);
????// 【2】按原始圖的參數(shù)規(guī)格來創(chuàng)建效果圖
????Mat dstImage(srcImage.cols, srcImage.rows, srcImage.type());
????// 參數(shù)1 傳入Mat對象适刀,參數(shù)2輸出的Mat對象秤朗,參數(shù)3將色值衰減為原來的1/32 ???
?????colorReduceDymatic(srcImage, dstImage, 32);
????// void imshow(const string& winname, InputArray image) winname要顯示的窗口名,image需要顯示的內(nèi)容
????imshow("【效果圖】", dstImage);
????// 按esc關(guān)閉窗口
????waitKey(0);
????}
????// 獲取到所有的像素點笔喉,因為該圖片是三通道的圖片取视,所以對每個像素的3個通道都需要進行像素值的衰減。通過這個方法將圖片的色值衰減為原來的1/32
????void colorReduceDymatic(Mat& inputImage, Mat& outputImage, int div) {
????????outputImage = inputImage.clone(); int rowNumber = outputImage.rows;
????????// 行
????????int colNumber = outputImage.cols;
????????// 列 // 存取彩色圖像像素
????????for(int i = 0; i < rowNumber; i++) {
????????????for(int j = 0; j < colNumber; j++) {
????????????????outputImage.at(i, j)[0] = outputImage.at(i, j)[0]/div*div ; //藍(lán)色通道
????????????????outputImage.at(i, j)[1] = outputImage.at(i, j)[1]/div*div ; //綠色通道
? ? ? ? ? ? ? ? outputImage.at(i, j)[2] = outputImage.at(i, j)[2]/div*div ; //紅色通道
????????}
????}
}
原始圖片
色值衰減之后的圖片
在POCT-IR中涉及到需要畫線常挚,以提取T線和C線的色值的內(nèi)容作谭。所以在本節(jié)中將介紹一個通過OpenCV在Mat上繪制圖像的例子。
OpenCV繪制圖像的過程與iOS中繪制圖像的過程也是一致的奄毡,調(diào)用了基本的line和 ellipse函數(shù)進行繪制折欠。
#define WINDOW_WIDTH 600
void drawEllipse(Mat img, double angle);
// 畫線 Point 基本的數(shù)據(jù)結(jié)構(gòu) 定義線的起點和終點
void drawLine(Mat img, Point state, Point end);
int main() {
? ? Mat ellipse = Mat::zeros(Size(WINDOW_WIDTH, WINDOW_WIDTH), CV_8UC3);
? ? Mat line = Mat::zeros(Size(WINDOW_WIDTH, WINDOW_WIDTH), CV_8UC3);
? ? drawEllipse(ellipse, 90);
? ? imshow("【橢圓】", ellipse);
? ? drawLine(line, Point(0, 0), Point(100, 200));
? ? drawLine(line, Point(100, 200), Point(300, 200));
? ? drawLine(line, Point(300, 200), Point(300, 0));
? ? drawLine(line, Point(300, 0), Point(0, 0));
? ? imshow("【畫線】", line);
? ? waitKey();
? ? return 0;
}
// 使用line函數(shù)進行繪制
void drawLine(Mat img, Point start, Point end) {
? ? int thickness = 2;
? ? int lineType = 8;
? ? line(img,
? ? ? ? start,
? ? ? ? end,
? ? ? ? // 色值
? ? ? ? Scalar(0, 255, 255),
? ? ? ? thickness,
? ? ? ? lineType);
}
// 實現(xiàn)繪制不同角度、相同尺寸的橢圓
void drawEllipse(Mat img, double angle) {
? ? int thickness = 2;
? ? int lineType = 8;
? ? ellipse(img,
? ? ? ? ? ? Point(WINDOW_WIDTH/2, WINDOW_WIDTH/2),
? ? ? ? ? ? Size(WINDOW_WIDTH/4, WINDOW_WIDTH/16),
? ? ? ? ? ? angle,
? ? ? ? ? ? 0,
? ? ? ? ? ? 360,
? ? ? ? ? ? Scalar(255, 129, 9),
? ? ? ? ? ? thickness,
? ? ? ? ? ? lineType
? ? );
}
在本篇文章中介紹的API只涵蓋了OpenCV3 API極小的一部分內(nèi)容吼过,通過對本節(jié)開始內(nèi)容的學(xué)習(xí)锐秦,可以對OpenCV3的使用模式有初步了解,更多相關(guān)的接口使用事例.(因案例內(nèi)容都是手寫盗忱,如若遇到注釋解釋不清酱床,內(nèi)容描述有誤的部分請及時指正,以便進行及時修改趟佃。)