| 1 | // This file is part of OpenCV project. |
| 2 | // It is subject to the license terms in the LICENSE file found in the top-level directory |
| 3 | // of this distribution and at http://opencv.org/license.html |
| 4 | |
| 5 | #include "../precomp.hpp" |
| 6 | |
| 7 | #include <opencv2/calib3d.hpp> |
| 8 | #include <opencv2/core/utils/logger.hpp> |
| 9 | #include "opencv2/objdetect/charuco_detector.hpp" |
| 10 | #include "aruco_utils.hpp" |
| 11 | |
| 12 | namespace cv { |
| 13 | namespace aruco { |
| 14 | |
| 15 | using namespace std; |
| 16 | |
| 17 | struct CharucoDetector::CharucoDetectorImpl { |
| 18 | CharucoBoard board; |
| 19 | CharucoParameters charucoParameters; |
| 20 | ArucoDetector arucoDetector; |
| 21 | |
| 22 | CharucoDetectorImpl(const CharucoBoard& _board, const CharucoParameters _charucoParameters, |
| 23 | const ArucoDetector& _arucoDetector): board(_board), charucoParameters(_charucoParameters), |
| 24 | arucoDetector(_arucoDetector) |
| 25 | {} |
| 26 | |
| 27 | bool checkBoard(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray charucoCorners, InputArray charucoIds) { |
| 28 | vector<Mat> mCorners; |
| 29 | markerCorners.getMatVector(mv&: mCorners); |
| 30 | const Mat mIds = markerIds.getMat(); |
| 31 | const Mat chCorners = charucoCorners.getMat(); |
| 32 | const Mat chIds = charucoIds.getMat(); |
| 33 | const vector<int>& boardIds = board.getIds(); |
| 34 | |
| 35 | const vector<vector<int> > nearestMarkerIdx = board.getNearestMarkerIdx(); |
| 36 | vector<Point2f> distance(board.getNearestMarkerIdx().size(), Point2f(0.f, std::numeric_limits<float>::max())); |
| 37 | // distance[i].x: max distance from the i-th charuco corner to charuco corner-forming markers. |
| 38 | // The two charuco corner-forming markers of i-th charuco corner are defined in getNearestMarkerIdx()[i] |
| 39 | // distance[i].y: min distance from the charuco corner to other markers. |
| 40 | for (size_t i = 0ull; i < chIds.total(); i++) { |
| 41 | int chId = chIds.ptr<int>(y: 0)[i]; |
| 42 | Point2f charucoCorner(chCorners.ptr<Point2f>(y: 0)[i]); |
| 43 | for (size_t j = 0ull; j < mIds.total(); j++) { |
| 44 | int idMaker = mIds.ptr<int>(y: 0)[j]; |
| 45 | // skip the check if the marker is not in the current board. |
| 46 | if (find(first: boardIds.begin(), last: boardIds.end(), val: idMaker) == boardIds.end()) |
| 47 | continue; |
| 48 | Point2f centerMarker((mCorners[j].ptr<Point2f>(y: 0)[0] + mCorners[j].ptr<Point2f>(y: 0)[1] + |
| 49 | mCorners[j].ptr<Point2f>(y: 0)[2] + mCorners[j].ptr<Point2f>(y: 0)[3]) / 4.f); |
| 50 | float dist = sqrt(x: normL2Sqr<float>(pt: centerMarker - charucoCorner)); |
| 51 | // nearestMarkerIdx contains for each charuco corner, nearest marker index in ids array |
| 52 | const int nearestMarkerId1 = boardIds[nearestMarkerIdx[chId][0]]; |
| 53 | const int nearestMarkerId2 = boardIds[nearestMarkerIdx[chId][1]]; |
| 54 | if (nearestMarkerId1 == idMaker || nearestMarkerId2 == idMaker) { |
| 55 | int nearestCornerId = nearestMarkerId1 == idMaker ? board.getNearestMarkerCorners()[chId][0] : board.getNearestMarkerCorners()[chId][1]; |
| 56 | Point2f nearestCorner = mCorners[j].ptr<Point2f>(y: 0)[nearestCornerId]; |
| 57 | // distToNearest: distance from the charuco corner to charuco corner-forming markers |
| 58 | float distToNearest = sqrt(x: normL2Sqr<float>(pt: nearestCorner - charucoCorner)); |
| 59 | distance[chId].x = max(a: distance[chId].x, b: distToNearest); |
| 60 | // check that nearestCorner is nearest point |
| 61 | { |
| 62 | Point2f mid1 = (mCorners[j].ptr<Point2f>(y: 0)[(nearestCornerId + 1) % 4]+nearestCorner)*0.5f; |
| 63 | Point2f mid2 = (mCorners[j].ptr<Point2f>(y: 0)[(nearestCornerId + 3) % 4]+nearestCorner)*0.5f; |
| 64 | float tmpDist = min(a: sqrt(x: normL2Sqr<float>(pt: mid1 - charucoCorner)), b: sqrt(x: normL2Sqr<float>(pt: mid2 - charucoCorner))); |
| 65 | if (tmpDist < distToNearest) |
| 66 | return false; |
| 67 | } |
| 68 | } |
| 69 | // check distance from the charuco corner to other markers |
| 70 | else |
| 71 | distance[chId].y = min(a: distance[chId].y, b: dist); |
| 72 | } |
| 73 | // if distance from the charuco corner to charuco corner-forming markers more then distance from the charuco corner to other markers, |
| 74 | // then a false board is found. |
| 75 | if (distance[chId].x > 0.f && distance[chId].y < std::numeric_limits<float>::max() && distance[chId].x > distance[chId].y) |
| 76 | return false; |
| 77 | } |
| 78 | return true; |
| 79 | } |
| 80 | |
| 81 | /** Calculate the maximum window sizes for corner refinement for each charuco corner based on the distance |
| 82 | * to their closest markers */ |
| 83 | vector<Size> getMaximumSubPixWindowSizes(InputArrayOfArrays markerCorners, InputArray markerIds, |
| 84 | InputArray charucoCorners) { |
| 85 | size_t nCharucoCorners = charucoCorners.getMat().total(); |
| 86 | |
| 87 | CV_Assert(board.getNearestMarkerIdx().size() == nCharucoCorners); |
| 88 | |
| 89 | vector<Size> winSizes(nCharucoCorners, Size(-1, -1)); |
| 90 | for(size_t i = 0ull; i < nCharucoCorners; i++) { |
| 91 | if(charucoCorners.getMat().at<Point2f>(i0: (int)i) == Point2f(-1.f, -1.f)) continue; |
| 92 | if(board.getNearestMarkerIdx()[i].empty()) continue; |
| 93 | double minDist = -1; |
| 94 | int counter = 0; |
| 95 | // calculate the distance to each of the closest corner of each closest marker |
| 96 | for(size_t j = 0; j < board.getNearestMarkerIdx()[i].size(); j++) { |
| 97 | // find marker |
| 98 | int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]]; |
| 99 | int markerIdx = -1; |
| 100 | for(size_t k = 0; k < markerIds.getMat().total(); k++) { |
| 101 | if(markerIds.getMat().at<int>(i0: (int)k) == markerId) { |
| 102 | markerIdx = (int)k; |
| 103 | break; |
| 104 | } |
| 105 | } |
| 106 | if(markerIdx == -1) continue; |
| 107 | Point2f markerCorner = |
| 108 | markerCorners.getMat(i: markerIdx).at<Point2f>(i0: board.getNearestMarkerCorners()[i][j]); |
| 109 | Point2f charucoCorner = charucoCorners.getMat().at<Point2f>(i0: (int)i); |
| 110 | double dist = norm(pt: markerCorner - charucoCorner); |
| 111 | if(minDist == -1) minDist = dist; // if first distance, just assign it |
| 112 | minDist = min(a: dist, b: minDist); |
| 113 | counter++; |
| 114 | } |
| 115 | // if this is the first closest marker, dont do anything |
| 116 | if(counter == 0) |
| 117 | continue; |
| 118 | else { |
| 119 | // else, calculate the maximum window size |
| 120 | int winSizeInt = int(minDist - 2); // remove 2 pixels for safety |
| 121 | if(winSizeInt < 1) winSizeInt = 1; // minimum size is 1 |
| 122 | if(winSizeInt > 10) winSizeInt = 10; // maximum size is 10 |
| 123 | winSizes[i] = Size(winSizeInt, winSizeInt); |
| 124 | } |
| 125 | } |
| 126 | return winSizes; |
| 127 | } |
| 128 | |
| 129 | /** @brief From all projected chessboard corners, select those inside the image and apply subpixel refinement */ |
| 130 | void selectAndRefineChessboardCorners(InputArray allCorners, InputArray image, OutputArray selectedCorners, |
| 131 | OutputArray selectedIds, const vector<Size> &winSizes) { |
| 132 | const int minDistToBorder = 2; // minimum distance of the corner to the image border |
| 133 | // remaining corners, ids and window refinement sizes after removing corners outside the image |
| 134 | vector<Point2f> filteredChessboardImgPoints; |
| 135 | vector<Size> filteredWinSizes; |
| 136 | vector<int> filteredIds; |
| 137 | // filter corners outside the image |
| 138 | Rect innerRect(minDistToBorder, minDistToBorder, image.getMat().cols - 2 * minDistToBorder, |
| 139 | image.getMat().rows - 2 * minDistToBorder); |
| 140 | for(unsigned int i = 0; i < allCorners.getMat().total(); i++) { |
| 141 | if(innerRect.contains(pt: allCorners.getMat().at<Point2f>(i0: i))) { |
| 142 | filteredChessboardImgPoints.push_back(x: allCorners.getMat().at<Point2f>(i0: i)); |
| 143 | filteredIds.push_back(x: i); |
| 144 | filteredWinSizes.push_back(x: winSizes[i]); |
| 145 | } |
| 146 | } |
| 147 | // if none valid, return 0 |
| 148 | if(filteredChessboardImgPoints.empty()) return; |
| 149 | // corner refinement, first convert input image to grey |
| 150 | Mat grey; |
| 151 | if(image.type() == CV_8UC3) |
| 152 | cvtColor(src: image, dst: grey, code: COLOR_BGR2GRAY); |
| 153 | else |
| 154 | grey = image.getMat(); |
| 155 | //// For each of the charuco corners, apply subpixel refinement using its correspondind winSize |
| 156 | parallel_for_(range: Range(0, (int)filteredChessboardImgPoints.size()), functor: [&](const Range& range) { |
| 157 | const int begin = range.start; |
| 158 | const int end = range.end; |
| 159 | for (int i = begin; i < end; i++) { |
| 160 | vector<Point2f> in; |
| 161 | in.push_back(x: filteredChessboardImgPoints[i] - Point2f(0.5, 0.5)); // adjust sub-pixel coordinates for cornerSubPix |
| 162 | Size winSize = filteredWinSizes[i]; |
| 163 | if (winSize.height == -1 || winSize.width == -1) |
| 164 | winSize = Size(arucoDetector.getDetectorParameters().cornerRefinementWinSize, |
| 165 | arucoDetector.getDetectorParameters().cornerRefinementWinSize); |
| 166 | cornerSubPix(image: grey, corners: in, winSize, zeroZone: Size(), |
| 167 | criteria: TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, |
| 168 | arucoDetector.getDetectorParameters().cornerRefinementMaxIterations, |
| 169 | arucoDetector.getDetectorParameters().cornerRefinementMinAccuracy)); |
| 170 | filteredChessboardImgPoints[i] = in[0] + Point2f(0.5, 0.5); |
| 171 | } |
| 172 | }); |
| 173 | // parse output |
| 174 | Mat(filteredChessboardImgPoints).copyTo(m: selectedCorners); |
| 175 | Mat(filteredIds).copyTo(m: selectedIds); |
| 176 | } |
| 177 | |
| 178 | /** Interpolate charuco corners using approximated pose estimation */ |
| 179 | void interpolateCornersCharucoApproxCalib(InputArrayOfArrays markerCorners, InputArray markerIds, |
| 180 | InputArray image, OutputArray charucoCorners, OutputArray charucoIds) { |
| 181 | CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3); |
| 182 | CV_Assert(markerCorners.total() == markerIds.getMat().total()); |
| 183 | |
| 184 | // approximated pose estimation using marker corners |
| 185 | Mat approximatedRvec, approximatedTvec; |
| 186 | Mat objPoints, imgPoints; // object and image points for the solvePnP function |
| 187 | Board simpleBoard(board.getObjPoints(), board.getDictionary(), board.getIds()); |
| 188 | simpleBoard.matchImagePoints(detectedCorners: markerCorners, detectedIds: markerIds, objPoints, imgPoints); |
| 189 | if (objPoints.total() < 4ull) // need, at least, 4 corners |
| 190 | return; |
| 191 | |
| 192 | solvePnP(objectPoints: objPoints, imagePoints: imgPoints, cameraMatrix: charucoParameters.cameraMatrix, distCoeffs: charucoParameters.distCoeffs, rvec: approximatedRvec, tvec: approximatedTvec); |
| 193 | |
| 194 | // project chessboard corners |
| 195 | vector<Point2f> allChessboardImgPoints; |
| 196 | projectPoints(objectPoints: board.getChessboardCorners(), rvec: approximatedRvec, tvec: approximatedTvec, cameraMatrix: charucoParameters.cameraMatrix, |
| 197 | distCoeffs: charucoParameters.distCoeffs, imagePoints: allChessboardImgPoints); |
| 198 | // calculate maximum window sizes for subpixel refinement. The size is limited by the distance |
| 199 | // to the closes marker corner to avoid erroneous displacements to marker corners |
| 200 | vector<Size> subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, charucoCorners: allChessboardImgPoints); |
| 201 | // filter corners outside the image and subpixel-refine charuco corners |
| 202 | selectAndRefineChessboardCorners(allCorners: allChessboardImgPoints, image, selectedCorners: charucoCorners, selectedIds: charucoIds, winSizes: subPixWinSizes); |
| 203 | } |
| 204 | |
| 205 | /** Interpolate charuco corners using local homography */ |
| 206 | void interpolateCornersCharucoLocalHom(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray image, |
| 207 | OutputArray charucoCorners, OutputArray charucoIds) { |
| 208 | CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3); |
| 209 | CV_Assert(markerCorners.total() == markerIds.getMat().total()); |
| 210 | size_t nMarkers = markerIds.getMat().total(); |
| 211 | // calculate local homographies for each marker |
| 212 | vector<Mat> transformations(nMarkers); |
| 213 | vector<bool> validTransform(nMarkers, false); |
| 214 | const auto& ids = board.getIds(); |
| 215 | for(size_t i = 0ull; i < nMarkers; i++) { |
| 216 | vector<Point2f> markerObjPoints2D; |
| 217 | int markerId = markerIds.getMat().at<int>(i0: (int)i); |
| 218 | auto it = find(first: ids.begin(), last: ids.end(), val: markerId); |
| 219 | if(it == ids.end()) continue; |
| 220 | auto boardIdx = it - ids.begin(); |
| 221 | markerObjPoints2D.resize(new_size: 4ull); |
| 222 | for(size_t j = 0ull; j < 4ull; j++) |
| 223 | markerObjPoints2D[j] = |
| 224 | Point2f(board.getObjPoints()[boardIdx][j].x, board.getObjPoints()[boardIdx][j].y); |
| 225 | transformations[i] = getPerspectiveTransform(src: markerObjPoints2D, dst: markerCorners.getMat(i: (int)i)); |
| 226 | // set transform as valid if transformation is non-singular |
| 227 | double det = determinant(mtx: transformations[i]); |
| 228 | validTransform[i] = std::abs(x: det) > 1e-6; |
| 229 | } |
| 230 | size_t nCharucoCorners = (size_t)board.getChessboardCorners().size(); |
| 231 | vector<Point2f> allChessboardImgPoints(nCharucoCorners, Point2f(-1, -1)); |
| 232 | // for each charuco corner, calculate its interpolation position based on the closest markers |
| 233 | // homographies |
| 234 | for(size_t i = 0ull; i < nCharucoCorners; i++) { |
| 235 | Point2f objPoint2D = Point2f(board.getChessboardCorners()[i].x, board.getChessboardCorners()[i].y); |
| 236 | vector<Point2f> interpolatedPositions; |
| 237 | for(size_t j = 0ull; j < board.getNearestMarkerIdx()[i].size(); j++) { |
| 238 | int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]]; |
| 239 | int markerIdx = -1; |
| 240 | for(size_t k = 0ull; k < markerIds.getMat().total(); k++) { |
| 241 | if(markerIds.getMat().at<int>(i0: (int)k) == markerId) { |
| 242 | markerIdx = (int)k; |
| 243 | break; |
| 244 | } |
| 245 | } |
| 246 | if (markerIdx != -1 && |
| 247 | validTransform[markerIdx]) |
| 248 | { |
| 249 | vector<Point2f> in, out; |
| 250 | in.push_back(x: objPoint2D); |
| 251 | perspectiveTransform(src: in, dst: out, m: transformations[markerIdx]); |
| 252 | interpolatedPositions.push_back(x: out[0]); |
| 253 | } |
| 254 | } |
| 255 | // none of the closest markers detected |
| 256 | if(interpolatedPositions.empty()) continue; |
| 257 | // more than one closest marker detected, take middle point |
| 258 | if(interpolatedPositions.size() > 1ull) { |
| 259 | allChessboardImgPoints[i] = (interpolatedPositions[0] + interpolatedPositions[1]) / 2.; |
| 260 | } |
| 261 | // a single closest marker detected |
| 262 | else allChessboardImgPoints[i] = interpolatedPositions[0]; |
| 263 | } |
| 264 | // calculate maximum window sizes for subpixel refinement. The size is limited by the distance |
| 265 | // to the closes marker corner to avoid erroneous displacements to marker corners |
| 266 | vector<Size> subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, charucoCorners: allChessboardImgPoints); |
| 267 | // filter corners outside the image and subpixel-refine charuco corners |
| 268 | selectAndRefineChessboardCorners(allCorners: allChessboardImgPoints, image, selectedCorners: charucoCorners, selectedIds: charucoIds, winSizes: subPixWinSizes); |
| 269 | } |
| 270 | |
| 271 | /** Remove charuco corners if any of their minMarkers closest markers has not been detected */ |
| 272 | int filterCornersWithoutMinMarkers(InputArray _allCharucoCorners, InputArray allCharucoIds, InputArray allArucoIds, |
| 273 | OutputArray _filteredCharucoCorners, OutputArray _filteredCharucoIds) { |
| 274 | CV_Assert(charucoParameters.minMarkers >= 0 && charucoParameters.minMarkers <= 2); |
| 275 | vector<Point2f> filteredCharucoCorners; |
| 276 | vector<int> filteredCharucoIds; |
| 277 | // for each charuco corner |
| 278 | for(unsigned int i = 0; i < allCharucoIds.getMat().total(); i++) { |
| 279 | int currentCharucoId = allCharucoIds.getMat().at<int>(i0: i); |
| 280 | int totalMarkers = 0; // nomber of closest marker detected |
| 281 | // look for closest markers |
| 282 | for(unsigned int m = 0; m < board.getNearestMarkerIdx()[currentCharucoId].size(); m++) { |
| 283 | int markerId = board.getIds()[board.getNearestMarkerIdx()[currentCharucoId][m]]; |
| 284 | bool found = false; |
| 285 | for(unsigned int k = 0; k < allArucoIds.getMat().total(); k++) { |
| 286 | if(allArucoIds.getMat().at<int>(i0: k) == markerId) { |
| 287 | found = true; |
| 288 | break; |
| 289 | } |
| 290 | } |
| 291 | if(found) totalMarkers++; |
| 292 | } |
| 293 | // if enough markers detected, add the charuco corner to the final list |
| 294 | if(totalMarkers >= charucoParameters.minMarkers) { |
| 295 | filteredCharucoIds.push_back(x: currentCharucoId); |
| 296 | filteredCharucoCorners.push_back(x: _allCharucoCorners.getMat().at<Point2f>(i0: i)); |
| 297 | } |
| 298 | } |
| 299 | // parse output |
| 300 | Mat(filteredCharucoCorners).copyTo(m: _filteredCharucoCorners); |
| 301 | Mat(filteredCharucoIds).copyTo(m: _filteredCharucoIds); |
| 302 | return (int)_filteredCharucoIds.total(); |
| 303 | } |
| 304 | |
| 305 | void detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, |
| 306 | InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) { |
| 307 | CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.total() == markerIds.total())); |
| 308 | vector<vector<Point2f>> tmpMarkerCorners; |
| 309 | vector<int> tmpMarkerIds; |
| 310 | InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; |
| 311 | InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; |
| 312 | |
| 313 | if (markerCorners.empty() && markerIds.empty()) { |
| 314 | vector<vector<Point2f> > rejectedMarkers; |
| 315 | arucoDetector.detectMarkers(image, corners: _markerCorners, ids: _markerIds, rejectedImgPoints: rejectedMarkers); |
| 316 | if (charucoParameters.tryRefineMarkers) |
| 317 | arucoDetector.refineDetectedMarkers(image, board, detectedCorners: _markerCorners, detectedIds: _markerIds, rejectedCorners: rejectedMarkers); |
| 318 | if (_markerCorners.empty() && _markerIds.empty()) |
| 319 | return; |
| 320 | } |
| 321 | // if camera parameters are avaible, use approximated calibration |
| 322 | if(!charucoParameters.cameraMatrix.empty()) |
| 323 | interpolateCornersCharucoApproxCalib(markerCorners: _markerCorners, markerIds: _markerIds, image, charucoCorners, charucoIds); |
| 324 | // else use local homography |
| 325 | else |
| 326 | interpolateCornersCharucoLocalHom(markerCorners: _markerCorners, markerIds: _markerIds, image, charucoCorners, charucoIds); |
| 327 | // to return a charuco corner, its closest aruco markers should have been detected |
| 328 | filterCornersWithoutMinMarkers(allCharucoCorners: charucoCorners, allCharucoIds: charucoIds, allArucoIds: _markerIds, filteredCharucoCorners: charucoCorners, filteredCharucoIds: charucoIds); |
| 329 | } |
| 330 | |
| 331 | void detectBoardWithCheck(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, |
| 332 | InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) { |
| 333 | vector<vector<Point2f>> tmpMarkerCorners; |
| 334 | vector<int> tmpMarkerIds; |
| 335 | InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; |
| 336 | InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; |
| 337 | detectBoard(image, charucoCorners, charucoIds, markerCorners: _markerCorners, markerIds: _markerIds); |
| 338 | if (charucoParameters.checkMarkers && checkBoard(markerCorners: _markerCorners, markerIds: _markerIds, charucoCorners, charucoIds) == false) { |
| 339 | CV_LOG_DEBUG(NULL, "ChArUco board is built incorrectly" ); |
| 340 | charucoCorners.release(); |
| 341 | charucoIds.release(); |
| 342 | } |
| 343 | } |
| 344 | }; |
| 345 | |
| 346 | CharucoDetector::CharucoDetector(const CharucoBoard &board, const CharucoParameters &charucoParams, |
| 347 | const DetectorParameters &detectorParams, const RefineParameters& refineParams) { |
| 348 | this->charucoDetectorImpl = makePtr<CharucoDetectorImpl>(a1: board, a1: charucoParams, a1: ArucoDetector(board.getDictionary(), detectorParams, refineParams)); |
| 349 | } |
| 350 | |
| 351 | const CharucoBoard& CharucoDetector::getBoard() const { |
| 352 | return charucoDetectorImpl->board; |
| 353 | } |
| 354 | |
| 355 | void CharucoDetector::setBoard(const CharucoBoard& board) { |
| 356 | this->charucoDetectorImpl->board = board; |
| 357 | charucoDetectorImpl->arucoDetector.setDictionary(board.getDictionary()); |
| 358 | } |
| 359 | |
| 360 | const CharucoParameters &CharucoDetector::getCharucoParameters() const { |
| 361 | return charucoDetectorImpl->charucoParameters; |
| 362 | } |
| 363 | |
| 364 | void CharucoDetector::setCharucoParameters(CharucoParameters &charucoParameters) { |
| 365 | charucoDetectorImpl->charucoParameters = charucoParameters; |
| 366 | } |
| 367 | |
| 368 | const DetectorParameters& CharucoDetector::getDetectorParameters() const { |
| 369 | return charucoDetectorImpl->arucoDetector.getDetectorParameters(); |
| 370 | } |
| 371 | |
| 372 | void CharucoDetector::setDetectorParameters(const DetectorParameters& detectorParameters) { |
| 373 | charucoDetectorImpl->arucoDetector.setDetectorParameters(detectorParameters); |
| 374 | } |
| 375 | |
| 376 | const RefineParameters& CharucoDetector::getRefineParameters() const { |
| 377 | return charucoDetectorImpl->arucoDetector.getRefineParameters(); |
| 378 | } |
| 379 | |
| 380 | void CharucoDetector::setRefineParameters(const RefineParameters& refineParameters) { |
| 381 | charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters); |
| 382 | } |
| 383 | |
| 384 | void CharucoDetector::detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, |
| 385 | InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) const { |
| 386 | charucoDetectorImpl->detectBoardWithCheck(image, charucoCorners, charucoIds, markerCorners, markerIds); |
| 387 | } |
| 388 | |
| 389 | void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diamondCorners, OutputArray _diamondIds, |
| 390 | InputOutputArrayOfArrays inMarkerCorners, InputOutputArray inMarkerIds) const { |
| 391 | CV_Assert(getBoard().getChessboardSize() == Size(3, 3)); |
| 392 | CV_Assert((inMarkerCorners.empty() && inMarkerIds.empty() && !image.empty()) || (inMarkerCorners.total() == inMarkerIds.total())); |
| 393 | |
| 394 | vector<vector<Point2f>> tmpMarkerCorners; |
| 395 | vector<int> tmpMarkerIds; |
| 396 | InputOutputArrayOfArrays _markerCorners = inMarkerCorners.needed() ? inMarkerCorners : tmpMarkerCorners; |
| 397 | InputOutputArray _markerIds = inMarkerIds.needed() ? inMarkerIds : tmpMarkerIds; |
| 398 | if (_markerCorners.empty() && _markerIds.empty()) { |
| 399 | charucoDetectorImpl->arucoDetector.detectMarkers(image, corners: _markerCorners, ids: _markerIds); |
| 400 | } |
| 401 | |
| 402 | const float minRepDistanceRate = 1.302455f; |
| 403 | vector<vector<Point2f>> diamondCorners; |
| 404 | vector<Vec4i> diamondIds; |
| 405 | |
| 406 | // stores if the detected markers have been assigned or not to a diamond |
| 407 | vector<bool> assigned(_markerIds.total(), false); |
| 408 | if(_markerIds.total() < 4ull) return; // a diamond need at least 4 markers |
| 409 | |
| 410 | // convert input image to grey |
| 411 | Mat grey; |
| 412 | if(image.type() == CV_8UC3) |
| 413 | cvtColor(src: image, dst: grey, code: COLOR_BGR2GRAY); |
| 414 | else |
| 415 | grey = image.getMat(); |
| 416 | auto board = getBoard(); |
| 417 | |
| 418 | // for each of the detected markers, try to find a diamond |
| 419 | for(unsigned int i = 0; i < (unsigned int)_markerIds.total(); i++) { |
| 420 | if(assigned[i]) continue; |
| 421 | |
| 422 | // calculate marker perimeter |
| 423 | float perimeterSq = 0; |
| 424 | Mat corners = _markerCorners.getMat(i); |
| 425 | for(int c = 0; c < 4; c++) { |
| 426 | Point2f edge = corners.at<Point2f>(i0: c) - corners.at<Point2f>(i0: (c + 1) % 4); |
| 427 | perimeterSq += edge.x*edge.x + edge.y*edge.y; |
| 428 | } |
| 429 | // maximum reprojection error relative to perimeter |
| 430 | float minRepDistance = sqrt(x: perimeterSq) * minRepDistanceRate; |
| 431 | |
| 432 | int currentId = _markerIds.getMat().at<int>(i0: i); |
| 433 | |
| 434 | // prepare data to call refineDetectedMarkers() |
| 435 | // detected markers (only the current one) |
| 436 | vector<Mat> currentMarker; |
| 437 | vector<int> currentMarkerId; |
| 438 | currentMarker.push_back(x: _markerCorners.getMat(i)); |
| 439 | currentMarkerId.push_back(x: currentId); |
| 440 | |
| 441 | // marker candidates (the rest of markers if they have not been assigned) |
| 442 | vector<Mat> candidates; |
| 443 | vector<int> candidatesIdxs; |
| 444 | for(unsigned int k = 0; k < assigned.size(); k++) { |
| 445 | if(k == i) continue; |
| 446 | if(!assigned[k]) { |
| 447 | candidates.push_back(x: _markerCorners.getMat(i: k)); |
| 448 | candidatesIdxs.push_back(x: k); |
| 449 | } |
| 450 | } |
| 451 | if(candidates.size() < 3ull) break; // we need at least 3 free markers |
| 452 | // modify charuco layout id to make sure all the ids are different than current id |
| 453 | vector<int> tmpIds(4ull); |
| 454 | for(int k = 1; k < 4; k++) |
| 455 | tmpIds[k] = currentId + 1 + k; |
| 456 | // current id is assigned to [0], so it is the marker on the top |
| 457 | tmpIds[0] = currentId; |
| 458 | |
| 459 | // create Charuco board layout for diamond (3x3 layout) |
| 460 | charucoDetectorImpl->board = CharucoBoard(Size(3, 3), board.getSquareLength(), |
| 461 | board.getMarkerLength(), board.getDictionary(), tmpIds); |
| 462 | |
| 463 | // try to find the rest of markers in the diamond |
| 464 | vector<int> acceptedIdxs; |
| 465 | if (currentMarker.size() != 4ull) |
| 466 | { |
| 467 | RefineParameters refineParameters(minRepDistance, -1.f, false); |
| 468 | RefineParameters tmp = charucoDetectorImpl->arucoDetector.getRefineParameters(); |
| 469 | charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters); |
| 470 | charucoDetectorImpl->arucoDetector.refineDetectedMarkers(image: grey, board: getBoard(), detectedCorners: currentMarker, detectedIds: currentMarkerId, |
| 471 | rejectedCorners: candidates, |
| 472 | cameraMatrix: noArray(), distCoeffs: noArray(), recoveredIdxs: acceptedIdxs); |
| 473 | charucoDetectorImpl->arucoDetector.setRefineParameters(tmp); |
| 474 | } |
| 475 | |
| 476 | // if found, we have a diamond |
| 477 | if(currentMarker.size() == 4ull) { |
| 478 | assigned[i] = true; |
| 479 | // calculate diamond id, acceptedIdxs array indicates the markers taken from candidates array |
| 480 | Vec4i markerId; |
| 481 | markerId[0] = currentId; |
| 482 | for(int k = 1; k < 4; k++) { |
| 483 | int currentMarkerIdx = candidatesIdxs[acceptedIdxs[k - 1]]; |
| 484 | markerId[k] = _markerIds.getMat().at<int>(i0: currentMarkerIdx); |
| 485 | assigned[currentMarkerIdx] = true; |
| 486 | } |
| 487 | |
| 488 | // interpolate the charuco corners of the diamond |
| 489 | vector<Point2f> currentMarkerCorners; |
| 490 | Mat aux; |
| 491 | charucoDetectorImpl->detectBoardWithCheck(image: grey, charucoCorners: currentMarkerCorners, charucoIds: aux, markerCorners: currentMarker, markerIds: currentMarkerId); |
| 492 | |
| 493 | // if everything is ok, save the diamond |
| 494 | if(currentMarkerCorners.size() > 0ull) { |
| 495 | // reorder corners |
| 496 | vector<Point2f> currentMarkerCornersReorder; |
| 497 | currentMarkerCornersReorder.resize(new_size: 4); |
| 498 | currentMarkerCornersReorder[0] = currentMarkerCorners[0]; |
| 499 | currentMarkerCornersReorder[1] = currentMarkerCorners[1]; |
| 500 | currentMarkerCornersReorder[2] = currentMarkerCorners[3]; |
| 501 | currentMarkerCornersReorder[3] = currentMarkerCorners[2]; |
| 502 | |
| 503 | diamondCorners.push_back(x: currentMarkerCornersReorder); |
| 504 | diamondIds.push_back(x: markerId); |
| 505 | } |
| 506 | } |
| 507 | } |
| 508 | charucoDetectorImpl->board = board; |
| 509 | |
| 510 | if(diamondIds.size() > 0ull) { |
| 511 | // parse output |
| 512 | Mat(diamondIds).copyTo(m: _diamondIds); |
| 513 | |
| 514 | _diamondCorners.create(rows: (int)diamondCorners.size(), cols: 1, CV_32FC2); |
| 515 | for(unsigned int i = 0; i < diamondCorners.size(); i++) { |
| 516 | _diamondCorners.create(rows: 4, cols: 1, CV_32FC2, i, allowTransposed: true); |
| 517 | for(int j = 0; j < 4; j++) { |
| 518 | _diamondCorners.getMat(i).at<Point2f>(i0: j) = diamondCorners[i][j]; |
| 519 | } |
| 520 | } |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | void drawDetectedCornersCharuco(InputOutputArray _image, InputArray _charucoCorners, |
| 525 | InputArray _charucoIds, Scalar cornerColor) { |
| 526 | CV_Assert(!_image.getMat().empty() && |
| 527 | (_image.getMat().channels() == 1 || _image.getMat().channels() == 3)); |
| 528 | CV_Assert((_charucoCorners.total() == _charucoIds.total()) || |
| 529 | _charucoIds.total() == 0); |
| 530 | CV_Assert(_charucoCorners.channels() == 2); |
| 531 | |
| 532 | Mat charucoCorners = _charucoCorners.getMat(); |
| 533 | if (charucoCorners.type() != CV_32SC2) |
| 534 | charucoCorners.convertTo(m: charucoCorners, CV_32SC2); |
| 535 | Mat charucoIds; |
| 536 | if (!_charucoIds.empty()) |
| 537 | charucoIds = _charucoIds.getMat(); |
| 538 | size_t nCorners = charucoCorners.total(); |
| 539 | for(size_t i = 0; i < nCorners; i++) { |
| 540 | Point corner = charucoCorners.at<Point>(i0: (int)i); |
| 541 | // draw first corner mark |
| 542 | rectangle(img: _image, pt1: corner - Point(3, 3), pt2: corner + Point(3, 3), color: cornerColor, thickness: 1, lineType: LINE_AA); |
| 543 | // draw ID |
| 544 | if(!_charucoIds.empty()) { |
| 545 | int id = charucoIds.at<int>(i0: (int)i); |
| 546 | stringstream s; |
| 547 | s << "id=" << id; |
| 548 | putText(img: _image, text: s.str(), org: corner + Point(5, -5), fontFace: FONT_HERSHEY_SIMPLEX, fontScale: 0.5, |
| 549 | color: cornerColor, thickness: 2); |
| 550 | } |
| 551 | } |
| 552 | } |
| 553 | |
| 554 | void drawDetectedDiamonds(InputOutputArray _image, InputArrayOfArrays _corners, InputArray _ids, Scalar borderColor) { |
| 555 | CV_Assert(_image.getMat().total() != 0 && |
| 556 | (_image.getMat().channels() == 1 || _image.getMat().channels() == 3)); |
| 557 | CV_Assert((_corners.total() == _ids.total()) || _ids.total() == 0); |
| 558 | |
| 559 | // calculate colors |
| 560 | Scalar textColor, cornerColor; |
| 561 | textColor = cornerColor = borderColor; |
| 562 | swap(a&: textColor.val[0], b&: textColor.val[1]); // text color just sawp G and R |
| 563 | swap(a&: cornerColor.val[1], b&: cornerColor.val[2]); // corner color just sawp G and B |
| 564 | |
| 565 | int nMarkers = (int)_corners.total(); |
| 566 | for(int i = 0; i < nMarkers; i++) { |
| 567 | Mat currentMarker = _corners.getMat(i); |
| 568 | CV_Assert(currentMarker.total() == 4 && currentMarker.channels() == 2); |
| 569 | if (currentMarker.type() != CV_32SC2) |
| 570 | currentMarker.convertTo(m: currentMarker, CV_32SC2); |
| 571 | |
| 572 | // draw marker sides |
| 573 | for(int j = 0; j < 4; j++) { |
| 574 | Point p0, p1; |
| 575 | p0 = currentMarker.at<Point>(i0: j); |
| 576 | p1 = currentMarker.at<Point>(i0: (j + 1) % 4); |
| 577 | line(img: _image, pt1: p0, pt2: p1, color: borderColor, thickness: 1); |
| 578 | } |
| 579 | |
| 580 | // draw first corner mark |
| 581 | rectangle(img: _image, pt1: currentMarker.at<Point>(i0: 0) - Point(3, 3), |
| 582 | pt2: currentMarker.at<Point>(i0: 0) + Point(3, 3), color: cornerColor, thickness: 1, lineType: LINE_AA); |
| 583 | |
| 584 | // draw id composed by four numbers |
| 585 | if(_ids.total() != 0) { |
| 586 | Point cent(0, 0); |
| 587 | for(int p = 0; p < 4; p++) |
| 588 | cent += currentMarker.at<Point>(i0: p); |
| 589 | cent = cent / 4.; |
| 590 | stringstream s; |
| 591 | s << "id=" << _ids.getMat().at< Vec4i >(i0: i); |
| 592 | putText(img: _image, text: s.str(), org: cent, fontFace: FONT_HERSHEY_SIMPLEX, fontScale: 0.5, color: textColor, thickness: 2); |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | |
| 597 | } |
| 598 | } |
| 599 | |