将Python机器学习模型集成到C++ Qt客户端应用程序中|Qt调用python详解

本文探讨了在C++Qt应用程序中集成Python机器学习模型的多种方法,包括创建API、Python与Qt集成、C++库、嵌入式Python和ONNX转换。作者分享了在QtCreator中使用Python的具体步骤,如设置环境、添加依赖和调用Python函数的详细过程。
摘要由CSDN通过智能技术生成

0、前言

有几个不同的选项可以将你的Python机器学习模型集成到你的C++ Qt客户端应用程序中。以下是一些可能的解决方案:

  1. 创建API: 将你的机器学习模型部署为一个API服务。你可以使用像Flask这样的轻量级Web框架来创建一个简单的HTTP服务。这样,你的Qt应用程序可以通过HTTP请求来与这个服务交互,并获取模型的预测结果。部署完成后,你需要在Qt中使用QNetworkAccessManager或其他HTTP客户端来呼叫远端API。
  2. 使用Python和Qt的集成: 如果你希望避免网络请求,可以考虑在你的应用程序中直接使用Python。这可以通过使用像PyQt5或PyOtherSide这样的库实现。
  3. C++机器学习库: 如果模型不是特别复杂,你可以考虑使用C++机器学习库(例如Shark、dlib或mlpack)重新实现你的模型。这样可以避免使用Python,但可能需要重新训练模型并调整算法。
  4. 使用嵌入式Python解释器: 在你的Qt应用程序中嵌入or指定一个Python解释器。这允许你直接从C++代码中调用Python代码,并使用你的模型。
    	Py_SetPythonHome(L"D:\\Anaconda\\envs\\cp39_torch1_13_1_vision_0_14_cu117");
        Py_Initialize();
    

     通过嵌入式Python部署方法,目标机器(用户的机器)无需单独安装Python。这是因为所有必要的Python组件都应该被包含在你的应用程序中,作为该应用程序的一部分进行分发。这意味着Python解释器和所有必要的库、模块及其他依赖都被静态链接到应用程序或以其他形式捆绑在一起,用户不需要执行额外的安装步骤。

     但是,这种方法也意味着你的Qt应用程序的部署包会更大,因为它可能需要包括Python解释器和额外的Python库。

  5. 将模型转换为ONNX: 如果你的机器学习模型支持导出为ONNX(Open Neural Network Exchange)格式,你可以将其转换为ONNX,然后在C++应用程序中使用支持ONNX的机器学习库(例如ONNX Runtime)来加载和运行模型。
  6. 使用远程服务器处理: 如果客户端应用程序的性能和网络延迟不是问题,你可以在远程服务器上托管你的Python模型,并通过网络请求进行交互。
    每个选项都有其优缺点,你应该根据你的具体需求来决定使用哪一种。比如,第一个选项可能是最简单的开始方式,而第三个和第五个选项可能提供了最好的性能。最后选择哪种方法,取决于你的应用需求、性能考虑以及你对于不同技术的熟悉程度。

最近和之前在做的项目都是在Qt中用c++编码项目的开发,其中的功能涉及到机器学习的算法,之前有尝试过直接调用python,但是一直不成功, 再加上想到直接打包python程序也会更大,遂放弃。前面几篇博客讲到把Yolov8的目标检测和分割模型部署成c++的,是因为当时采用了上述的第五类方法,直接将算法由python转换为c++嵌入到程序中了,但是实际中这样虽然性能最好,但是很麻烦。这次的项目涉及到多种检测算法,且算法需要随着实验进行实时迭代,不可能说将python转换成c++了(这样十分麻烦,涉及到重新编写…配置环境…),于是这次打算再次采用第四种方法,在Qt项目中直接使用c++来调用python。为什么不适用创建API的方法呢?主要还是因为数据涉及到不能通过网络传输。总之,让我们快速进入正题,如何使用Qt调用python把!

一、Qt调用python

如果是vs2019,可以看看前面讲解在vs2019下部署yolov8中的配置依赖环境,都是一样的,这里因为换成你了Qtcreator,那就以qtcreator来讲:

注意,看一下自己的项目环境是否是64位,否则后面会报错:
在这里插入图片描述

1.1:pro文件增加python目录

INCLUDEPATH += -I D:\software\anaconda3\envs\yolov8\include
LIBS += -LD:\software\anaconda3\envs\yolov8\libs -lpython39

在这里插入图片描述

1.2:将Python集成到Qt中

工具->选项->环境->外部工具,添加->添加目录 (双击可任意更改名称这里更改为RunPy)->添加工具(双击可任意更改名称这里更改为Python3)。点击Python3,配置执行档、参数等配置:

  • 执行档: python的安装目录,我这里(D:\software\anaconda3\envs\yolov8\python.exe),你自己找到自己安装的python.exe目录
  • 参数:%{CurrentDocument:FilePath}
  • 工作目录:%{CurrentDocument:Path}
    在这里插入图片描述

1.3:将相关的文件拷贝到项目的可执行目录

  • 所需拷贝文件:python相关的Dll文件、以及对应的你想要调用的py文件

在这里插入图片描述

