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++.

+*[\*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. -

- -

- -*[\*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 ![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) 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 3d86b55..0000000 Binary files a/bench/results/multi_large.png and /dev/null differ diff --git a/bench/results/multi_small.png b/bench/results/multi_small.png deleted file mode 100644 index acf68a9..0000000 Binary files a/bench/results/multi_small.png and /dev/null differ 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 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 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