IsometricPatternMatcher/HexGridFitting.cpp (729 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #include <IsometricPatternMatcher/CameraModels.h> #include <IsometricPatternMatcher/HexGridCostFunction.h> #include <IsometricPatternMatcher/HexGridFitting.h> #include <IsometricPatternMatcher/IsometricPattern.h> #include <IsometricPatternMatcher/LocalParamSe3.h> #include <fmt/format.h> #include <glog/logging.h> #include <src/Core/Matrix.h> #include <limits> #include <numeric> #include <queue> namespace surreal_opensource { HexGridFitting::HexGridFitting(const Eigen::Matrix2Xd& imageDots, const Eigen::Vector2d& centerXY, double focalLength, const Eigen::VectorXd& intensity, bool ifDistort, bool ifTwoShot, bool ifPoseMerge, double spacing, int numNeighboursForPoseEst, int numberSegX, int numberSegY, double perPointSearchRadius, int numNeighbourLayer) : spacing_(spacing), numNeighboursForPoseEst_(numNeighboursForPoseEst), numberSegX_(numberSegX), numberSegY_(numberSegY), imageDots_(imageDots), intensity_(intensity), perPointSearchRadius_(perPointSearchRadius), numNeighbourLayer_(numNeighbourLayer), focalLength_(focalLength), centerXY_(centerXY), ifDistort_(ifDistort), ifTwoShot_(ifTwoShot), ifPoseMerge_(ifPoseMerge) { distortionParams_ = Eigen::Vector4d::Zero(4, 1); ceres::Solver::Options solverOptions; findPoseAndCamModel(solverOptions); transferDots_ = reprojectDots(T_camera_target_, distortionParams_, imageDots_); getStorageMap(); } HexGridFitting::HexGridFitting( const Eigen::Matrix2Xd& imageDots, const Eigen::Vector2d& centerXY, double focalLength, const Eigen::VectorXi& dotLabels, bool ifDistort, bool ifTwoShot, bool ifPoseMerge, double goodPoseInlierRatio, double spacing, int numNeighboursForPoseEst, int numberSegX, int numberSegY, double perPointSearchRadius, int numNeighbourLayer) : spacing_(spacing), numNeighboursForPoseEst_(numNeighboursForPoseEst), numberSegX_(numberSegX), numberSegY_(numberSegY), imageDots_(imageDots), dotLabels_(dotLabels), perPointSearchRadius_(perPointSearchRadius), numNeighbourLayer_(numNeighbourLayer), focalLength_(focalLength), centerXY_(centerXY), ifDistort_(ifDistort), ifTwoShot_(ifTwoShot), ifPoseMerge_(ifPoseMerge) { distortionParams_ = Eigen::Vector4d::Zero(4, 1); ceres::Solver::Options solverOptions; if (ifPoseMerge_) { Eigen::VectorXi selectedPoseIdx(numberSegX * numberSegY); selectedPoseIdx = findGoodPoseIndex(goodPoseInlierRatio, solverOptions); std::cout << "selected PoseIdx is: "; for (size_t i = 0; i < selectedPoseIdx.size(); i++) std::cout << ' ' << selectedPoseIdx[i]; std::cout << '\n'; std::vector<Eigen::Matrix2Xd> transferDotsGroup; for (int i = 0; i < selectedPoseIdx.size(); ++i) { if (selectedPoseIdx(i) == -1) { break; // selectedPoseIdx is index of selected good poses and unselected pose // index will be assigned as -1 so -1 is served as a stop sign } findPoseAndCamModel(solverOptions, selectedPoseIdx(i)); Eigen::Matrix2Xd transferDotsNew = reprojectDots(T_camera_target_, distortionParams_, imageDots_); transferDotsGroup.push_back(transferDotsNew); } getStorageMapFromPoseSeq(transferDotsGroup); transferDots_ = transferDotsGroup.at(0); } else { findPoseAndCamModel(solverOptions, -1); transferDots_ = reprojectDots(T_camera_target_, distortionParams_, imageDots_); getStorageMap(); } } void HexGridFitting::setParams(const Eigen::Vector2d& centerXY, double focalLength, bool ifDistort, bool ifTwoShot, bool ifPoseMerge, double spacing, int numNeighboursForPoseEst, int numberSegX, int numberSegY, double perPointSearchRadius, int numNeighbourLayer) { spacing_ = spacing; numNeighboursForPoseEst_ = numNeighboursForPoseEst; numberSegX_ = numberSegX; numberSegY_ = numberSegY; perPointSearchRadius_ = perPointSearchRadius; numNeighbourLayer_ = numNeighbourLayer; focalLength_ = focalLength; centerXY_ = centerXY; ifDistort_ = ifDistort; ifTwoShot_ = ifTwoShot; ifPoseMerge_ = ifPoseMerge; distortionParams_ = Eigen::Vector4d::Zero(4, 1); } void HexGridFitting::setImageDots(const Eigen::Matrix2Xd& imageDots) { imageDots_ = imageDots; } void HexGridFitting::setTransferedDots(const Eigen::Matrix2Xd& transferDots) { transferDots_ = transferDots; } void HexGridFitting::setIntensity(const Eigen::VectorXd& intensity) { intensity_ = intensity; } void HexGridFitting::setIndexMap(const Eigen::MatrixXi& indexMap) { indexMap_ = indexMap; } std::vector<Eigen::Matrix2Xd> HexGridFitting::imageNeighbourMatrix( int numberNeighours) { std::vector<Eigen::Matrix2Xd> result; for (int j = 0; j < numberNeighours; ++j) { result.push_back(Eigen::Matrix2Xd::Zero(2, imageDots_.cols())); } for (int idxDot = 0; idxDot < imageDots_.cols(); ++idxDot) { std::vector<double> distance; std::vector<int> indx; for (int j = 0; j < imageDots_.cols(); ++j) distance.push_back((imageDots_.col(idxDot) - imageDots_.col(j)).norm()); getSortIndx(distance, indx); for (int j = 0; j < numberNeighours; ++j) { result.at(j).col(idxDot) = imageDots_.col(indx.at(j + 1)); // the first one is itself } } // end for return result; } template <typename T> void HexGridFitting::getSortIndx(const T& coords, std::vector<int>& idx) { idx.resize(coords.size()); std::iota(idx.begin(), idx.end(), 0); sort(idx.begin(), idx.end(), [&coords](size_t i1, size_t i2) { return coords[i1] < coords[i2]; }); return; } std::vector<int> HexGridFitting::findInliers( const std::vector<Eigen::Matrix2Xd>& neighbourDots, const Sophus::SE3d& T_camera_target, const Eigen::Vector4d& distortionParams, const double inlierThreshold) { // if each distance of the numberNeighbour closest neighours to the dots in // the transferred space is around spacing, the dot is considered as an inlier std::vector<int> inliersIndx; size_t numberNeighbour = neighbourDots.size(); std::vector<Eigen::VectorXd> distance; for (auto const& neighbourMatrix : neighbourDots) { distance.push_back( (reprojectDots(T_camera_target, distortionParams, imageDots_) - reprojectDots(T_camera_target, distortionParams, neighbourMatrix)) .colwise() .norm() - Eigen::MatrixXd::Constant(1, imageDots_.cols(), spacing_)); } for (int i = 0; i < imageDots_.cols(); ++i) { size_t numInlierPts = 0; for (const auto& neighourDist : distance) { if (abs(neighourDist(i)) <= inlierThreshold) { numInlierPts++; } } if (numInlierPts == numberNeighbour) inliersIndx.push_back(i); } return inliersIndx; } bool HexGridFitting::findT_camera_target( const ceres::Solver::Options& solverOption, const std::vector<Eigen::Matrix2Xd>& neighbourDots, // each Matrix2Xd is the neighbours of a detected dot (# // neighbours = numNeighboursForPoseEst_) const std::vector<int>& sampleIndx, // sampleIndx stores the indices of // detected dots for the patch const double inlierThreshold, const Sophus::Plane3d& plane, Sophus::SE3d& T_camera_target, std::vector<int>& inliersIndx) { ceres::Problem::Options options; ceres::Problem problem(options); std::vector<double*> params = {T_camera_target.data()}; LocalParamSe3* localParamSe3 = new LocalParamSe3; problem.AddParameterBlock(params[0], Sophus::SE3d::num_parameters, localParamSe3); ceres::LossFunction* loss_function = nullptr; Eigen::VectorXd cameraModelParams; if (ifDistort_) { // KB3 camera model cameraModelParams.resize(KannalaBrandtK3Projection::kNumParams, 1); cameraModelParams << focalLength_, focalLength_, centerXY_(0), centerXY_(1), 0.0, 0.0, 0.0, 0.0; } else { // Linear camera model cameraModelParams.resize(PinholeProjection::kNumParams, 1); cameraModelParams << focalLength_, focalLength_, centerXY_(0), centerXY_(1); } for (auto& i : sampleIndx) { for (auto const& neighbourMatrix : neighbourDots) { Ray3d rayInCamera; Ray3d rayNeighbourInCamera; if (ifDistort_) { rayInCamera = KannalaBrandtK3Projection::unproject(imageDots_.col(i), cameraModelParams); rayNeighbourInCamera = KannalaBrandtK3Projection::unproject( neighbourMatrix.col(i), cameraModelParams); } else { rayInCamera = PinholeProjection::unproject(imageDots_.col(i), cameraModelParams); rayNeighbourInCamera = PinholeProjection::unproject( neighbourMatrix.col(i), cameraModelParams); } ceres::CostFunction* cost = new ceres::AutoDiffCostFunction<isometricPatternPoseCost, 1, Sophus::SE3d::num_parameters>( new isometricPatternPoseCost(rayInCamera, rayNeighbourInCamera, plane, spacing_)); problem.AddResidualBlock(cost, loss_function, params[0]); } } ceres::Solver::Summary summary; ceres::Solve(solverOption, &problem, &summary); if (!summary.IsSolutionUsable()) return false; if (T_camera_target.inverse().translation().z() < 0) { T_camera_target.translation().z() = -T_camera_target.translation().z(); } // make sure the camera is on top of the target (not behind the target) if (T_camera_target.translation().z() < 0) { T_camera_target *= Sophus::SE3d::rotX(M_PI); } // make sure the camera is facing the target inliersIndx = findInliers(neighbourDots, T_camera_target, distortionParams_, inlierThreshold); return true; } bool HexGridFitting::findKb3DistortionParams( const ceres::Solver::Options& solverOption, const std::vector<Eigen::Matrix2Xd>& neighbourDots, const std::vector<int>& sampleIndx, const double inlierThreshold, const Sophus::Plane3d& plane, Sophus::SE3d& T_camera_target, std::vector<double>& distortionParams, std::vector<int>& inliersIndx) { ceres::LossFunction* loss_function = nullptr; // new ceres::CauchyLoss(1.0); size_t numberNeighbour = neighbourDots.size(); ceres::Problem::Options options; std::vector<double*> params = {T_camera_target.data()}; std::vector<double*> paramsDistortion = {distortionParams.data()}; ceres::Problem problemDistortion(options); problemDistortion.AddParameterBlock(params[0], Sophus::SE3d::num_parameters, new LocalParamSe3); for (size_t j = 0; j < numberNeighbour; ++j) { for (const int& i : sampleIndx) { ceres::CostFunction* cost = new ceres::AutoDiffCostFunction<isometricPatternDistortionCost, 1, Sophus::SE3d::num_parameters, 4>( new isometricPatternDistortionCost( imageDots_.col(i), neighbourDots.at(j).col(i), centerXY_, focalLength_, plane)); problemDistortion.AddResidualBlock(cost, loss_function, params[0], paramsDistortion[0]); } } ceres::Solver::Summary summary; ceres::Solve(solverOption, &problemDistortion, &summary); if (!summary.IsSolutionUsable()) return false; inliersIndx = findInliers( neighbourDots, T_camera_target, Eigen::Map<Eigen::Vector4d>(distortionParams.data()), inlierThreshold); if (T_camera_target.inverse().translation().z() < 0) { T_camera_target.translation().z() = -T_camera_target.translation().z(); } // make sure the camera is on top of the target (not behind the target) if (T_camera_target.translation().z() < 0) { T_camera_target *= Sophus::SE3d::rotX(M_PI); } // make sure the camera is facing the target return true; } Eigen::VectorXi HexGridFitting::findGoodPoseIndex( double goodPoseInlierRatio, const ceres::Solver::Options& solverOption, const Sophus::SE3d& initT_camera_target) { Eigen::VectorXi selectedPoseIdx(numberSegX_ * numberSegY_); selectedPoseIdx.fill(-1); Sophus::Plane3d plane(Sophus::Vector3d(0.0, 0.0, 1.0), 0.0); std::vector<Eigen::Matrix2Xd> neighbourDots = imageNeighbourMatrix(numNeighboursForPoseEst_); std::vector<Sophus::SE3d> Ts_camera_targetForSubregions; std::vector<std::vector<int>> inliersIndx(numberSegX_ * numberSegY_); std::vector<int> bestIndxs = calculateSubregionPosesAndBestIndex( solverOption, plane, neighbourDots, initT_camera_target, Ts_camera_targetForSubregions, inliersIndx); // build descend order selectedPoseIdx for (int i = 0; i < selectedPoseIdx.size(); ++i) { int idx = bestIndxs.size() - i - 1; int poseidx = bestIndxs.at(idx); // only select pose with enough inliers if (inliersIndx[poseidx].size() >= imageDots_.cols() * goodPoseInlierRatio) { selectedPoseIdx(i) = poseidx; } } return selectedPoseIdx; } std::vector<int> HexGridFitting::calculateSubregionPosesAndBestIndex( const ceres::Solver::Options& solverOption, const Sophus::Plane3d& plane, const std::vector<Eigen::Matrix2Xd>& neighbourDots, const Sophus::SE3d& initT_camera_target, std::vector<Sophus::SE3d>& Ts_camera_targetForSubregions, std::vector<std::vector<int>>& inliersIndx) { // blocks double maxX = imageDots_.row(0).maxCoeff(); double minX = imageDots_.row(0).minCoeff(); double maxY = imageDots_.row(1).maxCoeff(); double minY = imageDots_.row(1).minCoeff(); std::vector<double> segX; std::vector<double> segY; // segment X for (int i = 0; i <= numberSegX_; i++) { segX.push_back(minX + (maxX - minX) / numberSegX_ * i); } // segment Y for (int i = 0; i <= numberSegY_; i++) { segY.push_back(minY + (maxY - minY) / numberSegY_ * i); } std::vector<std::vector<int>> blockIndx(numberSegX_ * numberSegY_); for (size_t i = 0; i < imageDots_.cols(); ++i) { for (int x = 0; x < numberSegX_; x++) { for (int y = 0; y < numberSegY_; y++) { if (imageDots_(0, i) >= segX[x] && imageDots_(0, i) < segX[x + 1] && imageDots_(1, i) >= segY[y] && imageDots_(1, i) < segY[y + 1]) blockIndx[x * numberSegY_ + y].push_back(i); } } } std::vector<int> numberInliers; std::vector<int> indx; for (int i = 0; i < blockIndx.size(); ++i) { Ts_camera_targetForSubregions.push_back( initT_camera_target); // initialization if (findT_camera_target(solverOption, neighbourDots, blockIndx[i], 0.2, plane, Ts_camera_targetForSubregions[i], inliersIndx[i])) { numberInliers.push_back(inliersIndx[i].size()); } } getSortIndx(numberInliers, indx); // ascend order return indx; } void HexGridFitting::findPoseAndCamModel( const ceres::Solver::Options& solverOption, int selectIndx, const Sophus::SE3d& initT_camera_target) { Sophus::Plane3d plane(Sophus::Vector3d(0.0, 0.0, 1.0), 0.0); std::vector<Eigen::Matrix2Xd> neighbourDots = imageNeighbourMatrix(numNeighboursForPoseEst_); std::vector<Sophus::SE3d> Ts_camera_targetForSubregions; std::vector<std::vector<int>> inliersIndx(numberSegX_ * numberSegY_); // per block pose estimation std::vector<int> bestIndxs = calculateSubregionPosesAndBestIndex( solverOption, plane, neighbourDots, initT_camera_target, Ts_camera_targetForSubregions, inliersIndx); int bestIndx = bestIndxs.back(); if (selectIndx != -1) { bestIndx = selectIndx; } // global re-estimate T_camera_target_ = Ts_camera_targetForSubregions[bestIndx]; std::vector<int> inliersPoseIndx; bool ifFindTCameraTarget = findT_camera_target(solverOption, neighbourDots, inliersIndx[bestIndx], 0.3, plane, T_camera_target_, inliersPoseIndx); CHECK(ifFindTCameraTarget) << "Cannot find T_camera_target"; CHECK_GT(inliersPoseIndx.size(), 0) << "No inliers for T_camera_target. You " "can try with different focal length."; inlierPose.resize(2, inliersPoseIndx.size()); for (size_t i = 0; i < inliersPoseIndx.size(); ++i) { inlierPose.col(i) = imageDots_.col(inliersPoseIndx[i]); } if (ifDistort_) { std::vector<int> inliersDistortIndx; std::vector<double> distortionParams = {0.0, 0.0, 0.0, 0.0}; if (findKb3DistortionParams(solverOption, neighbourDots, inliersPoseIndx, 0.3, plane, T_camera_target_, distortionParams, inliersDistortIndx)) { distortionParams_ = Eigen::Map<Eigen::Vector4d>(distortionParams.data()); // fmt::print("Distortion parameters: {} \n", // distortionParams_.transpose()); inlierDistortion.resize(2, inliersDistortIndx.size()); for (size_t i = 0; i < inliersDistortIndx.size(); ++i) { inlierDistortion.col(i) = imageDots_.col(inliersDistortIndx[i]); } } else { LOG(INFO) << "Warning: Cannot find distortion parameters. Please try " "with different focal length"; } } } Eigen::Matrix2Xd HexGridFitting::reprojectDots( const Sophus::SE3d& T_camera_target, const Eigen::Vector4d& distortionParams, const Eigen::Matrix2Xd& imageDots) { Eigen::Matrix2Xd result; result.resize(2, imageDots.cols()); Sophus::Plane3d plane(Sophus::Vector3d(0.0, 0.0, 1.0), 0); for (int i = 0; i < imageDots.cols(); ++i) { Ray3d rayInCamera; if (ifDistort_) { Sophus::Vector<double, KannalaBrandtK3Projection::kNumParams> intrinsics; intrinsics << focalLength_, focalLength_, centerXY_(0), centerXY_(1), distortionParams(0), distortionParams(1), distortionParams(2), distortionParams(3); rayInCamera = KannalaBrandtK3Projection::unproject(imageDots.col(i), intrinsics); } else { Sophus::Vector<double, PinholeProjection::kNumParams> intrinsics; intrinsics << focalLength_, focalLength_, centerXY_(0), centerXY_(1); rayInCamera = PinholeProjection::unproject(imageDots.col(i), intrinsics); } Ray3d rayInTarget = T_camera_target.inverse() * rayInCamera; Sophus::Vector3d ptTarget3d = rayInTarget.line().intersectionPoint(plane); result.col(i) = ptTarget3d.head<2>(); } return result; } void HexGridFitting::getStorageMap() { Eigen::Matrix3Xi cubeCoor; cubeCoor.resize(3, transferDots_.cols()); cubeCoor.fill(std::numeric_limits<int>::max()); // not detected =max int // get cube coordinate int minX = 0; int maxX = 0; int minZ = 0; int maxZ = 0; getCubeCoordinate(transferDots_, minX, maxX, minZ, maxZ, cubeCoor, bfsProcessSeq_); binaryCode_ = Eigen::VectorXi::Constant(transferDots_.cols(), 1, 2); buildBinaryCode(cubeCoor, minX, maxX, minZ, maxZ); } int HexGridFitting::getCubeCoordinate(const Eigen::Matrix2Xd& transferDots, int& minX, int& maxX, int& minZ, int& maxZ, Eigen::Matrix3Xi& cubeCoor, std::vector<int>& bfsProcessSeq) { cubeCoor.resize(3, transferDots.cols()); cubeCoor.fill(std::numeric_limits<int>::max()); // not detected =max int std::queue<int> bfsQueue; Eigen::VectorXi processed; processed.setZero(transferDots.cols(), 1); Eigen::Matrix3Xi cubedirection; cubedirection.resize(3, IsometricGridDot::kNumNeighbours); cubedirection << -1, 0, 1, 1, 0, -1, 1, 1, 0, -1, -1, 0, 0, -1, -1, 0, 1, 1; // start from the center Eigen::Vector2d center; center(0) = transferDots.row(0).mean(); center(1) = transferDots.row(1).mean(); Eigen::VectorXi centerNeighbour; neighboursIdxInArea(transferDots, center, 2 * spacing_, centerNeighbour); // find a point near the center CHECK(centerNeighbour.rows() > 0) << "center neighbor size should be >0"; int startIdx = centerNeighbour(0); // manually set transferDots transferDots_ = transferDots; searchDirectionsOnPattern_ = getDirections(startIdx); // get cube coordinates processed(startIdx) = 1; cubeCoor.col(startIdx) << 0, 0, 0; bfsQueue.push(startIdx); while (!bfsQueue.empty()) { int centerIndx = bfsQueue.front(); bfsQueue.pop(); for (int k = 0; k < IsometricGridDot::kNumNeighbours; k++) { Eigen::VectorXi Indx; Eigen::Vector2d possLocation = transferDots.col(centerIndx) + searchDirectionsOnPattern_.col(k); if (neighboursIdxInArea(transferDots, possLocation, perPointSearchRadius_, Indx)) { if (processed(Indx(0)) == 0) { // check if the point is near the possible location bfsQueue.push(Indx(0)); bfsProcessSeq.push_back(Indx(0)); processed(Indx(0)) = 1; cubeCoor.col(Indx(0)) = cubeCoor.col(centerIndx) + cubedirection.col(k); if (minX > cubeCoor(0, Indx(0))) minX = cubeCoor(0, Indx(0)); if (minZ > cubeCoor(2, Indx(0))) minZ = cubeCoor(2, Indx(0)); if (maxX < cubeCoor(0, Indx(0))) maxX = cubeCoor(0, Indx(0)); if (maxZ < cubeCoor(2, Indx(0))) maxZ = cubeCoor(2, Indx(0)); } // end if } // end if } // end for } // end while if (processed.sum() < transferDots.cols()) LOG(INFO) << fmt::format( "{} points are processed in BFS from total {} detected points", processed.sum(), transferDots.cols()); return startIdx; } Eigen::Vector3i HexGridFitting::doLeftRotate(const Eigen::Vector3i& coord, size_t rotIdx) { // iteration for rotIdx times of 60 degree left (counter clockwise) rotation Eigen::Vector3i rotCubeCoor = coord; for (size_t i = 0; i < rotIdx; ++i) { rotCubeCoor = IsometricGridDot::rotateLeft60ForDot(rotCubeCoor); } return rotCubeCoor; } Eigen::Vector3i HexGridFitting::doRightRotate(const Eigen::Vector3i& coord, size_t rotIdx) { // iteration for rotIdx times of 60 degree right (clockwise) rotation Eigen::Vector3i rotCubeCoor = coord; for (size_t i = 0; i < rotIdx; ++i) { rotCubeCoor = IsometricGridDot::rotateRight60ForDot(rotCubeCoor); } return rotCubeCoor; } int HexGridFitting::determineRotation(const Eigen::Matrix3Xi& cubeCoor1, const Eigen::Matrix3Xi& cubeCoor2, const Eigen::Matrix3Xi& cubeCoorDiff) { CHECK(cubeCoor1.cols() == cubeCoor2.cols()) << "cubeCoor should have same size"; size_t rotIdx = 0; // number of left 60 degree Eigen::VectorXi inlierNumber(6); inlierNumber.fill(0); for (int i = 0; i < cubeCoor1.cols(); i++) { if ((cubeCoor1(0, i) != std::numeric_limits<int>::max()) && (cubeCoor2(0, i) != std::numeric_limits<int>::max())) { // we only use shared dot to determine the rotation for (rotIdx = 0; rotIdx < 6; ++rotIdx) { if (cubeCoor1.col(i) == doLeftRotate(cubeCoor2.col(i), rotIdx) + cubeCoorDiff) { inlierNumber(rotIdx) += 1; } } } } // determine best rotation index int bestNumber = 0; for (int i = 0; i < 6; ++i) { if (bestNumber < inlierNumber(i)) { bestNumber = inlierNumber(i); rotIdx = i; } } return rotIdx; } Eigen::Matrix3Xi HexGridFitting::mergeCubeCoordinate( const Eigen::Matrix3Xi& cubeCoor1, const Eigen::Matrix3Xi& cubeCoor2, int startIdx1, int startIdx2, int& minX, int& maxX, int& minZ, int& maxZ, int poseIdx) { // merge cube coordinate results from two poses Eigen::Matrix3Xi cubeCoor; cubeCoor.resize(3, cubeCoor1.cols()); cubeCoor.fill(std::numeric_limits<int>::max()); // not detected =max int // calculate coordinate translation Eigen::Matrix3Xi cubeCoorDiff; cubeCoorDiff.resize(3, 1); cubeCoorDiff << 0, 0, 0; if (startIdx1 == startIdx2) { CHECK(cubeCoor1.col(startIdx2) == cubeCoor2.col(startIdx2)) << "Both startIdx should have 0,0,0 cube coordinate"; } else { CHECK(cubeCoor1(0, startIdx2) != std::numeric_limits<int>::max()) << "we assume the center of transferDot2 has valid cubeCoord1"; cubeCoorDiff = cubeCoor1.col(startIdx2) - cubeCoor2.col(startIdx2); } LOG(INFO) << fmt::format("translation is {}, {}, {}", cubeCoorDiff(0, 0), cubeCoorDiff(1, 0), cubeCoorDiff(2, 0)); // calculate coordinate rotation int rotIdx = determineRotation(cubeCoor1, cubeCoor2, cubeCoorDiff); LOG(INFO) << fmt::format("rotation is left {} degree", rotIdx * 60); // Merge coordinate and update minX, maxX, minZ, maxZ for (int i = 0; i < cubeCoor.cols(); i++) { if (cubeCoor1(0, i) != std::numeric_limits<int>::max()) { // Add cube coordinate from valid cubeCoor1 cubeCoor.col(i) = cubeCoor1.col(i); if (poseIdx == 1) { // we only push back index i at first time it is met bfsProcessSeq_.push_back(i); } } else if ((cubeCoor2(0, i) != std::numeric_limits<int>::max())) { // Add cube coordinate from valid cubeCoor2 while cubeCoor1 is invalid cubeCoor.col(i) = doLeftRotate(cubeCoor2.col(i), rotIdx) + cubeCoorDiff; if (minX > cubeCoor(0, i)) minX = cubeCoor(0, i); if (minZ > cubeCoor(2, i)) minZ = cubeCoor(2, i); if (maxX < cubeCoor(0, i)) maxX = cubeCoor(0, i); if (maxZ < cubeCoor(2, i)) maxZ = cubeCoor(2, i); bfsProcessSeq_.push_back(i); } } LOG(INFO) << fmt::format("total processed points from 2 poses are {}", bfsProcessSeq_.size()); return cubeCoor; } void HexGridFitting::getStorageMapFromPoseSeq( const std::vector<Eigen::Matrix2Xd>& transferDotsGroup) { int minX = 0; int maxX = 0; int minZ = 0; int maxZ = 0; // get cube coordinate from first pose: base pose Eigen::Matrix2Xd transferDotsBase = transferDotsGroup.at(0); Eigen::Matrix3Xi cubeCoorBase; std::vector<int> bfsProcessSeqBase; int startIdxBase = getCubeCoordinate(transferDotsBase, minX, maxX, minZ, maxZ, cubeCoorBase, bfsProcessSeqBase); if (transferDotsGroup.size() == 1) { bfsProcessSeq_ = bfsProcessSeqBase; } // loop over transferred dot group: calculate and merge cube coordinate for (int poseIdx = 1; poseIdx < transferDotsGroup.size(); ++poseIdx) { Eigen::Matrix2Xd transferDotsNew = transferDotsGroup.at(poseIdx); CHECK(transferDotsBase.cols() == transferDotsNew.cols()) << "transferDots should have same size"; // get cube coordinate from transferred dot of new pose Eigen::Matrix3Xi cubeCoorNew; std::vector<int> bfsProcessSeqNew; int startIdxNew = getCubeCoordinate(transferDotsNew, minX, maxX, minZ, maxZ, cubeCoorNew, bfsProcessSeqNew); cubeCoorBase = mergeCubeCoordinate(cubeCoorBase, cubeCoorNew, startIdxBase, startIdxNew, minX, maxX, minZ, maxZ, poseIdx); } // build binary code binaryCode_ = Eigen::VectorXi::Constant(transferDotsBase.cols(), 1, 2); buildBinaryCode(cubeCoorBase, minX, maxX, minZ, maxZ); } void HexGridFitting::buildBinaryCode(const Eigen::Matrix3Xi& cubeCoor, int minX, int maxX, int minZ, int maxZ) { int storageMapRow = maxZ - minZ > maxX - minX ? maxZ - minZ + 1 : maxX - minX + 1; detectPattern_ = Eigen::MatrixXi::Constant( storageMapRow, storageMapRow, 2); // not detected pt in Pattern =2 indexMap_.setConstant(storageMapRow, storageMapRow, -1); // not detected pt in index map = -1 for (int i = 0; i < cubeCoor.cols(); ++i) { if (cubeCoor(0, i) != std::numeric_limits<int>::max()) { indexMap_(cubeCoor(2, i) - minZ, cubeCoor(0, i) - minX) = i; } } for (int i = 0; i < cubeCoor.cols(); ++i) { if (cubeCoor(0, i) != std::numeric_limits<int>::max()) { Eigen::Vector2i centerRQ = Eigen::Vector2i(cubeCoor(2, i), cubeCoor(0, i)); centerRQ(0) -= minZ; centerRQ(1) -= minX; if (ifTwoShot_) { binaryCode_(i) = dotLabels_(i); } else { binaryCode_(i) = getBinarycode(centerRQ, numNeighbourLayer_); } detectPattern_(cubeCoor(2, i) - minZ, cubeCoor(0, i) - minX) = binaryCode_(i); } } } bool HexGridFitting::neighboursIdxInArea(const Eigen::Matrix2Xd& dotMatrix, const Eigen::Vector2d& center, double searchRadius, Eigen::VectorXi& result) { bool flag = false; std::vector<std::pair<double, int>> distanceIndxPair; Eigen::VectorXd distance = ((dotMatrix.row(0) - Eigen::MatrixXd::Constant(1, dotMatrix.cols(), center(0))) .cwiseAbs2() + (dotMatrix.row(1) - Eigen::MatrixXd::Constant(1, dotMatrix.cols(), center(1))) .cwiseAbs2()) .cwiseSqrt(); for (int i = 0; i < dotMatrix.cols(); ++i) { if (distance(i) < searchRadius) { distanceIndxPair.push_back(std::make_pair(distance(i), i)); flag = true; } } if (flag) { result.resize(distanceIndxPair.size(), 1); sort(distanceIndxPair.begin(), distanceIndxPair.end()); for (size_t i = 0; i < distanceIndxPair.size(); ++i) { result(i) = distanceIndxPair[i].second; } } return flag; } int HexGridFitting::getBinarycode( const Eigen::Vector2i& centerRQ, int layer) { // the number of layers that the neighours are used to // deternmine the binary code of the center std::vector<double> colorNeighbour; for (int r = -layer; r <= layer; ++r) { for (int q = -layer; q <= layer; ++q) { if (r + q >= -layer && r + q <= layer && r + centerRQ.x() >= 0 && r + centerRQ.x() < indexMap_.rows() && q + centerRQ.y() >= 0 && q + centerRQ.y() < indexMap_.cols()) { if (indexMap_(r + centerRQ.x(), q + centerRQ.y()) != -1) colorNeighbour.push_back( intensity_(indexMap_(r + centerRQ.x(), q + centerRQ.y()))); } } // end c } // end r if (colorNeighbour.size() > 2) { // more than two neighbours std::sort(colorNeighbour.begin(), colorNeighbour.end()); double colorMedian = (*colorNeighbour.begin() + colorNeighbour.back()) / 2.0; return intensity_(indexMap_(centerRQ.x(), centerRQ.y())) > colorMedian ? 1 : 0; } else { return 2; } } Eigen::Matrix2Xd HexGridFitting::getDirections(int startIndx) { Eigen::VectorXi neighbourIndx; neighboursIdxInArea(transferDots_, transferDots_.col(startIndx), spacing_ + perPointSearchRadius_, neighbourIndx); Eigen::Matrix2Xd result; result.resize(2, IsometricGridDot::kNumNeighbours); result.col(0) = transferDots_.col(neighbourIndx(1)) - transferDots_.col(startIndx); // neighbourIndx(0) is the point itself const double cos60 = 0.50; const double sin60 = sqrt(3.0) / 2.0; for (int i = 0; i < 5; ++i) { result(0, i + 1) = result(0, i) * cos60 + result(1, i) * sin60; result(1, i + 1) = result(1, i) * cos60 - result(0, i) * sin60; } return result; } int HexGridFitting::findOffset( const std::array<Eigen::MatrixXi, 6>& patternReference, const Eigen::MatrixXi& pattern, Eigen::Vector2i& bestOffset, int& bestIndx) const { const int R = pattern.rows(); const int C = pattern.cols(); const int referenceR = patternReference[0].rows(); const int referenceC = patternReference[0].cols(); int bestMatch = 0; for (int patternIndx = 0; patternIndx < 6; ++patternIndx) { // For all offsets for (int offsetR = -R; offsetR < R; ++offsetR) { for (int offsetC = -C; offsetC < C; ++offsetC) { int numberMatch = 0; for (int r = 0; r < R; ++r) { for (int c = 0; c < C; ++c) { if (r + offsetR >= 0 && r + offsetR < referenceR && c + offsetC >= 0 && c + offsetC < referenceC) { if (patternReference[patternIndx](r + offsetR, c + offsetC) == pattern(r, c) && pattern(r, c) != 2) numberMatch += 1; } // end if } // end c } // end r if (numberMatch > bestMatch) { bestMatch = numberMatch; bestOffset << offsetR, offsetC; bestIndx = patternIndx; } } // end offsetC } // end offsetR } if (bfsProcessSeq_.size() == 0 || (double(bestMatch) / double(bfsProcessSeq_.size())) < 0.7) { LOG(INFO) << fmt::format( "Pattern matching failed, only {} points can be matched from {} " "processed points.", bestMatch, bfsProcessSeq_.size()); } return bestMatch; } } // namespace surreal_opensource