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 具有模組化結構,這意味著該軟體包包含多個共享或靜態庫。以下模組可用:

  • 核心功能 (core) - 一個緊湊的模組,定義了基本資料結構,包括密集多維陣列 Mat 和所有其他模組使用的基本函式。
  • 影像處理 (imgproc) - 一個影像處理模組,包括線性及非線性影像濾波、幾何影像變換(調整大小、仿射和透視扭曲、基於通用表的重新對映)、色彩空間轉換、直方圖等。
  • 影像檔案讀寫 (imgcodecs) - 包括用於讀取和寫入各種格式影像檔案的函式。
  • 影片 I/O (videoio) - 一個易於使用的影片捕獲和影片編解碼器介面。
  • 高階 GUI (highgui) - 一個易於使用的簡單 UI 功能介面。
  • 影片分析 (video) - 一個影片分析模組,包括運動估計、背景減除和目標跟蹤演算法。
  • 相機標定與 3D 重建 (calib3d) - 基本多視角幾何演算法、單目和立體相機標定、物體姿態估計、立體匹配演算法以及 3D 重建元素。
  • 2D 特徵框架 (features2d) - 顯著特徵檢測器、描述符和描述符匹配器。
  • 目標檢測 (objdetect) - 檢測預定義類別的物件和例項(例如,人臉、眼睛、馬克杯、人物、汽車等)。
  • 深度神經網路模組 (dnn) - 深度神經網路模組。
  • 機器學習 (ml) - 機器學習模組包含一組用於資料的統計分類、迴歸和聚類的類和函式。
  • 計算攝影 (photo) - 高階照片處理技術,如去噪、影像修復。
  • 影像拼接 (stitching) - 影像拼接和全景圖建立功能。
  • ...其他一些輔助模組,如 FLANN 和 Google 測試封裝器、Python 繫結等。

文件的後續章節將描述每個模組的功能。但首先,請務必熟悉庫中廣泛使用的通用 API 概念。

API 概念

cv 名稱空間

所有 OpenCV 類和函式都放在 cv 名稱空間中。因此,要從程式碼中訪問這些功能,請使用 cv:: 限定符或 using namespace cv; 指令。

#include "opencv2/core.hpp"
...
cv::Mat H = cv::findHomography(points1, points2, cv::RANSAC, 5);
...
Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method=0, double ransacReprojThreshold=3, OutputArray mask=noArray(), const int maxIters=2000, const double confidence=0.995)
尋找兩個平面之間的透視變換。
@ RANSAC
RANSAC 演算法。
定義 calib3d.hpp:552

#include "opencv2/core.hpp"
using namespace cv;
...
Mat H = findHomography(points1, points2, RANSAC, 5 );
...
n 維密集陣列類
定義於 mat.hpp:840
定義 core.hpp:107

當前或未來的某些 OpenCV 外部名稱可能與 STL 或其他庫衝突。在這種情況下,使用顯式名稱空間限定符來解決名稱衝突。

Mat a(100, 100, CV_32F);
randu(a, Scalar::all(1), Scalar::all(std::rand()));
cv::log(a, a);
a /= std::log(2.);
void log(InputArray src, OutputArray dst)
計算每個陣列元素的自然對數。
#define CV_32F
定義位於 interface.h:78

自動記憶體管理

OpenCV 自動處理所有記憶體。

首先,std::vector、cv::Mat 以及函式和方法使用的其他資料結構都具有解構函式,這些解構函式會在需要時釋放底層記憶體緩衝區。這意味著解構函式並不總是像 Mat 那樣釋放緩衝區。它們會考慮可能的資料共享。解構函式會遞減與矩陣資料緩衝區關聯的引用計數器。當且僅當引用計數器達到零時,即當沒有其他結構引用同一緩衝區時,才會釋放緩衝區。同樣,當 Mat 例項被複制時,實際上並沒有複製任何資料。相反,引用計數器會遞增,以記錄存在同一資料的另一個所有者。cv::Mat::clone 方法會建立矩陣資料的完整副本。請參見以下示例:

