Skip to content

Commit 51078a5

Browse files
Alexander Panovurbste
andauthored
Merge pull request #3151 from AleksandrPanov:rebase_aruco_speedup
Feature/aruco speedup (rebased with 4.x) * initial commit of new aruco functionality * add performance FHD tile tests and fix pyramid bugs * remove global ArUco3 params (threshold) * add ArUco params to test classes Co-authored-by: Steffen Urban <[email protected]>
1 parent 30156e9 commit 51078a5

File tree

8 files changed

+577
-79
lines changed

8 files changed

+577
-79
lines changed

modules/aruco/include/opencv2/aruco.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,24 @@ enum CornerRefineMethod{
146146
* Parameter is the standard deviation in pixels. Very noisy images benefit from non-zero values (e.g. 0.8). (default 0.0)
147147
* - detectInvertedMarker: to check if there is a white marker. In order to generate a "white" marker just
148148
* invert a normal marker by using a tilde, ~markerImage. (default false)
149+
* - useAruco3Detection: to enable the new and faster Aruco detection strategy. The most important observation from the authors of
150+
* Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018) is, that the binary
151+
* code of a marker can be reliably detected if the canonical image (that is used to extract the binary code)
152+
* has a size of minSideLengthCanonicalImg (in practice tau_c=16-32 pixels).
153+
* Link to article: https://www.researchgate.net/publication/325787310_Speeded_Up_Detection_of_Squared_Fiducial_Markers
154+
* In addition, very small markers are barely useful for pose estimation and thus a we can define a minimum marker size that we
155+
* still want to be able to detect (e.g. 50x50 pixel).
156+
* To decouple this from the initial image size they propose to resize the input image
157+
* to (I_w_r, I_h_r) = (tau_c / tau_dot_i) * (I_w, I_h), with tau_dot_i = tau_c + max(I_w,I_h) * tau_i.
158+
* Here tau_i (parameter: minMarkerLengthRatioOriginalImg) is a ratio in the range [0,1].
159+
* If we set this to 0, the smallest marker we can detect
160+
* has a side length of tau_c. If we set it to 1 the marker would fill the entire image.
161+
* For a FullHD video a good value to start with is 0.1.
162+
* - minSideLengthCanonicalImg: minimum side length of a marker in the canonical image.
163+
* Latter is the binarized image in which contours are searched.
164+
* So all contours with a size smaller than minSideLengthCanonicalImg*minSideLengthCanonicalImg will omitted from the search.
165+
* - minMarkerLengthRatioOriginalImg: range [0,1], eq (2) from paper
166+
* The parameter tau_i has a direct influence on the processing speed.
149167
*/
150168
struct CV_EXPORTS_W DetectorParameters {
151169

@@ -188,6 +206,12 @@ struct CV_EXPORTS_W DetectorParameters {
188206

189207
// to detect white (inverted) markers
190208
CV_PROP_RW bool detectInvertedMarker;
209+
210+
// New Aruco functionality proposed in the paper:
211+
// Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018)
212+
CV_PROP_RW bool useAruco3Detection;
213+
CV_PROP_RW int minSideLengthCanonicalImg;
214+
CV_PROP_RW float minMarkerLengthRatioOriginalImg;
191215
};
192216

193217

modules/aruco/perf/perf_aruco.cpp

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html
4+
#include "perf_precomp.hpp"
5+
6+
namespace opencv_test {
7+
using namespace perf;
8+
9+
typedef tuple<bool, int> UseArucoParams;
10+
typedef TestBaseWithParam<UseArucoParams> EstimateAruco;
11+
#define ESTIMATE_PARAMS Combine(Values(false, true), Values(-1))
12+
13+
static double deg2rad(double deg) { return deg * CV_PI / 180.; }
14+
15+
class MarkerPainter
16+
{
17+
private:
18+
int imgMarkerSize = 0;
19+
Mat cameraMatrix;
20+
public:
21+
MarkerPainter(const int size)
22+
{
23+
setImgMarkerSize(size);
24+
}
25+
26+
void setImgMarkerSize(const int size)
27+
{
28+
imgMarkerSize = size;
29+
cameraMatrix = Mat::eye(3, 3, CV_64FC1);
30+
cameraMatrix.at<double>(0, 0) = cameraMatrix.at<double>(1, 1) = imgMarkerSize;
31+
cameraMatrix.at<double>(0, 2) = imgMarkerSize / 2.0;
32+
cameraMatrix.at<double>(1, 2) = imgMarkerSize / 2.0;
33+
}
34+
35+
static std::pair<Mat, Mat> getSyntheticRT(double yaw, double pitch, double distance)
36+
{
37+
auto rvec_tvec = std::make_pair(Mat(3, 1, CV_64FC1), Mat(3, 1, CV_64FC1));
38+
Mat& rvec = rvec_tvec.first;
39+
Mat& tvec = rvec_tvec.second;
40+
41+
// Rvec
42+
// first put the Z axis aiming to -X (like the camera axis system)
43+
Mat rotZ(3, 1, CV_64FC1);
44+
rotZ.ptr<double>(0)[0] = 0;
45+
rotZ.ptr<double>(0)[1] = 0;
46+
rotZ.ptr<double>(0)[2] = -0.5 * CV_PI;
47+
48+
Mat rotX(3, 1, CV_64FC1);
49+
rotX.ptr<double>(0)[0] = 0.5 * CV_PI;
50+
rotX.ptr<double>(0)[1] = 0;
51+
rotX.ptr<double>(0)[2] = 0;
52+
53+
Mat camRvec, camTvec;
54+
composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)),
55+
camRvec, camTvec);
56+
57+
// now pitch and yaw angles
58+
Mat rotPitch(3, 1, CV_64FC1);
59+
rotPitch.ptr<double>(0)[0] = 0;
60+
rotPitch.ptr<double>(0)[1] = pitch;
61+
rotPitch.ptr<double>(0)[2] = 0;
62+
63+
Mat rotYaw(3, 1, CV_64FC1);
64+
rotYaw.ptr<double>(0)[0] = yaw;
65+
rotYaw.ptr<double>(0)[1] = 0;
66+
rotYaw.ptr<double>(0)[2] = 0;
67+
68+
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
69+
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
70+
71+
// compose both rotations
72+
composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec,
73+
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
74+
75+
// Tvec, just move in z (camera) direction the specific distance
76+
tvec.ptr<double>(0)[0] = 0.;
77+
tvec.ptr<double>(0)[1] = 0.;
78+
tvec.ptr<double>(0)[2] = distance;
79+
return rvec_tvec;
80+
}
81+
82+
std::pair<Mat, vector<Point2f> > getProjectMarker(int id, double yaw, double pitch,
83+
const Ptr<aruco::DetectorParameters>& parameters,
84+
const Ptr<aruco::Dictionary>& dictionary)
85+
{
86+
auto marker_corners = std::make_pair(Mat(imgMarkerSize, imgMarkerSize, CV_8UC1, Scalar::all(255)), vector<Point2f>());
87+
Mat& img = marker_corners.first;
88+
vector<Point2f>& corners = marker_corners.second;
89+
90+
// canonical image
91+
const int markerSizePixels = static_cast<int>(imgMarkerSize/sqrt(2.f));
92+
aruco::drawMarker(dictionary, id, markerSizePixels, img, parameters->markerBorderBits);
93+
94+
// get rvec and tvec for the perspective
95+
const double distance = 0.1;
96+
auto rvec_tvec = MarkerPainter::getSyntheticRT(yaw, pitch, distance);
97+
Mat& rvec = rvec_tvec.first;
98+
Mat& tvec = rvec_tvec.second;
99+
100+
const float markerLength = 0.05f;
101+
vector<Point3f> markerObjPoints;
102+
markerObjPoints.emplace_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0));
103+
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, 0, 0));
104+
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0));
105+
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(0, -markerLength, 0));
106+
107+
// project markers and draw them
108+
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
109+
projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
110+
111+
vector<Point2f> originalCorners;
112+
originalCorners.emplace_back(Point2f(0.f, 0.f));
113+
originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, 0));
114+
originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels));
115+
originalCorners.emplace_back(originalCorners[0]+Point2f(0, (float)markerSizePixels));
116+
117+
Mat transformation = getPerspectiveTransform(originalCorners, corners);
118+
119+
warpPerspective(img, img, transformation, Size(imgMarkerSize, imgMarkerSize), INTER_NEAREST, BORDER_CONSTANT,
120+
Scalar::all(255));
121+
return marker_corners;
122+
}
123+
124+
std::pair<Mat, map<int, vector<Point2f> > > getProjectMarkersTile(const int numMarkers,
125+
const Ptr<aruco::DetectorParameters>& params,
126+
const Ptr<aruco::Dictionary>& dictionary)
127+
{
128+
Mat tileImage(imgMarkerSize*numMarkers, imgMarkerSize*numMarkers, CV_8UC1, Scalar::all(255));
129+
map<int, vector<Point2f> > idCorners;
130+
131+
int iter = 0, pitch = 0, yaw = 0;
132+
for (int i = 0; i < numMarkers; i++)
133+
{
134+
for (int j = 0; j < numMarkers; j++)
135+
{
136+
int currentId = iter;
137+
auto marker_corners = getProjectMarker(currentId, deg2rad(70+yaw), deg2rad(pitch), params, dictionary);
138+
Point2i startPoint(j*imgMarkerSize, i*imgMarkerSize);
139+
Mat tmp_roi = tileImage(Rect(startPoint.x, startPoint.y, imgMarkerSize, imgMarkerSize));
140+
marker_corners.first.copyTo(tmp_roi);
141+
142+
for (Point2f& point: marker_corners.second)
143+
point += static_cast<Point2f>(startPoint);
144+
idCorners[currentId] = marker_corners.second;
145+
auto test = idCorners[currentId];
146+
yaw = (yaw + 10) % 51; // 70+yaw >= 70 && 70+yaw <= 120
147+
iter++;
148+
}
149+
pitch = (pitch + 60) % 360;
150+
}
151+
return std::make_pair(tileImage, idCorners);
152+
}
153+
};
154+
155+
static inline double getMaxDistance(map<int, vector<Point2f> > &golds, const vector<int>& ids,
156+
const vector<vector<Point2f> >& corners)
157+
{
158+
std::map<int, double> mapDist;
159+
for (const auto& el : golds)
160+
mapDist[el.first] = std::numeric_limits<double>::max();
161+
for (size_t i = 0; i < ids.size(); i++)
162+
{
163+
int id = ids[i];
164+
const auto gold_corners = golds.find(id);
165+
if (gold_corners != golds.end()) {
166+
double distance = 0.;
167+
for (int c = 0; c < 4; c++)
168+
distance = std::max(distance, cv::norm(gold_corners->second[c] - corners[i][c]));
169+
mapDist[id] = distance;
170+
}
171+
}
172+
return std::max_element(std::begin(mapDist), std::end(mapDist),
173+
[](const pair<int, double>& p1, const pair<int, double>& p2){return p1.second < p2.second;})->second;
174+
}
175+
176+
PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS)
177+
{
178+
UseArucoParams testParams = GetParam();
179+
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
180+
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
181+
detectorParams->minDistanceToBorder = 1;
182+
detectorParams->markerBorderBits = 1;
183+
detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
184+
185+
const int markerSize = 100;
186+
const int numMarkersInRow = 9;
187+
//USE_ARUCO3
188+
detectorParams->useAruco3Detection = get<0>(testParams);
189+
if (detectorParams->useAruco3Detection) {
190+
detectorParams->minSideLengthCanonicalImg = 32;
191+
detectorParams->minMarkerLengthRatioOriginalImg = 0.04f / numMarkersInRow;
192+
}
193+
MarkerPainter painter(markerSize);
194+
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
195+
196+
// detect markers
197+
vector<vector<Point2f> > corners;
198+
vector<int> ids;
199+
TEST_CYCLE()
200+
{
201+
aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams);
202+
}
203+
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
204+
double maxDistance = getMaxDistance(image_map.second, ids, corners);
205+
ASSERT_LT(maxDistance, 3.);
206+
SANITY_CHECK_NOTHING();
207+
}
208+
209+
PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS)
210+
{
211+
UseArucoParams testParams = GetParam();
212+
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
213+
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
214+
detectorParams->minDistanceToBorder = 1;
215+
detectorParams->markerBorderBits = 1;
216+
detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
217+
218+
//USE_ARUCO3
219+
detectorParams->useAruco3Detection = get<0>(testParams);
220+
if (detectorParams->useAruco3Detection) {
221+
detectorParams->minSideLengthCanonicalImg = 64;
222+
detectorParams->minMarkerLengthRatioOriginalImg = 0.f;
223+
}
224+
const int markerSize = 200;
225+
const int numMarkersInRow = 11;
226+
MarkerPainter painter(markerSize);
227+
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
228+
229+
// detect markers
230+
vector<vector<Point2f> > corners;
231+
vector<int> ids;
232+
TEST_CYCLE()
233+
{
234+
aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams);
235+
}
236+
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
237+
double maxDistance = getMaxDistance(image_map.second, ids, corners);
238+
ASSERT_LT(maxDistance, 3.);
239+
SANITY_CHECK_NOTHING();
240+
}
241+
242+
struct Aruco3Params
243+
{
244+
bool useAruco3Detection = false;
245+
float minMarkerLengthRatioOriginalImg = 0.f;
246+
int minSideLengthCanonicalImg = 0;
247+
248+
Aruco3Params(bool useAruco3, float minMarkerLen, int minSideLen): useAruco3Detection(useAruco3),
249+
minMarkerLengthRatioOriginalImg(minMarkerLen),
250+
minSideLengthCanonicalImg(minSideLen) {}
251+
friend std::ostream& operator<<(std::ostream& os, const Aruco3Params& d)
252+
{
253+
os << d.useAruco3Detection << " " << d.minMarkerLengthRatioOriginalImg << " " << d.minSideLengthCanonicalImg;
254+
return os;
255+
}
256+
};
257+
typedef tuple<Aruco3Params, pair<int, int>> ArucoTestParams;
258+
259+
typedef TestBaseWithParam<ArucoTestParams> EstimateLargeAruco;
260+
#define ESTIMATE_FHD_PARAMS Combine(Values(Aruco3Params(false, 0.f, 0), Aruco3Params(true, 0.f, 32), \
261+
Aruco3Params(true, 0.015f, 32), Aruco3Params(true, 0.f, 16), Aruco3Params(true, 0.0069f, 16)), \
262+
Values(std::make_pair(1440, 1), std::make_pair(480, 3), std::make_pair(144, 10)))
263+
264+
PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS)
265+
{
266+
ArucoTestParams testParams = GetParam();
267+
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
268+
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
269+
detectorParams->minDistanceToBorder = 1;
270+
detectorParams->markerBorderBits = 1;
271+
detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
272+
273+
//USE_ARUCO3
274+
detectorParams->useAruco3Detection = get<0>(testParams).useAruco3Detection;
275+
if (detectorParams->useAruco3Detection) {
276+
detectorParams->minSideLengthCanonicalImg = get<0>(testParams).minSideLengthCanonicalImg;
277+
detectorParams->minMarkerLengthRatioOriginalImg = get<0>(testParams).minMarkerLengthRatioOriginalImg;
278+
}
279+
const int markerSize = get<1>(testParams).first; // 1440 or 480 or 144
280+
const int numMarkersInRow = get<1>(testParams).second; // 1 or 3 or 144
281+
MarkerPainter painter(markerSize); // num pixels is 1440x1440 as in FHD 1920x1080
282+
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
283+
284+
// detect markers
285+
vector<vector<Point2f> > corners;
286+
vector<int> ids;
287+
TEST_CYCLE()
288+
{
289+
aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams);
290+
}
291+
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
292+
double maxDistance = getMaxDistance(image_map.second, ids, corners);
293+
ASSERT_LT(maxDistance, 3.);
294+
SANITY_CHECK_NOTHING();
295+
}
296+
297+
}

