這個其實是翻譯的opencv的官方文檔的aruco部分。
https://docs.opencv.org/3.1.0/d5/dae/tutorial_aruco_detection.html
視覺降落的二維碼檢測用到這裡面
摘自:https://blog.csdn.net/qq_26751117/article/details/73733337
https://blog.csdn.net/u010260681/article/details/77089657
【OpenCV3.2】Detection of ArUco Markers
Monet66 2017-06-26 14:20:06 5531 收藏 17
姿态估計(Pose estimation)在計算機視覺領域扮演着十分重要的角色:機器人導航、增強現實以及其它。這一過程的基礎是找到現實世界和圖像投影之間的對應點。這通常是很困難的一步,是以我們常常用自己制作的或基本的marker來讓這一切變得更容易。
最為流行的一個途徑是基于二進制平方的标記。這種Marker的主要便利之處在于,一個Marker提供了足夠多的對應(四個角)來擷取相機的資訊。同樣的,内部的二進制編碼使得算法非常健壯,允許應用錯誤檢測和校正技術的可能性。
aruco子產品基于ArUco庫,這是一個檢測二進制marker的非常流行的庫,是由Rafael Muñoz和Sergio Garrido完成的。
aruco的函數包含在
#include <opencv2/aruco.hpp>
Markers and Dictionaries
一個ArUco marker是一個二進制平方标記,它由一個寬的黑邊和一個内部的二進制矩陣組成,内部的矩陣決定了它們的id。黑色的邊界有利于快速檢測到圖像,二進制編碼可以驗證id,并且允許錯誤檢測和矯正技術的應用。marker的大小決定了内部矩陣的大小。例如,一個4x4的marker由16bits組成。
一些ArUco markers的例子:
Example of markers images
應當注意到,我們需要檢測到一個Marker在空間中發生了旋轉,但是,檢測的過程需要确定它的初始角度,是以每個角落需要是明确的,不能有歧義,保證上述這點也是靠二進制編碼完成的。
markers的字典是在一個特殊應用中使用到的marker的集合。這僅僅是每個marker的二進制編碼的連結清單。
字典的主要性質是字典的大小和marker的大小:
- 字典的大小是組成字典的marker的數量
- marker的大小是這些marker的尺寸(位的個數)
aruco子產品包含了一些預定義的字典,這些字典涵蓋了一系列的字典大小和marker尺寸。
有些人可能會認為Marker的id是從十進制轉成二進制的。但是,考慮到較大的marker會有較多的位數,管理如此多的資料不那麼現實,這并不可能。反之,一個marker的id僅需是marker在它所在的字典的下标。例如,一個字典裡的五個marker的id是:0,1,2,3和4。
更多有關字典的資訊在“Selecting a dictionary”部分提及。
Marker Creation
在檢測之前,我們需要列印marker,以把它們放到環境中。marker的圖像可以使用drawMarker()函數生成。
例如,讓我們分析一下如下的調用:
-
cv::Mat markerImage;
-
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
-
cv::aruco::drawMarker(dictionary, 23, 200, markerImage, 1);
首先,我們通過選擇aruco子產品中一個預定義的字典來建立一個Dictionary對象,具體而言,這個字典是由250個marker組成的,每個marker的大小為6x6bits(DICT_6X6_250)
drawMarker
的參數如下:
- 第一個參數是之前建立的字典對象。
- 第二個參數是marker的id,在這個例子中選擇的是字典DICT_6X6_250第23個marker。注意到每個字典是由不同數目的Marker組成的,在這個例子中,有效的Id數字範圍是0到249。不在有效區間的特定id将會産生異常。
- 第三個參數,200,是輸出Marker圖像的大小。在這個例子中,輸出的圖像将是200x200像素大小。注意到這一參數需要滿足能夠存儲特定字典 的所有位。是以,舉例而言,你不能為6x6大小的marker生成一個5x5圖像(這還沒有考慮到Marker的邊界)。除此之外,為了避免變形,這一參數最好和位數+邊界的大小成正比,至少要比marker的大小大得多(如這個例子中的200),這樣變形就不顯著了。
- 第四個參數是輸出的圖像。
- 最終,最後一個參數是一個可選的參數,它指定了Marer黑色邊界的大小。這一大小與位數數目成正比。例如,值為2意味着邊界的寬度将會是2的倍數。預設的值為1。
- 生成的圖像如下:
Generated marker
詳細的例子在子產品示範檔案夾中的
create_marker.cpp
注意:此樣例現在通過指令行利用 OpenCV Commandline Parser輸入。對于這個檔案,例子中的參數可以寫作
"/Users/Sarthak/Dropbox/OpenCV_GSoC/marker.png" -d=10 -id=1
Marker Detection
給定一個可以看見ArUco marker的圖像,檢測程式應當傳回檢測到的marker的清單。每個檢測到的marker包括:
- 圖像四個角的位置(按照原始的順序)
- marker的Id
marker檢測過程由以下兩個主要步驟構成:
- 檢測有哪些marker。在這一階段我們分析圖像,以找到哪些形狀可以被識别為markers。首先要做的是利用自适應性門檻值來分割marker,然後從門檻值化的圖像中提取外形輪廓,并且舍棄那些非凸多邊形的,以及 那些不是方形的。我們還使用了一些額外的濾波(來剔除那些過小或者過大的輪廓,過于相近的凸多邊形,等)
- 檢測完marker之後,我們有必要分析它的内部編碼來确定它們是否确實是marker。此步驟首先提取每個标記的标記位。為了達到這個目的,首先,我們需要對圖像進行透視變換,來得到它規範的形态(正視圖)。然後,對規範的圖像用Otsu門檻值化以分離白色和黑色位。這一圖像根據marker大小和邊界大小被分為不同格子,我們統計落在每個格子中的黑白像素數目來決定這是黑色還是白色的位。最終,我們分析這些位數來決定這個marker是屬于哪個特定字典的,如果有必要的話,需要對錯誤進行檢測。
考慮如下圖像:
Original image with markers
這些是檢測出來的marker(用綠色标記)
Image with detected markers
以下是識别階段被剔除的Marker候選(用粉紅色标記):
Image with rejectedcandidates
在aruco子產品,檢測是由
detectMarkers()
函數完成的,這一函數是這個子產品中最重要的函數,因為剩下的所有函數操作都基于detectMarkers()傳回的檢測出的markers。
一個marker檢測的例子:
-
cv::Mat inputImage;
-
...
-
std::vector<int> markerIds;
-
std::vector<std::vector<cv::Point2f>> markerCorners, rejectedCandidates;
-
cv::Ptr<cv::aruco::DetectorParameters> parameters;
-
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
-
cv::aruco::detectMarkers(inputImage, dictionary, markerCorners, markerIds, parameters, rejectedCandidates);
detectMarkers
的參數清單如下:
- 第一個參數是待檢測marker的圖像。
- 第二個參數是字典對象,在這一例子中是之前定義的字典 (
).DICT_6X6_250
- 檢測出的markers存儲在
和markerCorners
結構中:·markerIds
-
-
是檢測出的圖像的角的清單。對于每個marker,将傳回按照原始順序排列的四個角(從左上角順時針開始)。是以,第一個點是左上角的角,緊接着右上角、右下角和左下角。markerCorners
-
是在markerCorners檢測出的所有maker的id清單.注意傳回的markerCorners和markerIds
向量具有相同的大小。markerIds
-
- 第四個參數是類型的對象
. 這一對象包含了檢測階段的所有參數。這一參數将在 下一章節詳細介紹。DetectionParameters
- 最後的參數,
, 傳回了所有的marker候選, 例如, 那些被檢測出來的不是有效編碼的方形。每個候選同樣由四個角定義, 它的 形式和markerCorners的參數一樣。這一參數可以省略,它僅僅用于debug階段,或是用于“再次尋找”政策(見refineDetectedMarkers())rejectedCandidates
detectMarkers()之後,接下來你想要做的事情可能是檢查你的marker是否被正确地檢測出來了。幸運的是,aruco子產品提供了一個函數,它能在輸入圖像中來繪制檢測出來的markers,這個函數就是drawDetectedMarkers() ,例子如下:
-
cv::Mat outputImage
-
cv::aruco::drawDetectedMarkers(image, markerCorners, markerIds);
-
是輸入/輸出圖像,程式将在這張圖上繪制marker。(它通常就是檢測出marker的那張圖像)image
-
和markerCorners
是檢測出marker的結構,它們的格式和markerIds
函數提供的一樣。detectMarkers()
Image with detected markers
注意到這個函數僅僅用于可視化,而沒有别的什麼用途。
使用這兩個函數我們完成了基本的marker識别步驟,我們可以從相機中檢測出Marker了。
-
cv::VideoCapture inputVideo;
-
inputVideo.open(0);
-
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
-
while (inputVideo.grab()) {
-
cv::Mat image, imageCopy;
-
inputVideo.retrieve(image);
-
image.copyTo(imageCopy);
-
std::vector<int> ids;
-
std::vector<std::vector<cv::Point2f> > corners;
-
cv::aruco::detectMarkers(image, dictionary, corners, ids);
-
// if at least one marker detected
-
if (ids.size() > 0)
-
cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);
-
cv::imshow("out", imageCopy);
-
char key = (char) cv::waitKey(waitTime);
-
if (key == 27)
-
break;
-
}
注意到這裡忽略了有些可選的參數,比如檢測參數對象、以及輸出的被剔除的候選對象向量。
完整的工程代碼包含在子產品樣例檔案夾中的
detect_markers.cpp
注意:指令行
-c="_path_/calib.txt" -d=10
Pose Estimation
接下來你想要做的應當是通過marker檢測來擷取相機pose。
為了展現相機的Pose檢測,你需要知道你的相機的校準(Calibration)參數。這是一個相機矩陣和畸變系數。如果你不知道如何校準你的相機,你可以看一看
calibrateCamera()
函數,以及OpenCV的校準教程。你同樣可以使用aruco子產品來校準你的相機,這在使用aruco進行校準的教程中将會介紹。注意這個過程隻需要做一次,除非你的相機的光學性質發生了改變(例如調焦)
最後,在校準之後我們得到的是相機矩陣:這是一個3x3的矩陣,包含了焦距和相機中心坐标(相機的内參),以及畸變系數:一個包含五個以上元素的向量,它描述的是相機産生的畸變。
當你用ArUco marker來檢測相機Pose時,你可以單獨地檢測每個Marker的pose。如果你想要從一堆Marker裡檢測出一個pose,你需要的是aruco闆。(參見ArUco闆教程)
涉及到marker的相機pose是一個從marker坐标系統到相機坐标系統的三維變換。這是由一個旋轉和一個平移向量确定的(參見
solvePnP()
函數)
aruco子產品提供了一個函數,用來檢測所有探測到的Marker的pose。
-
cv::Mat cameraMatrix, distCoeffs;
-
...
-
std::vector<cv::Vec3d> rvecs, tvecs;
-
cv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs);
-
參數是marker的角向量,是由corners
函數傳回的。detectMarkers()
- 第二個參數是marker的大小(機關是米或者其它)。注意Pose檢測的平移矩陣機關都是相同的。
-
和cameraMatrix
是需要求解的相機校準參數。distCoeffs
-
和rvecs
分别是每個markers角的旋轉和平移向量。tvecs
這一函數擷取的marker坐标系統處在marker重心,Z坐标指向紙面外部,如下圖所示。坐标的顔色為,X:紅色,Y:綠色,Z:藍色。
Image with axis drawn
aruco子產品提供了一個函數繪制上圖中的坐标,是以我們可以檢查pose檢測的正确性。
cv::aruco::drawAxis(image, cameraMatrix, distCoeffs, rvec, tvec, 0.1);
-
是輸入/輸出圖像,坐标将會在這張圖像上繪制(通常就是檢測marker的那張圖像)。image
-
和cameraMatrix
是相機校準參數。distCoeffs
-
和rvec
是Pose參數,指明了坐标繪制的位置。tvec
- 最後一個參數是坐标軸的長度,和tvec機關一樣(通常是米)。
針對一個marker的pose檢測的基本的完整示例:
-
cv::VideoCapture inputVideo;
-
inputVideo.open(0);
-
cv::Mat cameraMatrix, distCoeffs;
-
// camera parameters are read from somewhere
-
readCameraParameters(cameraMatrix, distCoeffs);
-
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
-
while (inputVideo.grab()) {
-
cv::Mat image, imageCopy;
-
inputVideo.retrieve(image);
-
image.copyTo(imageCopy);
-
std::vector<int> ids;
-
std::vector<std::vector<cv::Point2f>> corners;
-
cv::aruco::detectMarkers(image, dictionary, corners, ids);
-
// if at least one marker detected
-
if (ids.size() > 0) {
-
cv::aruco::drawDetectedMarkers(imageCopy, corners, ids);
-
std::vector<cv::Mat> rvecs, tvecs;
-
cv::aruco::estimatePoseSingleMarkers(corners, 0.05, cameraMatrix, distCoeffs, rvecs, tvecs);
-
// draw axis for each marker
-
for(int i=0; i<ids.size(); i++)
-
cv::aruco::drawAxis(imageCopy, cameraMatrix, distCoeffs, rvecs[i], tvecs[i], 0.1);
-
}
-
cv::imshow("out", imageCopy);
-
char key = (char) cv::waitKey(waitTime);
-
if (key == 27)
-
break;
-
}
樣例視訊:
https://youtu.be/IsXWrcB_Hvs
完整的工程代碼包含在子產品樣例檔案夾中的
detect_markers.cpp
指令行:
-c="_path_/calib.txt" -d=10
Selecting a dictionary
aruco子產品提供了 Dictionary類來描述marker的字典。
除了marker大小和字典中的marker數目,字典還有一個很重要的參數,就是内部marker的距離。内部marker的距離是marker之間的最小距離,它決定了字典錯誤檢測和糾正能力。
) 一般而言,較小的字典大小和較大的marker大小将會産生更大的内部marker距離,反之亦然。但是,過大的Marker在檢測中更加困難,因為我們需要從圖像中提取出更多位的資訊。
例如,如果你的應用中僅僅需要10個marker,最好使用隻包含10個marker的字典,而不是包含1000個marker的字典。原因在于,由10個marker組成的字典将會有更大的内部Marker距離,是以,它的容錯性更強。
結果,aruco子產品包含了很多選擇marker字典的途徑,是以你可以讓你的系統變得更加健壯。
- 預定義的字典:
這是選擇字典最簡單的辦法。aruco子產品包含了一系列預定義的字典,涵蓋了不同的marker大小和marker數量。例如:
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_6X6_250);
DICT_6X6_250 是一個預定義的字典,它包含6x6位的marker,總共有250個marker。
在所有提供的字典中,我們推薦使用你選擇盡可能小的marker。例如,如果你需要6x6位的200個marker,選擇DICT_6X6_250要優于選擇DICT_6X6_1000。字典越小,内部距離就越大。
- 自動生成的字典:
我們可以針對想要的marker數量和位來生成字典,以得到最優的内部Marker距離。
cv::Ptr<cv::aruco::Dictionary> dictionary = cv::aruco::generateCustomDictionary(36, 5);
這将會産生一個由 36 個 5X5 位字典組成的标準字典。這個過程需要幾秒鐘,具體時間取決于你的參數(更大的字典和更多的位數會耗費更多的時間)
- 手動生成的字典:
最終,我們可以手動設定字典,友善做任何修改。為了做到這一點,我們需要手動給
Dictionary
對象參數指派。必須注意的是,除非你有一些特别的理由需要自己來生成字典,一般情況下我們推薦前面的任一種方案。
字典參數為:
-
class Dictionary {
-
public:
-
Mat bytesList;
-
int markerSize;
-
int maxCorrectionBits; // maximum number of bits that can be corrected
-
...
-
}
bytesList
是一個數組,它包含了所有marker代碼的資訊。markerSize是每個marker的次元(例如,參數為5代表5x5位)。最終,
maxCorrectionBits
是marker檢測中可校正的最大比特數,如果這個值過大,會得到大量的錯誤位置。
bytesList
中的每一行代表字典中的一個marker。但是,這些marker的資料并不以二進制形式存儲,而是以一種特殊的方式存儲,這樣可以簡化檢測的過程。
幸運的是,我們可以簡單地調用靜态方法
Dictionary::getByteListFromBits()
來轉換到這種形式。
示例:
-
cv::aruco::Dictionary dictionary;
-
// markers of 6x6 bits
-
dictionary.markerSize = 6;
-
// maximum number of bit corrections
-
dictionary.maxCorrectionBits = 3;
-
// lets create a dictionary of 100 markers
-
for(int i=0; i<100; i++)
-
{
-
// assume generateMarkerBits() generate a new marker in binary format, so that
-
// markerBits is a 6x6 matrix of CV_8UC1 type, only containing 0s and 1s
-
cv::Mat markerBits = generateMarkerBits();
-
cv::Mat markerCompressed = cv::aruco::Dictionary::getByteListFromBits(markerBits);
-
// add the marker as a new row
-
dictionary.bytesList.push_back(markerCompressed);
-
}
Detector Parameters
detectMarkers()的一個參數是DetectorParameters對象。這一對象包含了marker檢測過程中所有特定的選項。
在這一章節中,我們将介紹所有的參數。我們可以根據它們涉及的階段來給這些參數分類。
門檻值化
檢測的第一步是輸入圖像的門檻值化。
例如,上述樣例中的圖像門檻值化的結果如下:
Thresholded image
這一門檻值化過程由以下幾個參數決定:
-
,int adaptiveThreshWinSizeMin
,int adaptiveThreshWinSizeMax
int adaptiveThreshWinSizeStep
adaptiveThreshWinSizeMin
和
adaptiveThreshWinSizeMax
參數代表選擇的自适應門檻值視窗大小(以像素為機關)間隔(具體參見opencv的
threshold()
函數)。
參數adaptiveThreshWinSizeStep表明了視窗從adaptiveThreshWinSizeMin到adaptiveThreshWinSizeMax大小的增量。
例如,對于adaptiveThreshWinSizeMin=5,adaptiveThreshWinSizeMax=21以及adaptiveThreshWinSizeStep=4,那麼将會産生5個門檻值化步驟,視窗大小分别為5, 9, 13, 17 和 21。在每個門檻值化圖像中,都會選出一些marker候選。
如果marker大小太大的話,較小的視窗大小可能會切割marker的邊界,是以它将不會被檢測到,就像下圖一樣:
Broken marker image
另一方面,如果marker太小的話,較大的視窗大小也會有類似的效果。此外這一過程将會趨向于全局門檻值,而失去了自适應的特性。
最簡單的例子是對adaptiveThreshWinSizeMin和
adaptiveThreshWinSizeMax
使用相同的值,這樣就隻會執行一次門檻值化步驟。但是,最好還是使用一個範圍的值作為視窗大小,雖然較多的門檻值化步驟會在一定程度上降低性能。
預設參數:
adaptiveThreshWinSizeMin
: 3,
adaptiveThreshWinSizeMax
: 23,
adaptiveThreshWinSizeStep
: 10
-
double adaptiveThreshConstant
這一參數表達了門檻值狀态下的常量(參見Opencv函數)。它的預設值是大多數例子下較好的情況。
預設值: 7
輪廓濾波
門檻值化之後,我們需要檢測輪廓。但是,我們并不會把所有的輪廓都當作是候選。在不同步驟中,我們通過濾波剔除一些不太可能是marker的輪廓。這一章節中的參數可以自定義這一過程。
需要注意到,大多數例子中我們需要平衡檢測的性能和效率。所有考慮到的輪廓都會在接下來的過程中做進一步處理,這通常産生了更高的計算消耗。是以,我們希望能夠在這一階段就丢棄錯誤的候選,而不是下一階段繼續處理。
另一方面,如果濾波的條件過于苛刻,事實上的marker輪廓可能會被錯誤地剔除,是以,沒有檢測到marker。
-
,double minMarkerPerimeterRate
double maxMarkerPerimeterRate
這些參數決定了marker的最小值和最大值,具體來說,是最大最小marker的周長。它們并不是以絕對像素值作為機關,而是相對于輸入圖檔的最大尺寸指定的。
例如,大小為640x480,最小相對marker周長為0.05的圖像,将會産生一個最小周長640x0.05= 32(像素)的marker,因為640是圖像的最大尺寸。參數
maxMarkerPerimeterRate
也是類似的。
如果
minMarkerPerimeterRate
太小,檢測階段性能會降低,因為會有更多的輪廓進入到接下來的階段。這一弊端對于
maxMarkerPerimeterRate
參數而言不是那麼顯著,因為小的輪廓數目通常要多于大的輪廓。選取
minMarkerPerimeterRate
值為0以及值為4,就相當于考慮了圖像中的所有輪廓,但是出于性能考慮這是不推薦的。
預設值:
Default values:
minMarkerPerimeterRate
:0.03,
maxMarkerPerimeterRate
: 4.0
-
double polygonalApproxAccuracyRate
我們對所有的候選進行多邊形近似,隻有近似結果為方形的形狀才能通過測試。這一值決定了多邊形近似産生的最大誤差(參見
approxPolyDP()
函數)。
這一參數是相對于候選長度的(像素上)。是以如果候選的周長為100像素,polygonalApproxAccuracyRate的值為0.04,那麼最大的誤差應當為100x0.04=5.4像素。
在大多例子中,預設參數的表現已經很好了,但對高失真的圖像,我們需要更大的誤內插補點。
預設值:0.05
-
double minCornerDistanceRate
同一張marker中每一對角的最小距離。這是相對于marker周長的值。像素的最小距離為Perimeter *minCornerDistanceRate.
預設值: 0.05
-
double minMarkerDistanceRate
兩張不同的marker之間的任一對角的最小距離。它表示相對于兩個marker的最小标記周長。如果兩個候選太接近,較小的一個被忽略。
預設值:0.05
-
int minDistanceToBorder
marker角到圖像邊緣最小距離。部分圖像邊緣被遮擋的marker也能被正确地檢測出來,如果遮擋部分比較小的話。但是,如果其中一個角被擋住了,傳回的角通常在圖像邊界的一個錯誤的位置。
如果marker角的位置很重要的話,例如你想要做pose檢測,最好舍棄掉那些離圖像邊緣太接近的角。否則就沒有必要。
預設值:3
比特位提取
檢測到候選之後,我們需要分析每個候選的比特位,來确定它們是不是marker。
在分析二進制編碼之前,我們需要提取出比特位。為了達到這個目的,将對透視變換後的圖像使用Otsu進行門檻值化,來分離黑色和白色像素。
以下是一個透視變換後的圖像:
Perspective removing
接下來,圖像被劃分為網格,和marker位數相同。在每個單元格裡,我們統計黑色和白色的個數,決定這個單元格的比特位。
Marker cells
以下參數可以自定義這一過程:
-
int markerBorderBits
這一參數指定了marker邊界的寬度。這和每個比特位的大小相關。是以,值為2意味着邊界的長度是兩個内部比特位的長度。
這一參數需要和你使用的Marker邊界大小一緻,邊界的大小可以在繪制函數如
drawMarker()
中設定。
預設值:1
-
double minOtsuStdDev
這個值決定了進行Otsu的最小标準差的像素值。如果偏差很低,這可能意味着所有方形都是黑色的(或白色的),Ostu将不起作用。如果是這樣的話,所有的比特位都根據平均值大于還是小于128被設為0或者1.
預設值:5.0
-
int perpectiveRemovePixelPerCell
這一參數決定了透視變換後圖像的像素數目(每個單元格,包含邊界)。這是上圖中紅色正方形的大小。
例如,讓我們假設我們在處理5x5比特位、邊界為1比特位的marker(參見markerBorderBit)。然後,每一維的單元格/比特位的個數為:5 + 2* 1 = 7(邊界需要被統計2次)。單元格總體大小為:7x7。
如果perpectiveRemovePixelPerCell的值為10,那麼擷取到的圖像大小為10*7 = 70 -> 70x70
這一參數選擇更大的值可以提升比特位的提取過程(在某一程度上),但是它同樣也降低了性能。
預設值:4
-
double perspectiveRemoveIgnoredMarginPerCell
當提取每個單元格的比特位時,需要統計黑色和白色的像素個數。一般而言,我們不推薦考慮單元格的所有像素。反之,最好忽略單元格的一些像素。
原因在于,透視變換之後,單元格的顔色不會完全分離,白色的單元格可能會混入一些黑色的單元格(反之亦然)。是以,最好忽略這些像素,以避免錯誤的像素計數。
例如,以下圖像:
Marker cell margins
我們隻考慮處在綠色正方形中的像素。我們可以在右邊的圖像中看到,最終的像素包含了鄰域單元格更少的噪聲。參數
perspectiveRemoveIgnoredMarginPerCell
指明了紅色和綠色正方形之間的距離。
這一參數是相對于單元格整體的大小的。例如,如果單元格的大小為40像素,這一參數的值為0.1,那麼大小為40*0.1=4像素的邊界将被剔除。這意味着每個單元格實際上要分析的像素大小為32x32,而不是40x40。
預設值:0.13
Marker ID
比特位提取之後,接下來的步驟是檢查提取的編碼是否屬于這個marker字典,有必要的話,還需要做錯誤檢測步驟。
-
double maxErroneousBitsInBorderRate
marker邊界的比特位應當是黑色的。這一參數指明了允許的邊界出錯比特位的個數。如,邊界可以出現的白色比特位的最大值。它的大小相對于marker中的比特位總數。
預設值:0.35
-
double errorCorrectionRate
每個marker字典有一位可以糾正的理論最大值(Dictionary.maxCorrectionBits)。但是,這個值可以由
errorCorrectionRate
參數來修改。
例如,如果允許糾正的比特位(對于使用的字典)數目為6,
errorCorrectionRate
的值為0.5,那麼實際上最大的可以糾正的比特位個數為6*0.5=3
這一值對減少錯誤容忍率以避免錯誤的位置識别很有幫助。
預設值:0.6
角落細化(Corner Refinement)
當我們檢測完marker,并且驗證了它們的id之後,最後要做的一步是在角落處的亞像素級的細化(參見OpenCV
cornerSubPix()
)
注意,這一步是可選的,僅在我們對marker角位置的準确性要求很高時才有意義。例如,pose的檢測。這一步驟很耗費時間,是以預設下是不做的。
-
bool doCornerRefinement
這一參數決定了是否要進行角落亞像素級細化過程,如果對角點的準确性要求不高,可以不進行這一過程。
預設值:false
-
int cornerRefinementWinSize
這一參數決定了亞像素級細化過程的視窗大小。
較大的值可以産生視窗區域内比較靠近的圖像角,marker角會移動到一個不同的錯誤的地方。除此之外這還會影響到性能。
預設值:5
-
,int cornerRefinementMaxIterations
double cornerRefinementMinAccuracy
這兩個值決定了亞像素級細化過程的結束條件。cornerRefinementMaxIterations指明了疊代的最大次數,
cornerRefinementMinAccuracy
是結束這一過程前的最小錯誤值。
如果疊代次數過高,這會影響到性能。此外,如果太小的話,亞像素級細化就基本沒有發揮作用。
預設值:
cornerRefinementMaxIterations
: 30,
cornerRefinementMinAccuracy
: 0.1