3.7.cuda运行时API-使用cuda核函数加速warpaffine

news2024/9/27 12:11:51

目录

    • 前言
    • 1. warpAffine
    • 2. warpAffine案例
      • 2.1 导言
      • 2.2 main函数
      • 2.3 warpaffine_to_center_align函数
      • 2.4 warp_affine_bilinear函数
      • 2.5 warp_affine_bilinear_kernel核函数
      • 2.6 AffineMatrix结构体
    • 3. 补充知识
    • 总结

前言

杜老师推出的 tensorRT从零起步高性能部署 课程,之前有看过一遍,但是没有做笔记,很多东西也忘了。这次重新撸一遍,顺便记记笔记。

本次课程学习精简 CUDA 教程-使用 cuda 核函数加速 warpAffine

课程大纲可看下面的思维导图

在这里插入图片描述

1. warpAffine

warpAffine 主要解决图像的缩放和平移,它用来处理目标检测中常见的预处理行为

关于 warpAfiine 你需要知道:

  1. warpAffine 是对图像做平移缩放旋转变换进行综合统一描述的方法
  2. 同时也是一个很容易实现的 cuda 并行加速算法
  3. 在深度学习领域通常需要做预处理,比如 CopyMakeBorder,RGB->BGR,减去均值除以标准差,BGRBGRBGR->BBBGGGRRR
# 正常预处理流程
img = cv2.resize(img)
img = cv2.copyMakeBorder(img)
img = cv2.cvtColor(img, BGR2RGB)
img = (img - mean) / std
img = img.transpose(2, 0, 1)[None]

# 一个warpAffine完成整个预处理
img = warpAffine(img)
  1. 如果使用 cuda 进行并行加速实现,那么可以对整个预处理都进行统一,并且性能贼好
  2. 由于 warpAffine 是标准的矩阵映射坐标,并且可逆,所以逆变换就是其变换矩阵的逆矩阵
  3. 对于缩放和平移的变换矩阵,其有效自由度为 3

在这里插入图片描述

2. warpAffine案例

关于 warpAffine 原理和更多细节请查看 YOLOv5推理详解及预处理高性能实现

2.1 导言

神经网络模型的输入一般是固定大小的,比如 640x640,但是我们都知道图片尺寸有大有小,怎么将图片大小转换成网络模型输入的大小呢?在这里可以通过 warpaffine 来实现,warpaffine 的本质是对源图上的每一个像素点进行坐标变换,将源图上的像素点填充到目标图像上即可,而当目标图像通过坐标变换的逆变换即 M − 1 M^{-1} M1 去源图取值时,可能得出的坐标是非整数,无法进行取值,需要使用插值算法,整个预处理过程如下

在这里插入图片描述

图2-1 神经网络预处理过程

2.2 main函数

首先写一个 main 函数,用于实现总体框架,主要通过读取一张图像,经过预处理之后,保存下来,其代码如下

#include <cuda_runtime.h>
#include <opencv2/opencv.hpp>
#include <stdio.h>

using namespace cv;

#define min(a, b)   ((a) < (b) ? (a) : (b))
#define checkRuntime(op) __check_cuda_runtime((op), #op, __FILE__, __LINE__)

bool __check_cuda_runtime(cudaError_t code, const char* op, const char* file, int line){
    if(code != cudaSuccess){
        const char* err_name = cudaGetErrorName(code);
        const char* err_message = cudaGetErrorString(code);
        printf("runtime error %s:%d %s failed. \n code = %s, message = %s\n", file, line, op, err_name, err_message);
        return false;
    }
    return true;
}

int main(){
    Mat image = imread("cat.png");
    Mat output = warpaffine_to_center_align(image, Size(640, 640));
    imwrite("output.png", output);
    printf("Done");

    return 0;
}

2.3 warpaffine_to_center_align函数

该函数为 host 函数,用于分配内存,调用预处理函数等功能,首先来看该函数要实现的具体功能

    1. 在 CPU 上开辟空间,用于保存预处理完成后的图像
    1. 在 GPU 上开辟空间,将读取的图像数据拷贝到 GPU 上
    1. 调用预处理函数
    1. 将预处理后的图片重新拷贝到 CPU 上进行保存
    1. 释放 CPU 和 GPU 上分配的内存

