Как создать запрос к базе данных Postgresql в Django с побитовым сравнением BitXor и фильтрацией по значению расстояния Хэмминга?

Моя задача - разработать функцию для поиска изображений в базе данных с использованием персептивного хэша и сравнения расстояния Хэмминга. Код ниже работает, но из-за того, что фильтрация происходит в среде Django и на Python, код выполняется крайне медленно, в среднем время выполнения составляет более 7 секунд при 1 500 000 записей в базе данных, но на рабочем сервере количество записей будет гораздо больше, и скорость обработки запросов упадет еще больше.

models.py

# models.py
from django.db import models

class Fids(models.Model):
    """
    The model stores records with computed image hashes (>6000000 records)
    picture = 'https://ae01.alicdn.com/kf/Sc835208f704548b7b230fd12f72850bfU.jpg'
    hs8 = '9aca3edfc0c23538'
    bin8 = '1001101011001010001111101101111111000000110000100011010100111000'
    """
    picture = models.URLField()
    hs8 = models.CharField(max_length=50)
    bin8 = models.CharField(max_length=64)

service.py

# service.py
import requests
import json
import imagehash
from PIL import Image
from main.models import Fids


def hamming_distance(binary_str1, binary_str2):
    """
    The function calculates the Hamming distance
    example input:
    binary_str1 = '1001101011001010001111101101111111000000110000100011010100111000'
    binary_str2 = '1001101011001010001111101101111111000000110000100011010100111000'
    """
    if len(binary_str1) != len(binary_str2):
        return -1
    xor_result = int(binary_str1, 2) ^ int(binary_str2, 2)
    hamming_distance = bin(xor_result).count('1')
    return hamming_distance


def search(url=None, hamming=None):
    """
    Search query processing function
    
    example input:
    url = 'https://ae01.alicdn.com/kf/Sc835208f704548b7b230fd12f72850bfU.jpg'
    hamming = 8

    output:
    delay: 8-15 sec
    """
    if url is None:
        return -1
    hamming = 0 if hamming is None else int(hamming)
    try:
        res = requests.get(url, stream=True).raw
        hash = imagehash.phash(Image.open(res), hash_size=8)
        str_hash = str(hash)
        binary = bin(int(str_hash, 16))[2:].zfill(len(str_hash)*4)
    except Exception as er:
        return json.dumps({'search': url, 'error': str(er)})
    result = []
    queryset = Fids.objects.all().values('picture', 'bin8')
    for item in queryset:
        bin8 = item['bin8']
        bin8 = '' if bin8 is None else bin8
        distance = hamming_distance(binary, bin8)
        if 0 <= distance <= hamming:
            result.append({'url': item['picture']})
    return json.dumps({'search': url, 'results': result}) 

Думаю, вам нужно правильно составить SQL-запрос, чтобы фильтрация осуществлялась с использованием базы данных Postgreql-14. Я нашел агрегатную функцию BitXor в Django: from django.contrib.postgres.aggregates import BitXor

Я считаю, что он выполняет побитовое сравнение, но не понимаю, как его применять, в интернете очень мало примеров, Более того, вам также необходимо отфильтровать запрос по расстройству Хэмминга.

ВSQL также есть оператор #, который также выполняет побитовое сравнение. Например: SELECT X'eb35b232b08ac33e' # X'eb35b232b08ac33e'; Но я не понимаю, как применить это к моей проблеме.

Помогите мне создать правильный запрос с использованием ORM Django или SQL-запрос для выполнения поиска и фильтрации с использованием побитового сравнения и сравнения результата с заданным значением расстояния Хэмминга.

В результате поиска решения был получен следующий SQL-запрос:

SELECT * FROM main_fids WHERE int7 # 521635964142742 = 1;

Придется добавить поле int7 и хранить хэш в десятичном виде и меньшего размера, но я получаю ошибку из-за большого размера числа. Я не смог понять, как сравнить двоичные и шестнадцатеричные значения, которые я сохранил как Char. Осталось только понять, как сформировать такой запрос в Django и передать поисковое хэш-значение вместо 521635964142742 и значение расстояния Хэмминга, заданное запросом, вместо 1.

Для решения проблемы я нашел следующее решение:

SELECT id, hsi7 
FROM main_fids 
WHERE length(regexp_replace(((hsi7 # 524383500174776)::bit(64))::text, '0', '', 'g')) < 8 ;

Преобразую результат битового XOR к двоичному типу, затем преобразую его в строку, удаляю нули, подсчитываю количество символов в получившейся строке, получая таким образом расстояние Хэмминга, сравниваю его с заданным значением (в примере 8). Запрос по-прежнему работает очень медленно; возможно, стоит как-то изменить запрос или структуру таблицы, или преобразовать данные к другому типу. Если возможно, посоветуйте, пожалуйста, более элегантное и эффективное решение. Пожалуйста.

Вернуться на верх