IsometricPatternMatcher/PatternMatcherIsometric.cpp (240 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/DotExtractor.h>
#include <IsometricPatternMatcher/HexGridCostFunction.h>
#include <IsometricPatternMatcher/PatternMatcherIsometric.h>
#include <glog/logging.h>
namespace surreal_opensource {
Eigen::Matrix2Xd PatternMatcherIsometric::DotDetection(
const Image<uint8_t>& image) const {
DotExtractor32 extractor;
ManagedImage<DotTypeFloat> dots;
uint32_t numDots;
Eigen::AlignedBox2i roi(
Eigen::Vector2i(10, 10),
Eigen::Vector2i(image.Width() - 10, image.Height() - 10));
extractor.setHessThresh(opts_.hessThresh);
extractor.setBlurKernelRadius(opts_.blurKernelRadius);
extractor.loadIrImage(image);
extractor.extractDots(roi);
extractor.copyDetectedDots(dots, numDots);
Eigen::Matrix2Xd detectedDots;
detectedDots.resize(2, numDots);
for (int i = 0; i < numDots; ++i) {
detectedDots(0, i) = dots[i](0);
detectedDots(1, i) = dots[i](1);
}
return detectedDots;
}
PatternMatcherIsometric::PatternMatcherIsometric(
std::vector<std::shared_ptr<const IsometricGridDot>>& targets,
const IsometricOpts& opts)
: isometricGrids_(targets), opts_(opts) {
// TODO: HACK: currently only support one pattern
CHECK_EQ(isometricGrids_.size(), 1) << "Currently only supporting 1 target!";
}
PatternMatcherIsometric::PatternMatcherIsometric(
const std::string& patternFiles, const IsometricOpts& opts)
: opts_(opts) {
CHECK_GT(patternFiles.size(), 0) << fmt::format(
"Matcher needs at least one target, {} specified", patternFiles.size());
isometricGrids_.push_back(std::make_shared<IsometricGridDot>(patternFiles));
}
std::vector<std::shared_ptr<const IsometricGridDot>>
PatternMatcherIsometric::GetPatterns() const {
return isometricGrids_;
}
Eigen::VectorXd PatternMatcherIsometric::GetIntensity(
const Eigen::Matrix2Xd& detectedDots, const Image<uint8_t>& imageU8) const {
Eigen::VectorXd intensity;
intensity.resize(detectedDots.cols());
for (int i = 0; i < detectedDots.cols(); ++i) {
double max = 0;
for (int r = -opts_.intensityWindowSize / 2;
r <= opts_.intensityWindowSize / 2; ++r) {
for (int c = -opts_.intensityWindowSize / 2;
c <= opts_.intensityWindowSize / 2; ++c) {
const auto* r0 = imageU8.RowPtr((int)detectedDots(1, i) + r) +
(int)detectedDots(0, i) + c;
if (*r0 > max) {
max = *r0;
}
}
}
intensity(i) = max;
}
return intensity;
}
void PatternMatcherIsometric::StoreIntoMap(const HexGridFitting& grid,
const Eigen::Matrix2Xd& detectedDots,
PatternMatcherIsometric::Result& res,
int& rotationIndx,
Eigen::Vector2i& offset) const {
Eigen::VectorXi binaryCode = grid.binaryCode();
res.debug.detected_labels.reserve(binaryCode.size());
for (int i = 0; i < binaryCode.size(); ++i) {
res.debug.detected_labels.push_back(binaryCode(i));
}
int numberMatch = grid.findOffset(isometricGrids_[0]->GetBinaryPatternGroup(),
grid.detectPattern(), offset, rotationIndx);
fmt::print("{} points can be matched from total {} detected points \n",
numberMatch, detectedDots.cols());
}
void PatternMatcherIsometric::generateResult(
const HexGridFitting& grid, const Eigen::Matrix2Xd& detectedDots,
int rotationIndx, const Eigen::Vector2i& offset,
PatternMatcherIsometric::Result& res) const {
Eigen::MatrixXi referenceIndxMap;
size_t storageMapRows = isometricGrids_[0]->storageMapRows();
referenceIndxMap.resize(storageMapRows, storageMapRows);
for (int r = 0; r < storageMapRows; ++r) {
for (int c = 0; c < storageMapRows; ++c)
referenceIndxMap(r, c) = r * storageMapRows + c;
}
fmt::print("rotationIndx: {}, offset: ({}, {}).\n", rotationIndx, offset.x(),
offset.y());
for (int i = 0; i < rotationIndx; ++i) {
referenceIndxMap = isometricGrids_[0]->Rotate60Right(referenceIndxMap);
}
Eigen::Matrix<double, 2, Eigen::Dynamic> correspondences;
correspondences.resize(2, isometricGrids_[0]->GetPattern().cols());
correspondences.setConstant(std::numeric_limits<double>::quiet_NaN());
Eigen::MatrixXi detectedIndxMap = grid.indexMap();
int numVisualizedDot = 0;
for (int r = 0; r < detectedIndxMap.rows(); ++r) {
for (int c = 0; c < detectedIndxMap.cols(); ++c) {
if (r + offset.x() >= 0 && r + offset.x() < storageMapRows &&
c + offset.y() >= 0 && c + offset.y() < storageMapRows) {
const int id = referenceIndxMap(r + offset.x(), c + offset.y());
if (detectedIndxMap(r, c) >= 0) {
CHECK(id < correspondences.cols());
if (isometricGrids_[0]->GetBinaryPatternGroup()[rotationIndx](
r + offset.x(), c + offset.y()) ==
grid.detectPattern()(r, c) &&
grid.detectPattern()(r, c) != 2) {
correspondences.col(id) = detectedDots.col(detectedIndxMap(r, c));
numVisualizedDot += 1;
}
}
} // end if
} // end c
} // end r
res.detections.emplace_back(0, correspondences);
std::cout << "Visualize matched point number is: " << numVisualizedDot
<< std::endl;
}
PatternMatcherIsometric::Result PatternMatcherIsometric::Match(
const Image<uint8_t>& imageU8) const {
PatternMatcherIsometric::Result res;
// detect dots
Eigen::Matrix2Xd detectedDots = DotDetection(imageU8);
for (int i = 0; i < detectedDots.cols(); ++i) {
res.debug.feature_pts.push_back(detectedDots.col(i));
}
// get intensity of the extracted dots
Eigen::VectorXd intensity = GetIntensity(detectedDots, imageU8);
// detect pattern from the extracted dot
Eigen::Vector2d centerXY;
centerXY << imageU8.w / 2, imageU8.h / 2;
double spacing = 1.0;
int numNeighboursForPoseEst = 3;
int numberSegX = 3;
int numberSegY = 3; // devide the dots into numberSegX*numberSegY patches
HexGridFitting grid(detectedDots, centerXY, opts_.focalLength, intensity,
opts_.ifDistort, false, false, spacing,
numNeighboursForPoseEst, numberSegX, numberSegY);
// store detected pattern into a storagemap
Eigen::Vector2i offset;
int rotationIndx;
StoreIntoMap(grid, detectedDots, res, rotationIndx, offset);
// Generate result
generateResult(grid, detectedDots, rotationIndx, offset, res);
return res;
}
PatternMatcherIsometric::Result PatternMatcherIsometric::MatchImagePairs(
const Image<uint8_t>& imageCode1U8,
const Image<uint8_t>& imageCode0U8) const {
CHECK(((imageCode1U8.w == imageCode0U8.w) &&
(imageCode1U8.h == imageCode0U8.h)))
<< "imageCode1 and imageCode0 should have same size";
// detect dots
PatternMatcherIsometric::Result res;
Eigen::Matrix2Xd detectedCode1Dots = DotDetection(imageCode1U8);
Eigen::Matrix2Xd detectedCode0Dots = DotDetection(imageCode0U8);
Eigen::Matrix2Xd detectedDots(
detectedCode1Dots.rows(),
detectedCode1Dots.cols() + detectedCode0Dots.cols());
detectedDots << detectedCode1Dots, detectedCode0Dots;
for (int i = 0; i < detectedCode1Dots.cols(); ++i) {
res.debug.feature_pts.push_back(detectedCode1Dots.col(i));
}
for (int i = 0; i < detectedCode0Dots.cols(); ++i) {
res.debug.feature_pts.push_back(detectedCode0Dots.col(i));
}
// get intensity of the extracted dots
Eigen::VectorXd intensityCode1 =
GetIntensity(detectedCode1Dots, imageCode1U8);
Eigen::VectorXd intensityCode0 =
GetIntensity(detectedCode0Dots, imageCode0U8);
CHECK(intensityCode1.rows() == detectedCode1Dots.cols())
<< "intensity and dot position length not consistent";
CHECK(intensityCode1.rows() == detectedCode1Dots.cols())
<< "intensity and dot position length not consistent";
// build dot code labels
Eigen::VectorXi dotLabels(intensityCode1.rows() + intensityCode0.rows());
Eigen::VectorXi labelOne(intensityCode1.rows());
labelOne.setOnes();
Eigen::VectorXi labelZero(intensityCode0.rows());
labelZero.setZero();
dotLabels << labelOne, labelZero;
// detect pattern from the extracted dots
Eigen::Vector2d centerXY;
centerXY << imageCode1U8.w / 2, imageCode1U8.h / 2;
double spacing = 1.0;
int numNeighboursForPoseEst = 3;
HexGridFitting grid(
detectedDots, centerXY, opts_.focalLength, dotLabels, opts_.ifDistort,
true, opts_.ifPoseMerge, opts_.goodPoseInlierRatio, spacing,
numNeighboursForPoseEst, opts_.numberSegX, opts_.numberSegY);
// store detected pattern into a storagemap
Eigen::Vector2i offset;
int rotationIndx;
StoreIntoMap(grid, detectedDots, res, rotationIndx, offset);
// Generate result
generateResult(grid, detectedDots, rotationIndx, offset, res);
return res;
}
PatternMatcherIsometric::Result
PatternMatcherIsometric::MatchImagePairsWithConics(
const Image<uint8_t>& imageCode1U8, const Image<uint8_t>& imageCode0U8,
const Eigen::Matrix2Xd& detectedCode1Dots,
const Eigen::Matrix2Xd& detectedCode0Dots) const {
CHECK(((imageCode1U8.w == imageCode0U8.w) &&
(imageCode1U8.h == imageCode0U8.h)))
<< "imageCode1 and imageCode0 should have same size";
PatternMatcherIsometric::Result res;
// process detected dots
Eigen::Matrix2Xd detectedDots(
detectedCode1Dots.rows(),
detectedCode1Dots.cols() + detectedCode0Dots.cols());
detectedDots << detectedCode1Dots, detectedCode0Dots;
for (int i = 0; i < detectedCode1Dots.cols(); ++i) {
res.debug.feature_pts.push_back(detectedCode1Dots.col(i));
}
for (int i = 0; i < detectedCode0Dots.cols(); ++i) {
res.debug.feature_pts.push_back(detectedCode0Dots.col(i));
}
// get intensity of the extracted dots
Eigen::VectorXd intensityCode1 =
GetIntensity(detectedCode1Dots, imageCode1U8);
Eigen::VectorXd intensityCode0 =
GetIntensity(detectedCode0Dots, imageCode0U8);
CHECK(intensityCode1.rows() == detectedCode1Dots.cols())
<< "intensity and dot position length not consistent";
CHECK(intensityCode1.rows() == detectedCode1Dots.cols())
<< "intensity and dot position length not consistent";
// build dot code labels
Eigen::VectorXi dotLabels(intensityCode1.rows() + intensityCode0.rows());
Eigen::VectorXi labelOne(intensityCode1.rows());
labelOne.setOnes();
Eigen::VectorXi labelZero(intensityCode0.rows());
labelZero.setZero();
dotLabels << labelOne, labelZero;
// detect pattern from the extracted dots
Eigen::Vector2d centerXY;
centerXY << imageCode1U8.w / 2, imageCode1U8.h / 2;
double spacing = 1.0;
int numNeighboursForPoseEst = 3;
HexGridFitting grid(
detectedDots, centerXY, opts_.focalLength, dotLabels, opts_.ifDistort,
true, opts_.ifPoseMerge, opts_.goodPoseInlierRatio, spacing,
numNeighboursForPoseEst, opts_.numberSegX, opts_.numberSegY);
// store detected pattern into a storagemap
Eigen::Vector2i offset;
int rotationIndx;
StoreIntoMap(grid, detectedDots, res, rotationIndx, offset);
// Generate result
generateResult(grid, detectedDots, rotationIndx, offset, res);
return res;
}
} // namespace surreal_opensource