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


-**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 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