1 繪圖和注釋
OpenCV提供了一些列繪制線、矩形、圓形等類似圖形的函數(shù)试浙,其中大部分都支持設(shè)置顏色、線寬寞蚌、抗鋸齒類型和亞像素對(duì)齊田巴。在設(shè)置顏色時(shí)通常使用cv::Scalar
實(shí)例,盡管我們大多數(shù)時(shí)候只使用其前三個(gè)元素挟秤。同樣按照慣例壹哺,在調(diào)用如cv::imread()
函數(shù)讀取圖像,imshow()
函數(shù)渲染彩色圖形時(shí)艘刚,OpenCV內(nèi)部使用BGR的顏色順序斗躏。另外如果使用宏CV_RGB(r, g, b)
生成一個(gè)cv::Scalar實(shí)例,則其值為{b, g, r, 0}
。當(dāng)然你自己實(shí)現(xiàn)的函數(shù)可以不用遵循這些規(guī)范啄糙,只需要知道和OpenCV提供的一些渲染函數(shù)交互時(shí)注意顏色數(shù)據(jù)的格式即可笛臣。
1.1 線條藝術(shù)和多邊形填充
繪制包含線條及包含線條的圖形時(shí)通常可以設(shè)置線寬thickness
和線條類型lineType
隧饼,這兩個(gè)參數(shù)都是整型沈堡,需要注意的是后者只能接受值4
、8
或者cv::LINE_AA
燕雁。對(duì)于如圓等封閉圖形诞丽,線寬thickness
可以設(shè)置為cv::FILLED
即-1
,這表示使用線條的顏色填充這個(gè)封閉圖形拐格。線條類型lineType
設(shè)置為4
僧免、8
或者cv::LINE_AA
時(shí)分別表示使用4鄰域連接、8鄰域連接還是使用抗鋸齒連接法捏浊。這三種線條連接的效果如下圖懂衩,4鄰域連接袁滥、8鄰域連接選項(xiàng)使用到了Bresenham算法驾凶,抗鋸齒選項(xiàng)使用到了高斯濾波器。另外寬直線總是會(huì)被繪制成圓頭的犁跪。
OpenCV提供的繪圖相關(guān)函數(shù)如下表胡岔,其中線的端點(diǎn)法希、圓心、矩形的角都被規(guī)定為整型數(shù)據(jù)靶瘸。但是這些函數(shù)支持設(shè)置亞像素對(duì)其參數(shù)shift
苫亦,該參數(shù)表示對(duì)整型參數(shù)應(yīng)用二進(jìn)制位移操作,可以理解為將原始值除以2的shift
次方怨咪,通過(guò)這種方式就可以將線的端點(diǎn)著觉、圓心、矩形的角調(diào)整為小數(shù)惊暴。如設(shè)置圓心在點(diǎn)(5, 5)饼丘,同時(shí)將shift參數(shù)設(shè)置為1,則實(shí)際點(diǎn)圓心位于(2.5, 2.5)辽话。
繪圖相關(guān)函數(shù) | 描述 |
---|---|
cv::circle() | 繪制圓形 |
cv::clipLine() | 判斷一條線是否在給定的矩形內(nèi) |
cv::ellipse() | 繪制橢圓肄鸽,可以傾斜,或者只包含部分圓弧 |
cv::ellipse2Poly() | 計(jì)算近似橢圓的多邊形 |
cv::fillConvexPoly() | 繪制封閉的簡(jiǎn)單的多邊形 |
cv::fillPoly() | 繪制封閉的任意多邊形 |
cv::line() | 繪制線 |
cv::rectangle() | 繪制矩形 |
cv::polyLines() | 繪制折線 |
cv::circle()
繪制圓形的函數(shù)原型如下油啤。
// img:需要繪制圓形的圖像
// 可以理解為一個(gè)畫布典徘,可以讀取一個(gè)圖片文件作為畫布
// center:圓心,單位像素
// radius:半徑益咬,單位像素
// color:繪制顏色逮诲,RGB格式
// thickness:線寬
// lineType:線型,線條繪制算法,支持4或者8
// shift:亞像素對(duì)其方式梅鹦,影響參數(shù)radius和center
void circle(cv::Mat& img, cv::Point center, int radius,
const cv::Scalar& color,
int thickness = 1, int lineType = 8, int shift = 0);
cv::clipLine()
判斷一條線是否在給定的矩形內(nèi)的函數(shù)原型如下裆甩。需要注意的是只有當(dāng)整個(gè)線段都位于指定的矩形外時(shí),下面兩個(gè)函數(shù)才返回false齐唆,否則返回true嗤栓。
// imgRect:需要判斷的矩形區(qū)域
// pt1:線段起點(diǎn)
// pt2:線段終點(diǎn)
bool clipLine(cv::Rect imgRect, cv::Point& pt1, cv::Point& pt2);
// imaSize:矩形的尺寸,默認(rèn)矩形左上角點(diǎn)為(0, 0)
bool clipLine(cv::Size imgSize, cv::Point& pt1, cv::Point& pt2);
cv::ellipse()
繪制橢圓函數(shù)原型如下箍邮。
// img:需要繪制圓形的圖像
// 可以理解為一個(gè)畫布茉帅,可以讀取一個(gè)圖片文件作為畫布
// center:橢圓中心,單位像素
// axes:其height和width屬性分別表示橢圓的長(zhǎng)軸和短軸距離
// angle:長(zhǎng)軸的傾角锭弊,單位為角度堪澎,從x軸正方向逆時(shí)針計(jì)算
// startAngle:橢圓弧的起始角度,單位為角度
// endAngle:橢圓弧的終止角度味滞,單位為角度
// color:繪制顏色樱蛤,BGR格式
// lineType:線型,只能傳4或者8
// thickness桃犬、shift見函數(shù)cv::circle()對(duì)應(yīng)注釋
bool ellipse(cv::Mat& img,
cv::Point center, cv::Size axes, double angle,
double startAngle, double endAngle,
const cv::Scalar& color, int thickness = 1, int lineType = 8,
int shift = 0);
// 通過(guò)指定邊界框的方式繪制橢圓
// rect:橢圓邊界框,決定了橢圓的大小和方向
bool ellipse(cv::Mat& img, const cv::RotatedRect& rect,
const cv::Scalar& color, int thickness = 1, int lineType = 8,
int shift = 0);
這兩種繪制橢圓的方法如下圖行楞,左圖是使用上面的第一個(gè)函數(shù)的示意圖攒暇,而右圖則是使用上面的第二個(gè)函數(shù)的示意圖。
cv::ellipse2Poly()
函數(shù)cv::ellipse()
內(nèi)部會(huì)調(diào)用到函數(shù)cv::ellipse2Poly()
計(jì)算一個(gè)近似橢圓弧的多邊形子房,并保存該多邊形的頂點(diǎn)形用,此外你也可以直接調(diào)用該函數(shù)。通過(guò)指定和函數(shù)cv::ellipse()
中含義相似的center
证杭、axes
田度、angle
、startAngle
解愤、endAngle
參數(shù)后镇饺,再指定沒兩個(gè)相鄰頂點(diǎn)的的角度參數(shù)后,該函數(shù)會(huì)講一個(gè)近似表示該圓弧的多邊形頂點(diǎn)寫入到向量參數(shù)pts
中送讲。函數(shù)原型如下奸笤。
// center、axes哼鬓、angle监右、startAngle、endAngle含義同函數(shù)cv::ellipse()
// delta:兩個(gè)相鄰頂點(diǎn)之間的角度
// pts:保存近似橢圓多邊形的頂點(diǎn)向量
void ellipse2Poly(cv::Point center, cv::Size axes, double angle,
double startAngle, double endAngle, int delta,
vector<cv::Point>& pts);
cv::fillConvexPoly()
該函數(shù)較于接下來(lái)即將講到的繪制封閉多邊形的函數(shù)而言使用了更簡(jiǎn)單的算法异希,但是它不能處理有相交邊的多邊形健盒。參數(shù)pts
保存的頂點(diǎn)會(huì)作為序列將兩點(diǎn)連接成一條線,同時(shí)也會(huì)將該序列點(diǎn)第一個(gè)點(diǎn)和最后一個(gè)點(diǎn)相連接形成一個(gè)封閉的多邊形。其函數(shù)原型如下扣癣。
// pts:組成封閉多邊形的頂點(diǎn)
// npts:頂點(diǎn)個(gè)數(shù)
// img惰帽、shift含義同函數(shù)cv::circle()
// color:繪制顏色,BGR格式
// lineType:線型搏色,只能傳4或者8
void fillConvexPoly(cv::Mat& img, const cv::Point* pts, int npts,
const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::fillPoly()
繪制任意多邊形的函數(shù)原型如下善茎,和函數(shù)cv::fillConvexPoly()
不同,該函數(shù)能夠處理有相交邊的多邊形频轿。另外該函數(shù)繪制的是一個(gè)封閉的多邊形垂涯,參數(shù)pts
中的第一個(gè)和最后一個(gè)頂點(diǎn)將會(huì)被連接。
// img航邢、shift含義同函數(shù)cv::circle()
// pts:c語(yǔ)言風(fēng)格二維數(shù)組耕赘,保存所有頂點(diǎn),pts[i][j]表示第i個(gè)輪廓的第j個(gè)頂點(diǎn)
// 其尺寸=ncontours??npts
// npts:c語(yǔ)言風(fēng)格數(shù)組膳殷,puts[i]表示第i個(gè)輪廓的頂點(diǎn)個(gè)數(shù)
// ncontours:多邊形的輪廓數(shù)
// color:繪制顏色操骡,BGR格式
// lineType:線型,只能傳入4或8
// offset:每個(gè)頂點(diǎn)的像素偏移量
void fillPoly(cv::Mat& img,
const cv::Point* pts, int npts, int ncontours,
const cv::Scalar& color, int lineType = 8,
int shift = 0, cv::Point offset = Point());
cv::line()
繪制直線的函數(shù)原型如下赚窃,超出圖像范圍的部分將會(huì)被裁減册招。
// img、shift含義同函數(shù)cv::circle()
// pt1:直線起點(diǎn)
// pt2:直線終點(diǎn)
// color:繪制顏色勒极,BGR格式
// lineType:線型是掰,只能傳入4或8
void line(cv::Mat& img, cv::Point pt1, cv::Point pt2,
const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::rectangle()
繪制矩形的函數(shù)原型如下。
// img辱匿、shift含義同函數(shù)cv::circle()
// pt1:矩形的左上角頂點(diǎn)
// pt2:矩形的右下角頂點(diǎn)
// color:繪制顏色键痛,BGR格式
// lineType:線型,只能傳入4或8
void rectangle(cv::Mat& img, cv::Point pt1, cv::Point pt2,
const cv::Scalar& color, int lineType = 8, int shift = 0);
// r:需要繪制的矩形
void rectangle(cv::Mat& img, cv::Rect r,
const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::polyLines()
繪制任意多邊形的函數(shù)原型如下匾七,它可以處理邊相交的多邊形絮短。
// img、shift含義同函數(shù)cv::circle()
// pts:c語(yǔ)言風(fēng)格二維數(shù)組昨忆,保存所有頂點(diǎn)丁频,pts[i][j]表示第i個(gè)輪廓的第j個(gè)頂點(diǎn)
// 其尺寸=ncontours??npts
// npts:c語(yǔ)言風(fēng)格數(shù)組,puts[i]表示第i個(gè)輪廓的頂點(diǎn)個(gè)數(shù)
// ncontours:多邊形的輪廓數(shù)
// isClosed:多邊形是否封閉邑贴,即是否將pts的第一個(gè)和最后一個(gè)頂點(diǎn)相連
// color:繪制顏色限府,BGR格式
// lineType:線型,只能傳入4或8
void polyLines(cv::Mat& img,
const cv::Point* pts, int npts, int ncontours, bool isClosed,
const cv::Scalar& color, int lineType = 8, int shift = 0);
cv::LineIterator
線迭代器實(shí)例cv::LineIterator
可以從線序列中提取每個(gè)像素痢缎,其構(gòu)造函數(shù)如下胁勺。線迭代器是OpenCV中的第一個(gè)函數(shù)對(duì)象(functor)的例子,在下篇文章中將會(huì)介紹更多類似的例子独旷。
// img:被繪制的圖像署穗,可以從圖片文件讀取
// pt1:線段起點(diǎn)
// pt2:線段終點(diǎn)
// lineType:線型寥裂,只能傳入4或8
// leftToRight:像素提取方向是否從左至右
LineIterator::LineIterator(cv::Mat& img, cv::Point pt1, cv::Point pt2,
int lineType = 8, bool leftToRight = false);
線迭代器初始化成功后,其整型成員屬性cv::LineIterator::count
將會(huì)記錄這條線上的像素個(gè)數(shù)案疲。重載的運(yùn)算符??會(huì)返回一個(gè)uchar??
的指針封恰,它指向了當(dāng)前像素?cái)?shù)據(jù)的地址,這里可以簡(jiǎn)單將線迭代器理解為指向指針的一個(gè)指針變量褐啡。通過(guò)該運(yùn)算符訪問的當(dāng)前像素從線段的一端開始诺舔,通過(guò)重載后的運(yùn)算符++
逐像素向線的另一端移動(dòng)。實(shí)際的遍歷過(guò)程是通過(guò)前面提到的Bresenham算法完成的备畦。
使用該函數(shù)獲取到的像素可能是多種通道低飒,也可能是任意位深度的,但是返回的像素?cái)?shù)據(jù)地址都是uchar??
的指針懂盐,在使用時(shí)需要將其強(qiáng)轉(zhuǎn)為正確的數(shù)據(jù)類型褥赊。例如在訪問由3通道32位浮點(diǎn)型數(shù)據(jù)表示的圖像時(shí),如果線迭代器變量為iter
莉恼,則獲取像素?cái)?shù)據(jù)的方式應(yīng)該是(Vec3f??)??iter
拌喉。
1.2 文字和字體
繪圖的另外一種方式就是繪制文字,下表列出了OpenCV提供的文字繪制相關(guān)函數(shù)俐银。
文字繪制相關(guān)函數(shù) | 描述 |
---|---|
cv::putText() | 在圖上繪制指定的文字 |
cv::getTextSize() | 獲取字符串的寬和高 |
cv::putText()
文字繪制函數(shù)原型如下尿背。
// img:文字繪制的圖片目標(biāo),可以從圖片文件讀取或者直接創(chuàng)建
// tex:需要繪制的文字捶惜,通常使用cv::format創(chuàng)建
// origin:文字框的定位頂點(diǎn)
// fontFace:文字字體田藐,如cv::FONT_HERSHEY_PLAIN,具體取值見下文
// fontScale:文字縮放比例售躁,一個(gè)乘數(shù)坞淮,和字體指定的基礎(chǔ)尺寸相乘得到文字真實(shí)大小
// color:文字顏色茴晋,RGB格式
// thickness:線條厚度
// lineType:線型陪捷,只能傳入4或8
// bottomLeftOrigin:定位頂點(diǎn)位置
// true表示定位頂點(diǎn)origin為文本框左下角頂點(diǎn)
// false表示定位頂點(diǎn)origin為文本框左上角頂點(diǎn)
void cv::putText(cv::Mat& img, const string& text,
cv::Point origin, int fontFace, double fontScale,
cv::Scalar color, int thickness = 1, int lineType = 8,
bool bottomLeftOrigin = false);
參數(shù)fontFace
可選子字體如下表。
參數(shù)fontFace取值 | 描述 |
---|---|
cv::FONT_HERSHEY_SIMPLEX | 標(biāo)準(zhǔn)大小無(wú)襯線字體 Normal size sans-serif |
cv::FONT_HERSHEY_PLAIN | 小號(hào)無(wú)襯線字體 Small size sans-serif |
cv::FONT_HERSHEY_DUPLEX Normal | 普通大小無(wú)襯線字體诺擅,比cv::FONT_HERSHEY_?SIM?PLEX更復(fù)雜 |
cv::FONT_HERSHEY_COMPLEX | 普通大小無(wú)襯線字體市袖,比cv::FONT_HERSHEY_DUPLEX更復(fù)雜 |
cv::FONT_HERSHEY_TRIPLEX | 普通大小無(wú)襯線字體,比cv::FONT_HERSHEY_COMPLEX更復(fù)雜 |
cv::FONT_HERSHEY_COMPLEX_SMALL | 小號(hào)版本的cv::FONT_HERSHEY_COMPLEX |
cv::FONT_HERSHEY_SCRIPT_SIMPLEX | 手寫體 |
cv::FONT_HERSHEY_SCRIPT_COMPLEX | 比cv::FONT_HERSHEY_SCRIPT_SIMPLEX更復(fù)雜的變體 |
上表的所有選項(xiàng)都可以使用邏輯與運(yùn)算符和cv::FONT_HERSHEY_ITALIC
組合從而渲染斜體文字烁涌。每個(gè)字體都有一個(gè)基礎(chǔ)的大小苍碟,當(dāng)函數(shù)cv::putText()
中參數(shù)fontScale
的值不等于1
時(shí),最終繪制的文字大小等于字體的基本大小和這個(gè)縮放系數(shù)的乘積撮执。下圖依次展示了上表每種字體的示例微峰。
cv::getTextSize()
該函數(shù)返回在指定策略下被繪制的文字最終顯示的大小,其原型如下抒钱。文本基線值的時(shí)文本排列時(shí)的基準(zhǔn)線蜓肆,如由字符a和b構(gòu)成的文本颜凯,其基線就是緊貼字符底部的一條直線,而對(duì)于由y和g構(gòu)成的文本仗扬,文字是掛在基線上的症概,即有部分內(nèi)容是位于基線下方。
// text早芭、origin彼城、fontFace、fontScale退个、thickness含義合函數(shù)cv::putText()一致
// baseLine:文本基線相對(duì)于文本最低點(diǎn)的y坐標(biāo)
cv::Size cv::getTextSize(const string& text, cv::Point origin,
int fontFace, double fontScale,
int thickness, int* baseLine);
2 函數(shù)對(duì)象
隨著OpenCV庫(kù)的發(fā)展募壕,漸漸出現(xiàn)了很多封裝復(fù)雜功能的對(duì)象,這些功能不是單一函數(shù)能夠完成的帜乞,但是將它們實(shí)現(xiàn)為一組函數(shù)又會(huì)使得OpenCV的整個(gè)函數(shù)庫(kù)變得太雜亂司抱。因此 OpenCV引入了函數(shù)對(duì)象(fuction objects),它是實(shí)現(xiàn)這些復(fù)雜功能的封裝黎烈∠澳可以使用該函數(shù)提供服務(wù)時(shí)必要數(shù)據(jù)和配置創(chuàng)建一個(gè)函數(shù)對(duì)象,該對(duì)象內(nèi)部會(huì)維護(hù)自己的狀態(tài)信息照棋,然后在需要使用這個(gè)服務(wù)時(shí)通過(guò)其成員函數(shù)或者將函數(shù)對(duì)象自身作為函數(shù)調(diào)用资溃,后者通常是通過(guò)重載運(yùn)算符()
來(lái)實(shí)現(xiàn)的。
2.1 主成分分析
主成分分析(Principal component analysis)是一種分析多維樣本分布并從中提取包含最多信息量維度子集的方法烈炭。主成分分析計(jì)算出的維度不一定和分布最初指定的基礎(chǔ)維度相同溶锭,實(shí)際上它的一個(gè)最重要的作用就是能夠根據(jù)維度的重要性重新排序得到新的基礎(chǔ)維度,可能你已經(jīng)看出來(lái)這里已經(jīng)涉及到機(jī)器學(xué)習(xí)的概念了符隙,在后面的內(nèi)容中將會(huì)詳細(xì)介紹趴捅。這些基向量可以被證明是整個(gè)分布的協(xié)方差矩陣的特征向量,相應(yīng)的特征值就是對(duì)應(yīng)維度的分布范圍霹疫。主成分分析函數(shù)對(duì)象cv::PCA
的工作原理如下圖拱绑。
在a圖中,使用正態(tài)分布逼近原始數(shù)據(jù)丽蝎,原始數(shù)據(jù)中的某個(gè)樣本會(huì)被投影到由這個(gè)正態(tài)分布的協(xié)方差矩陣特征向量定義的空間中猎拨。對(duì)a圖中的樣本調(diào)用函數(shù)cv::PCA::project()
在被投影到b圖空間中的相應(yīng)位置后,再通過(guò)KLT投影到由最有用的特征向量子集定義的降維空間中屠阻,即c圖中的白色方塊红省。該樣本還可以通過(guò)調(diào)用函數(shù)cv::PCA::backProject()
投影回其原始空間中,即a圖中的黑色方塊国觉。
給定一個(gè)樣本集吧恃,PCA對(duì)象能夠計(jì)算并且記憶一個(gè)新的基,這個(gè)基和該分布的協(xié)方差矩陣相關(guān)麻诀,它最大的好處就是和大的特征值對(duì)應(yīng)的基向量能夠攜帶分布中樣本的主要信息痕寓。因此缸逃,在不損失太多精確度的情況下,我們可以丟棄包含少量信息的維度厂抽,這種降維方式被稱為KLT(Karhunen-Loeve Transform)需频。一旦你載入了一個(gè)樣本分布,主要的成分就將被計(jì)算筷凤,這個(gè)信息可以用來(lái)做很多事情昭殉,如對(duì)新向量應(yīng)用KLT變換。這種降PCA功能實(shí)現(xiàn)為一個(gè)函數(shù)對(duì)象的方法方式藐守,使得它能夠記憶分布的必要信息挪丢,并在需要的時(shí)候提供變換向量的“服務(wù)”。
cv::PCA::PCA()
類PCA的構(gòu)造函數(shù)如下卢厂,其中默認(rèn)構(gòu)造函數(shù)PCA::PCA()
僅創(chuàng)建一個(gè)實(shí)例并初始化一個(gè)空結(jié)構(gòu)乾蓬。而第二個(gè)構(gòu)造函數(shù)除了執(zhí)行默認(rèn)的構(gòu)造函數(shù)外,還使用提供的參數(shù)調(diào)用接下來(lái)即將講到的被重載的運(yùn)算符()
慎恒。
PCA::PCA();
// data:原始樣本數(shù)據(jù)任内,由行向量或者列向量組成的二維數(shù)組
// mean:均值矩陣,其尺寸為1??n或者n??1融柬,表示在對(duì)應(yīng)維度的樣本均值// flags:原始樣本數(shù)據(jù)結(jié)構(gòu)標(biāo)志
// cv::PCA_DATA_AS_ROW表示其由列向量組成
// cv::PCA_DATA_AS_COL表示其由行向量組成
// 每個(gè)向量表示一個(gè)樣本在各個(gè)維度的值
// maxComponents:主成分分析保留的最大維度死嗦,0表示保留所有維度
PCA::PCA(cv::InputArray data, cv::InputArray mean,
int flags, int maxComponents = 0);
cv::PCA::operator()()
PCA實(shí)例重載的運(yùn)算符()
及其參數(shù)如下,該運(yùn)算符會(huì)根據(jù)特征向量建立內(nèi)部樣本的分布模型粒氧。每次調(diào)用該重載的運(yùn)算符都會(huì)重新計(jì)算其內(nèi)部的特征向量和特征值越除,也就是說(shuō)如果你需要重新分析一組新的樣本,可以重用PCA對(duì)象再調(diào)用該重載運(yùn)算符外盯,無(wú)需再重新實(shí)例化一個(gè)新的對(duì)象摘盆。
// 參數(shù)含義和類PCA的構(gòu)造函數(shù)中一致
PCA::operator()(cv::InputArray data, cv::InputArray mean,
int flags, int maxComponents = 0);
cv::PCA::project()
在基于特征向量的分布模型建立后,就可以執(zhí)行如將一些向量集投影到基于主成分分析的新的基向量上的KLT變換等操作饱苟。執(zhí)行KLT變換等兩個(gè)函數(shù)原型如下孩擂,其中將投影結(jié)果返回到函數(shù)的一個(gè)優(yōu)勢(shì)是能夠直接在矩陣表達(dá)式中使用。
// 返回值:投影結(jié)果掷空,二維矩陣肋殴,其向量組成方式及數(shù)量應(yīng)該和這個(gè)PCA對(duì)象載入原始樣本分布時(shí)的數(shù)據(jù)結(jié)構(gòu)
// 相同囤锉,但是其維度應(yīng)當(dāng)和載入原始樣本分布時(shí)傳入的參數(shù)maxComponents相同
// vec:需要投影的向量集坦弟,二維矩陣
// 由列向量或者行向量組成,其組成方式和每個(gè)向量的維度應(yīng)該和這個(gè)PCA對(duì)象載入原始樣本分布時(shí)的
// 數(shù)據(jù)結(jié)構(gòu)相同
cv::Mat PCA::project(cv::InputArray vec) const;
// result:投影結(jié)果官地,是一個(gè)二維矩陣
void PCA::project(cv::InputArray vec, cv::OutputArray result) const;
cv::PCA::backProject()
該函數(shù)是函數(shù)cv::PCA::project()
的逆運(yùn)算酿傍,他們對(duì)輸入和輸出矩陣的向量組成方式,向量維度及數(shù)量要求類似驱入。
// 返回值:樣本在原始分布空間的取值赤炒,二維矩陣氯析,其向量組成方式、數(shù)量及維度應(yīng)該和這個(gè)PCA對(duì)象載入
// 原始樣本分布時(shí)的數(shù)據(jù)結(jié)構(gòu)相同
// vec:經(jīng)過(guò)函數(shù)cv::PCA::project()投影到主成分空間中的樣本集合莺褒,二維矩陣
// 由列向量或者行向量組成掩缓,其組成方式和每個(gè)向量的維度應(yīng)該和這個(gè)PCA對(duì)象載入原始樣本分布
// 時(shí)的數(shù)據(jù)結(jié)構(gòu)相同,其維度應(yīng)當(dāng)和載入原始樣本分布時(shí)傳入的參數(shù)maxComponents相同
cv::Mat PCA::backProject(cv::InputArray vec} const;
void PCA::backProject(cv::InputArray vec, cv::OutputArray result) const;
需要注意的是如果在載入原始數(shù)據(jù)分布時(shí)設(shè)定不保留所有的維度遵岩,則對(duì)于原始樣本向量x你辣,經(jīng)過(guò)KLT投影得到向量x1,再將x1逆投影得到x2尘执,則x和x2是有差異的舍哄,當(dāng)然這種差異會(huì)很小,即使在載入原始數(shù)據(jù)分布時(shí)丟棄了很多維度誊锭,這也是使用PCA的意義所在表悬。
2.2 奇異值分解
奇異值分解(Singular Value Decomposition)函數(shù)對(duì)象本質(zhì)上是一個(gè)處理非方陣的、病態(tài)的(不適定的)或者在解決欠定線性系統(tǒng)中遇到的不良矩陣的工具丧靡。在數(shù)學(xué)上奇異值分解是對(duì)尺寸為m??n矩陣A的分解蟆沫。其分解形式如下。
其中W為m??n的對(duì)角矩陣温治,U和V是m??m和n??n的單位矩陣饥追。這里對(duì)角矩陣只行和列下標(biāo)不相等的元素值都為0。
cv::SVD()
類SVD也包含兩個(gè)構(gòu)造函數(shù)罐盔,其中默認(rèn)構(gòu)造函數(shù)不包含任何參數(shù)但绕,僅僅創(chuàng)建了SVD對(duì)象,初始化了一個(gè)空的結(jié)構(gòu)惶看。第二個(gè)構(gòu)造函數(shù)在默認(rèn)構(gòu)造函數(shù)的基礎(chǔ)上還使用傳入的參數(shù)調(diào)用了接下來(lái)即將講到的重載運(yùn)算符()
捏顺,它們函數(shù)原型如下。參數(shù)flags表示奇異值分解策略纬黎,其取值可以是cv::SVD::MODIFY_A
幅骄、cv::SVD::NO::UV
和cv::SVD::FULL_UV
,其中后兩個(gè)值是互斥的本今,但是它們都能通過(guò)邏輯與符號(hào)和第一個(gè)值組合拆座。cv::SVD::MODIFY_A
表示在計(jì)算過(guò)程中可以修改矩陣A的值,從而加速計(jì)算并節(jié)約內(nèi)存冠息,在處理輸入矩陣尺寸非常大時(shí)挪凑,這個(gè)選項(xiàng)很有用。cv::SVD::NO::UV
表示不用顯示的計(jì)算出矩陣U
和Vt
逛艰,cv::SVD::FULL_UV
則表示不僅要計(jì)算出這兩個(gè)矩陣的值躏碳,而且它們還應(yīng)該表示為全尺寸的正交方陣。
SVD::SVD();
// A:需要處理的矩陣
// flags:奇異值分解策略
SVD::SVD(cv::InputArray A, int flags = 0);
cv::SVD::operator()()
使用重載運(yùn)算符載入矩陣后A
散怖,會(huì)計(jì)算出上面講解奇異值分解公式時(shí)引入的矩陣U
和V
t菇绵,以及構(gòu)成對(duì)角矩陣W的一組奇異值肄渗。
// 參數(shù)含義同默認(rèn)的構(gòu)造函數(shù)一致
SVD::& SVD::operator() (cv::InputArray A, int flags = 0);
cv::SVD::compute()
該函數(shù)是計(jì)算奇異值分解的另外一種方式,和前面的函數(shù)不同的是這種方式會(huì)將矩陣W
咬最、U
和Vt
寫入到用戶指定的矩陣中翎嫡。
// A:需要處理的矩陣
// W、U永乌、Vt:見奇異值分解等式
// flags:見構(gòu)造函數(shù)中的定義
void SVD::compute(cv::InputArray A,
cv::OutputArray W, cv::OutputArray U, cv::OutputArray Vt,
int flags = 0);
cv::SVD::solveZ()
該函數(shù)原型如下钝的,給定一個(gè)欠定線性系統(tǒng),函數(shù)cv::SVD::solveZ()
會(huì)嘗試尋找一個(gè)單位長(zhǎng)度解使得A??X=0
成立铆遭,其中X
向量為線性系統(tǒng)的解硝桩。并將結(jié)果寫入到矩陣Z
中。因?yàn)榫€性系統(tǒng)是奇異的枚荣,因此可能存在無(wú)窮多個(gè)解碗脊,也可能無(wú)無(wú)解。如果存在解橄妆,將會(huì)將結(jié)果寫入到矩陣Z
中衙伶,如果不存在解,則會(huì)找到一個(gè)使得A??X
最小的解害碾。
// A:待分解的奇異線性系統(tǒng)矩陣
// z:一個(gè)可能的單位長(zhǎng)度解
void SVD::solveZ(cv::InputArray A, cv::OutputArray z);
cv::SVD::backSubst()
求解線性系統(tǒng)的另一類函數(shù)具有兩種形式矢劲,第一種形式函數(shù)原型如下,需要使用前面已經(jīng)介紹過(guò)的方法先求出矩陣U
慌随、W
和Vt
芬沉。
// b:線性系統(tǒng)的右側(cè)常數(shù)矩陣
// x:線性系統(tǒng)的解
void SVD::backSubst(cv::InputArray b, cv::OutputArray x);
該函數(shù)的計(jì)算公式如下。
第二種形式的函數(shù)原型如下阁猜,它需要傳入矩陣U
丸逸、W
和Vt
。
// W:奇異值對(duì)角線矩陣
// U:左側(cè)奇異向量
// Vt:右側(cè)奇異向量
// b:線性系統(tǒng)的右側(cè)常數(shù)矩陣
// x:線性系統(tǒng)的解
void SVD::backSubst(cv::InputArray W, cv::InputArray U,
cv::InputArray Vt, cv::InputArray b,
cv::OutputArray x);
該函數(shù)的計(jì)算公式如下剃袍。
這兩種函數(shù)在求解時(shí)如果處理的是超定線性系統(tǒng)黄刚,則會(huì)使用最小二乘法求出一個(gè)最接近的解,如果處理的是恰定方程組則會(huì)直接求出線性系統(tǒng)的解民效。實(shí)際上憔维,很少情況會(huì)直接使用到函數(shù)cv::SVD::backSubst()
。因?yàn)槭褂煤瘮?shù)cv::solve()
并將參數(shù)flag
設(shè)置為cv::DECOMP_SVD
能達(dá)到同樣的目的畏邢,而且這種方式更簡(jiǎn)單业扒。只有在很少的情況下,如果需要使用相同的系數(shù)矩陣即左手側(cè)處理不同的線性系統(tǒng)時(shí)才需要優(yōu)先調(diào)用 函數(shù)cv::SVD::backSubst()
棵红,相反如果使用不同的常數(shù)矩陣即右手側(cè)處理相同線性系統(tǒng)時(shí)最好調(diào)用函數(shù)cv::solve()
凶赁。
2.3 隨機(jī)數(shù)生成器
隨機(jī)數(shù)生成器(Random Number Generator)函數(shù)對(duì)象cv::RNG
持有生成隨機(jī)數(shù)的偽隨機(jī)數(shù)隊(duì)列的狀態(tài)咧栗,這樣可以更方便的得到多個(gè)偽隨機(jī)數(shù)流逆甜。在大型系統(tǒng)中虱肄,在不同模塊使用不同不同的隨機(jī)數(shù)流是一個(gè)很好的習(xí)慣,這樣當(dāng)某個(gè)模塊移除后不會(huì)改變其他模塊內(nèi)部的隨機(jī)數(shù)流交煞。隨機(jī)數(shù)生成器創(chuàng)建后咏窿,可以提供均一分布或者正態(tài)分布的隨機(jī)數(shù)。生成均一分布隨機(jī)數(shù)使用了Multiply with Carry(MWC)算法素征,生成正態(tài)分布隨機(jī)數(shù)使用了Ziggurat算法集嵌。
cv::theRNG()
該函數(shù)返回當(dāng)前線程的默認(rèn)的隨機(jī)數(shù)生成器。其原型如下御毅。
cv::RNG& theRNG(void);
OpenCV會(huì)默認(rèn)為每個(gè)正在執(zhí)行的線程創(chuàng)建一個(gè)隨機(jī)數(shù)生成器根欧,你也可以通過(guò)調(diào)用函數(shù)cv::randu()
或者cv::randn()
隱式的使用這個(gè)隨機(jī)數(shù)生成器。如果你只是簡(jiǎn)單的創(chuàng)建一個(gè)隨機(jī)數(shù)或者是初始化一個(gè)矩陣端蛆,使用這些方法更為便捷凤粗。但是如果有一個(gè)循環(huán)需要生成大量的隨機(jī)數(shù),最好持有一個(gè)隨機(jī)數(shù)生成器 今豆。這個(gè)函數(shù)獲取的是默認(rèn)的隨機(jī)數(shù)生成器嫌拣,當(dāng)然你也可以創(chuàng)建一個(gè)新的實(shí)例,然后通過(guò)重載的運(yùn)算符()獲取隨機(jī)數(shù)呆躲。
cv::RNG()
隨機(jī)數(shù)生成器的構(gòu)造函數(shù)原型如下异逐。
// 使用默認(rèn)的隨機(jī)種子創(chuàng)建隨機(jī)數(shù)生成器,通常為2^32 - 1
cv::RNG::RNG( void );
// state:隨機(jī)種子插掂,傳入0時(shí)使用默認(rèn)的隨機(jī)種子
cv::RNG::RNG( uint64 state );
cv::RNG::operator T()
重載的類型轉(zhuǎn)換運(yùn)算符T()
是從某個(gè)隨機(jī)數(shù)生成器生成指定類型的隨機(jī)數(shù)方法集灰瞻,其中T表示的是數(shù)據(jù)類型。另外使用(T)
風(fēng)格的類型轉(zhuǎn)換符也是有效的辅甥。這些重載的類型轉(zhuǎn)換運(yùn)算符如下箩祥。
cv::RNG::operator uchar();
cv::RNG::operator schar();
cv::RNG::operator ushort();
cv::RNG::operator short int();
cv::RNG::operator int();
cv::RNG::operator unsigned();
cv::RNG::operator float();
cv::RNG::operator double();
生成了不同類型的隨機(jī)數(shù)代碼如下。
cv::RNG rng = cv::theRNG();
cout << "An integer: " << (int)rng << endl;
cout << "Another integer: " << int(rng) << endl;
cout << "A float: " << (float)rng << endl;
cout << "Another float: " << float(rng) << endl;
生成整型隨機(jī)數(shù)時(shí)取值空間同整型數(shù)的取值空間一致肆氓,而在生成浮點(diǎn)型隨機(jī)數(shù)時(shí)袍祖,取值空間是[0.0,1.0]
谢揪。
cv::RNG::operator()
通過(guò)重載的運(yùn)算符()獲取隨機(jī)數(shù)會(huì)更加便利蕉陋,本質(zhì)上,使用該運(yùn)算符等同于(unsigned int)rng
拨扶,此外還支持設(shè)置一個(gè)參數(shù)N
凳鬓,通過(guò)rng(N)
理由獲取取值取決為[0, N-1]
的均勻分布隨機(jī)數(shù)。
unsigned int cv::RNG::operator()();
unsigned int cv::RNG::operator()(unsigned int N);
cv::RNG::uniform()
生成指定區(qū)間均勻分布隨機(jī)數(shù)的函數(shù)原型如下患民。
// 取值區(qū)間為[a, b-1]
int cv::RNG::uniform(int a, int b);
// 取值區(qū)間為[a, b)
float cv::RNG::uniform(float a, float b);
// 取值區(qū)間為[a, b)
double cv::RNG::uniform(double a, double b);
C++編譯器在處理函數(shù)名相同的函數(shù)時(shí)只考慮參數(shù)類型缩举,而不會(huì)考慮返回值類型,因此對(duì)于代碼float x = rng.uniform(0, 1
),得到的結(jié)果只會(huì)是0.0f
仅孩,因?yàn)榇a段中參數(shù)0和1都被識(shí)別為整型數(shù)據(jù)托猩。如果想要得到浮點(diǎn)型數(shù)據(jù),則必須顯示告訴編譯器辽慕,則代碼應(yīng)該寫成float x = rng.uniform(0.f, 1.f)
京腥,如果想要得到雙精度數(shù)據(jù),則代碼應(yīng)該寫成double x = rng.uniform(0., 1.)
溅蛉,當(dāng)然對(duì)參數(shù)使用顯示類型轉(zhuǎn)換也是可以的公浪。
cv::RNG::gaussian()
生成期望為0,標(biāo)準(zhǔn)差為sigma
的正態(tài)分布隨機(jī)數(shù)的函數(shù)原型如下船侧。
// sigma:正態(tài)分布的標(biāo)準(zhǔn)差
double cv::RNG::gaussian(double sigma);
cv::RNG::fill()
該函數(shù)可以使用指定分布的隨機(jī)數(shù)填充一個(gè)最多四通道的矩陣欠气,其原型如下。矩陣a和b的含義取決于使用的分布類型镜撩,但是它們的尺寸都是n??1
或者1??n
晃琳,其中n等于矩陣mat
的通道數(shù)。在使用隨機(jī)數(shù)填充矩陣是琐鲁,矩陣的每個(gè)元素的每個(gè)通道都使用矩陣a
和矩陣b
中對(duì)應(yīng)通道的特征元素決定卫旱。
// mat:需要被填充的矩陣
// distType:隨機(jī)數(shù)分布的類型,正態(tài)分布或者均勻分布
// a:對(duì)于正態(tài)分布围段,表示的是期望顾翼,對(duì)于均勻分布表示的是最小值
// b:對(duì)于正態(tài)分布,表示的是方差奈泪,對(duì)于均勻分布表示的是最大值
void cv::RNG::fill(InputOutputArray mat, int distType,
InputArray a, InputArray b);
3 小結(jié)
本章首先有關(guān)圖形和文字繪制的函數(shù)适贸,它們都是應(yīng)用在矩陣對(duì)象cv::Mat
上。接著又介紹了函數(shù)對(duì)象的概念涝桅,如負(fù)責(zé)主成分分析的PCA拜姿,負(fù)責(zé)奇異值矩陣求解線性系統(tǒng)的SVD,負(fù)責(zé)生成隨機(jī)值的RNG對(duì)象冯遂。隨著對(duì)OpenCV學(xué)習(xí)的深入蕊肥,將會(huì)遇到更多使用這一概念的現(xiàn)代插件。