今天学习 Qt 的时候顺手写了一个,包含一个头文件 qcvdisplay.h 和一个源文件 qcvdisplay.cpp,因为这是 qt 默认的文件命名方式,在 Qt Designer 中提升控件时会省去输入文件名的步骤,所以最好不要改名。
qcvdisplay.h :
#ifndef QCVDISPLAY_H #define QCVDISPLAY_H #include <QWidget> #include <QException> #include <opencv2/core/core.hpp> // 当图片不是灰度图或者 BGR 图像时抛出此异常 class UnsupportedFormatError : public QException { public: void raise() const { throw *this; } UnsupportedFormatError *clone() const { return new UnsupportedFormatError(*this); } }; // 显示 opencv 图片的自定义控件 class QCVDisplay : public QWidget { Q_OBJECT public: explicit QCVDisplay(QWidget *parent = 0); cv::Mat getBuffer(); public slots: void displayImage(const cv::Mat& img); protected: void matToQImage(const cv::Mat3b &src, QImage &dest); void paintEvent(QPaintEvent *event); void resizeEvent(QResizeEvent *event); QImage data; cv::Mat buffer; }; #endif // QCVDISPLAY_H
qcvdisplay.cpp
#include "qcvdisplay.h" #include <QResizeEvent> #include <QPaintEvent> #include <QPainter> #include <opencv2/imgproc/imgproc.hpp> QCVDisplay::QCVDisplay(QWidget *parent) : QWidget(parent) { } cv::Mat QCVDisplay::getBuffer() { return buffer; } // 将 Mat 转换为 QImage,由于 QImage 的每一行有多余对齐字节 // 故采用 RBG32 来消除多余字节 void QCVDisplay::matToQImage(const cv::Mat3b &src, QImage& dest) { for (int y = 0; y < src.rows; ++y) { const cv::Vec3b *srcrow = src[y]; QRgb *destrow = (QRgb*)dest.scanLine(y); for (int x = 0; x < src.cols; ++x) { destrow[x] = qRgba(srcrow[x][2], srcrow[x][1], srcrow[x][0], 255); } } } // 在控件上显示图片,使用 opencv 自带的 resize 使其缩放到和控件大小一致 void QCVDisplay::displayImage(const cv::Mat &img) { QSize sz = data.size(); if (img.channels() == 3) { buffer = img.clone(); } else if (img.channels() == 1) { cv::cvtColor(img, buffer, CV_GRAY2RGB); } else { throw UnsupportedFormatError(); } cv::Mat resized; if (!sz.isEmpty()) { cv::resize(buffer, resized, cv::Size(sz.width(), sz.height())); matToQImage(resized, data); update(); } } // 绘图事件处理函数 void QCVDisplay::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.drawImage(event->rect(), data, event->rect()); } // 缩放事件也采用 opencv 自带的 resize void QCVDisplay::resizeEvent(QResizeEvent *event) { if (data.size() != event->size()) { cv::Mat resized; data = QImage(event->size(), QImage::Format_RGB32); if (!buffer.empty() && !event->size().isEmpty()) { cv::resize(buffer, resized, cv::Size(event->size().width(), event->size().height())); matToQImage(resized, data); } } QWidget::resizeEvent(event); }
实现的思路很直接,重写 paintEvent 和 resizeEvent 两个事件处理函数来进行控件的自定义显示,用一个 Mat 作为原始图片的缓存(buffer),将其调整到与空间大小一致后再显示。QImage 的格式选择 RGB32 (第一个字节为 0xFF),使得图像每一行的像素个数全部都是 4 的倍数,消除多余的对齐像素,避免图片在显示时变形。(OpenCV 2.0 以后的图片已经不存在对齐像素了,即图像数据在内存中是连续的)
使用时将两个文件加入当前项目,在 Qt Designer 中拖入一个 widget 基类,在 widget 上点右键,选择 “提升为...”,在弹出的对话框中按照下图输入。
输入完提升的类名称,点击添加,提升即可。