在这里插入图片描述

图2-2 warpaffine_to_center_align函数功能

了解完 warpaffine_to_center_align 函数需要实现的功能之后,其代码就相对容易实现了,代码如下

void warp_affine_bilinear(// 声明
    uint8_t* src, int src_line_size, int src_width, int src_height,
    uint8_t* dst, int dst_line_size, int dst_width, int dst_height,
    uint8_t fill_value
);

Mat warpaffine_to_center_align(Mat image, const Size& size){
    // 步骤1 在CPU上开辟空间,用于保存预处理完成后的图像
    Mat output(size, CV_8UC3);

    // 步骤2 在GPU上开辟空间,将读取的图像数据拷贝到GPU上
    uint8_t* psrc_device = nullptr;
    uint8_t* pdst_device = nullptr;

    size_t src_size = image.cols * image.rows * 3;
    size_t dst_size = size.width * size.height * 3;

    checkRuntime(cudaMalloc(&psrc_device, src_size));
    checkRuntime(cudaMalloc(&pdst_device, dst_size));

    checkRuntime(cudaMemcpy(psrc_device, image.data, src_size, cudaMemcpyHostToDevice));
    
    // 步骤3 调用预处理函数
    warp_affine_bilinear(// 调用
        psrc_device, image.cols * 3, image.cols, image.rows,
        pdst_device, size.width * 3, size.width, size.height,
        114
    );

    // 步骤4 将预处理后的图片重新拷贝到CPU上进行保存
    checkRuntime(cudaPeekAtLastError());    // 检查核函数执行是否存在错误
    checkRuntime(cudaMemcpy(output.data, pdst_device, dst_size, cudaMemcpyDeviceToHost));

    // 步骤5 释放CPU和GPU上分配的内存
    checkRuntime(cudaFree(pdst_device));
    checkRuntime(cudaFree(psrc_device));

    return output;
}

2.4 warp_affine_bilinear函数

该函数为 host 函数,用于调用核函数,主要有以下几点说明

    1. 首先确定 grid_size 和 block_size 的大小。我们启动的线程数为图像宽*图像高,每个线程处理一个像素(3通道),又由于之前提到过核函数启动时 gird 只是逻辑层,而 SM 才是执行的物理层,且 SM 的基本执行单元是包含 32 个线程的线程束,所以 block 一般设置为 32 的倍数,在这里 block_size 设置为 2-dim 的 block(32, 32),grid_size 可通过 block_size 的大小计算得到。
    1. 定义仿射变化矩阵 M \scriptsize M M
    1. 调用 warp_affine_bilinear_kernel 核函数

代码如下

#include <cuda_runtime.h>
#include <iostream>

void warp_affine_bilinear(// 实现
    uint8_t* src, int src_line_size, int src_width, int src_height, // 像素0~255 uint8
    uint8_t* dst, int dst_line_size, int dst_width, int dst_height,
    uint8_t fill_value
){
    // 需要多少threads
    dim3 block_size(32, 32);
    dim3 grid_size((dst_width + 31) / 32, (dst_height + 31) / 32);
    
    AffineMatrix affine;
    affine.compute(Size(src_width, src_height), Size(dst_width, dst_height));

    warp_affine_bilinear_kernel<<<grid_size, block_size, 0, nullptr>>>(
        src, src_line_size, src_width, src_height,
        dst, dst_line_size, dst_width, dst_height,
        fill_value, affine
    );
}

2.5 warp_affine_bilinear_kernel核函数

核函数的功能主要是实现 warpAffine 和双线性插值即预处理,主要有以下几点说明

    1. 一个 thread 负责一个像素(3通道),所以有全局 Idx 的计算
    1. 已知源图像求目标图像的像素,通过仿射变换逆矩阵 M − 1 M^{-1} M1 可得,即代码中的 affine_project()
    1. 之前讨论过,目标图像通过 M − 1 M^{-1} M1 去取源图像时可能存在超出边界问题,此时填充常量值即可
    1. 对于能取到值的像素,其映射回源图坐标可能是非整数,此时需要插值算法(双线性插值),计算距离最近的四个像素点的加权值作为最终的像素值
    1. 已知全局 Idx 索引 (dx,dy),如何确定像素填充的具体位置呢?可看 图2-3,图中是一个 4x4 大小的图片,如果我想要填充 Idx=(1,2) 的像素值,我如何获得其内存地址呢?假设图片起始地址为 src,则有 p s r c = s r c + d y ∗ s r c _ l i n e _ s i z e + d x ∗ 3 psrc = src + dy * src\_line\_size + dx * 3 psrc=src+dysrc_line_size+dx3

