Fix whash for images with invalid sizes, add remove_ll option for whash, remove multi hash

This commit is contained in:
simon987 2020-04-26 14:46:20 -04:00
parent b17c7f70a1
commit a37c08c11b
11 changed files with 38 additions and 301 deletions

View File

@ -25,12 +25,6 @@ add_library(
fastimagehash.cpp fastimagehash.h
)
add_library(
fastimagehash_debug
SHARED
fastimagehash.cpp fastimagehash.h
)
target_include_directories(
fastimagehash
PUBLIC
@ -39,14 +33,6 @@ target_include_directories(
${FFTW_INCLUDE_DIRS}
)
target_include_directories(
fastimagehash_debug
PUBLIC
${CMAKE_SOURCE_DIR}/thirdparty/wavelib/header/
${OpenCV_INCLUDE_DIRS}
${FFTW_INCLUDE_DIRS}
)
target_link_libraries(
fastimagehash
${OpenCV_LIBS}
@ -55,35 +41,18 @@ target_link_libraries(
pthread
)
target_link_libraries(
fastimagehash_debug
asan
${OpenCV_LIBS}
${FFTW_LIBRARIES}
wavelib
pthread
)
target_compile_options(
fastimagehash
PRIVATE
-fPIC
-Ofast
# -march=native
-march=native
-fno-stack-protector
-fomit-frame-pointer
-freciprocal-math
)
target_compile_options(
fastimagehash_debug
PRIVATE
-fPIC
-g
-fsanitize=address
)
add_executable(bm benchmark.cpp)
target_link_libraries(
bm
@ -99,7 +68,7 @@ set_target_properties(
add_executable(imhash imhash.c)
target_link_libraries(
imhash
${CMAKE_SOURCE_DIR}/libfastimagehash_debug.so
${CMAKE_SOURCE_DIR}/libfastimagehash.so
)
target_compile_options(
imhash
@ -109,13 +78,12 @@ target_compile_options(
set_target_properties(fastimagehash PROPERTIES PUBLIC_HEADER "fastimagehash.h")
INSTALL(
TARGETS fastimagehash fastimagehash_debug
TARGETS fastimagehash
LIBRARY DESTINATION /usr/lib/
PUBLIC_HEADER DESTINATION /usr/include/
)
add_dependencies(fastimagehash wavelib)
add_dependencies(fastimagehash_debug wavelib)
add_dependencies(bm fastimagehash)
add_dependencies(bm benchmark)
add_dependencies(imhash fastimagehash_debug)
add_dependencies(imhash fastimagehash)

View File

@ -18,6 +18,8 @@ replacement for C/C++.
<img src="bench/results/phash_large.png"/>
</p>
*[\*See all benchmarks](bench/README.md)*
### Example usage
```C
@ -30,15 +32,6 @@ int main() {
}
```
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

View File

@ -30,6 +30,3 @@ fastimagehash v0.1
![ahash_s](results/ahash_small.png)
![ahash_l](results/ahash_large.png)
**multi_hash**
![multi_s](results/multi_small.png)
![multi_l](results/multi_large.png)

View File

@ -37,15 +37,3 @@ print_result("ahash", timeit.timeit(
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
))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -34,6 +34,4 @@ for f in files:
method = "ahash"
if "whash" in m:
method = "whash"
if "multi" in m:
method = "multi"
print("%s_%s,%s" % (f, method, t))

View File

@ -35,7 +35,7 @@ static void BM_whash(benchmark::State &state) {
void *buf = load_test_file(&size);
for (auto _ : state) {
whash_mem(buf, size, tmp, state.range(), 0, "haar");
whash_mem(buf, size, tmp, state.range(), 0, 0, "haar");
}
free(buf);
@ -74,27 +74,11 @@ static void BM_mhash(benchmark::State &state) {
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, "haar");
}
multi_hash_destroy(m);
free(buf);
}
BENCHMARK(BM_phash)->ArgName("size")->Arg(8);
BENCHMARK(BM_whash)->ArgName("size")->Arg(8);
BENCHMARK(BM_dhash)->ArgName("size")->Arg(8);
BENCHMARK(BM_ahash)->ArgName("size")->Arg(8);
BENCHMARK(BM_mhash)->ArgName("size")->Arg(8);
BENCHMARK(BM_multi)->ArgName("size")->Arg(8);
int main(int argc, char **argv) {

View File

@ -10,6 +10,7 @@
#include <sys/stat.h>
static void init() __attribute__((constructor));
void init() {
fftw_make_planner_thread_safe();
}
@ -36,7 +37,7 @@ double median(uchar *arr, size_t len) {
std::sort(arr, arr + len);
if (len % 2 == 0) {
return (double)(arr[(len / 2) - 1] + arr[len / 2]) / 2;
return (double) (arr[(len / 2) - 1] + arr[len / 2]) / 2;
} else {
return arr[(len + 1 / 2)];
}
@ -200,19 +201,20 @@ int dhash_mem(void *buf, size_t buf_len, uchar *out, int hash_size) {
return FASTIMAGEHASH_OK;
}
int whash_file(const char *filepath, uchar *out, int hash_size, int img_scale, const char* wname) {
int whash_file(const char *filepath, uchar *out, int hash_size, int img_scale, int remove_max_ll, const char *wname) {
size_t size;
void *buf = load_file_in_mem(filepath, &size);
if (buf == nullptr) {
return FASTIMAGEHASH_READ_ERR;
}
int ret = whash_mem(buf, size, out, hash_size, img_scale, wname);
int ret = whash_mem(buf, size, out, hash_size, img_scale, remove_max_ll, wname);
free(buf);
return ret;
}
int whash_mem(void *buf, size_t buf_len, uchar *out, const int hash_size, int img_scale, const char *wname) {
int whash_mem(void *buf, size_t buf_len, uchar *out, const int hash_size, int img_scale, int remove_max_ll,
const char *wname) {
Mat im;
try {
im = imdecode(Mat(1, buf_len, CV_8UC1, buf), IMREAD_GRAYSCALE);
@ -246,6 +248,10 @@ int whash_mem(void *buf, size_t buf_len, uchar *out, const int hash_size, int im
int dwt_level = ll_max_level - level;
if (dwt_level < 1) {
dwt_level = 1;
}
try {
resize(im, im, Size(img_scale, img_scale), 0, 0, INTER_AREA);
} catch (Exception &e) {
@ -260,6 +266,21 @@ int whash_mem(void *buf, size_t buf_len, uchar *out, const int hash_size, int im
data[i] = (double) pixel[i] / 255;
}
if (remove_max_ll) {
// Remove low level frequency
wave_object w_haar_tmp = wave_init("haar");
wt2_object wt_haar_tmp = wt2_init(w_haar_tmp, "dwt", img_scale, img_scale, ll_max_level);
double *coeffs = dwt2(wt_haar_tmp, data);
coeffs[0] = 0;
idwt2(wt_haar_tmp, coeffs, data);
wt2_free(wt_haar_tmp);
wave_free(w_haar_tmp);
}
wave_object w = wave_init(wname);
wt2_object wt = wt2_init(w, "dwt", img_scale, img_scale, dwt_level);
@ -346,183 +367,3 @@ int phash_mem(void *buf, size_t buf_len, uchar *out, const int hash_size, int hi
}
return FASTIMAGEHASH_OK;
}
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) * 5);
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;
multi_hash->mhash = data + (hash_size + 1) * 4;
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, const char* wname) {
size_t size;
void *buf = load_file_in_mem(filepath, &size);
if (buf == nullptr) {
return FASTIMAGEHASH_READ_ERR;
}
int ret = multi_hash_mem(buf, size, out, hash_size, ph_highfreq_factor, wh_img_scale, wname);
free(buf);
return ret;
}
int multi_hash_mem(void *buf, size_t buf_len, multi_hash_t *out,
int hash_size, int ph_highfreq_factor, int wh_img_scale,
const char*wname) {
if (strcmp(wname, "haar") != 0 && strcmp(wname, "db4") != 0) {
throw std::invalid_argument("wname must be either of 'haar' or 'db4'");
}
Mat im;
try {
im = imdecode(Mat(1, buf_len, CV_8UC1, buf), IMREAD_GRAYSCALE);
} catch (Exception &e) {
return FASTIMAGEHASH_DECODE_ERR;
}
Mat ahash_im; // Also used for mhash!
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_DECODE_ERR;
}
auto 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;
// mhash
uchar mhash_sorted [ahash_im.cols * ahash_im.rows];
mempcpy(mhash_sorted, pixel, endPixel);
double m_median = median(mhash_sorted, endPixel);
for (int i = 0; i < endPixel; i++) {
set_bit_at(out->ahash, i, pixel[i] > avg);
set_bit_at(out->mhash, i, pixel[i] > m_median);
}
//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;
}
wave_object w = wave_init(wname);
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);
}
wt2_free(wt);
wave_free(w);
free(coeffs);
delete[] pixels;
return FASTIMAGEHASH_OK;
}

View File

@ -1,37 +1,21 @@
#ifndef FASTIMAGEHASH_FASTIMAGEHASH_H
#define FASTIMAGEHASH_FASTIMAGEHASH_H
#define FASTIMAGEHASH_VERSION "3.0"
#define FASTIMAGEHASH_VERSION "4.0"
#include <stdio.h>
typedef unsigned char uchar;
typedef struct multi_hash {
uchar *ahash;
uchar *phash;
uchar *dhash;
uchar *whash;
uchar *mhash;
} multi_hash_t;
#ifdef __cplusplus
extern "C" {
#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, const char*wname);
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);
int multi_hash_mem(void *buf, size_t buf_len, multi_hash_t *out, int hash_size, int ph_highfreq_factor, int wh_img_scale, const char* wname);
int mhash_mem(void *buf, size_t buf_len,uchar *out, int hash_size);
int mhash_file(const char *filepath, uchar *out, int hash_size);
@ -44,9 +28,9 @@ int dhash_file(const char *filepath, uchar *out, int hash_size);
int dhash_mem(void *buf, size_t buf_len, uchar *out, int hash_size);
int whash_file(const char *filepath, uchar *out, int hash_size, int img_scale, const char* wname);
int whash_file(const char *filepath, uchar *out, int hash_size, int img_scale, int remove_max_ll, const char* wname);
int whash_mem(void *buf, size_t buf_len, uchar *out, int hash_size, int img_scale, const char*wname);
int whash_mem(void *buf, size_t buf_len, uchar *out, int hash_size, int img_scale, int remove_max_ll, const char*wname);
int phash_file(const char *buf, uchar *out, int hash_size, int highfreq_factor);

View File

@ -29,8 +29,8 @@ int main(int argc, char *argv[]) {
do_mhash = 1;
} else {
uchar hash[9];
char hashstr[17];
uchar hash[256];
char hashstr[512];
if (do_phash) {
if (phash_file(argv[i], hash, 8, 4) == 0) {
@ -51,7 +51,7 @@ int main(int argc, char *argv[]) {
}
}
if (do_whash) {
if (whash_file(argv[i], hash, 8, 0, "haar") == 0) {
if (whash_file(argv[i], hash, 8, 0, 1, "haar") == 0) {
hash_to_hex_string_reversed(hash, hashstr, 8);
printf("%s\tw:%s\n", argv[i], hashstr);
}
@ -62,22 +62,6 @@ int main(int argc, char *argv[]) {
printf("%s\tm:%s\n", argv[i], hashstr);
}
}
multi_hash_t *m = multi_hash_create(8);
multi_hash_file(argv[i], m, 8, 4, 0, "haar");
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);
hash_to_hex_string_reversed(m->mhash, hashstr, 8);
printf("%s\tmm:%s\n", argv[i], hashstr);
multi_hash_destroy(m);
}
}
}