// 建立一個 8MB 的大矩陣
Mat A(1000, 1000, CV_64F);
// 為同一矩陣建立另一個頭;
// 這是一個即時操作,與矩陣大小無關。
Mat B = A;
// 為 A 的第 3 行建立另一個頭;也沒有複製資料
Mat C = B.row(3);
// 現在建立矩陣的單獨副本
Mat D = B.clone();
// 將 B 的第 5 行復制到 C,即複製 A 的第 5 行
// 到 A 的第 3 行。
B.row(5).copyTo(C);
// 現在讓 A 和 D 共享資料;之後修改過的版本
// A 仍然被 B 和 C 引用。
A = D;
// 現在將 B 設定為空矩陣(不引用任何記憶體緩衝區),
// 但 A 的修改版本仍將被 C 引用,
// 儘管 C 只是原始 A 的一行
B.release();
// 最後,對 C 進行完整複製。結果,大的修改後的
// 矩陣將被釋放,因為它沒有被任何人引用。
C = C.clone();
CV_NODISCARD_STD Mat clone() const
建立陣列及其底層資料的完整副本。
void copyTo(OutputArray m) const
將矩陣複製到另一個矩陣。
Mat row(int y) const
為指定的矩陣行建立矩陣頭。
void release()
遞減引用計數器,如果需要,反分配矩陣。
#define CV_64F
定義位於 interface.h:79

您可以看到 Mat 和其他基本結構的使用很簡單。但是,對於高階類或甚至在沒有考慮自動記憶體管理的情況下建立的使用者資料型別呢?對於它們,OpenCV 提供了類似於 C++11 中 std::shared_ptr 的 cv::Ptr 模板類。因此,不是使用普通指標:

T* ptr = new T(...);

您可以使用:

Ptr<T> ptr(new T(...));
std::shared_ptr< _Tp > Ptr
定義 cvstd_wrapper.hpp:23

或者

Ptr<T> ptr = makePtr<T>(...);

Ptr<T> 封裝了一個指向 T 例項的指標以及與該指標關聯的引用計數器。有關詳細資訊,請參閱 cv::Ptr 的描述。

自動分配輸出資料

OpenCV 自動釋放記憶體,並且在大多數情況下自動為輸出函式引數分配記憶體。因此,如果一個函式有一個或多個輸入陣列(cv::Mat 例項)和一些輸出陣列,則輸出陣列會自動分配或重新分配。輸出陣列的大小和型別由輸入陣列的大小和型別決定。如果需要,函式會採用額外的引數來幫助確定輸出陣列的屬性。

示例

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;
}
用於指定影像或矩形大小的模板類。
定義 types.hpp:335
從影片檔案、影像序列或攝像頭捕獲影片的類。
定義 videoio.hpp:786
int main(int argc, char *argv[])
定義 highgui_qt.cpp:3

陣列 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::mixChannelscv::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++ 型別轉換操作。下面是上述公式的實現:

I.at<uchar>(y, x) = saturate_cast<uchar>(r);
unsigned char uchar
定義於 interface.h:51

其中 cv::uchar 是 OpenCV 的 8 位無符號整型。在最佳化的 SIMD 程式碼中,使用了 paddusb、packuswb 等 SSE2 指令。它們有助於實現與 C++ 程式碼完全相同的行為。

注意
當結果是 32 位整數時,不應用飽和。

固定畫素型別。模板的有限使用

模板是 C++ 的一個強大功能,可以實現功能強大、高效且安全的資料結構和演算法。然而,大量使用模板可能會大大增加編譯時間和程式碼大小。此外,當完全使用模板時,很難分離介面和實現。這對於基本演算法可能很好,但對於計算機視覺庫來說卻不好,因為單個演算法可能跨越數千行程式碼。由於這個原因,並且為了簡化 Python、Java、Matlab 等沒有模板或模板功能有限的其他語言的繫結開發,當前的 OpenCV 實現基於多型性和執行時排程,而不是模板。在執行時排程速度太慢(如畫素訪問運算子)、不可能(通用 cv::Ptr<> 實現)或只是非常不方便(cv::saturate_cast<>())的地方,當前實現引入了小的模板類、方法和函式。在當前 OpenCV 版本的其他地方,模板的使用受到限制。