在这里插入图片描述

图2-3 由全局索引获取对应内存地址

代码如下

__device__ void affine_project(float* matrix, int x, int y, float* proj_x, float* proj_y){

    // matrix
    // m0, m1, m2
    // m3, m4, m5
    *proj_x = matrix[0] * x + matrix[1] * y + matrix[2];
    *proj_y = matrix[3] * x + matrix[4] * y + matrix[5];
}

__global__ void warp_affine_bilinear_kernel(
    uint8_t* src, int src_line_size, int src_width, int src_height,
    uint8_t* dst, int dst_line_size, int dst_width, int dst_height,
    uint8_t fill_value, AffineMatrix matrix
){
    // 全局idx的计算,一个thread负责一个像素(3通道)
    int dx = blockDim.x * blockIdx.x + threadIdx.x;
    int dy = blockDim.y * blockIdx.y + threadIdx.y;

    if(dx >= dst_width  || dy >= dst_height) return;
    
    float c0 = fill_value, c1 = fill_value, c2 = fill_value;
    float src_x = 0; float src_y = 0;
    affine_project(matrix.d2i, dx, dy, &src_x, &src_y);     // 由目标图像dx,dy以及M^-1计算源图像坐标src_x,src_y

    // 已知src_x,src_y
    if(src_x < -1 || src_x >= src_width || src_y < -1 || src_y >= src_height){
        // out of range
        // src_x < -1时,其高位high_x < 0,超出范围
        // src_x >= -1时,其高位high_x >= 0,存在取值
    }else{
        int y_low = floorf(src_y);
        int x_low = floorf(src_x);
        int y_high = y_low + 1;
        int x_high = x_low + 1;
        
        uint8_t const_values[] = {fill_value, fill_value, fill_value};
        float ly = src_y - y_low;
        float lx = src_x - x_low;
        float hy = 1 - ly;
        float hx = 1 - lx;
        float w1 = hy * hx,  w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;
        uint8_t* v1 = const_values;
        uint8_t* v2 = const_values;
        uint8_t* v3 = const_values;
        uint8_t* v4 = const_values;
        if(y_low >= 0){
            if(x_low >= 0)
                v1 = src + y_low * src_line_size + x_low * 3;
            
            if(x_high < src_width)
                v2 = src + y_low * src_line_size + x_high * 3;
        }

        if(y_high < src_height){
            if(x_low >= 0)
                v3 = src + y_high * src_line_size + x_low * 3;
            
            if(x_high < src_width)
                v4 = src + y_high * src_line_size + x_high * 3;
        }

        c0 = floorf(w1 * v1[0] + w2 * v2[0] + w3 * v3[0] + w4 * v4[0] + 0.5f);
        c1 = floorf(w1 * v1[1] + w2 * v2[1] + w3 * v3[1] + w4 * v4[1] + 0.5f);
        c2 = floorf(w1 * v1[2] + w2 * v2[2] + w3 * v3[2] + w4 * v4[2] + 0.5f);
    }

    uint8_t* pdst = dst + dy * dst_line_size + dx * 3;
    pdst[0] = c0; pdst[1] = c1; pdst[2] = c2;
}

2.6 AffineMatrix结构体

结构体主要是为了计算仿射变换矩阵 M M M 以及逆矩阵 M − 1 M^{-1} M1

代码如下

struct Size{
    int width = 0, height = 0;

    Size() = default;
    Size(int w, int h)
    :width(w), height(h){}
};

struct AffineMatrix{
    float i2d[6];       // image to dst(network), 2x3 matrix ==> M
    float d2i[6];       // dst to image, 2x3 matrix ==> M^-1

