Есть ли быстрый способ добавить два трехмерных массива? - PullRequest
1 голос
/ 09 июня 2019

Три строки кода, добавляющие маскировку [высота, ширина, 1] к R, G, B, также [высота, ширина, 1] сокращают время выполнения этого кода с менее чем за секунду до 5 - 10 минут.

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

Это цветной мат RGB.Он разбит на небольшие области, называемые суперпикселями, которые представляют собой просто группы пикселей.Я пытаюсь взять среднее значение всех пикселей в каждой группе и заполнить эту группу одним цветом.При первом исполнении это сработало идеально, завершив картину менее чем за секунду.Однако все чернокожие были вывезены.Чтобы решить эту проблему, я решил добавить 1, где маскирование суперпикселя равно, но все еще равно нулю, таким образом я могу учесть в среднем черные пиксели.

import cv2 as cv
import os
import numpy as np
img = cv.imread(path+item)
f, e = os.path.splitext(path+item)

for x in range(0, 3):
    img = cv.pyrDown(img)

height, width, channel = img.shape

img_super = cv.ximgproc.createSuperpixelSLIC(img, cv.ximgproc.MSLIC, 100, 10.0)
img_super.iterate(3)

labels = np.zeros((height, width), np.int32)
labels = img_super.getLabels(labels)

super_pixelized = np.zeros_like(img)

print("Check-1")

for x in range(0, img_super.getNumberOfSuperpixels()):
    new_img = img.copy()
    #print(new_img.shape)

    mask = cv.inRange(labels, x, x)

    new_img = cv.bitwise_and(img, new_img, mask=mask)

    r, g, b = np.dsplit(new_img, 3)

    print("Check-2")
    basis = np.expand_dims(mask, 1)

    basis = basis * 1/255

    print(basis)

    r = np.add(r, basis)
    g = np.add(g, basis)
    b = np.add(b, basis)

    r_avg = int(np.sum(r)/np.count_nonzero(r))
    g_avg = int(np.sum(g)/np.count_nonzero(g))
    b_avg = int(np.sum(b)/np.count_nonzero(b))

    #print(r_avg)
    #print(g_avg)
    #print(b_avg)

    r = mask * r_avg
    g = mask * g_avg
    b = mask * b_avg

    final_img = np.dstack((r, g, b))

    super_pixelized = cv.bitwise_or(final_img, super_pixelized)

Эта простая процедура добавления вызвала кодвремя выполнения значительно увеличится.

1 Ответ

2 голосов
/ 11 июня 2019

Исправление замедления

Конкретная проблема, которая замедляет вашу программу, заключается в вызове np.expand_dims(...):

basis = np.expand_dims(mask, 1)

Второй параметр -msgstr "позиция в расширенных осях, где размещена новая ось."Поскольку mask имеет 2 оси в этой точке, вы вставляете новую ось между первой и второй.

Например:

>>> import numpy as np
>>> mask = np.zeros((240, 320), np.uint8)
>>> mask.shape
(240L, 320L)
>>> expanded = np.expand_dims(mask, 1)
>>> expanded.shape
(240L, 1L, 320L)

Мы получаем изображение формы (240L, 1L, 320L) где мы действительно хотим (240L, 320L, 1L).

Позже вы добавите этот неправильный массив к каждому из изображений разделенных каналов, которые имеют форму (240L, 320L, 1L).

>>> img = np.zeros((240,320,3), np.uint8)
>>> r, g, b = np.dsplit(img, 3)
>>> r.shape
(240L, 320L, 1L)
>>> r = np.add(r, expanded)
>>> r.shape
(240L, 320L, 320L)

Из-за того, как работают правила NumPy Broadcast , вы получите 320-канальное изображение (вместо 1-канального).

Это на несколько порядков большее количество значений для обработкив дальнейших шагах, отсюда и такое резкое замедление.

Исправление простое, просто добавьте ось в нужном месте:

basis = np.expand_dims(mask, 2)

Это исправит замедление, однакоЕсть еще много вопросов, которые необходимо решить, и возможная оптимизация.


