QT创建和使用插件——以OpenCV中值滤波为例

参考书目:《OpenCV 3和Qt5计算机视觉应用开发》—— [伊朗]阿敏·艾哈迈迪·泰兹坎迪

首先,需要定义一组接口,以实现应用程序与插件之间的对话。

在C++中,与接口等价的是具有纯虚函数的类。接口基本上是一个啥也不做的类,但在这里它列出了应用程序所需要的所有插件的草图。

打开QT Creator ,新建C++项目,选择“C++ Header File"。在新建出来的H文件里添加需要的头文件,新建一个类,如下所示:

类的第一个成员(带 ~ 符号的)为虚析构函数,用于避免内存泄漏;

类的第二个成员:description( ),用于返回插件的描述信息,介绍说明等;

类的第三个成员用于实现需要的功能,如图像处理、过滤等操作。

在此之后,使用Q_DECLARE_INTERFACE宏将类定义为接口。如果不包含这个宏,Qt将无法把这个类作为插件接口来使用。CVPLUGININTERFACE_IID应该是与包名格式类似的唯一字符串,但是可以根据自己的喜好来更改。

保存H文件后,进行下一步,创建一个使用该接口的插件。

 

使用上一步的接口创建一个插件

现在将使用上一步创建的名为 “CvPluginInterface” 接口类,创建名为 “median_filter_plugin” 的插件。

新建一个Library工程,选择 “C++LIbrary" 。一定要将类型选为“Shared Library”,然后输入“median_filter_plugin”作为名称并单击“Next”。选择工具箱类型作为桌面,并单击“forward”。在“Select Required Modules”页面中,确保只选中了“QtCore”,并继续单击“Next”(最后单击“Finish”),不需要更改任何选项,直到最终进入Qt Creator的代码编辑器页面。

这样QT插件工程项目创建完成,项目结构与常规应用程序项目非常相似,这是因为一个插件与一个应用程序实际上没有区别,只是不能自己运行。

接着,将上一步的H文件添加进工程,在PRO文件中当然也要添加与OpenCV相关的链接库:

#-------------------------------------------------
#
# Project created by QtCreator 2019-03-03T10:56:02
#
#-------------------------------------------------

QT       -= gui

TARGET = median_filter_plugin
TEMPLATE = lib
CONFIG += plugin

DEFINES += MEDIAN_FILTER_PLUGIN_LIBRARY

DEFINES += QT_DEPRECATED_WARNINGS


SOURCES += \
        median_filter_plugin.cpp

HEADERS += \
        median_filter_plugin.h \
        median_filter_plugin_global.h \   
    cvplugininterface.h

unix {
    target.path = /usr/lib
    INSTALLS += target
}

INCLUDEPATH += C:\Program_ME\opencv4\opencv\build\include
INCLUDEPATH += C:\Program_ME\opencv4\opencv\build\include\opencv2
Debug: {
LIBS += C:\Program_ME\opencv4\opencv\build\x64\vc15\lib\opencv_world400d.lib
}
Release: {
LIBS += C:\Program_ME\opencv4\opencv\build\x64\vc15\lib\opencv_world400.lib
}

以上是个人的PRO文件添加OpenCV方式,每个人或许有不同的方法,如添加额外的PRI文件等,总之,需要添加进OpenCV和上一步的H文件。场景已经设置好了,可以开始编写第一个“Qt+OpenCV”插件的代码了。

首先打开“median_filter_plugin.h”文件,并做如下修改:

#ifndef MEDIAN_FILTER_PLUGIN_H
#define MEDIAN_FILTER_PLUGIN_H

#include "median_filter_plugin_global.h"
#include "cvplugininterface.h"

class MEDIAN_FILTER_PLUGINSHARED_EXPORT Median_filter_plugin : public QObject, public CvPlugininterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "com.amin.cvplugininterface")
    Q_INTERFACES(CvPlugininterface)

public:
    Median_filter_plugin();
    ~Median_filter_plugin();
    QString description();
    void processImage(const cv::Mat &inputImage, cv::Mat &outputImage);
};

#endif // MEDIAN_FILTER_PLUGIN_H

看看在这个类中究竟添加了什么内容:

1.首先,包含了cvplugininterface.h头文件。

2.然后,确保Median_filter_plugin类继承了QObject和CvPluginInterface。

3.之后,添加了Qt所需的一些宏,以便可以将我们的库识别为插件。

这意味着三行代码:第一个是Q_OBJECT宏(在本章前面学习过的),在默认情况下应该存在于所有Qt类中,以允许Qt特定的功能(比如信号和槽)可用;下一个是Q_PLUGIN_METADATA,需要在一个插件的源代码中刚好出现一次,用于添加关于插件的元数据;最后一个是Q_INTERFACES,用于声明插件中实现的接口。

