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

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

  • 相关阅读:
    创建线程的方式三:实现Callable接口
    线程通信的应用 经典例题: 生产者/消费者问题
    剑指offer-26 树的子结构
    leetcode-567 字符串排列
    剑指offer -11题
    leetcode
    移动端开发利器vConsole.js,app内嵌H5开发时调试用
    TCP/IP分为几层?各层的作用是什么?
    如何测试一支笔
    日志分析
  • 原文地址:https://www.cnblogs.com/heleifz/p/qt-opencv-widget.html
Copyright © 2020-2023  润新知