Подготовка к оптимизации

Поскольку мы заинтересованы в производительности кода, который обрабатывает метки, давайте проведем рефакторингнемного, и сделать простой тестовый комплект, содержащий всеобщие биты, а также средства для синхронизации отдельных шагов и отчета о времени:

файл superpix_harness.py

import cv2
import time

def run_test(superpix_size, fn, file_name_in, reduce_count, file_name_out):
    times = []
    times.append(time.time())

    img = cv2.imread(file_name_in)
    for _ in range(0, reduce_count):
        img = cv2.pyrDown(img)
    times.append(time.time())

    img_super = cv2.ximgproc.createSuperpixelSLIC(img, cv2.ximgproc.MSLIC, superpix_size, 10.0)
    img_super.iterate(3)
    labels = img_super.getLabels()
    superpixel_count = img_super.getNumberOfSuperpixels()    
    times.append(time.time())

    super_pixelized = fn(img, labels, superpixel_count)
    times.append(time.time())   

    cv2.imwrite(file_name_out, super_pixelized)
    times.append(time.time())

    return (img.shape, superpix_size, superpixel_count, times)

def print_header():
    print "Width, Height, SP Size, SP Count, Time Load, Time SP, Time Process, Time Save, Time Total"

def print_report(test_result):
    shape, sp_size, sp_count, times = test_result
    print "%d , %d , %d , %d" % (shape[0], shape[1], sp_size, sp_count),
    for i in range(len(times) - 1):
        print (", %0.4f" % (times[i+1] - times[i])),
    print ", %0.4f" % (times[-1] - times[0])

def measure_fn(fn):
    print_header()
    for reduction in [3,2,1,0]:
        for sp_size in [100,50,25,12]:
            print_report(run_test(sp_size, fn, 'barrack.jpg', reduction, 'output_%01d_%03d.jpg' % (reduction, sp_size)))

Случайно, это оказалось первымдостаточно большое изображение, с которым я столкнулся, чтобы проверить это с (barrack.jpg):

Sample input image


Baseline

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

Прежде всего, обратите внимание, что, поскольку мы находимся в Python, мы не говорим о Mat,а точнее numpy.ndarray.Следует также помнить, что OpenCV по умолчанию использует формат цвета BGR, поэтому переменные должны быть соответствующим образом переименованы.

Начальная копия входного изображения, которое вы делаете с помощью new_img = img.copy(), бесполезна, поскольку вы перезаписываете егодостаточно скоро.Давайте отбросим это и просто сделаем new_img = cv.bitwise_and(img, img, mask=mask).

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

b_avg = int(np.sum(b) / np.count_nonzero(b))

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

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

Наконец, мы можем воспользоваться индексом numpy для записи среднего цвета канала только взамаскированные пиксели (например, b[mask != 0] = b_avg).

Файл op_labels.py

import cv2
import numpy as np

def super_pixelize(img, labels, superpixel_count):
    result = np.zeros_like(img)

    for x in range(superpixel_count):
        mask = cv2.inRange(labels, x, x)
        new_img = cv2.bitwise_and(img, img, mask=mask)

        r, g, b = np.dsplit(new_img, 3)

        label_pixel_count = np.count_nonzero(mask)
        b_avg = np.uint8(np.sum(b) / label_pixel_count)
        g_avg = np.uint8(np.sum(g) / label_pixel_count)
        r_avg = np.uint8(np.sum(r) / label_pixel_count)

        b[mask != 0] = b_avg
        g[mask != 0] = g_avg
        r[mask != 0] = r_avg

        final_img = np.dstack((r, g, b))

        result = cv2.bitwise_or(final_img, result)

    return result

Теперь мы можем измерить производительность нашего кода.

Сценарий эталонного теста:

from superpix_harness import *

import op_labels    
measure_fn(op_labels.super_pixelize)

Время:

