Есть ли способ запустить OpenCV SIFT быстрее?

У меня есть каталог изображений, который содержит много неопознанных дубликатов. Моя цель - выявить дубликаты. Поскольку дубликаты были обрезаны, изменены по размеру или преобразованы в другой формат изображения, их нельзя обнаружить путем сравнения их хэшей.

Я написал скрипт, который успешно обнаруживает дубликаты, но есть один существенный недостаток: скрипт медленный. На тест-драйве с папкой, содержащей 60 элементов, запуск занял пять часов (это также может быть отражением моего все более глючного и медленного компьютера). Поскольку в моем каталоге примерно 66 000 изображений, по моим оценкам, выполнение сценария займет 229 дней.

Кто-нибудь может предложить решения? Мое исследование показало, что вы можете освободить память, освободив изображение, хранящееся в переменной, по завершении цикла, но вся информация о том, как это сделать, кажется, написана на C, а не на python. Я также думал о попытке использовать orb вместо просеивания, но есть опасения по поводу его точности. Кто-нибудь может посоветовать, какой из двух вариантов лучше выбрать? Или способ переписать скрипт, чтобы он занимал меньше памяти? Спасибо заранее.

from __future__ import division

import cv2
import numpy as np
import glob
import pandas as pd
   

listOfTitles1 = []
listOfTitles2 = []
listOfSimilarities = []
    
    # Sift and Flann
sift = cv2.xfeatures2d.SIFT_create()


index_params = dict(algorithm=0, trees=5)
search_params = dict()
flann = cv2.FlannBasedMatcher(index_params, search_params)

# Load all the images1

countInner = 0
countOuter = 1

folder = r"/Downloads/images/**/*"

for a in glob.iglob(folder,recursive=True):
    for b in glob.iglob(folder,recursive=True):
    
        if not a.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):

            continue

        if not b.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):

            continue

        if b.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):

            countInner += 1
        
        print(countInner, "", countOuter)
    
        if countInner <= countOuter:

            continue

        image1 = cv2.imread(a)
        kp_1, desc_1 = sift.detectAndCompute(image1, None)
    
        image2 = cv2.imread(b)
        kp_2, desc_2 = sift.detectAndCompute(image2, None)

        matches = flann.knnMatch(desc_1, desc_2, k=2)

        good_points = []

        if good_points == 0:

            continue

        for m, n in matches:
            if m.distance < 0.6*n.distance:
                good_points.append(m)

        number_keypoints = 0
        if len(kp_1) >= len(kp_2):
            number_keypoints = len(kp_1)
        else:
            number_keypoints = len(kp_2)
            
        percentage_similarity = float(len(good_points)) / number_keypoints * 100

        listOfSimilarities.append(str(int(percentage_similarity)))
        listOfTitles2.append(b)

        listOfTitles1.append(a)
        
    countInner = 0
    if a.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):
        countOuter += 1

zippedList =  list(zip(listOfTitles1,listOfTitles2, listOfSimilarities))

print(zippedList)

dfObj = pd.DataFrame(zippedList, columns = ['Original', 'Title' , 'Similarity'])

dfObj.to_csv(r"/Downloads/images/DuplicateImages3.csv")

person oymonk    schedule 07.07.2020    source источник
comment
Вы можете поднять код, обрабатывающий изображение a, вне цикла «for b in…». Это немного поможет, но не такое увеличение производительности, которое вы ищете.   -  person Wilf Rosenbaum    schedule 07.07.2020
comment
Вы также можете попытаться перебрать все изображения один раз, вызвав sift.detectAndCompute, а затем кэшировать результаты. Прямо сейчас вы вызываете просеивать много раз для каждого изображения.   -  person Wilf Rosenbaum    schedule 07.07.2020
comment
Наконец, я подозреваю, что ваша оценка в 229 дней для запуска этого кода на 66000 изображений слишком оптимистична. Это время выполнения кода пропорционально квадрату количества сравниваемых изображений.   -  person Wilf Rosenbaum    schedule 07.07.2020


Ответы (2)


Я запустил вашу существующую реализацию на своем компьютере на 100 изображениях. Этот код выполнялся 6 часов 31 минуту. Затем я изменил реализацию, как я предложил в своем комментарии, чтобы вычислить sift.detectAndCompute только один раз для каждого изображения, кэшировать результаты и использовать кэшированные результаты в сравнениях. Это сократило время выполнения на моем компьютере тех же 100 изображений с 6 часов 31 минуты до 6 минут 29 секунд. Я не знаю, будет ли этого достаточно быстро для всех ваших изображений, но это значительное сокращение.

