aboutsummaryrefslogblamecommitdiff
path: root/xsig/src/signature.cpp
blob: 0f0b2e9e08a0e08700b04edaaec52c6c3c9f4f4e (plain) (tree)
1
2
3
4
5
6
7
8
9

                  





                                                               
                                                           
   
                        

                    
                  









                                
 
                                      
 








                               










                                     
                         







                                                                
                      














                                                                           
                     

                                   


                                                         



























                                                                
                         
























                                                                    
                                           








                                            
                     



                           
                                               













                                                  
                                               





















                                                              
                                                                                                                            
        
                                                                                                                                









                                                              










                                                    
                                 
                                                  

                                  




                            


                             






                                  
                                                                     




                                                    
                                                                               




                                                    
                                                                            


                      


                                                   
                       









                                                                                
                                     










                                                                   
                                                                                      
 
                                     
                                           


                 
                                                                                 
        
                     

                                                                               








                              
                                                                           
 
                                     

                   
                       



                                            

                                 




                                                 
                                                         

 
                                                                           

                                                       
                                               

 


                                                                                              
                                               

 


                                         



                                                                      








                                                               
 
//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 <algorithm>
#include <fstream>
#include <cmath>
#include <cstdint>
#include <vector>

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#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<float> lv;
    compressed_vector<uint8_t, 3> ct;
    std::vector<uint8_t> 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<float>(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<float>(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<double> lights;
    std::vector<double> 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\n"); return -1;}
    return p->length();
}

double signature::distance(const signature &o) const
{
    if (!p || !o.p) {fprintf(stderr, "distance: null signature\n"); return -1;}
    return p->distance(*o.p);
}

bool signature::operator==(const signature &o) const
{
    if (!p || !o.p) {fprintf(stderr, "eq: null signature\n"); return false;}
    return *p == *o.p;
}

std::string signature::to_string() const
{
    if (!p || !p->compressed) return std::string();
    base64_encoder 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;
    base64_decoder 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)
{
    if (!m->data) return signature();
    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)
{
    if (!m->data) return signature();
    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<uint8_t, 3>{}(sig.p->ct);
    else
    {
        size_t ret = 0;
        for (uint8_t &v : sig.p->uct)
            ret ^= v + 0x9e3779b9 + (ret << 6) + (ret >> 2);
        return ret;
    }
}