Reduction, Width, Height, SP Size, SP Count, Time Load, Time SP, Time Process, Time Save, Time Total
3 ,  420 ,  336 , 100 ,  155 , 0.1490 , 0.0590 , 0.3990 , 0.0070 , 0.6140
3 ,  420 ,  336 ,  50 ,  568 , 0.1490 , 0.0670 , 1.4510 , 0.0070 , 1.6740
3 ,  420 ,  336 ,  25 , 1415 , 0.1480 , 0.0720 , 3.6580 , 0.0080 , 3.8860
3 ,  420 ,  336 ,  12 , 3009 , 0.1490 , 0.0860 , 7.7170 , 0.0070 , 7.9590
2 ,  839 ,  672 , 100 ,  617 , 0.1460 , 0.3570 , 7.1140 , 0.0150 , 7.6320
2 ,  839 ,  672 ,  50 , 1732 , 0.1460 , 0.3590 , 20.0610 , 0.0150 , 20.5810
2 ,  839 ,  672 ,  25 , 3556 , 0.1520 , 0.3860 , 40.8780 , 0.0160 , 41.4320
2 ,  839 ,  672 ,  12 , 6627 , 0.1460 , 0.3990 , 76.1310 , 0.0160 , 76.6920
1 , 1678 , 1344 , 100 , 1854 , 0.1430 , 2.2480 , 88.3880 , 0.0460 , 90.8250
1 , 1678 , 1344 ,  50 , 4519 , 0.1430 , 2.2440 , 221.7200 , 0.0580 , 224.1650
1 , 1678 , 1344 ,  25 , 9083 , 0.1530 , 2.2100 , 442.7040 , 0.0480 , 445.1150
1 , 1678 , 1344 ,  12 , 17869 , 0.1440 , 2.2620 , 849.9970 , 0.0500 , 852.4530
0 , 3356 , 2687 , 100 , 4786 , 0.1300 , 10.9440 , 916.8950 , 0.1570 , 928.1260
0 , 3356 , 2687 ,  50 , 11942 , 0.1280 , 10.7100 , 2284.5040 , 0.1680 , 2295.5100
0 , 3356 , 2687 ,  25 , 29066 , 0.1300 , 10.7440 , 5561.0440 , 0.1690 , 5572.0870
0 , 3356 , 2687 ,  12 , 59634 , 0.1250 , 10.9860 , 11409.9540 , 0.1770 , 11421.2420

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


Оптимизация базовой линии

Прежде всего, мы можем избежать необходимости разбивать изображение на одноканальные iamges, обрабатывать их изатем собрать их обратно в формат BGR.К счастью, OpenCV предоставляет функцию cv2.mean, которая будет вычислять среднее значение для каждого канала (возможно, маскируемого) изображения.

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

Самое важное замечание, которое следует сделать в этот момент, это то, что размер каждого суперпикселя (региона с определенной меткой), как правило, намного меньше, чем все изображение.Вместо обработки всего изображения для каждой метки, мы должны ограничить большую часть работы областью интереса (ROI) - минимальным прямоугольником, который соответствует пикселям, принадлежащим определенной метке.

Чтобы определитьROI, мы можем использовать cv2.boundingRect на mask.

Файл improved_labels.py

import cv2
import numpy as np

def super_pixelize(img, labels, superpixel_count):
    result = np.zeros_like(img)

    mask = np.zeros(img.shape[:2], np.uint8) # Here it seems to make more sense to pre-alloc and reuse
    for label in range(0, superpixel_count):
        cv2.inRange(labels, label, label, dst=mask)

        # Find the bounding box of this label
        x,y,w,h = cv2.boundingRect(mask)

        # Work only on the rectangular region containing the label
        mask_roi = mask[y:y+h,x:x+w]
        img_roi = img[y:y+h,x:x+w]

        # Per-channel mean of the masked pixels (we're usingo BGR, so we drop the useless 4th channel it gives us)
        roi_mean = cv2.mean(img_roi, mask_roi)[:3]

        # Set all masked pixels in the ROI of the target image the the mean colour
        result[y:y+h,x:x+w][mask_roi != 0] = roi_mean

    return result

Тест сценария:

from superpix_harness import *

import improved_labels
measure_fn(improved_labels.super_pixelize)

