lardon3d/src/feature_extractor_opencv.cpp

343 lines
15 KiB
C++

#include <algorithm>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <new>
#include <sys/stat.h>
#include <vector>
#include <opencv2/features2d.hpp>
#include <opencv2/imgcodecs.hpp>
#include <openssl/sha.h>
extern "C" {
#include <lardon3d/feature_extractor.h>
}
extern "C" bool lardon3d_feature_opencv_configure_threads(unsigned int threads) {
if (threads == 0 || threads > static_cast<unsigned int>(std::numeric_limits<int>::max())) {
return false;
}
cv::setNumThreads(static_cast<int>(threads));
return cv::getNumThreads() == static_cast<int>(threads);
}
extern "C" unsigned int lardon3d_feature_opencv_thread_count(void) {
int threads = cv::getNumThreads();
return threads > 0 ? static_cast<unsigned int>(threads) : 1U;
}
extern "C" bool
lardon3d_feature_extractor_parameters_valid(const Lardon3DFeatureExtractorParameters *parameters) {
return parameters && parameters->max_features > 0 &&
parameters->max_features <= LARDON3D_FEATURE_MAX_FEATURES &&
parameters->pyramid_levels >= 1 && parameters->pyramid_levels <= 16 &&
parameters->fast_threshold >= 1 && parameters->fast_threshold <= 255;
}
static void put_u32(unsigned char *output, uint32_t value) {
output[0] = static_cast<unsigned char>(value);
output[1] = static_cast<unsigned char>(value >> 8);
output[2] = static_cast<unsigned char>(value >> 16);
output[3] = static_cast<unsigned char>(value >> 24);
}
static void put_u64(unsigned char *output, uint64_t value) {
for (unsigned int index = 0; index < 8; ++index) {
output[index] = static_cast<unsigned char>(value >> (8U * index));
}
}
extern "C" void lardon3d_feature_extractor_parameter_fingerprint(
const Lardon3DFeatureExtractorParameters *parameters, unsigned char fingerprint[32]) {
if (!fingerprint) {
return;
}
std::memset(fingerprint, 0, 32);
if (!lardon3d_feature_extractor_parameters_valid(parameters)) {
return;
}
unsigned char canonical[24] = {'L', '3', 'D', 'O', 'R', 'B', 'P', '1'};
put_u32(canonical + 8, LARDON3D_FEATURE_EXTRACTOR_VERSION);
put_u32(canonical + 12, parameters->max_features);
put_u32(canonical + 16, parameters->pyramid_levels);
put_u32(canonical + 20, parameters->fast_threshold);
(void)SHA256(canonical, sizeof(canonical), fingerprint);
}
extern "C" bool
lardon3d_sift_extractor_parameters_valid(const Lardon3DSiftExtractorParameters *p) {
return p && p->max_features >= 1 && p->max_features <= LARDON3D_FEATURE_MAX_FEATURES &&
p->octave_layers >= 1 && p->octave_layers <= 8 && std::isfinite(p->contrast_threshold) &&
p->contrast_threshold >= 0.001 && p->contrast_threshold <= 0.2 &&
std::isfinite(p->edge_threshold) && p->edge_threshold >= 1.0 &&
p->edge_threshold <= 100.0 && std::isfinite(p->sigma) && p->sigma >= 0.5 &&
p->sigma <= 3.0 && p->grid_rows >= 1 && p->grid_rows <= 32 && p->grid_cols >= 1 &&
p->grid_cols <= 32 && p->max_features_per_cell >= 1 &&
p->max_features_per_cell <= LARDON3D_FEATURE_MAX_FEATURES;
}
extern "C" Lardon3DSiftExtractorParameters lardon3d_sift_precision_classic_v1(bool rootsift) {
return {4096, 3, 0.02, 10.0, 1.6, 8, 8, 96, rootsift};
}
extern "C" void lardon3d_sift_extractor_parameter_fingerprint(
const Lardon3DSiftExtractorParameters *p, unsigned char fingerprint[32]) {
if (!fingerprint) return;
std::memset(fingerprint, 0, 32);
if (!lardon3d_sift_extractor_parameters_valid(p)) return;
unsigned char canonical[60] = {'L', '3', 'D', 'S', 'I', 'F', 'T', '1'};
put_u32(canonical + 8, 1);
put_u32(canonical + 12, p->max_features);
put_u32(canonical + 16, p->octave_layers);
uint64_t bits = 0;
std::memcpy(&bits, &p->contrast_threshold, 8);
put_u64(canonical + 20, bits);
std::memcpy(&bits, &p->edge_threshold, 8);
put_u64(canonical + 28, bits);
std::memcpy(&bits, &p->sigma, 8);
put_u64(canonical + 36, bits);
put_u32(canonical + 44, p->grid_rows);
put_u32(canonical + 48, p->grid_cols);
put_u32(canonical + 52, p->max_features_per_cell);
put_u32(canonical + 56, p->rootsift ? 1U : 0U);
(void)SHA256(canonical, sizeof(canonical), fingerprint);
}
static bool sift_point_before(const cv::KeyPoint &a, size_t ai, const cv::KeyPoint &b, size_t bi) {
if (a.response != b.response) return a.response > b.response;
if (a.pt.y != b.pt.y) return a.pt.y < b.pt.y;
if (a.pt.x != b.pt.x) return a.pt.x < b.pt.x;
return ai < bi;
}
static bool select_sift_points(const cv::Mat &image, const std::vector<cv::KeyPoint> &points,
const Lardon3DSiftExtractorParameters *parameters,
std::vector<size_t> &selected, std::vector<uint32_t> &cells) {
std::vector<size_t> order(points.size());
for (size_t index = 0; index < order.size(); ++index) order[index] = index;
std::sort(order.begin(), order.end(), [&](size_t a, size_t b) {
return sift_point_before(points[a], a, points[b], b);
});
cells.assign(static_cast<size_t>(parameters->grid_rows) * parameters->grid_cols, 0);
selected.reserve(parameters->max_features);
float width = static_cast<float>(image.cols), height = static_cast<float>(image.rows);
for (size_t source : order) {
const cv::KeyPoint &point = points[source];
if (!std::isfinite(point.pt.x) || !std::isfinite(point.pt.y) || point.pt.x < 0 ||
point.pt.y < 0 || point.pt.x >= width || point.pt.y >= height)
return false;
uint32_t column = std::min(
parameters->grid_cols - 1,
static_cast<uint32_t>(point.pt.x / width * static_cast<float>(parameters->grid_cols)));
uint32_t row = std::min(
parameters->grid_rows - 1,
static_cast<uint32_t>(point.pt.y / height * static_cast<float>(parameters->grid_rows)));
uint32_t &used = cells[static_cast<size_t>(row) * parameters->grid_cols + column];
if (used < parameters->max_features_per_cell) {
selected.push_back(source);
++used;
}
if (selected.size() == parameters->max_features) break;
}
return true;
}
static Lardon3DFeatureExtractResult copy_sift_output(
const std::vector<cv::KeyPoint> &points, const cv::Mat &descriptors,
const std::vector<size_t> &selected, const Lardon3DSiftExtractorParameters *parameters,
Lardon3DExtractedFeatures *output) {
size_t count = selected.size();
Lardon3DFeatureKeypoint *keypoints = count ? static_cast<Lardon3DFeatureKeypoint *>(
std::calloc(count, sizeof(*keypoints)))
: nullptr;
unsigned char *bytes = count ? static_cast<unsigned char *>(
std::malloc(count * 128U * sizeof(float)))
: nullptr;
if (count && (!keypoints || !bytes)) {
std::free(keypoints);
std::free(bytes);
return LARDON3D_FEATURE_EXTRACT_OUT_OF_MEMORY;
}
for (size_t index = 0; index < count; ++index) {
const cv::KeyPoint &point = points[selected[index]];
int octave = point.octave & 255;
if (octave >= 128) octave -= 256;
keypoints[index] = {point.pt.x, point.pt.y, point.size, point.angle, point.response, octave};
const float *source = descriptors.ptr<float>(static_cast<int>(selected[index]));
float *destination = reinterpret_cast<float *>(bytes) + index * 128U;
double l1 = 0.0;
for (size_t dimension = 0; dimension < 128; ++dimension) {
if (!std::isfinite(source[dimension]) || source[dimension] < 0.0F ||
source[dimension] > 512.0F) {
std::free(keypoints);
std::free(bytes);
return LARDON3D_FEATURE_EXTRACT_ERROR;
}
if (parameters->rootsift) l1 += static_cast<double>(source[dimension]);
}
for (size_t dimension = 0; dimension < 128; ++dimension) {
destination[dimension] =
parameters->rootsift
? (l1 > 0.0 ? static_cast<float>(std::sqrt(source[dimension] / l1)) : 0.0F)
: source[dimension];
}
}
output->feature_count = static_cast<uint32_t>(count);
output->keypoints = keypoints;
output->descriptors = bytes;
output->descriptor_bytes = count * 128U * sizeof(float);
return LARDON3D_FEATURE_EXTRACT_OK;
}
extern "C" Lardon3DFeatureExtractResult lardon3d_feature_extract_sift(
const char *path, const Lardon3DSiftExtractorParameters *p, Lardon3DExtractedFeatures *out) {
if (out) std::memset(out, 0, sizeof(*out));
if (!path || !path[0] || !out || !lardon3d_sift_extractor_parameters_valid(p))
return LARDON3D_FEATURE_EXTRACT_INVALID_ARGUMENT;
struct stat information{};
if (lstat(path, &information) != 0)
return errno == ENOENT ? LARDON3D_FEATURE_EXTRACT_IMAGE_NOT_FOUND
: LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
if (!S_ISREG(information.st_mode) || S_ISLNK(information.st_mode))
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
try {
cv::Mat image = cv::imread(path, cv::IMREAD_GRAYSCALE);
uint64_t pixels = image.empty() ? 0 : static_cast<uint64_t>(image.cols) * image.rows;
if (image.empty() || image.cols <= 0 || image.rows <= 0 ||
pixels > LARDON3D_FEATURE_MAX_IMAGE_PIXELS)
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
uint32_t candidates =
std::min(static_cast<uint32_t>(LARDON3D_FEATURE_MAX_FEATURES), p->max_features * 2U);
cv::Ptr<cv::SIFT> extractor = cv::SIFT::create(
static_cast<int>(candidates), static_cast<int>(p->octave_layers), p->contrast_threshold,
p->edge_threshold, p->sigma, true);
std::vector<cv::KeyPoint> points;
cv::Mat descriptors;
extractor->detectAndCompute(image, cv::noArray(), points, descriptors);
if ((!points.empty() && (descriptors.type() != CV_32FC1 || descriptors.cols != 128 ||
descriptors.rows != static_cast<int>(points.size()))) ||
points.size() > 65536U)
return LARDON3D_FEATURE_EXTRACT_ERROR;
std::vector<size_t> selected;
std::vector<uint32_t> cells;
if (!select_sift_points(image, points, p, selected, cells))
return LARDON3D_FEATURE_EXTRACT_ERROR;
Lardon3DFeatureExtractResult copied = copy_sift_output(points, descriptors, selected, p, out);
if (copied != LARDON3D_FEATURE_EXTRACT_OK) return copied;
uint32_t occupied = static_cast<uint32_t>(
std::count_if(cells.begin(), cells.end(), [](uint32_t value) { return value > 0; }));
size_t cell_count = cells.size();
out->image_width = static_cast<uint32_t>(image.cols);
out->image_height = static_cast<uint32_t>(image.rows);
out->quality = {occupied, static_cast<uint32_t>(cell_count),
static_cast<double>(occupied) / static_cast<double>(cell_count),
pixels ? static_cast<double>(selected.size()) * 1000000.0 /
static_cast<double>(pixels)
: 0.0};
return LARDON3D_FEATURE_EXTRACT_OK;
} catch (const cv::Exception &) {
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
} catch (const std::bad_alloc &) {
return LARDON3D_FEATURE_EXTRACT_OUT_OF_MEMORY;
} catch (...) {
return LARDON3D_FEATURE_EXTRACT_ERROR;
}
}
extern "C" void lardon3d_extracted_features_destroy(Lardon3DExtractedFeatures *features) {
if (!features) {
return;
}
std::free(features->keypoints);
std::free(features->descriptors);
std::memset(features, 0, sizeof(*features));
}
extern "C" Lardon3DFeatureExtractResult
lardon3d_feature_extract_orb(const char *path, const Lardon3DFeatureExtractorParameters *parameters,
Lardon3DExtractedFeatures *features) {
if (features) {
std::memset(features, 0, sizeof(*features));
}
if (!path || !path[0] || !features || !lardon3d_feature_extractor_parameters_valid(parameters)) {
return LARDON3D_FEATURE_EXTRACT_INVALID_ARGUMENT;
}
struct stat information{};
if (lstat(path, &information) != 0) {
return errno == ENOENT ? LARDON3D_FEATURE_EXTRACT_IMAGE_NOT_FOUND
: LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
}
if (!S_ISREG(information.st_mode) || S_ISLNK(information.st_mode)) {
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
}
try {
cv::Mat image = cv::imread(path, cv::IMREAD_GRAYSCALE);
if (image.empty()) {
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
}
if (image.cols <= 0 || image.rows <= 0 ||
static_cast<uint64_t>(image.cols) * static_cast<uint64_t>(image.rows) >
LARDON3D_FEATURE_MAX_IMAGE_PIXELS) {
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
}
cv::Ptr<cv::ORB> extractor =
cv::ORB::create(static_cast<int>(parameters->max_features), 1.2F,
static_cast<int>(parameters->pyramid_levels), 31, 0, 2,
cv::ORB::HARRIS_SCORE, 31, static_cast<int>(parameters->fast_threshold));
std::vector<cv::KeyPoint> points;
cv::Mat descriptors;
extractor->detectAndCompute(image, cv::noArray(), points, descriptors);
if (points.size() > parameters->max_features ||
points.size() > std::numeric_limits<uint32_t>::max() ||
(!points.empty() && (descriptors.rows != static_cast<int>(points.size()) ||
descriptors.cols != LARDON3D_FEATURE_DESCRIPTOR_DIMENSION ||
descriptors.type() != CV_8UC1))) {
return LARDON3D_FEATURE_EXTRACT_ERROR;
}
size_t count = points.size();
Lardon3DFeatureKeypoint *keypoints =
count == 0 ? nullptr
: static_cast<Lardon3DFeatureKeypoint *>(
std::calloc(count, sizeof(Lardon3DFeatureKeypoint)));
unsigned char *bytes = count == 0 ? nullptr
: static_cast<unsigned char *>(std::malloc(
count * LARDON3D_FEATURE_DESCRIPTOR_DIMENSION));
if (count > 0 && (!keypoints || !bytes)) {
std::free(keypoints);
std::free(bytes);
return LARDON3D_FEATURE_EXTRACT_OUT_OF_MEMORY;
}
for (size_t index = 0; index < count; ++index) {
const cv::KeyPoint &point = points[index];
if (!std::isfinite(point.pt.x) || !std::isfinite(point.pt.y) || !std::isfinite(point.size) ||
!std::isfinite(point.angle) || !std::isfinite(point.response) || point.pt.x < 0.0F ||
point.pt.y < 0.0F || point.pt.x >= static_cast<float>(image.cols) ||
point.pt.y >= static_cast<float>(image.rows) || point.size <= 0.0F ||
point.angle < 0.0F || point.angle >= 360.0F) {
std::free(keypoints);
std::free(bytes);
return LARDON3D_FEATURE_EXTRACT_ERROR;
}
keypoints[index] = {point.pt.x, point.pt.y, point.size,
point.angle, point.response, point.octave};
std::memcpy(bytes + index * LARDON3D_FEATURE_DESCRIPTOR_DIMENSION,
descriptors.ptr(static_cast<int>(index)), LARDON3D_FEATURE_DESCRIPTOR_DIMENSION);
}
features->image_width = static_cast<uint32_t>(image.cols);
features->image_height = static_cast<uint32_t>(image.rows);
features->feature_count = static_cast<uint32_t>(count);
features->keypoints = keypoints;
features->descriptors = bytes;
features->descriptor_bytes = count * LARDON3D_FEATURE_DESCRIPTOR_DIMENSION;
return LARDON3D_FEATURE_EXTRACT_OK;
} catch (const cv::Exception &) {
return LARDON3D_FEATURE_EXTRACT_IMAGE_INVALID;
} catch (const std::bad_alloc &) {
return LARDON3D_FEATURE_EXTRACT_OUT_OF_MEMORY;
} catch (...) {
return LARDON3D_FEATURE_EXTRACT_ERROR;
}
}