Introduction
OpenCV擦俐,或稱開源計(jì)算機(jī)視覺庫目派,是一個(gè)基于BSD開源協(xié)議憾股,包含許多種計(jì)算機(jī)視覺算法的開源庫。此文檔所描述的OpenCV 2.x API實(shí)質(zhì)上是C++的API庵佣,與基于C語言的OpenCV 1.x不同(C語言的API已被廢棄,自O(shè)penCV 2.4之后的發(fā)行版便再也沒用C編譯器做過測(cè)試)汛兜。
OpenCV擁有模塊化結(jié)構(gòu)巴粪,也即軟件包包括了許多動(dòng)態(tài)或者靜態(tài)庫。例如以下是一些可用模組粥谬。
- 核心功能(core):一個(gè)緊湊的模塊肛根,定義了基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),包括稠密多維數(shù)組
Mat
以及一些可被其它模塊用到的函數(shù) - 圖像處理(imgproc):一個(gè)圖像處理模塊漏策,包含線性和非線性濾波器派哲,幾何圖像變換(大小改變
(resize)
,仿射(affine)
以及扭曲透視,基于泛型表的重新映射)掺喻,色彩空間轉(zhuǎn)換芭届,直方圖等等储矩。 - 視頻分析(video):視頻分析包括了運(yùn)動(dòng)估計(jì),背景減法褂乍,對(duì)象追蹤等散發(fā)
- 相機(jī)標(biāo)定及三維重建(calib3d):基本的多視圖幾何算法持隧,單目雙目相機(jī)標(biāo)定,對(duì)象姿態(tài)估計(jì)逃片,雙目立體匹配算法等屡拨,還包括一些用于三維重建的元素。
- 二維特征框架(features2d):顯著特征檢測(cè)器题诵,描述符以及描述符匹配器
- 對(duì)象檢測(cè)(objdetect):對(duì)象檢測(cè)以及預(yù)先定義好的類的實(shí)例(比如臉洁仗、眼睛、被子性锭、行人赠潦、車輛等等)
- 高級(jí)用戶界面和媒體流I/O操作(highgui):簡(jiǎn)單易用的一個(gè)基本UI界面
- 視頻輸入/輸出(videoio):簡(jiǎn)單易用的視頻捕捉和解碼模塊
- ……以及一些有用的其它模塊,比如flann和Google test wrappers草冈,Python binding等她奥。
接下來的文檔會(huì)詳細(xì)介紹每個(gè)模塊的功能及用法。但首先請(qǐng)熟悉本庫中最基礎(chǔ)的API概念及其用法怎棱。
API概念(API Concept)
cv命名空間(cv Namespace)
OpenCV所有的類和函數(shù)都放置在cv這個(gè)命名空間里的哩俭,因此想要在你的代碼中使用OpenCV的功能,請(qǐng)使用cv::
標(biāo)示符或者直接使用:using namespace cv;
#include "opencv2/core.hpp"
...
cv::Mat H = cv::findHomography(points1, points2, cv::RANSAC, 5);
...
或者是:
#include "opencv2/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, RANSAC, 5 );
...
現(xiàn)有或者在將來可能會(huì)出現(xiàn)一些外部名稱和STL相同(沖突)拳恋,因此凡资,請(qǐng)使用明確的cv::
標(biāo)示符已解決名稱沖突:
Mat a(100, 100, CV_32F);
randu(a, Scalar::all(1), Scalar::all(std::rand()));
cv::log(a, a);
a /= std::log(2.);
std::
與cv::
中均有log()
函數(shù)
自動(dòng)內(nèi)存管理(Automatic Memory Management)
OpenCV對(duì)內(nèi)存實(shí)行自動(dòng)管理
首先,被大多數(shù)函數(shù)或者方法調(diào)用的std::vector
和cv::Mat
以及其它數(shù)據(jù)結(jié)構(gòu)有析構(gòu)函數(shù)谬运,在需要時(shí)釋放底層內(nèi)存緩沖區(qū)隙赁。這意味著析構(gòu)函數(shù)并需要總是釋放Mat的內(nèi)存緩沖,它們考慮到了可能的數(shù)據(jù)共享梆暖,析構(gòu)函數(shù)與矩陣數(shù)據(jù)緩存區(qū)相關(guān)的引用計(jì)數(shù)器(reference counter)遞減伞访。當(dāng)且僅當(dāng)引用計(jì)數(shù)到了0的情況下,緩存被釋放轰驳,也即是說厚掷,沒有其它結(jié)構(gòu)引用同一緩沖區(qū)。與之相似级解,當(dāng)一個(gè)Mat的實(shí)例被復(fù)制冒黑,實(shí)際上并沒有數(shù)據(jù)復(fù)制,取而代之的是引用計(jì)數(shù)器遞增以記住同一數(shù)據(jù)的另一個(gè)變體勤哗。而Mat::clone()的方法就是創(chuàng)建一個(gè)矩陣的完整副本(內(nèi)存拷貝)薛闪。參考以下例子:
//創(chuàng)建一個(gè)8Mb的矩陣
Mat A(1000, 1000, CV_64F);
//為相同矩陣創(chuàng)建另一個(gè)頭部;
//這個(gè)是立即操作,和矩陣大小無關(guān)俺陋。
Mat B = A;
//使用A中的第三行數(shù)據(jù)創(chuàng)建另一個(gè)頭部豁延,創(chuàng)建過程中不存在數(shù)據(jù)復(fù)制昙篙。
Mat C = B.row(3);
//現(xiàn)在創(chuàng)建一個(gè)獨(dú)立的矩陣副本
Mat D = B.clone();
//復(fù)制B的第五行到C,實(shí)質(zhì)上是拷貝了A的第五行到A的第三行诱咏。
B.row(5).copyTo(C);
//現(xiàn)在讓A和D共享數(shù)據(jù)苔可;剛剛修改的那個(gè)版本依舊被B和C所引用。
A = D;
//現(xiàn)在讓B成為一個(gè)空矩陣(不再引用內(nèi)存緩沖區(qū))袋狞,
//但剛剛修改的版本依舊被C所引用著焚辅,盡管C僅僅是A原始數(shù)據(jù)中的單獨(dú)一行。
B.release();
//最終苟鸯,對(duì)C做一個(gè)完整的復(fù)制同蜻,結(jié)果剛剛被修改過的那個(gè)矩陣被釋放,
//因?yàn)橐呀?jīng)沒有任何引用了早处。
C = C.clone();
可以看出湾蔓,使用Mat以及其它數(shù)據(jù)結(jié)構(gòu)是非常簡(jiǎn)單的,但是對(duì)于沒有考慮到自動(dòng)內(nèi)存管理的其它高級(jí)類和用戶定義的數(shù)據(jù)類型呢砌梆?對(duì)于它們默责,OpenCV提供了cv::Ptr
這樣一個(gè)模板類,與C++11中的std::shared_ptr有些相像咸包。所以桃序,不使用普通指針:
T* ptr = new T(...);
你可以使用:
Ptr<T> ptr(new T(...));
或者是
Ptr<T> ptr = makePtr<T>(...)
Ptr<T>
封裝了一個(gè)指向T實(shí)例的指針和與指針相關(guān)聯(lián)的引用計(jì)數(shù)器。
輸出數(shù)據(jù)的自動(dòng)分配 (Automatic Allocation of the Output Data)
OpenCV自動(dòng)釋放內(nèi)存烂瘫,并在大多數(shù)時(shí)間自動(dòng)為輸出函數(shù)參數(shù)分配內(nèi)存媒熊。因此,如果函數(shù)具有一個(gè)或多個(gè)輸入數(shù)組(cv :: Mat實(shí)例)和一些輸出數(shù)組坟比,則會(huì)自動(dòng)分配或重新分配輸出數(shù)組泛释。輸出數(shù)組的大小和類型由輸入數(shù)組的大小和類型決定。如果需要温算,函數(shù)會(huì)采用額外的參數(shù)來幫助確定輸出數(shù)組屬性。
例如:
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
int main(int, char**)
{
VideoCapture cap(0);
if(!cap.isOpened()) return -1;
Mat frame, edges;
namedWindow("edges", WINDOW_AUTOSIZE);
for(;;)
{
cap >> frame;
cvtColor(frame, edges, COLOR_BGR2GRAY);
GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
Canny(edges, edges, 0, 30, 3);
imshow("edges", edges);
if(waitKey(30) >= 0) break;
}
return 0;
}
由于視頻幀分辨率和比特深度對(duì)于視頻捕獲模塊是已知的间影,所以陣列幀由>>
運(yùn)算符自動(dòng)分配注竿。數(shù)組edge由cvtColor函數(shù)自動(dòng)分配。它具有與輸入數(shù)組相同的大小和位深度魂贬。通道數(shù)為1巩割,因?yàn)轭伾D(zhuǎn)換代碼cv::COLOR_BGR2GRAY
被傳遞,這意味著顏色將轉(zhuǎn)邊為灰度圖付燥。請(qǐng)注意宣谈,在第一次執(zhí)行循環(huán)體時(shí),frame和edge僅分配一次键科,因?yàn)橹笏械膸╢rame)都具有相同的分辨率闻丑。如果以某種方式更改視頻分辨率漩怎,則會(huì)自動(dòng)重新分配陣列。
該技術(shù)實(shí)現(xiàn)的關(guān)鍵部分是Mat::create
方法嗦嗡。它需要所需的數(shù)組大小和類型勋锤。如果數(shù)組已具有指定的大小和類型,則該方法不執(zhí)行任何操作侥祭。否則叁执,它釋放先前分配的數(shù)據(jù)(如果有的話)(這部分涉及遞減引用計(jì)數(shù)器并將其與零比較),然后分配所需大小的新緩沖區(qū)矮冬。大多數(shù)函數(shù)為每個(gè)輸出數(shù)組調(diào)用Mat::create
方法谈宛,因此實(shí)現(xiàn)了自動(dòng)輸出數(shù)據(jù)分配。
值得一些注意的是cv::mixChannels
胎署,cv::RNG::fill
吆录,以及一些其他函數(shù)和方法例外。它們無法分配輸出數(shù)組硝拧,因此必須提前執(zhí)行此操作径筏。
飽和算法(Saturation Arithmetics)
作為一個(gè)計(jì)算機(jī)視覺庫,OpenCV需要處理大量的圖像像素障陶,圖像通常會(huì)以8-16位的緊湊方式編入每個(gè)通道滋恬,形成具有最大值和最小值的區(qū)間范圍。此外還有一些必要的圖像處理操作抱究,例如色彩空間轉(zhuǎn)換恢氯,亮度、對(duì)比度鼓寺、銳度調(diào)節(jié)勋拟,復(fù)雜修改(bicubic,Lanczos)能產(chǎn)生超出有效范圍的數(shù)值妈候。如果僅存儲(chǔ)結(jié)果的低8(16)位敢靡,可能會(huì)導(dǎo)致視覺偽影,并可能影響更進(jìn)一步的圖像分析苦银。為了解決這個(gè)問題啸胧,飽和算法(saturation arithmetics)應(yīng)運(yùn)而生。如存儲(chǔ)數(shù)值R幔虏,操作的結(jié)果是轉(zhuǎn)化成8bit的圖像纺念,以便能在0-255范圍內(nèi)找到最接近的數(shù)值:
相似的規(guī)則應(yīng)用在有符號(hào)8位、無符號(hào)16位想括、有符號(hào)16位類型中陷谱。此語義在庫中具有通用性,在C++代碼中是通過類似于標(biāo)準(zhǔn)C++操作符saturate_cast<>
函數(shù)實(shí)現(xiàn)的瑟蜈。見下文所提供的實(shí)現(xiàn)公式:
I.at<uchar>(y, x) = saturate_cast<uchar>(r);
這里的cv::uchar是一個(gè)OpenCV 8bit的無符號(hào)類型烟逊。在SIMD的優(yōu)化代碼里渣窜,以及如SSE2的操作指南中的paddusb、packuswb等等都在使用這種類型焙格。在其幫助下图毕,它們達(dá)到了和C++中一樣的高效表現(xiàn)。
注意: 飽和算法不適用于32位的整型結(jié)果
固定像素類型和對(duì)模板的限制使用(Fixed Pixel Types. Limited Use of Templates)
模板是C++中一個(gè)非常重要的特性眷唉,它可以實(shí)現(xiàn)非常強(qiáng)大予颤、高效且安全的數(shù)據(jù)結(jié)構(gòu)和算法。但是模板的濫用會(huì)導(dǎo)致編譯時(shí)間的顯著增加冬阳、內(nèi)存占用過大等問題蛤虐。而且大量使用模板使會(huì)接口和實(shí)現(xiàn)很難分離,模板對(duì)于一些基礎(chǔ)算法還好肝陪,但是涉及到計(jì)算機(jī)視覺單個(gè)算法就有成百上千行來說卻很累贅驳庭。正因如此,同時(shí)也為了其它語言接口(如Python氯窍、Java饲常、Matlab,這些語言不支持模板或者支持的很有限)開發(fā)的簡(jiǎn)便性狼讨,當(dāng)前OpenCV的實(shí)現(xiàn)是基于的多態(tài)和模板上的運(yùn)行時(shí)調(diào)度贝淤。在某些時(shí)候,運(yùn)行時(shí)調(diào)度很慢(如訪問像素的操作)政供、無法訪問(通常是Ptr<>
的實(shí)現(xiàn))或者就是不方便(saturate_cast<>()
)播聪,實(shí)現(xiàn)時(shí)就小的模板類,方法和函數(shù)布隔。當(dāng)前版本中的OpenCV模板使用時(shí)受限制的离陶。
故OpenCV庫可操作性的數(shù)據(jù)類型是有限的,為以下類型之一:
- 8-bit 無符號(hào)整形(uchar)
- 8-bit 有符號(hào)整形(schar)
- 16-bit 無符號(hào)整形(ushort)
- 16-bit 有符號(hào)整形(short)
- 32-bit 有符號(hào)整形(int)
- 32-bit 浮點(diǎn)型(float)
- 64-bit 浮點(diǎn)型(double)
- 所有元素都是同一數(shù)據(jù)類型(以上7種之一)的由幾個(gè)元素組成的一個(gè)元組(tuple)
- 由以上元組所組成的單位數(shù)組(array)就叫做多通道數(shù)組衅檀。與單位元素都是標(biāo)量的單通道數(shù)組相對(duì)立招刨。通道可能的最大數(shù)是由
CV_CN_MAX
這個(gè)常量定義的,當(dāng)前是512哀军。
針對(duì)如上基本類型定義了如下枚舉:
enum { CV_8U = 0,
CV_8S = 1,
CV_16U = 2,
CV_16S = 3,
CV_32S = 4,
CV_32F = 5,
CV_64F = 6
};
多通道(n通道)則可據(jù)如下選項(xiàng)定義:
- CV_8UC1 ... CV_64FC4 常量 (通道數(shù)1-4)
- CV_8UC(n) ... CV_64FC(n) or CV_MAKETYPE(CV_8U, n) ... CV_MAKETYPE(CV_64F, n)等宏指令意指編譯時(shí)通道數(shù)大于4或者不明確
注意 CV_32FC1 == CV_32F, CV_32FC2 == CV_32FC(2) == CV_MAKETYPE(CV_32F, 2)
以及CV_MAKETYPE(depth, n) == ((depth&7) + ((n-1)<<3)
意味著常量類型是通過深度組成的沉眶,最低3位且通道數(shù)減1,下一個(gè)取log2(CV_CN_MAX)
位
例如:
Mat mtx(3, 3, CV_32F); //創(chuàng)建一個(gè)3x3的浮點(diǎn)矩陣
Mat cmtx(10, 1, CV_64FC2); //創(chuàng)建一個(gè)10x1的2通道浮點(diǎn)
//矩陣(10個(gè)元素的合成的迭代器)
Mat img(Size(1920, 1080), CV_8UC3); //創(chuàng)建一個(gè)3通道(彩色)圖片
//1920列1080行排苍。
Mat grayscale(image.size(), CV_MAKETYPE(image.depth(), 1)); //創(chuàng)建一個(gè)單通道圖片,帶有相同的大小和相同的通道類型学密。
OpenCV不能構(gòu)建和處理更復(fù)雜的元素?cái)?shù)組淘衙。此外每個(gè)函數(shù)以及方法僅可以執(zhí)行所有數(shù)組類型中的一個(gè)類型,通常算法越復(fù)雜腻暮,格式可支持性越小彤守。
下面是一些格式使用范圍的例子:
- 面部檢測(cè)算法僅支持8位的灰度或者色彩圖像
- 線性代數(shù)算法和大多數(shù)的機(jī)器學(xué)習(xí)算法僅支持浮點(diǎn)數(shù)組毯侦。
- 基礎(chǔ)函數(shù),例如
cv::add
具垫,支持所有類型侈离。 - 色彩空間轉(zhuǎn)換函數(shù)支持8位、16位無符號(hào)類型和32位浮點(diǎn)類型筝蚕。
每個(gè)函數(shù)支持的類型已經(jīng)根據(jù)實(shí)際需要定義好了卦碾,且可以在未來根據(jù)用戶的需求拓展。
輸入/輸出數(shù)組(InputArray and OutputArray)
許多OpenCV函數(shù)都能夠處理稠密二維或者多維數(shù)組起宽。這些函數(shù)通常都以cppMat
作為參數(shù)洲胖,但在某些時(shí)候使用std::vector<>
(比如存放點(diǎn)集)和cv::Matx
(類似于3x3的單應(yīng)矩陣)顯得更為方便。為避免API的命名重復(fù)坯沪,特殊的“代理”類也被引入绿映。最基本的代理類就是cv::InputArray
,其作用是將只讀數(shù)組作為函數(shù)輸入腐晾。派生自InputArray的類cv::OutputArray
作為函數(shù)的輸出叉弦。通常不需要關(guān)注中間類型(不可聲明明確類型的變量)——它會(huì)自動(dòng)工作。故除了InputArray和OutputArray不能使用藻糖,總能使用的是 Mat
, std::vector
, cv::Matx<>
, cv::Vec<>
和cv::Scalar
淹冰。當(dāng)一個(gè)函數(shù)有可選的輸入和輸出數(shù)組,而你并沒有或者并不想輸入輸出颖御,傳遞cv::noArray()
錯(cuò)誤處理
OpenCV使用異常(Expection)來示意嚴(yán)重錯(cuò)誤榄棵。當(dāng)輸入數(shù)據(jù)類型正確且在規(guī)定的范圍之內(nèi),但是算法因?yàn)槟承┰驘o法運(yùn)作(比如優(yōu)化算法沒有收斂)時(shí)會(huì)返回一個(gè)特殊的錯(cuò)誤代碼(通常就是布爾值)潘拱。
異痴铞可以被cv::CV_Error(errcode, description)
或者它的派生類實(shí)例化。反過來芦岂,cv::Exception
也是std::Exception
的派生類瘪弓。故其也可以很好的兼容標(biāo)準(zhǔn)C++庫中的代碼。
異常拋出通常是通過CV_Error(errcode, description)
宏禽最、CV_Error(errcode, printf-spec,(打印參數(shù)))
與打印類似的變量腺怯,或者是使用CV_Assert(condition)
宏來檢查條件,并在不滿足條件時(shí)拋出異常川无。對(duì)于性能臨界的代碼呛占,CV_DbgAssert(condition)
宏只在調(diào)試配置(Debug configuration)過程中保留。在自動(dòng)內(nèi)存管理情況下懦趋,如果突然發(fā)生錯(cuò)誤晾虑,所有內(nèi)部緩存會(huì)被自動(dòng)釋放。用戶只需要添加一個(gè)聲明即可捕獲異常:
try
{
... // call OpenCV
}
catch( cv::Exception& e )
{
const char* err_msg = e.what();
std::cout << "exception caught: " << err_msg << std::endl;
}
多線程和可重入性
當(dāng)前版本的OpenCV支持重入,也即是說同樣的函數(shù)或是不同類中的同樣方法可以被不同的線程調(diào)用帜篇。而且相同的Mat
可以在不同線程中調(diào)用糙捺,因?yàn)橐糜?jì)數(shù)使用了特定體系結(jié)構(gòu)的原子指令(atomic instructions)。