![]() |
OpenCV 4.13.0
開源計算機視覺庫 (Open Source Computer Vision)
|
OpenCV (Open Source Computer Vision Library:https://opencv.tw) 是一個開源庫,包含數百種計算機視覺演算法。本文件描述的是所謂的 OpenCV 2.x API,它本質上是一個 C++ API,與基於 C 語言的 OpenCV 1.x API 不同(自 OpenCV 2.4 版本釋出以來,C API 已被棄用且不再使用 "C" 編譯器進行測試)。
OpenCV 具有模組化結構,這意味著該軟體包包含多個共享或靜態庫。以下模組可用:
文件的後續章節將描述每個模組的功能。但首先,請務必熟悉庫中廣泛使用的通用 API 概念。
所有 OpenCV 類和函式都放在 cv 名稱空間中。因此,要從程式碼中訪問這些功能,請使用 cv:: 限定符或 using namespace cv; 指令。
或
當前或未來的某些 OpenCV 外部名稱可能與 STL 或其他庫衝突。在這種情況下,使用顯式名稱空間限定符來解決名稱衝突。
OpenCV 自動處理所有記憶體。
首先,std::vector、cv::Mat 以及函式和方法使用的其他資料結構都具有解構函式,這些解構函式會在需要時釋放底層記憶體緩衝區。這意味著解構函式並不總是像 Mat 那樣釋放緩衝區。它們會考慮可能的資料共享。解構函式會遞減與矩陣資料緩衝區關聯的引用計數器。當且僅當引用計數器達到零時,即當沒有其他結構引用同一緩衝區時,才會釋放緩衝區。同樣,當 Mat 例項被複制時,實際上並沒有複製任何資料。相反,引用計數器會遞增,以記錄存在同一資料的另一個所有者。cv::Mat::clone 方法會建立矩陣資料的完整副本。請參見以下示例:
您可以看到 Mat 和其他基本結構的使用很簡單。但是,對於高階類或甚至在沒有考慮自動記憶體管理的情況下建立的使用者資料型別呢?對於它們,OpenCV 提供了類似於 C++11 中 std::shared_ptr 的 cv::Ptr 模板類。因此,不是使用普通指標:
您可以使用:
或者
Ptr<T> 封裝了一個指向 T 例項的指標以及與該指標關聯的引用計數器。有關詳細資訊,請參閱 cv::Ptr 的描述。
OpenCV 自動釋放記憶體,並且在大多數情況下自動為輸出函式引數分配記憶體。因此,如果一個函式有一個或多個輸入陣列(cv::Mat 例項)和一些輸出陣列,則輸出陣列會自動分配或重新分配。輸出陣列的大小和型別由輸入陣列的大小和型別決定。如果需要,函式會採用額外的引數來幫助確定輸出陣列的屬性。
示例
陣列 frame 由 >> 運算子自動分配,因為影片捕獲模組知道影片幀的解析度和位深。陣列 edges 由 cvtColor 函式自動分配。它與輸入陣列具有相同的大小和位深。通道數為 1,因為傳遞了顏色轉換程式碼 [8U/16U/32F] convert between RGB/BGR and grayscale, color conversionscv::COLOR_BGR2GRAY,這意味著從彩色轉換為灰度。請注意,frame 和 edges 僅在迴圈體第一次執行時分配一次,因為所有後續影片幀都具有相同的解析度。如果您以某種方式更改影片解析度,陣列將自動重新分配。
這項技術的關鍵組成部分是 cv::Mat::create 方法。它接受所需的陣列大小和型別。如果陣列已經具有指定的大小和型別,則該方法不執行任何操作。否則,它會釋放先前分配的資料(如果有)(這部分涉及遞減引用計數器並將其與零進行比較),然後分配所需大小的新緩衝區。大多數函式會為每個輸出陣列呼叫 cv::Mat::create 方法,因此實現了自動輸出資料分配。
該方案的一些顯著例外是 cv::mixChannels、cv::RNG::fill 以及其他一些函式和方法。它們無法分配輸出陣列,因此您必須提前完成此操作。
作為一個計算機視覺庫,OpenCV 經常處理影像畫素,這些畫素通常以緊湊的 8 位或 16 位每通道形式編碼,因此具有有限的值範圍。此外,影像上的某些操作,如色彩空間轉換、亮度/對比度調整、銳化、複雜插值(雙三次、Lanczos)可能會產生超出可用範圍的值。如果您只儲存結果的最低 8 (16) 位,這會導致視覺偽影並可能影響進一步的影像分析。為了解決這個問題,使用了所謂的*飽和*算術。例如,要將操作結果 r 儲存到 8 點陣圖像中,您會找到 0..255 範圍內最接近的值。
\[I(x,y)= \min ( \max (\textrm{round}(r), 0), 255)\]
類似的規則適用於 8 位有符號、16 位有符號和無符號型別。這種語義在庫中無處不在。在 C++ 程式碼中,它是透過 cv::saturate_cast<> 函式完成的,這些函式類似於標準 C++ 型別轉換操作。下面是上述公式的實現:
其中 cv::uchar 是 OpenCV 的 8 位無符號整型。在最佳化的 SIMD 程式碼中,使用了 paddusb、packuswb 等 SSE2 指令。它們有助於實現與 C++ 程式碼完全相同的行為。
模板是 C++ 的一個強大功能,可以實現功能強大、高效且安全的資料結構和演算法。然而,大量使用模板可能會大大增加編譯時間和程式碼大小。此外,當完全使用模板時,很難分離介面和實現。這對於基本演算法可能很好,但對於計算機視覺庫來說卻不好,因為單個演算法可能跨越數千行程式碼。由於這個原因,並且為了簡化 Python、Java、Matlab 等沒有模板或模板功能有限的其他語言的繫結開發,當前的 OpenCV 實現基於多型性和執行時排程,而不是模板。在執行時排程速度太慢(如畫素訪問運算子)、不可能(通用 cv::Ptr<> 實現)或只是非常不方便(cv::saturate_cast<>())的地方,當前實現引入了小的模板類、方法和函式。在當前 OpenCV 版本的其他地方,模板的使用受到限制。
因此,庫可以操作的原始資料型別集是有限且固定的。也就是說,陣列元素應具有以下型別之一:
對於這些基本型別,應用以下列舉:
可以使用以下選項指定多通道(n 通道)型別:
#CV_32FC1 == #CV_32F, #CV_32FC2 == #CV_32FC(2) == #CV_MAKETYPE(CV_32F, 2),以及 #CV_MAKETYPE(depth, n) == ((depth&7) + ((n-1)<<3)。這意味著常量型別由深度(取最低 3 位)和通道數減 1(取接下來的 log2(CV_CN_MAX) 位)組成。示例
使用 OpenCV 無法構建或處理具有更復雜元素的陣列。此外,每個函式或方法只能處理所有可能陣列型別的一個子集。通常,演算法越複雜,支援的格式子集越小。請參見下面此類限制的典型示例:
每個函式支援的型別子集是根據實際需求定義的,將來可能會根據使用者請求進行擴充套件。
許多 OpenCV 函式處理密集的二維或多維數值陣列。通常,這些函式將 cv::Mat 作為引數,但在某些情況下,使用 std::vector<>(例如,用於點集)或 cv::Matx<>(例如,用於 3x3 單應矩陣)更方便。為了避免 API 中出現大量重複,引入了特殊的“代理”類。基本“代理”類是 cv::InputArray。它用於在函式輸入時傳遞只讀陣列。派生自 InputArray 類的 cv::OutputArray 用於指定函式的輸出陣列。通常,您不應該關心這些中間型別(也不應該顯式宣告這些型別的變數)——它會自動工作。您可以假定除了 InputArray/OutputArray 之外,您總是可以使用 cv::Mat、std::vector<>、cv::Matx<>、cv::Vec<> 或 cv::Scalar。當函式具有可選的輸入或輸出陣列,而您沒有或不想要時,傳遞 cv::noArray()。
OpenCV 使用異常來表示關鍵錯誤。當輸入資料格式正確且屬於指定值範圍,但演算法由於某種原因無法成功時(例如,最佳化演算法未收斂),它會返回一個特殊的錯誤程式碼(通常只是一個布林變數)。
異常可以是 cv::Exception 類或其派生類的例項。反過來,cv::Exception 是 std::exception 的派生類。因此,可以使用其他標準 C++ 庫元件在程式碼中優雅地處理它。
異常通常透過 #CV_Error(errcode, description) 宏,或者其類似 printf 的 #CV_Error_(errcode, (printf-spec, printf-args)) 變體,或者使用 CV_Assert(condition) 宏來丟擲。CV_Assert(condition) 宏檢查條件並在不滿足時丟擲異常。對於效能關鍵程式碼,有 CV_DbgAssert(condition),它僅保留在 Debug 配置中。由於自動記憶體管理,所有中間緩衝區在發生突然錯誤時都會自動釋放。如果需要,您只需新增 try 語句來捕獲異常:
當前的 OpenCV 實現是完全可重入的。也就是說,可以從不同的執行緒呼叫同一函式或不同類例項的同一方法。此外,同一 Mat 可以在不同執行緒中使用,因為引用計數操作使用架構特定的原子指令。