Вложенные циклы с cython для обработки изображений

Я пытаюсь перебрать 2D-изображение, содержащее данные о глубине с плавающей запятой, оно имеет несколько нормальное разрешение (640, 480), но python слишком медленный, поэтому я пытался оптимизировать проблему с помощью cython.

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

Я попытался избавиться от объектов python из цикла prange(), предварительно переместив их в раздел with gil, поэтому:

cdef int[:] w_list = array.array(range(0, w_inc, interpolation))

вместо

for r in range(0, w_inc, interpolation):

но ошибка сохраняется

Мой код работает в двух частях:

  1. Метод split_data() подразделяет изображение на num квадрантов, которые хранятся в трехмерном массиве bits. Они используются для упрощения разделения работы на несколько потоков/процессов. Эта часть работает нормально.
@cython.cdivision(True)
@cython.boundscheck(False)
cpdef split_data(double[:, :] frame, int h, int w, int num):
    cdef double[:, :, :] bits = np.zeros(shape=(num, h // num, w // num), dtype=float)
    cdef int c_count = os.cpu_count()
    cdef int i, j, k

    for i in prange(num, nogil=True, num_threads=c_count):
        for j in prange(h // num):
            for k in prange(w // num):
                bits[i, j, k] = frame[i * (h // num) + j, i * (w // num) + k]

    return bits
  1. Метод scatter_data() берет массив bits из предыдущей функции, а затем создает другой 3D-массив с длиной num, где num — это длина bits, называемая points, которая представляет собой ряд 3D-координат, представляющих допустимые точки глубины. Затем он использует prange() для извлечения действительных данных о глубине из каждого из этих bits и сохраняет их в points.
@cython.cdivision(True)
@cython.boundscheck(False)
cpdef scatter_data(double[:, :] depths, object validator=None,
                         int h=-1, int w=-1, int interpolation=1):

    # Handles if h or w is -1 (default)
    if h < 0 or w < 0:
        h = depths.shape[0] if h < 0 else h
        w = depths.shape[1] if w < 0 else w

    cdef int max_num = w * h
    cdef int c_count = os.cpu_count()
    cdef int h_inc = h // c_count, w_inc = w // c_count

    cdef double[:, :, :] points = np.zeros(shape=(c_count, max_num, 3), dtype=float)

    cdef double[:, :, :] bits = split_data(depths, h, w, c_count)

    cdef int count = 0
    cdef int i, r, c

    cdef int[:] w_list = array.array(range(0, w_inc, interpolation))
    cdef int[:] h_list = array.array(range(0, h_inc, interpolation))

    for i in prange(c_count, nogil=True, num_threads=c_count):
        count = 0
        for r in w_list:
            for c in h_list:
                if depths[c, r] != 0:
                    points[i, count, 0] = w - r
                    points[i, count, 1] = c
                    points[i, count, 2] = depths[c, r]
                    count = count + 1

    points = points[:count]

    return points

и для полноты 3. Вот мои операторы импорта

import cython
from cython.parallel import prange
from cpython cimport array
import array
cimport numpy as np
import numpy as np
import os

При компиляции кода я продолжаю получать сообщения об ошибках, что-то вроде:

Error compiling Cython file:
------------------------------------------------------------
...
    cdef int[:] w_list = array.array(range(0, w_inc, interpolation))
    cdef int[:] h_list = array.array(range(0, h_inc, interpolation))

    for i in prange(c_count, nogil=True, num_threads=c_count):
        count = 0
        for r in w_list:
                ^
------------------------------------------------------------

data_util/cy_scatter.pyx:70:17: Iterating over Python object not allowed without gil

а также

Error compiling Cython file:
------------------------------------------------------------
...
    cdef int[:] w_list = array.array(range(0, w_inc, interpolation))
    cdef int[:] h_list = array.array(range(0, h_inc, interpolation))

    for i in prange(c_count, nogil=True, num_threads=c_count):
        count = 0
        for r in w_list:
                ^
------------------------------------------------------------

data_util/cy_scatter.pyx:70:17: Coercion from Python not allowed without the GIL

а также

Error compiling Cython file:
------------------------------------------------------------
...
    cdef int[:] w_list = array.array(range(0, w_inc, interpolation))
    cdef int[:] h_list = array.array(range(0, h_inc, interpolation))

    for i in prange(c_count, nogil=True, num_threads=c_count):
        count = 0
        for r in w_list:
                ^
------------------------------------------------------------

data_util/cy_scatter.pyx:70:17: Converting to Python object not allowed without gil

Есть ли способ сделать это? И если да, то как мне это сделать?


person iggy12345    schedule 22.05.2019    source источник


Ответы (1)


Вы просто хотите выполнять итерацию по индексу, а не по итератору Python:

for ri in range(w_list.shape[0]):
    r = w_list[ri]

Это где-то, где передовая практика в Python отличается от передовой практики в Cython — Cython только ускоряет итерацию по числовым циклам. То, как вы пытаетесь это сделать, вернется к тому, чтобы быть итератором Python, который медленнее и требует GIL.

person DavidW    schedule 22.05.2019
comment
Итак, есть ли разница между этим и использованием for r in range(0, w_inc, interpolation):? Почему у вас работает, а у меня нет? - person iggy12345; 22.05.2019
comment
Похоже, что Cython может справиться с 1 или 2 аргументами для range, но не создал специальный случай для версии с тремя аргументами. Я не уверен, есть ли для этого веская причина или это просто еще не сделано (я не могу придумать ни одной, но это может быть что-то тонкое). - person DavidW; 22.05.2019