Быстрая абсолютная разница двух массивов uint8

У меня есть два массива numpy с dtype=np.uint8 - вот так:

img1=np.uint8(np.random.randint(0, 255, (480, 640)))
img2=np.uint8(np.random.randint(0, 255, (480, 640)))

И я хочу построить положительную разницу этих массивов.

Вот мои первые два утверждения (и третий для справки):

def differenceImageV1(img1, img2):
  diff=np.empty_like(img1)
  h, w=img1.shape
  for y in range(h):
    for x in range(w):
      if img1[y, x]<img2[y, x]: diff[y, x]=img2[y, x]-img1[y, x]
      else:                     diff[y, x]=img1[y, x]-img2[y, x]
  return(diff)

def differenceImageV2(img1, img2):
  return(np.uint8(np.absolute(np.int16(img1)-np.int16(img2))))

def differenceImageV3(img1, img2):  # fast - but wrong result
  return(img1-img2)

Я получаю эти времена выполнения (и суммы для проверки, если они равны):

  10x: 1893.54 ms  np.sum=26122208
1000x:  411.71 ms  np.sum=26122208
1000x:   26.60 ms  np.sum=39123624

Есть ли способ получить правильный результат быстрее, чем с V2?

Ответ 1

Здесь один подход, который значительно быстрее, чем V2: возьмите img1-img2 и умножьте на 1 или -1 в зависимости от img1>img2. Вот как это реализовано:

def differenceImageV6(img1, img2):
  a = img1-img2
  b = np.uint8(img1<img2) * 254 + 1
  return a * b

Испытательный жгут для тестирования производительности:

import numpy as np

img1=np.uint8(np.random.randint(0, 255, (480, 640)))
img2=np.uint8(np.random.randint(0, 255, (480, 640)))

def differenceImageV1(img1, img2):
  diff=np.empty_like(img1)
  h, w=img1.shape
  for y in range(h):
    for x in range(w):
      if img1[y, x]<img2[y, x]: diff[y, x]=img2[y, x]-img1[y, x]
      else:                     diff[y, x]=img1[y, x]-img2[y, x]
  return(diff)

def differenceImageV2(img1, img2):
  return(np.uint8(np.abs(np.int16(img1)-img2)))

def differenceImageV3(img1, img2):  # fast - but wrong result
  return(img1-img2)

def differenceImageV4(img1, img2):
  return np.where(img1>img2, img1-img2, img2-img1)

def differenceImageV5(img1, img2):
  a = img1-img2
  b = img2-img1
  c = img1>img2
  return a*c + b*(~c)

def differenceImageV6(img1, img2):
  a = img1-img2
  b = np.uint8(img1<img2) * 254 + 1
  return a * b

import timeit
def testit():
  for fn in [differenceImageV2, differenceImageV3, differenceImageV4, differenceImageV5, differenceImageV6]:
    print fn.__name__, np.sum(fn(img1, img2).astype('int64')),
    print timeit.timeit("%s(img1, img2)" % fn.__name__, "from test import img1, img2, %s" % fn.__name__, number=1000)

if __name__ == '__main__':
    testit()

и итоговые номера производительности:

differenceImageV2 26071358 0.982538938522
differenceImageV3 39207702 0.0261280536652
differenceImageV4 26071358 1.36270809174
differenceImageV5 26071358 0.220561981201
differenceImageV6 26071358 0.154536962509

differenceImageV6 примерно на 6 раз медленнее, чем неправильный differenceImageV3, но все же примерно на 6 раз быстрее предыдущего differenceImageV2. differenceImageV1 не тестируется, потому что он легко на несколько порядков медленнее остальных.

Примечание. Я использовал метод np.where для сравнения; Я думал, что это может иметь хорошую производительность, но оказалось, что она довольно бедна. Кажется, что выполнение разрезания булевым массивом в NumPy довольно медленное.

Ответ 2

Если у вас есть opencv, вы также можете использовать:

def differenceImageV4(img1, img2):
  return cv2.absdiff(img1, img2)

которая почти такая же скорость, как differenceImageV3.