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