CUDA系列二、CUDA图像标准化

CUDA系列二、CUDA图像标准化

一、概述

在上一篇文章CUDA系列一、CUDA简介 中我们对CUDA进行了介绍,了解到CUDA能让开发者利用GPU的强大算力进行大规模的并行计算,在本篇文章中将介绍如何使用cuda进行图像标准化。
图像标准化在视觉领域内有广泛的应用,通过标准化能使图像数据有更稳定的分布,从而提高模型的训练效果和泛化能力,所以在图像相关的模型部署过程中,图像标化通是模型预处理的第一步,本篇通过图像标准化代码详细介绍并行带来的优势。

二、图像标准化原理

图像标准化,是将图像像素值调整到特定分布范围的操作,通常是把图像均值调整为0,标准差调整为1 。对于一幅图像,其标准化公式为:
[
I_{norm}(x,y) = \frac{I(x,y) - \mu}{\sigma}
]

其中,(I(x,y)) 是原始图像在坐标 ((x,y)) 处的像素值,(\mu) 是图像的均值,(\sigma) 是图像的标准差。通俗的讲就是每个像素需要减去均值再除以标准差。

三、代码及解析

3.1 头文件

下面代码是 cu_standardization.hpp头文件 ,使用OpenCV读取和存储图像,所以需要加载OpenCV相关的库,然后声明了一个 standardizationRGB函数用于图像的标准化调用。

1
2
3
4
5
6
7
8
9
10
11
12
	#pragma once
#include <array>
#include <memory>
#include <string>
#include <vector>
#include <opencv2/highgui/highgui.hpp>
#include <chrono>
#include <opencv2/opencv.hpp>
#include <device_launch_parameters.h>
#include "opencv2/core/cuda/common.hpp"
//标准化
void standardizationRGB(const cv::cuda::GpuMat cu_src, cv::cuda::GpuMat cu_dst);

3.2 标准化核心文件

下面代码是 cu_standardization.cu文件,的首先通过 cpp__constant__ 常量内存定义了RGB图像的均值和方差,常量内存在核函数执行期间不会发生变化,且数据在所有线程共享。然后__global__ void kenlStandardizationRGB 核函数中,根据均值和标准差对每个像素进行标准差处理,由于是RGB图像,所以每个个线程独立负责某一坐标下的RGB三个像素值的计算 。void standardizationRGB 函数定义了线程块的大小为BLOCK_SIZExBLOCK_SIZE=1024,cv::cuda::device::divUp用于向上取整的除法运算,所以开辟的总线程数量是≥图像的宽x高的。``cv::cuda::PtrStepSz```是 OpenCV 中用于在 CUDA 设备上处理二维数组的类,它封装了二维数组的行数、列数、数据指针以及每行的步长信息,我们将图像传入到这个类中便于核函数的调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
///////////cu文件 
#include "cu_standardization.hpp"

// DEBUG 不支持过大的BLOCK
#ifdef _DEBUG
#define BLOCK_SIZE 16
#else
#define BLOCK_SIZE 32
#endif
// 定义一个极小值 避免除零
#define EPSILON 1e-8

//定义常量内存均值和方差
__constant__ float3 mean_ = { 0.485f, 0.456f, 0.406f };
__constant__ float3 std_ = { 0.229f, 0.224f, 0.225f };

//计算
__global__ void kenlStandardizationRGB(const cv::cuda::PtrStepSz<uchar3> src, cv::cuda::PtrStepSz<float3> dst
)
{
const int x = blockIdx.x * blockDim.x + threadIdx.x;
const int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x >= src.cols || y >= src.rows)
return;

dst(y, x).x = (src(y, x).x / 255.0 - mean_.x) / (std_.x + EPSILON);
dst(y, x).y = (src(y, x).y / 255.0 - mean_.y) / (std_.y + EPSILON);
dst(y, x).z = (src(y, x).z / 255.0 - mean_.z) / (std_.z + EPSILON);
}


void standardizationRGB(const cv::cuda::GpuMat cu_src, cv::cuda::GpuMat cu_dst)
{
dim3 block(BLOCK_SIZE, BLOCK_SIZE);
dim3 grid(cv::cuda::device::divUp(cu_src.cols, block.x), cv::cuda::device::divUp(cu_src.rows, block.y));
cv::cuda::PtrStepSz<float3> dst_(cu_dst.rows, cu_dst.cols, (float3*)cu_dst.data, cu_dst.step);
cv::cuda::PtrStepSz<uchar3> src_(cu_src.rows, cu_src.cols, (uchar3*)cu_src.data, cu_src.step);
// 标准化
kenlStandardizationRGB << <grid, block >> > (src_, dst_);
cudaSafeCall(cudaDeviceSynchronize());
}

3.3 主函数

下面代码是执行的主函数,main.cpp,这个文件加载cu_standardization.hpp头文件,并使用OpenCV读取一张彩色图像,并使用 cv::cuda::GpuMat 将图像将主机的图像传入到设备的图像,也就是cpu中的Mat到GPU中的Mat,然后定义一个和输入图像一样大小的GPU图像作为输出值,调用standardizationRGB对图像进行标准化,最后将标准化的图像从GPU中拷贝到CPU中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include"cu_standardization.hpp"
int main()
{
std::string path = R"(D:\img.png)";
cv::Mat img_src = cv::imread(path);
cv::cuda::GpuMat gpu_img_src;
gpu_img_src.upload(img_src);

cv::cuda::GpuMat gpu_img_dst(img_src.size(), CV_32FC3);

standardizationRGB(gpu_img_src, gpu_img_dst);

cv::Mat img_dst;
gpu_img_dst.download(img_dst);
}

这就是整个GPU处理图像标准化的全过程,希望这篇文章能带你正式走进CUDA的开发之路。
注。1

本文仅供学习参考,未经允许,拒绝转载或者用作他用。


CUDA系列二、CUDA图像标准化
http://example.com/2025/02/24/CUDA系列二、CUDA实现图像标准化/
作者
sunTFly
发布于
2025年2月24日
许可协议