1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 23:33:05 +04:00

Merge branch 4.x

This commit is contained in:
Alexander Alekhin
2023-01-09 11:08:02 +00:00
880 changed files with 83958 additions and 9368 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,117 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
//
// This software was developed in the APRIL Robotics Lab under the
// direction of Edwin Olson, ebolson@umich.edu. This software may be
// available under alternative licensing terms; contact the address above.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the Regents of The University of Michigan.
// limitation: image size must be <32768 in width and height. This is
// because we use a fixed-point 16 bit integer representation with one
// fractional bit.
#ifndef _OPENCV_APRIL_QUAD_THRESH_HPP_
#define _OPENCV_APRIL_QUAD_THRESH_HPP_
#include "unionfind.hpp"
#include "zmaxheap.hpp"
#include "zarray.hpp"
namespace cv {
namespace aruco {
static inline uint32_t u64hash_2(uint64_t x) {
return uint32_t((2654435761UL * x) >> 32);
}
struct uint64_zarray_entry{
uint64_t id;
zarray_t *cluster;
struct uint64_zarray_entry *next;
};
struct pt{
// Note: these represent 2*actual value.
uint16_t x, y;
float theta;
int16_t gx, gy;
};
struct remove_vertex{
int i; // which vertex to remove?
int left, right; // left vertex, right vertex
double err;
};
struct segment{
int is_vertex;
// always greater than zero, but right can be > size, which denotes
// a wrap around back to the beginning of the points. and left < right.
int left, right;
};
struct line_fit_pt{
double Mx, My;
double Mxx, Myy, Mxy;
double W; // total weight
};
/**
* lfps contains *cumulative* moments for N points, with
* index j reflecting points [0,j] (inclusive).
* fit a line to the points [i0, i1] (inclusive). i0, i1 are both (0, sz)
* if i1 < i0, we treat this as a wrap around.
*/
void fit_line(struct line_fit_pt *lfps, int sz, int i0, int i1, double *lineparm, double *err, double *mse);
int err_compare_descending(const void *_a, const void *_b);
/**
1. Identify A) white points near a black point and B) black points near a white point.
2. Find the connected components within each of the classes above,
yielding clusters of "white-near-black" and
"black-near-white". (These two classes are kept separate). Each
segment has a unique id.
3. For every pair of "white-near-black" and "black-near-white"
clusters, find the set of points that are in one and adjacent to the
other. In other words, a "boundary" layer between the two
clusters. (This is actually performed by iterating over the pixels,
rather than pairs of clusters.) Critically, this helps keep nearby
edges from becoming connected.
**/
int quad_segment_maxima(const DetectorParameters &td, int sz, struct line_fit_pt *lfps, int indices[4]);
/**
* returns 0 if the cluster looks bad.
*/
int quad_segment_agg(int sz, struct line_fit_pt *lfps, int indices[4]);
/**
* return 1 if the quad looks okay, 0 if it should be discarded
* quad
**/
int fit_quad(const DetectorParameters &_params, const Mat im, zarray_t *cluster, struct sQuad *quad);
void threshold(const Mat mIm, const DetectorParameters &parameters, Mat& mThresh);
zarray_t *apriltag_quad_thresh(const DetectorParameters &parameters, const Mat & mImg,
std::vector<std::vector<Point> > &contours);
void _apriltag(Mat im_orig, const DetectorParameters &_params, std::vector<std::vector<Point2f> > &candidates,
std::vector<std::vector<Point> > &contours);
}}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,131 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
//
// This software was developed in the APRIL Robotics Lab under the
// direction of Edwin Olson, ebolson@umich.edu. This software may be
// available under alternative licensing terms; contact the address above.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the Regents of The University of Michigan.
#ifndef _OPENCV_UNIONFIND_HPP_
#define _OPENCV_UNIONFIND_HPP_
namespace cv {
namespace aruco {
typedef struct unionfind unionfind_t;
struct unionfind{
uint32_t maxid;
struct ufrec *data;
};
struct ufrec{
// the parent of this node. If a node's parent is its own index,
// then it is a root.
uint32_t parent;
// for the root of a connected component, the number of components
// connected to it. For intermediate values, it's not meaningful.
uint32_t size;
};
static inline unionfind_t *unionfind_create(uint32_t maxid){
unionfind_t *uf = (unionfind_t*) calloc(1, sizeof(unionfind_t));
uf->maxid = maxid;
uf->data = (struct ufrec*) malloc((maxid+1) * sizeof(struct ufrec));
for (unsigned int i = 0; i <= maxid; i++) {
uf->data[i].size = 1;
uf->data[i].parent = i;
}
return uf;
}
static inline void unionfind_destroy(unionfind_t *uf){
free(uf->data);
free(uf);
}
/*
static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id)
{
// base case: a node is its own parent
if (uf->data[id].parent == id)
return id;
// otherwise, recurse
uint32_t root = unionfind_get_representative(uf, uf->data[id].parent);
// short circuit the path. [XXX This write prevents tail recursion]
uf->data[id].parent = root;
return root;
}
*/
// this one seems to be every-so-slightly faster than the recursive
// version above.
static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id){
uint32_t root = id;
// chase down the root
while (uf->data[root].parent != root) {
root = uf->data[root].parent;
}
// go back and collapse the tree.
//
// XXX: on some of our workloads that have very shallow trees
// (e.g. image segmentation), we are actually faster not doing
// this...
while (uf->data[id].parent != root) {
uint32_t tmp = uf->data[id].parent;
uf->data[id].parent = root;
id = tmp;
}
return root;
}
static inline uint32_t unionfind_get_set_size(unionfind_t *uf, uint32_t id){
uint32_t repid = unionfind_get_representative(uf, id);
return uf->data[repid].size;
}
static inline uint32_t unionfind_connect(unionfind_t *uf, uint32_t aid, uint32_t bid){
uint32_t aroot = unionfind_get_representative(uf, aid);
uint32_t broot = unionfind_get_representative(uf, bid);
if (aroot == broot)
return aroot;
// we don't perform "union by rank", but we perform a similar
// operation (but probably without the same asymptotic guarantee):
// We join trees based on the number of *elements* (as opposed to
// rank) contained within each tree. I.e., we use size as a proxy
// for rank. In my testing, it's often *faster* to use size than
// rank, perhaps because the rank of the tree isn't that critical
// if there are very few nodes in it.
uint32_t asize = uf->data[aroot].size;
uint32_t bsize = uf->data[broot].size;
// optimization idea: We could shortcut some or all of the tree
// that is grafted onto the other tree. Pro: those nodes were just
// read and so are probably in cache. Con: it might end up being
// wasted effort -- the tree might be grafted onto another tree in
// a moment!
if (asize > bsize) {
uf->data[broot].parent = aroot;
uf->data[aroot].size += bsize;
return aroot;
} else {
uf->data[aroot].parent = broot;
uf->data[broot].size += asize;
return broot;
}
}
}}
#endif
@@ -0,0 +1,148 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
//
// This software was developed in the APRIL Robotics Lab under the
// direction of Edwin Olson, ebolson@umich.edu. This software may be
// available under alternative licensing terms; contact the address above.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the Regents of The University of Michigan.
#ifndef _OPENCV_ZARRAY_HPP_
#define _OPENCV_ZARRAY_HPP_
namespace cv {
namespace aruco {
struct sQuad{
float p[4][2]; // corners
};
/**
* Defines a structure which acts as a resize-able array ala Java's ArrayList.
*/
typedef struct zarray zarray_t;
struct zarray{
size_t el_sz; // size of each element
int size; // how many elements?
int alloc; // we've allocated storage for how many elements?
char *data;
};
/**
* Creates and returns a variable array structure capable of holding elements of
* the specified size. It is the caller's responsibility to call zarray_destroy()
* on the returned array when it is no longer needed.
*/
inline static zarray_t *_zarray_create(size_t el_sz){
zarray_t *za = (zarray_t*) calloc(1, sizeof(zarray_t));
za->el_sz = el_sz;
return za;
}
/**
* Frees all resources associated with the variable array structure which was
* created by zarray_create(). After calling, 'za' will no longer be valid for storage.
*/
inline static void _zarray_destroy(zarray_t *za){
if (za == NULL)
return;
if (za->data != NULL)
free(za->data);
memset(za, 0, sizeof(zarray_t));
free(za);
}
/**
* Retrieves the number of elements currently being contained by the passed
* array, which may be different from its capacity. The index of the last element
* in the array will be one less than the returned value.
*/
inline static int _zarray_size(const zarray_t *za){
return za->size;
}
/**
* Allocates enough internal storage in the supplied variable array structure to
* guarantee that the supplied number of elements (capacity) can be safely stored.
*/
inline static void _zarray_ensure_capacity(zarray_t *za, int capacity){
if (capacity <= za->alloc)
return;
while (za->alloc < capacity) {
za->alloc *= 2;
if (za->alloc < 8)
za->alloc = 8;
}
za->data = (char*) realloc(za->data, za->alloc * za->el_sz);
}
/**
* Adds a new element to the end of the supplied array, and sets its value
* (by copying) from the data pointed to by the supplied pointer 'p'.
* Automatically ensures that enough storage space is available for the new element.
*/
inline static void _zarray_add(zarray_t *za, const void *p){
_zarray_ensure_capacity(za, za->size + 1);
memcpy(&za->data[za->size*za->el_sz], p, za->el_sz);
za->size++;
}
/**
* Retrieves the element from the supplied array located at the zero-based
* index of 'idx' and copies its value into the variable pointed to by the pointer
* 'p'.
*/
inline static void _zarray_get(const zarray_t *za, int idx, void *p){
CV_DbgAssert(idx >= 0);
CV_DbgAssert(idx < za->size);
memcpy(p, &za->data[idx*za->el_sz], za->el_sz);
}
/**
* Similar to zarray_get(), but returns a "live" pointer to the internal
* storage, avoiding a memcpy. This pointer is not valid across
* operations which might move memory around (i.e. zarray_remove_value(),
* zarray_remove_index(), zarray_insert(), zarray_sort(), zarray_clear()).
* 'p' should be a pointer to the pointer which will be set to the internal address.
*/
inline static void _zarray_get_volatile(const zarray_t *za, int idx, void *p){
CV_DbgAssert(idx >= 0);
CV_DbgAssert(idx < za->size);
*((void**) p) = &za->data[idx*za->el_sz];
}
inline static void _zarray_truncate(zarray_t *za, int sz){
za->size = sz;
}
/**
* Sets the value of the current element at index 'idx' by copying its value from
* the data pointed to by 'p'. The previous value of the changed element will be
* copied into the data pointed to by 'outp' if it is not null.
*/
static inline void _zarray_set(zarray_t *za, int idx, const void *p, void *outp){
CV_DbgAssert(idx >= 0);
CV_DbgAssert(idx < za->size);
if (outp != NULL)
memcpy(outp, &za->data[idx*za->el_sz], za->el_sz);
memcpy(&za->data[idx*za->el_sz], p, za->el_sz);
}
}
}
#endif
@@ -0,0 +1,207 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
//
// This software was developed in the APRIL Robotics Lab under the
// direction of Edwin Olson, ebolson@umich.edu. This software may be
// available under alternative licensing terms; contact the address above.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the Regents of The University of Michigan.
#include "../../precomp.hpp"
#include "zmaxheap.hpp"
// 0
// 1 2
// 3 4 5 6
// 7 8 9 10 11 12 13 14
//
// Children of node i: 2*i+1, 2*i+2
// Parent of node i: (i-1) / 2
//
// Heap property: a parent is greater than (or equal to) its children.
#define MIN_CAPACITY 16
namespace cv {
namespace aruco {
struct zmaxheap
{
size_t el_sz;
int size;
int alloc;
float *values;
char *data;
void (*swap)(zmaxheap_t *heap, int a, int b);
};
static inline void _swap_default(zmaxheap_t *heap, int a, int b)
{
float t = heap->values[a];
heap->values[a] = heap->values[b];
heap->values[b] = t;
cv::AutoBuffer<char> tmp(heap->el_sz);
memcpy(tmp.data(), &heap->data[a*heap->el_sz], heap->el_sz);
memcpy(&heap->data[a*heap->el_sz], &heap->data[b*heap->el_sz], heap->el_sz);
memcpy(&heap->data[b*heap->el_sz], tmp.data(), heap->el_sz);
}
static inline void _swap_pointer(zmaxheap_t *heap, int a, int b)
{
float t = heap->values[a];
heap->values[a] = heap->values[b];
heap->values[b] = t;
void **pp = (void**) heap->data;
void *tmp = pp[a];
pp[a] = pp[b];
pp[b] = tmp;
}
zmaxheap_t *zmaxheap_create(size_t el_sz)
{
zmaxheap_t *heap = (zmaxheap_t*)calloc(1, sizeof(zmaxheap_t));
heap->el_sz = el_sz;
heap->swap = _swap_default;
if (el_sz == sizeof(void*))
heap->swap = _swap_pointer;
return heap;
}
void zmaxheap_destroy(zmaxheap_t *heap)
{
free(heap->values);
free(heap->data);
memset(heap, 0, sizeof(zmaxheap_t));
free(heap);
}
static void _zmaxheap_ensure_capacity(zmaxheap_t *heap, int capacity)
{
if (heap->alloc >= capacity)
return;
int newcap = heap->alloc;
while (newcap < capacity) {
if (newcap < MIN_CAPACITY) {
newcap = MIN_CAPACITY;
continue;
}
newcap *= 2;
}
heap->values = (float*)realloc(heap->values, newcap * sizeof(float));
heap->data = (char*)realloc(heap->data, newcap * heap->el_sz);
heap->alloc = newcap;
}
void zmaxheap_add(zmaxheap_t *heap, void *p, float v)
{
_zmaxheap_ensure_capacity(heap, heap->size + 1);
int idx = heap->size;
heap->values[idx] = v;
memcpy(&heap->data[idx*heap->el_sz], p, heap->el_sz);
heap->size++;
while (idx > 0) {
int parent = (idx - 1) / 2;
// we're done!
if (heap->values[parent] >= v)
break;
// else, swap and recurse upwards.
heap->swap(heap, idx, parent);
idx = parent;
}
}
// Removes the item in the heap at the given index. Returns 1 if the
// item existed. 0 Indicates an invalid idx (heap is smaller than
// idx). This is mostly intended to be used by zmaxheap_remove_max.
static int zmaxheap_remove_index(zmaxheap_t *heap, int idx, void *p, float *v)
{
if (idx >= heap->size)
return 0;
// copy out the requested element from the heap.
if (v != NULL)
*v = heap->values[idx];
if (p != NULL)
memcpy(p, &heap->data[idx*heap->el_sz], heap->el_sz);
heap->size--;
// If this element is already the last one, then there's nothing
// for us to do.
if (idx == heap->size)
return 1;
// copy last element to first element. (which probably upsets
// the heap property).
heap->values[idx] = heap->values[heap->size];
memcpy(&heap->data[idx*heap->el_sz], &heap->data[heap->el_sz * heap->size], heap->el_sz);
// now fix the heap. Note, as we descend, we're "pushing down"
// the same node the entire time. Thus, while the index of the
// parent might change, the parent_score doesn't.
int parent = idx;
float parent_score = heap->values[idx];
// descend, fixing the heap.
while (parent < heap->size) {
int left = 2*parent + 1;
int right = left + 1;
// assert(parent_score == heap->values[parent]);
float left_score = (left < heap->size) ? heap->values[left] : -INFINITY;
float right_score = (right < heap->size) ? heap->values[right] : -INFINITY;
// put the biggest of (parent, left, right) as the parent.
// already okay?
if (parent_score >= left_score && parent_score >= right_score)
break;
// if we got here, then one of the children is bigger than the parent.
if (left_score >= right_score) {
CV_Assert(left < heap->size);
heap->swap(heap, parent, left);
parent = left;
} else {
// right_score can't be less than left_score if right_score is -INFINITY.
CV_Assert(right < heap->size);
heap->swap(heap, parent, right);
parent = right;
}
}
return 1;
}
int zmaxheap_remove_max(zmaxheap_t *heap, void *p, float *v)
{
return zmaxheap_remove_index(heap, 0, p, v);
}
}}
@@ -0,0 +1,38 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2013-2016, The Regents of The University of Michigan.
//
// This software was developed in the APRIL Robotics Lab under the
// direction of Edwin Olson, ebolson@umich.edu. This software may be
// available under alternative licensing terms; contact the address above.
//
// The views and conclusions contained in the software and documentation are those
// of the authors and should not be interpreted as representing official policies,
// either expressed or implied, of the Regents of The University of Michigan.
#ifndef _OPENCV_ZMAXHEAP_HPP_
#define _OPENCV_ZMAXHEAP_HPP_
namespace cv {
namespace aruco {
typedef struct zmaxheap zmaxheap_t;
typedef struct zmaxheap_iterator zmaxheap_iterator_t;
struct zmaxheap_iterator {
zmaxheap_t *heap;
int in, out;
};
zmaxheap_t *zmaxheap_create(size_t el_sz);
void zmaxheap_destroy(zmaxheap_t *heap);
void zmaxheap_add(zmaxheap_t *heap, void *p, float v);
// returns 0 if the heap is empty, so you can do
// while (zmaxheap_remove_max(...)) { }
int zmaxheap_remove_max(zmaxheap_t *heap, void *p, float *v);
}}
#endif
+579
View File
@@ -0,0 +1,579 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "../precomp.hpp"
#include "opencv2/objdetect/aruco_board.hpp"
#include <opencv2/objdetect/aruco_dictionary.hpp>
#include <numeric>
namespace cv {
namespace aruco {
using namespace std;
struct Board::Impl {
Dictionary dictionary;
std::vector<int> ids;
std::vector<std::vector<Point3f> > objPoints;
Point3f rightBottomBorder;
explicit Impl(const Dictionary& _dictionary):
dictionary(_dictionary)
{}
virtual ~Impl() {}
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
virtual void matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray _objPoints,
OutputArray imgPoints) const;
virtual void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const;
};
void Board::Impl::matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray _objPoints,
OutputArray imgPoints) const {
CV_Assert(ids.size() == objPoints.size());
CV_Assert(detectedIds.total() == detectedCorners.total());
size_t nDetectedMarkers = detectedIds.total();
vector<Point3f> objPnts;
objPnts.reserve(nDetectedMarkers);
vector<Point2f> imgPnts;
imgPnts.reserve(nDetectedMarkers);
// look for detected markers that belong to the board and get their information
for(unsigned int i = 0; i < nDetectedMarkers; i++) {
int currentId = detectedIds.getMat().ptr< int >(0)[i];
for(unsigned int j = 0; j < ids.size(); j++) {
if(currentId == ids[j]) {
for(int p = 0; p < 4; p++) {
objPnts.push_back(objPoints[j][p]);
imgPnts.push_back(detectedCorners.getMat(i).ptr<Point2f>(0)[p]);
}
}
}
}
// create output
Mat(objPnts).copyTo(_objPoints);
Mat(imgPnts).copyTo(imgPoints);
}
void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const {
CV_Assert(!outSize.empty());
CV_Assert(marginSize >= 0);
img.create(outSize, CV_8UC1);
Mat out = img.getMat();
out.setTo(Scalar::all(255));
out.adjustROI(-marginSize, -marginSize, -marginSize, -marginSize);
// calculate max and min values in XY plane
CV_Assert(objPoints.size() > 0);
float minX, maxX, minY, maxY;
minX = maxX = objPoints[0][0].x;
minY = maxY = objPoints[0][0].y;
for(unsigned int i = 0; i < objPoints.size(); i++) {
for(int j = 0; j < 4; j++) {
minX = min(minX, objPoints[i][j].x);
maxX = max(maxX, objPoints[i][j].x);
minY = min(minY, objPoints[i][j].y);
maxY = max(maxY, objPoints[i][j].y);
}
}
float sizeX = maxX - minX;
float sizeY = maxY - minY;
// proportion transformations
float xReduction = sizeX / float(out.cols);
float yReduction = sizeY / float(out.rows);
// determine the zone where the markers are placed
if(xReduction > yReduction) {
int nRows = int(sizeY / xReduction);
int rowsMargins = (out.rows - nRows) / 2;
out.adjustROI(-rowsMargins, -rowsMargins, 0, 0);
} else {
int nCols = int(sizeX / yReduction);
int colsMargins = (out.cols - nCols) / 2;
out.adjustROI(0, 0, -colsMargins, -colsMargins);
}
// now paint each marker
Mat marker;
Point2f outCorners[3];
Point2f inCorners[3];
for(unsigned int m = 0; m < objPoints.size(); m++) {
// transform corners to markerZone coordinates
for(int j = 0; j < 3; j++) {
Point2f pf = Point2f(objPoints[m][j].x, objPoints[m][j].y);
// move top left to 0, 0
pf -= Point2f(minX, minY);
pf.x = pf.x / sizeX * float(out.cols);
pf.y = pf.y / sizeY * float(out.rows);
outCorners[j] = pf;
}
// get marker
Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order
dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); //marker should be square
dictionary.generateImageMarker(ids[m], dst_sz.width, marker, borderBits);
if((outCorners[0].y == outCorners[1].y) && (outCorners[1].x == outCorners[2].x)) {
// marker is aligned to image axes
marker.copyTo(out(Rect(outCorners[0], dst_sz)));
continue;
}
// interpolate tiny marker to marker position in markerZone
inCorners[0] = Point2f(-0.5f, -0.5f);
inCorners[1] = Point2f(marker.cols - 0.5f, -0.5f);
inCorners[2] = Point2f(marker.cols - 0.5f, marker.rows - 0.5f);
// remove perspective
Mat transformation = getAffineTransform(inCorners, outCorners);
warpAffine(marker, out, transformation, out.size(), INTER_LINEAR,
BORDER_TRANSPARENT);
}
}
Board::Board(const Ptr<Impl>& _impl):
impl(_impl)
{
CV_Assert(impl);
}
Board::Board():
impl(nullptr)
{}
Board::Board(InputArrayOfArrays objPoints, const Dictionary &dictionary, InputArray ids):
Board(new Board::Impl(dictionary)) {
CV_Assert(ids.size() == objPoints.size());
CV_Assert(objPoints.total() == ids.total());
CV_Assert(objPoints.type() == CV_32FC3 || objPoints.type() == CV_32FC1);
vector<vector<Point3f> > obj_points_vector;
Point3f rightBottomBorder = Point3f(0.f, 0.f, 0.f);
for (unsigned int i = 0; i < objPoints.total(); i++) {
vector<Point3f> corners;
Mat corners_mat = objPoints.getMat(i);
if (corners_mat.type() == CV_32FC1)
corners_mat = corners_mat.reshape(3);
CV_Assert(corners_mat.total() == 4);
for (int j = 0; j < 4; j++) {
const Point3f &corner = corners_mat.at<Point3f>(j);
corners.push_back(corner);
rightBottomBorder.x = std::max(rightBottomBorder.x, corner.x);
rightBottomBorder.y = std::max(rightBottomBorder.y, corner.y);
rightBottomBorder.z = std::max(rightBottomBorder.z, corner.z);
}
obj_points_vector.push_back(corners);
}
ids.copyTo(impl->ids);
impl->objPoints = obj_points_vector;
impl->rightBottomBorder = rightBottomBorder;
}
const Dictionary& Board::getDictionary() const {
CV_Assert(this->impl);
return this->impl->dictionary;
}
const vector<vector<Point3f> >& Board::getObjPoints() const {
CV_Assert(this->impl);
return this->impl->objPoints;
}
const Point3f& Board::getRightBottomCorner() const {
CV_Assert(this->impl);
return this->impl->rightBottomBorder;
}
const vector<int>& Board::getIds() const {
CV_Assert(this->impl);
return this->impl->ids;
}
/** @brief Implementation of draw planar board that accepts a raw Board pointer.
*/
void Board::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const {
CV_Assert(this->impl);
impl->generateImage(outSize, img, marginSize, borderBits);
}
void Board::matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray objPoints,
OutputArray imgPoints) const {
CV_Assert(this->impl);
impl->matchImagePoints(detectedCorners, detectedIds, objPoints, imgPoints);
}
struct GridBoardImpl : public Board::Impl {
GridBoardImpl(const Dictionary& _dictionary, const Size& _size, float _markerLength, float _markerSeparation):
Board::Impl(_dictionary),
size(_size),
markerLength(_markerLength),
markerSeparation(_markerSeparation)
{
CV_Assert(size.width*size.height > 0 && markerLength > 0 && markerSeparation > 0);
}
// number of markers in X and Y directions
const Size size;
// marker side length (normally in meters)
float markerLength;
// separation between markers in the grid
float markerSeparation;
};
GridBoard::GridBoard() {}
GridBoard::GridBoard(const Size& size, float markerLength, float markerSeparation,
const Dictionary &dictionary, InputArray ids):
Board(new GridBoardImpl(dictionary, size, markerLength, markerSeparation)) {
size_t totalMarkers = (size_t) size.width*size.height;
CV_Assert(ids.empty() || totalMarkers == ids.total());
vector<vector<Point3f> > objPoints;
objPoints.reserve(totalMarkers);
if(!ids.empty()) {
ids.copyTo(impl->ids);
} else {
impl->ids = std::vector<int>(totalMarkers);
std::iota(impl->ids.begin(), impl->ids.end(), 0);
}
// calculate Board objPoints
for (int y = 0; y < size.height; y++) {
for (int x = 0; x < size.width; x++) {
vector <Point3f> corners(4);
corners[0] = Point3f(x * (markerLength + markerSeparation),
y * (markerLength + markerSeparation), 0);
corners[1] = corners[0] + Point3f(markerLength, 0, 0);
corners[2] = corners[0] + Point3f(markerLength, markerLength, 0);
corners[3] = corners[0] + Point3f(0, markerLength, 0);
objPoints.push_back(corners);
}
}
impl->objPoints = objPoints;
impl->rightBottomBorder = Point3f(size.width * markerLength + markerSeparation * (size.width - 1),
size.height * markerLength + markerSeparation * (size.height - 1), 0.f);
}
Size GridBoard::getGridSize() const {
CV_Assert(impl);
return static_pointer_cast<GridBoardImpl>(impl)->size;
}
float GridBoard::getMarkerLength() const {
CV_Assert(impl);
return static_pointer_cast<GridBoardImpl>(impl)->markerLength;
}
float GridBoard::getMarkerSeparation() const {
CV_Assert(impl);
return static_pointer_cast<GridBoardImpl>(impl)->markerSeparation;
}
struct CharucoBoardImpl : Board::Impl {
CharucoBoardImpl(const Dictionary& _dictionary, const Size& _size, float _squareLength, float _markerLength):
Board::Impl(_dictionary),
size(_size),
squareLength(_squareLength),
markerLength(_markerLength)
{}
// chessboard size
Size size;
// Physical size of chessboard squares side (normally in meters)
float squareLength;
// Physical marker side length (normally in meters)
float markerLength;
// vector of chessboard 3D corners precalculated
std::vector<Point3f> chessboardCorners;
// for each charuco corner, nearest marker id and nearest marker corner id of each marker
std::vector<std::vector<int> > nearestMarkerIdx;
std::vector<std::vector<int> > nearestMarkerCorners;
void calcNearestMarkerCorners();
void matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds,
OutputArray objPoints, OutputArray imgPoints) const override;
void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const override;
};
/** Fill nearestMarkerIdx and nearestMarkerCorners arrays */
void CharucoBoardImpl::calcNearestMarkerCorners() {
nearestMarkerIdx.resize(chessboardCorners.size());
nearestMarkerCorners.resize(chessboardCorners.size());
unsigned int nMarkers = (unsigned int)objPoints.size();
unsigned int nCharucoCorners = (unsigned int)chessboardCorners.size();
for(unsigned int i = 0; i < nCharucoCorners; i++) {
double minDist = -1; // distance of closest markers
Point3f charucoCorner = chessboardCorners[i];
for(unsigned int j = 0; j < nMarkers; j++) {
// calculate distance from marker center to charuco corner
Point3f center = Point3f(0, 0, 0);
for(unsigned int k = 0; k < 4; k++)
center += objPoints[j][k];
center /= 4.;
double sqDistance;
Point3f distVector = charucoCorner - center;
sqDistance = distVector.x * distVector.x + distVector.y * distVector.y;
if(j == 0 || fabs(sqDistance - minDist) < cv::pow(0.01 * squareLength, 2)) {
// if same minimum distance (or first iteration), add to nearestMarkerIdx vector
nearestMarkerIdx[i].push_back(j);
minDist = sqDistance;
} else if(sqDistance < minDist) {
// if finding a closest marker to the charuco corner
nearestMarkerIdx[i].clear(); // remove any previous added marker
nearestMarkerIdx[i].push_back(j); // add the new closest marker index
minDist = sqDistance;
}
}
// for each of the closest markers, search the marker corner index closer
// to the charuco corner
for(unsigned int j = 0; j < nearestMarkerIdx[i].size(); j++) {
nearestMarkerCorners[i].resize(nearestMarkerIdx[i].size());
double minDistCorner = -1;
for(unsigned int k = 0; k < 4; k++) {
double sqDistance;
Point3f distVector = charucoCorner - objPoints[nearestMarkerIdx[i][j]][k];
sqDistance = distVector.x * distVector.x + distVector.y * distVector.y;
if(k == 0 || sqDistance < minDistCorner) {
// if this corner is closer to the charuco corner, assing its index
// to nearestMarkerCorners
minDistCorner = sqDistance;
nearestMarkerCorners[i][j] = k;
}
}
}
}
}
void CharucoBoardImpl::matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds,
OutputArray _objPoints, OutputArray imgPoints) const {
if (detectedCorners.kind() == _InputArray::STD_VECTOR_VECTOR ||
detectedCorners.isMatVector() || detectedCorners.isUMatVector())
Board::Impl::matchImagePoints(detectedCorners, detectedIds, _objPoints, imgPoints);
else {
CV_Assert(detectedCorners.isMat() || detectedCorners.isVector());
size_t nDetected = detectedCorners.total();
vector<Point3f> objPnts(nDetected);
vector<Point2f> imgPnts(nDetected);
for(size_t i = 0ull; i < nDetected; i++) {
int pointId = detectedIds.getMat().at<int>((int)i);
CV_Assert(pointId >= 0 && pointId < (int)chessboardCorners.size());
objPnts[i] = chessboardCorners[pointId];
imgPnts[i] = detectedCorners.getMat().at<Point2f>((int)i);
}
Mat(objPnts).copyTo(_objPoints);
Mat(imgPnts).copyTo(imgPoints);
}
}
void CharucoBoardImpl::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const {
CV_Assert(!outSize.empty());
CV_Assert(marginSize >= 0);
img.create(outSize, CV_8UC1);
img.setTo(255);
Mat out = img.getMat();
Mat noMarginsImg =
out.colRange(marginSize, out.cols - marginSize).rowRange(marginSize, out.rows - marginSize);
double totalLengthX, totalLengthY;
totalLengthX = squareLength * size.width;
totalLengthY = squareLength * size.height;
// proportional transformation
double xReduction = totalLengthX / double(noMarginsImg.cols);
double yReduction = totalLengthY / double(noMarginsImg.rows);
// determine the zone where the chessboard is placed
Mat chessboardZoneImg;
if(xReduction > yReduction) {
int nRows = int(totalLengthY / xReduction);
int rowsMargins = (noMarginsImg.rows - nRows) / 2;
chessboardZoneImg = noMarginsImg.rowRange(rowsMargins, noMarginsImg.rows - rowsMargins);
} else {
int nCols = int(totalLengthX / yReduction);
int colsMargins = (noMarginsImg.cols - nCols) / 2;
chessboardZoneImg = noMarginsImg.colRange(colsMargins, noMarginsImg.cols - colsMargins);
}
// determine the margins to draw only the markers
// take the minimum just to be sure
double squareSizePixels = min(double(chessboardZoneImg.cols) / double(size.width),
double(chessboardZoneImg.rows) / double(size.height));
double diffSquareMarkerLength = (squareLength - markerLength) / 2;
int diffSquareMarkerLengthPixels =
int(diffSquareMarkerLength * squareSizePixels / squareLength);
// draw markers
Mat markersImg;
Board::Impl::generateImage(chessboardZoneImg.size(), markersImg, diffSquareMarkerLengthPixels, borderBits);
markersImg.copyTo(chessboardZoneImg);
// now draw black squares
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(y % 2 != x % 2) continue; // white corner, dont do anything
double startX, startY;
startX = squareSizePixels * double(x);
startY = squareSizePixels * double(y);
Mat squareZone = chessboardZoneImg.rowRange(int(startY), int(startY + squareSizePixels))
.colRange(int(startX), int(startX + squareSizePixels));
squareZone.setTo(0);
}
}
}
CharucoBoard::CharucoBoard(){}
CharucoBoard::CharucoBoard(const Size& size, float squareLength, float markerLength,
const Dictionary &dictionary, InputArray ids):
Board(new CharucoBoardImpl(dictionary, size, squareLength, markerLength)) {
CV_Assert(size.width > 1 && size.height > 1 && markerLength > 0 && squareLength > markerLength);
vector<vector<Point3f> > objPoints;
float diffSquareMarkerLength = (squareLength - markerLength) / 2;
int totalMarkers = (int)(ids.total());
ids.copyTo(impl->ids);
// calculate Board objPoints
int nextId = 0;
for(int y = 0; y < size.height; y++) {
for(int x = 0; x < size.width; x++) {
if(y % 2 == x % 2) continue; // black corner, no marker here
vector<Point3f> corners(4);
corners[0] = Point3f(x * squareLength + diffSquareMarkerLength,
y * squareLength + diffSquareMarkerLength, 0);
corners[1] = corners[0] + Point3f(markerLength, 0, 0);
corners[2] = corners[0] + Point3f(markerLength, markerLength, 0);
corners[3] = corners[0] + Point3f(0, markerLength, 0);
objPoints.push_back(corners);
// first ids in dictionary
if (totalMarkers == 0)
impl->ids.push_back(nextId);
nextId++;
}
}
if (totalMarkers > 0 && nextId != totalMarkers)
CV_Error(cv::Error::StsBadSize, "Size of ids must be equal to the number of markers: "+std::to_string(nextId));
impl->objPoints = objPoints;
// now fill chessboardCorners
std::vector<Point3f> & c = static_pointer_cast<CharucoBoardImpl>(impl)->chessboardCorners;
for(int y = 0; y < size.height - 1; y++) {
for(int x = 0; x < size.width - 1; x++) {
Point3f corner;
corner.x = (x + 1) * squareLength;
corner.y = (y + 1) * squareLength;
corner.z = 0;
c.push_back(corner);
}
}
impl->rightBottomBorder = Point3f(size.width * squareLength, size.height * squareLength, 0.f);
static_pointer_cast<CharucoBoardImpl>(impl)->calcNearestMarkerCorners();
}
Size CharucoBoard::getChessboardSize() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->size;
}
float CharucoBoard::getSquareLength() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->squareLength;
}
float CharucoBoard::getMarkerLength() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->markerLength;
}
bool CharucoBoard::checkCharucoCornersCollinear(InputArray charucoIds) const {
CV_Assert(impl);
unsigned int nCharucoCorners = (unsigned int)charucoIds.getMat().total();
if (nCharucoCorners <= 2)
return true;
// only test if there are 3 or more corners
auto board = static_pointer_cast<CharucoBoardImpl>(impl);
CV_Assert(board->chessboardCorners.size() >= charucoIds.getMat().total());
Vec<double, 3> point0(board->chessboardCorners[charucoIds.getMat().at<int>(0)].x,
board->chessboardCorners[charucoIds.getMat().at<int>(0)].y, 1);
Vec<double, 3> point1(board->chessboardCorners[charucoIds.getMat().at<int>(1)].x,
board->chessboardCorners[charucoIds.getMat().at<int>(1)].y, 1);
// create a line from the first two points.
Vec<double, 3> testLine = point0.cross(point1);
Vec<double, 3> testPoint(0, 0, 1);
double divisor = sqrt(testLine[0]*testLine[0] + testLine[1]*testLine[1]);
CV_Assert(divisor != 0.0);
// normalize the line with normal
testLine /= divisor;
double dotProduct;
for (unsigned int i = 2; i < nCharucoCorners; i++){
testPoint(0) = board->chessboardCorners[charucoIds.getMat().at<int>(i)].x;
testPoint(1) = board->chessboardCorners[charucoIds.getMat().at<int>(i)].y;
// if testPoint is on testLine, dotProduct will be zero (or very, very close)
dotProduct = testPoint.dot(testLine);
if (std::abs(dotProduct) > 1e-6){
return false;
}
}
// no points found that were off of testLine, return true that all points collinear.
return true;
}
std::vector<Point3f> CharucoBoard::getChessboardCorners() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->chessboardCorners;
}
std::vector<std::vector<int> > CharucoBoard::getNearestMarkerIdx() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->nearestMarkerIdx;
}
std::vector<std::vector<int> > CharucoBoard::getNearestMarkerCorners() const {
CV_Assert(impl);
return static_pointer_cast<CharucoBoardImpl>(impl)->nearestMarkerCorners;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,442 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "../precomp.hpp"
#include "opencv2/core/hal/hal.hpp"
#include "aruco_utils.hpp"
#include "predefined_dictionaries.hpp"
#include "apriltag/predefined_dictionaries_apriltag.hpp"
#include <opencv2/objdetect/aruco_dictionary.hpp>
namespace cv {
namespace aruco {
using namespace std;
Dictionary::Dictionary(): markerSize(0), maxCorrectionBits(0) {}
Dictionary::Dictionary(const Mat &_bytesList, int _markerSize, int _maxcorr) {
markerSize = _markerSize;
maxCorrectionBits = _maxcorr;
bytesList = _bytesList;
}
bool Dictionary::readDictionary(const cv::FileNode& fn) {
int nMarkers = 0, _markerSize = 0;
if (fn.empty() || !readParameter("nmarkers", nMarkers, fn) || !readParameter("markersize", _markerSize, fn))
return false;
Mat bytes(0, 0, CV_8UC1), marker(_markerSize, _markerSize, CV_8UC1);
std::string markerString;
for (int i = 0; i < nMarkers; i++) {
std::ostringstream ostr;
ostr << i;
if (!readParameter("marker_" + ostr.str(), markerString, fn))
return false;
for (int j = 0; j < (int) markerString.size(); j++)
marker.at<unsigned char>(j) = (markerString[j] == '0') ? 0 : 1;
bytes.push_back(Dictionary::getByteListFromBits(marker));
}
int _maxCorrectionBits = 0;
readParameter("maxCorrectionBits", _maxCorrectionBits, fn);
*this = Dictionary(bytes, _markerSize, _maxCorrectionBits);
return true;
}
void Dictionary::writeDictionary(FileStorage& fs, const String &name)
{
CV_Assert(fs.isOpened());
if (!name.empty())
fs << name << "{";
fs << "nmarkers" << bytesList.rows;
fs << "markersize" << markerSize;
fs << "maxCorrectionBits" << maxCorrectionBits;
for (int i = 0; i < bytesList.rows; i++) {
Mat row = bytesList.row(i);;
Mat bitMarker = getBitsFromByteList(row, markerSize);
std::ostringstream ostr;
ostr << i;
string markerName = "marker_" + ostr.str();
string marker;
for (int j = 0; j < markerSize * markerSize; j++)
marker.push_back(bitMarker.at<uint8_t>(j) + '0');
fs << markerName << marker;
}
if (!name.empty())
fs << "}";
}
bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double maxCorrectionRate) const {
CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize);
int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate);
// get as a byte list
Mat candidateBytes = getByteListFromBits(onlyBits);
idx = -1; // by default, not found
// search closest marker in dict
for(int m = 0; m < bytesList.rows; m++) {
int currentMinDistance = markerSize * markerSize + 1;
int currentRotation = -1;
for(unsigned int r = 0; r < 4; r++) {
int currentHamming = cv::hal::normHamming(
bytesList.ptr(m)+r*candidateBytes.cols,
candidateBytes.ptr(),
candidateBytes.cols);
if(currentHamming < currentMinDistance) {
currentMinDistance = currentHamming;
currentRotation = r;
}
}
// if maxCorrection is fulfilled, return this one
if(currentMinDistance <= maxCorrectionRecalculed) {
idx = m;
rotation = currentRotation;
break;
}
}
return idx != -1;
}
int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const {
CV_Assert(id >= 0 && id < bytesList.rows);
unsigned int nRotations = 4;
if(!allRotations) nRotations = 1;
Mat candidateBytes = getByteListFromBits(bits.getMat());
int currentMinDistance = int(bits.total() * bits.total());
for(unsigned int r = 0; r < nRotations; r++) {
int currentHamming = cv::hal::normHamming(
bytesList.ptr(id) + r*candidateBytes.cols,
candidateBytes.ptr(),
candidateBytes.cols);
if(currentHamming < currentMinDistance) {
currentMinDistance = currentHamming;
}
}
return currentMinDistance;
}
void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits) const {
CV_Assert(sidePixels >= (markerSize + 2*borderBits));
CV_Assert(id < bytesList.rows);
CV_Assert(borderBits > 0);
_img.create(sidePixels, sidePixels, CV_8UC1);
// create small marker with 1 pixel per bin
Mat tinyMarker(markerSize + 2 * borderBits, markerSize + 2 * borderBits, CV_8UC1,
Scalar::all(0));
Mat innerRegion = tinyMarker.rowRange(borderBits, tinyMarker.rows - borderBits)
.colRange(borderBits, tinyMarker.cols - borderBits);
// put inner bits
Mat bits = 255 * getBitsFromByteList(bytesList.rowRange(id, id + 1), markerSize);
CV_Assert(innerRegion.total() == bits.total());
bits.copyTo(innerRegion);
// resize tiny marker to output size
cv::resize(tinyMarker, _img.getMat(), _img.getMat().size(), 0, 0, INTER_NEAREST);
}
Mat Dictionary::getByteListFromBits(const Mat &bits) {
// integer ceil
int nbytes = (bits.cols * bits.rows + 8 - 1) / 8;
Mat candidateByteList(1, nbytes, CV_8UC4, Scalar::all(0));
unsigned char currentBit = 0;
int currentByte = 0;
// the 4 rotations
uchar* rot0 = candidateByteList.ptr();
uchar* rot1 = candidateByteList.ptr() + 1*nbytes;
uchar* rot2 = candidateByteList.ptr() + 2*nbytes;
uchar* rot3 = candidateByteList.ptr() + 3*nbytes;
for(int row = 0; row < bits.rows; row++) {
for(int col = 0; col < bits.cols; col++) {
// circular shift
rot0[currentByte] <<= 1;
rot1[currentByte] <<= 1;
rot2[currentByte] <<= 1;
rot3[currentByte] <<= 1;
// set bit
rot0[currentByte] |= bits.at<uchar>(row, col);
rot1[currentByte] |= bits.at<uchar>(col, bits.cols - 1 - row);
rot2[currentByte] |= bits.at<uchar>(bits.rows - 1 - row, bits.cols - 1 - col);
rot3[currentByte] |= bits.at<uchar>(bits.rows - 1 - col, row);
currentBit++;
if(currentBit == 8) {
// next byte
currentBit = 0;
currentByte++;
}
}
}
return candidateByteList;
}
Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize) {
CV_Assert(byteList.total() > 0 &&
byteList.total() >= (unsigned int)markerSize * markerSize / 8 &&
byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1);
Mat bits(markerSize, markerSize, CV_8UC1, Scalar::all(0));
unsigned char base2List[] = { 128, 64, 32, 16, 8, 4, 2, 1 };
int currentByteIdx = 0;
// we only need the bytes in normal rotation
unsigned char currentByte = byteList.ptr()[0];
int currentBit = 0;
for(int row = 0; row < bits.rows; row++) {
for(int col = 0; col < bits.cols; col++) {
if(currentByte >= base2List[currentBit]) {
bits.at<unsigned char>(row, col) = 1;
currentByte -= base2List[currentBit];
}
currentBit++;
if(currentBit == 8) {
currentByteIdx++;
currentByte = byteList.ptr()[currentByteIdx];
// if not enough bits for one more byte, we are in the end
// update bit position accordingly
if(8 * (currentByteIdx + 1) > (int)bits.total())
currentBit = 8 * (currentByteIdx + 1) - (int)bits.total();
else
currentBit = 0; // ok, bits enough for next byte
}
}
}
return bits;
}
Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
// DictionaryData constructors calls
// moved out of globals so construted on first use, which allows lazy-loading of opencv dll
static const Dictionary DICT_ARUCO_DATA = Dictionary(Mat(1024, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_BYTES), 5, 0);
static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 0);
static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 6);
static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 4);
static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 9);
static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 6);
static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, 0);
static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, 0);
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
switch(name) {
case DICT_ARUCO_ORIGINAL:
return Dictionary(DICT_ARUCO_DATA);
case DICT_4X4_50:
return Dictionary(DICT_4X4_50_DATA);
case DICT_4X4_100:
return Dictionary(DICT_4X4_100_DATA);
case DICT_4X4_250:
return Dictionary(DICT_4X4_250_DATA);
case DICT_4X4_1000:
return Dictionary(DICT_4X4_1000_DATA);
case DICT_5X5_50:
return Dictionary(DICT_5X5_50_DATA);
case DICT_5X5_100:
return Dictionary(DICT_5X5_100_DATA);
case DICT_5X5_250:
return Dictionary(DICT_5X5_250_DATA);
case DICT_5X5_1000:
return Dictionary(DICT_5X5_1000_DATA);
case DICT_6X6_50:
return Dictionary(DICT_6X6_50_DATA);
case DICT_6X6_100:
return Dictionary(DICT_6X6_100_DATA);
case DICT_6X6_250:
return Dictionary(DICT_6X6_250_DATA);
case DICT_6X6_1000:
return Dictionary(DICT_6X6_1000_DATA);
case DICT_7X7_50:
return Dictionary(DICT_7X7_50_DATA);
case DICT_7X7_100:
return Dictionary(DICT_7X7_100_DATA);
case DICT_7X7_250:
return Dictionary(DICT_7X7_250_DATA);
case DICT_7X7_1000:
return Dictionary(DICT_7X7_1000_DATA);
case DICT_APRILTAG_16h5:
return Dictionary(DICT_APRILTAG_16h5_DATA);
case DICT_APRILTAG_25h9:
return Dictionary(DICT_APRILTAG_25h9_DATA);
case DICT_APRILTAG_36h10:
return Dictionary(DICT_APRILTAG_36h10_DATA);
case DICT_APRILTAG_36h11:
return Dictionary(DICT_APRILTAG_36h11_DATA);
}
return Dictionary(DICT_4X4_50_DATA);
}
Dictionary getPredefinedDictionary(int dict) {
return getPredefinedDictionary(PredefinedDictionaryType(dict));
}
/**
* @brief Generates a random marker Mat of size markerSize x markerSize
*/
static Mat _generateRandomMarker(int markerSize, RNG &rng) {
Mat marker(markerSize, markerSize, CV_8UC1, Scalar::all(0));
for(int i = 0; i < markerSize; i++) {
for(int j = 0; j < markerSize; j++) {
unsigned char bit = (unsigned char) (rng.uniform(0,2));
marker.at<unsigned char>(i, j) = bit;
}
}
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.
* See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
* "Automatic generation and detection of highly reliable fiducial markers under occlusion".
* Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
*/
static int _getSelfDistance(const Mat &marker) {
Mat bytes = Dictionary::getByteListFromBits(marker);
int minHamming = (int)marker.total() + 1;
for(int r = 1; r < 4; r++) {
int currentHamming = cv::hal::normHamming(bytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols);
if(currentHamming < minHamming) minHamming = currentHamming;
}
return minHamming;
}
Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary, int randomSeed) {
RNG rng((uint64)(randomSeed));
Dictionary out = Dictionary(Mat(), markerSize);
out.markerSize = markerSize;
// theoretical maximum intermarker distance
// See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014.
// "Automatic generation and detection of highly reliable fiducial markers under occlusion".
// Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005
int C = (int)std::floor(float(markerSize * markerSize) / 4.f);
int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f);
// if baseDictionary is provided, calculate its intermarker distance
if(baseDictionary.bytesList.rows > 0) {
CV_Assert(baseDictionary.markerSize == markerSize);
out.bytesList = baseDictionary.bytesList.clone();
int minDistance = markerSize * markerSize + 1;
for(int i = 0; i < out.bytesList.rows; i++) {
Mat markerBytes = out.bytesList.rowRange(i, i + 1);
Mat markerBits = Dictionary::getBitsFromByteList(markerBytes, markerSize);
minDistance = min(minDistance, _getSelfDistance(markerBits));
for(int j = i + 1; j < out.bytesList.rows; j++) {
minDistance = min(minDistance, out.getDistanceToId(markerBits, j));
}
}
tau = minDistance;
}
// current best option
int bestTau = 0;
Mat bestMarker;
// after these number of unproductive iterations, the best option is accepted
const int maxUnproductiveIterations = 5000;
int unproductiveIterations = 0;
while(out.bytesList.rows < nMarkers) {
Mat currentMarker = _generateRandomMarker(markerSize, rng);
int selfDistance = _getSelfDistance(currentMarker);
int minDistance = selfDistance;
// if self distance is better or equal than current best option, calculate distance
// to previous accepted markers
if(selfDistance >= bestTau) {
for(int i = 0; i < out.bytesList.rows; i++) {
int currentDistance = out.getDistanceToId(currentMarker, i);
minDistance = min(currentDistance, minDistance);
if(minDistance <= bestTau) {
break;
}
}
}
// if distance is high enough, accept the marker
if(minDistance >= tau) {
unproductiveIterations = 0;
bestTau = 0;
Mat bytes = Dictionary::getByteListFromBits(currentMarker);
out.bytesList.push_back(bytes);
} else {
unproductiveIterations++;
// if distance is not enough, but is better than the current best option
if(minDistance > bestTau) {
bestTau = minDistance;
bestMarker = currentMarker;
}
// if number of unproductive iterarions has been reached, accept the current best option
if(unproductiveIterations == maxUnproductiveIterations) {
unproductiveIterations = 0;
tau = bestTau;
bestTau = 0;
Mat bytes = Dictionary::getByteListFromBits(bestMarker);
out.bytesList.push_back(bytes);
}
}
}
// update the maximum number of correction bits for the generated dictionary
out.maxCorrectionBits = (tau - 1) / 2;
return out;
}
}
}
@@ -0,0 +1,50 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "../precomp.hpp"
#include "aruco_utils.hpp"
namespace cv {
namespace aruco {
using namespace std;
void _copyVector2Output(vector<vector<Point2f> > &vec, OutputArrayOfArrays out, const float scale) {
out.create((int)vec.size(), 1, CV_32FC2);
if(out.isMatVector()) {
for (unsigned int i = 0; i < vec.size(); i++) {
out.create(4, 1, CV_32FC2, i);
Mat &m = out.getMatRef(i);
Mat(Mat(vec[i]).t()*scale).copyTo(m);
}
}
else if(out.isUMatVector()) {
for (unsigned int i = 0; i < vec.size(); i++) {
out.create(4, 1, CV_32FC2, i);
UMat &m = out.getUMatRef(i);
Mat(Mat(vec[i]).t()*scale).copyTo(m);
}
}
else if(out.kind() == _OutputArray::STD_VECTOR_VECTOR){
for (unsigned int i = 0; i < vec.size(); i++) {
out.create(4, 1, CV_32FC2, i);
Mat m = out.getMat(i);
Mat(Mat(vec[i]).t()*scale).copyTo(m);
}
}
else {
CV_Error(cv::Error::StsNotImplemented,
"Only Mat vector, UMat vector, and vector<vector> OutputArrays are currently supported.");
}
}
void _convertToGrey(InputArray _in, OutputArray _out) {
CV_Assert(_in.type() == CV_8UC1 || _in.type() == CV_8UC3);
if(_in.type() == CV_8UC3)
cvtColor(_in, _out, COLOR_BGR2GRAY);
else
_in.copyTo(_out);
}
}
}
@@ -0,0 +1,45 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef __OPENCV_OBJDETECT_ARUCO_UTILS_HPP__
#define __OPENCV_OBJDETECT_ARUCO_UTILS_HPP__
#include <opencv2/core.hpp>
#include <vector>
namespace cv {
namespace aruco {
/**
* @brief Copy the contents of a corners vector to an OutputArray, settings its size.
*/
void _copyVector2Output(std::vector<std::vector<Point2f> > &vec, OutputArrayOfArrays out, const float scale = 1.f);
/**
* @brief Convert input image to gray if it is a 3-channels image
*/
void _convertToGrey(InputArray _in, OutputArray _out);
template<typename T>
inline bool readParameter(const std::string& name, T& parameter, const FileNode& node)
{
if (!node.empty() && !node[name].empty()) {
node[name] >> parameter;
return true;
}
return false;
}
template<typename T>
inline bool readWriteParameter(const std::string& name, T& parameter, const FileNode* readNode, FileStorage* writeStorage)
{
if (readNode)
return readParameter(name, parameter, *readNode);
CV_Assert(writeStorage);
*writeStorage << name << parameter;
return true;
}
}
}
#endif
@@ -0,0 +1,521 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "../precomp.hpp"
#include <opencv2/3d.hpp>
#include "opencv2/objdetect/charuco_detector.hpp"
#include "aruco_utils.hpp"
namespace cv {
namespace aruco {
using namespace std;
struct CharucoDetector::CharucoDetectorImpl {
CharucoBoard board;
CharucoParameters charucoParameters;
ArucoDetector arucoDetector;
CharucoDetectorImpl(const CharucoBoard& _board, const CharucoParameters _charucoParameters,
const ArucoDetector& _arucoDetector): board(_board), charucoParameters(_charucoParameters),
arucoDetector(_arucoDetector)
{}
/** Calculate the maximum window sizes for corner refinement for each charuco corner based on the distance
* to their closest markers */
vector<Size> getMaximumSubPixWindowSizes(InputArrayOfArrays markerCorners, InputArray markerIds,
InputArray charucoCorners) {
size_t nCharucoCorners = charucoCorners.getMat().total();
CV_Assert(board.getNearestMarkerIdx().size() == nCharucoCorners);
vector<Size> winSizes(nCharucoCorners, Size(-1, -1));
for(size_t i = 0ull; i < nCharucoCorners; i++) {
if(charucoCorners.getMat().at<Point2f>((int)i) == Point2f(-1.f, -1.f)) continue;
if(board.getNearestMarkerIdx()[i].empty()) continue;
double minDist = -1;
int counter = 0;
// calculate the distance to each of the closest corner of each closest marker
for(size_t j = 0; j < board.getNearestMarkerIdx()[i].size(); j++) {
// find marker
int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]];
int markerIdx = -1;
for(size_t k = 0; k < markerIds.getMat().total(); k++) {
if(markerIds.getMat().at<int>((int)k) == markerId) {
markerIdx = (int)k;
break;
}
}
if(markerIdx == -1) continue;
Point2f markerCorner =
markerCorners.getMat(markerIdx).at<Point2f>(board.getNearestMarkerCorners()[i][j]);
Point2f charucoCorner = charucoCorners.getMat().at<Point2f>((int)i);
double dist = norm(markerCorner - charucoCorner);
if(minDist == -1) minDist = dist; // if first distance, just assign it
minDist = min(dist, minDist);
counter++;
}
// if this is the first closest marker, dont do anything
if(counter == 0)
continue;
else {
// else, calculate the maximum window size
int winSizeInt = int(minDist - 2); // remove 2 pixels for safety
if(winSizeInt < 1) winSizeInt = 1; // minimum size is 1
if(winSizeInt > 10) winSizeInt = 10; // maximum size is 10
winSizes[i] = Size(winSizeInt, winSizeInt);
}
}
return winSizes;
}
/** @brief From all projected chessboard corners, select those inside the image and apply subpixel refinement */
void selectAndRefineChessboardCorners(InputArray allCorners, InputArray image, OutputArray selectedCorners,
OutputArray selectedIds, const vector<Size> &winSizes) {
const int minDistToBorder = 2; // minimum distance of the corner to the image border
// remaining corners, ids and window refinement sizes after removing corners outside the image
vector<Point2f> filteredChessboardImgPoints;
vector<Size> filteredWinSizes;
vector<int> filteredIds;
// filter corners outside the image
Rect innerRect(minDistToBorder, minDistToBorder, image.getMat().cols - 2 * minDistToBorder,
image.getMat().rows - 2 * minDistToBorder);
for(unsigned int i = 0; i < allCorners.getMat().total(); i++) {
if(innerRect.contains(allCorners.getMat().at<Point2f>(i))) {
filteredChessboardImgPoints.push_back(allCorners.getMat().at<Point2f>(i));
filteredIds.push_back(i);
filteredWinSizes.push_back(winSizes[i]);
}
}
// if none valid, return 0
if(filteredChessboardImgPoints.empty()) return;
// corner refinement, first convert input image to grey
Mat grey;
if(image.type() == CV_8UC3)
cvtColor(image, grey, COLOR_BGR2GRAY);
else
grey = image.getMat();
//// For each of the charuco corners, apply subpixel refinement using its correspondind winSize
parallel_for_(Range(0, (int)filteredChessboardImgPoints.size()), [&](const Range& range) {
const int begin = range.start;
const int end = range.end;
for (int i = begin; i < end; i++) {
vector<Point2f> in;
in.push_back(filteredChessboardImgPoints[i] - Point2f(0.5, 0.5)); // adjust sub-pixel coordinates for cornerSubPix
Size winSize = filteredWinSizes[i];
if (winSize.height == -1 || winSize.width == -1)
winSize = Size(arucoDetector.getDetectorParameters().cornerRefinementWinSize,
arucoDetector.getDetectorParameters().cornerRefinementWinSize);
cornerSubPix(grey, in, winSize, Size(),
TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS,
arucoDetector.getDetectorParameters().cornerRefinementMaxIterations,
arucoDetector.getDetectorParameters().cornerRefinementMinAccuracy));
filteredChessboardImgPoints[i] = in[0] + Point2f(0.5, 0.5);
}
});
// parse output
Mat(filteredChessboardImgPoints).copyTo(selectedCorners);
Mat(filteredIds).copyTo(selectedIds);
}
/** Interpolate charuco corners using approximated pose estimation */
void interpolateCornersCharucoApproxCalib(InputArrayOfArrays markerCorners, InputArray markerIds,
InputArray image, OutputArray charucoCorners, OutputArray charucoIds) {
CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3);
CV_Assert(markerCorners.total() == markerIds.getMat().total());
// approximated pose estimation using marker corners
Mat approximatedRvec, approximatedTvec;
Mat objPoints, imgPoints; // object and image points for the solvePnP function
printf("before board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);\n");
board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);
printf("after board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);\n");
if (objPoints.total() < 4ull) // need, at least, 4 corners
return;
solvePnP(objPoints, imgPoints, charucoParameters.cameraMatrix, charucoParameters.distCoeffs, approximatedRvec, approximatedTvec);
printf("after solvePnP\n");
// project chessboard corners
vector<Point2f> allChessboardImgPoints;
projectPoints(board.getChessboardCorners(), approximatedRvec, approximatedTvec, charucoParameters.cameraMatrix,
charucoParameters.distCoeffs, allChessboardImgPoints);
printf("after projectPoints\n");
// calculate maximum window sizes for subpixel refinement. The size is limited by the distance
// to the closes marker corner to avoid erroneous displacements to marker corners
vector<Size> subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, allChessboardImgPoints);
// filter corners outside the image and subpixel-refine charuco corners
printf("before selectAndRefineChessboardCorners\n");
selectAndRefineChessboardCorners(allChessboardImgPoints, image, charucoCorners, charucoIds, subPixWinSizes);
}
/** Interpolate charuco corners using local homography */
void interpolateCornersCharucoLocalHom(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray image,
OutputArray charucoCorners, OutputArray charucoIds) {
CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3);
CV_Assert(markerCorners.total() == markerIds.getMat().total());
size_t nMarkers = markerIds.getMat().total();
// calculate local homographies for each marker
vector<Mat> transformations(nMarkers);
vector<bool> validTransform(nMarkers, false);
const auto& ids = board.getIds();
for(size_t i = 0ull; i < nMarkers; i++) {
vector<Point2f> markerObjPoints2D;
int markerId = markerIds.getMat().at<int>((int)i);
auto it = find(ids.begin(), ids.end(), markerId);
if(it == ids.end()) continue;
auto boardIdx = it - ids.begin();
markerObjPoints2D.resize(4ull);
for(size_t j = 0ull; j < 4ull; j++)
markerObjPoints2D[j] =
Point2f(board.getObjPoints()[boardIdx][j].x, board.getObjPoints()[boardIdx][j].y);
transformations[i] = getPerspectiveTransform(markerObjPoints2D, markerCorners.getMat((int)i));
// set transform as valid if transformation is non-singular
double det = determinant(transformations[i]);
validTransform[i] = std::abs(det) > 1e-6;
}
size_t nCharucoCorners = (size_t)board.getChessboardCorners().size();
vector<Point2f> allChessboardImgPoints(nCharucoCorners, Point2f(-1, -1));
// for each charuco corner, calculate its interpolation position based on the closest markers
// homographies
for(size_t i = 0ull; i < nCharucoCorners; i++) {
Point2f objPoint2D = Point2f(board.getChessboardCorners()[i].x, board.getChessboardCorners()[i].y);
vector<Point2f> interpolatedPositions;
for(size_t j = 0ull; j < board.getNearestMarkerIdx()[i].size(); j++) {
int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]];
int markerIdx = -1;
for(size_t k = 0ull; k < markerIds.getMat().total(); k++) {
if(markerIds.getMat().at<int>((int)k) == markerId) {
markerIdx = (int)k;
break;
}
}
if (markerIdx != -1 &&
validTransform[markerIdx])
{
vector<Point2f> in, out;
in.push_back(objPoint2D);
perspectiveTransform(in, out, transformations[markerIdx]);
interpolatedPositions.push_back(out[0]);
}
}
// none of the closest markers detected
if(interpolatedPositions.empty()) continue;
// more than one closest marker detected, take middle point
if(interpolatedPositions.size() > 1ull) {
allChessboardImgPoints[i] = (interpolatedPositions[0] + interpolatedPositions[1]) / 2.;
}
// a single closest marker detected
else allChessboardImgPoints[i] = interpolatedPositions[0];
}
// calculate maximum window sizes for subpixel refinement. The size is limited by the distance
// to the closes marker corner to avoid erroneous displacements to marker corners
vector<Size> subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, allChessboardImgPoints);
// filter corners outside the image and subpixel-refine charuco corners
selectAndRefineChessboardCorners(allChessboardImgPoints, image, charucoCorners, charucoIds, subPixWinSizes);
}
/** Remove charuco corners if any of their minMarkers closest markers has not been detected */
int filterCornersWithoutMinMarkers(InputArray _allCharucoCorners, InputArray allCharucoIds, InputArray allArucoIds,
OutputArray _filteredCharucoCorners, OutputArray _filteredCharucoIds) {
CV_Assert(charucoParameters.minMarkers >= 0 && charucoParameters.minMarkers <= 2);
vector<Point2f> filteredCharucoCorners;
vector<int> filteredCharucoIds;
// for each charuco corner
for(unsigned int i = 0; i < allCharucoIds.getMat().total(); i++) {
int currentCharucoId = allCharucoIds.getMat().at<int>(i);
int totalMarkers = 0; // nomber of closest marker detected
// look for closest markers
for(unsigned int m = 0; m < board.getNearestMarkerIdx()[currentCharucoId].size(); m++) {
int markerId = board.getIds()[board.getNearestMarkerIdx()[currentCharucoId][m]];
bool found = false;
for(unsigned int k = 0; k < allArucoIds.getMat().total(); k++) {
if(allArucoIds.getMat().at<int>(k) == markerId) {
found = true;
break;
}
}
if(found) totalMarkers++;
}
// if enough markers detected, add the charuco corner to the final list
if(totalMarkers >= charucoParameters.minMarkers) {
filteredCharucoIds.push_back(currentCharucoId);
filteredCharucoCorners.push_back(_allCharucoCorners.getMat().at<Point2f>(i));
}
}
// parse output
Mat(filteredCharucoCorners).copyTo(_filteredCharucoCorners);
Mat(filteredCharucoIds).copyTo(_filteredCharucoIds);
return (int)_filteredCharucoIds.total();
}
};
CharucoDetector::CharucoDetector(const CharucoBoard &board, const CharucoParameters &charucoParams,
const DetectorParameters &detectorParams, const RefineParameters& refineParams) {
this->charucoDetectorImpl = makePtr<CharucoDetectorImpl>(board, charucoParams, ArucoDetector(board.getDictionary(), detectorParams, refineParams));
}
const CharucoBoard& CharucoDetector::getBoard() const {
return charucoDetectorImpl->board;
}
void CharucoDetector::setBoard(const CharucoBoard& board) {
this->charucoDetectorImpl->board = board;
charucoDetectorImpl->arucoDetector.setDictionary(board.getDictionary());
}
const CharucoParameters &CharucoDetector::getCharucoParameters() const {
return charucoDetectorImpl->charucoParameters;
}
void CharucoDetector::setCharucoParameters(CharucoParameters &charucoParameters) {
charucoDetectorImpl->charucoParameters = charucoParameters;
}
const DetectorParameters& CharucoDetector::getDetectorParameters() const {
return charucoDetectorImpl->arucoDetector.getDetectorParameters();
}
void CharucoDetector::setDetectorParameters(const DetectorParameters& detectorParameters) {
charucoDetectorImpl->arucoDetector.setDetectorParameters(detectorParameters);
}
const RefineParameters& CharucoDetector::getRefineParameters() const {
return charucoDetectorImpl->arucoDetector.getRefineParameters();
}
void CharucoDetector::setRefineParameters(const RefineParameters& refineParameters) {
charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters);
}
void CharucoDetector::detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds,
InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) const {
CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.size() == markerIds.size()));
vector<vector<Point2f>> tmpMarkerCorners;
vector<int> tmpMarkerIds;
InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners;
InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds;
if (markerCorners.empty() && markerIds.empty()) {
vector<vector<Point2f> > rejectedMarkers;
charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds, rejectedMarkers);
if (charucoDetectorImpl->charucoParameters.tryRefineMarkers)
charucoDetectorImpl->arucoDetector.refineDetectedMarkers(image, charucoDetectorImpl->board, _markerCorners,
_markerIds, rejectedMarkers);
}
// if camera parameters are avaible, use approximated calibration
if(!charucoDetectorImpl->charucoParameters.cameraMatrix.empty())
charucoDetectorImpl->interpolateCornersCharucoApproxCalib(_markerCorners, _markerIds, image, charucoCorners,
charucoIds);
// else use local homography
else
charucoDetectorImpl->interpolateCornersCharucoLocalHom(_markerCorners, _markerIds, image, charucoCorners,
charucoIds);
// to return a charuco corner, its closest aruco markers should have been detected
charucoDetectorImpl->filterCornersWithoutMinMarkers(charucoCorners, charucoIds, _markerIds, charucoCorners,
charucoIds);
}
void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diamondCorners, OutputArray _diamondIds,
InputOutputArrayOfArrays inMarkerCorners, InputOutputArrayOfArrays inMarkerIds) const {
CV_Assert(getBoard().getChessboardSize() == Size(3, 3));
CV_Assert((inMarkerCorners.empty() && inMarkerIds.empty() && !image.empty()) || (inMarkerCorners.size() == inMarkerIds.size()));
vector<vector<Point2f>> tmpMarkerCorners;
vector<int> tmpMarkerIds;
InputOutputArrayOfArrays _markerCorners = inMarkerCorners.needed() ? inMarkerCorners : tmpMarkerCorners;
InputOutputArray _markerIds = inMarkerIds.needed() ? inMarkerIds : tmpMarkerIds;
if (_markerCorners.empty() && _markerIds.empty()) {
charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds);
}
const float minRepDistanceRate = 1.302455f;
vector<vector<Point2f>> diamondCorners;
vector<Vec4i> diamondIds;
// stores if the detected markers have been assigned or not to a diamond
vector<bool> assigned(_markerIds.total(), false);
if(_markerIds.total() < 4ull) return; // a diamond need at least 4 markers
// convert input image to grey
Mat grey;
if(image.type() == CV_8UC3)
cvtColor(image, grey, COLOR_BGR2GRAY);
else
grey = image.getMat();
auto board = getBoard();
// for each of the detected markers, try to find a diamond
for(unsigned int i = 0; i < (unsigned int)_markerIds.total(); i++) {
if(assigned[i]) continue;
// calculate marker perimeter
float perimeterSq = 0;
Mat corners = _markerCorners.getMat(i);
for(int c = 0; c < 4; c++) {
Point2f edge = corners.at<Point2f>(c) - corners.at<Point2f>((c + 1) % 4);
perimeterSq += edge.x*edge.x + edge.y*edge.y;
}
// maximum reprojection error relative to perimeter
float minRepDistance = sqrt(perimeterSq) * minRepDistanceRate;
int currentId = _markerIds.getMat().at<int>(i);
// prepare data to call refineDetectedMarkers()
// detected markers (only the current one)
vector<Mat> currentMarker;
vector<int> currentMarkerId;
currentMarker.push_back(_markerCorners.getMat(i));
currentMarkerId.push_back(currentId);
// marker candidates (the rest of markers if they have not been assigned)
vector<Mat> candidates;
vector<int> candidatesIdxs;
for(unsigned int k = 0; k < assigned.size(); k++) {
if(k == i) continue;
if(!assigned[k]) {
candidates.push_back(_markerCorners.getMat(k));
candidatesIdxs.push_back(k);
}
}
if(candidates.size() < 3ull) break; // we need at least 3 free markers
// modify charuco layout id to make sure all the ids are different than current id
vector<int> tmpIds(4ull);
for(int k = 1; k < 4; k++)
tmpIds[k] = currentId + 1 + k;
// current id is assigned to [0], so it is the marker on the top
tmpIds[0] = currentId;
// create Charuco board layout for diamond (3x3 layout)
charucoDetectorImpl->board = CharucoBoard(Size(3, 3), board.getSquareLength(),
board.getMarkerLength(), board.getDictionary(), tmpIds);
// try to find the rest of markers in the diamond
vector<int> acceptedIdxs;
if (currentMarker.size() != 4ull)
{
RefineParameters refineParameters(minRepDistance, -1.f, false);
RefineParameters tmp = charucoDetectorImpl->arucoDetector.getRefineParameters();
charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters);
charucoDetectorImpl->arucoDetector.refineDetectedMarkers(grey, getBoard(), currentMarker, currentMarkerId,
candidates,
noArray(), noArray(), acceptedIdxs);
charucoDetectorImpl->arucoDetector.setRefineParameters(tmp);
}
// if found, we have a diamond
if(currentMarker.size() == 4ull) {
assigned[i] = true;
// calculate diamond id, acceptedIdxs array indicates the markers taken from candidates array
Vec4i markerId;
markerId[0] = currentId;
for(int k = 1; k < 4; k++) {
int currentMarkerIdx = candidatesIdxs[acceptedIdxs[k - 1]];
markerId[k] = _markerIds.getMat().at<int>(currentMarkerIdx);
assigned[currentMarkerIdx] = true;
}
// interpolate the charuco corners of the diamond
vector<Point2f> currentMarkerCorners;
Mat aux;
detectBoard(grey, currentMarkerCorners, aux, currentMarker, currentMarkerId);
// if everything is ok, save the diamond
if(currentMarkerCorners.size() > 0ull) {
// reorder corners
vector<Point2f> currentMarkerCornersReorder;
currentMarkerCornersReorder.resize(4);
currentMarkerCornersReorder[0] = currentMarkerCorners[0];
currentMarkerCornersReorder[1] = currentMarkerCorners[1];
currentMarkerCornersReorder[2] = currentMarkerCorners[3];
currentMarkerCornersReorder[3] = currentMarkerCorners[2];
diamondCorners.push_back(currentMarkerCornersReorder);
diamondIds.push_back(markerId);
}
}
}
charucoDetectorImpl->board = board;
if(diamondIds.size() > 0ull) {
// parse output
Mat(diamondIds).copyTo(_diamondIds);
_diamondCorners.create((int)diamondCorners.size(), 1, CV_32FC2);
for(unsigned int i = 0; i < diamondCorners.size(); i++) {
_diamondCorners.create(4, 1, CV_32FC2, i, true);
for(int j = 0; j < 4; j++) {
_diamondCorners.getMat(i).at<Point2f>(j) = diamondCorners[i][j];
}
}
}
}
void drawDetectedCornersCharuco(InputOutputArray _image, InputArray _charucoCorners,
InputArray _charucoIds, Scalar cornerColor) {
CV_Assert(!_image.getMat().empty() &&
(_image.getMat().channels() == 1 || _image.getMat().channels() == 3));
CV_Assert((_charucoCorners.getMat().total() == _charucoIds.getMat().total()) ||
_charucoIds.getMat().total() == 0);
size_t nCorners = _charucoCorners.getMat().total();
for(size_t i = 0; i < nCorners; i++) {
Point2f corner = _charucoCorners.getMat().at<Point2f>((int)i);
// draw first corner mark
rectangle(_image, corner - Point2f(3, 3), corner + Point2f(3, 3), cornerColor, 1, LINE_AA);
// draw ID
if(!_charucoIds.empty()) {
int id = _charucoIds.getMat().at<int>((int)i);
stringstream s;
s << "id=" << id;
putText(_image, s.str(), corner + Point2f(5, -5), FONT_HERSHEY_SIMPLEX, 0.5,
cornerColor, 2);
}
}
}
void drawDetectedDiamonds(InputOutputArray _image, InputArrayOfArrays _corners, InputArray _ids, Scalar borderColor) {
CV_Assert(_image.getMat().total() != 0 &&
(_image.getMat().channels() == 1 || _image.getMat().channels() == 3));
CV_Assert((_corners.total() == _ids.total()) || _ids.total() == 0);
// calculate colors
Scalar textColor, cornerColor;
textColor = cornerColor = borderColor;
swap(textColor.val[0], textColor.val[1]); // text color just sawp G and R
swap(cornerColor.val[1], cornerColor.val[2]); // corner color just sawp G and B
int nMarkers = (int)_corners.total();
for(int i = 0; i < nMarkers; i++) {
Mat currentMarker = _corners.getMat(i);
CV_Assert(currentMarker.total() == 4 && currentMarker.type() == CV_32FC2);
// draw marker sides
for(int j = 0; j < 4; j++) {
Point2f p0, p1;
p0 = currentMarker.at< Point2f >(j);
p1 = currentMarker.at< Point2f >((j + 1) % 4);
line(_image, p0, p1, borderColor, 1);
}
// draw first corner mark
rectangle(_image, currentMarker.at< Point2f >(0) - Point2f(3, 3),
currentMarker.at< Point2f >(0) + Point2f(3, 3), cornerColor, 1, LINE_AA);
// draw id composed by four numbers
if(_ids.total() != 0) {
Point2f cent(0, 0);
for(int p = 0; p < 4; p++)
cent += currentMarker.at< Point2f >(p);
cent = cent / 4.;
stringstream s;
s << "id=" << _ids.getMat().at< Vec4i >(i);
putText(_image, s.str(), cent, FONT_HERSHEY_SIMPLEX, 0.5, textColor, 2);
}
}
}
}
}
File diff suppressed because it is too large Load Diff
+365 -41
View File
@@ -8,6 +8,7 @@
#include "precomp.hpp"
#include "opencv2/objdetect.hpp"
#include "opencv2/3d.hpp"
#include <opencv2/core/utils/logger.hpp>
#ifdef HAVE_QUIRC
#include "quirc.h"
@@ -15,7 +16,6 @@
#include <limits>
#include <cmath>
#include <iostream>
#include <queue>
#include <limits>
#include <map>
@@ -23,6 +23,7 @@
namespace cv
{
using std::vector;
using std::pair;
static bool checkQRInputImage(InputArray img, Mat& gray)
{
@@ -136,7 +137,7 @@ void QRDetect::init(const Mat& src, double eps_vertical_, double eps_horizontal_
const int width = cvRound(src.size().width * coeff_expansion);
const int height = cvRound(src.size().height * coeff_expansion);
Size new_size(width, height);
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
}
else if (min_side > 512.0)
{
@@ -524,7 +525,7 @@ bool QRDetect::localization()
const int height = cvRound(bin_barcode.size().height * coeff_expansion);
Size new_size(width, height);
Mat intermediate;
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
bin_barcode = intermediate.clone();
for (size_t i = 0; i < localization_points.size(); i++)
{
@@ -537,7 +538,7 @@ bool QRDetect::localization()
const int height = cvRound(bin_barcode.size().height / coeff_expansion);
Size new_size(width, height);
Mat intermediate;
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
bin_barcode = intermediate.clone();
for (size_t i = 0; i < localization_points.size(); i++)
{
@@ -948,6 +949,7 @@ vector<Point2f> QRDetect::getQuadrilateral(vector<Point2f> angle_list)
return result_angle_list;
}
struct QRCodeDetector::Impl
{
public:
@@ -955,9 +957,13 @@ public:
~Impl() {}
double epsX, epsY;
vector<vector<Point2f>> alignmentMarkers;
vector<Point2f> updateQrCorners;
bool useAlignmentMarkers = true;
};
QRCodeDetector::QRCodeDetector() : p(new Impl) {}
QRCodeDetector::~QRCodeDetector() {}
void QRCodeDetector::setEpsX(double epsX) { p->epsX = epsX; }
@@ -981,6 +987,7 @@ bool QRCodeDetector::detect(InputArray in, OutputArray points) const
class QRDecode
{
public:
QRDecode(bool useAlignmentMarkers);
void init(const Mat &src, const vector<Point2f> &points);
Mat getIntermediateBarcode() { return intermediate; }
Mat getStraightBarcode() { return straight; }
@@ -988,9 +995,23 @@ public:
std::string getDecodeInformation() { return result_info; }
bool straightDecodingProcess();
bool curvedDecodingProcess();
vector<Point2f> alignment_coords;
float coeff_expansion = 1.f;
vector<Point2f> getOriginalPoints() {return original_points;}
bool useAlignmentMarkers;
protected:
bool updatePerspective();
double getNumModules();
Mat getHomography() {
CV_TRACE_FUNCTION();
vector<Point2f> perspective_points = {{0.f, 0.f}, {test_perspective_size, 0.f},
{test_perspective_size, test_perspective_size},
{0.f, test_perspective_size}};
vector<Point2f> pts = original_points;
return findHomography(pts, perspective_points);
}
bool updatePerspective(const Mat& H);
bool versionDefinition();
void detectAlignment();
bool samplingForVersion();
bool decodingProcess();
inline double pointPosition(Point2f a, Point2f b , Point2f c);
@@ -1019,6 +1040,7 @@ protected:
const static int NUM_SIDES = 2;
Mat original, bin_barcode, no_border_intermediate, intermediate, straight, curved_to_straight, test_image;
vector<Point2f> original_points;
Mat homography;
vector<Point2f> original_curved_points;
vector<Point> qrcode_locations;
vector<std::pair<size_t, Point> > closest_points;
@@ -1070,6 +1092,7 @@ float static getMinSideLen(const vector<Point2f> &points) {
return static_cast<float>(res);
}
void QRDecode::init(const Mat &src, const vector<Point2f> &points)
{
CV_TRACE_FUNCTION();
@@ -2180,6 +2203,8 @@ bool QRDecode::straightenQRCodeInParts()
pts.push_back(center_point);
Mat H = findHomography(pts, perspective_points);
if (H.empty())
return false;
Mat temp_intermediate(temporary_size, CV_8UC1);
warpPerspective(test_mask, temp_intermediate, H, temporary_size, INTER_NEAREST);
perspective_result += temp_intermediate;
@@ -2212,6 +2237,8 @@ bool QRDecode::straightenQRCodeInParts()
perspective_straight_points.push_back(Point2f(perspective_curved_size * 0.5f, perspective_curved_size * 0.5f));
Mat H = findHomography(original_curved_points, perspective_straight_points);
if (H.empty())
return false;
warpPerspective(inversion, temp_result, H, temporary_size, INTER_NEAREST, BORDER_REPLICATE);
no_border_intermediate = temp_result(Range(1, temp_result.rows), Range(1, temp_result.cols));
@@ -2250,33 +2277,124 @@ bool QRDecode::preparingCurvedQRCodes()
return true;
}
bool QRDecode::updatePerspective()
{
CV_TRACE_FUNCTION();
const Point2f centerPt = intersectionLines(original_points[0], original_points[2],
original_points[1], original_points[3]);
if (cvIsNaN(centerPt.x) || cvIsNaN(centerPt.y))
/**
* @param finderPattern 4 points of finder pattern markers, calculated by findPatternsVerticesPoints()
* @return true if the pattern has the correct side lengths
*/
static inline bool checkFinderPatternByAspect(const vector<Point> &finderPattern) {
if (finderPattern.size() != 4ull)
return false;
float sidesLen[4];
for (size_t i = 0; i < finderPattern.size(); i++) {
sidesLen[i] = (sqrt(normL2Sqr<float>(Point2f(finderPattern[i] - finderPattern[(i+1ull)%finderPattern.size()]))));
}
const float maxSide = max(max(sidesLen[0], sidesLen[1]), max(sidesLen[2], sidesLen[3]));
const float minSide = min(min(sidesLen[0], sidesLen[1]), min(sidesLen[2], sidesLen[3]));
const Size temporary_size(cvRound(test_perspective_size), cvRound(test_perspective_size));
const float patternMaxRelativeLen = .3f;
if (1.f - minSide / maxSide > patternMaxRelativeLen)
return false;
return true;
}
vector<Point2f> perspective_points;
perspective_points.push_back(Point2f(0.f, 0.f));
perspective_points.push_back(Point2f(test_perspective_size, 0.f));
/**
* @param finderPattern - 4 points of finder pattern markers, calculated by findPatternsVerticesPoints()
* @param cornerPointsQR - 4 corner points of QR code
* @return pair<int, int> first - the index in points of finderPattern closest to the corner of the QR code,
* second - the index in points of cornerPointsQR closest to the corner of finderPattern
*
* This function matches finder patterns to the corners of the QR code. Points of finder pattern calculated by
* findPatternsVerticesPoints() may be erroneous, so they are checked.
*/
static inline std::pair<int, int> matchPatternPoints(const vector<Point> &finderPattern,
const vector<Point2f>& cornerPointsQR) {
if (!checkFinderPatternByAspect(finderPattern))
return std::make_pair(-1, -1);
perspective_points.push_back(Point2f(test_perspective_size, test_perspective_size));
perspective_points.push_back(Point2f(0.f, test_perspective_size));
float distanceToOrig = normL2Sqr<float>(Point2f(finderPattern[0]) - cornerPointsQR[0]);
int closestFinderPatternV = 0;
int closetOriginalV = 0;
perspective_points.push_back(Point2f(test_perspective_size * 0.5f, test_perspective_size * 0.5f));
for (size_t i = 0ull; i < finderPattern.size(); i++) {
for (size_t j = 0ull; j < cornerPointsQR.size(); j++) {
const float tmp = normL2Sqr<float>(Point2f(finderPattern[i]) - cornerPointsQR[j]);
if (tmp < distanceToOrig) {
distanceToOrig = tmp;
closestFinderPatternV = (int)i;
closetOriginalV = (int)j;
}
}
}
distanceToOrig = sqrt(distanceToOrig);
vector<Point2f> pts = original_points;
pts.push_back(centerPt);
// check that the distance from the QR pattern to the corners of the QR code is small
const float originalQrSide = sqrt(normL2Sqr<float>(cornerPointsQR[0] - cornerPointsQR[1]))*0.5f +
sqrt(normL2Sqr<float>(cornerPointsQR[0] - cornerPointsQR[3]))*0.5f;
const float maxRelativeDistance = .1f;
Mat H = findHomography(pts, perspective_points);
Mat bin_original;
adaptiveThreshold(original, bin_original, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 83, 2);
if (distanceToOrig/originalQrSide > maxRelativeDistance)
return std::make_pair(-1, -1);
return std::make_pair(closestFinderPatternV, closetOriginalV);
}
double QRDecode::getNumModules() {
vector<vector<Point>> finderPatterns;
double numModulesX = 0., numModulesY = 0.;
if (findPatternsVerticesPoints(finderPatterns)) {
double pattern_distance[4] = {0.,0.,0.,0.};
for (auto& pattern : finderPatterns) {
auto indexes = matchPatternPoints(pattern, original_points);
if (indexes == std::make_pair(-1, -1))
return 0.;
Point2f vf[4] = {pattern[indexes.first % 4], pattern[(1+indexes.first) % 4],
pattern[(2+indexes.first) % 4], pattern[(3+indexes.first) % 4]};
for (int i = 1; i < 4; i++) {
pattern_distance[indexes.second] += (norm(vf[i] - vf[i-1]));
}
pattern_distance[indexes.second] += norm(vf[3] - vf[0]);
pattern_distance[indexes.second] /= 4.;
}
const double moduleSizeX = (pattern_distance[0] + pattern_distance[1])/(2.*7.);
const double moduleSizeY = (pattern_distance[0] + pattern_distance[3])/(2.*7.);
numModulesX = norm(original_points[1] - original_points[0])/moduleSizeX;
numModulesY = norm(original_points[3] - original_points[0])/moduleSizeY;
}
return (numModulesX + numModulesY)/2.;
}
// use code from https://stackoverflow.com/questions/13238704/calculating-the-position-of-qr-code-alignment-patterns
static inline vector<pair<int, int>> getAlignmentCoordinates(int version) {
if (version <= 1) return {};
int intervals = (version / 7) + 1; // Number of gaps between alignment patterns
int distance = 4 * version + 4; // Distance between first and last alignment pattern
int step = cvRound((double)distance / (double)intervals); // Round equal spacing to nearest integer
step += step & 0b1; // Round step to next even number
vector<int> coordinates((size_t)intervals + 1ull);
coordinates[0] = 6; // First coordinate is always 6 (can't be calculated with step)
for (int i = 1; i <= intervals; i++) {
coordinates[i] = (6 + distance - step * (intervals - i)); // Start right/bottom and go left/up by step*k
}
if (version >= 7) {
return {std::make_pair(coordinates.back(), coordinates.back()),
std::make_pair(coordinates.back(), coordinates[coordinates.size()-2]),
std::make_pair(coordinates[coordinates.size()-2], coordinates.back()),
std::make_pair(coordinates[coordinates.size()-2], coordinates[coordinates.size()-2]),
std::make_pair(coordinates[0], coordinates[1]),
std::make_pair(coordinates[1], coordinates[0]),
};
}
return {std::make_pair(coordinates.back(), coordinates.back())};
}
bool QRDecode::updatePerspective(const Mat& H)
{
if (H.empty())
return false;
homography = H;
Mat temp_intermediate;
warpPerspective(bin_original, temp_intermediate, H, temporary_size, INTER_NEAREST);
const Size temporary_size(cvRound(test_perspective_size), cvRound(test_perspective_size));
warpPerspective(bin_barcode, temp_intermediate, H, temporary_size, INTER_NEAREST);
no_border_intermediate = temp_intermediate(Range(1, temp_intermediate.rows), Range(1, temp_intermediate.cols));
const int border = cvRound(0.1 * test_perspective_size);
@@ -2285,7 +2403,7 @@ bool QRDecode::updatePerspective()
return true;
}
inline Point computeOffset(const vector<Point>& v)
static inline Point computeOffset(const vector<Point>& v)
{
// compute the width/height of convex hull
Rect areaBox = boundingRect(v);
@@ -2299,9 +2417,80 @@ inline Point computeOffset(const vector<Point>& v)
return offset;
}
// QR code with version 7 or higher has a special 18 bit version number code.
// @return std::pair<double, int> first - distance to estimatedVersion, second - version
/**
* @param numModules - estimated numModules
* @param estimatedVersion
* @return pair<double, int>, first - Hamming distance to 18 bit code, second - closest version
*
* QR code with version 7 or higher has a special 18 bit version number code:
* https://www.thonky.com/qr-code-tutorial/format-version-information
*/
static inline std::pair<double, int> getVersionByCode(double numModules, Mat qr, int estimatedVersion) {
const double moduleSize = qr.rows / numModules;
Point2d startVersionInfo1 = Point2d((numModules-8.-3.)*moduleSize, 0.);
Point2d endVersionInfo1 = Point2d((numModules-8.)*moduleSize, moduleSize*6.);
Point2d startVersionInfo2 = Point2d(0., (numModules-8.-3.)*moduleSize);
Point2d endVersionInfo2 = Point2d(moduleSize*6., (numModules-8.)*moduleSize);
Mat v1(qr, Rect2d(startVersionInfo1, endVersionInfo1));
Mat v2(qr, Rect2d(startVersionInfo2, endVersionInfo2));
const double thresh = 127.;
resize(v1, v1, Size(3, 6), 0., 0., INTER_AREA);
threshold(v1, v1, thresh, 255, THRESH_BINARY);
resize(v2, v2, Size(6, 3), 0., 0., INTER_AREA);
threshold(v2, v2, thresh, 255, THRESH_BINARY);
Mat version1, version2;
// convert version1 (top right version information block) and
// version2 (bottom left version information block) to version table format
// https://www.thonky.com/qr-code-tutorial/format-version-tables
rotate((255-v1)/255, version1, ROTATE_180), rotate(((255-v2)/255).t(), version2, ROTATE_180);
static uint8_t versionCodes[][18] = {{0,0,0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0},{0,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0},
{0,0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1},{0,0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1},
{0,0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,0},{0,0,1,1,0,0,0,1,1,1,0,1,1,0,0,0,1,0},
{0,0,1,1,0,1,1,0,0,0,0,1,0,0,0,1,1,1},{0,0,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,1},
{0,0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0,0},{0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0,0},
{0,1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,1},{0,1,0,0,1,0,1,0,1,0,0,0,0,1,0,1,1,1},
{0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1,0},{0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1,0},
{0,1,0,1,0,1,0,1,1,0,1,0,0,0,0,0,1,1},{0,1,0,1,1,0,1,0,0,0,1,1,0,0,1,0,0,1},
{0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,0,0},{0,1,1,0,0,0,1,1,1,0,1,1,0,0,0,1,0,0},
{0,1,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,1},{0,1,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1},
{0,1,1,0,1,1,0,0,0,0,1,0,0,0,1,1,1,0},{0,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0},
{0,1,1,1,0,1,0,0,1,1,0,0,1,1,1,1,1,1},{0,1,1,1,1,0,1,1,0,1,0,1,1,1,0,1,0,1},
{0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0,0,0},{1,0,0,0,0,0,1,0,0,1,1,1,0,1,0,1,0,1},
{1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0,0,0},{1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,1,0},
{1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,1,1},{1,0,0,1,0,0,1,0,1,1,0,0,0,0,1,0,1,1},
{1,0,0,1,0,1,0,1,0,0,0,0,1,0,1,1,1,0},{1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1,0,0},
{1,0,0,1,1,1,0,1,0,1,0,1,0,0,0,0,0,1},{1,0,1,0,0,0,1,1,0,0,0,1,1,0,1,0,0,1}
};
double minDist = 19.;
int bestVersion = -1;
const double penaltyFactor = 0.8;
for (int i = 0; i < (int)(sizeof(versionCodes)/sizeof(versionCodes[0])); i++) {
Mat currVers(Size(3, 6), CV_8UC1, versionCodes[i]);
// minimum hamming distance between version = 8
double tmp = norm(currVers, version1, NORM_HAMMING) + penaltyFactor*abs(estimatedVersion-i-7);
if (tmp < minDist) {
bestVersion = i+7;
minDist = tmp;
}
tmp = norm(currVers, version2, NORM_HAMMING) + penaltyFactor*abs(estimatedVersion-i-7);
if (tmp < minDist) {
bestVersion = i+7;
minDist = tmp;
}
}
return std::make_pair(minDist, bestVersion);
}
bool QRDecode::versionDefinition()
{
CV_TRACE_FUNCTION();
CV_LOG_DEBUG(NULL, "QR corners: " << original_points[0] << " " << original_points[1] << " " << original_points[2] <<
" " << original_points[3]);
LineIterator line_iter(intermediate, Point2f(0, 0), Point2f(test_perspective_size, test_perspective_size));
Point black_point = Point(0, 0);
for(int j = 0; j < line_iter.count; j++, ++line_iter)
@@ -2359,12 +2548,120 @@ bool QRDecode::versionDefinition()
transition_y++;
}
}
version = saturate_cast<uint8_t>((std::min(transition_x, transition_y) - 1) * 0.25 - 1);
if ( !( 0 < version && version <= 40 ) ) { return false; }
const int versionByTransition = saturate_cast<uint8_t>((std::min(transition_x, transition_y) - 1) * 0.25 - 1);
const int numModulesByTransition = 21 + (versionByTransition - 1) * 4;
const double numModulesByFinderPattern = getNumModules();
const double versionByFinderPattern = (numModulesByFinderPattern - 21.) * .25 + 1.;
bool useFinderPattern = false;
const double thresholdFinderPattern = 0.2;
const double roundingError = abs(numModulesByFinderPattern - cvRound(numModulesByFinderPattern));
if (cvRound(versionByFinderPattern) >= 1 && versionByFinderPattern <= 6. &&
transition_x != transition_y && roundingError < thresholdFinderPattern) {
useFinderPattern = true;
}
bool useCode = false;
int versionByCode = 7;
if (cvRound(versionByFinderPattern) >= 7 || versionByTransition >= 7) {
vector<std::pair<double, int>> versionAndDistances;
if (cvRound(versionByFinderPattern) >= 7) {
versionAndDistances.push_back(getVersionByCode(numModulesByFinderPattern, no_border_intermediate,
cvRound(versionByFinderPattern)));
}
if (versionByTransition >= 7) {
versionAndDistances.push_back(getVersionByCode(numModulesByTransition, no_border_intermediate,
versionByTransition));
}
const auto& bestVersion = min(versionAndDistances.front(), versionAndDistances.back());
double distanceByCode = bestVersion.first;
versionByCode = bestVersion.second;
if (distanceByCode < 5.) {
useCode = true;
}
}
if (useCode) {
CV_LOG_DEBUG(NULL, "Version type: useCode");
version = (uint8_t)versionByCode;
}
else if (useFinderPattern ) {
CV_LOG_DEBUG(NULL, "Version type: useFinderPattern");
version = (uint8_t)cvRound(versionByFinderPattern);
}
else {
CV_LOG_DEBUG(NULL, "Version type: useTransition");
version = (uint8_t)versionByTransition;
}
version_size = 21 + (version - 1) * 4;
if ( !(0 < version && version <= 40) ) { return false; }
CV_LOG_DEBUG(NULL, "QR version: " << (int)version);
return true;
}
void QRDecode::detectAlignment() {
vector<pair<int, int>> alignmentPositions = getAlignmentCoordinates(version);
if (alignmentPositions.size() > 0) {
vector<Point2f> perspective_points = {{0.f, 0.f}, {test_perspective_size, 0.f}, {0.f, test_perspective_size}};
vector<Point2f> object_points = {original_points[0], original_points[1], original_points[3]};
// create alignment image
static uint8_t alignmentMarker[25] = {
0, 0, 0, 0, 0,
0, 255, 255, 255, 0,
0, 255, 0, 255, 0,
0, 255, 255, 255, 0,
0, 0, 0, 0, 0
};
Mat alignmentMarkerMat(5, 5, CV_8UC1, alignmentMarker);
const float module_size = test_perspective_size / version_size;
Mat resizedAlignmentMarker;
resize(alignmentMarkerMat, resizedAlignmentMarker,
Size(cvRound(module_size * 5.f), cvRound(module_size * 5.f)), 0, 0, INTER_AREA);
const float module_offset = 1.9f;
const float offset = (module_size * (5 + module_offset * 2)); // 5 modules in alignment marker, 2 x module_offset modules in offset
for (const pair<int, int>& alignmentPos : alignmentPositions) {
const float left_top_x = (module_size * (alignmentPos.first - 2.f - module_offset)); // add offset
const float left_top_y = (module_size * (alignmentPos.second - 2.f - module_offset)); // add offset
Mat subImage(no_border_intermediate, Rect(cvRound(left_top_x), cvRound(left_top_y), cvRound(offset), cvRound(offset)));
Mat resTemplate;
matchTemplate(subImage, resizedAlignmentMarker, resTemplate, TM_CCOEFF_NORMED);
double minVal = 0., maxVal = 0.;
Point minLoc, maxLoc, matchLoc;
minMaxLoc(resTemplate, &minVal, &maxVal, &minLoc, &maxLoc);
CV_LOG_DEBUG(NULL, "Alignment maxVal: " << maxVal);
if (maxVal > 0.65) {
const float templateOffset = static_cast<float>(resizedAlignmentMarker.size().width) / 2.f;
Point2f alignmentCoord(Point2f(maxLoc.x + left_top_x + templateOffset, maxLoc.y + left_top_y + templateOffset));
alignment_coords.push_back(alignmentCoord);
perspectiveTransform(alignment_coords, alignment_coords, homography.inv());
CV_LOG_DEBUG(NULL, "Alignment coords: " << alignment_coords);
const float relativePosX = (alignmentPos.first + 0.5f) / version_size;
const float relativePosY = (alignmentPos.second + 0.5f) / version_size;
perspective_points.push_back({relativePosX * test_perspective_size, relativePosY * test_perspective_size});
object_points.push_back(alignment_coords.back());
}
}
if (object_points.size() > 3ull) {
double ransacReprojThreshold = 10.;
if (version == 2) { // in low version original_points[2] may be calculated more accurately using intersections method
object_points.push_back(original_points[2]);
ransacReprojThreshold = 5.; // set more strict ransacReprojThreshold
perspective_points.push_back({test_perspective_size, test_perspective_size});
}
Mat H = findHomography(object_points, perspective_points, RANSAC, ransacReprojThreshold);
if (H.empty())
return;
updatePerspective(H);
vector<Point2f> newCorner2 = {{test_perspective_size, test_perspective_size}};
perspectiveTransform(newCorner2, newCorner2, H.inv());
original_points[2] = newCorner2.front();
}
}
}
bool QRDecode::samplingForVersion()
{
CV_TRACE_FUNCTION();
@@ -2451,8 +2748,10 @@ bool QRDecode::decodingProcess()
bool QRDecode::straightDecodingProcess()
{
#ifdef HAVE_QUIRC
if (!updatePerspective()) { return false; }
if (!updatePerspective(getHomography())) { return false; }
if (!versionDefinition()) { return false; }
if (useAlignmentMarkers)
detectAlignment();
if (!samplingForVersion()) { return false; }
if (!decodingProcess()) { return false; }
return true;
@@ -2476,6 +2775,13 @@ bool QRDecode::curvedDecodingProcess()
#endif
}
QRDecode::QRDecode(bool _useAlignmentMarkers):
useAlignmentMarkers(_useAlignmentMarkers),
version(0),
version_size(0),
test_perspective_size(0.f)
{}
std::string QRCodeDetector::decode(InputArray in, InputArray points,
OutputArray straight_qrcode)
{
@@ -2488,7 +2794,7 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
CV_Assert(src_points.size() == 4);
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
QRDecode qrdec;
QRDecode qrdec(p->useAlignmentMarkers);
qrdec.init(inarr, src_points);
bool ok = qrdec.straightDecodingProcess();
@@ -2501,7 +2807,10 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points,
{
qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1);
}
if (ok && !decoded_info.empty()) {
p->alignmentMarkers = {qrdec.alignment_coords};
p->updateQrCorners = qrdec.getOriginalPoints();
}
return ok ? decoded_info : std::string();
}
@@ -2517,7 +2826,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points,
CV_Assert(src_points.size() == 4);
CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points");
QRDecode qrdec;
QRDecode qrdec(p->useAlignmentMarkers);
qrdec.init(inarr, src_points);
bool ok = qrdec.curvedDecodingProcess();
@@ -2742,7 +3051,7 @@ void QRDetectMulti::init(const Mat& src, double eps_vertical_, double eps_horizo
const int width = cvRound(src.size().width * coeff_expansion);
const int height = cvRound(src.size().height * coeff_expansion);
Size new_size(width, height);
resize(src, barcode, new_size, 0, 0, INTER_LINEAR);
resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT);
}
else if (min_side > 512.0)
{
@@ -3099,7 +3408,7 @@ int QRDetectMulti::findNumberLocalizationPoints(vector<Point2f>& tmp_localizatio
const int height = cvRound(bin_barcode.size().height * coeff_expansion);
Size new_size(width, height);
Mat intermediate;
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
bin_barcode = intermediate.clone();
}
else if (purpose == ZOOMING)
@@ -3108,7 +3417,7 @@ int QRDetectMulti::findNumberLocalizationPoints(vector<Point2f>& tmp_localizatio
const int height = cvRound(bin_barcode.size().height / coeff_expansion);
Size new_size(width, height);
Mat intermediate;
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR);
resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT);
bin_barcode = intermediate.clone();
}
else
@@ -3126,7 +3435,7 @@ void QRDetectMulti::findQRCodeContours(vector<Point2f>& tmp_localization_points,
const int width = cvRound(bin_barcode.size().width);
const int height = cvRound(bin_barcode.size().height);
Size new_size(width, height);
resize(bar, bar, new_size, 0, 0, INTER_LINEAR);
resize(bar, bar, new_size, 0, 0, INTER_LINEAR_EXACT);
blur(bar, blur_image, Size(3, 3));
threshold(blur_image, threshold_output, 50, 255, THRESH_BINARY);
@@ -3552,15 +3861,15 @@ public:
else if (std::min(inarr.size().width, inarr.size().height) > 512)
{
const int min_side = std::min(inarr.size().width, inarr.size().height);
double coeff_expansion = min_side / 512;
const int width = cvRound(inarr.size().width / coeff_expansion);
const int height = cvRound(inarr.size().height / coeff_expansion);
qrdec[i].coeff_expansion = min_side / 512.f;
const int width = cvRound(inarr.size().width / qrdec[i].coeff_expansion);
const int height = cvRound(inarr.size().height / qrdec[i].coeff_expansion);
Size new_size(width, height);
Mat inarr2;
resize(inarr, inarr2, new_size, 0, 0, INTER_AREA);
for (size_t j = 0; j < 4; j++)
for (size_t j = 0ull; j < 4ull; j++)
{
src_points[i][j] /= static_cast<float>(coeff_expansion);
src_points[i][j] /= qrdec[i].coeff_expansion;
}
qrdec[i].init(inarr2, src_points[i]);
ok = qrdec[i].straightDecodingProcess();
@@ -3568,6 +3877,8 @@ public:
{
decoded_info[i] = qrdec[i].getDecodeInformation();
straight_barcode[i] = qrdec[i].getStraightBarcode();
for (size_t j = 0ull; j < qrdec[i].alignment_coords.size(); j++)
qrdec[i].alignment_coords[j] *= qrdec[i].coeff_expansion;
}
}
if (decoded_info[i].empty())
@@ -3608,7 +3919,7 @@ bool QRCodeDetector::decodeMulti(
}
}
CV_Assert(src_points.size() > 0);
vector<QRDecode> qrdec(src_points.size());
vector<QRDecode> qrdec(src_points.size(), p->useAlignmentMarkers);
vector<Mat> straight_barcode(src_points.size());
vector<std::string> info(src_points.size());
ParallelDecodeProcess parallelDecodeProcess(inarr, qrdec, info, straight_barcode, src_points);
@@ -3639,6 +3950,13 @@ bool QRCodeDetector::decodeMulti(
{
decoded_info.push_back(info[i]);
}
p->alignmentMarkers.resize(src_points.size());
p->updateQrCorners.resize(src_points.size()*4ull);
for (size_t i = 0ull; i < src_points.size(); i++) {
p->alignmentMarkers[i] = qrdec[i].alignment_coords;
for (size_t j = 0ull; j < 4ull; j++)
p->updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion;
}
if (!decoded_info.empty())
return true;
else
@@ -3669,7 +3987,13 @@ bool QRCodeDetector::detectAndDecodeMulti(
updatePointsResult(points_, points);
decoded_info.clear();
ok = decodeMulti(inarr, points, decoded_info, straight_qrcode);
updatePointsResult(points_, p->updateQrCorners);
return ok;
}
void QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) {
p->useAlignmentMarkers = useAlignmentMarkers;
}
} // namespace