Время:

Reduction, Width, Height, SP Size, SP Count, Time Load, Time SP, Time Process, Time Save, Time Total
3 ,  420 ,  336 , 100 ,  155 , 0.1500 , 0.0600 , 0.0250 , 0.0070 , 0.2420
3 ,  420 ,  336 ,  50 ,  568 , 0.1490 , 0.0670 , 0.0760 , 0.0070 , 0.2990
3 ,  420 ,  336 ,  25 , 1415 , 0.1480 , 0.0740 , 0.1740 , 0.0070 , 0.4030
3 ,  420 ,  336 ,  12 , 3009 , 0.1480 , 0.0860 , 0.3560 , 0.0070 , 0.5970
2 ,  839 ,  672 , 100 ,  617 , 0.1510 , 0.3720 , 0.2450 , 0.0150 , 0.7830
2 ,  839 ,  672 ,  50 , 1732 , 0.1480 , 0.3610 , 0.6450 , 0.0170 , 1.1710
2 ,  839 ,  672 ,  25 , 3556 , 0.1480 , 0.3730 , 1.2930 , 0.0160 , 1.8300
2 ,  839 ,  672 ,  12 , 6627 , 0.1480 , 0.4160 , 2.3840 , 0.0160 , 2.9640
1 , 1678 , 1344 , 100 , 1854 , 0.1420 , 2.2140 , 2.8510 , 0.0460 , 5.2530
1 , 1678 , 1344 ,  50 , 4519 , 0.1480 , 2.2280 , 6.7440 , 0.0470 , 9.1670
1 , 1678 , 1344 ,  25 , 9083 , 0.1430 , 2.1920 , 13.5850 , 0.0480 , 15.9680
1 , 1678 , 1344 ,  12 , 17869 , 0.1440 , 2.2960 , 26.3940 , 0.0490 , 28.8830
0 , 3356 , 2687 , 100 , 4786 , 0.1250 , 10.9570 , 30.8380 , 0.1570 , 42.0770
0 , 3356 , 2687 ,  50 , 11942 , 0.1310 , 10.7930 , 76.1670 , 0.1710 , 87.2620
0 , 3356 , 2687 ,  25 , 29066 , 0.1250 , 10.7480 , 184.0220 , 0.1720 , 195.0670
0 , 3356 , 2687 ,  12 , 59634 , 0.1240 , 11.0440 , 377.8910 , 0.1790 , 389.2380

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

Есть еще варианты, чтобы сделать лучше, но мы должны будем подумать о коробке.


Идем дальше

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

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

В Python мы можем реализовать этот алгоритмследующим образом:

Примечание: Хотя это довольно многословно, это наиболее эффективная версия - почему, я не могу точно объяснить, но она довольно близко соответствует лучшей-функционирующая функция Cython.

Файл fast_labels_python.py

import numpy as np

def super_pixelize(img, labels, superpixel_count):
    rows = img.shape[0]
    cols = img.shape[1]

    assert img.shape[0] == labels.shape[0]
    assert img.shape[1] == labels.shape[1]
    assert img.shape[2] == 3

    sums = np.zeros((superpixel_count, 3), dtype=np.int64)
    counts = np.zeros((superpixel_count, 1), dtype=np.int64)

    for r in range(rows):
        for c in range(cols):
            label = labels[r,c]
            sums[label, 0] = (sums[label, 0] + img[r, c, 0])
            sums[label, 1] = (sums[label, 1] + img[r, c, 1])
            sums[label, 2] = (sums[label, 2] + img[r, c, 2])
            counts[label, 0] = (counts[label, 0] + 1)

    label_colors = np.uint8(sums / counts)

    result = np.zeros_like(img)    
    for r in range(rows):
        for c in range(cols):
            label = labels[r,c]
            result[r, c, 0] = label_colors[label, 0]
            result[r, c, 1] = label_colors[label, 1]
            result[r, c, 2] = label_colors[label, 2]

    return result

Контрольный скрипт:

from superpix_harness import *

