mirror of
https://github.com/simon987/fastimagehash.git
synced 2025-04-10 14:16:49 +00:00
Add multi_hash (wip/untested)
This commit is contained in:
parent
514e53b934
commit
5ebbcf2845
31
README.md
31
README.md
@ -13,21 +13,42 @@ replacement for C/C++.
|
|||||||
<img src="bench/results/phash_large.png"/>
|
<img src="bench/results/phash_large.png"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
*[\*benchmarks](bench/)*
|
|
||||||
|
|
||||||
### Example usage
|
### Example usage
|
||||||
|
|
||||||
```C++
|
```C
|
||||||
#include "fastimagehash.h"
|
#include "fastimagehash.h"
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
// TODO
|
unsigned char result[HASH_SIZE];
|
||||||
|
|
||||||
|
phash_file("image.jpeg", result, HASH_SIZE, HIGHFREQ_FACTOR);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For slight additional performance gains, `libfastimagehash` can
|
||||||
|
compute all hashes at once instead of decoding the same
|
||||||
|
image at each step.
|
||||||
|
<p align="center">
|
||||||
|
<img src="bench/results/multi_small.png"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
*[\*See all benchmarks](bench/)*
|
||||||
|
|
||||||
|
|
||||||
### Build from source
|
### Build from source
|
||||||
|
|
||||||
// TODO
|
```bash
|
||||||
|
# Download dependencies
|
||||||
|
apt install libopencv-dev libfftw3-dev cmake
|
||||||
|
|
||||||
|
# Checkout source
|
||||||
|
git clone --recursive https://github.com/simon987/fastimagehash
|
||||||
|
|
||||||
|
# Build
|
||||||
|
cmake .
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
**Built with**
|
**Built with**
|
||||||
* [opencv](https://github.com/opencv) for image decoding & resizing
|
* [opencv](https://github.com/opencv) for image decoding & resizing
|
||||||
|
@ -29,3 +29,7 @@ fastimagehash v0.1
|
|||||||
**ahash**
|
**ahash**
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
**multi_hash**
|
||||||
|

|
||||||
|

|
||||||
|
@ -36,3 +36,16 @@ print_result("ahash", timeit.timeit(
|
|||||||
stmt="average_hash(Image.open('%s'), hash_size=%d)" % (IMAGE, SIZE),
|
stmt="average_hash(Image.open('%s'), hash_size=%d)" % (IMAGE, SIZE),
|
||||||
number=COUNT
|
number=COUNT
|
||||||
))
|
))
|
||||||
|
|
||||||
|
print_result("multi", timeit.timeit(
|
||||||
|
setup="from imagehash import average_hash,phash,whash,dhash \n"
|
||||||
|
"from PIL import Image",
|
||||||
|
stmt="im = Image.open('%s');"
|
||||||
|
"size = %d;"
|
||||||
|
"average_hash(im.copy(), hash_size=size);"
|
||||||
|
"phash(im.copy(), hash_size=size);"
|
||||||
|
"whash(im.copy(), hash_size=size, remove_max_haar_ll=False);"
|
||||||
|
"dhash(im.copy(), hash_size=size);"
|
||||||
|
% (IMAGE, SIZE),
|
||||||
|
number=COUNT
|
||||||
|
))
|
||||||
|
BIN
bench/results/multi_large.png
Normal file
BIN
bench/results/multi_large.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
bench/results/multi_small.png
Normal file
BIN
bench/results/multi_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -34,4 +34,6 @@ for f in files:
|
|||||||
method = "ahash"
|
method = "ahash"
|
||||||
if "whash" in m:
|
if "whash" in m:
|
||||||
method = "whash"
|
method = "whash"
|
||||||
|
if "multi" in m:
|
||||||
|
method = "multi"
|
||||||
print("%s_%s,%s" % (f, method, t))
|
print("%s_%s,%s" % (f, method, t))
|
||||||
|
@ -67,10 +67,28 @@ static void BM_ahash(benchmark::State &state) {
|
|||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void BM_multi(benchmark::State &state) {
|
||||||
|
|
||||||
|
size_t size;
|
||||||
|
void *buf = load_test_file(&size);
|
||||||
|
|
||||||
|
multi_hash_t *m = multi_hash_create(state.range());
|
||||||
|
|
||||||
|
for (auto _ : state) {
|
||||||
|
multi_hash_file(filepath, m, state.range(), 4, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_hash_destroy(m);
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BENCHMARK(BM_phash)->ArgName("size")->Arg(8);
|
BENCHMARK(BM_phash)->ArgName("size")->Arg(8);
|
||||||
BENCHMARK(BM_whash)->ArgName("size")->Arg(8);
|
BENCHMARK(BM_whash)->ArgName("size")->Arg(8);
|
||||||
BENCHMARK(BM_dhash)->ArgName("size")->Arg(8);
|
BENCHMARK(BM_dhash)->ArgName("size")->Arg(8);
|
||||||
BENCHMARK(BM_ahash)->ArgName("size")->Arg(8);
|
BENCHMARK(BM_ahash)->ArgName("size")->Arg(8);
|
||||||
|
BENCHMARK(BM_multi)->ArgName("size")->Arg(8);
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
@ -119,7 +119,7 @@ int ahash_mem(void *buf, uchar *out, size_t buf_len, int hash_size) {
|
|||||||
|
|
||||||
uchar *pixel = im.ptr(0);
|
uchar *pixel = im.ptr(0);
|
||||||
int endPixel = im.cols * im.rows;
|
int endPixel = im.cols * im.rows;
|
||||||
for (int i = 0; i <= endPixel; i++) {
|
for (int i = 0; i < endPixel; i++) {
|
||||||
set_bit_at(out, i, pixel[i] > avg);
|
set_bit_at(out, i, pixel[i] > avg);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -213,7 +213,7 @@ int whash_mem(void *buf, uchar *out, size_t buf_len, int hash_size, int img_scal
|
|||||||
|
|
||||||
uchar *pixel = im.ptr(0);
|
uchar *pixel = im.ptr(0);
|
||||||
const int endPixel = im.cols * im.rows;
|
const int endPixel = im.cols * im.rows;
|
||||||
for (int i = 0; i <= endPixel; i++) {
|
for (int i = 0; i < endPixel; i++) {
|
||||||
data[i] = (double) pixel[i] / 255;
|
data[i] = (double) pixel[i] / 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ int phash_mem(void *buf, uchar *out, size_t buf_len, int hash_size, int highfreq
|
|||||||
|
|
||||||
uchar *pixel = im.ptr(0);
|
uchar *pixel = im.ptr(0);
|
||||||
int endPixel = im.cols * im.rows;
|
int endPixel = im.cols * im.rows;
|
||||||
for (int i = 0; i <= endPixel; i++) {
|
for (int i = 0; i < endPixel; i++) {
|
||||||
pixels[i] = (double) pixel[i] / 255;
|
pixels[i] = (double) pixel[i] / 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,3 +302,169 @@ int phash_mem(void *buf, uchar *out, size_t buf_len, int hash_size, int highfreq
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
multi_hash_t *multi_hash_create(int hash_size) {
|
||||||
|
auto multi_hash = (multi_hash_t *) malloc(sizeof(multi_hash_t));
|
||||||
|
auto data = (uchar *) malloc((hash_size + 1) * 4);
|
||||||
|
|
||||||
|
multi_hash->ahash = data;
|
||||||
|
multi_hash->phash = data + (hash_size + 1);
|
||||||
|
multi_hash->dhash = data + (hash_size + 1) * 2;
|
||||||
|
multi_hash->whash = data + (hash_size + 1) * 3;
|
||||||
|
|
||||||
|
return multi_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
void multi_hash_destroy(multi_hash_t *h) {
|
||||||
|
free(h->ahash);
|
||||||
|
free(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
int multi_hash_file(const char *filepath, multi_hash_t *out, int hash_size,
|
||||||
|
int ph_highfreq_factor, int wh_img_scale) {
|
||||||
|
|
||||||
|
size_t size;
|
||||||
|
void *buf = load_file_in_mem(filepath, &size);
|
||||||
|
|
||||||
|
if (buf == nullptr) {
|
||||||
|
return FASTIMAGEHASH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = multi_hash_mem(buf, out, size, hash_size, ph_highfreq_factor, wh_img_scale);
|
||||||
|
free(buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int multi_hash_mem(void *buf, multi_hash_t *out, size_t buf_len,
|
||||||
|
int hash_size, int ph_highfreq_factor, int wh_img_scale) {
|
||||||
|
|
||||||
|
Mat im;
|
||||||
|
try {
|
||||||
|
im = imdecode(Mat(1, buf_len, CV_8UC1, buf), IMREAD_GRAYSCALE);
|
||||||
|
} catch (Exception &e) {
|
||||||
|
return FASTIMAGEHASH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat ahash_im;
|
||||||
|
Mat dhash_im;
|
||||||
|
Mat phash_im;
|
||||||
|
Mat whash_im;
|
||||||
|
|
||||||
|
int ph_img_scale = hash_size * ph_highfreq_factor;
|
||||||
|
|
||||||
|
if ((hash_size & (hash_size - 1)) != 0) {
|
||||||
|
throw std::invalid_argument("hash_size must be a power of two");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wh_img_scale != 0) {
|
||||||
|
if ((wh_img_scale & (wh_img_scale - 1)) != 0) {
|
||||||
|
throw std::invalid_argument("wh_img_scale must be a power of two");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int image_natural_scale = (int) pow(2, (int) log2(MIN(im.rows, im.cols)));
|
||||||
|
wh_img_scale = MAX(image_natural_scale, hash_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ll_max_level = (int) log2(wh_img_scale);
|
||||||
|
int level = (int) log2(hash_size);
|
||||||
|
|
||||||
|
if (ll_max_level < level) {
|
||||||
|
throw std::invalid_argument("hash_size in a wrong range");
|
||||||
|
}
|
||||||
|
|
||||||
|
int dwt_level = ll_max_level - level;
|
||||||
|
|
||||||
|
try {
|
||||||
|
im = imdecode(Mat(1, buf_len, CV_8UC1, buf), IMREAD_GRAYSCALE);
|
||||||
|
|
||||||
|
resize(im, ahash_im, Size(hash_size, hash_size), 0, 0, INTER_AREA);
|
||||||
|
resize(im, dhash_im, Size(hash_size + 1, hash_size), 0, 0, INTER_AREA);
|
||||||
|
resize(im, whash_im, Size(wh_img_scale, wh_img_scale), 0, 0, INTER_AREA);
|
||||||
|
resize(im, phash_im, Size(ph_img_scale, ph_img_scale), 0, 0, INTER_AREA);
|
||||||
|
|
||||||
|
} catch (Exception &e) {
|
||||||
|
return FASTIMAGEHASH_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
double *pixels = new double[MAX(ph_img_scale, wh_img_scale) * MAX(ph_img_scale, wh_img_scale)];
|
||||||
|
|
||||||
|
// ahash
|
||||||
|
double avg = mean(ahash_im).val[0];
|
||||||
|
|
||||||
|
uchar *pixel = ahash_im.ptr(0);
|
||||||
|
int endPixel = ahash_im.cols * ahash_im.rows;
|
||||||
|
for (int i = 0; i < endPixel; i++) {
|
||||||
|
set_bit_at(out->ahash, i, pixel[i] > avg);
|
||||||
|
}
|
||||||
|
|
||||||
|
//dhash
|
||||||
|
int offset = 0;
|
||||||
|
for (int i = 0; i < dhash_im.rows; ++i) {
|
||||||
|
pixel = dhash_im.ptr(i);
|
||||||
|
|
||||||
|
for (int j = 1; j < dhash_im.cols; ++j) {
|
||||||
|
set_bit_at(out->dhash, offset++, pixel[j] > pixel[j - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//phash
|
||||||
|
pixel = phash_im.ptr(0);
|
||||||
|
endPixel = phash_im.cols * phash_im.rows;
|
||||||
|
for (int i = 0; i < endPixel; i++) {
|
||||||
|
pixels[i] = (double) pixel[i] / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
double dct_out[ph_img_scale * ph_img_scale];
|
||||||
|
fftw_plan plan = fftw_plan_r2r_2d(
|
||||||
|
ph_img_scale, ph_img_scale,
|
||||||
|
pixels, dct_out,
|
||||||
|
FFTW_REDFT10, FFTW_REDFT10, // DCT-II
|
||||||
|
FFTW_ESTIMATE
|
||||||
|
);
|
||||||
|
fftw_execute(plan);
|
||||||
|
fftw_destroy_plan(plan);
|
||||||
|
|
||||||
|
double dct_lowfreq[hash_size * hash_size];
|
||||||
|
double sorted[hash_size * hash_size];
|
||||||
|
|
||||||
|
int ptr_low = 0;
|
||||||
|
int ptr = 0;
|
||||||
|
for (int i = 0; i < hash_size; ++i) {
|
||||||
|
for (int j = 0; j < hash_size; ++j) {
|
||||||
|
dct_lowfreq[ptr_low] = dct_out[ptr];
|
||||||
|
sorted[ptr_low] = dct_out[ptr];
|
||||||
|
ptr_low += 1;
|
||||||
|
ptr += 1;
|
||||||
|
}
|
||||||
|
ptr += (ph_img_scale - hash_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
double med = median(sorted, hash_size * hash_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < hash_size * hash_size; ++i) {
|
||||||
|
set_bit_at(out->phash, i, dct_lowfreq[i] > med);
|
||||||
|
}
|
||||||
|
|
||||||
|
//whash
|
||||||
|
pixel = whash_im.ptr(0);
|
||||||
|
endPixel = whash_im.cols * whash_im.rows;
|
||||||
|
for (int i = 0; i < endPixel; i++) {
|
||||||
|
pixels[i] = (double) pixel[i] / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: haar option
|
||||||
|
wave_object w = wave_init("haar");
|
||||||
|
wt2_object wt = wt2_init(w, "dwt", wh_img_scale, wh_img_scale, dwt_level);
|
||||||
|
|
||||||
|
double *coeffs = dwt2(wt, pixels);
|
||||||
|
|
||||||
|
memcpy(sorted, coeffs, sizeof(double) * (hash_size * hash_size));
|
||||||
|
|
||||||
|
med = median(sorted, hash_size * hash_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < hash_size * hash_size; ++i) {
|
||||||
|
set_bit_at(out->whash, i, coeffs[i] > med);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] pixels;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -8,10 +8,25 @@
|
|||||||
|
|
||||||
typedef unsigned char uchar;
|
typedef unsigned char uchar;
|
||||||
|
|
||||||
|
typedef struct multi_hash {
|
||||||
|
uchar *ahash;
|
||||||
|
uchar *phash;
|
||||||
|
uchar *dhash;
|
||||||
|
uchar *whash;
|
||||||
|
} multi_hash_t;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
multi_hash_t *multi_hash_create(int hash_size);
|
||||||
|
|
||||||
|
void multi_hash_destroy(multi_hash_t *h);
|
||||||
|
|
||||||
|
int multi_hash_file(const char *filepath, multi_hash_t *out, int hash_size, int ph_highfreq_factor, int wh_img_scale);
|
||||||
|
|
||||||
|
int multi_hash_mem(void *buf, multi_hash_t *out, size_t buf_len, int hash_size, int ph_highfreq_factor, int wh_img_scale);
|
||||||
|
|
||||||
void hash_to_hex_string_reversed(const uchar *h, char *out, int hash_size);
|
void hash_to_hex_string_reversed(const uchar *h, char *out, int hash_size);
|
||||||
|
|
||||||
void hash_to_hex_string(const uchar *h, char *out, int hash_size);
|
void hash_to_hex_string(const uchar *h, char *out, int hash_size);
|
||||||
|
12
imhash.c
12
imhash.c
@ -54,6 +54,18 @@ int main(int argc, char *argv[]) {
|
|||||||
printf("%s\tw:%s\n", argv[i], hashstr);
|
printf("%s\tw:%s\n", argv[i], hashstr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
multi_hash_t *m = multi_hash_create(8);
|
||||||
|
multi_hash_file(argv[i], m, 8, 4, 0);
|
||||||
|
|
||||||
|
hash_to_hex_string_reversed(m->phash, hashstr, 8);
|
||||||
|
printf("%s\tmp:%s\n", argv[i], hashstr);
|
||||||
|
hash_to_hex_string_reversed(m->ahash, hashstr, 8);
|
||||||
|
printf("%s\tma:%s\n", argv[i], hashstr);
|
||||||
|
hash_to_hex_string_reversed(m->dhash, hashstr, 8);
|
||||||
|
printf("%s\tmd:%s\n", argv[i], hashstr);
|
||||||
|
hash_to_hex_string_reversed(m->whash, hashstr, 8);
|
||||||
|
printf("%s\tmw:%s\n", argv[i], hashstr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user