OpenCV GpuMat点积

时间:2017-10-31 06:42:23

标签: c++ opencv cublas

我目前正在使用Nvidia Jetson TX1 / 2进行开发。

我的代码中最慢的部分是(为了便于阅读而更改了变量名称):

....
cv::Mat A, B;
GpuMat_A.download(A, Cuda_stream);
GpuMat_B.download(B, Cuda_stream);
double C = A.dot(B);

B = B.inv() * C;
GpuMat_B.upload(B, Cuda_stream);
....

我之前从未使用过GpuMat,而且似乎是dot产品& inv()函数不存在,迫使我从&amp ;,下载()和upload()到Gpu到RAM。

那些下载&上传需要3ms~但这会在迭代循环中重复,然后在55ms进程中花费我45ms。

1)我是否错过了博士中的那些人? (反转和点是计算机视觉中的标准操作,所以我认为它们应该存在)。

2)如果没有,那么在Gpu方面做这两个方面最有效的方法(如果可能的话)是什么?

更新:1)似乎GpuMat没有" native"点积。

所以这就是我想要做的事情:(现在只需获得A& B的第一行并进行矢量Dot产品)

void GpuMat_Dot(GpuMat& A, GpuMat& B, double& dot)
{
    CV_ASSERT(A.type() == B.type() && A.rows == B.cols && A.cols == B.rows);
    const double* Ptr_first_row = A.ptr(0); //const _Tp GpuMat::Ptr()
    const double* Ptr_first_col = &B.ptr(0)[0]; //I couldn't find a equivalent of Ptr() that return the col address directly also this might be wrong
    dot = cublasDdot((int)A.cols, Ptr_first_row, A.elemsize()/*1 ?*/, Ptr_first_col, B.elemsize()/*1 */);
} 

它确实编译(从电话错字编辑可能存在),但结果不是它应该是...

2 个答案:

答案 0 :(得分:1)

通过抓取GPU垫内的原始指针尝试通过CUBLAS进行矩阵反转,与点积相同。

请注意,反转大矩阵远非直截了当,通常是一个迭代过程。

使用GPU的更典型方式是通过“统一”UMat接口。

答案 1 :(得分:0)

对于点积产品,我的建议是使用NVIDIA Performance Primitives,如果所有图像都具有相同的大小,则可以编写带有预先计算的缓冲区的版本以提高性能。

double dotGpuMat(cv::cuda::GpuMat m1, cv::cuda::GpuMat m2)
{
    int hpBufferSize;
    Npp8u *pDeviceBuffer;
    NppiSize ns;
    double pDp;
    double *pDp_dev;

    ns.height = m1.rows;
    ns.width = m1.cols;
    cudaMalloc((void**)&pDp_dev, sizeof(double));
    nppiDotProdGetBufferHostSize_32f64f_C1R(ns, &hpBufferSize);
    cudaMalloc((void**)&pDeviceBuffer, sizeof(Npp8u)*hpBufferSize);
    nppiDotProd_32f64f_C1R(m1.ptr<Npp32f>(), static_cast<int>(m1.step), m2.ptr<Npp32f>(), static_cast<int>(m2.step), ns, pDp_dev, pDeviceBuffer);
    cudaMemcpy(&pDp, pDp_dev, sizeof(double), cudaMemcpyDeviceToHost);
    cudaFree(pDeviceBuffer);
    cudaFree(pDp_dev);
    return pDp;
}

反比更复杂。首先,不能保证GpuMat是连续的。其次,如果我理解正确,那么Gpumat会按行主要顺序存储,而Cusolver会按列主要顺序存储。因此,您需要一对内核来将GpuMat复制到float数组中,反之亦然,而另一个内核则用于创建身份矩阵。

#define IDX2C(i,j,ld) (((j)*(ld))+(i))
#define _x_ threadIdx.x
#define _y_ blockIdx.x
#define _i_ blockIdx.x
#define _j_ threadIdx.x
#define _ld_ gridDim.x

__global__ void copyDataGpuMat2Array(cv::cuda::PtrStepSzf src, float *dst)
{
    dst[IDX2C(_i_, _j_, _ld_)] = src(_y_, _x_);
}

__global__ void copyDataArray2GpuMat(float *src, cv::cuda::PtrStepSzf dst)
{
    dst(_y_, _x_) = src[IDX2C(_i_, _j_, _ld_)];
}

__global__ void eye(float *srcDst)
{
    if (_i_ == _j_)
        srcDst[IDX2C(_i_, _j_, _ld_)] = 1;
    else
        srcDst[IDX2C(_i_, _j_, _ld_)] = 0;
}

cv::cuda::GpuMat inverse_wr(const cv::cuda::GpuMat &m)
{
    float *d_m, *d_minv;
    cusolverDnHandle_t handle;
    int *d_pivot, *d_info, Lwork;
    float *d_Work;
    cv::cuda::GpuMat minv;

    if (m.rows != m.cols )//m must be square
        return cv::cuda::GpuMat();

    cusolverDnCreate(&handle);

    cudaMalloc((void**)&d_m   , sizeof(float)*m.rows*m.cols);
    cudaMalloc((void**)&d_minv, sizeof(float)*m.rows*m.cols);
    cudaMalloc((void **)&d_pivot, m.rows * sizeof(int));
    cudaMalloc((void **)&d_info, sizeof(int));

    copyDataGpuMat2Array<<<m.rows, m.cols>>>(m, d_m);
    eye<<<m.rows, m.cols>>>(d_minv);

    cusolverDnSgetrf_bufferSize(handle, m.rows, m.rows, d_m, m.rows, &Lwork);
    cudaMalloc((void **)&d_Work, Lwork * sizeof(float));
    cusolverDnSgetrf(handle, m.rows, m.rows, d_m, m.rows, d_Work, d_pivot, d_info);
    cusolverDnSgetrs(handle, CUBLAS_OP_N, m.rows, m.rows, d_m, m.rows, d_pivot, d_minv, m.rows, d_info);

    minv = cv::cuda::GpuMat(m.rows, m.cols, CV_32FC1);
    copyDataArray2GpuMat<<<m.rows, m.cols>>>(d_minv, minv);

    cudaFree(d_Work);
    cudaFree(d_pivot);
    cudaFree(d_info);
    cudaFree(d_m);
    cudaFree(d_minv);

    cusolverDnDestroy(handle);
    return minv;
}

PS:为简单起见,我没有在代码中编写任何形式的保护措施。