| 
1 | 1 | from collections.abc import Iterable  | 
2 | 2 | from math import sqrt  | 
3 |  | -from typing import TYPE_CHECKING  | 
4 | 3 | 
 
  | 
5 | 4 | import cv2  | 
6 | 5 | import Levenshtein  | 
 | 
22 | 21 | RANGES = (0, MAXRANGE, 0, MAXRANGE, 0, MAXRANGE)  | 
23 | 22 | MASK_SIZE_MULTIPLIER = ColorChannel.Alpha * MAXBYTE * MAXBYTE  | 
24 | 23 | MAX_VALUE = 1.0  | 
 | 24 | +CV2_PHASH_SIZE = 8  | 
25 | 25 | 
 
  | 
26 | 26 | 
 
  | 
27 | 27 | def compare_histograms(source: MatLike, capture: MatLike, mask: MatLike | None = None):  | 
@@ -90,41 +90,39 @@ def compare_template(source: MatLike, capture: MatLike, mask: MatLike | None = N  | 
90 | 90 |     return 1 - (min_val / max_error)  | 
91 | 91 | 
 
  | 
92 | 92 | 
 
  | 
93 |  | -try:  | 
94 |  | -    from scipy import fft  | 
95 |  | - | 
96 |  | -    def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: int = 4):  | 
97 |  | -        """Implementation copied from https://github.com/JohannesBuchner/imagehash/blob/38005924fe9be17cfed145bbc6d83b09ef8be025/imagehash/__init__.py#L260 ."""  # noqa: E501  | 
98 |  | -        img_size = hash_size * highfreq_factor  | 
99 |  | -        image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)  | 
100 |  | -        image = cv2.resize(image, (img_size, img_size), interpolation=cv2.INTER_AREA)  | 
101 |  | -        dct = fft.dct(fft.dct(image, axis=0), axis=1)  | 
102 |  | -        dct_low_frequency = dct[:hash_size, :hash_size]  | 
103 |  | -        median = np.median(dct_low_frequency)  | 
104 |  | -        return dct_low_frequency > median  | 
105 |  | - | 
106 |  | -    def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):  | 
107 |  | -        source_hash = __cv2_scipy_compute_phash(source, hash_size)  | 
108 |  | -        capture_hash = __cv2_scipy_compute_phash(capture, hash_size)  | 
109 |  | -        hash_diff = np.count_nonzero(source_hash != capture_hash)  | 
110 |  | -        return 1 - (hash_diff / 64.0)  | 
111 |  | - | 
112 |  | -except ModuleNotFoundError:  | 
113 |  | -    if not TYPE_CHECKING:  # opencv-contrib-python-headless being installed is based on architecture  | 
114 |  | - | 
115 |  | -        def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):  | 
116 |  | -            # OpenCV has its own pHash comparison implementation in `cv2.img_hash`,  | 
117 |  | -            # but it requires contrib/extra modules and is inaccurate  | 
118 |  | -            # unless we precompute the size with a specific interpolation.  | 
119 |  | -            # See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684  | 
120 |  | -            #  | 
121 |  | -            phash = cv2.img_hash.PHash.create()  | 
122 |  | -            source = cv2.resize(source, (hash_size, hash_size), interpolation=cv2.INTER_AREA)  | 
123 |  | -            capture = cv2.resize(capture, (hash_size, hash_size), interpolation=cv2.INTER_AREA)  | 
124 |  | -            source_hash = phash.compute(source)  | 
125 |  | -            capture_hash = phash.compute(capture)  | 
126 |  | -            hash_diff = phash.compare(source_hash, capture_hash)  | 
127 |  | -            return 1 - (hash_diff / 64.0)  | 
 | 93 | +# The old scipy-based implementation.  | 
 | 94 | +# Turns out this cuases an extra 25 MB build compared to opencv-contrib-python-headless  | 
 | 95 | +# # from scipy import fft  | 
 | 96 | +# def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: int = 4):  | 
 | 97 | +#     """Implementation copied from https://github.com/JohannesBuchner/imagehash/blob/38005924fe9be17cfed145bbc6d83b09ef8be025/imagehash/__init__.py#L260 ."""  # noqa: E501  | 
 | 98 | +#     img_size = hash_size * highfreq_factor  | 
 | 99 | +#     image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)  | 
 | 100 | +#     image = cv2.resize(image, (img_size, img_size), interpolation=cv2.INTER_AREA)  | 
 | 101 | +#     dct = fft.dct(fft.dct(image, axis=0), axis=1)  | 
 | 102 | +#     dct_low_frequency = dct[:hash_size, :hash_size]  | 
 | 103 | +#     median = np.median(dct_low_frequency)  | 
 | 104 | +#     return dct_low_frequency > median  | 
 | 105 | +# def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):  | 
 | 106 | +#     source_hash = __cv2_scipy_compute_phash(source, hash_size)  | 
 | 107 | +#     capture_hash = __cv2_scipy_compute_phash(capture, hash_size)  | 
 | 108 | +#     hash_diff = np.count_nonzero(source_hash != capture_hash)  | 
 | 109 | +#     return 1 - (hash_diff / 64.0)  | 
 | 110 | + | 
 | 111 | + | 
 | 112 | +def __cv2_phash(source: MatLike, capture: MatLike):  | 
 | 113 | +    """  | 
 | 114 | +    OpenCV has its own pHash comparison implementation in `cv2.img_hash`,  | 
 | 115 | +    but is inaccurate unless we precompute the size with a specific interpolation.  | 
 | 116 | +
  | 
 | 117 | +    See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684  | 
 | 118 | +    """  | 
 | 119 | +    phash = cv2.img_hash.PHash.create()  | 
 | 120 | +    source = cv2.resize(source, (CV2_PHASH_SIZE, CV2_PHASH_SIZE), interpolation=cv2.INTER_AREA)  | 
 | 121 | +    capture = cv2.resize(capture, (CV2_PHASH_SIZE, CV2_PHASH_SIZE), interpolation=cv2.INTER_AREA)  | 
 | 122 | +    source_hash = phash.compute(source)  | 
 | 123 | +    capture_hash = phash.compute(capture)  | 
 | 124 | +    hash_diff = phash.compare(source_hash, capture_hash)  | 
 | 125 | +    return 1 - (hash_diff / 64.0)  | 
128 | 126 | 
 
  | 
129 | 127 | 
 
  | 
130 | 128 | def compare_phash(source: MatLike, capture: MatLike, mask: MatLike | None = None):  | 
 | 
0 commit comments