    // 求解M的逆矩阵,由于这个3x3矩阵的第三行是确定的0, 0, 1,因此可以简写如下
    void invertAffineTransform(float imat[6], float omat[6]){
        float i00 = imat[0]; float i01 = imat[1]; float i02 = imat[2];
        float i10 = imat[3]; float i11 = imat[4]; float i12 = imat[5];

        // 计算行列式
        float D = i00 * i11 - i01 * i10;
        D = D != 0 ? 1.0 / D : 0;
        
        // 计算剩余的伴随矩阵除以行列式
        float A11 = i11 * D;
        float A22 = i00 * D;
        float A12 = -i01 * D;
        float A21 = -i10 * D;
        float b1 = -A11 * i02 - A12 * i12;
        float b2 = -A21 * i02 - A22 * i12;
        omat[0] = A11; omat[1] = A12; omat[2] = b1;
        omat[3] = A21; omat[4] = A22; omat[5] = b2;
    }

    // 求解M矩阵
    void compute(const Size& from, const Size& to){
        float scale_x = to.width / (float)from.width;
        float scale_y = to.height / (float)from.height;

        float scale = min(scale_x, scale_y);
        /*
        M = [
        scale,    0,     -scale * from.width  * 0.5 + to.width  * 0.5
        0,     scale,    -scale * from.height * 0.5 + to.height * 0.5
        0,        0,                     1
        ]
        */
        /*
            - 0.5 的主要原因是使得中心更加对齐,下采样不明显,但是上采样时就比较明显
            参考:https://www.iteye.com/blog/handspeaker-1545126
        */
        i2d[0] = scale; i2d[1] = 0; i2d[2] = 
            -scale * from.width  * 0.5 + to.width  * 0.5 + scale * 0.5 - 0.5;
        
        i2d[3] = 0; i2d[4] = scale; i2d[5] =
            -scale * from.height * 0.5 + to.height * 0.5 + scale * 0.5 - 0.5;
        
        invertAffineTransform(i2d, d2i);
    }
};

OK!至此 warpAffine 案例代码已完成了简单分析,运行效果如下:

在这里插入图片描述

图2-4 warpAffine案例运行效果

在这里插入图片描述

图2-5 warpAffine运行后图像

3. 补充知识

关于 warpAffine 的知识点:(from 杜老师)

  1. 抖音辅助讲解视频合集
  2. 仿射变换+双线性插值,在 CV 场景下,解决图像预处理是非常合适的
  • 例如 Yolo 的 letterbox,实则是边缘填充
  • 例如 CenterNet 的居中对齐
  1. 该代码仿射变换对象是 CV8UC3 的图像,经过简单修改后,可以做到端到端,把结果输入到 tensorRT 中
  • 可以直接实现 warpAffine + Normalize + SwapRB
  • 参考:https://github.com/shouxieai/tensorRT_Pro/blob/main/src/tensorRT/common/preprocess_kernel.cu
  • 这样的话性能会非常好
  1. 在仿射变换函数里面,我们循环的次数,是 dst.width * dst.height,以 dst 为参照集
  • 因此无论 src 多大,dst 固定的话,计算量也是固定的
  • 另外核函数里面得到的是 dst.x,dst.y,我们需要获取对应在 src 上的坐标
  • 所以需要 src.x,src.y = project(matrix, dst.x, dst.y)
  • 因此这里的 project 是 dst-> src 的变换
  • AffineMatrix 的 compute 函数计算的是 src->dst 的变换矩阵 i2d
  • 所以才需要 invertAffineTransform 得到逆矩阵,即 dst->src,d2i

总结

本次课程学习了使用 cuda 核函数加速 warpAffine,相比上次课难度跨度有点大呀😂。在这次课程可能你需要对目标检测中的预处理有一定了解,同时还需要掌握双线性插值、仿射变换的推导,具体可参考 YOLOv5推理详解及预处理高性能实现

利用 cuda 核函数对 warpAffine 进行加速处理可以实现高性能预处理,同时一些常见的预处理操作比如 BGR->RGB,减均值除标准差都可以一并处理,性能非常好

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/738288.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux后端服务器开发】软硬链接与动静态库

