5.9. Арифметические операции

Семейство рисования из предыдущего раздела рисует в изображение. Арифметическое семейство объединяет два изображения в третье – складывая их значения пикселей, вычитая одно из другого, беря минимум или максимум в каждой позиции. Этот небольшой набор попиксельных арифметических операций – это то, на чем построены разностное вычитание кадров, вычитание фона, наложение экспозиций и горстка других классических приемов.

Арифметическое семейство класса Image достаточно мало, чтобы перечислить его целиком:

  • add() – попиксельно self + other, с ограничением максимумом формата.

  • sub() – попиксельно self - other, с ограничением 0 снизу.

  • rsub() – попиксельно other - self, с ограничением 0 (та же арифметика, что и sub, но с переставленными операндами).

  • min() – попиксельный минимум двух значений.

  • max() – попиксельный максимум.

  • difference() – попиксельно |self - other|, абсолютная разница.

Плюс две связанные операции над одним изображением:

  • invert() – заменяет каждый пиксель на 255 - pixel (или эквивалентный максимум для формата).

  • negate() – псевдоним для invert().

Две горизонтальные градиентные полосы сверху, представляющие исходные изображения A и B -- A идет от темного к яркому слева направо, B идет от яркого к темному слева направо. Под ними пять градиентных полос, представляющих результат каждой попарной операции, примененной к A и B: A.add(B) выглядит равномерно белым, потому что каждая позиция в сумме превышает 255 и обрезается; A.sub(B) равно нулю в левой половине и ярчает к правому краю; A.difference(B) показывает форму V, яркую на каждом конце и темную в середине; A.min(B) темная на концах и ярче в середине; A.max(B) яркая на концах и серая в середине.

Два исходных градиента A и B и результат каждой попарной операции, примененной к ним. Каждая операция выполняется позиция за позицией – то, что показано в результате в любом отдельном месте, зависит только от двух исходных пикселей в этом месте.

5.9.1. Две формы операндов

Каждый из методов над двумя изображениями принимает любую из форм для своего второго операнда:

  • Другое изображение Image тех же размеров. Арифметика выполняется позиция за позицией – результат в (x, y) – это операция, примененная к исходным пикселям в (x, y) обоих изображений.

  • Скалярное значение – целое число для оттенков серого, кортеж (r, g, b) для RGB565. Один и тот же скаляр применяется в каждой позиции.

Скалярная форма полезна, когда приложению нужно сдвинуть каждый пиксель на постоянную величину. img.add(40) осветляет все изображение на 40; img.sub((20, 20, 20)) затемняет каждый пиксель на 20 по каждому каналу; img.max(50) поднимает любой пиксель ниже 50 до 50 и оставляет остальные нетронутыми – та операция, которая превращает почти черный нижний порог датчика в ровный темно-серый, относительно которого работают последующие этапы.

5.9.2. Ограничение значений

Значения пикселей остаются внутри диапазона формата при каждой операции. Для 8-битного канала это означает 0255: все, что переполнилось бы за 255, обрезается обратно до 255, а все, что ушло бы ниже 0, поднимается до 0. Циклического переноса нет.

Этот выбор имеет значение на практике. add, осветляющий пиксели, никогда не создает внезапный артефакт затемнения на ярком конце, где математика иначе переполнилась бы; sub, затемняющий пиксели, никогда не создает внезапный артефакт осветления на темном конце, где иначе произошло бы антипереполнение. Результаты остаются визуально осмысленными ценой некоторой потери информации на насыщенных крайностях.

Ограничение значений также является причиной того, почему sub и rsub возвращают разные результаты. img_a.sub(img_b) дает ту часть a, которая ярче b, и ноль везде остальном; img_a.rsub(img_b) дает ту часть b, которая ярче a. Любой из них полезен для одностороннего обнаружения изменений – если приложение заботится только о пикселях, которые стали ярче, или только о тех, что стали темнее, – но ни один не захватывает все изменения между двумя кадрами.

5.9.3. Операция разницы

Для двустороннего обнаружения изменений операция, к которой стоит обратиться, – это difference(), которая вычисляет |self - other| в каждой позиции – абсолютную разницу, без знака. Каждый пиксель, изменившийся в любом направлении, проявляется как ненулевое значение в результате, причем величина пропорциональна тому, насколько он изменился в этой позиции.

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

5.9.4. Ограничение области с помощью маски

Все арифметические методы принимают именованный аргумент mask, представленный на странице об областях и масках. Когда передается маска, операция выполняется только в позициях, где маска ненулевая; везде остальном целевое изображение остается нетронутым.

Эта композиция проявляется в двух приемах. Первый – ограничение операции известной областью: например, сложение двух кадров только внутри ограничивающей рамки обнаруженного маркера. Второй – построение составного кадра по частям – min по последовательности кадров внутри маски переднего плана, max по той же последовательности внутри дополняющей маски – такого рода прием.

5.9.5. На месте, с сохранением входных данных

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

Когда приложению нужно сохранить оба входа, безопасный прием – сначала скопировать один из них:

diff = current.copy()       # leaves current intact
diff.difference(reference)  # diff now holds the absolute difference

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

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