См. мою модифицированную реализацию ниже.

from __future__ import division

import cv2
import numpy as np
import glob
import pandas as pd


listOfTitles1 = []
listOfTitles2 = []
listOfSimilarities = []

    # Sift and Flann
sift = cv2.xfeatures2d.SIFT_create()


index_params = dict(algorithm=0, trees=5)
search_params = dict()
flann = cv2.FlannBasedMatcher(index_params, search_params)

# Load all the images1

countInner = 0
countOuter = 1

folder = r"/Downloads/images/**/*"
folder = "SiftImages/*"


siftOut = {}
for a in glob.iglob(folder,recursive=True):
    if not a.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):
        continue
    image1 = cv2.imread(a)
    kp_1, desc_1 = sift.detectAndCompute(image1, None)
    siftOut[a]=(kp_1,desc_1)



for a in glob.iglob(folder,recursive=True):
    if not a.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):
        continue

    (kp_1,desc_1) = siftOut[a]

    for b in glob.iglob(folder,recursive=True):


        if not b.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):

            continue

        if b.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):

            countInner += 1


        print(countInner, "", countOuter)

        if countInner <= countOuter:

            continue

        #### image1 = cv2.imread(a)
        #### kp_1, desc_1 = sift.detectAndCompute(image1, None)
        ####
        #### image2 = cv2.imread(b)
        #### kp_2, desc_2 = sift.detectAndCompute(image2, None)

        (kp_2,desc_2) = siftOut[b]

        matches = flann.knnMatch(desc_1, desc_2, k=2)

        good_points = []

        if good_points == 0:

            continue

        for m, n in matches:
            if m.distance < 0.6*n.distance:
                good_points.append(m)

        number_keypoints = 0
        if len(kp_1) >= len(kp_2):
            number_keypoints = len(kp_1)
        else:
            number_keypoints = len(kp_2)

        percentage_similarity = float(len(good_points)) / number_keypoints * 100

        listOfSimilarities.append(str(int(percentage_similarity)))
        listOfTitles2.append(b)

        listOfTitles1.append(a)

    countInner = 0
    if a.lower().endswith(('.jpg','.png','.tif','.tiff','.gif')):
        countOuter += 1

zippedList =  list(zip(listOfTitles1,listOfTitles2, listOfSimilarities))

print(zippedList)

dfObj = pd.DataFrame(zippedList, columns = ['Original', 'Title' , 'Similarity'])

### dfObj.to_csv(r"/Downloads/images/DuplicateImages3.csv")
dfObj.to_csv(r"DuplicateImages3.2.csv")
person Wilf Rosenbaum    schedule 07.07.2020
comment
Работает намного быстрее оригинала. - person oymonk; 08.07.2020

Я думаю, что с помощью простых изменений можно добиться значительных улучшений производительности:

  1. Во-первых, поскольку вас интересует сравнение пар изображений, ваш цикл может выглядеть так:
files = ... # preload all file names with glob

for a_idx in range(len(files)):
  for b_idx in range(a_idx, len(files)): # notice loop here
    image_1 = cv2.imread(files[a_idx])
    image_2 = cv2.imread(files[b_idx])

Это рассматривает все пары без повторения, например. (а, б) && (б, а)

  1. Во-вторых, вам не нужно пересчитывать признаки для a при сравнении для каждого b.
for a_idx in range(len(files)):
  image_1 = cv2.imread(files[a_idx])
  kp_1, desc_1 = sift.detectAndCompute(image1, None) # never recoompute SIFT!

  for b_idx in range(a_idx, len(files)):
    image_2 = cv2.imread(files[b_idx])
    kp_2, desc_2 = sift.detectAndCompute(image2, None)
  1. Я бы также проверил размеры изображения. Я предполагаю, что есть некоторые действительно большие, которые замедляют ваш внутренний цикл. Даже все 60*60 == 3600 пар не должны занимать так много времени. Если изображение действительно большое, вы можете уменьшить его разрешение для повышения эффективности.
person fizzybear    schedule 07.07.2020
comment
Большое спасибо за вашу работу над этим. Я все еще работаю над тем, как интегрировать ваши предложения в мой сценарий. Вернемся к вам в ближайшее время. - person oymonk; 08.07.2020
comment
Спасибо за подсказку о субдискретизации больших изображений и однократном просеивании. - person oymonk; 08.07.2020