Введение
Статья 563 показала преобразование BGR/RGB для интеграции с Qt — в этой статье разберём цветовые пространства шире, особенно HSV (часто более удобное для задач выделения объектов определённого цвета, чем RGB/BGR), и пороговую обработку (thresholding) — фундаментальную операцию преобразования изображения в бинарную (чёрно-белую) маску.
Концепция
RGB/BGR представляет цвет через интенсивность трёх базовых цветовых каналов (красный, зелёный, синий), что интуитивно для отображения, но менее удобно для задач, требующих выделения объектов определённого ЦВЕТА независимо от их яркости/освещённости — HSV (Hue, Saturation, Value — тон, насыщенность, яркость) разделяет эти аспекты, делая канал тона (Hue) относительно устойчивым к изменениям освещённости при выделении объектов конкретного цвета. Пороговая обработка (cv::threshold) преобразует изображение (обычно в оттенках серого) в бинарное — каждый пиксель становится либо полностью белым, либо полностью чёрным, в зависимости от того, превышает ли его исходная яркость заданный порог, что является фундаментальным первым шагом для многих задач сегментации и последующего анализа контуров (статья 566).
Пример кода: Цветовые пространства и пороговая обработка
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat image = cv::imread("colorful_object.jpg");
if (image.empty()) return 1;
cv::Mat hsv;
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV); // преобразование в HSV — для выделения по цвету
// Выделение, например, красных объектов — диапазон значений Hue для красного цвета
cv::Mat redMask;
cv::inRange(hsv, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), redMask);
// ^ только пиксели, чей HSV попадает в этот диапазон, становятся БЕЛЫМИ в redMask, остальные — ЧЁРНЫМИ
cv::imwrite("red_mask.png", redMask);
return 0;
}
// Пороговая обработка — преобразование оттенков серого в бинарное изображение
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat gray = cv::imread("document.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat binary;
cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
// пиксели ярче 127 -> 255 (белый), темнее -> 0 (чёрный)
cv::Mat adaptiveBinary;
cv::adaptiveThreshold(gray, adaptiveBinary, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY, 11, 2);
// АДАПТИВНЫЙ порог — вычисляется ОТДЕЛЬНО для каждой локальной области, а не единый для всего изображения,
// что значительно лучше работает для изображений с НЕРАВНОМЕРНЫМ освещением
cv::imwrite("binary.png", binary);
cv::imwrite("adaptive_binary.png", adaptiveBinary);
return 0;
}
Пояснения к коду
cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV) с последующим cv::inRange() показывает типичный, идиоматичный паттерн выделения объектов определённого цвета — диапазон значений Scalar(0, 100, 100)—Scalar(10, 255, 255) определяет границы для канала Hue (тон, в OpenCV представленный значениями 0-180 для красного-через-весь-спектр-обратно-к-красному), Saturation (насыщенность) и Value (яркость), и эта операция значительно более устойчива к изменениям освещения сцены по сравнению с попыткой выделить тот же цветовой диапазон непосредственно в RGB/BGR пространстве. Сравнение cv::threshold (единый, глобальный порог для всего изображения) и cv::adaptiveThreshold (порог, вычисляемый локально, отдельно для каждой небольшой области изображения) показывает практически важное различие — для изображений с равномерным освещением простой глобальный порог работает достаточно хорошо, но для изображений с неравномерным освещением (например, документ, сфотографированный с тенью в одной части кадра) адаптивный порог даёт значительно лучший, более корректный результат бинаризации именно потому, что учитывает локальные особенности освещения каждой конкретной области отдельно.
Подводные камни
- Использование единого, глобального порога для изображений с существенно неравномерным освещением — простой
cv::thresholdдля таких изображений часто даёт неудовлетворительный результат (часть изображения становится полностью белой или полностью чёрной независимо от реального содержимого из-за локальных различий в освещённости), и для таких случаевcv::adaptiveThreshold(или предварительная коррекция освещения) обычно даёт значительно лучший результат. - Неправильное понимание диапазона значений канала Hue в OpenCV (0-180, а не 0-360, как может ожидаться по аналогии с математическим представлением цветового круга в градусах) — это специфичная для OpenCV деталь представления (вызванная ограничением 8-битного представления каждого канала), и код, написанный с предположением о диапазоне 0-360 (типичном для некоторых других библиотек/инструментов), даст неверные результаты при выделении конкретного диапазона цветов в OpenCV.
- Выбор диапазона
inRangeдля выделения красного цвета без учёта того, что красный находится на ОБОИХ концах диапазона Hue (близко к 0 и близко к 180, поскольку цветовой круг замкнут) — простое выделение единственного диапазона (как0-10в примере) пропустит красные оттенки, находящиеся ближе к другому концу диапазона (170-180), и для полного, корректного выделения красного цвета часто нужны ДВА отдельных диапазонаinRange, объединённых через логическое ИЛИ, либо более сложная обработка «обёртывания» вокруг границы цветового круга. - Применение пороговой обработки к цветному изображению без предварительного преобразования в оттенки серого (или работа лишь с одним из цветовых каналов без должного, осознанного выбора) —
cv::thresholdожидает одноканальное (обычно серое) изображение, и попытка применить его непосредственно к многоканальному цветному изображению без явного, продуманного решения о том, какой канал или комбинация каналов реально должна быть использована для определения порога, может привести либо к ошибке, либо к результату, не соответствующему реальному намерению задачи.