mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 15:53:03 +04:00
Merge pull request #25708 from kaingwade:flann2annoy
Add interface to Annoy which will replace the FLANN #25708 This PR is to add interface to [Annoy](https://github.com/spotify/annoy) which will replace the FLANN, part of one of the cleanup work of OpenCV 5.0: #24998. After it, there will be consecutive patches: - [ ] Add Annoy based DescriptorMatcher - [ ] Replace FLANN based code with Annoy and remove FLANN completely ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2013 Spotify AB
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
use this file except in compliance with the License. You may obtain a copy of
|
||||
the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations under
|
||||
the License.
|
||||
+1597
File diff suppressed because it is too large
Load Diff
+242
@@ -0,0 +1,242 @@
|
||||
|
||||
// This is from https://code.google.com/p/mman-win32/
|
||||
//
|
||||
// Licensed under MIT
|
||||
|
||||
#ifndef _MMAN_WIN32_H
|
||||
#define _MMAN_WIN32_H
|
||||
|
||||
#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
|
||||
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <windows.h>
|
||||
#include <errno.h>
|
||||
#include <io.h>
|
||||
|
||||
#define PROT_NONE 0
|
||||
#define PROT_READ 1
|
||||
#define PROT_WRITE 2
|
||||
#define PROT_EXEC 4
|
||||
|
||||
#define MAP_FILE 0
|
||||
#define MAP_SHARED 1
|
||||
#define MAP_PRIVATE 2
|
||||
#define MAP_TYPE 0xf
|
||||
#define MAP_FIXED 0x10
|
||||
#define MAP_ANONYMOUS 0x20
|
||||
#define MAP_ANON MAP_ANONYMOUS
|
||||
|
||||
#define MAP_FAILED ((void *)-1)
|
||||
|
||||
/* Flags for msync. */
|
||||
#define MS_ASYNC 1
|
||||
#define MS_SYNC 2
|
||||
#define MS_INVALIDATE 4
|
||||
|
||||
#ifndef FILE_MAP_EXECUTE
|
||||
#define FILE_MAP_EXECUTE 0x0020
|
||||
#endif
|
||||
|
||||
static int __map_mman_error(const DWORD err, const int /*deferr*/)
|
||||
{
|
||||
if (err == 0)
|
||||
return 0;
|
||||
//TODO: implement
|
||||
return err;
|
||||
}
|
||||
|
||||
static DWORD __map_mmap_prot_page(const int prot)
|
||||
{
|
||||
DWORD protect = 0;
|
||||
|
||||
if (prot == PROT_NONE)
|
||||
return protect;
|
||||
|
||||
if ((prot & PROT_EXEC) != 0)
|
||||
{
|
||||
protect = ((prot & PROT_WRITE) != 0) ?
|
||||
PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
|
||||
}
|
||||
else
|
||||
{
|
||||
protect = ((prot & PROT_WRITE) != 0) ?
|
||||
PAGE_READWRITE : PAGE_READONLY;
|
||||
}
|
||||
|
||||
return protect;
|
||||
}
|
||||
|
||||
static DWORD __map_mmap_prot_file(const int prot)
|
||||
{
|
||||
DWORD desiredAccess = 0;
|
||||
|
||||
if (prot == PROT_NONE)
|
||||
return desiredAccess;
|
||||
|
||||
if ((prot & PROT_READ) != 0)
|
||||
desiredAccess |= FILE_MAP_READ;
|
||||
if ((prot & PROT_WRITE) != 0)
|
||||
desiredAccess |= FILE_MAP_WRITE;
|
||||
if ((prot & PROT_EXEC) != 0)
|
||||
desiredAccess |= FILE_MAP_EXECUTE;
|
||||
|
||||
return desiredAccess;
|
||||
}
|
||||
|
||||
inline void* mmap(void * /*addr*/, size_t len, int prot, int flags, int fildes, off_t off)
|
||||
{
|
||||
HANDLE fm, h;
|
||||
|
||||
void * map = MAP_FAILED;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4293)
|
||||
#endif
|
||||
|
||||
const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) ?
|
||||
(DWORD)off : (DWORD)(off & 0xFFFFFFFFL);
|
||||
const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) ?
|
||||
(DWORD)0 : (DWORD)((off >> 32) & 0xFFFFFFFFL);
|
||||
const DWORD protect = __map_mmap_prot_page(prot);
|
||||
const DWORD desiredAccess = __map_mmap_prot_file(prot);
|
||||
|
||||
const off_t maxSize = off + (off_t)len;
|
||||
|
||||
const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) ?
|
||||
(DWORD)maxSize : (DWORD)(maxSize & 0xFFFFFFFFL);
|
||||
const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) ?
|
||||
(DWORD)0 : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
errno = 0;
|
||||
|
||||
if (len == 0
|
||||
/* Unsupported flag combinations */
|
||||
|| (flags & MAP_FIXED) != 0
|
||||
/* Usupported protection combinations */
|
||||
|| prot == PROT_EXEC)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
h = ((flags & MAP_ANONYMOUS) == 0) ?
|
||||
(HANDLE)_get_osfhandle(fildes) : INVALID_HANDLE_VALUE;
|
||||
|
||||
if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
errno = EBADF;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL);
|
||||
|
||||
if (fm == NULL)
|
||||
{
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
map = MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len);
|
||||
|
||||
CloseHandle(fm);
|
||||
|
||||
if (map == NULL)
|
||||
{
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
inline int munmap(void *addr, size_t /*len*/)
|
||||
{
|
||||
if (UnmapViewOfFile(addr))
|
||||
return 0;
|
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline int mprotect(void *addr, size_t len, int prot)
|
||||
{
|
||||
DWORD newProtect = __map_mmap_prot_page(prot);
|
||||
DWORD oldProtect = 0;
|
||||
|
||||
if (VirtualProtect(addr, len, newProtect, &oldProtect))
|
||||
return 0;
|
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline int msync(void *addr, size_t len, int /*flags*/)
|
||||
{
|
||||
if (FlushViewOfFile(addr, len))
|
||||
return 0;
|
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline int mlock(const void *addr, size_t len)
|
||||
{
|
||||
if (VirtualLock((LPVOID)addr, len))
|
||||
return 0;
|
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline int munlock(const void *addr, size_t len)
|
||||
{
|
||||
if (VirtualUnlock((LPVOID)addr, len))
|
||||
return 0;
|
||||
|
||||
errno = __map_mman_error(GetLastError(), EPERM);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if !defined(__MINGW32__)
|
||||
inline int ftruncate(const int fd, const int64_t size) {
|
||||
if (fd < 0) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE h = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
||||
LARGE_INTEGER li_start, li_size;
|
||||
li_start.QuadPart = static_cast<int64_t>(0);
|
||||
li_size.QuadPart = size;
|
||||
if (SetFilePointerEx(h, li_start, NULL, FILE_CURRENT) == ~0 ||
|
||||
SetFilePointerEx(h, li_size, NULL, FILE_BEGIN) == ~0 ||
|
||||
!SetEndOfFile(h)) {
|
||||
unsigned long error = GetLastError();
|
||||
fprintf(stderr, "I/O error while truncating: %lu\n", error);
|
||||
switch (error) {
|
||||
case ERROR_INVALID_HANDLE:
|
||||
errno = EBADF;
|
||||
break;
|
||||
default:
|
||||
errno = EIO;
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -9,3 +9,4 @@ endif()
|
||||
ocv_define_module(features2d opencv_imgproc ${debug_modules} OPTIONAL opencv_flann WRAP java objc python js)
|
||||
|
||||
ocv_install_3rdparty_licenses(mscr "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/mscr/chi_table_LICENSE.txt")
|
||||
ocv_install_3rdparty_licenses(annoylib "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/annoy/LICENSE")
|
||||
|
||||
@@ -67,6 +67,13 @@
|
||||
@{
|
||||
@defgroup features2d_hal_interface Interface
|
||||
@}
|
||||
|
||||
@defgroup features2d_annoy Approximate Nearest Neighbors Search in Multi-Dimensional Spaces
|
||||
|
||||
This section documents OpenCV's interface to the Annoy. Annoy (Approximate Nearest Neighbors Oh Yeah)
|
||||
is a library to search for points in space that are close to a given query point. It also creates
|
||||
large read-only file-based data structures that are mmapped into memory so that many processes may
|
||||
share the same data.
|
||||
@}
|
||||
*/
|
||||
|
||||
@@ -1179,6 +1186,102 @@ CV_EXPORTS int getNearestPoint( const std::vector<Point2f>& recallPrecisionCurve
|
||||
//! @}
|
||||
|
||||
|
||||
/****************************************************************************************\
|
||||
* Approximate Nearest Neighbors *
|
||||
\****************************************************************************************/
|
||||
|
||||
//! @addtogroup features2d_annoy
|
||||
//! @{
|
||||
|
||||
class CV_EXPORTS_W ANNIndex
|
||||
{
|
||||
public:
|
||||
/** @brief Metrics used to calculate the distance between two feature vectors.
|
||||
*/
|
||||
enum Distance
|
||||
{
|
||||
DIST_EUCLIDEAN,
|
||||
DIST_MANHATTAN,
|
||||
DIST_ANGULAR,
|
||||
DIST_HAMMING,
|
||||
DIST_DOTPRODUCT
|
||||
};
|
||||
|
||||
virtual ~ANNIndex() = default;
|
||||
|
||||
/** @brief Add feature vectors to index.
|
||||
*
|
||||
* @param features Matrix containing the feature vectors to index. The size of the matrix is
|
||||
num_features x feature_dimension.
|
||||
*/
|
||||
CV_WRAP virtual void addItems(InputArray features) = 0;
|
||||
|
||||
/** @brief Build the index.
|
||||
*
|
||||
* @param trees Number of trees in the index. If not provided, the number is determined automatically
|
||||
* in a way that at most 2x as much memory as the features vectors take is used.
|
||||
*/
|
||||
CV_WRAP virtual void build(int trees=-1) = 0;
|
||||
|
||||
/** @brief Performs a K-nearest neighbor search for given query vector(s) using the index.
|
||||
*
|
||||
* @param query The query vector(s).
|
||||
* @param indices Matrix that will contain the indices of the K-nearest neighbors found, optional.
|
||||
* @param dists Matrix that will contain the distances to the K-nearest neighbors found, optional.
|
||||
* @param knn Number of nearest neighbors to search for.
|
||||
* @param search_k The maximum number of nodes to inspect, which defaults to trees x knn if not provided.
|
||||
*/
|
||||
CV_WRAP virtual void knnSearch(InputArray query, OutputArray indices, OutputArray dists, int knn, int search_k=-1) = 0;
|
||||
|
||||
/** @brief Save the index to disk and loads it. After saving, no more vectors can be added.
|
||||
*
|
||||
* @param filename Filename of the index to be saved.
|
||||
* @param prefault If prefault is set to true, it will pre-read the entire file into memory (using mmap
|
||||
* with MAP_POPULATE). Default is false.
|
||||
*/
|
||||
CV_WRAP virtual void save(const String &filename, bool prefault=false) = 0;
|
||||
|
||||
/** @brief Loads (mmaps) an index from disk.
|
||||
*
|
||||
* @param filename Filename of the index to be loaded.
|
||||
* @param prefault If prefault is set to true, it will pre-read the entire file into memory (using mmap
|
||||
* with MAP_POPULATE). Default is false.
|
||||
*/
|
||||
CV_WRAP virtual void load(const String &filename, bool prefault=false) = 0;
|
||||
|
||||
/** @brief Return the number of trees in the index.
|
||||
*/
|
||||
CV_WRAP virtual int getTreeNumber() = 0;
|
||||
|
||||
/** @brief Return the number of feature vectors in the index.
|
||||
*/
|
||||
CV_WRAP virtual int getItemNumber() = 0;
|
||||
|
||||
/** @brief Prepare to build the index in the specified file instead of RAM (execute before adding
|
||||
* items, no need to save after build)
|
||||
*
|
||||
* @param filename Filename of the index to be built.
|
||||
*/
|
||||
CV_WRAP virtual bool setOnDiskBuild(const String &filename) = 0;
|
||||
|
||||
/** @brief Initialize the random number generator with the given seed. Only necessary to pass this
|
||||
* before adding the items. Will have no effect after calling build() or load().
|
||||
*
|
||||
* @param seed The given seed of the random number generator. Its value should be within the range of uint32_t.
|
||||
*/
|
||||
CV_WRAP virtual void setSeed(int seed) = 0;
|
||||
|
||||
/** @brief Creates an instance of annoy index class with given parameters
|
||||
*
|
||||
* @param dim The dimension of the feature vector.
|
||||
* @param distType Metric to calculate the distance between two feature vectors, can be DIST_EUCLIDEAN,
|
||||
DIST_MANHATTAN, DIST_ANGULAR, DIST_HAMMING, or DIST_DOTPRODUCT.
|
||||
*/
|
||||
CV_WRAP static Ptr<ANNIndex> create(int dim, ANNIndex::Distance distType=ANNIndex::DIST_EUCLIDEAN);
|
||||
};
|
||||
|
||||
//! @} features2d_annoy
|
||||
|
||||
} /* namespace cv */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
// 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 "../3rdparty/annoy/annoylib.h"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
struct Random
|
||||
{
|
||||
static const uint64 default_seed = 0xffffffff;
|
||||
#if __cplusplus < 201103L
|
||||
typedef uint64 seed_type;
|
||||
#endif
|
||||
|
||||
RNG rng;
|
||||
|
||||
Random(uint64 seed = default_seed)
|
||||
{
|
||||
rng.state = seed;
|
||||
}
|
||||
|
||||
inline int flip()
|
||||
{
|
||||
// Draw random 0 or 1
|
||||
return rng.next() & 1;
|
||||
}
|
||||
|
||||
inline size_t index(size_t n)
|
||||
{
|
||||
// Draw random integer between 0 and n-1 where n is at most the number of data points you have
|
||||
return rng(unsigned(n));
|
||||
}
|
||||
|
||||
inline void set_seed(uint64 seed)
|
||||
{
|
||||
rng.state = seed;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename DataType, typename DistanceType>
|
||||
class ANNIndexImpl : public ANNIndex
|
||||
{
|
||||
public:
|
||||
ANNIndexImpl(int dimension) : dim(dimension)
|
||||
{
|
||||
index = makePtr<::cvannoy::AnnoyIndex<int, DataType, DistanceType, Random, ::cvannoy::AnnoyIndexSingleThreadedBuildPolicy>>(dimension);
|
||||
}
|
||||
|
||||
void addItems(InputArray _dataset) CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(!_dataset.empty());
|
||||
|
||||
Mat features = _dataset.getMat();
|
||||
CV_Assert(features.cols == dim);
|
||||
CV_Assert(features.type() == cv::DataType<DataType>::type);
|
||||
|
||||
int num = features.rows;
|
||||
char* msg = nullptr;
|
||||
if (!index->add_item(0, features.ptr<DataType>(0), &msg))
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
String errorMsg = msg;
|
||||
free(msg);
|
||||
CV_Error(Error::StsError, errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, "Fail to add an item.");
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < num; ++i)
|
||||
index->add_item(i, features.ptr<DataType>(i));
|
||||
}
|
||||
|
||||
void build(int trees) CV_OVERRIDE
|
||||
{
|
||||
if (index->get_n_items() <= 0)
|
||||
CV_Error(Error::StsError, "No items added. Please add items before building the index.");
|
||||
|
||||
if (trees <= 0)
|
||||
trees = -1;
|
||||
|
||||
char* msg = nullptr;
|
||||
if (!index->build(trees, -1, &msg))
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
String errorMsg = msg;
|
||||
free(msg);
|
||||
CV_Error(Error::StsError, errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, "Fail to build the index.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void knnSearch(InputArray _query, OutputArray _indices, OutputArray _dists, int knn, int search_k) CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(!_query.empty() && _query.isContinuous());
|
||||
Mat query = _query.getMat(), indices, dists;
|
||||
CV_Assert(query.type() == cv::DataType<DataType>::type);
|
||||
CV_Assert(knn > 0 && knn <= index->get_n_items());
|
||||
|
||||
int numQuery = query.rows;
|
||||
if (_indices.needed())
|
||||
{
|
||||
indices = _indices.getMat();
|
||||
if (!indices.isContinuous() || indices.type() != CV_32S ||
|
||||
indices.rows != numQuery || indices.cols != knn)
|
||||
{
|
||||
if (!indices.isContinuous())
|
||||
_indices.release();
|
||||
_indices.create(numQuery, knn, CV_32S);
|
||||
indices = _indices.getMat();
|
||||
}
|
||||
}
|
||||
else
|
||||
indices.create(numQuery, knn, CV_32S);
|
||||
|
||||
if (_dists.needed())
|
||||
{
|
||||
dists = _dists.getMat();
|
||||
if (!dists.isContinuous() || dists.type() != cv::DataType<DataType>::type ||
|
||||
dists.rows != numQuery || dists.cols != knn)
|
||||
{
|
||||
if (!_dists.isContinuous())
|
||||
_dists.release();
|
||||
_dists.create(numQuery, knn, cv::DataType<DataType>::type);
|
||||
dists = _dists.getMat();
|
||||
}
|
||||
}
|
||||
else
|
||||
dists.create(numQuery, knn, cv::DataType<DataType>::type);
|
||||
|
||||
auto processBatch = [&](const Range& range)
|
||||
{
|
||||
std::vector<int> nns;
|
||||
std::vector<DataType> distances;
|
||||
|
||||
for (int i = range.start; i < range.end; ++i)
|
||||
{
|
||||
index->get_nns_by_vector(query.ptr<DataType>(i), knn, search_k, &nns, &distances);
|
||||
|
||||
std::copy(nns.begin(), nns.end(), indices.ptr<int>(i));
|
||||
std::copy(distances.begin(), distances.end(), dists.ptr<DataType>(i));
|
||||
|
||||
nns.clear();
|
||||
distances.clear();
|
||||
}
|
||||
};
|
||||
|
||||
parallel_for_(Range(0, numQuery), processBatch);
|
||||
}
|
||||
|
||||
void save(const String &filename, bool prefault) CV_OVERRIDE
|
||||
{
|
||||
char* msg = nullptr;
|
||||
if (!index->save(filename.c_str(), prefault, &msg))
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
String errorMsg = msg;
|
||||
free(msg);
|
||||
CV_Error(Error::StsError, errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, "Fail to save the index.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load(const String &filename, bool prefault) CV_OVERRIDE
|
||||
{
|
||||
char* msg = nullptr;
|
||||
if (!index->load(filename.c_str(), prefault, &msg))
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
String errorMsg = msg;
|
||||
free(msg);
|
||||
CV_Error(Error::StsError, errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsError, "Fail to load the index.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getTreeNumber() CV_OVERRIDE
|
||||
{
|
||||
return index->get_n_trees();
|
||||
}
|
||||
|
||||
int getItemNumber() CV_OVERRIDE
|
||||
{
|
||||
return index->get_n_items();
|
||||
}
|
||||
|
||||
bool setOnDiskBuild(const String &filename) CV_OVERRIDE
|
||||
{
|
||||
char* msg = nullptr;
|
||||
if (index->on_disk_build(filename.c_str(), &msg))
|
||||
return true;
|
||||
else
|
||||
{
|
||||
if (msg)
|
||||
{
|
||||
String errorMsg = msg;
|
||||
CV_LOG_ERROR(NULL, errorMsg);
|
||||
free(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_LOG_ERROR(NULL, "Cannot set build on disk.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void setSeed(int seed) CV_OVERRIDE
|
||||
{
|
||||
index->set_seed(static_cast<uint32_t>(seed));
|
||||
}
|
||||
|
||||
private:
|
||||
int dim;
|
||||
Ptr<::cvannoy::AnnoyIndex<int, DataType, DistanceType, Random, ::cvannoy::AnnoyIndexSingleThreadedBuildPolicy>> index;
|
||||
};
|
||||
|
||||
Ptr<ANNIndex> ANNIndex::create(int dim, ANNIndex::Distance distType)
|
||||
{
|
||||
switch (distType)
|
||||
{
|
||||
case ANNIndex::DIST_EUCLIDEAN:
|
||||
return makePtr<ANNIndexImpl<float, ::cvannoy::Euclidean>>(dim);
|
||||
break;
|
||||
case ANNIndex::DIST_MANHATTAN:
|
||||
return makePtr<ANNIndexImpl<float, ::cvannoy::Manhattan>>(dim);
|
||||
break;
|
||||
case ANNIndex::DIST_ANGULAR:
|
||||
return makePtr<ANNIndexImpl<float, ::cvannoy::Angular>>(dim);
|
||||
break;
|
||||
case ANNIndex::DIST_HAMMING:
|
||||
return makePtr<ANNIndexImpl<uchar, ::cvannoy::Hamming>>(dim);
|
||||
break;
|
||||
case ANNIndex::DIST_DOTPRODUCT:
|
||||
return makePtr<ANNIndexImpl<float, ::cvannoy::DotProduct>>(dim);
|
||||
break;
|
||||
default:
|
||||
CV_Error(Error::StsBadArg, "Unknown/unsupported distance type");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -330,4 +330,55 @@ TEST(Features2d_FLANN_Saved, regression) { CV_FlannSavedIndexTest test; test.saf
|
||||
|
||||
#endif
|
||||
|
||||
class CV_AnnoyTest : public NearestNeighborTest
|
||||
{
|
||||
public:
|
||||
CV_AnnoyTest() : NearestNeighborTest(), index(NULL) {}
|
||||
|
||||
protected:
|
||||
void createModel(const Mat& data) CV_OVERRIDE
|
||||
{
|
||||
index = ANNIndex::create(data.cols);
|
||||
index->addItems(data);
|
||||
index->build();
|
||||
}
|
||||
|
||||
int findNeighbors(Mat& points, Mat& neighbors) CV_OVERRIDE
|
||||
{
|
||||
Mat distances(points.rows, neighbors.cols, CV_32FC1);
|
||||
int knn = 1;
|
||||
|
||||
index->knnSearch(points, neighbors, distances, knn);
|
||||
|
||||
Mat neighbors1(neighbors.size(), CV_32SC1);
|
||||
for(int i = 0; i < points.rows; i++)
|
||||
{
|
||||
float* fltPtr = points.ptr<float>(i);
|
||||
vector<float> query(fltPtr, fltPtr + points.cols);
|
||||
vector<int> indices(neighbors1.cols, 0);
|
||||
vector<float> dists(distances.cols, 0);
|
||||
|
||||
index->knnSearch(query, indices, dists, knn);
|
||||
|
||||
vector<int>::const_iterator it = indices.begin();
|
||||
for(int j = 0; it != indices.end(); ++it, j++)
|
||||
neighbors1.at<int>(i, j) = *it;
|
||||
}
|
||||
|
||||
if (cvtest::norm(neighbors, neighbors1, NORM_L1) > 0)
|
||||
{
|
||||
ts->printf(cvtest::TS::LOG, "bad accuray of find nearest neighbors\n");
|
||||
ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY);
|
||||
}
|
||||
|
||||
return ts->get_err_code();
|
||||
}
|
||||
|
||||
void releaseModel() CV_OVERRIDE {}
|
||||
|
||||
Ptr<ANNIndex> index;
|
||||
};
|
||||
|
||||
TEST(Features2d_ANNIndex, regression) {CV_AnnoyTest test; test.safe_run();}
|
||||
|
||||
}} // namespace
|
||||
|
||||
Reference in New Issue
Block a user