4.然后,将描述的定义和processImage函数添加到类中。与只有一个声明而没有实现的接口类相反,在这里我们将真正定义插件的功能。

5.最后,可以将所需的修改和具体实现添加到“median_filter_plugin.cpp”文件中。确保在“median_filter_plugin.cpp”文件的底部添加下面这三个函数:

#include "median_filter_plugin.h"


Median_filter_plugin::Median_filter_plugin()
{
}
Median_filter_plugin::~Median_filter_plugin()
{
}
QString Median_filter_plugin::description()
{
    return "This plugin applies median blur filters to image."
            "This plugin's goal is to make us more familiar with the"
            "concept of plugins in general.";
}
void Median_filter_plugin::processImage(const cv::Mat &inputImage, cv::Mat &outputImage)
{
    cv::medianBlur(inputImage, outputImage, 5);
}

 我们只添加了类析构函数的实现、description函数和processImage函数。正如所看到的,description函数返回有关插件的有用信息,在本例中,没有复杂的帮助页面,只有几句话。processImage函数只是将medianBlur应用于图像。

现在,可以在工程项目上单击右键,然后单击“Rebuild”,或者单击主菜单的“Build”项。上述步骤将创建一个插件文件,通常与工程项目在同一级文件夹中,下一步将用到该文件。操作系统不同,插件文件的扩展名也可能不同。例如,Windows系统下应该是.dll,macOS和Linux下应该是.dylib和.so,等等。本文为:median_filter_plugin.dll 。

 

插件加载与使用——显示调用

现在,将使用前一节中创建的插件。首先,创建一个新的Qt控件应用程序(QT Widgets Application)工程项目,并将其命名为“Plugin_User”。工程项目创建后,首先将OpenCV框架添加至*.PRO文件,然后继续创建一个与此类似的用户界面:

1.注意控件名称,因为会与后面程序对应。

2.将cvplugininterface.h文件添加到这个工程项目。

3.现在,可以开始为用户界面编写代码,以及加载、检查和使用插件所需的代码。首先在mainwindow.h文件中,添加所需的头文件:

#include <QMainWindow>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QPluginLoader>
#include <QFileInfoList>
#include "opencv2/opencv.hpp"
#include "cvplugininterface.h"

4. 然后,在“};”之前,将下面函数添加为MainWindow类的私有成员:void getPluginsList(); ,用于打开程序时,扫描并加载插件列表。

5. 现在,切换到mainwindow.cpp文件,在mainwindow.cpp文件的顶部已有的#include代码行之后,添加以下定义:#define FILTERS_SUBFOLDER "/filter_plugins/" ,单纯用于方便写路径。

6. 然后,将以下函数添加到mainwindow.cpp,这基本上是getplugin函数的实现:

void MainWindow::getPluginsList()
{
    QDir filtersDir(qApp->applicationDirPath() + FILTERS_SUBFOLDER);
    QFileInfoList filters = filtersDir.entryInfoList(
                QDir::NoDotAndDotDot |
                QDir::Files, QDir::Name);
    foreach(QFileInfo filter, filters)
    {
        if(QLibrary::isLibrary(filter.absoluteFilePath()))
        {
            QPluginLoader pluginLoder(
                        filter.absoluteFilePath(),
                        this);
            if(dynamic_cast<CvPlugininterface*>(pluginLoder.instance()))
            {
                ui->filtersList->addItem(filter.fileName());
                pluginLoder.unload();//we can unload for now
            }
            else
            {
                QMessageBox::warning(
                            this, tr("Warning"),
                            QString(tr("Make sure%1 is a correct"
                                       "plugin for this application<br>"
                                       "and it's not in use by some other"
                                       "application!")).arg(filter.fileName())
                            );
            }
        }
        else
        {
            QMessageBox::warning(this, tr("Warning"),
                                 QString(tr("Make sure only plugins"
                                            "exist in plugins folder.<br>"
                                            "%1 is not a plugin.")).arg(filter.fileName()));
        }
    }
    if(ui->filtersList->count()<=0)
    {
        QMessageBox::critical(this, tr("No Plugins"),
                              tr("This application cannot work without plugins!"
                                 "<br>Make sure thatfilter_plugins folder exists "
                                 "in the same folder as the application<br>and that "
                                 "there are some filter plugins inside it"));
        this->setEnabled(false);
    }
}

void MainWindow::on_inputImgButton_clicked()
{
    QString fileName =
            QFileDialog::getOpenFileName(
                this,
                tr("Open Input Image"),
                QDir::currentPath(),
                tr("Images") + "(*.jpg *.png *.bmp)");
    if(QFile::exists(fileName))
    {
        ui->inputImgEdit->setText(fileName);
    }
}

下面分析这个函数:

首先,假设在一个名为filter_plugins的子文件夹中存在插件,这个子文件夹与应用程序可执行文件在同一个文件夹中;