因此,庫可以操作的原始資料型別集是有限且固定的。也就是說,陣列元素應具有以下型別之一:

  • 8 位無符號整數 (uchar)
  • 8 位有符號整數 (schar)
  • 16 位無符號整數 (ushort)
  • 16 位有符號整數 (short)
  • 32 位有符號整數 (int)
  • 32 位浮點數 (float)
  • 64 位浮點數 (double)
  • 若干個元素的元組,其中所有元素具有相同的型別(上述之一)。元素是此類元組的陣列稱為多通道陣列,與元素是標量值的單通道陣列相對。最大可能通道數由 CV_CN_MAX 常量定義,目前設定為 512。

對於這些基本型別,應用以下列舉:

enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 };
#define CV_8S
定義 interface.h:74
#define CV_8U
定義於 interface.h:73
#define CV_32S
定義位於 interface.h:77
#define CV_16S
定義 interface.h:76
#define CV_16U
定義 interface.h:75

可以使用以下選項指定多通道(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) 位)組成。

示例

Mat mtx(3, 3, CV_32F); // 建立一個 3x3 浮點矩陣
Mat cmtx(10, 1, CV_64FC2); // 建立一個 10x1 2 通道浮點矩陣
// 矩陣(10 元素複數向量)
Mat img(Size(1920, 1080), CV_8UC3); // 建立一個 1920 列 1080 行的 3 通道(彩色)影像。
// of 1920 columns and 1080 rows.
Mat grayscale(img.size(), CV_MAKETYPE(img.depth(), 1)); // 建立一個與 img 大小相同、
// 並且通道型別相同的 1 通道影像。
// channel type as img
#define CV_8UC3
定義於 interface.h:90
#define CV_64FC2
定義 interface.h:125
#define CV_MAKETYPE(depth, cn)
定義於 interface.h:85

使用 OpenCV 無法構建或處理具有更復雜元素的陣列。此外,每個函式或方法只能處理所有可能陣列型別的一個子集。通常,演算法越複雜,支援的格式子集越小。請參見下面此類限制的典型示例:

  • 人臉檢測演算法僅適用於 8 位灰度或彩色影像。
  • 線性代數函式和大多數機器學習演算法僅適用於浮點陣列。
  • 基本函式,例如 cv::add,支援所有型別。
  • 色彩空間轉換函式支援 8 位無符號、16 位無符號和 32 位浮點型別。

每個函式支援的型別子集是根據實際需求定義的,將來可能會根據使用者請求進行擴充套件。

InputArray 和 OutputArray

許多 OpenCV 函式處理密集的二維或多維數值陣列。通常,這些函式將 cv::Mat 作為引數,但在某些情況下,使用 std::vector<>(例如,用於點集)或 cv::Matx<>(例如,用於 3x3 單應矩陣)更方便。為了避免 API 中出現大量重複,引入了特殊的“代理”類。基本“代理”類是 cv::InputArray。它用於在函式輸入時傳遞只讀陣列。派生自 InputArray 類的 cv::OutputArray 用於指定函式的輸出陣列。通常,您不應該關心這些中間型別(也不應該顯式宣告這些型別的變數)——它會自動工作。您可以假定除了 InputArray/OutputArray 之外,您總是可以使用 cv::Matstd::vector<>cv::Matx<>cv::Vec<>cv::Scalar。當函式具有可選的輸入或輸出陣列,而您沒有或不想要時,傳遞 cv::noArray()

錯誤處理

OpenCV 使用異常來表示關鍵錯誤。當輸入資料格式正確且屬於指定值範圍,但演算法由於某種原因無法成功時(例如,最佳化演算法未收斂),它會返回一個特殊的錯誤程式碼(通常只是一個布林變數)。

異常可以是 cv::Exception 類或其派生類的例項。反過來,cv::Exceptionstd::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 語句來捕獲異常:

try
{
... // 呼叫 OpenCV
}
catch (const cv::Exception& e)
{
const char* err_msg = e.what();
std::cout << "exception caught: " << err_msg << std::endl;
}
Class passed to an error.
定義 core.hpp:120
virtual const char * what() const noexcept override

多執行緒和可重入性

當前的 OpenCV 實現是完全可重入的。也就是說,可以從不同的執行緒呼叫同一函式或不同類例項的同一方法。此外,同一 Mat 可以在不同執行緒中使用,因為引用計數操作使用架構特定的原子指令。