Merge branch 4.x

pull/3179/head
Alexander Alekhin 3 years ago
commit cbd4bd7e7c
  1. 26
      .github/ISSUE_TEMPLATE.md
  2. 6
      .github/PULL_REQUEST_TEMPLATE.md
  3. 8
      README.md
  4. 24
      modules/aruco/include/opencv2/aruco.hpp
  5. 297
      modules/aruco/perf/perf_aruco.cpp
  6. 3
      modules/aruco/perf/perf_main.cpp
  7. 11
      modules/aruco/perf/perf_precomp.hpp
  8. 7
      modules/aruco/samples/detector_params.yml
  9. 239
      modules/aruco/src/aruco.cpp
  10. 47
      modules/aruco/test/test_arucodetection.cpp
  11. 56
      modules/aruco/test/test_boarddetection.cpp
  12. 31
      modules/aruco/test/test_charucodetection.cpp
  13. 104
      modules/cudaarithm/test/test_event.cpp
  14. 43
      modules/cudaarithm/test/test_gpumat.cpp
  15. 2
      modules/cudafilters/src/filtering.cpp
  16. 10
      modules/cudaimgproc/doc/cudaimgproc.bib
  17. 44
      modules/cudaimgproc/include/opencv2/cudaimgproc.hpp
  18. 58
      modules/cudaimgproc/samples/connected_components.cpp
  19. 49
      modules/cudaimgproc/src/connectedcomponents.cpp
  20. 345
      modules/cudaimgproc/src/cuda/connectedcomponents.cu
  21. 479
      modules/cudaimgproc/test/test_connectedcomponents.cpp
  22. 5
      modules/line_descriptor/src/binary_descriptor.cpp
  23. 27
      modules/ovis/include/opencv2/ovis.hpp
  24. 17
      modules/ovis/src/ovis.cpp
  25. 2
      modules/sfm/src/libmv_light/libmv/correspondence/CMakeLists.txt
  26. 58
      modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp
  27. 7
      modules/ximgproc/src/edge_drawing.cpp
  28. 3
      modules/xphoto/src/inpainting.cpp
  29. 2
      modules/xphoto/src/inpainting_fsr.impl.hpp
  30. 6
      modules/xphoto/src/photomontage.hpp

