- 使用tensorrt进行模型推理前,使用cuda进行图像预处理,可以直接将结果输入tensorrt提高速度,平时目标检测推理的图像预处理主要为warpaffine的缩放和平移,将图像等比缩放并居中。
一、缩放、平移矩阵
1. 缩放
[ x ′ y ′ ] \begin{bmatrix} x\prime\\ y\prime\end{bmatrix} [x′y′]= [ s c a l e 0 0 s c a l e ] \begin{bmatrix} scale&0\\ 0&scale\end{bmatrix} [scale00scale]· [ x y ] \begin{bmatrix} x\\ y\end{bmatrix} [xy]
2. 平移
[ x ′ y ′ ] \begin{bmatrix} x\prime\\ y\prime\end{bmatrix} [x′y′]= [ 1 0 0 1 ] \begin{bmatrix} 1&0\\ 0&1\end{bmatrix} [1001]· [ x y ] \begin{bmatrix} x\\ y\end{bmatrix} [xy] + [ o x o y ] \begin{bmatrix} ox\\ oy\end{bmatrix} [oxoy]
- 合并为一个矩阵变换
[ x ′ y ′ w ] \begin{bmatrix} x\prime\\ y\prime \\w\end{bmatrix} x′y′w = [ 1 0 o x 0 1 o y 0 0 1 ] \begin{bmatrix} 1&0&ox\\ 0&1&oy\\ 0&0&1\end{bmatrix} 100010oxoy1 · [ x y 1 ] \begin{bmatrix} x\\ y\\1\end{bmatrix} xy1
3.图像等比缩放并居中
分为三个步骤
- 等比缩放
s c a l e = m i n ( d s t . w i d t h s r c . w i d t h , d s t . h e i g h t s r c . h e i g h t ) scale = min\ (\frac{dst.width}{src.width},\frac{dst.height}{src.height}) scale=min (src.widthdst.width,src.heightdst.height) - 平移到src原点(opencv中的图片左上角)
o x = − s c a l e × s r c . w i d t h 2 , o y = − s c a l e × s r c . h e i g h t 2 ox = -\frac{scale\times\ src.width}{2},\quad oy = -\frac{scale\times\ src.height}{2} ox=−2scale× src.width,oy=−2scale× src.height - 平移到dst中心位置
o x = d s t . w i d t h 2 , o y = d s t . h e i g h t 2 ox = \frac{dst.width}{2},\quad oy = \frac{dst.height}{2} ox=2dst.width,oy=2dst.height - 等比缩放并居中矩阵变换
[ x ′ y ′ ] \begin{bmatrix} x\prime\\ y\prime\end{bmatrix} [x′y′]= [ s c a l e 0 − s c a l e × s r c . w i d t h 2 + d s t . w i d t h 2 0 s c a l e − s c a l e × s r c . h e i g h t 2 + d s t . h e i g h t 2 ] \begin{bmatrix} scale&0&-\frac{scale\times\ src.width}{2}+\frac{dst.width}{2}\\ 0&scale&-\frac{scale\times\ src.height}{2}+\frac{dst.height}{2}\end{bmatrix} [scale00scale−2scale× src.width+2dst.width−2scale× src.height+2dst.height]· [ x y 1 ] \begin{bmatrix} x\\ y\\1\end{bmatrix} xy1
3.逆变换
- 在warpaffine实现过程需要其逆变换
k = s c a l e k = scale k=scale
b 1 = − s c a l e × s r c . w i d t h 2 + d s t . w i d t h 2 b1 = -\frac{scale\times\ src.width}{2}+\frac{dst.width}{2} b1=−2scale× src.width+2dst.width
b 2 = − s c a l e × s r c . h e i g h t 2 + d s t . h e i g h t 2 b2 = -\frac{scale\times\ src.height}{2}+\frac{dst.height}{2} b2=−2scale× src.height+2dst.height
x ′ = k x + b 1 x\prime=kx+b1 x′=kx+b1
y ′ = k y + b 2 y\prime=ky+b2 y′=ky+b2
x = x ′ − b 1 k = x ′ × 1 k + ( − b 1 k ) x = \frac{x\prime-b1}{k}=x\prime\times\frac{1}{k}+(-\frac{b1}{k}) x=kx′−b1=x′×k1+(−kb1)
y = x ′ − b 2 k = y ′ × 1 k + ( − b 2 k ) y = \frac{x\prime-b2}{k}=y\prime\times\frac{1}{k}+(-\frac{b2}{k}) y=kx′−b2=y′×k1+(−kb2)
W − 1 W^{-1} W−1= [ 1 k 0 − b 1 k 0 1 k − b 2 k ] \begin{bmatrix} \frac{1}{k}&0&-\frac{b1}{k}\\ 0&\frac{1}{k}&-\frac{b2}{k}\end{bmatrix} [k100k1−kb1−kb2]
二、cuda实现
1. main.cpp
- 使用
cudaMalloc
在GPU上开辟两块空间&psrc_device
和&pdst_device
,大小分别为src_size
和dst_size
- 将数据搬运到GPU
cudaMemcpy(psrc_device, image.data, src_size, cudaMemcpyHostToDevice);
//image为Mat格式,image.data返回image的首地址
2. warpaffine.cu
- host计算变换逆矩阵
- 开启dst图像大小个线程
dim3 block_size(32, 32); //blocksize最大就是1024
dim3 grid_size((dst_width + 31) / 32, (dst_height + 31) / 32);//向上取整
- 核函数
warp_affine_bilinear_kernel<<<grid_size, block_size, 0, nullptr>>>
- 线程索引并return多余线程
int dx = blockDim.x * blockIdx.x + threadIdx.x;
int dy = blockDim.y * blockIdx.y + threadIdx.y;
if (dx >= dst_width || dy >= dst_height) return;
//(dx,dy)即为dst图像对应的像素
- 由逆矩阵计算dst对应在src上的坐标
- 变换后超出src范围(
src_x < -1 || src_x >= src_width || src_y < -1 || src_y >= src_height
)的坐标取固定值fill_value
- 其余坐标的值由双线性插值计算
比如,计算左上角src的值时
uint8_t const_values[] = {fill_value, fill_value, fill_value};
uint8_t* v1 = const_values;
v1 = src + y_low * src_line_size + x_low * 3;
//src 表示存储图像像素值的连续缓冲区的起始地址
//y_low 表示像素所处的行数
//src_line_size 表示缓冲区一行像素值的字节数
//x_low 表示像素所处的列数,
//v1 则表示对应像素的存储地址
//因为一个像素的颜色值通常是由 3 个字节组成,因此需要将 x 坐标乘以 3,以计算出像素在缓冲区中的字节偏移量。
- RGB三个值通过地址的偏移的值的双线性插值计算
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);
3. main.cpp
- 处理完的数据从gpu搬回来
checkRuntime(cudaMemcpy(output.data, pdst_device, dst_size, cudaMemcpyDeviceToHost));
- 释放内存