接下来,使用QDir类的entryInfoList函数从文件夹中提取QFileInfoList;

之后,通过一个foreach循环遍历文件列表,检查插件文件夹中的每个文件,以确保只接受插件(库)文件;

然后,检查通过前一步骤的每个库文件,确保其与插件接口兼容。不会直接让任何一个库文件作为一个插件被接受;

如果一个库在上一步通过了测试,那么就认为它是正确的插件(与第一步定义的接口兼容),并在窗口中添加到列表控件并其卸载。我们只是在需要的时候重新加载并使用它;

最后,上述步骤中,只要有一个步骤有问题,将通过QMessageBox为用户显示有用的信息。

7. 注意还需要在CPP文件中的MainWindow构造函数里、setupUi之后,调用getPluginsList(); 确保函数能够运行。

8. 还要为按键编写代码,用于打开图像文件、显示插件的描述信息。如下:

void MainWindow::on_filterButton_clicked()
{
    if(ui->filtersList->currentRow() >= 0 && !ui->inputImgEdit->text().isEmpty())
    {
        QPluginLoader pluginLoader(
                    qApp->applicationDirPath() +
                    FILTERS_SUBFOLDER +
                    ui->filtersList->currentItem()->text());
        CvPlugininterface *plugin =
                dynamic_cast<CvPlugininterface*>(
                    pluginLoader.instance());
        if(plugin)
        {
            if(QFile::exists(ui->inputImgEdit->text()))
            {
                using namespace cv;
                Mat inputImage, outputImage;
                inputImage = imread(ui->inputImgEdit->text().toStdString());
                plugin->processImage(inputImage, outputImage);
                imshow(tr("Filtered Image").toStdString(), outputImage);
            }
            else
            {
                QMessageBox::warning(this, tr("Warning"),
                                     QString(tr("Make sure plugin %1" "exists."))
                                     .arg(ui->filtersList->currentItem()->text()));
            }
        }
        else
        {
            QMessageBox::warning(this, tr("Warning"),
                                 QString(tr("Make sure plugin %1" "exists and is usable."))
                                 .arg(ui->filtersList->currentItem()->text()));
        }
    }
    else
    {
        QMessageBox::warning(this, tr("Warning"),
                             QString(tr("First select a filter" "plugin from the list.")));
    }
}

void MainWindow::on_helpButton_clicked()
{
    if(ui->filtersList->currentRow() >= 0)
    {
        QPluginLoader pluginLoader(
                    qApp->applicationDirPath() +
                    FILTERS_SUBFOLDER +
                    ui->filtersList->currentItem()->text());
        CvPlugininterface *plugin =
                dynamic_cast<CvPlugininterface*>(
                    pluginLoader.instance());
        if(plugin)
        {
            QMessageBox::information(this, tr("Plugin Description"),
                                     plugin->description());
        }
        else
        {
            QMessageBox::warning(this, tr("Warning"),
                                 QString(tr("Make sure plugin %1" "exists and is usable."))
                                 .arg(ui->filtersList->currentItem()->text()));
        }
    }
    else
    {
        QMessageBox::warning(this, tr("Warning"),
                             QString(tr("First select a filter" "plugin from the list.")));
    }
}

 通过使用QMessageBox或其他类型的信息提供功能,始终让用户了解正在发生的事情以及提醒可能发生的问题,是非常重要的。如所见,它们通常比正在执行的实际任务需要占用更多的代码,但这对于避免应用程序中的崩溃是至关重要的。默认情况下,Qt不支持异常处理,并且相信开发人员会使用足够的if和else指令来处理所有可能的崩溃场景。

现在,已经准备好运行Plugin_User应用程序。如果现在运行它,将会看到一个错误消息,会警告我们说:“这里没有插件。”为了能够使用Plugin_User应用程序,需要完成以下工作:

1.在Plugin_User工程项目的构建文件夹中(debug或者release文件夹)创建一个名为“filter_plugins”的文件夹。

2.复制我们构建的插件文件(它是median_filter_plugin工程项目的构建文件夹内的库文件),并将其粘贴到第一步的filter_plugins文件夹中。如前所述,插件文件和可执行程序一样,将根据操作系统有一个扩展名。

现在,尝试运行Plugin_User,一切都应该没问题。应该能看到列表中有一个插件,选择它,点击“help”按钮获取有关信息,然后点击“filter”按钮,对一个图像应用插件中的滤波器,如图所示。

另外 

插件的调用采用了显式调用,虽然只需要H文件和DLL文件,但是调用过程较为复杂。还可以隐式调用,就还需要DLL文件夹里的LIB文件,调用文件增多但是调用过程简单,这里不再演示。






 

猜你喜欢

转载自blog.csdn.net/you_big_father/article/details/88116613