그 해의 CUDA 프로그래밍에 관한 것들 (3)

1. 개요

이전 두 기사에서는 CUDA의 기본 개념과 배열 및 행렬에 대한 간단한 합계 연산을 소개했습니다.

그 해의 CUDA 프로그래밍에 관한 것들 (1)
그 해의 CUDA 프로그래밍에 관한 것들 (2)

CUDA의 가장 널리 사용되는 장소 중 하나는 특징 추출, 스테레오 매칭, 3차원 재구성, 딥 러닝 훈련 및 탐지 등과 같은 2차원 이미지 처리입니다. 따라서 다음은 CUDA가 스레드 블록과 스레드의 조합을 통해 이미지 처리 목적을 달성하는 방법을 보여주는 간단한 예입니다.

목적: 8000*1000 단일 채널 이미지를 40x40(조정 가능) 블록으로 나누고 각 블록의 픽셀 값의 합, 최대, 최소 및 평균을 계산하고 계산 결과를 CPU 측에 저장합니다.

위의 설명과 다음 절차는 이 블로그를 참조하십시오.

2. 구현 단계

각 스레드 블록의 최대 스레드 수는 1024이므로 두 계산(각 계산은 40*20)을 완료하고 공유 메모리를 사용하여 각 블록에서 들어오는 이미지의 픽셀 데이터를 저장하고 마지막으로 반환을 사용하는 것으로 간주됩니다. 감소 알고리즘은 추가를 최적화합니다.

2.1 OpenCV를 사용하여 8000*1000 단일 채널 이미지 입력

무엇? 8000*1000 단일 채널 이미지가 없습니까? 함수를 직접 사용하십시오 resize.

	Mat image = imread("2.jpg", 0);   //读取待检测图片,0表示以灰度图读入
	cv::resize(image, image, cv::Size(1000, 8000));

2.2 CUDA 배열을 위한 메모리 할당

처리할 이미지 데이터, 처리 후 각 블록의 최대 및 최소 픽셀 값, 블록 내 픽셀 값의 합을 포함합니다. 구체적인 구현은 다음과 같습니다.

	//图像的所有字节数
	size_t memSize = image.cols*image.rows * sizeof(uchar);
	int size = 5000 * sizeof(int);

	//分配内存:GPU图像数据、求和结果数组、最大值结果数组、最小值结果数组
	uchar *d_src = NULL;
	int *d_sum = NULL;
	int *d_max = NULL;
	int *d_min = NULL;
	cudaMalloc((void**)&d_src, memSize);
	cudaMalloc((void**)&d_sum, size);
	cudaMalloc((void**)&d_max, size);
	cudaMalloc((void**)&d_min, size);

	//图像数据拷贝到GPU
	cudaMemcpy(d_src, image.data, memSize, cudaMemcpyHostToDevice);

2.3 스레드 및 스레드 할당, 커널 기능 실행

이 부분은 먼저 커널 함수의 일반적인 실행 과정을 제시하고 다음 섹션에서 커널 함수의 구체적인 구현 과정을 자세히 소개하는데, 이 역시 이 글의 핵심 부분이다.

	//图像宽、高
	int imgWidth = image.cols;
	int imgHeight = image.rows;

	//dim3 threadsPerBlock(20, 40); //每个block大小为20*40,对应matSum2核函数
	dim3 threadsPerBlock(40, 20); //每个block大小为40*20,对应matSum核函数

	dim3 blockPerGrid(25, 200); //将8000*1000的图片分为25*200个小图像块

	double time0 = static_cast<double>(getTickCount()); //计时器开始
	matSum << <blockPerGrid, threadsPerBlock, 3200 * sizeof(int) >> > (d_src, d_sum, d_max, d_min, imgHeight, imgWidth);

	//等待所有线程执行完毕
	cudaDeviceSynchronize();

	time0 = ((double)getTickCount() - time0) / getTickFrequency(); //计时器结束
	cout << "The Run Time is :" << time0 << "s" << endl; //输出运行时间

