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 13
| #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 文件,首先通过 __constant__ 常量内存定义了RGB图像的均值和方差,常量内存在核函数执行期间不会发生变化,且数据在所有线程共享。然后在 __global__ void kenlStandardizationRGB 核函数中,根据均值和标准差对每个像素进行标准化处理。由于是RGB图像,所以每个线程独立负责某一坐标下RGB三个像素值的计算。
void standardizationRGB 函数定义了线程块的大小为 BLOCK_SIZE x BLOCK_SIZE = 1024,cv::cuda::device::divUp 用于向上取整的除法运算,所以开辟的总线程数量不小于图像的宽乘高。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 42 43 44 45 46 47 48
| #include "cu_standardization.hpp"
#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中的GpuMat。然后定义一个和输入图像一样大小的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的开发之路。
本文仅供学习参考,未经允许,拒绝转载或者用作他用。