import fast_labels_python
measure_fn(fast_labels_python.super_pixelize)

Время:

Reduction, Width, Height, SP Size, SP Count, Time Load, Time SP, Time Process, Time Save, Time Total
3 ,  420 ,  336 , 100 ,  155 , 0.1530 , 0.0590 , 0.5160 , 0.0070 , 0.7350
3 ,  420 ,  336 ,  50 ,  568 , 0.1470 , 0.0680 , 0.5250 , 0.0070 , 0.7470
3 ,  420 ,  336 ,  25 , 1415 , 0.1480 , 0.0740 , 0.5140 , 0.0070 , 0.7430
3 ,  420 ,  336 ,  12 , 3009 , 0.1490 , 0.0870 , 0.5190 , 0.0070 , 0.7620
2 ,  839 ,  672 , 100 ,  617 , 0.1480 , 0.3770 , 2.0720 , 0.0150 , 2.6120
2 ,  839 ,  672 ,  50 , 1732 , 0.1490 , 0.3680 , 2.0480 , 0.0160 , 2.5810
2 ,  839 ,  672 ,  25 , 3556 , 0.1470 , 0.3730 , 2.0570 , 0.0150 , 2.5920
2 ,  839 ,  672 ,  12 , 6627 , 0.1460 , 0.4140 , 2.0530 , 0.0170 , 2.6300
1 , 1678 , 1344 , 100 , 1854 , 0.1430 , 2.2080 , 8.2970 , 0.0470 , 10.6950
1 , 1678 , 1344 ,  50 , 4519 , 0.1430 , 2.2240 , 8.2970 , 0.0480 , 10.7120
1 , 1678 , 1344 ,  25 , 9083 , 0.1430 , 2.2020 , 8.2280 , 0.0490 , 10.6220
1 , 1678 , 1344 ,  12 , 17869 , 0.1430 , 2.3010 , 8.3210 , 0.0520 , 10.8170
0 , 3356 , 2687 , 100 , 4786 , 0.1270 , 10.8630 , 33.0230 , 0.1580 , 44.1710
0 , 3356 , 2687 ,  50 , 11942 , 0.1270 , 10.7950 , 32.9230 , 0.1660 , 44.0110
0 , 3356 , 2687 ,  25 , 29066 , 0.1260 , 10.7270 , 33.3660 , 0.1790 , 44.3980
0 , 3356 , 2687 ,  12 , 59634 , 0.1270 , 11.0840 , 33.1850 , 0.1790 , 44.5750

Будучи чистой реализацией Python, это действительно страдает от накладных расходов интерпретатора.При небольших размерах изображения и низком количестве суперпикселей эти издержки преобладают.Тем не менее, мы можем видеть, что для данного размера изображения производительность остается достаточно постоянной независимо от количества суперпикселей.Это хороший знак.Еще одним хорошим признаком является то, что при достаточно больших размерах изображений и количестве суперпикселей наш более эффективный алгоритм начинает побеждать.

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


Использование Cython

Cython предоставляет средства для перевода (аннотированный)Код Python для C, и скомпилируйте результат как двоичный модуль Python.Это может привести к огромным улучшениям производительности, если все сделано правильно.Cython также включает в себя поддержку для массивов NumPy , которыми мы можем воспользоваться.

NB: Вам нужно будет взять уроки и документацию по Cython и сделать некоторыеэкспериментирование, чтобы выяснить, как комментировать вещи, чтобы получить лучшую производительность (как я сделал) - подробное объяснение выходит далеко за рамки этого (уже чрезмерного) ответа.

File fast_labels_cython.pyx

# cython: infer_types=True
import numpy as np
cimport cython