modules/aruco/perf/perf_main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include "perf_precomp.hpp"
2+
3+
CV_PERF_TEST_MAIN(aruco)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// This file is part of OpenCV project.
2+
// It is subject to the license terms in the LICENSE file found in the top-level directory
3+
// of this distribution and at http://opencv.org/license.html
4+
#ifndef __OPENCV_PERF_PRECOMP_HPP__
5+
#define __OPENCV_PERF_PRECOMP_HPP__
6+
7+
#include "opencv2/ts.hpp"
8+
#include "opencv2/aruco.hpp"
9+
#include "opencv2/calib3d.hpp"
10+
11+
#endif

modules/aruco/samples/detector_params.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ perspectiveRemoveIgnoredMarginPerCell: 0.13
2121
maxErroneousBitsInBorderRate: 0.04
2222
minOtsuStdDev: 5.0
2323
errorCorrectionRate: 0.6
24+
25+
# new aruco 3 functionality
26+
useAruco3Detection: 0
27+
minSideLengthCanonicalImg: 32 # 16, 32, 64 --> tau_c from the paper
28+
minMarkerLengthRatioOriginalImg: 0.02 # range [0,0.2] --> tau_i from the paper
29+
cameraMotionSpeed: 0.1 # range [0,1) --> tau_s from the paper
30+
useGlobalThreshold: 0

0 commit comments

Comments
 (0)