opencv的GPU编程(一)

来源:互联网 发布:用友软件股票行情 编辑:程序博客网 时间:2024/06/08 00:36

最近在做人脸检测的速度优化工作,对OPENCV下利用GPU加速人脸检测做了一些了解,现对齐进行梳理,了解下大概,主要内容来自
OPENCV tutorial on GPU
首先,是关于OPENCV上GPU的一些说明。

1.GPU有自己的内存,当你把Mat类型数据从硬盘读入到系统内存后,为了使用GPU模块,还必须将数据上传到GPU,GPU计算完后,再将计算结果下载下来,上传和下载一般比较耗时,因此不推荐将体量小的函数移植到GPU上使用,那样,GPU并行计算的能力会因长时间的上传下载操作而得不到充分展现。

2.OPENCV在GPU上有类似于Mat的数据结构,GpuMat。GpuMat与Mat的差别是,GpuMat只能包含2D数据且不能返回GpuMat的引用,简单的使用如下:

Mat I1; // Main memory item - read image into with imread for example
gpu::GpuMat gI; // GPU matrix - for now empty
gI1.upload(I1); // Upload a data from the system memory to the GPU memory

I1 = gI1; // Download,gI1.download(I1) will work too

3.GPU并不能对图片的各种通道都能进行有效的处理。
GPU只接受通道数为一或四的图片,且数值类型为char或float。GPU不接受double类型的图片数据,否则会有异常抛出。对于三通道的图片数据,要么在图片上再加一个通道变为四通道(但这样会比较消耗GPU内存不推荐);或者,将图片按通道划分成多个单通道图片依次处理。若对于某些函数,图片中元素的实际位置不影响处理,那么比较好的处理办法是将图片直接reshape成单通道图片,例如对于PSNR的absdiff操作。

其次,关于GPU具体的代码优化。

1.GPU上面的内存分配操作需要谨慎,尽可能越少越好。因此对于被多次调用的函数来说,函数使用的局部参数变量尽可能只声明一次。例如double getPSNR_GPU_optimized()函数与double getPSNR_GPU()函数的比较,同样是在GPU上运行,前者的效率要高于后者。因为前者使用了结构体BufferPSNR,将变量 gpu::GpuMat gI1, gI2, gs, t1,t2仅做一次声明;无论函数被调用多少次,变量的内存分配却只做了一次;而后者每次调用都要做一次变量声明,这样的结果是GPU要做多次的变量临时内存分配操作,影响效率。

2.避免不必要的数据转移存储操作。例如
b.t1 = 2 * b.mu1_mu2 + C1;
GPU在计算2*b.mu1_mu2后,会将结果暂存在一个新的分配的内存区间,然后再做加运算和赋值操作;
若将代码改为如下:
gpu::multiply(b.mu1_mu2, 2, b.t1); //b.t1 = 2 * b.mu1_mu2 + C1;
gpu::add(b.t1, C1, b.t1);
则可以避免GPU这种数据转移的操作。

3.借助gpu::Stream实现异步调用。默认情况下,调用一个函数会等它执行完并返回结果。异步调用则可以执行函数,内存分配并快速返回结果。
b.gI1.upload(i1);
b.gI2.upload(i2);
gpu::Stream stream;
stream.enqueueConvert(b.gI1, b.t1, CV_32F);
stream.enqueueConvert(b.gI2, b.t2, CV_32F);
gpu::split(b.t1, b.vI1, stream);
gpu::split(b.t2, b.vI2, stream);

gI1.upload(i1);
gI2.upload(i2);
gI1.convertTo(tmp1, CV_MAKE_TYPE(CV_32F, gI1.channels()));
gI2.convertTo(tmp2, CV_MAKE_TYPE(CV_32F, gI2.channels()));
vector vI1, vI2;
gpu::split(tmp1, vI1);
gpu::split(tmp2, vI2);
以上面两部分代码为例,第二部分代码是默认的图片上传,将图片按照通道进行划分,并对每个通道进行依次操作;第一部分使用了stream进行了优化,在函数执行期间,我们仍可以进行内存分配的操作。详细可见gpu::stream

附上两份完整的代码:
double getPSNR(const Mat& I1, const Mat& I2)
{
Mat s1;
absdiff(I1, I2, s1); // |I1 - I2|
s1.convertTo(s1, CV_32F); // cannot make a square on 8 bits
s1 = s1.mul(s1); // |I1 - I2|^2
Scalar s = sum(s1); // sum elements per channel
double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels
if( sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse =sse /(double)(I1.channels() * I1.total());
double psnr = 10.0*log10((255*255)/mse);
return psnr;
}
}

double getPSNR_GPU_optimized(const Mat& I1, const Mat& I2, BufferPSNR& b)
{
b.gI1.upload(I1);
b.gI2.upload(I2);
b.gI1.convertTo(b.t1, CV_32F);
b.gI2.convertTo(b.t2, CV_32F);
gpu::absdiff(b.t1.reshape(1), b.t2.reshape(1), b.gs);
gpu::multiply(b.gs, b.gs, b.gs);
double sse = gpu::sum(b.gs, b.buf)[0];
if( sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse = sse /(double)(I1.channels() * I1.total());
double psnr = 10.0*log10((255*255)/mse);
return psnr;
}
}

