Какой самый быстрый способ вычислить сумму абсолютных различий между двумя изображениями в Python? - PullRequest
0 голосов
/ 28 сентября 2018

Я пытаюсь сравнить изображения в приложении Python 3, которое использует Pillow и, возможно, Numpy.Из соображений совместимости я не собираюсь использовать другие внешние пакеты, отличные от Python.Я нашел этот алгоритм на основе подушек в коде Roseta, и он может служить моей цели, но это занимает некоторое время:

from PIL import Image

def compare_images(img1, img2):
    """Compute percentage of difference between 2 JPEG images of same size
    (using the sum of absolute differences). Alternatively, compare two bitmaps
    as defined in basic bitmap storage. Useful for comparing two JPEG images
    saved with a different compression ratios.

    Adapted from:
    http://rosettacode.org/wiki/Percentage_difference_between_images#Python

    :param img1: an Image object
    :param img2: an Image object
    :return: A float with the percentage of difference, or None if images are
    not directly comparable.
    """

    # Don't compare if images are of different modes or different sizes.
    if (img1.mode != img2.mode) \
            or (img1.size != img2.size) \
            or (img1.getbands() != img2.getbands()):
        return None

    pairs = zip(img1.getdata(), img2.getdata())
    if len(img1.getbands()) == 1:
        # for gray-scale jpegs
        dif = sum(abs(p1 - p2) for p1, p2 in pairs)
    else:
        dif = sum(abs(c1 - c2) for p1, p2 in pairs for c1, c2 in zip(p1, p2))

    ncomponents = img1.size[0] * img1.size[1] * 3
    return (dif / 255.0 * 100) / ncomponents  # Difference (percentage)

Пытаясь найти альтернативы, я обнаружил, что эту функцию можно переписать, используя Numpy:

import numpy as np    
from PIL import Image

def compare_images_np(img1, img2):
    if (img1.mode != img2.mode) \
            or (img1.size != img2.size) \
            or (img1.getbands() != img2.getbands()):
        return None

    dif = 0
    for band_index, band in enumerate(img1.getbands()):
        m1 = np.array([p[band_index] for p in img1.getdata()]).reshape(*img1.size)
        m2 = np.array([p[band_index] for p in img2.getdata()]).reshape(*img2.size)
        dif += np.sum(np.abs(m1-m2))

    ncomponents = img1.size[0] * img1.size[1] * 3
    return (dif / 255.0 * 100) / ncomponents  # Difference (percentage)

Я ожидал улучшения скорости обработки, но на самом деле это занимает немного больше времени.У меня нет опыта работы с Numpy, кроме базовых, поэтому мне интересно, есть ли способ сделать это быстрее, например, используя какой-то алгоритм, который не подразумевает цикл for.Есть идеи?

Ответы [ 2 ]

0 голосов
/ 02 октября 2018

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

from PIL import Image
import numpy as np

# Load images, convert to RGB, then to numpy arrays and ravel into long, flat things
a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()

# Calculate the sum of the absolute differences divided by number of elements
MAE = np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]

Единственная «хитрая» вещь - это принуждение типа результата np.subtract() кfloat, который гарантирует, что я могу хранить отрицательные числа.Возможно, стоит попробовать dtype=np.int16 на вашем оборудовании, чтобы проверить, не быстрее ли это.


Быстрый способ его тестирования заключается в следующем.Запустите ipython, а затем введите следующее:

from PIL import Image
import numpy as np

a=np.array(Image.open('a.png').convert('RGB')).ravel()
b=np.array(Image.open('b.png').convert('RGB')).ravel()

Теперь вы можете рассчитать мой код с помощью:

%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.float))) / a.shape[0]
6.72 µs ± 21.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Или вы можете попробовать версию int16 следующим образом:

%timeit np.sum(np.abs(np.subtract(a,b,dtype=np.int16))) / a.shape[0]
6.43 µs ± 30.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Если вы хотите определить время своего кода, вставьте его в функцию, затем используйте:

%timeit compare_images_pil(img1, img2)
0 голосов
/ 02 октября 2018

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

from PIL import Image
from PIL import ImageChops, ImageStat


def compare_images_pil(img1, img2):
    '''Calculate the difference between two images of the same size
    by comparing channel values at the pixel level.
    `delete_diff_file`: removes the diff image after ratio found
    `diff_img_file`: filename to store diff image

    Adapted from Nicolas Hahn:
    https://github.com/nicolashahn/diffimg/blob/master/diffimg/__init__.py
    '''

    # Don't compare if images are of different modes or different sizes.
    if (img1.mode != img2.mode) \
            or (img1.size != img2.size) \
            or (img1.getbands() != img2.getbands()):
        return None

    # Generate diff image in memory.
    diff_img = ImageChops.difference(img1, img2)

    # Calculate difference as a ratio.
    stat = ImageStat.Stat(diff_img)

    # Can be [r,g,b] or [r,g,b,a].
    sum_channel_values = sum(stat.mean)
    max_all_channels = len(stat.mean) * 255
    diff_ratio = sum_channel_values / max_all_channels

    return diff_ratio * 100

Для моего образца тестовых изображенийрезультаты выглядят одинаковыми (за исключением нескольких незначительных ошибок округления числа с плавающей запятой), и он работает значительно быстрее, чем первая версия, которую я имел выше.

...