CUDA 및 OpenCV를 사용하여 이미지 차단
1. 개요
이전 두 기사에서는 CUDA의 기본 개념과 배열 및 행렬에 대한 간단한 합계 연산을 소개했습니다.
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)을 계산하기에 충분하지 않기 때문입니다. 따라서 이미지 블록을 계산하기 위해서는 쓰레드 블록을 두 번 수행해야 한다.
우리의 목적은 스레드 블록의 각 스레드에 해당하는 이미지의 픽셀 좌표 인덱스 값을 얻은 다음 인덱스 값에 해당하는 픽셀 값을 나중에 계산하기 위해 해당 공유 메모리에 저장하는 것입니다. 다음 프로세스 포함:
- 이미지에서 스레드 블록의 절대 인덱스, blockindex1 및 blockindex2를 계산합니다. blockindex2는 두 번째 실행에서 첫 번째 스레드 블록의 해당 이미지에 있는 인덱스입니다.
- 스레드 블록, index1 및 index2의 스레드 이미지에서 인덱스를 계산합니다. index2는 첫 번째 스레드의 두 번째 실행에 해당하는 이미지의 인덱스입니다.
- 공유 메모리에 저장된 후속 데이터인 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