Я пытаюсь выполнять операции - в Python - с цветными изображениями (3 канала), такие как сложение, умножение и т.д ... Используя, например, cv.add(img, value), где img - это трехканальное изображение, а value - скаляр.
Но функции меняют только первый канал. Я обнаружил, что в C++ вы должны использовать Scalar(value1, value2, value3) для применения операций ко всем каналам.
Как я могу это сделать в Python? Есть ли способ передать эти 3 скалярных значения функции одновременно, чтобы не использовать циклы?
Редактировать: также, я думаю, что предпочтительнее использовать функции openCV, потому что они имеют то преимущество, что являются «насыщенными операциями». При работе с uint8, например, использование cv.add (250 + 10) вернет 255, а не 260. А при использовании numpy 250 + 10 = 260% 256 = 4.
Я создал изображение 4x4 пикселя, 3 канала, а затем попытался добавить скаляр.
import cv2 as cv
import numpy as np
img = np.zeros((4,4,3), np.uint8)
print(img)
cv.add(img, 2)
И вот результаты:
array([[[2, 0, 0],
[2, 0, 0],
[2, 0, 0],
[2, 0, 0]],
[[2, 0, 0],
[2, 0, 0],
[2, 0, 0],
[2, 0, 0]],
[[2, 0, 0],
[2, 0, 0],
[2, 0, 0],
[2, 0, 0]],
[[2, 0, 0],
[2, 0, 0],
[2, 0, 0],
[2, 0, 0]]], dtype=uint8)
Но если я использую только один пиксель, одну строку или один столбец, результаты будут правильными:
a = img[1,1]; print(a)
cv.add(a, 2)
a = img[:,1]; print(a)
cv.add(a, 2)
a = img[1,:,]; print(a)
cv.add(a, 2)
Результаты для последнего из трех приведенных выше примеров:
In [341]: a = img[1,:,]; print(a)
[[0 0 0]
[0 0 0]
[0 0 0]
[0 0 0]]
In [342]: cv.add(a, 2)
Out[342]:
array([[2, 2, 2],
[2, 2, 2],
[2, 2, 2],
[2, 2, 2]], dtype=uint8)
Во-первых, мне кажется странным, что вы не можете сделать это напрямую с помощью функций opencv. : P (Кроме того, функция работает для 1 столбца или 1 строки пикселей; это заставляет меня думать, что есть простое решение, чтобы заставить ее работать в opencv-python.)
Во-вторых, производительность кажется совсем другой. Абсолютное время не так уж велико, но если вам, например, нужно выполнить некоторую тяжелую обработку в живом видео, это может иметь значение.
Я запустил несколько простых cv.add() против numpy, добавив скаляр к одноканальному изображению:
img = np.zeros((500,500,1), np.uint8)
# sum without saturation
%timeit res1 = img + 100
%timeit res2 = cv.add(img, 100)
#sum with saturation (going over 255)
%timeit res1 = img + 300
%timeit res2 = cv.add(img, 300)
А вот результаты производительности:
In [56]: %timeit res1 = img + 100
688 µs ± 19.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [57]: %timeit res2 = cv.add(img, 100)
129 µs ± 9.96 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [58]: %timeit res1 = img + 300
1.41 ms ± 101 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [59]: %timeit res2 = cv.add(img, 300)
736 µs ± 9.04 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
При операциях без насыщения добавление numpy примерно в 5 раз медленнее, чем opencv. С насыщенностью примерно в 2 раза медленнее. Но вам все равно нужно исправить результаты numpy, чтобы он показал насыщенное значение 255; и в конечном итоге вам придется преобразовать его обратно в uint8 (numpy преобразовал результат в uint16, чтобы учесть результаты):
%timeit res1 = img + 300; res1[res1 > 255] = 255
2.89 ms ± 67.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit res1 = img + 300; res1[res1 > 255] = 255; res1 = np.uint8(res1)
3.79 ms ± 300 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Итак, полная операция снова примерно в 5 раз медленнее при использовании numpy ...






В привязках Python OpenCV, если вы хотите передать что-то в функцию OpenCV, которая должна интерпретироваться как скаляр, вы должны использовать кортеж с элементами 4. Размер важен, он позволяет коду оболочки распознавать его как таковой. Это соответствует типу cv::Scalar C++, который также содержит 4 значения. Используются только необходимые значения (соответствующие глубине канала другого операнда), остальные игнорируются.
Пример:
import cv2
import numpy as np
img = np.ones((4,4,3), np.uint8)
print cv2.add(img, (1,2,255,0))
Вывод в консоль:
[[[ 2 3 255]
[ 2 3 255]
[ 2 3 255]
[ 2 3 255]]
[[ 2 3 255]
[ 2 3 255]
[ 2 3 255]
[ 2 3 255]]
[[ 2 3 255]
[ 2 3 255]
[ 2 3 255]
[ 2 3 255]]
[[ 2 3 255]
[ 2 3 255]
[ 2 3 255]
[ 2 3 255]]]
@GustavoK Да, оба должны занять одинаковое количество времени. Когда вы устанавливаете для этого аргумента одно целое число или значение с плавающей запятой, оно интерпретируется так же, как если бы вы установили его на (n, 0, 0, 0) (где n - значение). Следовательно, обработка идет точно так же.
Только одно замечание для тех, кто в будущем: решение работает для сложения, вычитания, умножения, деления ... но cv.pow(img, n) применяет ^n для всех каналов сразу (мое ожидаемое поведение), а использование cv.pow(img, (n,n,n,0)) вызывает ошибку. (Я тестировал расчеты гамма-коррекции. Используя Python 3.6 и OpenCV 3.4.2)
@GustavoK Это потому, что в C++ второй параметр pow имеет тип double - это означает, что вы можете использовать только один и тот же показатель степени для всех каналов. То же самое и в Python.
Вот это да!!! Я много искал это! Спасибо! Кроме того, я провел несколько тестов производительности, и результаты очень интересны (и, возможно, актуальны): запуск
cv.add(img, 10)(добавление одного простого значения) иcv.add(img, (10, 10, 10, 0))(ваше решение: добавьте 3 значения одновременно) занимает такое же количество времени. . Итак, если вы запустите циклforдля распространенияcv.add(img, 10)по всем трем каналам, он будет в 3 раза медленнее, чем простойcv.add(img, (10, 10, 10, 0)).