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

Загрузка, отображение и сохранение изображений; конвертация Mat <-> QImage

Введение

Статья 562 разобрала cv::Mat как структуру данных — в этой статье разберём практические операции с реальными файлами изображений (загрузка, сохранение) и, что особенно важно для интеграции с Qt-приложением, конвертацию между cv::Mat и QImage, позволяющую отображать результат обработки OpenCV через обычные Qt-виджеты.

Концепция

cv::imread()/cv::imwrite() загружают/сохраняют изображения из/в файлы различных распространённых форматов (JPEG, PNG, BMP и другие, поддержка конкретного формата зависит от того, с какими дополнительными библиотеками была собрана конкретная установка OpenCV) — важная деталь, требующая внимания при интеграции с Qt: OpenCV по умолчанию хранит цветные изображения в порядке каналов BGR (синий-зелёный-красный), тогда как QImage и большинство других графических библиотек используют более распространённый порядок RGB, что требует явного преобразования порядка каналов при конвертации между этими двумя представлениями.

Пример кода: Загрузка, отображение и

#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
    cv::Mat image = cv::imread("photo.jpg"); // загрузка изображения из файла

    if (image.empty()) { // ОБЯЗАТЕЛЬНАЯ проверка — imread НЕ выбрасывает исключение при неудаче
        std::cerr << "Не удалось загрузить изображение" << std::endl;
        return 1;
    }

    std::cout << "Размер: " << image.cols << "x" << image.rows << std::endl;
    std::cout << "Каналов: " << image.channels() << std::endl; // обычно 3 для цветного изображения (BGR)

    cv::imwrite("output.png", image); // сохранение в другом формате — конвертация автоматическая

    return 0;
}
// Конвертация cv::Mat в QImage — с учётом разницы BGR (OpenCV) против RGB (Qt)
#include <opencv2/opencv.hpp>
#include <QImage>

QImage matToQImage(const cv::Mat &mat)
{
    cv::Mat rgbMat;
    cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB); // ОБЯЗАТЕЛЬНОЕ преобразование порядка каналов!

    return QImage(rgbMat.data, rgbMat.cols, rgbMat.rows,
                   static_cast<int>(rgbMat.step), QImage::Format_RGB888).copy();
    // .copy() — ВАЖНО, иначе QImage будет указывать на данные rgbMat,
    // которые перестанут существовать после выхода rgbMat из области видимости (статья 525 цикла синтаксиса)!
}
// Обратная конвертация — QImage в cv::Mat
cv::Mat qImageToMat(const QImage &image)
{
    QImage converted = image.convertToFormat(QImage::Format_RGB888);
    cv::Mat mat(converted.height(), converted.width(), CV_8UC3,
                const_cast<uchar *>(converted.bits()), converted.bytesPerLine());

    cv::Mat bgrMat;
    cv::cvtColor(mat, bgrMat, cv::COLOR_RGB2BGR); // обратное преобразование порядка каналов для OpenCV
    return bgrMat.clone(); // статья 562 — глубокое копирование, поскольку mat указывал на временные данные converted
}

Пояснения к коду

cv::imread() с явной проверкой image.empty() показывает обязательный паттерн обработки ошибок — в отличие от исключений (статья 540 цикла синтаксиса C/C++), imread при неудачной загрузке (несуществующий файл, неподдерживаемый или повреждённый формат) просто возвращает пустой Mat, и явная проверка перед дальнейшим использованием результата необходима, поскольку попытка использовать пустой Mat как полноценное изображение привела бы к ошибкам или неопределённому поведению в последующем коде. matToQImage() показывает критичную деталь интеграции — cv::cvtColor(mat, rgbMat, cv::COLOR_BGR2RGB) обязателен перед передачей данных в QImage, иначе итоговое изображение отображалось бы с переставленными местами красным и синим каналами (классическая, легко узнаваемая по характерному «синеватому» виду ошибка для тех, кто впервые интегрирует OpenCV с Qt и забывает об этом преобразовании), и явный .copy() в конце функции необходим, поскольку конструктор QImage, принимающий «сырой» указатель на данные, по умолчанию НЕ копирует эти данные, что привело бы к использованию уже уничтоженных данных rgbMat после выхода из функции (аналогично подводным камням статьи 525 цикла синтаксиса C/C++ про время жизни стековых объектов).

Подводные камни

  • Забытое преобразование BGR/RGB при конвертации между cv::Mat и QImage — это одна из самых частых, легко узнаваемых ошибок интеграции OpenCV и Qt, проявляющаяся как явно неправильные цвета на отображаемом изображении (красный и синий канал визуально «переставлены»), и явный, обязательный вызов cv::cvtColor с соответствующим кодом преобразования (COLOR_BGR2RGB/COLOR_RGB2BGR) необходим в обоих направлениях конвертации.
  • Забытый .copy() при создании QImage из «сырых» данных cv::Mat, как явно отмечено в пояснении — без этого явного копирования QImage хранит лишь указатель на чужие данные, и если исходный cv::Mat (или, как в примере, временный rgbMat) уничтожается раньше, чем QImage, последующее использование этого QImage обращается к уже освобождённой или переиспользованной памяти, что является неопределённым поведением (статья 555 цикла синтаксиса C/C++), часто проявляющимся как искажённое, «сломанное» изображение или крах приложения.
  • Игнорирование того, что imwrite() определяет формат сохраняемого файла по расширению, указанному в пути, а не по какому-либо отдельному, явному параметру формата — попытка сохранить файл с неверным или неожидаемым расширением (или вовсе без расширения) может привести либо к ошибке, либо к сохранению в формате, не соответствующем ожиданиям разработчика, и явное, корректное расширение в указанном пути сохранения — единственный способ OpenCV определить желаемый формат файла для этой операции.
  • Несогласованность шага строки (stride/step) между cv::Mat и QImage при ручной, низкоуровневой конвертации без использования промежуточных, безопасных методов — cv::Mat::step и QImage::bytesPerLine() оба представляют количество байт на одну строку изображения, но эти значения не обязательно равны простому произведению ширины на количество байт на пиксель (из-за возможного выравнивания строк для эффективности), и явное использование правильного значения шага (как показано в примерах конвертации) критично важно для корректной интерпретации данных изображения, особенно для изображений с нестандартной шириной, не кратной типичным границам выравнивания.