From a37c08c11ba097742d2eb68d55f55e29ec9e0e7b Mon Sep 17 00:00:00 2001 From: simon987 <me@simon987.net> Date: Sun, 26 Apr 2020 14:46:20 -0400 Subject: [PATCH] Fix whash for images with invalid sizes, add remove_ll option for whash, remove multi hash --- CMakeLists.txt | 40 +------ README.md | 11 +- bench/README.md | 3 - bench/benchmark.py | 12 -- bench/results/multi_large.png | Bin 17671 -> 0 bytes bench/results/multi_small.png | Bin 16164 -> 0 bytes bench/run.py | 2 - benchmark.cpp | 18 +-- fastimagehash.cpp | 209 ++++------------------------------ fastimagehash.h | 22 +--- imhash.c | 22 +--- 11 files changed, 38 insertions(+), 301 deletions(-) delete mode 100644 bench/results/multi_large.png delete mode 100644 bench/results/multi_small.png diff --git a/CMakeLists.txt b/CMakeLists.txt index d9e6570..e50d990 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index 625e106..bcd3aaa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bench/README.md b/bench/README.md index 7d4842c..b0bb287 100644 --- a/bench/README.md +++ b/bench/README.md @@ -30,6 +30,3 @@ fastimagehash v0.1   -**multi_hash** - - diff --git a/bench/benchmark.py b/bench/benchmark.py index b937dce..6cfc56d 100644 --- a/bench/benchmark.py +++ b/bench/benchmark.py @@ -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 -)) diff --git a/bench/results/multi_large.png b/bench/results/multi_large.png deleted file mode 100644 index 3d86b5577ff19c0a63d24d697084abcf4f1402fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17671 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 diff --git a/bench/results/multi_small.png b/bench/results/multi_small.png deleted file mode 100644 index acf68a9e174fef4d1406a9155910b1714aa1c01a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16164 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 Q`yE13R8FK^$ROyy0flhervLx| diff --git a/bench/run.py b/bench/run.py index 1fb061b..6abda3b 100644 --- a/bench/run.py +++ b/bench/run.py @@ -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)) diff --git a/benchmark.cpp b/benchmark.cpp index dc61db4..0e4ff96 100644 --- a/benchmark.cpp +++ b/benchmark.cpp @@ -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) { diff --git a/fastimagehash.cpp b/fastimagehash.cpp index 1e48e62..185dcd6 100644 --- a/fastimagehash.cpp +++ b/fastimagehash.cpp @@ -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; -} diff --git a/fastimagehash.h b/fastimagehash.h index 11d69b6..ec62585 100644 --- a/fastimagehash.h +++ b/fastimagehash.h @@ -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); diff --git a/imhash.c b/imhash.c index 8f43de2..35d31c8 100644 --- a/imhash.c +++ b/imhash.c @@ -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); } } } \ No newline at end of file