目录 一、软硬链接 二、动静态库 1. 静态库 2. 动态库 一、软硬链接 软链接&#xff1a;ln -s myfile soft_file.link 硬链接&#xff1a;ln myfile hard_file.link 查看映射关系&#xff1a;ll -li 软硬链接区别&#xff1a;是否具有独立的inode 软链接具有独立的inode…

Appium+python自动化(一)- 环境搭建—上(超详解)

最近整理了一下自动化的东西&#xff0c;先前整理的python接口自动化已经接近尾声。即将要开启新的征程和篇章&#xff08;Appium&python&#xff09;。那么首相的问题就是搭建环境了。好久没搭建环境又踩了不少坑&#xff0c;appium的环境搭建比较繁琐&#xff0c;好多同行…

零撸X2E大热门​Salad Venture的空投!

Salad Venture 随着Web3的走红&#xff0c;X to Earn成为热门概念&#xff0c;其中X可指运动、购物、游戏、学习和创作等诸多应用场景&#xff0c;Earn则是通过这些特定场景产生经济收益。与Web2企业将利益分配权牢牢掌握在自己手中不同&#xff0c;X to Earn的本质是将参与者…

Coggle 30 Days of ML(23年7月)任务七:训练TextCNN模型

Coggle 30 Days of ML&#xff08;23年7月&#xff09;任务七&#xff1a;训练TextCNN模型 任务七&#xff1a;使用Word2Vec词向量&#xff0c;搭建TextCNN模型进行训练和预测 说明&#xff1a;在这个任务中&#xff0c;你将使用Word2Vec词向量&#xff0c;搭建TextCNN模型进…

【LeetCode热题100】打卡第32天:最长连续序列只出现一次的数字单词拆分环形链表

文章目录 【LeetCode热题100】打卡第32天&#xff1a;最长连续序列&只出现一次的数字&单词拆分&环形链表⛅前言 最长连续序列&#x1f512;题目&#x1f511;题解 只出现一次的数字&#x1f512;题目&#x1f511;题解 单词拆分&#x1f512;题目&#x1f511;题解…

webAPI学习笔记5——移动端网页特效和本地存储

一、移动端网页特效 1. 触屏事件 1.1 触屏事件概述 移动端浏览器兼容性较好&#xff0c;我们不需要考虑以前 JS 的兼容性问题&#xff0c;可以放心的使用原生 JS 书写效果&#xff0c;但是移动端也有自己独特的地方。比如触屏事件 touch&#xff08;也称触摸事件&#xff09…

联想M7605DW怎么连接WiFi网络

联想M7605DW是一款拥有WiFi功能的打印机&#xff0c;可以通过WiFi连接无线网络&#xff0c;实现打印无线传输。 首先&#xff0c;需要确保你的WiFi网络已经正常连接&#xff0c;并且知道WiFI的网络名称和密码&#xff0c;同时确保你的电脑或手机设备与WiFi相连接。 启动联想M76…

数组、指针练习题及解析(含笔试题目讲解)其一

目录 前言 题目列表&#xff1a; 题目解析 一维数组 字符数组 字符串 字符指针 二维数组 笔试题 总结 前言 前几期的博客已经将有关指针、数组的所以知识都已基本讲解完毕&#xff0c;那么接下来我们就做一些练习巩固&#xff0c;这些练习依据历年来一些公司笔试题进行…

java的ThreadLocal变量

Java的ThreadLocal变量是线程的局部变量&#xff0c;只能被本线程访问&#xff0c;不能被其它线程访问&#xff0c;也就是说线程间进行了隔离。每个线程访问该变量的一个独立拷贝&#xff0c;互相不干扰。感觉跟synchronized的作用相反&#xff0c;synchronized是为了保护线程间…

Kafka入门,mysql5.7 Kafka-Eagle部署(二十五)

官网 https://www.kafka-eagle.org/ 下载解压 这里使用的是2.0.8 创建mysql数据库 创建名为ke数据库,新版本会自动创建&#xff0c;不会创建的话&#xff0c;自己手动创建&#xff0c;不然会报查不到相关表信息错误 SET NAMES utf8; SET FOREIGN_KEY_CHECKS 0;-- ------…

从2023中国峰会,看亚马逊云科技的生成式AI战略