@cython.boundscheck(False)  # Deactivate bounds checking
@cython.wraparound(False)   # Deactivate negative indexing.
def super_pixelize(unsigned char[:, :, :] img, int[:, :] labels, int superpixel_count):
    cdef Py_ssize_t rows = img.shape[0]
    cdef Py_ssize_t cols = img.shape[1]

    assert img.shape[0] == labels.shape[0]
    assert img.shape[1] == labels.shape[1]
    assert img.shape[2] == 3

    sums = np.zeros((superpixel_count, 3), dtype=np.int64)
    cdef long long[:, ::1] sums_view = sums

    counts = np.zeros((superpixel_count, 1), dtype=np.int64)
    cdef long long[:, ::1] counts_view = counts

    cdef Py_ssize_t r, c
    cdef int label

    for r in range(rows):
        for c in range(cols):
            label = labels[r,c]
            sums_view[label, 0] = (sums_view[label, 0] + img[r, c, 0])
            sums_view[label, 1] = (sums_view[label, 1] + img[r, c, 1])
            sums_view[label, 2] = (sums_view[label, 2] + img[r, c, 2])
            counts_view[label, 0] = (counts_view[label, 0] + 1)

    label_colors = np.uint8(sums / counts)
    cdef unsigned char[:, ::1] label_colors_view = label_colors

    result = np.zeros_like(img)    
    cdef unsigned char[:, :, ::1] result_view = result

    for r in range(rows):
        for c in range(cols):
            label = labels[r,c]
            result_view[r, c, 0] = label_colors_view[label, 0]
            result_view[r, c, 1] = label_colors_view[label, 1]
            result_view[r, c, 2] = label_colors_view[label, 2]

    return result

Компиляция:

cythonize.exe -2 -i fast_labels_cython.pyx

Базовый сценарий:

from superpix_harness import *

import fast_labels_python
measure_fn(fast_labels_python.super_pixelize)

Время:

Reduction, Width, Height, SP Size, SP Count, Time Load, Time SP, Time Process, Time Save, Time Total
3 ,  420 ,  336 , 100 ,  155 , 0.1550 , 0.0600 , 0.0010 , 0.0080 , 0.2240
3 ,  420 ,  336 ,  50 ,  568 , 0.1500 , 0.0680 , 0.0010 , 0.0070 , 0.2260
3 ,  420 ,  336 ,  25 , 1415 , 0.1480 , 0.0750 , 0.0010 , 0.0070 , 0.2310
3 ,  420 ,  336 ,  12 , 3009 , 0.1490 , 0.0880 , 0.0010 , 0.0070 , 0.2450
2 ,  839 ,  672 , 100 ,  617 , 0.1480 , 0.3580 , 0.0040 , 0.0150 , 0.5250
2 ,  839 ,  672 ,  50 , 1732 , 0.1480 , 0.3680 , 0.0050 , 0.0150 , 0.5360
2 ,  839 ,  672 ,  25 , 3556 , 0.1480 , 0.3780 , 0.0040 , 0.0170 , 0.5470
2 ,  839 ,  672 ,  12 , 6627 , 0.1470 , 0.4080 , 0.0040 , 0.0170 , 0.5760
1 , 1678 , 1344 , 100 , 1854 , 0.1440 , 2.2340 , 0.0170 , 0.0450 , 2.4400
1 , 1678 , 1344 ,  50 , 4519 , 0.1430 , 2.2450 , 0.0170 , 0.0480 , 2.4530
1 , 1678 , 1344 ,  25 , 9083 , 0.1440 , 2.2290 , 0.0170 , 0.0480 , 2.4380
1 , 1678 , 1344 ,  12 , 17869 , 0.1460 , 2.3310 , 0.0180 , 0.0500 , 2.5450
0 , 3356 , 2687 , 100 , 4786 , 0.1290 , 11.0840 , 0.0690 , 0.1560 , 11.4380
0 , 3356 , 2687 ,  50 , 11942 , 0.1330 , 10.7650 , 0.0680 , 0.1680 , 11.1340
0 , 3356 , 2687 ,  25 , 29066 , 0.1310 , 10.8120 , 0.0770 , 0.1710 , 11.1910
0 , 3356 , 2687 ,  12 , 59634 , 0.1310 , 11.1200 , 0.0790 , 0.1770 , 11.5070

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


Использование Boost.Python

