最近公司项目有个关于图像处理加速的模块,一开始使用的是多线程将图片分批次处理,但是每个图片处理的速度为1s,就算使用多线程,结果还是不达预期。所以这边文章旨在探究GPU加速是否可行以及记录步骤。
如何在QT里面使用CUDA加速,目前看到有两种,
第一种是创建VS+QT项目文件,并在该项目中创建cu文件,编写cuda函数,并于调用。
第二种是自己手动创建动态链接库(DLL), 封装好头文件和lib文件,然后将其导入到QT项目中。
第一种方法据说实现更为简单,但是没有实现过,第二种方法,配置和使用难度稍微较大,但是方便后续大项目的修改以及自己学习,基础的配置和调用一个下午应该就可以调试好,但是具体代码还需要自己另外编写调试。
本次项目考虑到后续的算法修改,采用的第二种。这篇博客详细的介绍了如何从0搭建项目,导出dll文件以及如何调用,也借鉴了很多其他大佬博客。
一:如何生成DLL文件以及lib
1.在VS里面创建新项目,选择DLL(动态链接库),之后选择一个地址创建新项目。
2. 之后在头文件中新建imageProcess.h 文件,本人经验最好与项目名字一样。
3. 接着在资源文件中新建kernel.cu文件,文件名字任意,后缀名是CU就行。
4. 然后开始配置环节,首先将编译器平台改为debug模式的x64。
5. 右击项目找到生成依赖项,选择生成自定义,选择自己电脑里对用的CUDA版本。
6.打开kernel.cu文件右击,选择属性 并改为为下图。
7. 右击项目属性,找到linker->Input->Additional Dependencies添加cudart.lib并保存。
8.至此,基本的CUDA环境已经配置好了,接下来分享一下简单的调用代码。
头文件imageProcess.h代码
#pragma once
extern "C" __declspec(dllexport) int vectorAdd(int c[], int a[], int b[], int size);
kernel.cu代码
#include "cuda_runtime.h";
#include "device_launch_parameters.h";
#include "imageProcess.h";
//#include <opencv2/opencv.hpp>
#include <cfloat>
#include <stdio.h>
__global__ void addKernel(int* c, const int* a, const int* b)
{
int i = threadIdx.x;
c[i] = a[i] + b[i];
}
int main()
{
const int arraySize = 5;
int a[arraySize] = { 1, 2, 3, 4, 5 };
int b[arraySize] = { 10, 20, 30, 40, 50 };
int c[arraySize] = { 0 };
// Add vectors in parallel.
int result = vectorAdd(c, a, b, arraySize);
printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",
c[0], c[1], c[2], c[3], c[4]);
return 0;
}
int vectorAdd(int c[], int a[], int b[], int size)
{
int result = -1;
int* dev_a = 0;
int* dev_b = 0;
int* dev_c = 0;
cudaError_t cudaStatus;
// 选择用于运行的GPU
cudaStatus = cudaSetDevice(0);
if (cudaStatus != cudaSuccess) {
result = 1;
goto Error;
}
// 在GPU中为变量dev_a、dev_b、dev_c分配内存空间.
cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));
if (cudaStatus != cudaSuccess) {
result = 2;
goto Error;
}
cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));
if (cudaStatus != cudaSuccess) {
result = 3;
goto Error;
}
cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));
if (cudaStatus != cudaSuccess) {
result = 4;
goto Error;
}
// 从主机内存复制数据到GPU内存中.
cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
if (cudaStatus != cudaSuccess) {
result = 5;
goto Error;
}
cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
if (cudaStatus != cudaSuccess) {
result = 6;
goto Error;
}
// 启动GPU内核函数
addKernel << <1, size >> > (dev_c, dev_a, dev_b);
// 采用cudaDeviceSynchronize等待GPU内核函数执行完成并且返回遇到的任何错误信息
cudaStatus = cudaDeviceSynchronize();
if (cudaStatus != cudaSuccess) {
result = 7;
goto Error;
}
// 从GPU内存中复制数据到主机内存中
cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);
if (cudaStatus != cudaSuccess) {
result = 8;
goto Error;
}
result = 0;
// 重置CUDA设备,在退出之前必须调用cudaDeviceReset
cudaStatus = cudaDeviceReset();
if (cudaStatus != cudaSuccess) {
return 9;
}
Error:
//释放设备中变量所占内存
cudaFree(dev_c);
cudaFree(dev_a);
cudaFree(dev_b);
return result;
}
9. 最后右击生成
10. 定位到项目文件夹下,找到头文件,dll和.lib文件。
找到imageProcess的头文件(下图第四个)
二:在QT Creator如何调用动态链接库。
1.假设已经创建好QT项目,并且之前的能够运行。右击项目点击添加库,在此之前,最好可以把之前找到的三个文件给提取出来。选择外部库。定位到lib库所在的位置,然后点击下一步即可,pro文件会自动加载配置。
2. 将之前的头文件拷贝到当前项目的头文件目录下,检查pro文件是否配置好。最后一步调试,引入对应的头文件。调用方法。
结果如下:
总结:
至此,QT调用CUDA文件的简单步骤已经完成,具体需求代码可以自己填写。
下一章等到完成使用OpenCV完成图像处理后在进行更新。