一个显示 OpenCV Mat 图像的自定义 Qt 控件

今天学习 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 上点右键,选择 “提升为...”,在弹出的对话框中按照下图输入。

输入完提升的类名称,点击添加,提升即可。

posted @ 2013-11-17 23:14  何磊  阅读(3426)  评论(0编辑  收藏  举报