OpenCV и Python: арифметические операции между скаляром и всеми пикселями и каналами

Я пытаюсь выполнять операции - в 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 вместо numpy?

Во-первых, мне кажется странным, что вы не можете сделать это напрямую с помощью функций 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 есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
0
2 374
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В привязках 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]]]

Вот это да!!! Я много искал это! Спасибо! Кроме того, я провел несколько тестов производительности, и результаты очень интересны (и, возможно, актуальны): запуск 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)).

Gustavo Kaneto 17.09.2018 21:34

@GustavoK Да, оба должны занять одинаковое количество времени. Когда вы устанавливаете для этого аргумента одно целое число или значение с плавающей запятой, оно интерпретируется так же, как если бы вы установили его на (n, 0, 0, 0) (где n - значение). Следовательно, обработка идет точно так же.

Dan Mašek 17.09.2018 21:55

Только одно замечание для тех, кто в будущем: решение работает для сложения, вычитания, умножения, деления ... но cv.pow(img, n) применяет ^n для всех каналов сразу (мое ожидаемое поведение), а использование cv.pow(img, (n,n,n,0)) вызывает ошибку. (Я тестировал расчеты гамма-коррекции. Используя Python 3.6 и OpenCV 3.4.2)

Gustavo Kaneto 17.09.2018 22:08

@GustavoK Это потому, что в C++ второй параметр pow имеет тип double - это означает, что вы можете использовать только один и тот же показатель степени для всех каналов. То же самое и в Python.

Dan Mašek 17.09.2018 22:13

Другие вопросы по теме