Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,12 @@ dependencies = [
"PyWinCtl >=0.0.42", # py.typed
"keyboard @ git+https://github.com/boppreh/keyboard.git", # Fix install on macos and linux-ci https://github.com/boppreh/keyboard/pull/568
"numpy >=2.3", # Windows ARM64 wheels
"opencv-contrib-python-headless >=4.10", # NumPy 2 support
"packaging >=20.0", # py.typed
# When needed, dev builds can be found at https://download.qt.io/snapshots/ci/pyside/dev?C=M;O=D
"PySide6-Essentials", # Let package resolution find the minimum for wheels (>=6.9.0 on Windows ARM64; <6.8.1 on ubuntu-22.04-arm (glibc 2.39))
"tomli-w >=1.1.0", # Typing fixes

# scipy is used for pHash calculation as a smaller, but still fast implementation.
# However, scipy is not available on all environments yet.
# In those cases, we're falling back to opencv-contrib-python's cv2.img_hash
"opencv-contrib-python-headless; platform_machine == 'ARM64' or platform_machine == 'aarch64'",
"opencv-python-headless; platform_machine != 'ARM64' and platform_machine != 'aarch64'",
"scipy >=1.14.1; platform_machine != 'ARM64' and platform_machine != 'aarch64'", # Python 3.13 support

#
# Build and compile resources
"pyinstaller >=6.14.0", # Mitigate issues with pkg_resources deprecation warning
Expand Down Expand Up @@ -59,7 +53,6 @@ dev = [
"ruff >=0.11.13",
#
# Types
"scipy-stubs >=1.14.1.1",
"types-PyAutoGUI",
"types-PyScreeze; sys_platform == 'linux'",
"types-keyboard",
Expand Down
70 changes: 34 additions & 36 deletions src/compare.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from collections.abc import Iterable
from math import sqrt
from typing import TYPE_CHECKING

import cv2
import Levenshtein
Expand All @@ -22,6 +21,7 @@
RANGES = (0, MAXRANGE, 0, MAXRANGE, 0, MAXRANGE)
MASK_SIZE_MULTIPLIER = ColorChannel.Alpha * MAXBYTE * MAXBYTE
MAX_VALUE = 1.0
CV2_PHASH_SIZE = 8


def compare_histograms(source: MatLike, capture: MatLike, mask: MatLike | None = None):
Expand Down Expand Up @@ -90,41 +90,39 @@ def compare_template(source: MatLike, capture: MatLike, mask: MatLike | None = N
return 1 - (min_val / max_error)


try:
from scipy import fft

def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: int = 4):
"""Implementation copied from https://github.com/JohannesBuchner/imagehash/blob/38005924fe9be17cfed145bbc6d83b09ef8be025/imagehash/__init__.py#L260 .""" # noqa: E501
img_size = hash_size * highfreq_factor
image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)
image = cv2.resize(image, (img_size, img_size), interpolation=cv2.INTER_AREA)
dct = fft.dct(fft.dct(image, axis=0), axis=1)
dct_low_frequency = dct[:hash_size, :hash_size]
median = np.median(dct_low_frequency)
return dct_low_frequency > median

def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
source_hash = __cv2_scipy_compute_phash(source, hash_size)
capture_hash = __cv2_scipy_compute_phash(capture, hash_size)
hash_diff = np.count_nonzero(source_hash != capture_hash)
return 1 - (hash_diff / 64.0)

except ModuleNotFoundError:
if not TYPE_CHECKING: # opencv-contrib-python-headless being installed is based on architecture

def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
# OpenCV has its own pHash comparison implementation in `cv2.img_hash`,
# but it requires contrib/extra modules and is inaccurate
# unless we precompute the size with a specific interpolation.
# See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684
#
phash = cv2.img_hash.PHash.create()
source = cv2.resize(source, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
capture = cv2.resize(capture, (hash_size, hash_size), interpolation=cv2.INTER_AREA)
source_hash = phash.compute(source)
capture_hash = phash.compute(capture)
hash_diff = phash.compare(source_hash, capture_hash)
return 1 - (hash_diff / 64.0)
# The old scipy-based implementation.
# Turns out this cuases an extra 25 MB build compared to opencv-contrib-python-headless
# # from scipy import fft
# def __cv2_scipy_compute_phash(image: MatLike, hash_size: int, highfreq_factor: int = 4):
# """Implementation copied from https://github.com/JohannesBuchner/imagehash/blob/38005924fe9be17cfed145bbc6d83b09ef8be025/imagehash/__init__.py#L260 .""" # noqa: E501
# img_size = hash_size * highfreq_factor
# image = cv2.cvtColor(image, cv2.COLOR_BGRA2GRAY)
# image = cv2.resize(image, (img_size, img_size), interpolation=cv2.INTER_AREA)
# dct = fft.dct(fft.dct(image, axis=0), axis=1)
# dct_low_frequency = dct[:hash_size, :hash_size]
# median = np.median(dct_low_frequency)
# return dct_low_frequency > median
# def __cv2_phash(source: MatLike, capture: MatLike, hash_size: int = 8):
# source_hash = __cv2_scipy_compute_phash(source, hash_size)
# capture_hash = __cv2_scipy_compute_phash(capture, hash_size)
# hash_diff = np.count_nonzero(source_hash != capture_hash)
# return 1 - (hash_diff / 64.0)


def __cv2_phash(source: MatLike, capture: MatLike):
"""
OpenCV has its own pHash comparison implementation in `cv2.img_hash`,
but is inaccurate unless we precompute the size with a specific interpolation.

See: https://github.com/opencv/opencv_contrib/issues/3295#issuecomment-1172878684
"""
phash = cv2.img_hash.PHash.create()
source = cv2.resize(source, (CV2_PHASH_SIZE, CV2_PHASH_SIZE), interpolation=cv2.INTER_AREA)
capture = cv2.resize(capture, (CV2_PHASH_SIZE, CV2_PHASH_SIZE), interpolation=cv2.INTER_AREA)
source_hash = phash.compute(source)
capture_hash = phash.compute(capture)
hash_diff = phash.compare(source_hash, capture_hash)
return 1 - (hash_diff / 64.0)


def compare_phash(source: MatLike, capture: MatLike, mask: MatLike | None = None):
Expand Down
70 changes: 5 additions & 65 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.