Python векторизация вложенных циклов обработки изображений - PullRequest
1 голос
/ 29 мая 2020

Я пытаюсь обнаружить кожу. Я нашел красивую и простую формулу для определения кожи на изображении RGB. Единственная проблема в том, что циклы for очень медленные, и мне нужно ускорить процесс. Я провел некоторое исследование, и векторизация могла бы закрепить мои циклы for, но я не знаю, как использовать это в моем случае.

Вот код моей функции:

Функция получает 1 параметр типа: numpy массив с формой (144x256x3), dtype = np.uint8

Функция возвращает координаты первого обнаруженного пикселя цвета кожи (как numpy .array [высота, ширина]) ; количество пикселей обнаруженных кожи (int) и рассчитанный угол (слева направо) первого обнаруженного изображения кожи (float)

# picture = npumpy array, with 144x256x3 shape, dtype=np.uint8
def filter_image(picture):
    r = 0.0
    g = 0.0
    b = 0.0

    # In first_point I save first occurrence of skin colored pixel, so I can track person movement
    first_point = np.array([-1,-1])

    # counter is used to count how many skin colored pixels are in an image (to determine distance to target, because LIDAR isn't working)
    counter = 0

    # angle of first pixel with skin color (from left to right, calculated with Horizontal FOV)
    angle = 0.0

    H = picture.shape[0]
    W = picture.shape[1]

    # loop through each pixel
    for i in range(H):
        for j in range(W):
            # if all RGB are 0(black), we take with next pixel
            if(int(picture[i,j][0]+picture[i,j][1]+picture[i,j][2])) == 0:
               continue
            #else we calculate r,g,b used for skin recognition
            else:    
                r = picture[i,j][0]/(int(picture[i,j][0]+picture[i,j][1]+picture[i,j][2]))
                g = picture[i,j][1]/(int(picture[i,j][0]+picture[i,j][1]+picture[i,j][2]))
                b = picture[i,j][2]/(int(picture[i,j][0]+picture[i,j][1]+picture[i,j][2]))
            # if one of r,g,b calculations are 0, we take next pixel
            if(g == 0 or r == 0 or b == 0):
                continue
            # if True, pixel is skin colored
            elif(r/g > 1.185 and (((r * b) / math.pow(r + b + g,2)) > 0.107) and ((r * g) / math.pow(r + b + g,2)) > 0.112):
                # if this is the first point with skin colors in the whole image, we save i,j coordinate
                if(first_point[0] == -1):
                    # save first skin color occurrence
                    first_point[0] = i
                    first_point[1] = j

                    # here angle is calculated, with width skin pixel coordinate, Hor. FOV of camera and constant
                    angle = (j+1)*91 *0.00390626

                # whenever we detect skin colored pixel, we increment the counter value
                counter += 1
                continue
    # funtion returns coordinates of first skin colored pixel, counter of skin colored pixels and calculated angle(from left to right based on j coordinate of first pixel with skin color)         
    return first_point,counter, angle

Функция работает хорошо, единственная проблема - скорость!

Спасибо за помощь!

Ответы [ 2 ]

2 голосов
/ 29 мая 2020

Вы можете пропустить все петли и выполнить операцию с трансляцией numpy. Процесс станет еще проще, если изображение будет преобразовано в 2D из 3D, что даст вам HxW рядов пикселей для работы.

def filter(picture):
    H,W = picture.shape[0],picture.shape[1]
    picture = picture.astype('float').reshape(-1,3)
    # A pixel with any r,g,b equalling zero can be removed.
    picture[np.prod(picture,axis=1)==0] = 0

    # Divide non-zero pixels by their rgb sum
    picsum = picture.sum(axis=1)
    nz_idx = picsum!=0
    picture[nz_idx] /= (picsum[nz_idx].reshape(-1,1))

    nonzeros = picture[nz_idx]

    # Condition 1: r/g > 1.185
    C1 = (nonzeros[:,0]/nonzeros[:,1]) > 1.185
    # Condition 2: r*b / (r+g+b)^2 > 0.107
    C2 = (nonzeros[:,0]*nonzeros[:,2])/(nonzeros.sum(axis=1)**2) > 0.107 
    # Condition 3: r*g / (r+g+b)^2 > 0.112
    C3 = (nonzeros[:,0]*nonzeros[:,1])/(nonzeros.sum(axis=1)**2) > 0.112
    # Combine conditions
    C = ((C1*C2*C3)!=0)
    picsum[nz_idx] = C
    skin_points = np.where(picsum!=0)[0]
    first_point = np.unravel_index(skin_points[0],(H,W))
    counter = len(skin_points)
    angle = (first_point[1]+1) * 91 * 0.00390626
    return first_point, counter, angle
2 голосов
/ 29 мая 2020

Одна вещь, которую часто бывает приятно попробовать в первую очередь при попытке улучшить производительность кода, - это посмотреть, насколько что-то вроде numba может сделать его быстрее в основном бесплатно.

Вот пример как использовать его для вашего кода:

import math
import time

# I'm just importing numpy here so I can make a random input of the
# same dimensions that you mention in your question.
import numpy as np
from numba import jit

@jit(nopython=True)
def filter_image(picture):
    ... I just copied the body of this function from your post above ...
    return first_point, counter, angle

def main():
    n_iterations = 10
    img = np.random.rand(144, 256, 3)
    before = time.time()
    for _ in range(n_iterations):
        # In Python 3, this was just a way I could get access to the original
        # function you defined, without having to make a separate function for
        # it (as the numba call replaces it with an optimized version).
        # It's equivalent to just calling your original function here.
        filter_image.__wrapped__(img)
    print(f'took: {time.time() - before:.3f} without numba')

    before = time.time()
    for _ in range(n_iterations):
        filter_image(img)
    print(f'took: {time.time() - before:.3f} WITH numba')

if __name__ == '__main__':
    main()

Вывод, показывающий разницу во времени:

took: 1.768 without numba
took: 0.414 WITH numba

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

Изменить (согласно комментарию макроэкономиста): время, о котором я сообщаю выше, также включает авансовые временные затраты numba только что -время компиляции вашей функции, которое происходит при первом вызове. Если вы делаете много вызовов этой функции, разница в производительности может быть гораздо более существенной c. Определение времени всех вызовов после первого первого должно сделать сравнение времени каждого звонка более точным.

...