🌸在Qt中创建一个Python脚本测试一下:

  • 创建python脚本(或者前面已经将Python脚本移动到可执行目录下,这个时候在Other Files中也能看到)
    在这里插入图片描述

这里我新建一个测试脚本test.py:

import matplotlib.pyplot as plt

def temperImg():
    plt.plot([1, 2, 1, 2])
    plt.show()

temperImg()
  • 选中文件->点击 工具->外部->RunPy->Python3,运行脚本
    在这里插入图片描述
    OK可以看到可以成功调用python:
    在这里插入图片描述

1.4:添加环境变量

在这里插入图片描述

1.5:修改include文件夹中的object.h文件

修改include文件夹中的object.h文件,因为Python中slots是关键字,Qt中slots也是关键字,会冲突。
编译报错error: expected unqualified-id before ‘;’ token,这是与qt的slots关键字冲突,解决办法是将python中的slot取消宏定义,然后再恢复,如下图
在这里插入图片描述

#undef slots

PyType_Slot *slots; /* terminated by slot==0. */

#define slots Q_SLOTS

1.6:C++程序调用

具体流程大概包括以下几个步骤:

  1. 设置Python环境:
    使用 Py_SetPythonHome() 指定Python环境路径。这个路径通常指向你希望使用的Python解释器的环境。

  2. 调用 Py_Initialize() 来初始化Python解释器。
    检查Python解释器是否初始化成功:

  3. 使用 Py_IsInitialized() 验证Python解释器是否已经成功初始化,如果没有,则输出相应的错误日志。

  4. 修改Python搜索路径:
    通过 sys.path.append(‘./’) 来修改Python模块搜索路径。这样做通常是为了确保Python能够找到你的脚本所在的目录。

  5. 导入Python模块:
    使用 PyImport_ImportModule(“testTunnel”) 来导入名为testTunnel的Python模块。

  6. 获取模块中函数的指针:
    获得模块中名为judge_image的函数指针,以便能够调用该函数。

  7. 准备函数参数:
    创建一个Python元组(PyTuple_New(1)),用以传递参数给Python函数。
    将C++字符串(在这里是一个文件夹路径)转换为Python字符串,并通过 PyTuple_SetItem() 设置到参数元组中。

  8. 调用Python函数:
    使用 PyEval_CallObject(pTest_1,pPara) 来调用Python函数,并传递参数元组pPara。这将执行Python脚本中的judge_image函数。

  9. 处理Python函数的返回值:
    PyEval_CallObject() 返回的PyObject* pyValue是Python函数的返回值。需要根据实际情况处理这个返回值。
    错误处理:
    如果在任何步骤中遇到错误,使用 PyErr_Print() 打印Python错误信息,并进行适当的错误处理。

  10. 清理资源:
    不要忘记在使用完Python对象后释放资源,比如使用 Py_DECREF() 减少引用计数,以及在最后使用 Py_Finalize() 终止Python解释器。

  // 获取选中项的文本,这里假设是文件夹的完整路径
    QString folderPath = item->text();
    QList<QFileInfo> noTargetList;
    
    
    Py_SetPythonHome(L"D:/software/anaconda3/envs/yolov8");
    Py_Initialize();
    if( !Py_IsInitialized() )
        qDebug()<<"图片加载模块的Python初始化失败";
    
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");//这一步很重要,修改Python路径
    //创建模块指针
    PyObject* pModule = PyImport_ImportModule("testTunnel");
    if (!pModule)
        qDebug()<<"图片加载模块的Python获取模块指针失败";
    else {
        qDebug()<<"successfuly obtained Module!";
    }
    const char* filename = PyModule_GetFilename(pModule);
    if (filename == NULL) {
        PyErr_Print();
        Py_DECREF(pModule);
        Py_Finalize();
    }
    
    //创建函数指针
    PyObject* pTest_1 = PyObject_GetAttrString(pModule, "judge_image");
    if (!pTest_1) {
        // Failed to get function pointer
        PyErr_Print(); // Print Python error indicator if available
        qDebug() << "获取函数指针失败";
    } else {
        qDebug() << "Successfully obtained function pointer";
    }
    
    PyObject* pPara = PyTuple_New(1);
    if (pPara == NULL) {
        // 元组创建失败,进行错误处理
        PyErr_Print();
        // 继续下一个迭代
    }
    
    qDebug() << "fPath:"<<folderPath;
    const char* cstr = folderPath.toUtf8().constData();  // 将 QString 转换为 C 字符串
    
    PyTuple_SetItem(pPara, 0, Py_BuildValue("s",cstr));  //参数1为String型 "Hello"
    
    
    PyObject* pyValue = PyEval_CallObject(pTest_1,pPara);
    PyErr_Print();
    qDebug() << "pyValue"<<pyValue;
    
    ...
      // 在使用完 pModule 后减少其引用计数
    Py_DECREF(pModule);
    // 在使用完列表后,减少其引用计数
    Py_DECREF(pyValue);
    // 在使用完参数元组后,减少其引用计数
    Py_DECREF(pPara);
    
    //    Py_Finalize();

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是瑶瑶子啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值