From 4401f681d33f534a7d7ef8f4f940bd54b60710c3 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Sun, 18 Sep 2022 01:52:26 -0400 Subject: Move stuff around to accommodate new family members. --- xsig/src/signature.cpp | 338 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 xsig/src/signature.cpp (limited to 'xsig/src/signature.cpp') diff --git a/xsig/src/signature.cpp b/xsig/src/signature.cpp new file mode 100644 index 0000000..b912198 --- /dev/null +++ b/xsig/src/signature.cpp @@ -0,0 +1,338 @@ +//Chris Xiong 2022 +//License: MPL-2.0 +/* Based on + * H. Chi Wong, M. Bern and D. Goldberg, + * "An image signature for any kind of image," Proceedings. + * International Conference on Image Processing, 2002, pp. I-I, + * doi: 10.1109/ICIP.2002.1038047. + * and + * libpuzzle (also an implementation of the article above). + */ +#include "signature.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "compressed_vector.hpp" +#include "imageutil.hpp" + +static signature_config _default_cfg = +{ + 9, //slices + 3, //blur_window + 2, //min_window + true, //crop + false, //comp + 0.5, //pr + 1./128,//noise_threshold + 0.05, //contrast_threshold + 0.25 //max_cropping +}; + +class signature_priv +{ +private: + cv::Mat fimg; + cv::Mat lch; + std::vector lv; + compressed_vector ct; + std::vector uct; + bool compressed; + signature_config cfg; +public: + float get_light_charistics_cell(int x, int y, int w, int h); + void get_light_charistics(); + void get_light_variance(); + void get_signature(); + double length() const; + double distance(const signature_priv &o) const; + bool operator==(const signature_priv &o) const; + void dump() const; + friend class signature; + friend struct signature_hash; +}; + +float signature_priv::get_light_charistics_cell(int x, int y, int w, int h) +{ + return cv::mean(fimg(cv::Range(y, y + h), cv::Range(x, x + w)))[0]; +} + +void signature_priv::get_light_charistics() +{ + double windowx, windowy; + int iw, ih, slc; + iw = fimg.size().width; + ih = fimg.size().height; + slc = cfg.slices; + windowx = iw / (double)slc / 2; + windowy = ih / (double)slc / 2; + int windows = round(std::min(iw, ih) / slc * cfg.pr); + if (windows < cfg.min_window) + windows = cfg.min_window; + double ww = (iw - 1) / (slc + 1.); + double wh = (ih - 1) / (slc + 1.); + double wxs = 0, wys = 0; + if (windows < ww) wxs = (ww - windows) / 2.; + if (windows < wh) wys = (wh - windows) / 2.; + lch.create(slc, slc, CV_32F); + float *lp = lch.ptr(0); + for (int i = 0; i < slc; ++i) + { + for (int j = 0; j < slc; ++j) + { + double cwx, cwy; + cwx = i * (iw - 1) / (slc + 1.) + windowx; + cwy = j * (ih - 1) / (slc + 1.) + windowy; + int x = (int)round(cwx + wxs); + int y = (int)round(cwy + wys); + int cww, cwh; + cww = (iw - x < windows) ? 1 : windows; + cwh = (ih - y < windows) ? 1 : windows; + *(lp++) = get_light_charistics_cell(x, y, cww, cwh); + } + } +} + +void signature_priv::get_light_variance() +{ + const int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + const int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; + int slc = cfg.slices; + float *lp = lch.ptr(0); + for (int x = 0; x < slc; ++x) + { + for (int y = 0; y < slc; ++y) + { + for (int k = 0; k < 8; ++k) + { + int nx = x + dx[k]; + int ny = y + dy[k]; + if (nx < 0 || ny < 0 || nx >= slc || ny >= slc) + lv.push_back(0); + else + lv.push_back(*lp - *(lp + dx[k] * slc + dy[k])); + } + ++lp; + } + } +} + +void signature_priv::get_signature() +{ + std::vector lights; + std::vector darks; + for (float &l : lv) + { + if (fabsf(l) > cfg.noise_threshold) + { + if (l > 0) + lights.push_back(l); + else + darks.push_back(l); + } + } + double lth = image_util::median(lights); + double dth = image_util::median(darks); + if (cfg.compress) + { + compressed = true; + for (float &l : lv) + { + if (fabsf(l) > cfg.noise_threshold) + { + if (l > 0) + ct.push_back(l > lth ? 4 : 3); + else + ct.push_back(l < dth ? 0 : 1); + } + else ct.push_back(2); + } + } + else + { + compressed = false; + for (float &l : lv) + { + if (fabsf(l) > cfg.noise_threshold) + { + if (l > 0) + uct.push_back(l > lth ? 4 : 3); + else + uct.push_back(l < dth ? 0 : 1); + } + else uct.push_back(2); + } + } +} + +double signature_priv::length() const +{ + if (compressed) + return image_util::length(ct, (uint8_t)2); + else + return image_util::length(uct, 2); +} + +double signature_priv::distance(const signature_priv &o) const +{ + if (compressed && o.compressed) + return image_util::distance(ct, o.ct) / (image_util::length(ct, uint8_t(2)) + image_util::length(o.ct, uint8_t(2))); + else + return image_util::distance(uct, o.uct) / (image_util::length(uct, uint8_t(2)) + image_util::length(o.uct, uint8_t(2))); +} + +bool signature_priv::operator==(const signature_priv &o) const +{ + if (compressed && o.compressed) + return ct == o.ct; + else + return uct == o.uct; +} + +void signature_priv::dump() const +{ + if (!compressed) + for (auto &x : this->uct) + printf("%u ", x); + else + for (size_t i = 0; i < this->ct.size(); ++i) + printf("%u ", this->ct.get(i)); + printf("\n"); +} + +signature::signature() = default; +signature::signature(signature_priv* _p) : p(_p){} +signature::~signature() = default; + +void signature::dump() const +{ + if (p) p->dump(); +} + +bool signature::valid() const +{return (bool)p;} + +signature signature::clone() const +{ + return signature(*this); +} + +double signature::length() const +{ + if (!p) {fprintf(stderr, "length: null signature"); return -1;} + return p->length(); +} + +double signature::distance(const signature &o) const +{ + if (!p || !o.p) {fprintf(stderr, "distance: null signature"); return -1;} + return p->distance(*o.p); +} + +bool signature::operator==(const signature &o) const +{ + if (!p || !o.p) {fprintf(stderr, "eq: null signature"); return false;} + return *p == *o.p; +} + +std::string signature::to_string() const +{ + if (!p || !p->compressed) return std::string(); + Base64Encoder enc; + size_t sz = p->ct.size(); + enc.encode_data(&p->cfg, sizeof(signature_config)); + enc.encode_data(&sz, sizeof(size_t)); + enc.encode_data(p->ct.internal_data(), p->ct.internal_container_size() * 8); + return enc.finalize(); +} + +signature signature::from_string(std::string &&s) +{ + signature_priv *p = new signature_priv; + Base64Decoder dec(std::move(s)); + size_t sz; + p->compressed = true; + size_t s1 = dec.decode_data(&p->cfg, sizeof(signature_config)); + size_t s2 = dec.decode_data(&sz, sizeof(size_t)); + size_t s3 = dec.decoded_length() - s1 - s2; + p->ct.internal_set_size(sz); + p->ct.internal_container_resize(s3 / 8); + dec.decode_data(p->ct.internal_data(), s3); + return signature(p); +} + +signature signature::from_preprocessed_matrix(cv::Mat *m, const signature_config &cfg) +{ + signature_priv *p = new signature_priv; + p->cfg = cfg; + + if (cfg.crop) + p->fimg = image_util::crop(*m, cfg.contrast_threshold, cfg.max_cropping); + else + p->fimg = *m; + if (cfg.blur_window > 1) + cv::blur(p->fimg, p->fimg, cv::Size(cfg.blur_window, cfg.blur_window)); + p->get_light_charistics(); + p->get_light_variance(); + p->get_signature(); + p->fimg.release(); + p->lch.release(); + p->lv.clear(); + return signature(p); +} + +signature signature::from_cvmatrix(cv::Mat *m, const signature_config &cfg) +{ + cv::Mat ma, bw; + double sc = 1; + switch (m->depth()) + { + case CV_8U: sc = 1. / 255; break; + case CV_16U: sc = 1. / 65535; break; + } + m->convertTo(ma, CV_32F, sc); + if (m->channels() == 4) + ma = image_util::blend_white(ma); + if (ma.channels() == 3) + cv::cvtColor(ma, bw, cv::COLOR_RGB2GRAY); + else + bw = ma; + return signature::from_preprocessed_matrix(&bw, cfg); +} + +signature signature::from_file(const char *fn, const signature_config &cfg) +{ + cv::Mat img = cv::imread(fn, cv::IMREAD_UNCHANGED); + return signature::from_cvmatrix(&img, cfg); +} + +signature signature::from_path(const std::filesystem::path &path, const signature_config &cfg) +{ + cv::Mat img = image_util::imread_path(path, cv::IMREAD_UNCHANGED); + return signature::from_cvmatrix(&img, cfg); +} + +signature_config signature::default_cfg() +{ + return _default_cfg; +} + +size_t signature_hash::operator()(signature const& sig) const noexcept +{ + if (sig.p->compressed) + return compressed_vector_hash{}(sig.p->ct); + else + { + size_t ret = 0; + for (uint8_t &v : sig.p->uct) + ret ^= v + 0x9e3779b9 + (ret << 6) + (ret >> 2); + return ret; + } +} -- cgit v1.2.3