5.18. Гистограммы и статистика

Помимо операций, изменяющих пиксели изображения, класс Image содержит семейство методов, которые их измеряют – обобщают распределение значений пикселей, возвращают среднюю и медианную яркость, находят оптимальную границу между тёмными и яркими пикселями, сообщают разброс цветовых каналов. Измерения находят применение в приложениях двумя способами: как входные данные для кода, который решает, какой порог использовать, какое усиление установить, как выглядит тональный профиль сцены; и как диагностические сигналы – «достаточно ли яркая сцена?» – на которые приложение может реагировать, не принимая решения о каком-либо конкретном пикселе.

Отправной точкой почти для любого измерения служит гистограмма.

5.18.1. Гистограмма

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

get_histogram() вычисляет её:

h = img.get_histogram()

Возвращаемый объект – это результат histogram, который предоставляет списки бинов по каждому каналу и несколько высокоуровневых запросов к ним. Счётчики бинов нормализованы так, что их сумма равна 1.0 – гистограмма описывает профиль распределения, а не абсолютное количество пикселей, что делает измерения сопоставимыми для изображений разных размеров.

Для изображений в оттенках серого гистограмма имеет один канал бинов, доступный как h.bins() (или эквивалентно h[0]). Для изображений RGB565 гистограмма вычисляется в цветовом пространстве LAB, представленном на странице бинарного порогирования, с тремя каналами бинов, доступными как h.l_bins(), h.a_bins(), h.b_bins() (или h[0], h[1], h[2]). LAB – тот же выбор, который используют методы порога и отслеживания; гистограммы согласуются с порогами относительно того, в каком пространстве измеряется цвет.

5.18.2. Бины и количество бинов

Гистограмма по умолчанию имеет один бин на каждое возможное значение пикселя – 256 бинов для 8-битного канала. Иногда это более тонкое разрешение, чем нужно приложению. Классификатору, которому важен лишь грубый профиль распределения, может больше подойти меньшее количество бинов – 32 или даже 8 бинов – что одновременно работает быстрее и даёт более чистый результат на фоне шума. Ключевое слово bins (и поканальные l_bins, a_bins, b_bins для цвета) задаёт это количество:

h = img.get_histogram(bins=32)

Ограничение по ROI и по порогу работает так же, как и во всех остальных методах измерения. Передайте roi, чтобы ограничить гистограмму прямоугольником пикселей; передайте список thresholds, чтобы включить только те пиксели, которые попадают в эти диапазоны. Форма с порогом – это то, что превращает «вычислить гистограмму только совпадающих пикселей» в операцию из одного вызова – распространённый шаблон, когда приложение хочет охарактеризовать текстуру уже обнаруженной области, не обходя пиксели вручную.

Гистограмма в оттенках серого, изображённая в виде ряда столбцов по диапазону яркости от 0 до 255. Распределение имеет два пика -- меньший тёмный пик и больший яркий пик -- разделённые чёткой впадиной. Поверх наложены три вертикальные линии: порог Оцу во впадине, среднее, смещённое к большему яркому пику, и медиана ещё правее, где накопленное число пикселей достигает половины.

Гистограмма в оттенках серого с тремя наложенными итоговыми измерениями: порог Оцу (граница, которая лучше всего разделяет тёмный и яркий кластеры), среднее и медиана. Каждое измерение говорит что-то своё об одном и том же распределении.

5.18.3. Статистика

Гистограмма – это описание распространённости каждого значения; статистика – это числовые сводки, полученные из неё. Объект statistics, возвращаемый методом get_statistics(), содержит стандартный набор:

  • mean – среднее арифметическое значений пикселей.

  • median – значение, ниже которого находится половина пикселей.

  • mode – самое часто встречающееся отдельное значение.

  • stdev – стандартное отклонение, мера разброса вокруг среднего.

  • min и max – самые яркие и самые тёмные присутствующие значения пикселей.

  • lq и uq – границы нижнего и верхнего квартилей.

Для изображения RGB565 поканальные формы (l_mean, a_median, b_mode и так далее) предоставляют те же измерения канал за каналом.

Большинство этих чисел возникают в конкретных контекстах. mean и stdev вместе дают оценку шума: сцена, которая должна быть однородной, имеет малое stdev, тогда как шумный датчик придаёт той же сцене большее stdev. min и max дают контраст изображения: чем они ближе, тем более плоская сцена; чем дальше друг от друга, тем больший динамический диапазон доступен алгоритму. median – устойчивый центр, когда в распределении есть выбросы (несколько очень ярких пикселей не смещают медиану так, как они смещают среднее). mode – самое часто встречающееся отдельное значение, полезное для определения уровня фона изображения, фон которого занимает большинство пикселей.

get_statistics() внутренне выполняет проход по гистограмме, а затем обобщает её; передача тех же аргументов thresholds и roi, что и для ранее вычисленной гистограммы, даёт статистику для того же набора пикселей.

5.18.4. Перцентили и поиск по CDF

Объект histogram предоставляет метод get_percentile(), который превращает долю в значение пикселя – значение, ниже которого находится запрошенная доля пикселей. h.get_percentile(0.5) – это медиана; h.get_percentile(0.05) и h.get_percentile(0.95) вместе дают устойчивые min/max, которые игнорируют нижние и верхние 5% как выбросы.

Это та форма, которую приложение использует, когда хочет охарактеризовать диапазон значений пикселей, не позволяя горстке случайных ярких или тёмных пикселей исказить результат. Устойчивые min/max из 5-го и 95-го перцентилей также являются естественным входом для прохода растяжения контраста – попиксельного переотображения, которое рассматривается в разделе «Тональные корректировки».

5.18.5. Метод Оцу

Гистограммы отвечают ещё на один вопрос, который стоит выделить отдельно: для изображения, пиксели которого разделяются на «тёмный» и «яркий» кластеры, какова граница между ними? Страница о порогировании уже назвала этот механизм по его результату – единый глобальный порог, который приложение может передать в binary() – но отложила вопрос как. Это метод Оцу, и он живёт на гистограмме.

Интуиция: изображение с чётким передним планом и фоном имеет два кластера в своей гистограмме яркости, с впадиной между ними. Правильное место для порога – дно впадины – значение, при котором два кластера лучше всего разделены. Метод Оцу перебирает каждую возможную границу и выбирает ту, при которой внутрикластерные дисперсии минимальны (что то же самое, что сказать, что межкластерная дисперсия максимальна), и результатом является оптимальное бинарное разделение для распределения конкретного изображения.

Объект histogram предоставляет метод Оцу через get_threshold:

h = img.get_histogram()
t = h.get_threshold()

Возвращаемый объект threshold имеет атрибут value (для оттенков серого) или l_value / a_value / b_value (для цвета), несущие выбранную границу. Передача результата напрямую обратно в binary() даёт самонастраивающийся глобальный порог, граница которого выбирается самим изображением:

img.binary([(t.value, 255)])

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

5.18.6. Вычисление на разностном изображении

Полезная деталь о get_histogram() и get_statistics(): оба принимают ключевое слово difference, которое принимает другое Image и вычисляет гистограмму (или статистику) попиксельной разности между исходным и этим изображением, без выделения отдельного разностного изображения. Это дешёвый способ спросить «насколько изменилась сцена с момента опорного кадра?», не платя за явный вызов difference() для создания изображения, единственная цель которого – быть измеренным. Для непрерывно работающего скрипта обнаружения движения экономия накапливается.