Манипуляция пикселями

Манипуляция пикселями — это процесс изменения цветовых данных изображения непосредственно на уровне отдельных пикселей перед их отображением на карте. В OpenLayers такая возможность используется для создания визуальных эффектов, тематического отображения растровых данных, анализа изображений, выделения объектов и динамической обработки картографической информации в браузере.

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

  • R (Red) — красный;
  • G (Green) — зелёный;
  • B (Blue) — синий;
  • A (Alpha) — прозрачность.

Каждый канал хранится в диапазоне от 0 до 255.

Например, пиксель полностью красного цвета выглядит следующим образом:

[255, 0, 0, 255]

Полностью прозрачный пиксель:

[0, 0, 0, 0]

В OpenLayers манипуляция пикселями обычно применяется при работе с растровыми слоями и механизмом растровой обработки (ol/source/Raster).


RasterSource как основа пиксельной обработки

Для изменения пикселей используется источник данных RasterSource.

Схема работы выглядит следующим образом:

  1. Исходный слой получает изображение.
  2. RasterSource извлекает пиксельные данные.
  3. Выполняется пользовательская операция.
  4. Результат отображается в новом слое.

Пример создания растрового источника:

import RasterSource from 'ol/source/Raster.js';
import OSM from 'ol/source/OSM.js';

const raster = new RasterSource({
    sources: [
        new OSM()
    ],
    operationType: 'pixel',
    operation: function(pixel) {
        return pixel;
    }
});

В данном случае операция ничего не изменяет и возвращает исходный пиксель.


Режимы обработки RasterSource

OpenLayers поддерживает два режима выполнения операций.

pixel

Операция вызывается для каждого пикселя отдельно.

operationType: 'pixel'

Функция получает массив:

function(pixel) {
    return pixel;
}

Структура:

[
    red,
    green,
    blue,
    alpha
]

Этот режим наиболее удобен для цветовых преобразований.


image

Операция получает сразу всё изображение.

operationType: 'image'

Пример:

function(inputs) {
    return inputs[0];
}

Такой подход подходит для сложных вычислений и анализа соседних пикселей.


Инвертирование цветов

Одной из самых простых операций является инверсия.

Исходный принцип:

новый цвет = 255 - старый цвет

Реализация:

const raster = new RasterSource({
    sources: [osmSource],
    operation: function(pixel) {

        pixel[0] = 255 - pixel[0];
        pixel[1] = 255 - pixel[1];
        pixel[2] = 255 - pixel[2];

        return pixel;
    }
});

Результат:

  • чёрный становится белым;
  • белый становится чёрным;
  • красный превращается в голубой;
  • зелёный превращается в пурпурный.

Перевод изображения в оттенки серого

Градации серого часто используются для анализа картографических изображений.

Наиболее распространённая формула:

Gray =
0.299 × R +
0.587 × G +
0.114 × B

Реализация:

const raster = new RasterSource({
    sources: [osmSource],
    operation: function(pixel) {

        const gray =
            0.299 * pixel[0] +
            0.587 * pixel[1] +
            0.114 * pixel[2];

        pixel[0] = gray;
        pixel[1] = gray;
        pixel[2] = gray;

        return pixel;
    }
});

После обработки карта отображается как чёрно-белое изображение.


Повышение яркости

Для увеличения яркости к каждому каналу добавляется некоторое значение.

const raster = new RasterSource({
    sources: [osmSource],
    operation: function(pixel) {

        pixel[0] += 40;
        pixel[1] += 40;
        pixel[2] += 40;

        return pixel;
    }
});

Однако значения не должны превышать 255.

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

function(pixel) {

    pixel[0] = Math.min(255, pixel[0] + 40);
    pixel[1] = Math.min(255, pixel[1] + 40);
    pixel[2] = Math.min(255, pixel[2] + 40);

    return pixel;
}

Уменьшение яркости

Затемнение выполняется аналогично.

function(pixel) {

    pixel[0] = Math.max(0, pixel[0] - 40);
    pixel[1] = Math.max(0, pixel[1] - 40);
    pixel[2] = Math.max(0, pixel[2] - 40);

    return pixel;
}

Чем больше величина вычитания, тем темнее становится изображение.


Изменение контрастности

Контрастность влияет на различимость светлых и тёмных областей.

Формула:

newValue =
(value - 128) * factor + 128

Пример:

const factor = 1.5;

function(pixel) {

    pixel[0] = (pixel[0] - 128) * factor + 128;
    pixel[1] = (pixel[1] - 128) * factor + 128;
    pixel[2] = (pixel[2] - 128) * factor + 128;

    return pixel;
}

При значениях:

  • больше 1 — контрастность увеличивается;
  • меньше 1 — уменьшается.

Управление прозрачностью

Альфа-канал расположен в четвёртом элементе массива.

pixel[3]

Полупрозрачность:

function(pixel) {

    pixel[3] = 128;

    return pixel;
}

Полная прозрачность:

function(pixel) {

    pixel[3] = 0;

    return pixel;
}

Полная непрозрачность:

function(pixel) {

    pixel[3] = 255;

    return pixel;
}

Выделение определённого цвета

Иногда требуется оставить только объекты определённого цвета.

Например, выделение зелёных областей.