struct BufferPSNR // Optimized GPU versions
{ // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.
gpu::GpuMat gI1, gI2, gs, t1,t2;

gpu::GpuMat buf;

};

double getPSNR_GPU(const Mat& I1, const Mat& I2)
{
gpu::GpuMat gI1, gI2, gs, t1,t2;
gI1.upload(I1);
gI2.upload(I2);
gI1.convertTo(t1, CV_32F);
gI2.convertTo(t2, CV_32F);
gpu::absdiff(t1.reshape(1), t2.reshape(1), gs);
gpu::multiply(gs, gs, gs);
Scalar s = gpu::sum(gs);
double sse = s.val[0] + s.val[1] + s.val[2];
if( sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse =sse /(double)(gI1.channels() * I1.total());
double psnr = 10.0*log10((255*255)/mse);
return psnr;
}
}

Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
const double C1 = 6.5025, C2 = 58.5225;
/******************* INITS ************************/
int d = CV_32F;

Mat I1, I2;i1.convertTo(I1, d);           // cannot calculate on one byte large valuesi2.convertTo(I2, d);Mat I2_2   = I2.mul(I2);        // I2^2Mat I1_2   = I1.mul(I1);        // I1^2Mat I1_I2  = I1.mul(I2);        // I1 * I2/*************************** END INITS **********************************/Mat mu1, mu2;   // PRELIMINARY COMPUTINGGaussianBlur(I1, mu1, Size(11, 11), 1.5);GaussianBlur(I2, mu2, Size(11, 11), 1.5);Mat mu1_2   =   mu1.mul(mu1);Mat mu2_2   =   mu2.mul(mu2);Mat mu1_mu2 =   mu1.mul(mu2);Mat sigma1_2, sigma2_2, sigma12;GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);sigma1_2 -= mu1_2;GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);sigma2_2 -= mu2_2;GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);sigma12 -= mu1_mu2;///////////////////////////////// FORMULA ////////////////////////////////Mat t1, t2, t3;t1 = 2 * mu1_mu2 + C1;t2 = 2 * sigma12 + C2;t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))t1 = mu1_2 + mu2_2 + C1;t2 = sigma1_2 + sigma2_2 + C2;t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))Mat ssim_map;divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;Scalar mssim = mean( ssim_map ); // mssim = average of ssim mapreturn mssim;

}