“生成式AI的发展就像一场马拉松比赛&#xff0c;当比赛刚刚开始时&#xff0c;如果只跑了三四步就断言某某会赢得这场比赛&#xff0c;显然是不合理的。我们现在还处于非常早期的阶段。” 近日&#xff0c;在2023亚马逊云科技中国峰会上&#xff0c;亚马逊云科技全球产品副总裁…

智慧农业:温室大棚物联网系统,助力实现可视化科学管理

我国传统农业的特点是靠天吃饭&#xff0c;而智慧农业发端于物联网设备和对应的农业信息化管理系统&#xff0c;是利用数字技术、数据分析和人工智能等先进技术手段&#xff0c;对农业生产进行精细化管理和智能化决策的一种新型农业生产模式。它可以通过实时监测、预测和调控土…

java 配置打包Spring Boot项目过程中跳过测试环节

上文 java 打包Spring Boot项目&#xff0c;并运行在windows系统中中 我们演示了打包 Spring Boot项目的并运行在本地的方法 但是 我们这里会看到 每次打包 他这都会有个T E S T S 测试的部分 但是 我们自己开发的程序 要上线 有没有问题我们肯定自己清楚啊 没必要它做测试 而且…

web学习笔记2

文档流 网页是一个多层的结构&#xff0c;设置样式也是一层一层的设置&#xff0c;最终我们看到的最上面的一层。 文档流是网页最底层 我们创建的元素默认情况下&#xff0c;都在文档流中 元素分为两种状态&#xff1a;在文档流中&#xff0c;脱离文档流 元素在文档流中的特点 …

同一段数据分别做傅里叶变化和逆变换的结果及分析

已知有公式 D F T &#xff1a; X [ k ] ∑ n 0 N − 1 x [ n ] e − j 2 π k n N &#xff0c; 0 ≤ k ≤ N − 1 DFT&#xff1a;Χ[k]\sum_{n0}^{N-1}x[n]e^{-\frac{j2\pi kn}{N}}&#xff0c;0≤k≤N-1 DFT&#xff1a;X[k]n0∑N−1​x[n]e−Nj2πkn​&#xff0c;0≤k…

超详细 | 模拟退火-粒子群自适应优化算法及其实现(Matlab)

作者在前面的文章中介绍了经典的优化算法——粒子群算法(PSO)&#xff0c;各种智能优化算法解决问题的方式和角度各不相同&#xff0c;都有各自的适用域和局限性&#xff0c;对智能优化算法自身做的改进在算法性能方面得到了一定程度的提升&#xff0c;但算法缺点的解决并不彻底…

学生公寓智能电表控电系统的技术要求

学生公寓电表智能控电石家庄光大远通电气有限公司模块采用高精度计量芯片,的计量计费功能。 控制路数&#xff1a;可输出1~4路输出,每个回路都可以设置负载识别,定时断送过载功率等控电参数。 自动断电 &#xff1a;具有自动断电功能,可用电量为0时,应自动切断该分路电源 支持正…

创建Spring CloudDEMO流程

创建普通的maven工程作为父工程 然后设置字符集为UTF-8 再注解生效激活 java编译版本选择8 idea文件忽略&#xff08;忽略乱七八糟的文件&#xff09; *.hprof;*.pyc;*.pyo;*.rbc;*.yarb;*~;.DS_Store;.git;.hg;.svn;CVS;__pycache__;_svn;vssver.scc;vssver2.scc;.idea;*.iml…

TencentOS3.1安装PHP+Nginx+redis测试系统

PHP和Nginx应用统一安装在/application下。 Nginx选用了较新的版本1.25.0 官网下载安装包&#xff0c;解包。执行如下命令编译&#xff1a; ./configure --prefix/application/nginx-1.25.0 --usernginx --groupnginx --with-http_ssl_module --with-http_stub_status_modu…

win系统电脑在线打开sketch文件的方法

自Sketch诞生以来&#xff0c;只有Mac版本。Windows计算机如何在线打开Sketch文件&#xff1f; 即时设计已经解决了你遇到的大部分问题&#xff0c;不占用内存也是免费的。 您可以使用此软件直接在线打开Sketch文件&#xff0c;完整预览并导出CSS、SVG、PNG等&#xff0c;还具…