Другой вариант - реализовать алгоритм непосредственно на языке более низкого уровня.Из-за моего знакомства я реализовал двоичный модуль Python в C ++, используя Boost.Python .В библиотеке также есть поддержка массивов Numpy, поэтому работа в основном сводилась к проверке входных аргументов и затем переносу алгоритма для использования необработанных указателей.

Файл fast_labels.cpp

#define BOOST_ALL_NO_LIB
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <iostream>

namespace bp = boost::python;

bp::numpy::ndarray super_pixelize(bp::numpy::ndarray const& image
    , bp::numpy::ndarray const& labels
    , int32_t label_count)
{
    if (image.get_dtype() != bp::numpy::dtype::get_builtin<uint8_t>()) {
        throw std::runtime_error("Invalid image dtype.");
    }
    if (image.get_nd() != 3) {
        throw std::runtime_error("Image must be a 3d ndarray.");
    }
    if (image.shape(2) != 3) {
        throw std::runtime_error("Image must have 3 channels.");
    }

    if (labels.get_dtype() != bp::numpy::dtype::get_builtin<int32_t>()) {
        throw std::runtime_error("Invalid label dtype.");
    }
    if (!((labels.get_nd() == 2) || ((labels.get_nd() == 3) && (labels.shape(2) == 1)))) {
        throw std::runtime_error("Labels must have 1 channel.");
    }

    if ((image.shape(0) != labels.shape(0)) || (image.shape(1) != labels.shape(1))) {
        throw std::runtime_error("Image and labels have incompatible shapes.");
    }

    if (label_count < 1) {
        throw std::runtime_error("Must have at least 1 label.");
    }

    bp::numpy::ndarray result(bp::numpy::zeros(image.get_nd(), image.get_shape(), image.get_dtype()));

    int32_t const ROWS(image.shape(0));
    int32_t const COLUMNS(image.shape(1));

    int32_t const ROW_STRIDE_IMAGE(image.strides(0));
    int32_t const COLUMN_STRIDE_IMAGE(image.strides(1));

    int32_t const ROW_STRIDE_LABELS(labels.strides(0));
    int32_t const COLUMN_STRIDE_LABELS(labels.strides(1));

    int32_t const ROW_STRIDE_RESULT(result.strides(0));
    int32_t const COLUMN_STRIDE_RESULT(result.strides(1));

    struct label_info
    {
        int64_t sum_b = 0;
        int64_t sum_g = 0;
        int64_t sum_r = 0;
        int64_t count = 0;
    };

    struct pixel_type
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
    };

    // Step 1: Collect data for each label
    std::vector<label_info> info(label_count);
    {
        char const* labels_row_ptr(labels.get_data());
        char const* image_row_ptr(image.get_data());
        for (int32_t row(0); row < ROWS; ++row) {
            char const* labels_col_ptr(labels_row_ptr);
            char const* image_col_ptr(image_row_ptr);
            for (int32_t col(0); col < COLUMNS; ++col) {
                int32_t label(*reinterpret_cast<int32_t const*>(labels_col_ptr));
                label_info& current_info(info[label]);

                pixel_type const& pixel(*reinterpret_cast<pixel_type const*>(image_col_ptr));
                current_info.sum_b += pixel.b;
                current_info.sum_g += pixel.g;
                current_info.sum_r += pixel.r;
                ++current_info.count;

                labels_col_ptr += COLUMN_STRIDE_LABELS;
                image_col_ptr += COLUMN_STRIDE_IMAGE;
            }
            labels_row_ptr += ROW_STRIDE_LABELS;
            image_row_ptr += ROW_STRIDE_IMAGE;
        }
    }

    // Step 2: Calculate mean color for each label
    std::vector<pixel_type> label_color(label_count);
    for (int32_t label(0); label < label_count; ++label) {
        label_info& current_info(info[label]);
        pixel_type& current_color(label_color[label]);

        current_color.b = current_info.sum_b / current_info.count;
        current_color.g = current_info.sum_g / current_info.count;
        current_color.r = current_info.sum_r / current_info.count;
    }


    // Step 3: Generate result
    {
        char const* labels_row_ptr(labels.get_data());
        char* result_row_ptr(result.get_data());
        for (int32_t row(0); row < ROWS; ++row) {
            char const* labels_col_ptr(labels_row_ptr);
            char* result_col_ptr(result_row_ptr);
            for (int32_t col(0); col < COLUMNS; ++col) {
                int32_t label(*reinterpret_cast<int32_t const*>(labels_col_ptr));
                pixel_type const& current_color(label_color[label]);

                pixel_type& pixel(*reinterpret_cast<pixel_type*>(result_col_ptr));
                pixel.b = current_color.b;
                pixel.g = current_color.g;
                pixel.r = current_color.r;

                labels_col_ptr += COLUMN_STRIDE_LABELS;
                result_col_ptr += COLUMN_STRIDE_RESULT;
            }
            labels_row_ptr += ROW_STRIDE_LABELS;
            result_row_ptr += ROW_STRIDE_RESULT;
        }
    }

    return result;
}

