IsometricPatternMatcher/DotExtractor.h (243 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. #pragma once #include <IsometricPatternMatcher/Image.h> #include <glog/logging.h> #include <Eigen/Core> #include <atomic> #include <memory> #include <vector> namespace surreal_opensource { typedef Eigen::Matrix<float, 2, 1> DotTypeFloat; typedef Eigen::Matrix<int, 2, 1> DotTypeInt; const size_t MAX_RAD_BLUR_SMEM = 8; enum RejectedDotStatus { DETERMINANT, HESSIAN, MAXDELTA, MAXNUM }; template <typename INDEX_TYPE> class DotExtractorWithIndexType { public: typedef std::shared_ptr<DotExtractorWithIndexType<INDEX_TYPE>> Ptr; DotExtractorWithIndexType( INDEX_TYPE numDots = std::numeric_limits<INDEX_TYPE>::max()) : rejectedDots_(numDots), rejectedStatus_(numDots), numRejectedDots_(0), maxNumDots_(numDots), name_("DotExtractor"), blurSigma_(1.161), maxDelta_(0.807f), clampDelta_(0.f), numDots_(0), hessThresh_(0.01), width_(0), height_(0), dots_(maxNumDots_), kernel_(MAX_RAD_BLUR_SMEM, 1) { blurKernel(blurKernelRadius_, blurSigma_); } ~DotExtractorWithIndexType() {} DotExtractorWithIndexType<INDEX_TYPE>& operator=( const DotExtractorWithIndexType<INDEX_TYPE>& c) { dots_.CopyFrom(c.dots_); blurredImage_.CopyFrom(c.blurredImage_); rejectedDots_ = c.rejectedDots_; rejectedStatus_.CopyFrom(c.rejectedStatus_); numRejectedDots_ = c.numRejectedDots_; numDots_ = c.numDots_; maxNumDots_ = c.maxNumDots_; maxDelta_ = c.maxDelta_; return *this; } template <typename Tout, typename Tin> void blur(surreal_opensource::ManagedImage<Tout>& out, const surreal_opensource::ManagedImage<Tin>& in) { CHECK(out.w == in.w && out.h == in.h); surreal_opensource::ManagedImage<Tout> med(in.w, in.h); for (int y = 0; y < in.h; y++) { for (int x = 0; x < in.w; ++x) { float blurSum = kernel_[0] * in(x, y); for (int r = 1; r < this->blurKernelRadius_; ++r) { float w = kernel_[r]; if (x - r >= 0) { blurSum += w * in(x - r, y); } if (x + r < in.w) { blurSum += w * in(x + r, y); } } med(x, y) = blurSum; } } for (int x = 0; x < in.w; x++) { for (int y = 0; y < in.h; ++y) { float blurSum = kernel_[0] * med(x, y); for (int r = 1; r < blurKernelRadius_; ++r) { const float w = kernel_[r]; if (y - r >= 0) { blurSum += w * med(x, y - r); } if (y + r < in.h) { blurSum += w * med(x, y + r); } } out(x, y) = blurSum; } } } void blurKernel(size_t& radius, const float sigma) { radius = std::min((size_t)ceil(3 * sigma), MAX_RAD_BLUR_SMEM - 1); float sum = 0.0f; const float a = 1.0f / (sigma * std::sqrt(2.0f * M_PI)); for (size_t i = 0; i < MAX_RAD_BLUR_SMEM; i++) { const float b = (float)i * (float)i; const float c = -b / (2.0f * (sigma * sigma)); kernel_[i] = a * exp(c); // the kernel is symmetric, so we only store one side of it // as a result, coeffs count double... sum += (i == 0 ? 1.0 : 2.0) * kernel_[i]; } for (size_t i = 0; i < MAX_RAD_BLUR_SMEM; i++) { kernel_[i] /= sum; } } void loadIrImage(const surreal_opensource::Image<uint8_t>& input_image) { const Eigen::Vector2i imgDim = input_image.Dim(); if (width_ != (size_t)imgDim(0) || height_ != (size_t)imgDim(1)) { width_ = imgDim(0); height_ = imgDim(1); blurredImage_.Reinitialise(width_, height_); rawImage8_.Reinitialise(width_, height_); } // apply blur rawImage8_.CopyFrom(input_image); blur(blurredImage_, rawImage8_); } void detectDots(const Eigen::AlignedBox2i roi) { std::atomic<INDEX_TYPE> atomicIndex(0); std::atomic<INDEX_TYPE> rejectedAtomicIndex(0); for (int y = roi.min().y(); y < roi.max().y(); ++y) { for (int x = roi.min().x(); x < roi.max().x(); ++x) { const auto* rm = blurredImage_.RowPtr(y - 1) + x; const auto* r0 = blurredImage_.RowPtr(y) + x; const auto* rp = blurredImage_.RowPtr(y + 1) + x; const float ixy = *r0; /* Ignore non-maximal pixels. Calculate and(ixy >= X) where X is all 8 other pixels surrounding the center pixel. */ bool isMaximalPixel = ixy > rm[-1] && ixy > rm[0] && ixy > rm[1] && ixy > r0[-1] && ixy > r0[1] && ixy > rp[-1] && ixy > rp[0] && ixy > rp[1]; if (isMaximalPixel) { const float dyy = (*rp + *rm - 2.0f * ixy); const float dxx = (r0[1] + r0[-1] - 2.0f * ixy); const float dxy = (rm[-1] - rm[1] - rp[-1] + rp[1]) / 4.0f; const float det = (dxx * dyy - dxy * dxy); if (det != 0.0f) { const float dx = (r0[1] - r0[-1]) / 2.0f; const float dy = (*rp - *rm) / 2.0f; const float invdet = 1.0f / det; // x,y update (newton step) Eigen::Vector2f fp((dxy * dy - dyy * dx) * invdet, (dxy * dx - dxx * dy) * invdet); if (fp.lpNorm<Eigen::Infinity>() < maxDelta_) { if (dxx * dxx + dyy * dyy > hessThresh_) { INDEX_TYPE memoryIndex = atomicIndex++; if (memoryIndex < maxNumDots_) { if (clampDelta_ > 0.f) { fp[0] = std::clamp(fp[0], -clampDelta_, clampDelta_); fp[1] = std::clamp(fp[1], -clampDelta_, clampDelta_); } dots_[memoryIndex] = Eigen::Matrix<float, 2, 1>(x + fp[0], y + fp[1]); } else { // maxdots INDEX_TYPE idx = rejectedAtomicIndex++; rejectedDots_[idx] = Eigen::Vector2f(x + fp[0], y + fp[1]); rejectedStatus_[idx] = RejectedDotStatus::MAXNUM; } } else { INDEX_TYPE idx = rejectedAtomicIndex++; rejectedDots_[idx] = Eigen::Vector2f(x + fp[0], y + fp[1]); rejectedStatus_[idx] = RejectedDotStatus::HESSIAN; } } else { INDEX_TYPE idx = rejectedAtomicIndex++; rejectedDots_[idx] = Eigen::Vector2f(x + fp[0], y + fp[1]); rejectedStatus_[idx] = RejectedDotStatus::MAXDELTA; } } else { INDEX_TYPE idx = rejectedAtomicIndex++; rejectedDots_[idx] = Eigen::Vector2f(x, y); rejectedStatus_[idx] = RejectedDotStatus::DETERMINANT; } } } } INDEX_TYPE ai = atomicIndex == 0 ? 0 : atomicIndex - 1; // to compensate for the last ++ numDots_ = std::clamp(ai, static_cast<INDEX_TYPE>(0), maxNumDots_); INDEX_TYPE rai = rejectedAtomicIndex == 0 ? 0 : rejectedAtomicIndex - 1; // to compensate for the last ++ numRejectedDots_ = std::clamp(rai, static_cast<INDEX_TYPE>(0), maxNumDots_); } void extractDots(const Eigen::AlignedBox2i roi) { detectDots(roi); } void extractDots() { Eigen::AlignedBox2i roi(Eigen::Vector2i(0, 0), Eigen::Vector2i(this->width_, this->height_)); detectDots(roi); } void copyDetectedDots(surreal_opensource::ManagedImage<DotTypeFloat>& dots, INDEX_TYPE& num_dots) { dots.CopyFrom(dots_); num_dots = numDots_; } surreal_opensource::ManagedImage<DotTypeFloat>& getDetectedDotsGPU() { return dots_; } INDEX_TYPE getNumDots() const { return numDots_; } DotExtractorWithIndexType<INDEX_TYPE>& setBlurSigma(float sigma) { if (sigma != blurSigma_) { blurKernel(blurKernelRadius_, sigma); } blurSigma_ = sigma; return *this; } float getBlurSigma() const { return blurSigma_; } DotExtractorWithIndexType<INDEX_TYPE>& setBlurKernelRadius(size_t radius) { if (radius != blurKernelRadius_) { blurKernel(radius, blurSigma_); } blurKernelRadius_ = radius; return *this; } size_t getBlurKernelRadius(size_t radius) { return blurKernelRadius_; } DotExtractorWithIndexType<INDEX_TYPE>& setHessThresh(float thresh) { hessThresh_ = thresh; return *this; } float getHessThresh() const { return hessThresh_; } DotExtractorWithIndexType<INDEX_TYPE>& setMaxDelta(float d) { maxDelta_ = d; return *this; } float getMaxDelta() const { return maxDelta_; } DotExtractorWithIndexType<INDEX_TYPE>& setClampDelta(float d) { clampDelta_ = d; return *this; } float getClampDelta() const { return clampDelta_; } surreal_opensource::ManagedImage<float> blurredImage_; std::vector<Eigen::Vector2f> rejectedDots_; surreal_opensource::ManagedImage<RejectedDotStatus> rejectedStatus_; INDEX_TYPE numRejectedDots_; private: INDEX_TYPE maxNumDots_; std::string name_; size_t blurKernelRadius_; float blurSigma_; // subpixel refinements larger than maxDelta_ are rejected float maxDelta_; // subpixel refinements larger than clampDelta_ are clamped to clampDelta_ float clampDelta_; INDEX_TYPE numDots_; float hessThresh_; INDEX_TYPE width_, height_; surreal_opensource::ManagedImage<unsigned char> rawImage8_; surreal_opensource::ManagedImage<DotTypeFloat> dots_; surreal_opensource::ManagedImage<int> globalNextFree_; surreal_opensource::ManagedImage<float> kernel_; }; using DotExtractor = DotExtractorWithIndexType<uint16_t>; using DotExtractor32 = DotExtractorWithIndexType<uint32_t>; using DotExtractor64 = DotExtractorWithIndexType<uint64_t>; } // namespace surreal_opensource