mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 15:53:03 +04:00
Merge branch 4.x
This commit is contained in:
@@ -383,31 +383,41 @@ static Mat _extractCellPixelRatio(InputArray _image, const vector<Point2f>& corn
|
||||
|
||||
|
||||
/**
|
||||
* @brief Return number of erroneous bits in border, i.e. bits for which pixel ratio > validBitIdThreshold.
|
||||
* @brief Return number of erroneous bits in border.
|
||||
*
|
||||
* Black border error if cellPixelRatio > validBitIdThreshold -> borderErrors
|
||||
* White border error if 1 - cellPixelRatio > validBitIdThreshold
|
||||
* <=> cellPixelRatio < 1 - validBitIdThreshold) -> invBorderErrors (inverted markers)
|
||||
*/
|
||||
static int _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold) {
|
||||
static void _getBorderErrors(const Mat &cellPixelRatio, int markerSize, int borderSize, float validBitIdThreshold,
|
||||
int &borderErrors, int &invBorderErrors) {
|
||||
|
||||
int sizeWithBorders = markerSize + 2 * borderSize;
|
||||
|
||||
CV_Assert(markerSize > 0 && cellPixelRatio.cols == sizeWithBorders && cellPixelRatio.rows == sizeWithBorders);
|
||||
|
||||
// Get border error. cellPixelRatio has the opposite color as the borders.
|
||||
int totalErrors = 0;
|
||||
const float invThreshold = 1.f - validBitIdThreshold;
|
||||
borderErrors = invBorderErrors = 0;
|
||||
const auto countCell = [&](float ratio) {
|
||||
if(ratio > validBitIdThreshold) borderErrors++;
|
||||
if(ratio < invThreshold) invBorderErrors++;
|
||||
};
|
||||
for(int y = 0; y < sizeWithBorders; y++) {
|
||||
const float* row = cellPixelRatio.ptr<float>(y);
|
||||
for(int k = 0; k < borderSize; k++) {
|
||||
// Left and right vertical sides
|
||||
if(cellPixelRatio.ptr<float>(y)[k] > validBitIdThreshold) totalErrors++;
|
||||
if(cellPixelRatio.ptr<float>(y)[sizeWithBorders - 1 - k] > validBitIdThreshold) totalErrors++;
|
||||
countCell(row[k]);
|
||||
countCell(row[sizeWithBorders - 1 - k]);
|
||||
}
|
||||
}
|
||||
for(int x = borderSize; x < sizeWithBorders - borderSize; x++) {
|
||||
for(int k = 0; k < borderSize; k++) {
|
||||
// Top and bottom horizontal sides
|
||||
if(cellPixelRatio.ptr<float>(k)[x] > validBitIdThreshold) totalErrors++;
|
||||
if(cellPixelRatio.ptr<float>(sizeWithBorders - 1 - k)[x] > validBitIdThreshold) totalErrors++;
|
||||
countCell(cellPixelRatio.ptr<float>(k)[x]);
|
||||
countCell(cellPixelRatio.ptr<float>(sizeWithBorders - 1 - k)[x]);
|
||||
}
|
||||
}
|
||||
return totalErrors;
|
||||
}
|
||||
|
||||
|
||||
@@ -456,7 +466,6 @@ static float _getMarkerConfidence(const Mat& groundTruthbits, const Mat &cellPix
|
||||
return std::max(0.f, std::min(1.f, normalizedMarkerConfidence));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Tries to identify one candidate given the dictionary
|
||||
* @return candidate typ. zero if the candidate is not valid,
|
||||
@@ -486,27 +495,24 @@ static uint8_t _identifyOneCandidate(const Dictionary& dictionary, const Mat& _i
|
||||
// analyze border bits
|
||||
int maximumErrorsInBorder =
|
||||
int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate);
|
||||
int borderErrors =
|
||||
_getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold);
|
||||
int borderErrors = 0, invBorderErrors = 0;
|
||||
_getBorderErrors(cellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold,
|
||||
borderErrors, invBorderErrors);
|
||||
|
||||
// check if it is a white marker
|
||||
if(params.detectInvertedMarker){
|
||||
Mat invCellPixelRatio = 1.f - cellPixelRatio;
|
||||
int invBError = _getBorderErrors(invCellPixelRatio, dictionary.markerSize, params.markerBorderBits, params.validBitIdThreshold);
|
||||
// white marker
|
||||
if(invBError<borderErrors){
|
||||
borderErrors = invBError;
|
||||
invCellPixelRatio.copyTo(cellPixelRatio);
|
||||
typ=2;
|
||||
}
|
||||
if(params.detectInvertedMarker && invBorderErrors < borderErrors) {
|
||||
// white marker: invert the observed ratios in place and continue as a normal marker
|
||||
borderErrors = invBorderErrors;
|
||||
subtract(Scalar::all(1), cellPixelRatio, cellPixelRatio);
|
||||
typ=2;
|
||||
}
|
||||
if(borderErrors > maximumErrorsInBorder) return 0; // border is wrong
|
||||
|
||||
// take only inner bits
|
||||
Mat onlyCellPixelRatio =
|
||||
cellPixelRatio.rowRange(params.markerBorderBits,
|
||||
cellPixelRatio.rows - params.markerBorderBits)
|
||||
.colRange(params.markerBorderBits, cellPixelRatio.cols - params.markerBorderBits);
|
||||
Mat onlyCellPixelRatio = cellPixelRatio(
|
||||
Rect(params.markerBorderBits, params.markerBorderBits,
|
||||
cellPixelRatio.cols - 2 * params.markerBorderBits,
|
||||
cellPixelRatio.rows - 2 * params.markerBorderBits));
|
||||
|
||||
// try to identify the marker
|
||||
if(!dictionary.identify(onlyCellPixelRatio, idx, rotation, params.errorCorrectionRate, params.validBitIdThreshold))
|
||||
@@ -1398,15 +1404,13 @@ void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board
|
||||
detectorParams.perspectiveRemovePixelPerCell,
|
||||
detectorParams.perspectiveRemoveIgnoredMarginPerCell, detectorParams.minOtsuStdDev);
|
||||
|
||||
Mat bits;
|
||||
cellPixelRatio.convertTo(bits, CV_8UC1);
|
||||
Mat onlyCellPixelRatio = cellPixelRatio(
|
||||
Rect(detectorParams.markerBorderBits, detectorParams.markerBorderBits,
|
||||
cellPixelRatio.cols - 2 * detectorParams.markerBorderBits,
|
||||
cellPixelRatio.rows - 2 * detectorParams.markerBorderBits));
|
||||
|
||||
Mat onlyBits =
|
||||
bits.rowRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits)
|
||||
.colRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits);
|
||||
|
||||
codeDistance =
|
||||
dictionary.getDistanceToId(onlyBits, undetectedMarkersIds[i], false);
|
||||
codeDistance = dictionary.getDistanceToId(onlyCellPixelRatio, undetectedMarkersIds[i],
|
||||
false, detectorParams.validBitIdThreshold);
|
||||
}
|
||||
|
||||
// if everythin is ok, assign values to current best match
|
||||
|
||||
@@ -15,6 +15,86 @@ namespace aruco {
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct CellBitMasks {
|
||||
CellBitMasks(const Mat &onlyCellPixelRatio, int markerSize, float validBitIdThreshold)
|
||||
: s((markerSize * markerSize + 8 - 1) / 8),
|
||||
totalCells(markerSize * markerSize),
|
||||
temp(4 * s),
|
||||
not0(temp.data()), not1(not0 + s), notXor(not1 + s), temp0(temp.data() + 3 * s) {
|
||||
uint8_t* not0Writable = temp.data();
|
||||
uint8_t* not1Writable = not0Writable + s;
|
||||
uint8_t* notXorWritable = not1Writable + s;
|
||||
|
||||
// Fill bit masks of cells that are not black (not0) and not white (not1).
|
||||
unsigned char not0Byte = 0, not1Byte = 0;
|
||||
int currentByte = 0, currentBit = 0;
|
||||
for(int j = 0; j < markerSize; j++) {
|
||||
const float* cellPixelRatioRow = onlyCellPixelRatio.ptr<float>(j);
|
||||
for(int i = 0; i < markerSize; i++) {
|
||||
not0Byte <<= 1; not1Byte <<= 1;
|
||||
if(cellPixelRatioRow[i] > validBitIdThreshold) not0Byte |= 1;
|
||||
if(cellPixelRatioRow[i] < 1 - validBitIdThreshold) not1Byte |= 1;
|
||||
++currentBit;
|
||||
if(currentBit == 8) {
|
||||
not0Writable[currentByte] = not0Byte;
|
||||
not1Writable[currentByte] = not1Byte;
|
||||
not0Byte = not1Byte = 0;
|
||||
++currentByte;
|
||||
currentBit = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(currentBit != 0) {
|
||||
not0Writable[currentByte] = not0Byte;
|
||||
not1Writable[currentByte] = not1Byte;
|
||||
}
|
||||
|
||||
// Computing: notXor = not0 ^ not1
|
||||
hal::xor8u(not0, s, not1, s, notXorWritable, s, s, 1, nullptr);
|
||||
}
|
||||
|
||||
CellBitMasks(const CellBitMasks&) = delete;
|
||||
CellBitMasks& operator=(const CellBitMasks&) = delete;
|
||||
|
||||
// Smallest Hamming distance between these cell masks and dictionary marker `id`,
|
||||
// searching the tested rotations; `rotation` returns the best one.
|
||||
// Mutates the internal buffer (temp0).
|
||||
int hammingDistanceToId(const Mat& bytesList, int id, bool allRotations, int& rotation) {
|
||||
CV_Assert(id >= 0 && id < bytesList.rows);
|
||||
|
||||
const unsigned int nRotations = allRotations ? 4u : 1u;
|
||||
int currentMinDistance = totalCells + 1;
|
||||
rotation = -1;
|
||||
|
||||
const uchar* bytesRot = bytesList.ptr(id);
|
||||
for(unsigned int r = 0; r < nRotations; r++, bytesRot += s) {
|
||||
// Error if (marker is 0 and input is not 0) or (marker is 1 and input is not 1)
|
||||
// i.e.: (!bytesRot && not0) || (bytesRot && not1)
|
||||
// This is equivalent to: not0 ^ ((not0 ^ not1) & bytesRot)
|
||||
// Computing: temp0 = (not0 ^ not1) & bytesRot
|
||||
hal::and8u(notXor, s, bytesRot, s, temp0, s, s, 1, nullptr);
|
||||
// Computing the final result (xor is performed internally).
|
||||
int currentHamming = cv::hal::normHamming(not0, temp0, s);
|
||||
|
||||
if(currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
rotation = static_cast<int>(r);
|
||||
// Break for perfect distance.
|
||||
if(currentMinDistance == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
return currentMinDistance;
|
||||
}
|
||||
|
||||
const int s; // bytes per rotation
|
||||
const int totalCells;
|
||||
std::vector<uint8_t> temp;
|
||||
const uint8_t *not0, *not1, *notXor;
|
||||
uint8_t *temp0; // internal scratch workspace
|
||||
};
|
||||
|
||||
|
||||
Dictionary::Dictionary(): markerSize(0), maxCorrectionBits(0) {}
|
||||
|
||||
|
||||
@@ -46,6 +126,7 @@ bool Dictionary::readDictionary(const cv::FileNode& fn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Dictionary::writeDictionary(FileStorage& fs, const String &name)
|
||||
{
|
||||
CV_Assert(fs.isOpened());
|
||||
@@ -75,6 +156,9 @@ void Dictionary::writeDictionary(FileStorage& fs, const String &name)
|
||||
|
||||
bool Dictionary::identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate, float validBitIdThreshold) const {
|
||||
CV_Assert(onlyCellPixelRatio.rows == markerSize && onlyCellPixelRatio.cols == markerSize);
|
||||
CV_Assert(onlyCellPixelRatio.type() == CV_32FC1);
|
||||
|
||||
CellBitMasks cellBitMasks(onlyCellPixelRatio, markerSize, validBitIdThreshold);
|
||||
|
||||
int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate);
|
||||
|
||||
@@ -82,29 +166,8 @@ bool Dictionary::identify(const Mat &onlyCellPixelRatio, CV_OUT int &idx, CV_OUT
|
||||
|
||||
// search closest marker in dict
|
||||
for(int m = 0; m < bytesList.rows; m++) {
|
||||
int currentMinDistance = markerSize * markerSize + 1;
|
||||
int currentRotation = -1;
|
||||
for(int r = 0; r < 4; r++) {
|
||||
|
||||
Mat bitsRot = getBitsFromByteList(bytesList.rowRange(m, m + 1), markerSize, r);
|
||||
bitsRot.convertTo(bitsRot, CV_32F);
|
||||
|
||||
// Loop over all bits dictBitsList [m, markerSize * markerSize, 4]; onlyCellPixelRatio [markerSize, markerSize]
|
||||
int currentHamming = 0;
|
||||
for(int i = 0; i < markerSize; i++) {
|
||||
for(int j = 0; j < markerSize; j++) {
|
||||
// If detected bit is too far from the ground truth, consider it false.
|
||||
if(fabs(onlyCellPixelRatio.at<float>(i, j) - static_cast<float>(bitsRot.at<float>(i, j))) > validBitIdThreshold){
|
||||
currentHamming++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentHamming < currentMinDistance) {
|
||||
currentMinDistance = currentHamming;
|
||||
currentRotation = r;
|
||||
}
|
||||
}
|
||||
int currentMinDistance = cellBitMasks.hammingDistanceToId(bytesList, m, true, currentRotation);
|
||||
|
||||
// if maxCorrection is fulfilled, return this one
|
||||
if(currentMinDistance <= maxCorrectionRecalculed) {
|
||||
@@ -122,9 +185,8 @@ bool Dictionary::identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rota
|
||||
CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
|
||||
|
||||
Mat candidateBitRatio;
|
||||
onlyBits.convertTo(candidateBitRatio, CV_32F);
|
||||
const float validBitIdThreshold = DEFAULT_VALID_BIT_ID_THRESHOLD;
|
||||
return identify(candidateBitRatio, idx, rotation, maxCorrectionRate, validBitIdThreshold);
|
||||
Mat(onlyBits > 0).convertTo(candidateBitRatio, CV_32F, 1.0 / 255.0);
|
||||
return identify(candidateBitRatio, idx, rotation, maxCorrectionRate, DEFAULT_VALID_BIT_ID_THRESHOLD);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +213,19 @@ int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) cons
|
||||
}
|
||||
|
||||
|
||||
int Dictionary::getDistanceToId(InputArray onlyCellPixelRatio, int id, bool allRotations, float validBitIdThreshold) const {
|
||||
|
||||
Mat onlyCellPixelRatioMat = onlyCellPixelRatio.getMat();
|
||||
CV_Assert(onlyCellPixelRatioMat.rows == markerSize && onlyCellPixelRatioMat.cols == markerSize);
|
||||
CV_Assert(onlyCellPixelRatioMat.type() == CV_32FC1);
|
||||
CV_Assert(id >= 0 && id < bytesList.rows);
|
||||
|
||||
int rotation = -1;
|
||||
CellBitMasks cellBitMasks(onlyCellPixelRatioMat, markerSize, validBitIdThreshold);
|
||||
return cellBitMasks.hammingDistanceToId(bytesList, id, allRotations, rotation);
|
||||
}
|
||||
|
||||
|
||||
void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits) const {
|
||||
CV_Assert(sidePixels >= (markerSize + 2*borderBits));
|
||||
CV_Assert(id < bytesList.rows);
|
||||
@@ -379,6 +454,7 @@ static Mat _generateRandomMarker(int markerSize, RNG &rng) {
|
||||
return marker;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Calculate selfDistance of the codification of a marker Mat. Self distance is the Hamming
|
||||
* distance of the marker to itself in the other rotations.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "opencv2/flann.hpp"
|
||||
#include "opencv2/geometry.hpp"
|
||||
#include "chessboard.hpp"
|
||||
#include "math.h"
|
||||
#include <math.h>
|
||||
|
||||
//#define CV_DETECTORS_CHESSBOARD_DEBUG
|
||||
#ifdef CV_DETECTORS_CHESSBOARD_DEBUG
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "precomp.hpp"
|
||||
#include "circlesgrid.hpp"
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
|
||||
// Requires CMake flag: DEBUG_opencv_calib=ON
|
||||
//#define DEBUG_CIRCLES
|
||||
@@ -574,8 +575,6 @@ CirclesGridFinder::Segment::Segment(cv::Point2f _s, cv::Point2f _e) :
|
||||
{
|
||||
}
|
||||
|
||||
void computeShortestPath(Mat &predecessorMatrix, int v1, int v2, std::vector<int> &path);
|
||||
void computePredecessorMatrix(const Mat &dm, int verticesCount, Mat &predecessorMatrix);
|
||||
|
||||
CirclesGridFinderParameters::CirclesGridFinderParameters()
|
||||
{
|
||||
@@ -1209,79 +1208,91 @@ void CirclesGridFinder::computeRNG(Graph &rng, std::vector<cv::Point2f> &vectors
|
||||
rng = Graph(keypoints.size());
|
||||
vectors.clear();
|
||||
|
||||
//TODO: use more fast algorithm instead of naive N^3
|
||||
for (size_t i = 0; i < keypoints.size(); i++)
|
||||
{
|
||||
for (size_t j = 0; j < keypoints.size(); j++)
|
||||
{
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
Point2f vec = keypoints[i] - keypoints[j];
|
||||
double dist = norm(vec);
|
||||
|
||||
bool isNeighbors = true;
|
||||
for (size_t k = 0; k < keypoints.size(); k++)
|
||||
{
|
||||
if (k == i || k == j)
|
||||
continue;
|
||||
|
||||
double dist1 = norm(keypoints[i] - keypoints[k]);
|
||||
double dist2 = norm(keypoints[j] - keypoints[k]);
|
||||
if (dist1 < dist && dist2 < dist)
|
||||
{
|
||||
isNeighbors = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNeighbors)
|
||||
{
|
||||
rng.addEdge(i, j);
|
||||
vectors.push_back(keypoints[i] - keypoints[j]);
|
||||
if (drawImage != 0)
|
||||
{
|
||||
line(*drawImage, keypoints[i], keypoints[j], Scalar(255, 0, 0), 2);
|
||||
circle(*drawImage, keypoints[i], 3, Scalar(0, 0, 255), -1);
|
||||
circle(*drawImage, keypoints[j], 3, Scalar(0, 0, 255), -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void computePredecessorMatrix(const Mat &dm, int verticesCount, Mat &predecessorMatrix)
|
||||
{
|
||||
CV_Assert( dm.type() == CV_32SC1 );
|
||||
predecessorMatrix.create(verticesCount, verticesCount, CV_32SC1);
|
||||
predecessorMatrix = -1;
|
||||
for (int i = 0; i < predecessorMatrix.rows; i++)
|
||||
{
|
||||
for (int j = 0; j < predecessorMatrix.cols; j++)
|
||||
{
|
||||
int dist = dm.at<int> (i, j);
|
||||
for (int k = 0; k < verticesCount; k++)
|
||||
{
|
||||
if (dm.at<int> (i, k) == dist - 1 && dm.at<int> (k, j) == 1)
|
||||
{
|
||||
predecessorMatrix.at<int> (i, j) = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void computeShortestPath(Mat &predecessorMatrix, size_t v1, size_t v2, std::vector<size_t> &path)
|
||||
{
|
||||
if (predecessorMatrix.at<int> ((int)v1, (int)v2) < 0)
|
||||
{
|
||||
path.push_back(v1);
|
||||
const size_t n = keypoints.size();
|
||||
if (n < 2)
|
||||
return;
|
||||
|
||||
// RNG is a subgraph of the Delaunay triangulation, so we only need to test
|
||||
// Delaunay edges as candidates. This brings the complexity from O(N^3) down
|
||||
// to O(N^2) in the worst case, and much better in practice for regular grids
|
||||
// where Delaunay edges (~3N) are almost all RNG edges anyway.
|
||||
|
||||
float minX = keypoints[0].x, minY = keypoints[0].y;
|
||||
float maxX = minX, maxY = minY;
|
||||
for (size_t i = 1; i < n; i++)
|
||||
{
|
||||
minX = std::min(minX, keypoints[i].x);
|
||||
minY = std::min(minY, keypoints[i].y);
|
||||
maxX = std::max(maxX, keypoints[i].x);
|
||||
maxY = std::max(maxY, keypoints[i].y);
|
||||
}
|
||||
|
||||
computeShortestPath(predecessorMatrix, v1, predecessorMatrix.at<int> ((int)v1, (int)v2), path);
|
||||
path.push_back(v2);
|
||||
// Subdiv2D requires a rect that strictly contains all points.
|
||||
const float margin = 1.f;
|
||||
Rect2f rect(minX - margin, minY - margin,
|
||||
(maxX - minX) + 2*margin,
|
||||
(maxY - minY) + 2*margin);
|
||||
Subdiv2D subdiv(rect);
|
||||
subdiv.insert(std::vector<Point2f>(keypoints.begin(), keypoints.end()));
|
||||
|
||||
// Map coordinates back to keypoint indices. Subdiv2D stores and returns the
|
||||
// exact float values we inserted, so direct comparison is safe here.
|
||||
std::map<std::pair<float, float>, size_t> ptToIdx;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
ptToIdx[{keypoints[i].x, keypoints[i].y}] = i;
|
||||
|
||||
std::vector<Vec4f> edgeList;
|
||||
subdiv.getEdgeList(edgeList);
|
||||
|
||||
for (const Vec4f& e : edgeList)
|
||||
{
|
||||
auto it1 = ptToIdx.find({e[0], e[1]});
|
||||
auto it2 = ptToIdx.find({e[2], e[3]});
|
||||
// Edges involving the virtual bounding-rect vertices won't be in ptToIdx.
|
||||
if (it1 == ptToIdx.end() || it2 == ptToIdx.end())
|
||||
continue;
|
||||
|
||||
size_t i = it1->second;
|
||||
size_t j = it2->second;
|
||||
if (i == j)
|
||||
continue;
|
||||
if (i > j)
|
||||
std::swap(i, j);
|
||||
|
||||
Point2f vec = keypoints[i] - keypoints[j];
|
||||
double distSq = (double)vec.x*vec.x + (double)vec.y*vec.y;
|
||||
|
||||
bool isRNG = true;
|
||||
for (size_t k = 0; k < n; k++)
|
||||
{
|
||||
if (k == i || k == j)
|
||||
continue;
|
||||
Point2f d1 = keypoints[i] - keypoints[k];
|
||||
Point2f d2 = keypoints[j] - keypoints[k];
|
||||
double d1Sq = (double)d1.x*d1.x + (double)d1.y*d1.y;
|
||||
double d2Sq = (double)d2.x*d2.x + (double)d2.y*d2.y;
|
||||
if (d1Sq < distSq && d2Sq < distSq)
|
||||
{
|
||||
isRNG = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRNG)
|
||||
{
|
||||
rng.addEdge(i, j);
|
||||
// Push both directions; findBasis needs the full set to cluster into
|
||||
// the 4 groups (two grid axes and their negatives) via k-means.
|
||||
vectors.push_back(keypoints[i] - keypoints[j]);
|
||||
vectors.push_back(keypoints[j] - keypoints[i]);
|
||||
if (drawImage != 0)
|
||||
{
|
||||
line(*drawImage, keypoints[i], keypoints[j], Scalar(255, 0, 0), 2);
|
||||
circle(*drawImage, keypoints[i], 3, Scalar(0, 0, 255), -1);
|
||||
circle(*drawImage, keypoints[j], 3, Scalar(0, 0, 255), -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t CirclesGridFinder::findLongestPath(std::vector<Graph> &basisGraphs, Path &bestPath)
|
||||
@@ -1290,40 +1301,90 @@ size_t CirclesGridFinder::findLongestPath(std::vector<Graph> &basisGraphs, Path
|
||||
std::vector<int> confidences;
|
||||
|
||||
size_t bestGraphIdx = 0;
|
||||
const int infinity = -1;
|
||||
for (size_t graphIdx = 0; graphIdx < basisGraphs.size(); graphIdx++)
|
||||
{
|
||||
const Graph &g = basisGraphs[graphIdx];
|
||||
Mat distanceMatrix;
|
||||
g.floydWarshall(distanceMatrix, infinity);
|
||||
Mat predecessorMatrix;
|
||||
computePredecessorMatrix(distanceMatrix, (int)g.getVerticesCount(), predecessorMatrix);
|
||||
const int n = (int)g.getVerticesCount();
|
||||
|
||||
double maxVal;
|
||||
Point maxLoc;
|
||||
minMaxLoc(distanceMatrix, 0, &maxVal, 0, &maxLoc);
|
||||
// BFS from every vertex to find the diameter (longest shortest path).
|
||||
// basisGraphs are sparse -- each vertex connects only to grid neighbors in
|
||||
// one direction -- so this is O(N^2) vs Floyd-Warshall's O(N^3).
|
||||
std::vector<int> dist(n);
|
||||
std::queue<int> q;
|
||||
|
||||
if (maxVal > longestPaths[0].length)
|
||||
int maxDist = 0;
|
||||
int srcBest = 0, dstBest = 0;
|
||||
|
||||
for (int src = 0; src < n; src++)
|
||||
{
|
||||
std::fill(dist.begin(), dist.end(), -1);
|
||||
dist[src] = 0;
|
||||
q.push(src);
|
||||
while (!q.empty())
|
||||
{
|
||||
int v = q.front(); q.pop();
|
||||
for (size_t nb : g.getNeighbors((size_t)v))
|
||||
{
|
||||
int u = (int)nb;
|
||||
if (dist[u] < 0)
|
||||
{
|
||||
dist[u] = dist[v] + 1;
|
||||
q.push(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int dst = 0; dst < n; dst++)
|
||||
{
|
||||
if (dist[dst] > maxDist)
|
||||
{
|
||||
maxDist = dist[dst];
|
||||
srcBest = src;
|
||||
dstBest = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxDist > longestPaths[0].length)
|
||||
{
|
||||
longestPaths.clear();
|
||||
confidences.clear();
|
||||
bestGraphIdx = graphIdx;
|
||||
}
|
||||
if (longestPaths.empty() || (maxVal == longestPaths[0].length && graphIdx == bestGraphIdx))
|
||||
if (longestPaths.empty() || (maxDist == longestPaths[0].length && graphIdx == bestGraphIdx))
|
||||
{
|
||||
Path path = Path(maxLoc.x, maxLoc.y, cvRound(maxVal));
|
||||
CV_Assert(maxLoc.x >= 0 && maxLoc.y >= 0)
|
||||
;
|
||||
size_t id1 = static_cast<size_t> (maxLoc.x);
|
||||
size_t id2 = static_cast<size_t> (maxLoc.y);
|
||||
computeShortestPath(predecessorMatrix, id1, id2, path.vertices);
|
||||
Path path = Path(srcBest, dstBest, maxDist);
|
||||
|
||||
// BFS again from srcBest to reconstruct the path to dstBest
|
||||
std::vector<int> pred(n, -1);
|
||||
std::fill(dist.begin(), dist.end(), -1);
|
||||
dist[srcBest] = 0;
|
||||
q.push(srcBest);
|
||||
while (!q.empty())
|
||||
{
|
||||
int v = q.front(); q.pop();
|
||||
for (size_t nb : g.getNeighbors((size_t)v))
|
||||
{
|
||||
int u = (int)nb;
|
||||
if (dist[u] < 0)
|
||||
{
|
||||
dist[u] = dist[v] + 1;
|
||||
pred[u] = v;
|
||||
q.push(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<size_t> pathVertices;
|
||||
for (int cur = dstBest; cur != srcBest; cur = pred[cur])
|
||||
pathVertices.push_back((size_t)cur);
|
||||
pathVertices.push_back((size_t)srcBest);
|
||||
std::reverse(pathVertices.begin(), pathVertices.end());
|
||||
path.vertices = pathVertices;
|
||||
|
||||
longestPaths.push_back(path);
|
||||
|
||||
int conf = 0;
|
||||
for (int v2 = 0; v2 < (int)path.vertices.size(); v2++)
|
||||
{
|
||||
conf += (int)basisGraphs[1 - (int)graphIdx].getDegree(v2);
|
||||
}
|
||||
conf += (int)basisGraphs[1 - (int)graphIdx].getDegree(path.vertices[v2]);
|
||||
confidences.push_back(conf);
|
||||
}
|
||||
}
|
||||
@@ -1339,7 +1400,6 @@ size_t CirclesGridFinder::findLongestPath(std::vector<Graph> &basisGraphs, Path
|
||||
}
|
||||
}
|
||||
|
||||
//int bestPathIdx = rand() % longestPaths.size();
|
||||
bestPath = longestPaths.at(bestPathIdx);
|
||||
bool needReverse = (bestGraphIdx == 0 && keypoints[bestPath.lastVertex].x < keypoints[bestPath.firstVertex].x)
|
||||
|| (bestGraphIdx == 1 && keypoints[bestPath.lastVertex].y < keypoints[bestPath.firstVertex].y);
|
||||
|
||||
@@ -1359,11 +1359,11 @@ private:
|
||||
void extractCodewords(Mat& source, std::vector<uint8_t>& codewords);
|
||||
bool errorCorrection(std::vector<uint8_t>& codewords);
|
||||
bool errorCorrectionBlock(std::vector<uint8_t>& codewords);
|
||||
void decodeSymbols(String& result);
|
||||
bool decodeSymbols(String& result);
|
||||
void decodeNumeric(String& result);
|
||||
void decodeAlpha(String& result);
|
||||
bool decodeAlpha(String& result);
|
||||
void decodeByte(String& result);
|
||||
void decodeECI(String& result);
|
||||
bool decodeECI(String& result);
|
||||
void decodeKanji(String& result);
|
||||
void decodeStructuredAppend(String& result);
|
||||
};
|
||||
@@ -1472,7 +1472,10 @@ bool QRCodeDecoderImpl::run(const Mat& straight, String& decoded_info) {
|
||||
if (!errorCorrection(bitstream.data)) {
|
||||
return false;
|
||||
}
|
||||
decodeSymbols(decoded_info);
|
||||
if (!decodeSymbols(decoded_info)) {
|
||||
decoded_info = "";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1737,7 +1740,7 @@ void QRCodeDecoderImpl::extractCodewords(Mat& source, std::vector<uint8_t>& code
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
bool QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
CV_Assert(!bitstream.empty());
|
||||
|
||||
// Decode depends on the mode
|
||||
@@ -1750,15 +1753,19 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
}
|
||||
|
||||
if (currMode == 0 || bitstream.empty())
|
||||
return;
|
||||
return true;
|
||||
if (currMode == QRCodeEncoder::EncodeMode::MODE_NUMERIC)
|
||||
decodeNumeric(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC)
|
||||
decodeAlpha(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC) {
|
||||
if (!decodeAlpha(result))
|
||||
return false;
|
||||
}
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_BYTE)
|
||||
decodeByte(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI)
|
||||
decodeECI(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI) {
|
||||
if (!decodeECI(result))
|
||||
return false;
|
||||
}
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_KANJI)
|
||||
decodeKanji(result);
|
||||
else if (currMode == QRCodeEncoder::EncodeMode::MODE_STRUCTURED_APPEND) {
|
||||
@@ -1769,6 +1776,7 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) {
|
||||
else
|
||||
CV_Error(Error::StsNotImplemented, format("mode %d", currMode));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeNumeric(String& result) {
|
||||
@@ -1788,7 +1796,7 @@ void QRCodeDecoderImpl::decodeNumeric(String& result) {
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeAlpha(String& result) {
|
||||
bool QRCodeDecoderImpl::decodeAlpha(String& result) {
|
||||
static const char map[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
@@ -1798,13 +1806,18 @@ void QRCodeDecoderImpl::decodeAlpha(String& result) {
|
||||
int num = bitstream.next(version <= 9 ? 9 : (version <= 26 ? 11 : 13));
|
||||
for (int i = 0; i < num / 2; ++i) {
|
||||
int tuple = bitstream.next(11);
|
||||
if (tuple >= 45 * 45)
|
||||
return false;
|
||||
result += map[tuple / 45];
|
||||
result += map[tuple % 45];
|
||||
}
|
||||
if (num % 2) {
|
||||
int value = bitstream.next(6);
|
||||
if (value >= 45)
|
||||
return false;
|
||||
result += map[value];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeByte(String& result) {
|
||||
@@ -1814,7 +1827,7 @@ void QRCodeDecoderImpl::decodeByte(String& result) {
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeECI(String& result) {
|
||||
bool QRCodeDecoderImpl::decodeECI(String& result) {
|
||||
int eciAssignValue = bitstream.next(8);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (eciAssignValue & 1 << (7 - i))
|
||||
@@ -1825,8 +1838,7 @@ void QRCodeDecoderImpl::decodeECI(String& result) {
|
||||
if (this->eci == 0) {
|
||||
this->eci = static_cast<QRCodeEncoder::ECIEncodings>(eciAssignValue);
|
||||
}
|
||||
decodeSymbols(result);
|
||||
|
||||
return decodeSymbols(result);
|
||||
}
|
||||
|
||||
void QRCodeDecoderImpl::decodeKanji(String& result) {
|
||||
|
||||
Reference in New Issue
Block a user