Scalar getMSSIM_GPU( const Mat& i1, const Mat& i2)
{
const float C1 = 6.5025f, C2 = 58.5225f;
/******************* INITS ************************/
gpu::GpuMat gI1, gI2, gs1, tmp1,tmp2;

gI1.upload(i1);gI2.upload(i2);gI1.convertTo(tmp1, CV_MAKE_TYPE(CV_32F, gI1.channels()));gI2.convertTo(tmp2, CV_MAKE_TYPE(CV_32F, gI2.channels()));vector<gpu::GpuMat> vI1, vI2;gpu::split(tmp1, vI1);gpu::split(tmp2, vI2);Scalar mssim;for( int i = 0; i < gI1.channels(); ++i ){    gpu::GpuMat I2_2, I1_2, I1_I2;    gpu::multiply(vI2[i], vI2[i], I2_2);        // I2^2    gpu::multiply(vI1[i], vI1[i], I1_2);        // I1^2    gpu::multiply(vI1[i], vI2[i], I1_I2);       // I1 * I2    /*************************** END INITS **********************************/    gpu::GpuMat mu1, mu2;   // PRELIMINARY COMPUTING    gpu::GaussianBlur(vI1[i], mu1, Size(11, 11), 1.5);    gpu::GaussianBlur(vI2[i], mu2, Size(11, 11), 1.5);    gpu::GpuMat mu1_2, mu2_2, mu1_mu2;    gpu::multiply(mu1, mu1, mu1_2);    gpu::multiply(mu2, mu2, mu2_2);    gpu::multiply(mu1, mu2, mu1_mu2);    gpu::GpuMat sigma1_2, sigma2_2, sigma12;    gpu::GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);    gpu::subtract(sigma1_2, mu1_2, sigma1_2); // sigma1_2 -= mu1_2;    gpu::GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);    gpu::subtract(sigma2_2, mu2_2, sigma2_2); // sigma2_2 -= mu2_2;    gpu::GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);    gpu::subtract(sigma12, mu1_mu2, sigma12); // sigma12 -= mu1_mu2;    ///////////////////////////////// FORMULA ////////////////////////////////    gpu::GpuMat t1, t2, t3;    mu1_mu2.convertTo(t1, -1, 2, C1); // t1 = 2 * mu1_mu2 + C1;    sigma12.convertTo(t2, -1, 2, C2); // t2 = 2 * sigma12 + C2;    gpu::multiply(t1, t2, t3);        // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))    gpu::addWeighted(mu1_2, 1.0, mu2_2, 1.0, C1, t1);       // t1 = mu1_2 + mu2_2 + C1;    gpu::addWeighted(sigma1_2, 1.0, sigma2_2, 1.0, C2, t2); // t2 = sigma1_2 + sigma2_2 + C2;    gpu::multiply(t1, t2, t1);                              // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))    gpu::GpuMat ssim_map;    gpu::divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;    Scalar s = gpu::sum(ssim_map);    mssim.val[i] = s.val[0] / (ssim_map.rows * ssim_map.cols);}return mssim;

}
struct BufferMSSIM // Optimized GPU versions
{ // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.
gpu::GpuMat gI1, gI2, gs, t1,t2;

gpu::GpuMat I1_2, I2_2, I1_I2;vector<gpu::GpuMat> vI1, vI2;gpu::GpuMat mu1, mu2;gpu::GpuMat mu1_2, mu2_2, mu1_mu2;gpu::GpuMat sigma1_2, sigma2_2, sigma12;gpu::GpuMat t3;gpu::GpuMat ssim_map;gpu::GpuMat buf;

};
Scalar getMSSIM_GPU_optimized( const Mat& i1, const Mat& i2, BufferMSSIM& b)
{
const float C1 = 6.5025f, C2 = 58.5225f;
/******************* INITS ************************/

b.gI1.upload(i1);b.gI2.upload(i2);gpu::Stream stream;stream.enqueueConvert(b.gI1, b.t1, CV_32F);stream.enqueueConvert(b.gI2, b.t2, CV_32F);gpu::split(b.t1, b.vI1, stream);gpu::split(b.t2, b.vI2, stream);Scalar mssim;gpu::GpuMat buf;for( int i = 0; i < b.gI1.channels(); ++i ){    gpu::multiply(b.vI2[i], b.vI2[i], b.I2_2, stream);        // I2^2    gpu::multiply(b.vI1[i], b.vI1[i], b.I1_2, stream);        // I1^2    gpu::multiply(b.vI1[i], b.vI2[i], b.I1_I2, stream);       // I1 * I2    gpu::GaussianBlur(b.vI1[i], b.mu1, Size(11, 11), buf, 1.5, 0, BORDER_DEFAULT, -1, stream);    gpu::GaussianBlur(b.vI2[i], b.mu2, Size(11, 11), buf, 1.5, 0, BORDER_DEFAULT, -1, stream);    gpu::multiply(b.mu1, b.mu1, b.mu1_2, stream);    gpu::multiply(b.mu2, b.mu2, b.mu2_2, stream);    gpu::multiply(b.mu1, b.mu2, b.mu1_mu2, stream);    gpu::GaussianBlur(b.I1_2, b.sigma1_2, Size(11, 11), buf, 1.5, 0, BORDER_DEFAULT, -1, stream);    gpu::subtract(b.sigma1_2, b.mu1_2, b.sigma1_2, gpu::GpuMat(), -1, stream);    //b.sigma1_2 -= b.mu1_2;  - This would result in an extra data transfer operation    gpu::GaussianBlur(b.I2_2, b.sigma2_2, Size(11, 11), buf, 1.5, 0, BORDER_DEFAULT, -1, stream);    gpu::subtract(b.sigma2_2, b.mu2_2, b.sigma2_2, gpu::GpuMat(), -1, stream);    //b.sigma2_2 -= b.mu2_2;    gpu::GaussianBlur(b.I1_I2, b.sigma12, Size(11, 11), buf, 1.5, 0, BORDER_DEFAULT, -1, stream);    gpu::subtract(b.sigma12, b.mu1_mu2, b.sigma12, gpu::GpuMat(), -1, stream);    //b.sigma12 -= b.mu1_mu2;    //here too it would be an extra data transfer due to call of operator*(Scalar, Mat)    gpu::multiply(b.mu1_mu2, 2, b.t1, 1, -1, stream); //b.t1 = 2 * b.mu1_mu2 + C1;    gpu::add(b.t1, C1, b.t1, gpu::GpuMat(), -1, stream);    gpu::multiply(b.sigma12, 2, b.t2, 1, -1, stream); //b.t2 = 2 * b.sigma12 + C2;    gpu::add(b.t2, C2, b.t2, gpu::GpuMat(), -12, stream);    gpu::multiply(b.t1, b.t2, b.t3, 1, -1, stream);     // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))    gpu::add(b.mu1_2, b.mu2_2, b.t1, gpu::GpuMat(), -1, stream);    gpu::add(b.t1, C1, b.t1, gpu::GpuMat(), -1, stream);    gpu::add(b.sigma1_2, b.sigma2_2, b.t2, gpu::GpuMat(), -1, stream);    gpu::add(b.t2, C2, b.t2, gpu::GpuMat(), -1, stream);    gpu::multiply(b.t1, b.t2, b.t1, 1, -1, stream);     // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))    gpu::divide(b.t3, b.t1, b.ssim_map, 1, -1, stream);      // ssim_map =  t3./t1;    stream.waitForCompletion();    Scalar s = gpu::sum(b.ssim_map, b.buf);    mssim.val[i] = s.val[0] / (b.ssim_map.rows * b.ssim_map.cols);}return mssim;

}

0 0
原创粉丝点击