Различные статьи по Qt и c++

Цветовые пространства и пороговая обработка: RGB, HSV, бинаризация

Введение

Статья 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 ожидает одноканальное (обычно серое) изображение, и попытка применить его непосредственно к многоканальному цветному изображению без явного, продуманного решения о том, какой канал или комбинация каналов реально должна быть использована для определения порога, может привести либо к ошибке, либо к результату, не соответствующему реальному намерению задачи.