위의 프로그램에는 두 가지 스레드 할당 방법이 있으며 둘 다 해당 기능을 수행할 수 있지만 스레드 실행 방법은 약간 다릅니다. 먼저 여기에 넣고 나중에 자세히 논의하십시오.

또한 이 매개변수는 위의 프로그램에도 포함됩니다 3200 * sizeof(int). 이 매개변수의 구체적인 의미는 커널 기능을 실행하는 동안 각 스레드 블록 간의 공유 메모리의 바이트 크기를 나타냅니다. 共享内存:简而言之就是线程块内部各个线程都可以共同使用的内存。이 글은 이것만 알면 충분합니다.

공유 메모리의 선언 방법은 정적 및 동적입니다. 다음과 같이 정적으로 선언됩니다.

//声明一个二维浮点数共享内存数组
__shared__ float a[size_x][size_y];

여기서 size_x와 size_y는 C++ 배열 선언과 동일하며, 컴파일 타임에 결정된 숫자라면 변수가 될 수 없다.

동적 선언은 다음과 같습니다.

extern __shared__ int tile[];

동적 선언은 1차원 배열만 지원합니다. 공유 메모리에 대한 자세한 내용은 이 블로그를 참조하십시오 .

2.4 결과 출력 및 프로그램 종료

주로 CUDA의 결과를 CPU로 출력한 다음 메모리를 해제하고 CUDA를 재설정하고 프로그램을 종료합니다.

	//将数据拷贝到CPU
	cudaMemcpy(sum, d_sum, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(max, d_max, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(min, d_min, size, cudaMemcpyDeviceToHost);

	//输出
	cout << "The sum is :" << sum[0] << endl;
	cout << "The max is :" << max[0] << endl;
	cout << "The min is :" << min[0] << endl;

	//释放内存
	cudaFree(d_src);
	cudaFree(d_sum);
	cudaFree(d_max);
	cudaFree(d_min);

	//重置设备
	cudaDeviceReset();

3. 커널 기능의 구체적인 구현 과정

이 섹션은 알고리즘 실행의 가장 중요한 부분이며 CUDA 가속의 핵심입니다. 커널 기능의 실행 프로세스는 다음과 같습니다. 커널 기능이 시작되면 모든 스레드 블록이 동기식으로 실행되고(반드시 완전히 동기식일 필요는 없음) 각 스레드 블록은 서로 영향을 주지 않고 독립적으로 실행되지만 스레드 블록 내부의 각 스레드는 데이터 상호 작용을 수행할 수 있습니다. .

3.1 공유 메모리 정의

스레드 블록의 각 스레드의 데이터 상호 작용은 공유 메모리를 통해 실현되어 최대값, 최소값 및 합계 연산을 실현합니다.

	//定义线程块中各个线程的共享数组:40*40=1600
	const int number = 1600;
	extern __shared__ int _sum[];  //动态共享数组, 用于求和
	__shared__ int _max[number];   //静态共享数组, 用于最大值的求取
	__shared__ int _min[number];   //静态共享数组, 用于最小值的求取

3.2 이미지의 각 스레드에 해당하는 인덱스 계산

여기에는 다음 세 가지 측면이 포함됩니다.
1. 이미지에서 스레드 블록의 절대 인덱스 값을 계산합니다.
2. 이미지의 스레드 블록에 있는 스레드의 절대 인덱스 값입니다.
3. 스레드 블록에 있는 스레드의 인덱스 값입니다.

	//根据线程块和线程的索引, 依次对应到图像数组中
	//1、线程块在图像中的索引值
	int blockindex1 = blockIdx.x*blockDim.x + 2*blockIdx.y*blockDim.y*imgWidth;
	int blockindex2 = blockIdx.x*blockDim.x + (2*blockIdx.y*blockDim.y + blockDim.y)*imgWidth;

	//2、线程块中的线程在图像中的索引值
	int index1 = threadIdx.x + threadIdx.y*imgWidth + blockindex1;
	int index2 = threadIdx.x + threadIdx.y*imgWidth + blockindex2;

	//3、线程在线程块中的索引值
	int thread = threadIdx.y*blockDim.x + threadIdx.x;

위의 계산 과정을 명확하게 보여주기 위해 다음 설명 다이어그램이 그려집니다.
여기에 이미지 설명 삽입
위 다이어그램에 대한 간단한 설명: 할당된 스레드 블록(40, 20)이 이미지 블록(40, 40)을 계산하기에 충분하지 않기 때문입니다. 따라서 이미지 블록을 계산하기 위해서는 쓰레드 블록을 두 번 수행해야 한다.

우리의 목적은 스레드 블록의 각 스레드에 해당하는 이미지의 픽셀 좌표 인덱스 값을 얻은 다음 인덱스 값에 해당하는 픽셀 값을 나중에 계산하기 위해 해당 공유 메모리에 저장하는 것입니다. 다음 프로세스 포함:

  1. 이미지에서 스레드 블록의 절대 인덱스, blockindex1 및 blockindex2를 계산합니다. blockindex2는 두 번째 실행에서 첫 번째 스레드 블록의 해당 이미지에 있는 인덱스입니다.
  2. 스레드 블록, index1 및 index2의 스레드 이미지에서 인덱스를 계산합니다. index2는 첫 번째 스레드의 두 번째 실행에 해당하는 이미지의 인덱스입니다.
  3. 공유 메모리에 저장된 후속 데이터인 thread1과 thread2의 인덱스 값으로 스레드 블록에 있는 스레드의 인덱스를 계산합니다. thread2는 첫 번째 스레드가 두 번째로 실행될 때 해당 스레드 블록의 인덱스입니다.

3.3 이미지 블록의 각 픽셀 값 저장

계산할 40*40 작은 이미지 블록의 모든 픽셀 값을 공유 배열로 두 번 전송

	//4、将待计算的40*40小图像块中的所有像素值分两次传送到共享数组中
	//将上半部分的40*20中所有数据赋值到共享数组中
	//将下半部分的40*20中所有数据赋值到共享数组中
	_sum[thread] = dataIn[index1];
	_sum[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_max[thread] = dataIn[index1];
	_max[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_min[thread] = dataIn[index1];
	_min[thread + blockDim.x*blockDim.y] = dataIn[index2];

3.4 축소 알고리즘을 사용하여 최종 결과 계산

축소 알고리즘을 사용하여 40*40 작은 이미지 블록에서 1600 픽셀 값의 합, 최대 값 및 최소 값을 찾습니다.

	//利用归约算法求出40*40小图像块中1600个像素值中的和、最大值以及最小值
	//使用归约算法后,最大值,最小值以及各像素之和,均保存在线程块中第一个线程对应的索引中。
	for (size_t s=number/2; s>0; s>>=1 )
	{
    
    
		_sum[thread] = _sum[thread] + _sum[thread + s];
		if (_max[thread] < _max[thread + s]) _max[thread] = _max[thread + s];
		if (_min[thread] > _min[thread + s]) _min[thread] = _min[thread + s];

		__syncthreads(); //所有线程同步
	}

	//将每个线程块的第一个数据结果保存
	if (thread == 0)
	{
    
    
		dataOutSum[blockIdx.x + blockIdx.y*gridDim.x] = _sum[0];
		dataOutMax[blockIdx.x + blockIdx.y*gridDim.x] = _max[0];
		dataOutMin[blockIdx.x + blockIdx.y*gridDim.x] = _min[0];
	}

3.5 다른 스레드 모드

계산 방법은 위와 유사하며 더 이상 설명하지 않습니다. 다음 섹션에서 전체 코드를 참조하십시오.

4. 완전한 엔지니어링 코드

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cuda.h>
#include <device_functions.h>
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

//threadsPerBlock(40, 20)
__global__ void matSum(uchar* dataIn, int *dataOutSum, int *dataOutMax, int *dataOutMin, int imgHeight, int imgWidth)
{
    
    
	//定义线程块中各个线程的共享数组:40*40=1600
	const int number = 1600;
	extern __shared__ int _sum[];  //动态共享数组, 用于求和
	__shared__ int _max[number];   //静态共享数组, 用于最大值的求取
	__shared__ int _min[number];   //静态共享数组, 用于最小值的求取

	//根据线程块和线程的索引, 依次对应到图像数组中
	//1、线程块在图像中的索引值
	int blockindex1 = blockIdx.x*blockDim.x + 2*blockIdx.y*blockDim.y*imgWidth;
	int blockindex2 = blockIdx.x*blockDim.x + (2*blockIdx.y*blockDim.y + blockDim.y)*imgWidth;

	//2、线程块中的线程在图像中的索引值
	int index1 = threadIdx.x + threadIdx.y*imgWidth + blockindex1;
	int index2 = threadIdx.x + threadIdx.y*imgWidth + blockindex2;

	//3、线程在线程块中的索引值
	int thread = threadIdx.y*blockDim.x + threadIdx.x;

	//4、将待计算的40*40小图像块中的所有像素值分两次传送到共享数组中
	//将上半部分的40*20中所有数据赋值到共享数组中
	//将下半部分的40*20中所有数据赋值到共享数组中
	_sum[thread] = dataIn[index1];
	_sum[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_max[thread] = dataIn[index1];
	_max[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_min[thread] = dataIn[index1];
	_min[thread + blockDim.x*blockDim.y] = dataIn[index2];

	//利用归约算法求出40*40小图像块中1600个像素值中的和、最大值以及最小值
	//使用归约算法后,最大值,最小值以及各像素之和,均保存在线程块中第一个线程对应的索引中。
	for (size_t s=number/2; s>0; s>>=1 )
	{
    
    
		_sum[thread] = _sum[thread] + _sum[thread + s];
		if (_max[thread] < _max[thread + s]) _max[thread] = _max[thread + s];
		if (_min[thread] > _min[thread + s]) _min[thread] = _min[thread + s];

		__syncthreads(); //所有线程同步
	}

	//将每个线程块的第一个数据结果保存
	if (thread == 0)
	{
    
    
		dataOutSum[blockIdx.x + blockIdx.y*gridDim.x] = _sum[0];
		dataOutMax[blockIdx.x + blockIdx.y*gridDim.x] = _max[0];
		dataOutMin[blockIdx.x + blockIdx.y*gridDim.x] = _min[0];
	}
}

//threadsPerBlock(20, 40)
__global__ void matSum2(uchar* dataIn, int *dataOutSum, int *dataOutMax, int *dataOutMin, int imgHeight, int imgWidth)
{
    
    
	//定义线程块中各个线程的共享数组:40*40=1600
	const int number = 1600;
	extern __shared__ int _sum[];  //动态共享数组, 用于求和
	__shared__ int _max[number];   //静态共享数组, 用于最大值的求取
	__shared__ int _min[number];   //静态共享数组, 用于最小值的求取

	int blockIndex1 = blockIdx.y*blockDim.y*imgWidth + 2 * blockIdx.x*blockDim.x;
	int blockIndex2 = blockIdx.y*blockDim.y*imgWidth + (2 * blockIdx.x + 1)*blockDim.x;

	int threadIndex1 = blockIndex1 + threadIdx.y*imgWidth + threadIdx.x;
	int threadIndex2 = blockIndex2 + threadIdx.y*imgWidth + threadIdx.x;

	int thread = threadIdx.x + threadIdx.y*blockDim.x;

	_sum[thread] = dataIn[threadIndex1];
	_sum[thread + blockDim.x*blockDim.y] = dataIn[threadIndex2];

	_max[thread] = dataIn[threadIndex1];
	_max[thread + blockDim.x*blockDim.y] = dataIn[threadIndex2];

	_min[thread] = dataIn[threadIndex1];
	_min[thread + blockDim.x*blockDim.y] = dataIn[threadIndex2];

	for (size_t i = number / 2; i > 0; i >>= 1)
	{
    
    
		_sum[thread] = _sum[thread] + _sum[thread + i];
		if (_min[thread] > _min[thread + i]) _min[thread] = _min[thread + i];
		if (_max[thread] < _max[thread + i]) _max[thread] = _max[thread + i];

		__syncthreads(); //所有线程同步
	}

	if (thread == 0)
	{
    
    
		dataOutSum[blockIdx.x + blockIdx.y*gridDim.x] = _sum[0];
		dataOutMax[blockIdx.x + blockIdx.y*gridDim.x] = _max[0];
		dataOutMin[blockIdx.x + blockIdx.y*gridDim.x] = _min[0];
	}
}

int main()
{
    
    
	Mat image = imread("2.jpg", 0); //读取待检测图片
	cv::resize(image, image, cv::Size(1000, 8000));

	int sum[5000];       //求和结果数组
	int max[5000];       //最大值结果数组
	int min[5000];       //最小值结果数组

	//图像的所有字节数
	size_t memSize = image.cols*image.rows * sizeof(uchar);
	int size = 5000 * sizeof(int);

	//分配内存:GPU图像数据、求和结果数组、最大值结果数组、最小值结果数组
	uchar *d_src = NULL;
	int *d_sum = NULL;
	int *d_max = NULL;
	int *d_min = NULL;
	cudaMalloc((void**)&d_src, memSize);
	cudaMalloc((void**)&d_sum, size);
	cudaMalloc((void**)&d_max, size);
	cudaMalloc((void**)&d_min, size);

	//图像数据拷贝到GPU
	cudaMemcpy(d_src, image.data, memSize, cudaMemcpyHostToDevice);

	//图像宽、高
	int imgWidth = image.cols;
	int imgHeight = image.rows;

	//dim3 threadsPerBlock(20, 40); //每个block大小为20*40,对应matSum2核函数
	dim3 threadsPerBlock(40, 20); //每个block大小为40*20,对应matSum核函数

	dim3 blockPerGrid(25, 200); //将8000*1000的图片分为25*200个小图像块

	double time0 = static_cast<double>(getTickCount()); //计时器开始
	matSum << <blockPerGrid, threadsPerBlock, 3200 * sizeof(int) >> > (d_src, d_sum, d_max, d_min, imgHeight, imgWidth);

	//等待所有线程执行完毕
	cudaDeviceSynchronize();

	time0 = ((double)getTickCount() - time0) / getTickFrequency(); //计时器结束
	cout << "The Run Time is :" << time0 << "s" << endl; //输出运行时间

	//将数据拷贝到CPU
	cudaMemcpy(sum, d_sum, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(max, d_max, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(min, d_min, size, cudaMemcpyDeviceToHost);

	//输出
	cout << "The sum is :" << sum[0] << endl;
	cout << "The max is :" << max[0] << endl;
	cout << "The min is :" << min[0] << endl;

	//释放内存
	cudaFree(d_src);
	cudaFree(d_sum);
	cudaFree(d_max);
	cudaFree(d_min);

	//重置设备
	cudaDeviceReset();

	waitKey(0);
	return 0;
}

5. 실험 결과

여기에 이미지 설명 삽입
스레드 블록의 실행 순서가 일정하지 않기 때문에 합계 결과의 첫 번째 데이터는 실행할 때마다 다를 수 있지만 최대값과 최소값의 실행 결과는 매번 일치해야 합니다.

6. 기타

참조 블로그: https://blog.csdn.net/MGotze/article/details/75268746?spm=1001.2014.3001.5501

추천

출처blog.csdn.net/qq_38589460/article/details/120317152