function(pixel) {

    const red = pixel[0];
    const green = pixel[1];
    const blue = pixel[2];

    if (green > 120 &&
        green > red &&
        green > blue) {

        return pixel;
    }

    return [0, 0, 0, 0];
}

В результате остаются только зелёные пиксели.

Подобная техника применяется для:

  • анализа растительности;
  • обработки спутниковых снимков;
  • тематического картографирования.

Создание эффекта сепии

Сепия часто используется для стилизации карт.

Формулы преобразования:

newRed =
0.393R + 0.769G + 0.189B

newGreen =
0.349R + 0.686G + 0.168B

newBlue =
0.272R + 0.534G + 0.131B

Реализация:

function(pixel) {

    const r = pixel[0];
    const g = pixel[1];
    const b = pixel[2];

    pixel[0] =
        0.393 * r +
        0.769 * g +
        0.189 * b;

    pixel[1] =
        0.349 * r +
        0.686 * g +
        0.168 * b;

    pixel[2] =
        0.272 * r +
        0.534 * g +
        0.131 * b;

    return pixel;
}

Получается эффект старой фотографии.


Постеризация изображения

Постеризация уменьшает количество цветовых уровней.

Пример:

function(pixel) {

    pixel[0] =
        Math.floor(pixel[0] / 64) * 64;

    pixel[1] =
        Math.floor(pixel[1] / 64) * 64;

    pixel[2] =
        Math.floor(pixel[2] / 64) * 64;

    return pixel;
}

Изображение приобретает характерный ступенчатый стиль.


Использование пользовательских данных

В RasterSource можно передавать дополнительные параметры.

Создание:

const raster = new RasterSource({
    sources: [osmSource],
    operation: function(pixel, data) {

        pixel[0] *= data.redFactor;
        pixel[1] *= data.greenFactor;
        pixel[2] *= data.blueFactor;

        return pixel;
    }
});

Передача параметров:

raster.set('redFactor', 1.2);
raster.set('greenFactor', 1.0);
raster.set('blueFactor', 0.8);

Подготовка данных:

raster.on('beforeoperations', function(event) {

    event.data.redFactor =
        raster.get('redFactor');

    event.data.greenFactor =
        raster.get('greenFactor');

    event.data.blueFactor =
        raster.get('blueFactor');
});

Такой механизм позволяет изменять обработку без пересоздания слоя.


Событие beforeoperations

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

Пример:

raster.on(
    'beforeoperations',
    function(event) {

        event.data.threshold = 100;
    }
);

Получение данных:

function(pixel, data) {

    if (
        pixel[0] > data.threshold
    ) {
        pixel[0] = 255;
    }

    return pixel;
}

Событие удобно использовать для передачи настроек пользовательского интерфейса.


Событие afteroperations

После завершения обработки вызывается событие:

raster.on(
    'afteroperations',
    function(event) {

        console.log(
            'Обработка завершена'
        );
    }
);

Обычно применяется для:

  • сбора статистики;
  • логирования;
  • отображения результатов вычислений;
  • анализа производительности.

Комбинирование нескольких источников

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

const raster = new RasterSource({
    sources: [
        source1,
        source2
    ],
    operationType: 'pixel',
    operation: function(pixels) {

        const p1 = pixels[0];
        const p2 = pixels[1];

        return [
            (p1[0] + p2[0]) / 2,
            (p1[1] + p2[1]) / 2,
            (p1[2] + p2[2]) / 2,
            255
        ];
    }
});

Подобный подход используется при:

  • объединении спутниковых снимков;
  • сравнении растров;
  • визуализации различий между изображениями;
  • построении аналитических карт.

Пороговая фильтрация

Пороговая обработка преобразует изображение в чёрно-белое.

Пример:

function(pixel) {

    const gray =
        (
            pixel[0] +
            pixel[1] +
            pixel[2]
        ) / 3;

    if (gray > 128) {
        return [255, 255, 255, 255];
    }

    return [0, 0, 0, 255];
}

Такой алгоритм применяется для:

  • выделения объектов;
  • сегментации изображений;
  • предварительной обработки данных компьютерного зрения.

Производительность пиксельных операций

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

Минимизировать количество вычислений

Плохо:

Math.pow(value, 2)

Лучше:

value * value

Избегать создания лишних объектов

Плохо:

return [
    pixel[0],
    pixel[1],
    pixel[2],
    pixel[3]
];

Лучше:

return pixel;

если значения не изменяются.


Не выполнять тяжёлые запросы внутри операции

Недопустимо:

fetch('/data');

или

console.log(pixel);

для каждого пикселя.


Передавать параметры через beforeoperations

Так исключается повторное получение данных внутри миллионов вызовов функции обработки.


Практические области применения

Манипуляция пикселями в OpenLayers используется для решения широкого круга задач:

  • обработка спутниковых снимков;
  • визуализация индексов растительности;
  • цветокоррекция растров;
  • тематическое отображение данных;
  • сравнение изображений разных периодов;
  • выделение объектов по цвету;
  • создание специальных визуальных эффектов;
  • анализ результатов дистанционного зондирования Земли;
  • предварительная подготовка данных для геоинформационных систем;
  • динамическое изменение внешнего вида карт без модификации исходных файлов.

Гибкость RasterSource позволяет реализовывать как простые цветовые фильтры, так и сложные алгоритмы обработки изображений непосредственно в браузере, используя стандартные возможности JavaScript и инфраструктуру OpenLayers.