BOOST_PYTHON_MODULE(fast_labels)
{
    bp::numpy::initialize();

    bp::def("super_pixelize", super_pixelize);
}

Компиляция:

За рамками этого ответа.Я использовал CMake для создания DLL, а затем переименовал ее в .pyd, чтобы Python распознал ее.

Сценарий тестирования:

from superpix_harness import *

import fast_labels
measure_fn(fast_labels.super_pixelize)

Время:

Reduction, Width, Height, SP Size, SP Count, Time Load, Time SP, Time Process, Time Save, Time Total
3 ,  420 ,  336 , 100 ,  155 , 0.1480 , 0.0580 , 0.0010 , 0.0070 , 0.2140
3 ,  420 ,  336 ,  50 ,  568 , 0.1490 , 0.0690 , 0.0010 , 0.0070 , 0.2260
3 ,  420 ,  336 ,  25 , 1415 , 0.1510 , 0.0820 , 0.0010 , 0.0070 , 0.2410
3 ,  420 ,  336 ,  12 , 3009 , 0.1510 , 0.0970 , 0.0010 , 0.0070 , 0.2560
2 ,  839 ,  672 , 100 ,  617 , 0.1490 , 0.3750 , 0.0030 , 0.0150 , 0.5420
2 ,  839 ,  672 ,  50 , 1732 , 0.1480 , 0.7540 , 0.0020 , 0.0160 , 0.9200
2 ,  839 ,  672 ,  25 , 3556 , 0.1490 , 0.7070 , 0.0030 , 0.0160 , 0.8750
2 ,  839 ,  672 ,  12 , 6627 , 0.1590 , 0.7300 , 0.0030 , 0.0160 , 0.9080
1 , 1678 , 1344 , 100 , 1854 , 0.1430 , 3.7120 , 0.0100 , 0.0450 , 3.9100
1 , 1678 , 1344 ,  50 , 4519 , 0.1430 , 2.2510 , 0.0090 , 0.0510 , 2.4540
1 , 1678 , 1344 ,  25 , 9083 , 0.1430 , 2.2080 , 0.0100 , 0.0480 , 2.4090
1 , 1678 , 1344 ,  12 , 17869 , 0.1680 , 2.4280 , 0.0100 , 0.0500 , 2.6560
0 , 3356 , 2687 , 100 , 4786 , 0.1270 , 10.9230 , 0.0380 , 0.1580 , 11.2460
0 , 3356 , 2687 ,  50 , 11942 , 0.1300 , 10.8860 , 0.0390 , 0.1640 , 11.2190
0 , 3356 , 2687 ,  25 , 29066 , 0.1270 , 10.8080 , 0.0410 , 0.1800 , 11.1560
0 , 3356 , 2687 ,  12 , 59634 , 0.1280 , 11.1280 , 0.0410 , 0.1800 , 11.4770

Немного лучше, хотя, поскольку мы более чем на 2 порядка быстрее, чем код, определяющий метки суперпикселя, нет необходимости идти дальше.С самым большим изображением и наименьшим размером суперпикселя мы улучшили его более чем на 6 порядков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...