@ -1,3 +1,10 @@
<!--
If you have a question rather than reporting a bug please go to https://forum.opencv.org where you get much faster responses.
If you need further assistance please read [How To Contribute](https://github.com/opencv/opencv/wiki/How_to_contribute).
This is a template helping you to create an issue which can be processed as quickly as possible. This is the bug reporting section for the OpenCV library.
-->
##### System information (version)
<!-- Example
- OpenCV => 4.2
@ -26,26 +33,25 @@
- [ ] I report the issue, it's not a question
<!--
OpenCV team works with answers.opencv.org, Stack Overflow and other communities
to discuss problems. Tickets with question without real issue statement will be
OpenCV team works with forum.opencv.org, Stack Overflow and other communities
to discuss problems. Tickets with questions without a real issue statement will be
closed.
-->
- [ ] I checked the problem with documentation, FAQ, open issues,
answers.opencv.org, Stack Overflow, etc and have not found solution
forum.opencv.org, Stack Overflow, etc and have not found any solution
<!--
Places to check:
* OpenCV documentation: https://docs.opencv.org
* FAQ page: https://github.com/opencv/opencv/wiki/FAQ
* OpenCV forum: https://answers.opencv.org
* OpenCV forum: https://forum.opencv.org
* OpenCV issue tracker: https://github.com/opencv/opencv/issues?q=is%3Aissue
* OpenCV Contrib issue tracker: https://github.com/opencv/opencv_contrib/issues?q=is%3Aissue
* Stack Overflow branch: https://stackoverflow.com/questions/tagged/opencv
-->
- [ ] I updated to latest OpenCV version and the issue is still there
- [ ] I updated to the latest OpenCV version and the issue is still there
<!--
master branch for OpenCV 4.x and 3.4 branch for OpenCV 3.x releases.
OpenCV team supports only latest release for each branch.
The ticket is closed, if the problem is not reproduced with modern version.
OpenCV team supports only the latest release for each branch.
The ticket is closed if the problem is not reproduced with the modern version.
-->
- [ ] There is reproducer code and related data files: videos, images, onnx, etc
<!--
@ -55,9 +61,9 @@
to reduce attachment size
* Use PNG for images, if you report some CV related bug, but not image reader
issue
* Attach the image as archive to the ticket, if you report some reader issue.
* Attach the image as an archive to the ticket, if you report some reader issue.
Image hosting services compress images and it breaks the repro code.
* Provide ONNX file for some public model or ONNX file with with random weights,
* Provide ONNX file for some public model or ONNX file with random weights,
if you report ONNX parsing or handling issue. Architecture details diagram
from netron tool can be very useful too. See https://lutzroeder.github.io/netron/
-->

@ -3,9 +3,9 @@
See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request
- [ ] I agree to contribute to the project under Apache 2 License.
- [ ] To the best of my knowledge, the proposed patch is not based on a code under GPL or other license that is incompatible with OpenCV
- [ ] The PR is proposed to proper branch
- [ ] There is reference to original bug report and related work
- [ ] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [ ] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake

@ -2,7 +2,7 @@
This repository is intended for the development of so-called "extra" modules,
contributed functionality. New modules quite often do not have stable API,
and they are not well-tested. Thus, they shouldn't be released as a part of
and they are not well-tested. Thus, they shouldn't be released as a part of the
official OpenCV distribution, since the library maintains binary compatibility,
and tries to provide decent performance and stability.
@ -33,7 +33,7 @@ $ cmake -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules -DBUILD_opencv_lega
If you also want to build the samples from the "samples" folder of each module, also include the "-DBUILD_EXAMPLES=ON" option.
If you prefer using the gui version of cmake (cmake-gui), then, you can add `opencv_contrib` modules within `opencv` core by doing the following:
If you prefer using the GUI version of CMake (cmake-gui), then, you can add `opencv_contrib` modules within `opencv` core by doing the following:
1. Start cmake-gui.
@ -55,6 +55,6 @@ If you prefer using the gui version of cmake (cmake-gui), then, you can add `ope
In order to keep a clean overview containing all contributed modules, the following files need to be created/adapted:
1. Update the README.md file under the modules folder. Here, you add your model with a single line description.
1. Update the README.md file under the modules folder. Here, you add your model with a single-line description.
2. Add a README.md inside your own module folder. This README explains which functionality (separate functions) is available, links to the corresponding samples and explains in somewhat more detail what the module is expected to do. If any extra requirements are needed to build the module without problems, add them here also.
2. Add a README.md inside your own module folder. This README explains which functionality (separate functions) is available, links to the corresponding samples, and explains in somewhat more detail what the module is expected to do. If any extra requirements are needed to build the module without problems, add them here also.

@ -146,6 +146,24 @@ enum CornerRefineMethod{
* Parameter is the standard deviation in pixels. Very noisy images benefit from non-zero values (e.g. 0.8). (default 0.0)
* - detectInvertedMarker: to check if there is a white marker. In order to generate a "white" marker just
* invert a normal marker by using a tilde, ~markerImage. (default false)
* - useAruco3Detection: to enable the new and faster Aruco detection strategy. The most important observation from the authors of
* Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018) is, that the binary
* code of a marker can be reliably detected if the canonical image (that is used to extract the binary code)
* has a size of minSideLengthCanonicalImg (in practice tau_c=16-32 pixels).
* Link to article: https://www.researchgate.net/publication/325787310_Speeded_Up_Detection_of_Squared_Fiducial_Markers
* In addition, very small markers are barely useful for pose estimation and thus a we can define a minimum marker size that we
* still want to be able to detect (e.g. 50x50 pixel).
* To decouple this from the initial image size they propose to resize the input image
* 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.
* Here tau_i (parameter: minMarkerLengthRatioOriginalImg) is a ratio in the range [0,1].
* If we set this to 0, the smallest marker we can detect
* has a side length of tau_c. If we set it to 1 the marker would fill the entire image.
* For a FullHD video a good value to start with is 0.1.
* - minSideLengthCanonicalImg: minimum side length of a marker in the canonical image.
* Latter is the binarized image in which contours are searched.
* So all contours with a size smaller than minSideLengthCanonicalImg*minSideLengthCanonicalImg will omitted from the search.
* - minMarkerLengthRatioOriginalImg: range [0,1], eq (2) from paper
* The parameter tau_i has a direct influence on the processing speed.
*/
struct CV_EXPORTS_W DetectorParameters {
@ -188,6 +206,12 @@ struct CV_EXPORTS_W DetectorParameters {
// to detect white (inverted) markers
CV_PROP_RW bool detectInvertedMarker;
// New Aruco functionality proposed in the paper:
// Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018)
CV_PROP_RW bool useAruco3Detection;
CV_PROP_RW int minSideLengthCanonicalImg;
CV_PROP_RW float minMarkerLengthRatioOriginalImg;
};

@ -0,0 +1,297 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "perf_precomp.hpp"
namespace opencv_test {
using namespace perf;
typedef tuple<bool, int> UseArucoParams;
typedef TestBaseWithParam<UseArucoParams> EstimateAruco;
#define ESTIMATE_PARAMS Combine(Values(false, true), Values(-1))
static double deg2rad(double deg) { return deg * CV_PI / 180.; }
class MarkerPainter
{
private:
int imgMarkerSize = 0;
Mat cameraMatrix;
public:
MarkerPainter(const int size)
{
setImgMarkerSize(size);
}
void setImgMarkerSize(const int size)
{
imgMarkerSize = size;
cameraMatrix = Mat::eye(3, 3, CV_64FC1);
cameraMatrix.at<double>(0, 0) = cameraMatrix.at<double>(1, 1) = imgMarkerSize;
cameraMatrix.at<double>(0, 2) = imgMarkerSize / 2.0;
cameraMatrix.at<double>(1, 2) = imgMarkerSize / 2.0;
}
static std::pair<Mat, Mat> getSyntheticRT(double yaw, double pitch, double distance)
{
auto rvec_tvec = std::make_pair(Mat(3, 1, CV_64FC1), Mat(3, 1, CV_64FC1));
Mat& rvec = rvec_tvec.first;
Mat& tvec = rvec_tvec.second;
// Rvec
// first put the Z axis aiming to -X (like the camera axis system)
Mat rotZ(3, 1, CV_64FC1);
rotZ.ptr<double>(0)[0] = 0;
rotZ.ptr<double>(0)[1] = 0;
rotZ.ptr<double>(0)[2] = -0.5 * CV_PI;
Mat rotX(3, 1, CV_64FC1);
rotX.ptr<double>(0)[0] = 0.5 * CV_PI;
rotX.ptr<double>(0)[1] = 0;
rotX.ptr<double>(0)[2] = 0;
Mat camRvec, camTvec;
composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)),
camRvec, camTvec);
// now pitch and yaw angles
Mat rotPitch(3, 1, CV_64FC1);
rotPitch.ptr<double>(0)[0] = 0;
rotPitch.ptr<double>(0)[1] = pitch;
rotPitch.ptr<double>(0)[2] = 0;
Mat rotYaw(3, 1, CV_64FC1);
rotYaw.ptr<double>(0)[0] = yaw;
rotYaw.ptr<double>(0)[1] = 0;
rotYaw.ptr<double>(0)[2] = 0;
composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw,
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
// compose both rotations
composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec,
Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec);
// Tvec, just move in z (camera) direction the specific distance
tvec.ptr<double>(0)[0] = 0.;
tvec.ptr<double>(0)[1] = 0.;
tvec.ptr<double>(0)[2] = distance;
return rvec_tvec;
}
std::pair<Mat, vector<Point2f> > getProjectMarker(int id, double yaw, double pitch,
const Ptr<aruco::DetectorParameters>& parameters,
const Ptr<aruco::Dictionary>& dictionary)
{
auto marker_corners = std::make_pair(Mat(imgMarkerSize, imgMarkerSize, CV_8UC1, Scalar::all(255)), vector<Point2f>());
Mat& img = marker_corners.first;
vector<Point2f>& corners = marker_corners.second;
// canonical image
const int markerSizePixels = static_cast<int>(imgMarkerSize/sqrt(2.f));
aruco::drawMarker(dictionary, id, markerSizePixels, img, parameters->markerBorderBits);
// get rvec and tvec for the perspective
const double distance = 0.1;
auto rvec_tvec = MarkerPainter::getSyntheticRT(yaw, pitch, distance);
Mat& rvec = rvec_tvec.first;
Mat& tvec = rvec_tvec.second;
const float markerLength = 0.05f;
vector<Point3f> markerObjPoints;
markerObjPoints.emplace_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0));
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, 0, 0));
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0));
markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(0, -markerLength, 0));
// project markers and draw them
Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0));
projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners);
vector<Point2f> originalCorners;
originalCorners.emplace_back(Point2f(0.f, 0.f));
originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, 0));
originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels));
originalCorners.emplace_back(originalCorners[0]+Point2f(0, (float)markerSizePixels));
Mat transformation = getPerspectiveTransform(originalCorners, corners);
warpPerspective(img, img, transformation, Size(imgMarkerSize, imgMarkerSize), INTER_NEAREST, BORDER_CONSTANT,
Scalar::all(255));
return marker_corners;
}
std::pair<Mat, map<int, vector<Point2f> > > getProjectMarkersTile(const int numMarkers,
const Ptr<aruco::DetectorParameters>& params,
const Ptr<aruco::Dictionary>& dictionary)
{
Mat tileImage(imgMarkerSize*numMarkers, imgMarkerSize*numMarkers, CV_8UC1, Scalar::all(255));
map<int, vector<Point2f> > idCorners;
int iter = 0, pitch = 0, yaw = 0;
for (int i = 0; i < numMarkers; i++)
{
for (int j = 0; j < numMarkers; j++)
{
int currentId = iter;
auto marker_corners = getProjectMarker(currentId, deg2rad(70+yaw), deg2rad(pitch), params, dictionary);
Point2i startPoint(j*imgMarkerSize, i*imgMarkerSize);
Mat tmp_roi = tileImage(Rect(startPoint.x, startPoint.y, imgMarkerSize, imgMarkerSize));
marker_corners.first.copyTo(tmp_roi);
for (Point2f& point: marker_corners.second)
point += static_cast<Point2f>(startPoint);
idCorners[currentId] = marker_corners.second;
auto test = idCorners[currentId];
yaw = (yaw + 10) % 51; // 70+yaw >= 70 && 70+yaw <= 120
iter++;
}
pitch = (pitch + 60) % 360;
}
return std::make_pair(tileImage, idCorners);
}
};
static inline double getMaxDistance(map<int, vector<Point2f> > &golds, const vector<int>& ids,
const vector<vector<Point2f> >& corners)
{
std::map<int, double> mapDist;
for (const auto& el : golds)
mapDist[el.first] = std::numeric_limits<double>::max();
for (size_t i = 0; i < ids.size(); i++)
{
int id = ids[i];
const auto gold_corners = golds.find(id);
if (gold_corners != golds.end()) {
double distance = 0.;
for (int c = 0; c < 4; c++)
distance = std::max(distance, cv::norm(gold_corners->second[c] - corners[i][c]));
mapDist[id] = distance;
}
}
return std::max_element(std::begin(mapDist), std::end(mapDist),
[](const pair<int, double>& p1, const pair<int, double>& p2){return p1.second < p2.second;})->second;
}
PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS)
{
UseArucoParams testParams = GetParam();
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
detectorParams->minDistanceToBorder = 1;
detectorParams->markerBorderBits = 1;
detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
const int markerSize = 100;
const int numMarkersInRow = 9;
//USE_ARUCO3
detectorParams->useAruco3Detection = get<0>(testParams);
if (detectorParams->useAruco3Detection) {
detectorParams->minSideLengthCanonicalImg = 32;
detectorParams->minMarkerLengthRatioOriginalImg = 0.04f / numMarkersInRow;
}
MarkerPainter painter(markerSize);
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
// detect markers
vector<vector<Point2f> > corners;
vector<int> ids;
TEST_CYCLE()
{
aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams);
}
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
double maxDistance = getMaxDistance(image_map.second, ids, corners);
ASSERT_LT(maxDistance, 3.);
SANITY_CHECK_NOTHING();
}
PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS)
{
UseArucoParams testParams = GetParam();
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
detectorParams->minDistanceToBorder = 1;
detectorParams->markerBorderBits = 1;
detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
//USE_ARUCO3
detectorParams->useAruco3Detection = get<0>(testParams);
if (detectorParams->useAruco3Detection) {
detectorParams->minSideLengthCanonicalImg = 64;
detectorParams->minMarkerLengthRatioOriginalImg = 0.f;
}
const int markerSize = 200;
const int numMarkersInRow = 11;
MarkerPainter painter(markerSize);
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
// detect markers
vector<vector<Point2f> > corners;
vector<int> ids;
TEST_CYCLE()
{
aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams);
}
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
double maxDistance = getMaxDistance(image_map.second, ids, corners);
ASSERT_LT(maxDistance, 3.);
SANITY_CHECK_NOTHING();
}
struct Aruco3Params
{
bool useAruco3Detection = false;
float minMarkerLengthRatioOriginalImg = 0.f;
int minSideLengthCanonicalImg = 0;
Aruco3Params(bool useAruco3, float minMarkerLen, int minSideLen): useAruco3Detection(useAruco3),
minMarkerLengthRatioOriginalImg(minMarkerLen),
minSideLengthCanonicalImg(minSideLen) {}
friend std::ostream& operator<<(std::ostream& os, const Aruco3Params& d)
{
os << d.useAruco3Detection << " " << d.minMarkerLengthRatioOriginalImg << " " << d.minSideLengthCanonicalImg;
return os;
}
};
typedef tuple<Aruco3Params, pair<int, int>> ArucoTestParams;
typedef TestBaseWithParam<ArucoTestParams> EstimateLargeAruco;
#define ESTIMATE_FHD_PARAMS Combine(Values(Aruco3Params(false, 0.f, 0), Aruco3Params(true, 0.f, 32), \
Aruco3Params(true, 0.015f, 32), Aruco3Params(true, 0.f, 16), Aruco3Params(true, 0.0069f, 16)), \
Values(std::make_pair(1440, 1), std::make_pair(480, 3), std::make_pair(144, 10)))
PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS)
{
ArucoTestParams testParams = GetParam();
Ptr<aruco::Dictionary> dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250);
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
detectorParams->minDistanceToBorder = 1;
detectorParams->markerBorderBits = 1;
detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
//USE_ARUCO3
detectorParams->useAruco3Detection = get<0>(testParams).useAruco3Detection;
if (detectorParams->useAruco3Detection) {
detectorParams->minSideLengthCanonicalImg = get<0>(testParams).minSideLengthCanonicalImg;
detectorParams->minMarkerLengthRatioOriginalImg = get<0>(testParams).minMarkerLengthRatioOriginalImg;
}
const int markerSize = get<1>(testParams).first; // 1440 or 480 or 144
const int numMarkersInRow = get<1>(testParams).second; // 1 or 3 or 144
MarkerPainter painter(markerSize); // num pixels is 1440x1440 as in FHD 1920x1080
auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary);
// detect markers
vector<vector<Point2f> > corners;
vector<int> ids;
TEST_CYCLE()
{
aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams);
}
ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast<int>(ids.size()));
double maxDistance = getMaxDistance(image_map.second, ids, corners);
ASSERT_LT(maxDistance, 3.);
SANITY_CHECK_NOTHING();
}
}

@ -0,0 +1,3 @@
#include "perf_precomp.hpp"
CV_PERF_TEST_MAIN(aruco)

@ -0,0 +1,11 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef __OPENCV_PERF_PRECOMP_HPP__
#define __OPENCV_PERF_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/aruco.hpp"
#include "opencv2/calib3d.hpp"
#endif

@ -21,3 +21,10 @@ perspectiveRemoveIgnoredMarginPerCell: 0.13
maxErroneousBitsInBorderRate: 0.04
minOtsuStdDev: 5.0
errorCorrectionRate: 0.6
# new aruco 3 functionality
useAruco3Detection: 0
minSideLengthCanonicalImg: 32 # 16, 32, 64 --> tau_c from the paper
minMarkerLengthRatioOriginalImg: 0.02 # range [0,0.2] --> tau_i from the paper
cameraMotionSpeed: 0.1 # range [0,1) --> tau_s from the paper
useGlobalThreshold: 0

@ -46,6 +46,8 @@ the use of this software, even if advised of the possibility of such damage.
#include "apriltag_quad_thresh.hpp"
#include "zarray.hpp"
#include <cmath>
//#define APRIL_DEBUG
#ifdef APRIL_DEBUG
#include "opencv2/imgcodecs.hpp"
@ -89,7 +91,11 @@ DetectorParameters::DetectorParameters()
aprilTagMaxLineFitMse(10.0),
aprilTagMinWhiteBlackDiff(5),
aprilTagDeglitch(0),
detectInvertedMarker(false){}
detectInvertedMarker(false),
useAruco3Detection(false),
minSideLengthCanonicalImg(32),
minMarkerLengthRatioOriginalImg(0.0)
{}
/**
@ -139,6 +145,10 @@ bool DetectorParameters::readDetectorParameters(const FileNode& fn, Ptr<Detector
checkRead |= readParameter(fn["maxErroneousBitsInBorderRate"], params->maxErroneousBitsInBorderRate);
checkRead |= readParameter(fn["minOtsuStdDev"], params->minOtsuStdDev);
checkRead |= readParameter(fn["errorCorrectionRate"], params->errorCorrectionRate);
// new aruco 3 functionality
checkRead |= readParameter(fn["useAruco3Detection"], params->useAruco3Detection);
checkRead |= readParameter(fn["minSideLengthCanonicalImg"], params->minSideLengthCanonicalImg);
checkRead |= readParameter(fn["minMarkerLengthRatioOriginalImg"], params->minMarkerLengthRatioOriginalImg);
return checkRead;
}
@ -175,7 +185,7 @@ static void _threshold(InputArray _in, OutputArray _out, int winSize, double con
static void _findMarkerContours(InputArray _in, vector< vector< Point2f > > &candidates,
vector< vector< Point > > &contoursOut, double minPerimeterRate,
double maxPerimeterRate, double accuracyRate,
double minCornerDistanceRate, int minDistanceToBorder) {
double minCornerDistanceRate, int minDistanceToBorder, int minSize) {
CV_Assert(minPerimeterRate > 0 && maxPerimeterRate > 0 && accuracyRate > 0 &&
minCornerDistanceRate >= 0 && minDistanceToBorder >= 0);
@ -186,6 +196,11 @@ static void _findMarkerContours(InputArray _in, vector< vector< Point2f > > &can
unsigned int maxPerimeterPixels =
(unsigned int)(maxPerimeterRate * max(_in.getMat().cols, _in.getMat().rows));
// for aruco3 functionality
if (minSize != 0) {
minPerimeterPixels = 4*minSize;
}
Mat contoursImg;
_in.getMat().copyTo(contoursImg);
vector< vector< Point > > contours;
@ -424,10 +439,9 @@ static void _detectInitialCandidates(const Mat &grey, vector< vector< Point2f >
_findMarkerContours(thresh, candidatesArrays[i], contoursArrays[i],
params->minMarkerPerimeterRate, params->maxMarkerPerimeterRate,
params->polygonalApproxAccuracyRate, params->minCornerDistanceRate,
params->minDistanceToBorder);
params->minDistanceToBorder, params->minSideLengthCanonicalImg);
}
});
// join candidates
for(int i = 0; i < nScales; i++) {
for(unsigned int j = 0; j < candidatesArrays[i].size(); j++) {
@ -441,25 +455,20 @@ static void _detectInitialCandidates(const Mat &grey, vector< vector< Point2f >
/**
* @brief Detect square candidates in the input image
*/
static void _detectCandidates(InputArray _image, vector< vector< vector< Point2f > > >& candidatesSetOut,
static void _detectCandidates(InputArray _grayImage, vector< vector< vector< Point2f > > >& candidatesSetOut,
vector< vector< vector< Point > > >& contoursSetOut, const Ptr<DetectorParameters> &_params) {
Mat grey = _grayImage.getMat();
CV_DbgAssert(grey.total() != 0);
CV_DbgAssert(grey.type() == CV_8UC1);
Mat image = _image.getMat();
CV_Assert(image.total() != 0);
/// 1. CONVERT TO GRAY
Mat grey;
_convertToGrey(image, grey);
/// 1. DETECT FIRST SET OF CANDIDATES
vector< vector< Point2f > > candidates;
vector< vector< Point > > contours;
/// 2. DETECT FIRST SET OF CANDIDATES
_detectInitialCandidates(grey, candidates, contours, _params);
/// 3. SORT CORNERS
/// 2. SORT CORNERS
_reorderCandidatesCorners(candidates);
/// 4. FILTER OUT NEAR CANDIDATE PAIRS
/// 3. FILTER OUT NEAR CANDIDATE PAIRS
// save the outter/inner border (i.e. potential candidates)
_filterTooCloseCandidates(candidates, candidatesSetOut, contours, contoursSetOut,
_params->minMarkerDistanceRate, _params->detectInvertedMarker);
@ -570,17 +579,24 @@ static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) {
* 2 if the candidate is a white candidate
*/
static uint8_t _identifyOneCandidate(const Ptr<Dictionary>& dictionary, InputArray _image,
vector<Point2f>& _corners, int& idx,
const Ptr<DetectorParameters>& params, int& rotation)
const vector<Point2f>& _corners, int& idx,
const Ptr<DetectorParameters>& params, int& rotation,
const float scale = 1.f)
{
CV_Assert(_corners.size() == 4);
CV_Assert(_image.getMat().total() != 0);
CV_Assert(params->markerBorderBits > 0);
CV_DbgAssert(_corners.size() == 4);
CV_DbgAssert(_image.getMat().total() != 0);
CV_DbgAssert(params->markerBorderBits > 0);
uint8_t typ=1;
// get bits
// scale corners to the correct size to search on the corresponding image pyramid
vector<Point2f> scaled_corners(4);
for (int i = 0; i < 4; ++i) {
scaled_corners[i].x = _corners[i].x * scale;
scaled_corners[i].y = _corners[i].y * scale;
}
Mat candidateBits =
_extractBits(_image, _corners, dictionary->markerSize, params->markerBorderBits,
_extractBits(_image, scaled_corners, dictionary->markerSize, params->markerBorderBits,
params->perspectiveRemovePixelPerCell,
params->perspectiveRemoveIgnoredMarginPerCell, params->minOtsuStdDev);
@ -620,28 +636,28 @@ static uint8_t _identifyOneCandidate(const Ptr<Dictionary>& dictionary, InputArr
/**
* @brief Copy the contents of a corners vector to an OutputArray, settings its size.
*/
static void _copyVector2Output(vector< vector< Point2f > > &vec, OutputArrayOfArrays out) {
static void _copyVector2Output(vector< vector< Point2f > > &vec, OutputArrayOfArrays out, const float scale = 1.f) {
out.create((int)vec.size(), 1, CV_32FC2);
if(out.isMatVector()) {
for (unsigned int i = 0; i < vec.size(); i++) {
out.create(4, 1, CV_32FC2, i);
Mat &m = out.getMatRef(i);
Mat(Mat(vec[i]).t()).copyTo(m);
Mat(Mat(vec[i]).t()*scale).copyTo(m);
}
}
else if(out.isUMatVector()) {
for (unsigned int i = 0; i < vec.size(); i++) {
out.create(4, 1, CV_32FC2, i);
UMat &m = out.getUMatRef(i);
Mat(Mat(vec[i]).t()).copyTo(m);
Mat(Mat(vec[i]).t()*scale).copyTo(m);
}
}
else if(out.kind() == _OutputArray::STD_VECTOR_VECTOR){
for (unsigned int i = 0; i < vec.size(); i++) {
out.create(4, 1, CV_32FC2, i);
Mat m = out.getMat(i);
Mat(Mat(vec[i]).t()).copyTo(m);
Mat(Mat(vec[i]).t()*scale).copyTo(m);
}
}
else {
@ -657,26 +673,46 @@ static void correctCornerPosition( vector< Point2f >& _candidate, int rotate){
std::rotate(_candidate.begin(), _candidate.begin() + 4 - rotate, _candidate.end());
}
static size_t _findOptPyrImageForCanonicalImg(
const std::vector<Mat>& img_pyr,
const int scaled_width,
const int cur_perimeter,
const int min_perimeter) {
CV_Assert(scaled_width > 0);
size_t optLevel = 0;
float dist = std::numeric_limits<float>::max();
for (size_t i = 0; i < img_pyr.size(); ++i) {
const float scale = img_pyr[i].cols / static_cast<float>(scaled_width);
const float perimeter_scaled = cur_perimeter * scale;
// instead of std::abs() favor the larger pyramid level by checking if the distance is postive
// will slow down the algorithm but find more corners in the end
const float new_dist = perimeter_scaled - min_perimeter;
if (new_dist < dist && new_dist > 0.f) {
dist = new_dist;
optLevel = i;
}
}
return optLevel;
}
/**
* @brief Identify square candidates according to a marker dictionary
*/
static void _identifyCandidates(InputArray _image, vector< vector< vector< Point2f > > >& _candidatesSet,
static void _identifyCandidates(InputArray grey,
const std::vector<cv::Mat>& image_pyr,
vector< vector< vector< Point2f > > >& _candidatesSet,
vector< vector< vector<Point> > >& _contoursSet, const Ptr<Dictionary> &_dictionary,
vector< vector< Point2f > >& _accepted, vector< vector<Point> >& _contours, vector< int >& ids,
const Ptr<DetectorParameters> &params,
OutputArrayOfArrays _rejected = noArray()) {
CV_DbgAssert(grey.getMat().total() != 0);
CV_DbgAssert(grey.getMat().type() == CV_8UC1);
int ncandidates = (int)_candidatesSet[0].size();
vector< vector< Point2f > > accepted;
vector< vector< Point2f > > rejected;
vector< vector< Point > > contours;
CV_Assert(_image.getMat().total() != 0);
Mat grey;
_convertToGrey(_image.getMat(), grey);
vector< int > idsTmp(ncandidates, -1);
vector< int > rotated(ncandidates, 0);
vector< uint8_t > validCandidates(ncandidates, 0);
@ -687,10 +723,22 @@ static void _identifyCandidates(InputArray _image, vector< vector< vector< Point
const int end = range.end;
vector< vector< Point2f > >& candidates = params->detectInvertedMarker ? _candidatesSet[1] : _candidatesSet[0];
vector< vector< Point > >& contourS = params->detectInvertedMarker ? _contoursSet[1] : _contoursSet[0];
for(int i = begin; i < end; i++) {
int currId;
validCandidates[i] = _identifyOneCandidate(_dictionary, grey, candidates[i], currId, params, rotated[i]);
int currId = -1;
// implements equation (4)
if (params->useAruco3Detection) {
const int perimeterOfContour = static_cast<int>(contourS[i].size());
const int min_perimeter = params->minSideLengthCanonicalImg * 4;
const size_t nearestImgId = _findOptPyrImageForCanonicalImg(image_pyr, grey.cols(), perimeterOfContour, min_perimeter);
const float scale = image_pyr[nearestImgId].cols / static_cast<float>(grey.cols());
validCandidates[i] = _identifyOneCandidate(_dictionary, image_pyr[nearestImgId], candidates[i], currId, params, rotated[i], scale);
}
else {
validCandidates[i] = _identifyOneCandidate(_dictionary, grey, candidates[i], currId, params, rotated[i]);
}
if(validCandidates[i] > 0)
idsTmp[i] = currId;
@ -1023,6 +1071,25 @@ static void _apriltag(Mat im_orig, const Ptr<DetectorParameters> & _params, std:
_zarray_destroy(quads);
}
static inline void findCornerInPyrImage(const float scale_init, const int closest_pyr_image_idx,
const std::vector<cv::Mat>& grey_pyramid, Mat corners,
const Ptr<DetectorParameters>& params) {
// scale them to the closest pyramid level
if (scale_init != 1.f)
corners *= scale_init; // scale_init * scale_pyr
for (int idx = closest_pyr_image_idx - 1; idx >= 0; --idx) {
// scale them to new pyramid level
corners *= 2.f; // *= scale_pyr;
// use larger win size for larger images
const int subpix_win_size = std::max(grey_pyramid[idx].cols, grey_pyramid[idx].rows) > 1080 ? 5 : 3;
cornerSubPix(grey_pyramid[idx], corners,
Size(subpix_win_size, subpix_win_size),
Size(-1, -1),
TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS,
params->cornerRefinementMaxIterations,
params->cornerRefinementMinAccuracy));
}
}
/**
*/
@ -1031,32 +1098,77 @@ void detectMarkers(InputArray _image, const Ptr<Dictionary> &_dictionary, Output
OutputArrayOfArrays _rejectedImgPoints, InputArrayOfArrays camMatrix, InputArrayOfArrays distCoeff) {
CV_Assert(!_image.empty());
CV_Assert(_params->markerBorderBits > 0);
// check that the parameters are set correctly if Aruco3 is used
CV_Assert(!(_params->useAruco3Detection == true &&
_params->minSideLengthCanonicalImg == 0 &&
_params->minMarkerLengthRatioOriginalImg == 0.0));
Mat grey;
_convertToGrey(_image.getMat(), grey);
/// STEP 1: Detect marker candidates
// Aruco3 functionality is the extension of Aruco.
// The description can be found in:
// [1] Speeded up detection of squared fiducial markers, 2018, FJ Romera-Ramirez et al.
// if Aruco3 functionality if not wanted
// change some parameters to be sure to turn it off
if (!_params->useAruco3Detection) {
_params->minMarkerLengthRatioOriginalImg = 0.0;
_params->minSideLengthCanonicalImg = 0;
}
else {
// always turn on corner refinement in case of Aruco3, due to upsampling
_params->cornerRefinementMethod = CORNER_REFINE_SUBPIX;
}
/// Step 0: equation (2) from paper [1]
const float fxfy = (!_params->useAruco3Detection ? 1.f : _params->minSideLengthCanonicalImg /
(_params->minSideLengthCanonicalImg + std::max(grey.cols, grey.rows)*_params->minMarkerLengthRatioOriginalImg));
/// Step 1: create image pyramid. Section 3.4. in [1]
std::vector<cv::Mat> grey_pyramid;
int closest_pyr_image_idx = 0, num_levels = 0;
//// Step 1.1: resize image with equation (1) from paper [1]
if (_params->useAruco3Detection) {
const float scale_pyr = 2.f;
const float img_area = static_cast<float>(grey.rows*grey.cols);
const float min_area_marker = static_cast<float>(_params->minSideLengthCanonicalImg*_params->minSideLengthCanonicalImg);
// find max level
num_levels = static_cast<int>(log2(img_area / min_area_marker)/scale_pyr);
// the closest pyramid image to the downsampled segmentation image
// will later be used as start index for corner upsampling
const float scale_img_area = img_area * fxfy * fxfy;
closest_pyr_image_idx = cvRound(log2(img_area / scale_img_area)/scale_pyr);
}
cv::buildPyramid(grey, grey_pyramid, num_levels);
// resize to segmentation image
// in this reduces size the contours will be detected
if (fxfy != 1.f)
cv::resize(grey, grey, cv::Size(cvRound(fxfy * grey.cols), cvRound(fxfy * grey.rows)));
/// STEP 2: Detect marker candidates
vector< vector< Point2f > > candidates;
vector< vector< Point > > contours;
vector< int > ids;
vector< vector< vector< Point2f > > > candidatesSet;
vector< vector< vector< Point > > > contoursSet;
/// STEP 1.a Detect marker candidates :: using AprilTag
/// STEP 2.a Detect marker candidates :: using AprilTag
if(_params->cornerRefinementMethod == CORNER_REFINE_APRILTAG){
_apriltag(grey, _params, candidates, contours);
candidatesSet.push_back(candidates);
contoursSet.push_back(contours);
}
/// STEP 1.b Detect marker candidates :: traditional way
/// STEP 2.b Detect marker candidates :: traditional way
else
_detectCandidates(grey, candidatesSet, contoursSet, _params);
/// STEP 2: Check candidate codification (identify markers)
_identifyCandidates(grey, candidatesSet, contoursSet, _dictionary, candidates, contours, ids, _params,
_rejectedImgPoints);
_identifyCandidates(grey, grey_pyramid, candidatesSet, contoursSet, _dictionary,
candidates, contours, ids, _params, _rejectedImgPoints);
// copy to output arrays
_copyVector2Output(candidates, _corners);
@ -1066,13 +1178,17 @@ void detectMarkers(InputArray _image, const Ptr<Dictionary> &_dictionary, Output
if( _params->cornerRefinementMethod == CORNER_REFINE_SUBPIX ) {
CV_Assert(_params->cornerRefinementWinSize > 0 && _params->cornerRefinementMaxIterations > 0 &&
_params->cornerRefinementMinAccuracy > 0);
//// do corner refinement for each of the detected markers
// Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners
parallel_for_(Range(0, _corners.cols()), [&](const Range& range) {
const int begin = range.start;
const int end = range.end;
for (int i = begin; i < end; i++) {
if (_params->useAruco3Detection) {
const float scale_init = (float) grey_pyramid[closest_pyr_image_idx].cols / grey.cols;
findCornerInPyrImage(scale_init, closest_pyr_image_idx, grey_pyramid, _corners.getMat(i), _params);
}
else
cornerSubPix(grey, _corners.getMat(i),
Size(_params->cornerRefinementWinSize, _params->cornerRefinementWinSize),
Size(-1, -1),
@ -1100,6 +1216,11 @@ void detectMarkers(InputArray _image, const Ptr<Dictionary> &_dictionary, Output
_copyVector2Output(candidates, _corners);
}
}
if (_params->cornerRefinementMethod != CORNER_REFINE_APRILTAG &&
_params->cornerRefinementMethod != CORNER_REFINE_SUBPIX) {
// scale to orignal size, this however will lead to inaccurate detections!
_copyVector2Output(candidates, _corners, 1.f/fxfy);
}
}
/**
@ -1324,7 +1445,7 @@ void refineDetectedMarkers(InputArray _image, const Ptr<Board> &_board,
_convertToGrey(_image, grey);
// vector of final detected marker corners and ids
vector< Mat > finalAcceptedCorners;
vector<vector<Point2f> > finalAcceptedCorners;
vector< int > finalAcceptedIds;
// fill with the current markers
finalAcceptedCorners.resize(_detectedCorners.total());
@ -1435,38 +1556,18 @@ void refineDetectedMarkers(InputArray _image, const Ptr<Board> &_board,
// parse output
if(finalAcceptedIds.size() != _detectedIds.total()) {
_detectedCorners.clear();
_detectedIds.clear();
// parse output
Mat(finalAcceptedIds).copyTo(_detectedIds);
_detectedCorners.create((int)finalAcceptedCorners.size(), 1, CV_32FC2);
for(unsigned int i = 0; i < finalAcceptedCorners.size(); i++) {
_detectedCorners.create(4, 1, CV_32FC2, i, true);
for(int j = 0; j < 4; j++) {
_detectedCorners.getMat(i).ptr< Point2f >()[j] =
finalAcceptedCorners[i].ptr< Point2f >()[j];
}
}
_copyVector2Output(finalAcceptedCorners, _detectedCorners);
// recalculate _rejectedCorners based on alreadyIdentified
vector< Mat > finalRejected;
vector<vector<Point2f> > finalRejected;
for(unsigned int i = 0; i < alreadyIdentified.size(); i++) {
if(!alreadyIdentified[i]) {
finalRejected.push_back(_rejectedCorners.getMat(i).clone());
}
}
_rejectedCorners.clear();
_rejectedCorners.create((int)finalRejected.size(), 1, CV_32FC2);
for(unsigned int i = 0; i < finalRejected.size(); i++) {
_rejectedCorners.create(4, 1, CV_32FC2, i, true);
for(int j = 0; j < 4; j++) {
_rejectedCorners.getMat(i).ptr< Point2f >()[j] =
finalRejected[i].ptr< Point2f >()[j];
}
}
_copyVector2Output(finalRejected, _rejectedCorners);
if(_recoveredIdxs.needed()) {
Mat(recoveredIdxs).copyTo(_recoveredIdxs);

@ -246,6 +246,13 @@ static Mat projectMarker(Ptr<aruco::Dictionary> &dictionary, int id, Mat cameraM
return img;
}
enum class ArucoAlgParams
{
USE_DEFAULT = 0,
USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag
DETECT_INVERTED_MARKER, /// Check if there is a white marker
USE_ARUCO3 /// Check if aruco3 should be used
};
/**
@ -253,22 +260,15 @@ static Mat projectMarker(Ptr<aruco::Dictionary> &dictionary, int id, Mat cameraM
*/
class CV_ArucoDetectionPerspective : public cvtest::BaseTest {
public:
CV_ArucoDetectionPerspective();
enum checkWithParameter{
USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag
DETECT_INVERTED_MARKER, /// Check if there is a white marker
};
CV_ArucoDetectionPerspective(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {}
protected:
void run(int);
ArucoAlgParams arucoAlgParams;
};
CV_ArucoDetectionPerspective::CV_ArucoDetectionPerspective() {}
void CV_ArucoDetectionPerspective::run(int tryWith) {
void CV_ArucoDetectionPerspective::run(int) {
int iter = 0;
int szEnclosed = 0;
@ -297,15 +297,20 @@ void CV_ArucoDetectionPerspective::run(int tryWith) {
projectMarker(dictionary, currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch),
distance, imgSize, markerBorder, groundTruthCorners, szEnclosed);
// marker :: Inverted
if(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith){
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams){
img = ~img;
params->detectInvertedMarker = true;
}
if(CV_ArucoDetectionPerspective::USE_APRILTAG == tryWith){
if(ArucoAlgParams::USE_APRILTAG == arucoAlgParams){
params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG;
}
if (ArucoAlgParams::USE_ARUCO3 == arucoAlgParams) {
params->useAruco3Detection = true;
params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
}
// detect markers
vector< vector< Point2f > > corners;
vector< int > ids;
@ -331,7 +336,7 @@ void CV_ArucoDetectionPerspective::run(int tryWith) {
}
}
// change the state :: to detect an enclosed inverted marker
if( CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith && distance == 0.1 ){
if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams && distance == 0.1){
distance -= 0.1;
szEnclosed++;
}
@ -523,15 +528,21 @@ void CV_ArucoBitCorrection::run(int) {
typedef CV_ArucoDetectionPerspective CV_AprilTagDetectionPerspective;
typedef CV_ArucoDetectionPerspective CV_InvertedArucoDetectionPerspective;
typedef CV_ArucoDetectionPerspective CV_Aruco3DetectionPerspective;
TEST(CV_InvertedArucoDetectionPerspective, algorithmic) {
CV_InvertedArucoDetectionPerspective test;
test.safe_run(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER);
CV_InvertedArucoDetectionPerspective test(ArucoAlgParams::DETECT_INVERTED_MARKER);
test.safe_run();
}
TEST(CV_AprilTagDetectionPerspective, algorithmic) {
CV_AprilTagDetectionPerspective test;
test.safe_run(CV_ArucoDetectionPerspective::USE_APRILTAG);
CV_AprilTagDetectionPerspective test(ArucoAlgParams::USE_APRILTAG);
test.safe_run();
}
TEST(CV_Aruco3DetectionPerspective, algorithmic) {
CV_Aruco3DetectionPerspective test(ArucoAlgParams::USE_ARUCO3);
test.safe_run();
}
TEST(CV_ArucoDetectionSimple, algorithmic) {
@ -540,7 +551,7 @@ TEST(CV_ArucoDetectionSimple, algorithmic) {
}
TEST(CV_ArucoDetectionPerspective, algorithmic) {
CV_ArucoDetectionPerspective test;
CV_ArucoDetectionPerspective test(ArucoAlgParams::USE_DEFAULT);
test.safe_run();
}

@ -151,23 +151,34 @@ static Mat projectBoard(Ptr<aruco::GridBoard> &board, Mat cameraMatrix, double y
return img;
}
enum class ArucoAlgParams
{
USE_DEFAULT = 0,
USE_ARUCO3 = 1
};
/**
* @brief Check pose estimation of aruco board
*/
class CV_ArucoBoardPose : public cvtest::BaseTest {
public:
CV_ArucoBoardPose();
CV_ArucoBoardPose(ArucoAlgParams arucoAlgParams)
{
params = aruco::DetectorParameters::create();
params->minDistanceToBorder = 3;
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) {
params->useAruco3Detection = true;
params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
params->minSideLengthCanonicalImg = 16;
}
}
protected:
Ptr<aruco::DetectorParameters> params;
void run(int);
};
CV_ArucoBoardPose::CV_ArucoBoardPose() {}
void CV_ArucoBoardPose::run(int) {
int iter = 0;
@ -197,8 +208,6 @@ void CV_ArucoBoardPose::run(int) {
vector< vector< Point2f > > corners;
vector< int > ids;
Ptr<aruco::DetectorParameters> params = aruco::DetectorParameters::create();
params->minDistanceToBorder = 3;
params->markerBorderBits = markerBorder;
aruco::detectMarkers(img, dictionary, corners, ids, params);
@ -253,16 +262,21 @@ void CV_ArucoBoardPose::run(int) {
*/
class CV_ArucoRefine : public cvtest::BaseTest {
public:
CV_ArucoRefine();
CV_ArucoRefine(ArucoAlgParams arucoAlgParams)
{
params = aruco::DetectorParameters::create();
params->minDistanceToBorder = 3;
params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3)
params->useAruco3Detection = true;
}
protected:
Ptr<aruco::DetectorParameters> params;
void run(int);
};
CV_ArucoRefine::CV_ArucoRefine() {}
void CV_ArucoRefine::run(int) {
int iter = 0;
@ -293,9 +307,6 @@ void CV_ArucoRefine::run(int) {
// detect markers
vector< vector< Point2f > > corners, rejected;
vector< int > ids;
Ptr<aruco::DetectorParameters> params = aruco::DetectorParameters::create();
params->minDistanceToBorder = 3;
params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
params->markerBorderBits = markerBorder;
aruco::detectMarkers(img, dictionary, corners, ids, params, rejected);
@ -326,12 +337,25 @@ void CV_ArucoRefine::run(int) {
TEST(CV_ArucoBoardPose, accuracy) {
CV_ArucoBoardPose test;
CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT);
test.safe_run();
}
typedef CV_ArucoBoardPose CV_Aruco3BoardPose;
TEST(CV_Aruco3BoardPose, accuracy) {
CV_Aruco3BoardPose test(ArucoAlgParams::USE_ARUCO3);
test.safe_run();
}
typedef CV_ArucoRefine CV_Aruco3Refine;
TEST(CV_ArucoRefine, accuracy) {
CV_ArucoRefine test;
CV_ArucoRefine test(ArucoAlgParams::USE_DEFAULT);
test.safe_run();
}
TEST(CV_Aruco3Refine, accuracy) {
CV_Aruco3Refine test(ArucoAlgParams::USE_ARUCO3);
test.safe_run();
}

@ -849,4 +849,35 @@ TEST(CV_ArucoTutorial, can_find_diamondmarkers)
EXPECT_EQ(counterGoldCornersIds, counterRes); // check the number of ArUco markers
}
TEST(Charuco, issue_14014)
{
string imgPath = cvtest::findDataFile("aruco/recover.png");
Mat img = imread(imgPath);
Ptr<aruco::Dictionary> dict = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(cv::aruco::DICT_7X7_250));
Ptr<aruco::CharucoBoard> board = aruco::CharucoBoard::create(8, 5, 0.03455f, 0.02164f, dict);
Ptr<aruco::DetectorParameters> detectorParams = aruco::DetectorParameters::create();
detectorParams->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX;
detectorParams->cornerRefinementMinAccuracy = 0.01;
vector<Mat> corners, rejectedPoints;
vector<int> ids;
aruco::detectMarkers(img, dict, corners, ids, detectorParams, rejectedPoints);
ASSERT_EQ(corners.size(), 19ull);
EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of detected corners
ASSERT_EQ(rejectedPoints.size(), 21ull);
EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of detected corners
aruco::refineDetectedMarkers(img, board, corners, ids, rejectedPoints);
ASSERT_EQ(corners.size(), 20ull);
EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of rejected corners after successfully refine
ASSERT_EQ(rejectedPoints.size(), 20ull);
EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of rejected corners after successfully refine
}
}} // namespace

@ -0,0 +1,104 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#ifdef HAVE_CUDA
#include <cuda_runtime.h>
#include "opencv2/core/cuda.hpp"
#include "opencv2/core/cuda_stream_accessor.hpp"
#include "opencv2/ts/cuda_test.hpp"
namespace opencv_test { namespace {
struct AsyncEvent : testing::TestWithParam<cv::cuda::DeviceInfo>
{
cv::cuda::HostMem src;
cv::cuda::GpuMat d_src;
cv::cuda::HostMem dst;
cv::cuda::GpuMat d_dst;
cv::cuda::Stream stream;
virtual void SetUp()
{
cv::cuda::DeviceInfo devInfo = GetParam();
cv::cuda::setDevice(devInfo.deviceID());
src = cv::cuda::HostMem(cv::cuda::HostMem::PAGE_LOCKED);
cv::Mat m = randomMat(cv::Size(128, 128), CV_8UC1);
m.copyTo(src);
}
};
void deviceWork(void* userData)
{
AsyncEvent* test = reinterpret_cast<AsyncEvent*>(userData);
test->d_src.upload(test->src, test->stream);
test->d_src.convertTo(test->d_dst, CV_32S, test->stream);
test->d_dst.download(test->dst, test->stream);
}
CUDA_TEST_P(AsyncEvent, WrapEvent)
{
cudaEvent_t cuda_event = NULL;
ASSERT_EQ(cudaSuccess, cudaEventCreate(&cuda_event));
{
cv::cuda::Event cudaEvent = cv::cuda::EventAccessor::wrapEvent(cuda_event);
deviceWork(this);
cudaEvent.record(stream);
cudaEvent.waitForCompletion();
cv::Mat dst_gold;
src.createMatHeader().convertTo(dst_gold, CV_32S);
ASSERT_MAT_NEAR(dst_gold, dst, 0);
}
ASSERT_EQ(cudaSuccess, cudaEventDestroy(cuda_event));
}
CUDA_TEST_P(AsyncEvent, WithFlags)
{
cv::cuda::Event cudaEvent = cv::cuda::Event(cv::cuda::Event::CreateFlags::BLOCKING_SYNC);
deviceWork(this);
cudaEvent.record(stream);
cudaEvent.waitForCompletion();
cv::Mat dst_gold;
src.createMatHeader().convertTo(dst_gold, CV_32S);
ASSERT_MAT_NEAR(dst_gold, dst, 0);
}
CUDA_TEST_P(AsyncEvent, Timing)
{
const std::vector<cv::cuda::Event::CreateFlags> eventFlags = { cv::cuda::Event::CreateFlags::BLOCKING_SYNC , cv::cuda::Event::CreateFlags::BLOCKING_SYNC | Event::CreateFlags::DISABLE_TIMING };
const std::vector<bool> shouldFail = { false, true };
for (size_t i = 0; i < eventFlags.size(); i++) {
const auto& flags = eventFlags.at(i);
cv::cuda::Event startEvent = cv::cuda::Event(flags);
cv::cuda::Event stopEvent = cv::cuda::Event(flags);
startEvent.record(stream);
deviceWork(this);
stopEvent.record(stream);
stopEvent.waitForCompletion();
cv::Mat dst_gold;
src.createMatHeader().convertTo(dst_gold, CV_32S);
ASSERT_MAT_NEAR(dst_gold, dst, 0);
bool failed = false;
try {
const double elTimeMs = Event::elapsedTime(startEvent, stopEvent);
ASSERT_GT(elTimeMs, 0);
}
catch (cv::Exception ex) {
failed = true;
}
ASSERT_EQ(failed, shouldFail.at(i));
}
}
INSTANTIATE_TEST_CASE_P(CUDA_Event, AsyncEvent, ALL_DEVICES);
}} // namespace
#endif // HAVE_CUDA

@ -386,6 +386,49 @@ INSTANTIATE_TEST_CASE_P(CUDA, GpuMat_ConvertTo, testing::Combine(
ALL_DEPTH,
WHOLE_SUBMAT));
////////////////////////////////////////////////////////////////////////////////
// locateROI
PARAM_TEST_CASE(GpuMat_LocateROI, cv::cuda::DeviceInfo, cv::Size, MatDepth, UseRoi)
{
cv::cuda::DeviceInfo devInfo;
cv::Size size;
int depth;
bool useRoi;
virtual void SetUp()
{
devInfo = GET_PARAM(0);
size = GET_PARAM(1);
depth = GET_PARAM(2);
useRoi = GET_PARAM(3);
cv::cuda::setDevice(devInfo.deviceID());
}
};
CUDA_TEST_P(GpuMat_LocateROI, locateROI)
{
Point ofsGold;
Size wholeSizeGold;
GpuMat src = createMat(size, depth, wholeSizeGold, ofsGold, useRoi);
Point ofs;
Size wholeSize;
src.locateROI(wholeSize, ofs);
ASSERT_TRUE(ofs == ofsGold && wholeSize == wholeSizeGold);
GpuMat srcPtr(src.size(), src.type(), src.data, src.step);
src.locateROI(wholeSize, ofs);
ASSERT_TRUE(ofs == ofsGold && wholeSize == wholeSizeGold);
}
INSTANTIATE_TEST_CASE_P(CUDA, GpuMat_LocateROI, testing::Combine(
ALL_DEVICES,
DIFFERENT_SIZES,
ALL_DEPTH,
WHOLE_SUBMAT));
////////////////////////////////////////////////////////////////////////////////
// ensureSizeIsEnough

@ -281,7 +281,7 @@ namespace
Size wholeSize;
src.locateROI(wholeSize, ofs);
GpuMat srcWhole(wholeSize, src.type(), src.datastart);
GpuMat srcWhole(wholeSize, src.type(), src.datastart, src.step);
func_(srcWhole, ofs.x, ofs.y, dst, kernel_.ptr<float>(),
kernel_.cols, kernel_.rows, anchor_.x, anchor_.y,

@ -0,0 +1,10 @@
@article{Allegretti2019,
title={Optimized block-based algorithms to label connected components on GPUs},
author={Allegretti, Stefano and Bolelli, Federico and Grana, Costantino},
journal={IEEE Transactions on Parallel and Distributed Systems},
volume={31},
number={2},
pages={423--438},
year={2019},
publisher={IEEE}
}

@ -731,6 +731,50 @@ type.
CV_EXPORTS_W void blendLinear(InputArray img1, InputArray img2, InputArray weights1, InputArray weights2,
OutputArray result, Stream& stream = Stream::Null());
/////////////////// Connected Components Labeling /////////////////////
//! Connected Components Algorithm
enum ConnectedComponentsAlgorithmsTypes {
CCL_DEFAULT = -1, //!< BKE @cite Allegretti2019 algorithm for 8-way connectivity.
CCL_BKE = 0, //!< BKE @cite Allegretti2019 algorithm for 8-way connectivity.
};
/** @brief Computes the Connected Components Labeled image of a binary image.
The function takes as input a binary image and performs Connected Components Labeling. The output
is an image where each Connected Component is assigned a unique label (integer value).
ltype specifies the output label image type, an important consideration based on the total
number of labels or alternatively the total number of pixels in the source image.
ccltype specifies the connected components labeling algorithm to use, currently
BKE @cite Allegretti2019 is supported, see the #ConnectedComponentsAlgorithmsTypes
for details. Note that labels in the output are not required to be sequential.
@param image The 8-bit single-channel image to be labeled.
@param labels Destination labeled image.
@param connectivity Connectivity to use for the labeling procedure. 8 for 8-way connectivity is supported.
@param ltype Output image label type. Currently CV_32S is supported.
@param ccltype Connected components algorithm type (see the #ConnectedComponentsAlgorithmsTypes).
@note A sample program demonstrating Connected Components Labeling in CUDA can be found at\n
opencv_contrib_source_code/modules/cudaimgproc/samples/connected_components.cpp
*/
CV_EXPORTS_AS(connectedComponentsWithAlgorithm) void connectedComponents(InputArray image, OutputArray labels,
int connectivity, int ltype, cv::cuda::ConnectedComponentsAlgorithmsTypes ccltype);
/** @overload
@param image The 8-bit single-channel image to be labeled.
@param labels Destination labeled image.
@param connectivity Connectivity to use for the labeling procedure. 8 for 8-way connectivity is supported.
@param ltype Output image label type. Currently CV_32S is supported.
*/
CV_EXPORTS_W void connectedComponents(InputArray image, OutputArray labels,
int connectivity = 8, int ltype = CV_32S);
//! @}
}} // namespace cv { namespace cuda {

@ -0,0 +1,58 @@
#include <iostream>
#include <opencv2/core/utility.hpp>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/cudaimgproc.hpp"
using namespace cv;
using namespace std;
using namespace cv::cuda;
void colorLabels(const Mat1i& labels, Mat3b& colors) {
colors.create(labels.size());
for (int r = 0; r < labels.rows; ++r) {
int const* labels_row = labels.ptr<int>(r);
Vec3b* colors_row = colors.ptr<Vec3b>(r);
for (int c = 0; c < labels.cols; ++c) {
colors_row[c] = Vec3b(labels_row[c] * 131 % 255, labels_row[c] * 241 % 255, labels_row[c] * 251 % 255);
}
}
}
int main(int argc, const char** argv)
{
CommandLineParser parser(argc, argv, "{@image|stuff.jpg|image for converting to a grayscale}");
parser.about("This program finds connected components in a binary image and assign each of them a different color.\n"
"The connected components labeling is performed in GPU.\n");
parser.printMessage();
String inputImage = parser.get<string>(0);
Mat1b img = imread(samples::findFile(inputImage), IMREAD_GRAYSCALE);
Mat1i labels;
if (img.empty())
{
cout << "Could not read input image file: " << inputImage << endl;
return EXIT_FAILURE;
}
GpuMat d_img, d_labels;
d_img.upload(img);
cuda::connectedComponents(d_img, d_labels, 8, CV_32S);
d_labels.download(labels);
Mat3b colors;
colorLabels(labels, colors);
imshow("Labels", colors);
waitKey(0);
return EXIT_SUCCESS;
}

@ -0,0 +1,49 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
using namespace cv;
using namespace cv::cuda;
#if !defined (HAVE_CUDA) || defined (CUDA_DISABLER)
void cv::cuda::connectedComponents(InputArray img_, OutputArray labels_, int connectivity,
int ltype, ConnectedComponentsAlgorithmsTypes ccltype) { throw_no_cuda(); }
#else /* !defined (HAVE_CUDA) */
namespace cv { namespace cuda { namespace device { namespace imgproc {
void BlockBasedKomuraEquivalence(const cv::cuda::GpuMat& img, cv::cuda::GpuMat& labels);
}}}}
void cv::cuda::connectedComponents(InputArray img_, OutputArray labels_, int connectivity,
int ltype, ConnectedComponentsAlgorithmsTypes ccltype) {
const cv::cuda::GpuMat img = img_.getGpuMat();
cv::cuda::GpuMat& labels = labels_.getGpuMatRef();
CV_Assert(img.channels() == 1);
CV_Assert(connectivity == 8);
CV_Assert(ltype == CV_32S);
CV_Assert(ccltype == CCL_BKE || ccltype == CCL_DEFAULT);
int iDepth = img_.depth();
CV_Assert(iDepth == CV_8U || iDepth == CV_8S);
labels.create(img.size(), CV_MAT_DEPTH(ltype));
if ((ccltype == CCL_BKE || ccltype == CCL_DEFAULT) && connectivity == 8 && ltype == CV_32S) {
using cv::cuda::device::imgproc::BlockBasedKomuraEquivalence;
BlockBasedKomuraEquivalence(img, labels);
}
}
void cv::cuda::connectedComponents(InputArray img_, OutputArray labels_, int connectivity, int ltype) {
cv::cuda::connectedComponents(img_, labels_, connectivity, ltype, CCL_DEFAULT);
}
#endif /* !defined (HAVE_CUDA) */

@ -0,0 +1,345 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#if !defined CUDA_DISABLER
#include "opencv2/core/cuda/common.hpp"
#include "opencv2/core/cuda/emulation.hpp"
#include "opencv2/core/cuda/transform.hpp"
#include "opencv2/core/cuda/functional.hpp"
#include "opencv2/core/cuda/utility.hpp"
#include "opencv2/core/cuda.hpp"
using namespace cv::cuda;
using namespace cv::cuda::device;
namespace cv { namespace cuda { namespace device { namespace imgproc {
constexpr int kblock_rows = 16;
constexpr int kblock_cols = 16;
namespace {
enum class Info : unsigned char { a = 0, b = 1, c = 2, d = 3, P = 4, Q = 5, R = 6, S = 7 };
// Only use it with unsigned numeric types
template <typename T>
__device__ __forceinline__ unsigned char HasBit(T bitmap, Info pos) {
return (bitmap >> static_cast<unsigned char>(pos)) & 1;
}
template <typename T>
__device__ __forceinline__ unsigned char HasBit(T bitmap, unsigned char pos) {
return (bitmap >> pos) & 1;
}
// Only use it with unsigned numeric types
__device__ __forceinline__ void SetBit(unsigned char& bitmap, Info pos) {
bitmap |= (1 << static_cast<unsigned char>(pos));
}
// Returns the root index of the UFTree
__device__ unsigned Find(const int* s_buf, unsigned n) {
while (s_buf[n] != n) {
n = s_buf[n];
}
return n;
}
__device__ unsigned FindAndCompress(int* s_buf, unsigned n) {
unsigned id = n;
while (s_buf[n] != n) {
n = s_buf[n];
s_buf[id] = n;
}
return n;
}
// Merges the UFTrees of a and b, linking one root to the other
__device__ void Union(int* s_buf, unsigned a, unsigned b) {
bool done;
do {
a = Find(s_buf, a);
b = Find(s_buf, b);
if (a < b) {
int old = atomicMin(s_buf + b, a);
done = (old == b);
b = old;
}
else if (b < a) {
int old = atomicMin(s_buf + a, b);
done = (old == a);
a = old;
}
else {
done = true;
}
} while (!done);
}
__global__ void InitLabeling(const cuda::PtrStepSzb img, cuda::PtrStepSzi labels, unsigned char* last_pixel) {
unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2;
unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2;
unsigned img_index = row * img.step + col;
unsigned labels_index = row * (labels.step / labels.elem_size) + col;
if (row < labels.rows && col < labels.cols) {
unsigned P = 0;
// Bitmask representing two kinds of information
// Bits 0, 1, 2, 3 are set if pixel a, b, c, d are foreground, respectively
// Bits 4, 5, 6, 7 are set if block P, Q, R, S need to be merged to X in Merge phase
unsigned char info = 0;
char buffer alignas(int)[4];
*(reinterpret_cast<int*>(buffer)) = 0;
// Read pairs of consecutive values in memory at once
if (col + 1 < img.cols) {
// This does not depend on endianness
*(reinterpret_cast<int16_t*>(buffer)) = *(reinterpret_cast<int16_t*>(img.data + img_index));
if (row + 1 < img.rows) {
*(reinterpret_cast<int16_t*>(buffer + 2)) = *(reinterpret_cast<int16_t*>(img.data + img_index + img.step));
}
}
else {
buffer[0] = img.data[img_index];
if (row + 1 < img.rows) {
buffer[2] = img.data[img_index + img.step];
}
}
if (buffer[0]) {
P |= 0x777;
SetBit(info, Info::a);
}
if (buffer[1]) {
P |= (0x777 << 1);
SetBit(info, Info::b);
}
if (buffer[2]) {
P |= (0x777 << 4);
SetBit(info, Info::c);
}
if (buffer[3]) {
SetBit(info, Info::d);
}
if (col == 0) {
P &= 0xEEEE;
}
if (col + 1 >= img.cols) {
P &= 0x3333;
}
else if (col + 2 >= img.cols) {
P &= 0x7777;
}
if (row == 0) {
P &= 0xFFF0;
}
if (row + 1 >= img.rows) {
P &= 0x00FF;
}
else if (row + 2 >= img.rows) {
P &= 0x0FFF;
}
// P is now ready to be used to find neighbor blocks
// P value avoids range errors
int father_offset = 0;
// P square
if (HasBit(P, 0) && img.data[img_index - img.step - 1]) {
father_offset = -(2 * (labels.step / labels.elem_size) + 2);
}
// Q square
if ((HasBit(P, 1) && img.data[img_index - img.step]) || (HasBit(P, 2) && img.data[img_index + 1 - img.step])) {
if (!father_offset) {
father_offset = -(2 * (labels.step / labels.elem_size));
}
else {
SetBit(info, Info::Q);
}
}
// R square
if (HasBit(P, 3) && img.data[img_index + 2 - img.step]) {
if (!father_offset) {
father_offset = -(2 * (labels.step / labels.elem_size) - 2);
}
else {
SetBit(info, Info::R);
}
}
// S square
if ((HasBit(P, 4) && img.data[img_index - 1]) || (HasBit(P, 8) && img.data[img_index + img.step - 1])) {
if (!father_offset) {
father_offset = -2;
}
else {
SetBit(info, Info::S);
}
}
labels.data[labels_index] = labels_index + father_offset;
if (col + 1 < labels.cols) {
last_pixel = reinterpret_cast<unsigned char*>(labels.data + labels_index + 1);
}
else if (row + 1 < labels.rows) {
last_pixel = reinterpret_cast<unsigned char*>(labels.data + labels_index + labels.step / labels.elem_size);
}
*last_pixel = info;
}
}
__global__ void Merge(cuda::PtrStepSzi labels, unsigned char* last_pixel) {
unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2;
unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2;
unsigned labels_index = row * (labels.step / labels.elem_size) + col;
if (row < labels.rows && col < labels.cols) {
if (col + 1 < labels.cols) {
last_pixel = reinterpret_cast<unsigned char*>(labels.data + labels_index + 1);
}
else if (row + 1 < labels.rows) {
last_pixel = reinterpret_cast<unsigned char*>(labels.data + labels_index + labels.step / labels.elem_size);
}
unsigned char info = *last_pixel;
if (HasBit(info, Info::Q)) {
Union(labels.data, labels_index, labels_index - 2 * (labels.step / labels.elem_size));
}
if (HasBit(info, Info::R)) {
Union(labels.data, labels_index, labels_index - 2 * (labels.step / labels.elem_size) + 2);
}
if (HasBit(info, Info::S)) {
Union(labels.data, labels_index, labels_index - 2);
}
}
}
__global__ void Compression(cuda::PtrStepSzi labels) {
unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2;
unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2;
unsigned labels_index = row * (labels.step / labels.elem_size) + col;
if (row < labels.rows && col < labels.cols) {
FindAndCompress(labels.data, labels_index);
}
}
__global__ void FinalLabeling(const cuda::PtrStepSzb img, cuda::PtrStepSzi labels) {
unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2;
unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2;
unsigned labels_index = row * (labels.step / labels.elem_size) + col;
if (row < labels.rows && col < labels.cols) {
int label;
unsigned char info;
unsigned long long buffer;
if (col + 1 < labels.cols) {
buffer = *reinterpret_cast<unsigned long long*>(labels.data + labels_index);
label = (buffer & (0xFFFFFFFF)) + 1;
info = (buffer >> 32) & 0xFFFFFFFF;
}
else {
label = labels[labels_index] + 1;
if (row + 1 < labels.rows) {
info = labels[labels_index + labels.step / labels.elem_size];
}
else {
// Read from the input image
// "a" is already in position 0
info = img[row * img.step + col];
}
}
if (col + 1 < labels.cols) {
*reinterpret_cast<unsigned long long*>(labels.data + labels_index) =
(static_cast<unsigned long long>(HasBit(info, Info::b) * label) << 32) | (HasBit(info, Info::a) * label);
if (row + 1 < labels.rows) {
*reinterpret_cast<unsigned long long*>(labels.data + labels_index + labels.step / labels.elem_size) =
(static_cast<unsigned long long>(HasBit(info, Info::d) * label) << 32) | (HasBit(info, Info::c) * label);
}
}
else {
labels[labels_index] = HasBit(info, Info::a) * label;
if (row + 1 < labels.rows) {
labels[labels_index + (labels.step / labels.elem_size)] = HasBit(info, Info::c) * label;
}
}
}
}
}
void BlockBasedKomuraEquivalence(const cv::cuda::GpuMat& img, cv::cuda::GpuMat& labels) {
dim3 grid_size;
dim3 block_size;
unsigned char* last_pixel;
bool last_pixel_allocated;
last_pixel_allocated = false;
if ((img.rows == 1 || img.cols == 1) && !((img.rows + img.cols) % 2)) {
cudaSafeCall(cudaMalloc(&last_pixel, sizeof(unsigned char)));
last_pixel_allocated = true;
}
else {
last_pixel = labels.data + ((labels.rows - 2) * labels.step) + (labels.cols - 2) * labels.elemSize();
}
grid_size = dim3((((img.cols + 1) / 2) - 1) / kblock_cols + 1, (((img.rows + 1) / 2) - 1) / kblock_rows + 1, 1);
block_size = dim3(kblock_cols, kblock_rows, 1);
InitLabeling << <grid_size, block_size >> > (img, labels, last_pixel);
cudaSafeCall(cudaGetLastError());
Compression << <grid_size, block_size >> > (labels);
cudaSafeCall(cudaGetLastError());
Merge << <grid_size, block_size >> > (labels, last_pixel);
cudaSafeCall(cudaGetLastError());
Compression << <grid_size, block_size >> > (labels);
cudaSafeCall(cudaGetLastError());
FinalLabeling << <grid_size, block_size >> > (img, labels);
cudaSafeCall(cudaGetLastError());
if (last_pixel_allocated) {
cudaSafeCall(cudaFree(last_pixel));
}
cudaSafeCall(cudaDeviceSynchronize());
}
}}}}
#endif /* CUDA_DISABLER */

@ -0,0 +1,479 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#ifdef HAVE_CUDA
namespace opencv_test {
namespace {
// This function force a row major order for the labels
template <typename LabelT>
void normalize_labels_impl(Mat& labels) {
std::map<LabelT, LabelT> map_new_labels;
LabelT i_max_new_label = 0;
for (int r = 0; r < labels.rows; ++r) {
LabelT* const mat_row = labels.ptr<LabelT>(r);
for (int c = 0; c < labels.cols; ++c) {
LabelT iCurLabel = mat_row[c];
if (iCurLabel > 0) {
if (map_new_labels.find(iCurLabel) == map_new_labels.end()) {
map_new_labels[iCurLabel] = ++i_max_new_label;
}
mat_row[c] = map_new_labels.at(iCurLabel);
}
}
}
}
void normalize_labels(Mat& labels) {
int type = labels.type();
int depth = type & CV_MAT_DEPTH_MASK;
int chans = 1 + (type >> CV_CN_SHIFT);
CV_Assert(chans == 1);
CV_Assert(depth == CV_16U || depth == CV_16S || depth == CV_32S);
switch (depth) {
case CV_16U: normalize_labels_impl<ushort>(labels); break;
case CV_16S: normalize_labels_impl<short>(labels); break;
case CV_32S: normalize_labels_impl<int>(labels); break;
default: CV_Assert(0);
}
}
////////////////////////////////////////////////////////
// ConnectedComponents
PARAM_TEST_CASE(ConnectedComponents, cv::cuda::DeviceInfo, int, int, cv::cuda::ConnectedComponentsAlgorithmsTypes)
{
cv::cuda::DeviceInfo devInfo;
int connectivity;
int ltype;
cv::cuda::ConnectedComponentsAlgorithmsTypes algo;
virtual void SetUp()
{
devInfo = GET_PARAM(0);
connectivity = GET_PARAM(1);
ltype = GET_PARAM(2);
algo = GET_PARAM(3);
cv::cuda::setDevice(devInfo.deviceID());
}
};
CUDA_TEST_P(ConnectedComponents, Chessboard_Even)
{
std::initializer_list<int> sizes{ 16, 16 };
cv::Mat1b input;
cv::Mat1i correct_output_int;
cv::Mat correct_output;
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input = cv::Mat1b(sizes, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
if (connectivity == 8) {
correct_output_int = cv::Mat1i(sizes, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
}
else {
correct_output_int = cv::Mat1i(sizes, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64,
65, 0, 66, 0, 67, 0, 68, 0, 69, 0, 70, 0, 71, 0, 72, 0,
0, 73, 0, 74, 0, 75, 0, 76, 0, 77, 0, 78, 0, 79, 0, 80,
81, 0, 82, 0, 83, 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0,
0, 89, 0, 90, 0, 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96,
97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0,
0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112,
113, 0, 114, 0, 115, 0, 116, 0, 117, 0, 118, 0, 119, 0, 120, 0,
0, 121, 0, 122, 0, 123, 0, 124, 0, 125, 0, 126, 0, 127, 0, 128
});
}
}
correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype));
cv::Mat labels;
cv::Mat diff;
cv::cuda::GpuMat d_input;
cv::cuda::GpuMat d_labels;
d_input.upload(input);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
diff = labels != correct_output;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
CUDA_TEST_P(ConnectedComponents, Chessboard_Odd)
{
std::initializer_list<int> sizes{ 15, 15 };
cv::Mat1b input;
cv::Mat1i correct_output_int;
cv::Mat correct_output;
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input = Mat1b(sizes, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
if (connectivity == 8) {
correct_output_int = Mat1i(sizes, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
}
else {
correct_output_int = Mat1i(sizes, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0,
16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23,
0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0,
31, 0, 32, 0, 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38,
0, 39, 0, 40, 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0,
46, 0, 47, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53,
0, 54, 0, 55, 0, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0,
61, 0, 62, 0, 63, 0, 64, 0, 65, 0, 66, 0, 67, 0, 68,
0, 69, 0, 70, 0, 71, 0, 72, 0, 73, 0, 74, 0, 75, 0,
76, 0, 77, 0, 78, 0, 79, 0, 80, 0, 81, 0, 82, 0, 83,
0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, 89, 0, 90, 0,
91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, 0, 97, 0, 98,
0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0,
106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113
});
}
}
correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype));
cv::Mat labels;
cv::Mat diff;
cv::cuda::GpuMat d_input;
cv::cuda::GpuMat d_labels;
d_input.upload(input);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
diff = labels != correct_output;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
CUDA_TEST_P(ConnectedComponents, Maxlabels_8conn_Even)
{
std::initializer_list<int> sizes{ 16, 16 };
cv::Mat1b input;
cv::Mat1i correct_output_int;
cv::Mat correct_output;
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input = Mat1b(sizes, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
});
correct_output_int = Mat1i(sizes, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
});
}
correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype));
cv::Mat labels;
cv::Mat diff;
cv::cuda::GpuMat d_input;
cv::cuda::GpuMat d_labels;
d_input.upload(input);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
diff = labels != correct_output;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
CUDA_TEST_P(ConnectedComponents, Maxlabels_8conn_Odd)
{
std::initializer_list<int> sizes{ 15, 15 };
cv::Mat1b input;
cv::Mat1i correct_output_int;
cv::Mat correct_output;
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input = Mat1b(sizes, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
correct_output_int = Mat1i(sizes, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64
});
}
correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype));
cv::Mat labels;
cv::Mat diff;
cv::cuda::GpuMat d_input;
cv::cuda::GpuMat d_labels;
d_input.upload(input);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
diff = labels != correct_output;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
CUDA_TEST_P(ConnectedComponents, Single_Row)
{
std::initializer_list<int> sizes{ 1, 15 };
cv::Mat1b input;
cv::Mat1i correct_output_int;
cv::Mat correct_output;
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input = Mat1b(sizes, { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 });
correct_output_int = Mat1i(sizes, { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 });
}
correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype));
cv::Mat labels;
cv::Mat diff;
cv::cuda::GpuMat d_input;
cv::cuda::GpuMat d_labels;
d_input.upload(input);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
diff = labels != correct_output;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
CUDA_TEST_P(ConnectedComponents, Single_Column)
{
std::initializer_list<int> sizes{ 15, 1 };
cv::Mat1b input;
cv::Mat1i correct_output_int;
cv::Mat correct_output;
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input = Mat1b(sizes, { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 });
correct_output_int = Mat1i(sizes, { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 });
}
correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype));
cv::Mat labels;
cv::Mat diff;
cv::cuda::GpuMat d_input;
cv::cuda::GpuMat d_labels;
d_input.upload(input);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
diff = labels != correct_output;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
CUDA_TEST_P(ConnectedComponents, Concentric_Circles)
{
string img_path = cvtest::TS::ptr()->get_data_path() + "connectedcomponents/concentric_circles.png";
string exp_path = cvtest::TS::ptr()->get_data_path() + "connectedcomponents/ccomp_exp.png";
Mat img = imread(img_path, 0);
EXPECT_FALSE(img.empty());
Mat exp = imread(exp_path, 0);
EXPECT_FALSE(exp.empty());
Mat labels;
exp.convertTo(exp, ltype);
GpuMat d_img;
GpuMat d_labels;
d_img.upload(img);
EXPECT_NO_THROW(cv::cuda::connectedComponents(d_img, d_labels, connectivity, ltype, algo));
d_labels.download(labels);
normalize_labels(labels);
Mat diff = labels != exp;
EXPECT_EQ(cv::countNonZero(diff), 0);
}
INSTANTIATE_TEST_CASE_P(CUDA_ImgProc, ConnectedComponents, testing::Combine(
ALL_DEVICES,
testing::Values(8),
testing::Values(CV_32S),
testing::Values(cv::cuda::CCL_DEFAULT, cv::cuda::CCL_BKE)
));
}
} // namespace
#endif // HAVE_CUDA

@ -2467,6 +2467,11 @@ int BinaryDescriptor::EDLineDetector::EDline( cv::Mat &image, LineChains &lines
offsetInLineArray = pLineSID[numOfLines]; // line was not accepted, the offset is set back
}
}
// Avoid array out of range
if(numOfLines >= lines.sId.size()) {
lines.sId.push_back(offsetInLineArray);
pLineSID = &lines.sId.front();
}
//Extract line segments from the remaining pixel; Current chain has been shortened already.
}
} //end for(unsigned int edgeID=0; edgeID<edges.numOfEdges; edgeID++)

@ -88,7 +88,7 @@ public:
virtual ~WindowScene();
/**
* set window background to custom image
* set window background to custom image/ color
* @param image
*/
CV_WRAP virtual void setBackground(InputArray image) = 0;
@ -101,8 +101,8 @@ public:
*
* this way you can add distortion or SSAO effects.
* The effects themselves must be defined inside Ogre .compositor scripts.
* @see addResourceLocation
* @param names compositor names that will be applied in order of appearance
* @see addResourceLocation
*/
CV_WRAP virtual void setCompositors(const std::vector<String>& names) = 0;
@ -110,12 +110,12 @@ public:
* place an entity of a mesh in the scene
*
* the mesh needs to be created beforehand. Either programmatically
* by e.g. @ref createPointCloudMesh or by placing an Ogre .mesh file in a resource location.
* @see addResourceLocation
* by e.g. @ref createPointCloudMesh or by placing the respective file in a resource location.
* @param name entity name
* @param meshname mesh name
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param tvec translation
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @see addResourceLocation
*/
CV_WRAP virtual void createEntity(const String& name, const String& meshname,
InputArray tvec = noArray(), InputArray rot = noArray()) = 0;
@ -131,13 +131,14 @@ public:
* @param name entity name
* @param prop @ref EntityProperty
* @param value the value
* @param subEntityIdx index of the sub-entity (default: all)
*/
CV_WRAP virtual void setEntityProperty(const String& name, int prop, const Scalar& value) = 0;
/// @overload
CV_WRAP virtual void setEntityProperty(const String& name, int prop, const String& value,
int subEntityIdx = -1) = 0;
/// @overload
CV_WRAP virtual void setEntityProperty(const String& name, int prop, const Scalar& value) = 0;
/**
* get the property of an entity
* @param name entity name
@ -153,8 +154,8 @@ public:
* @param K intrinsic matrix
* @param imsize image size
* @param zFar far plane in camera coordinates
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param tvec translation
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param color line color
* @return the extents of the Frustum at far plane, where the top left corner denotes the principal
* point offset
@ -167,8 +168,8 @@ public:
/**
* creates a point light in the scene
* @param name entity name
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param tvec translation
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param diffuseColor
* @param specularColor
*/
@ -180,8 +181,8 @@ public:
/**
* update entity pose by transformation in the parent coordinate space. (pre-rotation)
* @param name entity name
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param tvec translation
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
*/
CV_WRAP virtual void updateEntityPose(const String& name, InputArray tvec = noArray(),
InputArray rot = noArray()) = 0;
@ -189,8 +190,8 @@ public:
/**
* set entity pose in the world coordinate space.
* @param name enitity name
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param tvec translation
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param invert use the inverse of the given pose
*/
CV_WRAP virtual void setEntityPose(const String& name, InputArray tvec = noArray(),
@ -263,8 +264,8 @@ public:
/**
* Sets the current camera pose
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param tvec translation
* @param rot @ref Rodrigues vector or 3x3 rotation matrix
* @param invert use the inverse of the given pose
*/
CV_WRAP virtual void setCameraPose(InputArray tvec = noArray(), InputArray rot = noArray(),

@ -443,14 +443,21 @@ public:
_createTexture(name, image.getMat());
// ensure bgplane is visible
bgplane->setVisible(true);
bgplane->setDefaultUVs();
Pass* rpass = bgplane->getMaterial()->getBestTechnique()->getPasses()[0];
rpass->getTextureUnitStates()[0]->setTextureName(name);
rpass->getTextureUnitStates()[0]->setTextureAddressingMode(TAM_CLAMP);
Pass* rpass = bgplane->getMaterial()->getTechnique(0)->getPasses()[0];
auto tus = rpass->getTextureUnitStates()[0];
// ensure bgplane is visible
bgplane->setVisible(true);
if(tus->getTextureName() != name)
{
RTShader::ShaderGenerator::getSingleton().invalidateMaterial(
RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME, *bgplane->getMaterial());
tus->setTextureName(name);
tus->setTextureAddressingMode(TAM_CLAMP);
}
}
void setCompositors(const std::vector<String>& names) CV_OVERRIDE

@ -8,7 +8,7 @@ FILE(GLOB CORRESPONDENCE_HDRS *.h)
ADD_LIBRARY(correspondence STATIC ${CORRESPONDENCE_SRC} ${CORRESPONDENCE_HDRS})
TARGET_LINK_LIBRARIES(correspondence LINK_PRIVATE ${GLOG_LIBRARIES} multiview)
ocv_target_link_libraries(correspondence LINK_PRIVATE ${GLOG_LIBRARIES} multiview opencv_imgcodecs)
IF(TARGET Eigen3::Eigen)
TARGET_LINK_LIBRARIES(correspondence LINK_PUBLIC Eigen3::Eigen)
ENDIF()

@ -25,50 +25,71 @@ public:
enum GradientOperator
{
PREWITT = 0,
SOBEL = 1,
SCHARR = 2,
LSD = 3
SOBEL = 1,
SCHARR = 2,
LSD = 3
};
struct CV_EXPORTS_W_SIMPLE Params
{
CV_WRAP Params();
//! Parameter Free mode will be activated when this value is true.
//! Parameter Free mode will be activated when this value is set as true. Default value is false.
CV_PROP_RW bool PFmode;
//! indicates the operator used for gradient calculation.The following operation flags are available(cv::ximgproc::EdgeDrawing::GradientOperator)
/** @brief indicates the operator used for gradient calculation.
one of the flags cv::ximgproc::EdgeDrawing::GradientOperator. Default value is PREWITT
*/
CV_PROP_RW int EdgeDetectionOperator;
//! threshold value used to create gradient image.
//! threshold value of gradiential difference between pixels. Used to create gradient image. Default value is 20
CV_PROP_RW int GradientThresholdValue;
//! threshold value used to create gradient image.
//! threshold value used to select anchor points. Default value is 0
CV_PROP_RW int AnchorThresholdValue;
//! Default value is 1
CV_PROP_RW int ScanInterval;
//! minimun connected pixels length processed to create an edge segment.
/** @brief minimun connected pixels length processed to create an edge segment.
in gradient image, minimum connected pixels length processed to create an edge segment. pixels having upper value than GradientThresholdValue
will be processed. Default value is 10
*/
CV_PROP_RW int MinPathLength;
//! sigma value for internal GaussianBlur() function.
//! sigma value for internal GaussianBlur() function. Default value is 1.0
CV_PROP_RW float Sigma;
CV_PROP_RW bool SumFlag;
//! when this value is true NFA (Number of False Alarms) algorithm will be used for line and ellipse validation.
//! Default value is true. indicates if NFA (Number of False Alarms) algorithm will be used for line and ellipse validation.
CV_PROP_RW bool NFAValidation;
//! minimun line length to detect.
CV_PROP_RW int MinLineLength;
//! Default value is 6.0
CV_PROP_RW double MaxDistanceBetweenTwoLines;
//! Default value is 1.0
CV_PROP_RW double LineFitErrorThreshold;
//! Default value is 1.3
CV_PROP_RW double MaxErrorThreshold;
void read(const FileNode& fn);
void write(FileStorage& fs) const;
};
/** @brief Detects edges and prepares them to detect lines and ellipses.
/** @brief Detects edges in a grayscale image and prepares them to detect lines and ellipses.
@param src input image
@param src 8-bit, single-channel, grayscale input image.
*/
CV_WRAP virtual void detectEdges(InputArray src) = 0;
/** @brief returns Edge Image prepared by detectEdges() function.
@param dst returns 8-bit, single-channel output image.
*/
CV_WRAP virtual void getEdgeImage(OutputArray dst) = 0;
/** @brief returns Gradient Image prepared by detectEdges() function.
@param dst returns 16-bit, single-channel output image.
*/
CV_WRAP virtual void getGradientImage(OutputArray dst) = 0;
/** @brief Returns std::vector<std::vector<Point>> of detected edge segments, see detectEdges()
*/
*/
CV_WRAP virtual std::vector<std::vector<Point> > getSegments() = 0;
/** @brief Returns for each line found in detectLines() its edge segment index in getSegments()
@ -77,15 +98,15 @@ public:
/** @brief Detects lines.
@param lines output Vec<4f> contains start point and end point of detected lines.
@note you should call detectEdges() method before call this.
@param lines output Vec<4f> contains the start point and the end point of detected lines.
@note you should call detectEdges() before calling this function.
*/
CV_WRAP virtual void detectLines(OutputArray lines) = 0;
/** @brief Detects circles and ellipses.
@param ellipses output Vec<6d> contains center point and perimeter for circles.
@note you should call detectEdges() method before call this.
@param ellipses output Vec<6d> contains center point and perimeter for circles, center point, axes and angle for ellipses.
@note you should call detectEdges() before calling this function.
*/
CV_WRAP virtual void detectEllipses(OutputArray ellipses) = 0;
@ -93,7 +114,8 @@ public:
/** @brief sets parameters.
this function is meant to be used for parameter setting in other languages than c++.
this function is meant to be used for parameter setting in other languages than c++ like python.
@param parameters
*/
CV_WRAP void setParams(const EdgeDrawing::Params& parameters);
virtual ~EdgeDrawing() { }

@ -113,6 +113,7 @@ public:
void detectLines(OutputArray lines) CV_OVERRIDE;
void detectEllipses(OutputArray ellipses) CV_OVERRIDE;
virtual String getDefaultName() const CV_OVERRIDE;
virtual void read(const FileNode& fn) CV_OVERRIDE;
virtual void write(FileStorage& fs) const CV_OVERRIDE;
@ -317,6 +318,11 @@ void EdgeDrawing::Params::write(cv::FileStorage& fs) const
fs << "MaxErrorThreshold" << MaxErrorThreshold;
}
String EdgeDrawingImpl::getDefaultName() const
{
return String("EdgeDrawing");
}
void EdgeDrawingImpl::read(const cv::FileNode& fn)
{
params.read(fn);
@ -345,6 +351,7 @@ EdgeDrawingImpl::~EdgeDrawingImpl()
void EdgeDrawingImpl::detectEdges(InputArray src)
{
CV_Assert(!src.empty() && src.type() == CV_8UC1);
gradThresh = params.GradientThresholdValue;
anchorThresh = params.AnchorThresholdValue;
op = params.EdgeDetectionOperator;

@ -281,6 +281,9 @@ namespace xphoto
/** Writing result **/
for (size_t i = 0; i < labelSeq.size(); ++i)
{
if (pPath[i].x >= img.cols || pPath[i].y >= img.rows)
continue;
cv::Vec <float, cn> val = pointSeq[i][labelSeq[i]];
img.template at<cv::Vec <float, cn> >(pPath[i]) = val;
}

@ -318,7 +318,7 @@ icvGetTodoBlocks(Mat& sampled_img, Mat& sampling_mask, std::vector< std::tuple<
Mat error_mask_2d = sampling_mask(Range(yblock_counter*block_size - top_border, std::min(img_height, (yblock_counter*block_size + block_size + bottom_border))), Range(xblock_counter*block_size - left_border, std::min(img_width, (xblock_counter*block_size + block_size + right_border))));
// determine normalized and weighted standard deviation
if (block_size > block_size_min)
if (block_size > block_size_min && xblock_counter < sigma_n_array.cols && yblock_counter < sigma_n_array.rows)
{
double sigma_n = icvStandardDeviation(distorted_block_2d, error_mask_2d);
sigma_n_array.at<double>( yblock_counter, xblock_counter) = sigma_n;

@ -132,6 +132,12 @@ template <typename Tp> void Photomontage <Tp>::
setWeights(GCGraph <TWeight> &graph, const int idx1, const int idx2,
const int l1, const int l2, const int lx)
{
if ((size_t)idx1 >= pointSeq.size() || (size_t)idx2 >= pointSeq.size()
|| (size_t)l1 >= pointSeq[idx1].size() || (size_t)l1 >= pointSeq[idx2].size()
|| (size_t)l2 >= pointSeq[idx1].size() || (size_t)l2 >= pointSeq[idx2].size()
|| (size_t)lx >= pointSeq[idx1].size() || (size_t)lx >= pointSeq[idx2].size())
return;
if (l1 == l2)
{
/** Link from A to B **/

Loading…
Cancel
Save