DPC++加速基于Canny算子图像边缘检测

释放双眼,带上耳机,听听看~!
本报告基于DPC++编程模型,利用Canny算子实现了对图像的高效边缘检测,通过并行计算策略实现了准确、快速的边缘检测,为图像处理领域提供了一个高效的解决方案。

DPC++加速基于canny算子图像边缘检测

1. 摘要

图像边缘检测在计算机视觉领域具有重要的应用价值。本报告基于DPC++编程模型,旨在实现对图像的高效边缘检测。以Canny算子为基础,通过合理的算法优化和并行计算策略,实现了对图像边缘的准确、快速检测。本研究的关键工作包括:采用了Canny算子作为基础算法,并在其基础上进行了一系列的优化,包括滤波器设计、梯度计算和非极大值抑制等方面的工作,以提高边缘检测的准确性,充分利用了DPC++这一异构编程模型,充分发挥CPU和GPU的并行计算能力,以提升边缘检测的速度和效率。实验结果表明,提出的方法在保持边缘检测准确性的同时,相较于传统方法获得了明显的加速效果。通过充分利用DPC++编程模型,为图像处理领域的研究和实际应用提供了一个高效的解决方

关键词: DPC++, Canny算子, 图像边缘检测, 并行计算, 异构计算

2. 相关工作

当涉及到图像边缘检测问题时,许多研究者采用了不同的算子和方法。以下是一些常用的图像边缘检测算子及它们的优缺点:

1. Prewitt算子:

原理: Prewitt算子基于一组垂直和水平方向的卷积核,通过在图像上进行卷积操作来检测边缘。

优点:

  • Prewitt算子简单易实现。
  • 对噪声有一定的抵抗能力。

缺点:

  • 对边缘细节的检测能力较弱,容易丧失一些细微的边缘信息。
  • 对图像的灰度变化较敏感,可能会导致一些不必要的边缘检测。

2. Roberts交叉梯度算子:

原理: Roberts算子也是基于垂直和水平方向的卷积核,但相比于Prewitt,它使用了不同的卷积核。

优点:

  • Roberts算子对垂直和水平方向的边缘检测比较敏感,适合于特定方向的特征检测。

缺点:

  • 对噪声敏感,容易受到图像中的高频噪声的影响。
  • 对角线方向的边缘检测能力相对较弱。

3. Sobel算子:

原理: Sobel算子也采用了垂直和水平方向的卷积核,但相对于Prewitt和Roberts,它的卷积核设计更为平滑。

优点:

  • Sobel算子对噪声具有一定的抵抗能力,通过平滑卷积核减少了高频噪声的影响。
  • 在垂直和水平方向上的边缘检测能力均衡。

缺点:

  • 对于某些特定方向的边缘可能会有一些漏检。
  • 对于细节的边缘检测相对弱。

4. Laplacian算子:

原理: Laplacian算子采用了二阶导数的方法来检测图像中的边缘。

优点:

  • 能够检测到较细微的边缘和细节信息。
  • 对图像的灰度变化比较敏感。

缺点:

  • 容易受到噪声的干扰,可能会产生一些假阳性的边缘检测结果。
  • 会导致图像中的细节信息变得不够清晰。

5. canny 算子

优点:

  1. 准确性高: Canny算法能够在图像中准确地检测到边缘,因为它在检测过程中考虑了边缘的强度和方向。
  2. 抑制噪声: Canny算法使用了高斯滤波来减少图像中的噪声,这使得它对于处理嘈杂的图像具有较好的鲁棒性。
  3. 单一响应: 对于真实的边缘,Canny算法通常只会在边缘的位置产生单一响应,这使得后续处理更容易。
  4. 精细控制: Canny算法允许我们调整阈值来控制边缘的检测灵敏度,这使得它可以适应不同的应用场景。
  5. 能保持边缘的连续性: Canny算法通过非极大值抑制来保持边缘的连续性,避免了边缘被分成多段。

缺点:

  1. 计算复杂度高: Canny算法涉及到多个步骤,包括高斯滤波、梯度计算、非极大值抑制和阈值处理,这使得它的计算复杂度相对较高。
  2. 对参数敏感: Canny算法的性能很大程度上依赖于合适的参数选择,尤其是高斯滤波的标准差和阈值的设定。

在本研究中,根据 canny 算子比其他算子具有着更好的性能与效果,选择基于DPC++的 Canny 算子来改善现有技术,其异构计算能力和数据并行特性使得在大规模图像上进行边缘检测时能够获得显著的性能提升。

算法介绍

canny 算子

图像边缘检测是计算机视觉领域中的一个基础问题,对于目标检测、图像分割等任务具有至关重要的作用。边缘检测是基于灰度突变来分割图像的常用方法,其实本质是提取图像中不连续部分的特征。目前常见边缘检测算子有差分算子、Roberts算子、Sobel算子、Prewitt算子、Log算子以及Canny算子等。其中,Canny算子是由计算机科学家John F. Canny于1986年提出的一种边缘检测算子,是目前理论上相对最完善的一种边缘检测算法。Canny算子作为一种经典的边缘检测算法,以其出色的性能和精度成为了研究和实践中的热门选择。接下来将深入介绍 Canny 算子的原理、理论推导、优缺点以及应用领域,旨在为研究者们提供一个全面的了解。

算法原理

基于Canny算子的边缘检测主要有4个步骤,依次是用高斯滤波器平滑图像、用Sobel等梯度算子计算梯度幅值和方向、对梯度幅值进行非极大值抑制、用双阈值算法检测和连接边缘。

  1. 高斯滤波

高斯滤波使用的高斯核是具有 x 和 y 两个维度的高斯函数,且两个维度上标准差一般取相同,形式为:

G(x,y)=12πσ2exp(−x2+y22σ2)G(x,y)=frac1{2pisigma^2}exp(-frac{x^2+y^2}{2sigma^2})

高斯滤波,即使用即使用某一尺寸的二维高斯核与图像进行卷积。由于数字图像的数据形式为离散矩阵,高斯核是对连续高斯函数的离散近似,通过对高斯曲面进行离散采样和归一化得出。例如,尺寸为 3×3 ,标准差为 1 的高斯核为:

[0.07511360.12384140.07511360.12384140.20417990.12384140.07511360.12384140.0751136]begin{bmatrix}0.0751136&0.1238414&0.0751136\0.1238414&0.2041799&0.1238414\0.0751136&0.1238414&0.0751136end{bmatrix}

在确定高斯核后,将其与图像进行离散卷积即可。

  1. 用Sobel等梯度算子计算梯度幅值和方向

Sobel算子是两个 3×3 的矩阵,分别为 S_x 和 S_y 。前者用于计算图像 x 方向像素梯度矩阵 G_x ,后者用于计算图像 y 方向像素梯度矩阵 G_y。具体形式为:

Gx=Sx∗I=[−10+1−20+2−10+1]∗IGy=Sy∗I=[−1−2−1000+1+2+1]∗Ibegin{aligned} &boldsymbol{G}_x=boldsymbol{S}_x*boldsymbol{I}=begin{bmatrix}-1&0&+1\-2&0&+2\-1&0&+1end{bmatrix}*boldsymbol{I}\\ &boldsymbol{G}_y=boldsymbol{S}_y*boldsymbol{I}=begin{bmatrix}-1&-2&-1\0&0&0\+1&+2&+1end{bmatrix}*boldsymbol{I}end{aligned}

其中, I 为灰度图像矩阵,且此处的 ∗ 表示互相关运算(卷积运算可视为将卷积核旋转180°后的互相关运算)。需要说明的是,图像矩阵坐标系原点在左上角,且 x 正方向为从左到右, y 正方向为从上到下。

计算梯度幅值公式:

gxy(i,j)=gx(i,j)2+gy(i,j)2g_{xy}(i,j)=sqrt{g_x(i,j)^2+g_y(i,j)^2}

根据此公式就可以得到梯度幅值矩阵 G_{xy}。

计算方向公式:

θ=arctangy(m,n)gx(m,n)theta=arctanfrac{g_y(m,n)}{g_x(m,n)}

  1. 对梯度幅值进行非极大值抑制

非极大值像素梯度抑制的目的在于消除边缘检测带来的杂散响应,起到将边缘“瘦身”的作用。其基本方法是将当前像素梯度强度与沿正负梯度方向上的相邻像素的梯度强度进行比较,若其最大(即为极值),则保留该像素为边缘点,若不是最大,则对其进行抑制,不将其作为边缘点。为了更精确计算,通常在跨越梯度方向的两个相邻像素之间使用线性插值来得到要参与比较的像素梯度。

DPC++加速基于Canny算子图像边缘检测

可将像素的邻接情况划分为4个区域,其中每个区域包含上下两部分。若中心像素点 x 方向梯度强度为 g_x(i,j) , y 方向梯度强度为 g_y(i,j) ,梯度强度为 g_{xy}(i,j) ,则根据 g_x(i,j) 和 g_y(i,j) 的正负和大小可判断出其梯度方向所属区域,用当前的像素点幅值大小与通过根据梯度方向选择相邻的像素的幅值大小进行比较来决定是否保留当前像素的值。相关公式如下:

gup(i,j)=(1−t)⋅gxy(i,j+1)+t⋅gxy(i−1,j+1)gdown(i,j)=(1−t)⋅gxy(i,j−1)+t⋅gxy(i+1,j−1)begin{aligned}g_{up}(i,j)&=(1-t)cdot g_{xy}(i,j+1)+tcdot g_{xy}(i-1,j+1)\\g_{down}(i,j)&=(1-t)cdot g_{xy}(i,j-1)+tcdot g_{xy}(i+1,j-1)end{aligned}

其他三个区域的计算方法类似。

  1. 用双阈值算法检测和连接边缘

定义一个高阈值和一个低阈值。梯度强度低于低阈值的像素点被抑制,不作为边缘点;高于高阈值的像素点被定义为强边缘,保留为边缘点;处于高低阈值之间的定义为弱边缘,留待进一步处理。

通常而言,由真实边缘引起的弱边缘像素点将连接到强边缘像素点,而噪声响应则未连接。所以通过查看弱边缘像素及其8个邻域像素,可根据其与强边缘的连接情况来进行判断。一般,可定义只要其中邻域像素其中一个为强边缘像素点,则该弱边缘就可以保留为强边缘,即真实边缘点。具体如下:

DPC++加速基于Canny算子图像边缘检测

总体实现流程如下:

DPC++加速基于Canny算子图像边缘检测

应用领域

Canny算法在计算机视觉和图像处理领域有着广泛的应用,主要体现在以下几个方面:

边缘检测: 这是Canny算法最基本的应用。它能够高效准确地识别图像中的边缘,从而帮助我们理解图像的结构和内容。

物体识别与跟踪: Canny算法在目标检测、跟踪和识别中扮演着重要角色。通过检测物体的边缘,可以用于提取物体的轮廓信息,从而帮助识别和跟踪目标。

图像分割: Canny算法可以用于图像分割,将图像分成具有相似特征的区域。这在图像处理、计算机视觉以及医学图像处理等领域中都有广泛应用。

医学图像处理: 在医学影像学中,Canny算法用于从X光、MRI等医学图像中提取轮廓,帮助医生诊断疾病。

DPC++框架

DPC++(Data Parallel C++)是一个用于并行计算的编程模型,它基于C++并整合了SYCL(Standard C++ for Heterogeneous Computing)和Data Parallel C++ 技术。DPC++旨在简化跨异构计算设备的并行程序开发,使程序员能够利用多种计算资源,如CPU、GPU、FPGA等,以实现高性能的并行计算,结构图如下:

DPC++加速基于Canny算子图像边缘检测

DPC++编程模型特点:

  1. 单一源码:DPC++允许开发者在单一源码中编写并行代码,而不需要为不同设备编写不同的版本。这极大地简化了程序维护和移植的工作。
  2. 异构计算:DPC++可以在不同类型的计算设备上运行,包括CPU、GPU、FPGA等。这使得开发者可以利用各种硬件资源来优化程序性能。
  3. 数据并行:DPC++支持数据并行模型,可以很容易地将相同的操作应用于多个数据元素,从而充分发挥并行计算的优势。
  4. 内存模型:DPC++提供了一套统一的内存模型,使得在不同设备间共享数据变得相对容易。
  5. 异步任务图:DPC++使用异步任务图来描述并行计算任务之间的依赖关系,这使得程序可以在不同设备上异步执行不同的任务,从而提高了整体性能。

为何选择使用DPC++加速图像边缘检测:

  1. 多设备利用:图像处理通常是计算密集型任务,利用GPU等并行计算设备可以显著提升处理速度。DPC++的异构计算特性使得我们可以将相应的任务分配到不同的设备上执行,从而充分利用各个设备的计算能力。
  2. 统一编程模型:DPC++提供了统一的编程模型,允许我们在单一源码中编写并行代码,而不需要为不同设备编写不同的版本。这样可以减少代码维护的工作量,同时也使得程序在不同设备上的移植变得更加容易。
  3. 数据并行处理:图像边缘检测通常涉及对图像中的像素进行相似的操作,这正是DPC++所擅长的领域。通过利用DPC++的数据并行模型,我们可以高效地处理大量的像素数据。
  4. 异步任务图优化:在图像处理任务中,往往存在一些可以并行执行的子任务,如滤波、梯度计算等。DPC++的异步任务图特性使得这些子任务可以在不同设备上异步执行,从而提高整体的处理效率。
  5. 内存模型支持:DPC++提供了统一的内存模型,使得在不同设备间共享数据变得相对容易。这对于图像边缘检测中需要频繁访问和修改像素数据的情况非常重要。

综上所述,选择使用DPC++来加速图像边缘检测是一个合理的选择。其统一的编程模型、异构计算能力以及数据并行特性使得我们能够充分利用各种计算资源,从而提高图像边缘检测的性能。同时,DPC++的内存模型和异步任务图特性也能够进一步优化程序的执行效率。

实验设计与实现

预处理

在预处理阶段,本实验主要完成两个工作:第一个是将彩色图像转换成灰度图像,第二个是生成高斯滤波。接下来将详细介绍实现原理与相关代码。

将彩色RGB图像转换成灰度图像的函数命名为ConvertRGB2GRAY,包含以下参数:

  • image:输入的彩色RGB图像。
  • imageGray:转换后输出的灰度图像。

核心实现是将彩色图像每个像素点 (i, j),将其通过加权求和的方式将三通道的RGB值转换为灰度值,核心代码如下:

 gray = 0.114 * R + 0.587 * G + 0.299 * B

其中,RGB 分别代表红色、绿色和蓝色通道的像素值。

生成高斯卷积核的函数命名为 GetGaussianKernel,包含以下参数:

  • gaus:一个指向含有N个double类型数组的指针,用于存储生成的高斯卷积核。
  • size:高斯卷积核的尺寸大小,通常是奇数,以保证卷积核有一个中心点。
  • sigma:卷积核的标准差,决定了高斯分布的“瘦胖程度”。

高斯公式如下:

G(x,y)=12πσ2exp(−x2+y22σ2)G(x,y)=frac1{2pisigma^2}exp(-frac{x^2+y^2}{2sigma^2})

核心实现代码:

 gaus[i][j]=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));

最后还需要对高斯值做归一化,使得所有元素之和等于1。

核心实现

在本节主要实现 canny 算法的原理。

高斯滤波函数 GaussianFilter函数的目的是对输入的原始图像进行高斯滤波,其中包括了以下几个参数:

  • imageSource:待滤波原始图像。
  • imageGaussian:滤波后输出图像。
  • gaus:一个指向含有N个double类型数组的指针,用于存储高斯卷积核。
  • size:滤波核的尺寸。

核心实现:使用生成的高斯卷积核与输入图像进行卷积和操作,对图像进行高斯滤波过程。

核心代码如下:

 imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);

SobelGradDirction该函数的目的是计算给定原始灰度图像的 Sobel 算子的X、Y方向梯度,并计算梯度的方向角。其中包括了以下几个参数:

  • imageSource:原始灰度图像。
  • imageSobelX:X方向梯度图像。
  • imageSobelY:Y方向梯度图像。
  • pointDrection:梯度方向角数组指针。

计算图像 y 方向像素梯度矩阵 G_y。具体形式为:

Gx=Sx∗I=[−10+1−20+2−10+1]∗IGy=Sy∗I=[−1−2−1000+1+2+1]∗Ibegin{aligned} &boldsymbol{G}_x=boldsymbol{S}_x*boldsymbol{I}=begin{bmatrix}-1&0&+1\-2&0&+2\-1&0&+1end{bmatrix}*boldsymbol{I}\\ &boldsymbol{G}_y=boldsymbol{S}_y*boldsymbol{I}=begin{bmatrix}-1&-2&-1\0&0&0\+1&+2&+1end{bmatrix}*boldsymbol{I}end{aligned}

具体实现:

 //通过指针遍历图像上每一个像素 
 double gradY=P[(i-1)*step+j+1]+P[i*step+j+1]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[i*step+j-1]*2-P[(i+1)*step+j-1];
 PY[i*stepXY+j*(stepXY/step)]=abs(gradY);
 double gradX=P[(i+1)*step+j-1]+P[(i+1)*step+j]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[(i-1)*step+j]*2-P[(i-1)*step+j+1];
 PX[i*stepXY+j*(stepXY/step)]=abs(gradX);

SobelAmplitude函数的目的是计算给定X、Y方向梯度图像的Sobel算子梯度幅值,参数如下:

  • const Mat imageGradX:代表X方向的梯度图像。
  • const Mat imageGradY:代表Y方向的梯度图像。
  • Mat &SobelAmpXY:代表计算得到的Sobel算子梯度幅值图像。

主要是遍历图像的每个像素点计算 x,y 方向的梯度幅值,计算公式如下:

 gradient_magnitude = sqrt(gradX^2 + gradY^2)

LocalMaxValue函数的目的是对给定的Sobel梯度图像进行局部极大值抑制,参数如下:

  • const Mat imageInput:代表Sobel梯度图像。
  • Mat &imageOutput:代表局部极大值抑制后的图像。
  • double *pointDrection:指向图像上每个点的梯度方向数组的指针。

对于每一个像素点,根据其梯度方向,进行局部极大值抑制的判断:

  • 对于四个不同的梯度方向范围(0-45度,45-90度,90-135度,135-180度),分别进行了判断和抑制。
  • 如果某个像素点不是局部极大值,则将其值设为0,以实现抑制效果。

具体实现如下:

 if(pointDrection[k]>0&&pointDrection[k]<=45)
 {
     if(value11<=(value12+(value02-value12)*tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)*tan(pointDrection[i*imageOutput.rows+j]))))
     {
         imageOutput.at<uchar>(i,j)=0;
     }
 }   
 if(pointDrection[k]>45&&pointDrection[k]<=90)
 ​
 {
     if(value11<=(value01+(value02-value01)/tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/tan(pointDrection[i*imageOutput.cols+j])))
     {
         imageOutput.at<uchar>(i,j)=0;
 ​
     }
 }
 if(pointDrection[k]>90&&pointDrection[k]<=135)
 {
     if(value11<=(value01+(value00-value01)/tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrection[i*imageOutput.cols+j])))
     {
         imageOutput.at<uchar>(i,j)=0;
     }
 }
 if(pointDrection[k]>135&&pointDrection[k]<=180)
 {
     if(value11<=(value10+(value00-value10)*tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrection[i*imageOutput.cols+j])))
     {
         imageOutput.at<uchar>(i,j)=0;
     }
 }

DoubleThreshold函数的目的是对给定的Sobel梯度幅值图像进行双阈值处理参数如下:

  • Mat &imageInput:代表Sobel梯度幅值图像。
  • double lowThreshold:代表低阈值。
  • double highThreshold:代表高阈值。

对于每一个像素点,根据其灰度值与阈值的大小关系,进行了相应的处理:

  • 如果像素值大于高阈值 highThreshold,则将其设为255(白色)。
  • 如果像素值小于低阈值 lowThreshold,则将其设为0(黑色)。
  • 如果像素值介于低阈值和高阈值之间,则保持不变。
 if(imageIput.at<uchar>(i,j)>highThreshold)
 {
     imageIput.at<uchar>(i,j)=255;
 }   
 if(imageIput.at<uchar>(i,j)<lowThreshold)
 {
     imageIput.at<uchar>(i,j)=0;
 }

DoubleThresholdLink函数的目的是对给定的Sobel梯度幅值图像进行双阈值中间像素连接处理,参数如下:

  • Mat &imageInput:代表Sobel梯度幅值图像。
  • double lowThreshold:代表低阈值。
  • double highThreshold:代表高阈值。

采用递归方式对于每一个像素点,进行了如下处理:

  • 如果像素值大于低阈值 lowThreshold 且小于255,进入条件判断。
  • 在条件判断中,检查周围8个像素是否有白色(值为255)的像素,如果有任意一个周围像素是白色的,将当前像素设为白色。
  • 如果没有周围白色像素,则将当前像素设为黑色(值为0)。
  • 如果满足条件,递归调用 DoubleThresholdLink 函数,继续处理相邻的像素。

递归调用的目的是为了确保在某个像素的值被更新后,对其相邻的像素也进行相应的处理,以保证连接性。

 if(imageInput.at<uchar>(i,j)>lowThreshold&&imageInput.at<uchar>(i,j)<255)
 {
     if(imageInput.at<uchar>(i-1,j-1)==255||imageInput.at<uchar>(i-1,j)==255||imageInput.at<uchar>(i-1,j+1)==255||
        imageInput.at<uchar>(i,j-1)==255||imageInput.at<uchar>(i,j)==255||imageInput.at<uchar>(i,j+1)==255||
        imageInput.at<uchar>(i+1,j-1)==255||imageInput.at<uchar>(i+1,j)==255||imageInput.at<uchar>(i+1,j+1)==255)
     {
         imageInput.at<uchar>(i,j)=255;
         DoubleThresholdLink(imageInput,lowThreshold,highThreshold); //递归调用
     }   
     else
     {
         imageInput.at<uchar>(i,j)=0;
     }   

DPC++加速

为了加速图像边缘检测算法的执行,选择了使用DPC++(Data Parallel C++)来利用多核处理器和加速器的并行计算能力。DPC++是一个基于C++的并行编程扩展,它允许将任务分发到不同的计算设备上,从而实现高效的并行计算。

在具体的实现中,采取了以下关键步骤来利用DPC++加速图像边缘检测算法:

  1. 初始化DPC++环境

    首先初始化了DPC++环境,创建了一个SYCL队列以及启用了性能分析功能。这样我们可以在设备上执行任务并监测其执行时间。

     cppCopy codeauto propList = property_list{property::queue::enable_profiling()};
     queue q(propList);
    
  2. 定义输入输出缓冲区

    为了在设备上处理图像数据,我们创建了缓冲区来存储输入图像、输出图像以及高斯核数据。这样可以保证数据在设备和主机之间的高效传输。

     cppCopy codebuffer<uchar, 2> imageSourceBuffer(imageSource.data, global);
     buffer<uchar, 2> imageGaussianBuffer(imageGaussian.data, global);
     buffer<double, 1> gausBuffer(gausArray, range<1>(size*size));
    
  3. 设计SYCL内核

    我们定义了一个SYCL内核函数,它将在并行处理单元上执行。内核函数中,我们获取了对图像和高斯核的访问权限,并在图像上应用了高斯滤波算法。

     cppCopy codeauto event = q.submit([&](handler &h) {
         // 获取缓冲区访问权限
         auto sourceAccessor = imageSourceBuffer.get_access<access::mode::read_write>(h);
         auto gaussianAccessor = imageGaussianBuffer.get_access(h);
         auto gausAccessor = gausBuffer.get_access(h);
     ​
         h.parallel_for(global, [=](item<2> item) {
             // 内核函数代码
         });
     });
    
  4. 内核函数实现

    在内核函数内部,我们使用两层嵌套循环来遍历高斯核的每个元素,并在图像上进行相应的计算。这样可以充分利用设备上的并行计算能力。

     cppCopy codeint i = item.get_id(0);
     int j = item.get_id(1);
     int k = 0;
     for (int l = -size / 2; l <= size / 2; l++) {
         for (int g = -size / 2; g <= size / 2; g++) {
             // 计算高斯滤波
             // ...
             k++;
         }
     }
    
  5. 性能评估

    我们使用性能分析工具来评估DPC++实现相对于传统串行实现的性能提升。通过比较执行时间和计算加速比,我们可以清晰地了解DPC++的优势。

     cppCopy codeq.wait();
     double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
     std::cout << "Execution time: " << gpu_time_ns / 1000000 << " milliseconds" << std::endl;
    

通过以上步骤,我们成功地利用DPC++实现了高斯滤波器,并取得了显著的性能提升。

部分代码优化

在双阈值算法连接边缘这个步骤时候,传统 CPU 代码采用的递归方式去实现,如下:

 if(imageInput.at<uchar>(i,j)>lowThreshold&&imageInput.at<uchar>(i,j)<255)
 {
     if(imageInput.at<uchar>(i-1,j-1)==255||imageInput.at<uchar>(i-1,j)==255||imageInput.at<uchar>(i-1,j+1)==255||
        imageInput.at<uchar>(i,j-1)==255||imageInput.at<uchar>(i,j)==255||imageInput.at<uchar>(i,j+1)==255||
        imageInput.at<uchar>(i+1,j-1)==255||imageInput.at<uchar>(i+1,j)==255||imageInput.at<uchar>(i+1,j+1)==255)
     {
         imageInput.at<uchar>(i,j)=255;
         DoubleThresholdLink(imageInput,lowThreshold,highThreshold); //递归调用
     }   
     else
     {
         imageInput.at<uchar>(i,j)=0;
     }   

递归虽然能够解决问题,但是需要资源较多和时间长,为了提高性能速度,将该部分代码将递归方式采用遍历方式去实现,代码如下:

 if(imageInputAccessor[i][j] > lowThreshold && imageInputAccessor[i][j] < 255)
 {
     bool hasNeighbor255 = false;
     for(int dx = -1; dx <= 1; dx++)
     {
         for(int dy = -1; dy <= 1; dy++)
         {
             if(imageInputAccessor[i + dx][ j + dy] == 255)
             {
                 hasNeighbor255 = true;
                 break;
             }
         }
         if(hasNeighbor255)
             break;
     }
     if(hasNeighbor255)
     {
         imageInputAccessor[i][j] = 255;
     }
     else
     {
         imageInputAccessor[i][j] = 0;
     }

之后在经过 DPC++ 框架进行加速计算,时间从近10万毫秒缩小到 1 毫秒左右。

canny 算法的其他步骤:用Sobel等梯度算子计算梯度幅值和方向、对梯度幅值进行非极大值抑制、用双阈值算法检测和连接边缘,都是按照上诉过程进行实现的,具体代码请看附件。

实验结果

实验设置

硬件信息:

本实验在Intel DevCloud for oneAPI上完成,以下为该平台提供的硬件详细信息。 CPU——Intel(R) Xeon(R) Platinum 8480+ 内核数:56 线程数:112 最大睿频频率:3.80GHz 处理器基本频率:2.00GHz 缓存:105MB 英特尔®UPI Speed16GT/s UPI链接数:4 TDP:350W 最大内存大小:4TB 最大内存通道数:8

GPU——Intel(R) Data Center GPU Max 1100: 微架构:Xe-HPC Xe内核数:56 XMX引擎数:448 X矢量引擎数:448 显卡最大动态频率:1550MHz 显卡基本频率:1000MHz TDP:300W PCI Express配置:Gen 5 x 16 专用高带宽内存:48GB 内存类型:HBM2e 显卡内存接口:1024bit 显卡内存带宽:1228.8GB/s

软件信息:

操作系统:Ubuntu 20.04.6 LTS 编译器:gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.2) 开发语言:C++ MPI库:Intel(R) MPI Library for Linux* OS, Version 2021.10 Build 20230619

性能评估

照片1(1870 X 1870 X 3):

DPC++加速 单核 加速比
高斯滤波 24.983毫秒 1038.55 毫秒 41.57
Sobel算子计算梯度和方向 64.4015毫秒 120.455 毫秒 1.87
计算Sobel的X和Y方向梯度幅值 1.12026毫秒 66.5503 毫秒 59.377
局部极大值抑制 44.9236毫秒 270.987 毫秒 6.03
双阈值处理 0.519428毫秒 43.3463 毫秒 84.99
双阈值中间像素连接处理 0.9853毫秒 271568 毫秒 277110
总时间 136.93 毫秒 273107.89 毫秒 1944.46

照片2(1591 X 1621 X 3):

DPC++加速 单核 加速比
高斯滤波 21.256毫秒 777.92毫秒 36.59765053
Sobel算子计算梯度和方向 3.051毫秒 88.64毫秒 29.05215165
计算Sobel的X和Y方向梯度幅值 0.782毫秒 52.91毫秒 67.67494091
局部极大值抑制 13.001毫秒 202.95毫秒 15.61088548
双阈值处理 0.437毫秒 27.18毫秒 62.15290402
双阈值中间像素连接处理 0.781毫秒 124941.00毫秒 159973.0094
总时间 39.308毫秒 126090.60毫秒 3207.775995

照片3(2469 X 718 X 3):

DPC++加速 单核 加速比
高斯滤波 33.791毫秒 539.45毫秒 15.96452289
Sobel算子计算梯度和方向 25.948毫秒 62.16毫秒 2.395473237
计算Sobel的X和Y方向梯度幅值 0.392毫秒 39.39毫秒 100.3856349
局部极大值抑制 29.396毫秒 146.67毫秒 4.989454204
双阈值处理 0.226毫秒 31.00毫秒 136.8776852
双阈值中间像素连接处理 8.717毫秒 73188.30毫秒 8395.868847
总时间 98.470毫秒 74006.96毫秒 751.5664029

照片4(2560 X 1440 X3):

DPC++加速 单核 加速比
高斯滤波 26.611毫秒 1100.34毫秒 41.34984311
Sobel算子计算梯度和方向 3.125毫秒 130.91毫秒 41.89496256
计算Sobel的X和Y方向梯度幅值 0.996毫秒 64.93毫秒 65.19959028
局部极大值抑制 9.054毫秒 286.99毫秒 31.69620522
双阈值处理 0.563毫秒 42.53毫秒 75.574634
双阈值中间像素连接处理 1.052毫秒 75631.70毫秒 71896.66809
总时间 41.400毫秒 77257.38毫秒 1866.128642

照片5(2067 X 1763 X 3):

DPC++加速 单核 加速比
高斯滤波 27.402毫秒 1068.97毫秒 39.01136799
Sobel算子计算梯度和方向 3.325毫秒 119.14毫秒 35.83011537
计算Sobel的X和Y方向梯度幅值 0.986毫秒 62.57毫秒 63.44756255
局部极大值抑制 8.673毫秒 252.98毫秒 29.16801662
双阈值处理 0.500毫秒 49.30毫秒 98.52930596
双阈值中间像素连接处理 0.924毫秒 164764.00毫秒 178233.0734
总时间 41.811毫秒 166316.96毫秒 3977.857197

实现代码

%%writefile canny.cpp
#include "opencv2/core.hpp"  
#include "opencv2/highgui.hpp"  
#include "opencv2/imgproc.hpp"  
#include "iostream"
#include "math.h"
#include <CL/sycl.hpp>

using namespace sycl;
using namespace std; 
using namespace cv;  
 
//******************灰度转换函数*************************
//第一个参数image输入的彩色RGB图像;
//第二个参数imageGray是转换后输出的灰度图像;
//*************************************************************
void ConvertRGB2GRAY(const Mat &image,Mat &imageGray);
 
 
//******************高斯卷积核生成函数*************************
//第一个参数gaus是一个指向含有N个double类型数组的指针;
//第二个参数size是高斯卷积核的尺寸大小;
//第三个参数sigma是卷积核的标准差
//*************************************************************
void GetGaussianKernel(double **gaus, const int size,const double sigma);
 
//******************高斯滤波*************************
//第一个参数imageSource是待滤波原始图像;
//第二个参数imageGaussian是滤波后输出图像;
//第三个参数gaus是一个指向含有N个double类型数组的指针;
//第四个参数size是滤波核的尺寸
//*************************************************************
void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size);
void GaussianFilterSYCL(const Mat imageSource,Mat &imageGaussian,double **gaus,int size);
//******************Sobel算子计算梯度和方向********************
//第一个参数imageSourc原始灰度图像;
//第二个参数imageSobelX是X方向梯度图像;
//第三个参数imageSobelY是Y方向梯度图像;
//第四个参数pointDrection是梯度方向数组指针
//*************************************************************
void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection);
void SobelGradDirctionSYCL(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection);
 
//******************计算Sobel的X和Y方向梯度幅值*************************
//第一个参数imageGradX是X方向梯度图像;
//第二个参数imageGradY是Y方向梯度图像;
//第三个参数SobelAmpXY是输出的X、Y方向梯度图像幅值
//*************************************************************
void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY);
void SobelAmplitudeSYCL(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY);
//******************局部极大值抑制*************************
//第一个参数imageInput输入的Sobel梯度图像;
//第二个参数imageOutPut是输出的局部极大值抑制图像;
//第三个参数pointDrection是图像上每个点的梯度方向数组指针
//*************************************************************
void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection);
void LocalMaxValueSYCL(const Mat imageInput,Mat &imageOutput,double *pointDrection);
//******************双阈值处理*************************
//第一个参数imageInput输入和输出的的Sobel梯度幅值图像;
//第二个参数lowThreshold是低阈值
//第三个参数highThreshold是高阈值
//******************************************************
void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold);
void DoubleThresholdSYCL(Mat &imageIput,double lowThreshold,double highThreshold);
//******************双阈值中间像素连接处理*********************
//第一个参数imageInput输入和输出的的Sobel梯度幅值图像;
//第二个参数lowThreshold是低阈值
//第三个参数highThreshold是高阈值
//*************************************************************
void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold);
 
Mat imageSource;
Mat imageGray;
Mat imageGaussian;
 
int main(int argc,char *argv[])  
{
	imageSource=imread("./input_image/input_image.jpg");  //读入RGB图像
	//imshow("RGB Image",imageSource);
    imwrite("RGB_Image.jpg", imageSource);
	ConvertRGB2GRAY(imageSource,imageGray); //RGB转换为灰度图
	//imshow("Gray Image",imageGray);
    imwrite("Gray_Image.jpg", imageGray);
	int size=5; //定义卷积核大小
	double **gaus=new double *[size];  //卷积核数组
	for(int i=0;i<size;i++)
	{
		gaus[i]=new double[size];  //动态生成矩阵
	}	
	GetGaussianKernel(gaus,5,1); //生成5*5 大小高斯卷积核,Sigma=1;
	imageGaussian=Mat::zeros(imageGray.size(),CV_8UC1);
	//GaussianFilter(imageGray,imageGaussian,gaus,5);  //高斯滤波
	GaussianFilterSYCL(imageGray,imageGaussian,gaus,5);  //高斯滤波
    //imshow("Gaussian Image",imageGaussian);
    //imwrite("Gaussian_Image.jpg", imageGaussian);
	Mat imageSobelY;
	Mat imageSobelX;
	double *pointDirection=new double[(imageSobelX.cols)*(imageSobelX.rows)];  //定义梯度方向角数组
	//SobelGradDirction(imageGaussian,imageSobelX,imageSobelY,pointDirection);  //计算X、Y方向梯度和方向角
	SobelGradDirctionSYCL(imageGaussian,imageSobelX,imageSobelY,pointDirection);
    //imshow("Sobel Y",imageSobelY);
    //imwrite("Sobel_Y.jpg", imageSobelY);
	//imshow("Sobel X",imageSobelX);
    //imwrite("Sobel_X.jpg", imageSobelX);
	Mat SobelGradAmpl;
    SobelGradAmpl=Mat::zeros(imageSobelX.size(),CV_32FC1);
	//SobelAmplitude(imageSobelX,imageSobelY,SobelGradAmpl);   //计算X、Y方向梯度融合幅值
    SobelAmplitudeSYCL(imageSobelX,imageSobelY,SobelGradAmpl);
	//imshow("Soble XYRange",SobelGradAmpl);
    //imwrite("Soble_XYRange.jpg", SobelGradAmpl);
	Mat imageLocalMax;
	//LocalMaxValue(SobelGradAmpl,imageLocalMax,pointDirection);  //局部非极大值抑制
    LocalMaxValueSYCL(SobelGradAmpl,imageLocalMax,pointDirection);
	//imshow("Non-Maximum Image",imageLocalMax);
    imwrite("Non-Maximum_Image.jpg", imageLocalMax);
	Mat cannyImage;
	cannyImage=Mat::zeros(imageLocalMax.size(),CV_8UC1);
	//DoubleThreshold(imageLocalMax,90,160);        //双阈值处理
    DoubleThresholdSYCL(imageLocalMax,90,160);
	//imshow("Double Threshold Image",imageLocalMax);
    //imwrite("Double_Threshold_Image.jpg", imageLocalMax);
	DoubleThresholdLink(imageLocalMax,90,160);   //双阈值中间阈值滤除及连接
	//imshow("Canny Image",imageLocalMax);
    imwrite("Canny_Image.jpg", imageLocalMax);
	waitKey();
	return 0;
}
 
//******************高斯卷积核生成函数*************************
//第一个参数gaus是一个指向含有N个double类型数组的指针;
//第二个参数size是高斯卷积核的尺寸大小;
//第三个参数sigma是卷积核的标准差
//*************************************************************
void GetGaussianKernel(double **gaus, const int size,const double sigma)
{
	const double PI=4.0*atan(1.0); //圆周率π赋值
	int center=size/2;
	double sum=0;
	for(int i=0;i<size;i++)
	{
		for(int j=0;j<size;j++)
		{
			gaus[i][j]=(1/(2*PI*sigma*sigma))*exp(-((i-center)*(i-center)+(j-center)*(j-center))/(2*sigma*sigma));
			sum+=gaus[i][j];
		}
	}
	for(int i=0;i<size;i++)
	{
		for(int j=0;j<size;j++)
		{
			gaus[i][j]/=sum;
			cout<<gaus[i][j]<<"  ";
		}
	}
    cout<<std::endl;
	return ;
}
 
//******************灰度转换函数*************************
//第一个参数image输入的彩色RGB图像;
//第二个参数imageGray是转换后输出的灰度图像;
//*************************************************************
void ConvertRGB2GRAY(const Mat &image,Mat &imageGray)
{
	if(!image.data||image.channels()!=3)
	{
		return ;
	}
	imageGray=Mat::zeros(image.size(),CV_8UC1);
	uchar *pointImage=image.data;
	uchar *pointImageGray=imageGray.data;
	int stepImage=image.step;
	int stepImageGray=imageGray.step;
	for(int i=0;i<imageGray.rows;i++)
	{
		for(int j=0;j<imageGray.cols;j++)
		{
			pointImageGray[i*stepImageGray+j]=0.114*pointImage[i*stepImage+3*j]+0.587*pointImage[i*stepImage+3*j+1]+0.299*pointImage[i*stepImage+3*j+2];
		}
	}
}
 
//******************高斯滤波*************************
//第一个参数imageSource是待滤波原始图像;
//第二个参数imageGaussian是滤波后输出图像;
//第三个参数gaus是一个指向含有N个double类型数组的指针;
//第四个参数size是滤波核的尺寸
//*************************************************************
void GaussianFilter(const Mat imageSource,Mat &imageGaussian,double **gaus,int size)
{
	imageGaussian=Mat::zeros(imageSource.size(),CV_8UC1);
	if(!imageSource.data||imageSource.channels()!=1)
	{
		return ;
	}
	double gausArray[100]; 
	for(int i=0;i<size*size;i++)
	{
		gausArray[i]=0;  //赋初值,空间分配
	}
	int array=0;
	for(int i=0;i<size;i++)
	{
		for(int j=0;j<size;j++)
 
		{
			gausArray[array]=gaus[i][j];//二维数组到一维 方便计算
			array++;
		}
	}
	//滤波
	for(int i=0;i<imageSource.rows;i++)
	{
		for(int j=0;j<imageSource.cols;j++)
		{
			int k=0;
			for(int l=-size/2;l<=size/2;l++)
			{
				for(int g=-size/2;g<=size/2;g++)
				{
					//以下处理针对滤波后图像边界处理,为超出边界的值赋值为边界值
					int row=i+l;
					int col=j+g;
					row=row<0?0:row;
					row=row>=imageSource.rows?imageSource.rows-1:row;
					col=col<0?0:col;
					col=col>=imageSource.cols?imageSource.cols-1:col;
					//卷积和
					imageGaussian.at<uchar>(i,j)+=gausArray[k]*imageSource.at<uchar>(row,col);
					k++;
				}
			}
		}
	}
}

void GaussianFilterSYCL(const Mat imageSource,Mat &imageGaussian,double **gaus,int size)
{
    imageGaussian=Mat::zeros(imageSource.size(),CV_8UC1);
    double gausArray[100]; 
	for(int i=0;i<size*size;i++)
	{
		gausArray[i]=0;  //赋初值,空间分配
	}
	int array=0;
	for(int i=0;i<size;i++)
	{
		for(int j=0;j<size;j++)
 
		{
			gausArray[array]=gaus[i][j];//二维数组到一维 方便计算
			array++;
		}
	}
	 // 初始化SYCL环境
    auto propList = property_list{property::queue::enable_profiling()};
    queue q(propList);
    range<2> global(imageSource.rows, imageSource.cols);
    auto image_width=imageSource.rows;
    auto image_height=imageSource.cols;
    // 定义一个buffer来存储图像数据和高斯数组
    buffer<uchar, 2> imageSourceBuffer(imageSource.data, global);
    buffer<uchar, 2> imageGaussianBuffer(imageGaussian.data, global);
    buffer<double, 1> gausBuffer(gausArray, range<1>(size*size));
    
    auto event =q.submit([&](handler &h) {
        // 获取缓冲区访问权限
        auto sourceAccessor = imageSourceBuffer.get_access<access::mode::read_write>(h);
        auto gaussianAccessor = imageGaussianBuffer.get_access(h);
        auto gausAccessor = gausBuffer.get_access(h);

        h.parallel_for(global, [=](item<2> item) {
            int i = item.get_id(0);
            int j = item.get_id(1);
            int k=0;
                for (int l = -size / 2; l <= size / 2; l++) {
                    for (int g = -size / 2; g <= size / 2; g++) {
                        int row = i + l;
                        int col = j + g;
                        row=row<0?0:row;
                        row=row>=image_width?image_width-1:row;
                        col=col<0?0:col;
                        col=col>=image_height?image_height-1:col;
                        gaussianAccessor[i][j] += gausAccessor[k] * sourceAccessor[row][col];
                        k++;
                    }
                }
        });
    });
    q.wait();
    double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
    // 报告执行时间
    std::cout << "GaussianFilterSYCL function time...: " << gpu_time_ns / 1000000 << "milliseconds" << std::endl;
}
//******************Sobel算子计算X、Y方向梯度和梯度方向角********************
//第一个参数imageSourc原始灰度图像;
//第二个参数imageSobelX是X方向梯度图像;
//第三个参数imageSobelY是Y方向梯度图像;
//第四个参数pointDrection是梯度方向角数组指针
//*************************************************************
void SobelGradDirctionSYCL(Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection)
{
	pointDrection=new double[(imageSource.rows-1)*(imageSource.cols-1)];
	for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++)
	{
		pointDrection[i]=0;
	}
	imageSobelX=Mat::zeros(imageSource.size(),CV_32SC1);
	imageSobelY=Mat::zeros(imageSource.size(),CV_32SC1);
 
	int step=imageSource.step;  
	int stepXY=imageSobelX.step; 

    uchar *P=imageSource.data;  
	uchar *PX=imageSobelX.data;  
	uchar *PY=imageSobelY.data;
    // 初始化SYCL环境
    auto propList = property_list{property::queue::enable_profiling()};
    queue q(propList);
    range<2> global(imageSource.rows, imageSource.cols);
    auto image_width=imageSource.rows;
    auto image_height=imageSource.cols;
    auto image_size=image_width*image_height;
    // 定义一个buffer来存储图像数据和高斯数组
    buffer<uchar, 1> imageSourceBuffer(P, range<1> (image_width*image_height));
    buffer imageSobelXBuffer(PX, range<1> (image_width*image_height));
    buffer imageSobelYBuffer(PY, range<1> (image_width*image_height));
    buffer<double, 1> pointDrectionBuffer(pointDrection, range<1>(image_width*image_height));
    
     auto event =q.submit([&](handler &h) {
        // 获取缓冲区访问权限
        auto sourceAccessor = imageSourceBuffer.get_access(h);
        auto imageSobelXAccessor = imageSobelXBuffer.get_access(h);
        auto imageSobelYAccessor = imageSobelYBuffer.get_access(h);
        auto pointDrectionAccessor = pointDrectionBuffer.get_access(h);

        h.parallel_for(range<2>(image_width-2,image_height-2), [=](item<2> item) {
           int i = item.get_id(0)+1;
           int j = item.get_id(1)+1;
            //通过指针遍历图像上每一个像素 
			//double gradX=sourceAccessor[i-1][j+1]+sourceAccessor[i][j+1]*2+sourceAccessor[i+1][j+1]-sourceAccessor[i-1][j-1]-sourceAccessor[i][j-1]*2-sourceAccessor[i+1][j-1];
            double gradY=sourceAccessor[(i-1)*step+j+1]+sourceAccessor[i*step+j+1]*2+sourceAccessor[(i+1)*step+j+1]-sourceAccessor[(i-1)*step+j-1]-sourceAccessor[i*step+j-1]*2-sourceAccessor[(i+1)*step+j-1];
			imageSobelYAccessor[i*stepXY+j*(stepXY/step)]=abs(gradY);
            //double gradY=sourceAccessor[i+1][j-1]+sourceAccessor[i+1][j]*2+sourceAccessor[i+1][j+1]-sourceAccessor[i-1][j-1]-sourceAccessor[i-1][j]*2-sourceAccessor[i-1][j+1];
            double gradX=sourceAccessor[(i+1)*step+j-1]+sourceAccessor[(i+1)*step+j]*2+sourceAccessor[(i+1)*step+j+1]-sourceAccessor[(i-1)*step+j-1]-sourceAccessor[(i-1)*step+j]*2-sourceAccessor[(i-1)*step+j+1];
            imageSobelXAccessor[i*stepXY+j*(stepXY/step)]=abs(gradX);
			if(gradX==0)
			{
				gradX=0.00000000000000001;  //防止除数为0异常
			}
			pointDrectionAccessor[(i-1)*(image_height-1)+j-1]=atan(gradY/gradX)*57.3;//弧度转换为度
			pointDrectionAccessor[(i-1)*(image_height-1)+j-1]+=90;
        });
    });
    q.wait();
    double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
    // 报告执行时间
    std::cout << "SobelGradDirctionSYCL function time...: " << gpu_time_ns / 1000000 << "milliseconds" << std::endl;
	convertScaleAbs(imageSobelX,imageSobelX);
	convertScaleAbs(imageSobelY,imageSobelY);
}
void SobelGradDirction(const Mat imageSource,Mat &imageSobelX,Mat &imageSobelY,double *&pointDrection)
{
	pointDrection=new double[(imageSource.rows-1)*(imageSource.cols-1)];
	for(int i=0;i<(imageSource.rows-1)*(imageSource.cols-1);i++)
	{
		pointDrection[i]=0;
	}
	imageSobelX=Mat::zeros(imageSource.size(),CV_32SC1);
	imageSobelY=Mat::zeros(imageSource.size(),CV_32SC1);
	uchar *P=imageSource.data;  
	uchar *PX=imageSobelX.data;  
	uchar *PY=imageSobelY.data;  
 
	int step=imageSource.step;  
	int stepXY=imageSobelX.step; 
	int k=0;
	int m=0;
	int n=0;
	for(int i=1;i<(imageSource.rows-1);i++)  
	{  
		for(int j=1;j<(imageSource.cols-1);j++)  
		{  
			//通过指针遍历图像上每一个像素 
			double gradY=P[(i-1)*step+j+1]+P[i*step+j+1]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[i*step+j-1]*2-P[(i+1)*step+j-1];
			PY[i*stepXY+j*(stepXY/step)]=abs(gradY);
			double gradX=P[(i+1)*step+j-1]+P[(i+1)*step+j]*2+P[(i+1)*step+j+1]-P[(i-1)*step+j-1]-P[(i-1)*step+j]*2-P[(i-1)*step+j+1];
            PX[i*stepXY+j*(stepXY/step)]=abs(gradX);
			if(gradX==0)
			{
				gradX=0.00000000000000001;  //防止除数为0异常
			}
			pointDrection[k]=atan(gradY/gradX)*57.3;//弧度转换为度
			pointDrection[k]+=90;
			k++;
		}  
	} 
	convertScaleAbs(imageSobelX,imageSobelX);
	convertScaleAbs(imageSobelY,imageSobelY);
}
//******************计算Sobel的X和Y方向梯度幅值*************************
//第一个参数imageGradX是X方向梯度图像;
//第二个参数imageGradY是Y方向梯度图像;
//第三个参数SobelAmpXY是输出的X、Y方向梯度图像幅值
//*************************************************************
void SobelAmplitudeSYCL(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY)
{

      // 初始化SYCL环境
    auto propList = property_list{property::queue::enable_profiling()};
    queue q(propList);
    range<2> global(SobelAmpXY.rows, SobelAmpXY.cols);
    auto image_width=SobelAmpXY.rows;
    auto image_height=SobelAmpXY.cols;
    auto image_size=image_width*image_height;
    // 定义一个buffer来存储图像数据和高斯数组
    buffer<float,2> SobelAmpXYBuffer(SobelAmpXY.ptr<float>(), global);
    buffer imageGradXBuffer(imageGradX.data, global);
    buffer imageGradYBuffer(imageGradY.data, global);
   
	 auto event =q.submit([&](handler &h) {
        // 获取缓冲区访问权限
        auto imageGradXAccessor = imageGradXBuffer.get_access(h);
        auto imageGradYAccessor = imageGradYBuffer.get_access(h);
        auto SobelAmpXYAccessor = SobelAmpXYBuffer.get_access(h);

        h.parallel_for(global, [=](item<2> item) {
            int i = item.get_id(0);
            int j = item.get_id(1);
            SobelAmpXYAccessor[i][j]=sqrt(imageGradXAccessor[i][j]*imageGradXAccessor[i][j]+imageGradYAccessor[i][j]*imageGradYAccessor[i][j]);
        });
    });
    q.wait();
    double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
    // 报告执行时间
    std::cout << "SobelAmplitudeSYCL function time...: " << gpu_time_ns / 1000000 << "milliseconds" << std::endl;
	convertScaleAbs(SobelAmpXY,SobelAmpXY);
}
void SobelAmplitude(const Mat imageGradX,const Mat imageGradY,Mat &SobelAmpXY)
{
	SobelAmpXY=Mat::zeros(imageGradX.size(),CV_32FC1);
	for(int i=0;i<SobelAmpXY.rows;i++)
	{
		for(int j=0;j<SobelAmpXY.cols;j++)
		{
			SobelAmpXY.at<float>(i,j)=sqrt(imageGradX.at<uchar>(i,j)*imageGradX.at<uchar>(i,j)+imageGradY.at<uchar>(i,j)*imageGradY.at<uchar>(i,j));
		}
	}
	convertScaleAbs(SobelAmpXY,SobelAmpXY);
}
//******************局部极大值抑制*************************
//第一个参数imageInput输入的Sobel梯度图像;
//第二个参数imageOutPut是输出的局部极大值抑制图像;
//第三个参数pointDrection是图像上每个点的梯度方向数组指针
//*************************************************************
void LocalMaxValueSYCL(const Mat imageInput,Mat &imageOutput,double *pointDrection)
{
	imageOutput=imageInput.clone();
    
     // 初始化SYCL环境
    auto propList = property_list{property::queue::enable_profiling()};
    queue q(propList);
    range<2> global(imageInput.rows, imageInput.cols);
    auto image_width=imageInput.rows;
    auto image_height=imageInput.cols;
    auto image_size=(image_width-1)*(image_height-1);
    // 定义一个buffer来存储图像数据和高斯数组
    buffer imageInputBuffer(imageInput.data, global);
    buffer imageOutputBuffer(imageOutput.data, global);
    buffer<double> pointDrectionBuffer(pointDrection, range<1>(image_size));
    auto event =q.submit([&](handler &h) {
        // 获取缓冲区访问权限
        auto imageInputAccessor = imageInputBuffer.get_access(h);
        auto imageOutputAccessor = imageOutputBuffer.get_access(h);
        auto pointDrectionAccessor = pointDrectionBuffer.get_access(h);

        h.parallel_for(range<2>(image_width-2,image_height-2), [=](item<2> item) {
            int i = item.get_id(0)+1;
            int j = item.get_id(1)+1;
                
            
            int value00=imageInputAccessor[i-1][j-1];
			int value01=imageInputAccessor[i-1][j];
			int value02=imageInputAccessor[i-1][j+1];
			int value10=imageInputAccessor[i][j-1];
			int value11=imageInputAccessor[i][j];
			int value12=imageInputAccessor[i][j+1];
			int value20=imageInputAccessor[i+1][j-1];
			int value21=imageInputAccessor[i+1][j];
			int value22=imageInputAccessor[i+1][j+1];
 
			if(pointDrectionAccessor[(i-1)*(image_height-1)+j-1]>0&&pointDrectionAccessor[(i-1)*(image_height-1)+j-1]<=45)
			{
				if(value11<=(value12+(value02-value12)*tan(pointDrectionAccessor[i*image_width+j])||(value11<=(value10+(value20-value10)*tan(pointDrectionAccessor[i*image_width+j])))))
				{
					imageOutputAccessor[i][j]=0;
				}
			}	
			if(pointDrectionAccessor[(i-1)*(image_height-1)+j-1]>45&&pointDrectionAccessor[(i-1)*(image_height-1)+j-1]<=90)
 
			{
				if(value11<=(value01+(value02-value01)/tan(pointDrectionAccessor[i*image_height+j]))||value11<=(value21+(value20-value21)/tan(pointDrectionAccessor[i*image_height+j])))
				{
					imageOutputAccessor[i][j]=0;
 
				}
			}
			if(pointDrectionAccessor[(i-1)*(image_height-1)+j-1]>90&&pointDrectionAccessor[(i-1)*(image_height-1)+j-1]<=135)
			{
				if(value11<=(value01+(value00-value01)/tan(180-pointDrectionAccessor[i*image_height+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrectionAccessor[i*image_height+j])))
				{
					imageOutputAccessor[i][j]=0;
				}
			}
			if(pointDrectionAccessor[(i-1)*(image_height-1)+j-1]>135&&pointDrectionAccessor[(i-1)*(image_height-1)+j-1]<=180)
			{
				if(value11<=(value10+(value00-value10)*tan(180-pointDrectionAccessor[i*image_height+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrectionAccessor[i*image_height+j])))
				{
					imageOutputAccessor[i][j]=0;
				}
			}
        });
    });
    q.wait();
    double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
    // 报告执行时间
    std::cout << "LocalMaxValueSYCL function time...: " << gpu_time_ns / 1000000 << "milliseconds" << std::endl;

}
void LocalMaxValue(const Mat imageInput,Mat &imageOutput,double *pointDrection)
{
	//imageInput.copyTo(imageOutput);
	imageOutput=imageInput.clone();
	int k=0;
	for(int i=1;i<imageInput.rows-1;i++)
	{
		for(int j=1;j<imageInput.cols-1;j++)
		{
			int value00=imageInput.at<uchar>((i-1),j-1);
			int value01=imageInput.at<uchar>((i-1),j);
			int value02=imageInput.at<uchar>((i-1),j+1);
			int value10=imageInput.at<uchar>((i),j-1);
			int value11=imageInput.at<uchar>((i),j);
			int value12=imageInput.at<uchar>((i),j+1);
			int value20=imageInput.at<uchar>((i+1),j-1);
			int value21=imageInput.at<uchar>((i+1),j);
			int value22=imageInput.at<uchar>((i+1),j+1);
 
			if(pointDrection[k]>0&&pointDrection[k]<=45)
			{
				if(value11<=(value12+(value02-value12)*tan(pointDrection[i*imageOutput.rows+j]))||(value11<=(value10+(value20-value10)*tan(pointDrection[i*imageOutput.rows+j]))))
				{
					imageOutput.at<uchar>(i,j)=0;
				}
			}	
			if(pointDrection[k]>45&&pointDrection[k]<=90)
 
			{
				if(value11<=(value01+(value02-value01)/tan(pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value20-value21)/tan(pointDrection[i*imageOutput.cols+j])))
				{
					imageOutput.at<uchar>(i,j)=0;
 
				}
			}
			if(pointDrection[k]>90&&pointDrection[k]<=135)
			{
				if(value11<=(value01+(value00-value01)/tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value21+(value22-value21)/tan(180-pointDrection[i*imageOutput.cols+j])))
				{
					imageOutput.at<uchar>(i,j)=0;
				}
			}
			if(pointDrection[k]>135&&pointDrection[k]<=180)
			{
				if(value11<=(value10+(value00-value10)*tan(180-pointDrection[i*imageOutput.cols+j]))||value11<=(value12+(value22-value11)*tan(180-pointDrection[i*imageOutput.cols+j])))
				{
					imageOutput.at<uchar>(i,j)=0;
				}
			}
			k++;
		}
	}
}
 
//******************双阈值处理*************************
//第一个参数imageInput输入和输出的的Sobel梯度幅值图像;
//第二个参数lowThreshold是低阈值
//第三个参数highThreshold是高阈值
//******************************************************
void DoubleThresholdSYCL(Mat &imageInput,double lowThreshold,double highThreshold)
{
      // 初始化SYCL环境
    auto propList = property_list{property::queue::enable_profiling()};
    queue q(propList);
    range<2> global(imageInput.rows, imageInput.cols);
    auto image_width=imageInput.rows;
    auto image_height=imageInput.cols;
    auto image_size=image_width*image_height;
    // 定义一个buffer来存储图像数据和高斯数组
    buffer imageInputBuffer(imageInput.data, global);
    
    auto event =q.submit([&](handler &h) {
        // 获取缓冲区访问权限
        auto imageInputAccessor = imageInputBuffer.get_access(h);

        h.parallel_for(global, [=](item<2> item) {
            int i = item.get_id(0);
            int j = item.get_id(1);
            if(imageInputAccessor[i][j]>highThreshold)
			{
				imageInputAccessor[i][j]=255;
			}	
			if(imageInputAccessor[i][j]<lowThreshold)
			{
				imageInputAccessor[i][j]=0;
			}	
        });
    });
    q.wait();
    double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
    // 报告执行时间
    std::cout << "DoubleThresholdSYCL function time...: " << gpu_time_ns / 1000000 << "milliseconds" << std::endl;
}
void DoubleThreshold(Mat &imageIput,double lowThreshold,double highThreshold)
{
	for(int i=0;i<imageIput.rows;i++)
	{
		for(int j=0;j<imageIput.cols;j++)
		{
			if(imageIput.at<uchar>(i,j)>highThreshold)
			{
				imageIput.at<uchar>(i,j)=255;
			}	
			if(imageIput.at<uchar>(i,j)<lowThreshold)
			{
				imageIput.at<uchar>(i,j)=0;
			}	
		}
	}
}
//******************双阈值中间像素连接处理*********************
//第一个参数imageInput输入和输出的的Sobel梯度幅值图像;
//第二个参数lowThreshold是低阈值
//第三个参数highThreshold是高阈值
//*************************************************************
void DoubleThresholdLink(Mat &imageInput,double lowThreshold,double highThreshold)
{
      // 初始化SYCL环境
    auto propList = property_list{property::queue::enable_profiling()};
    queue q(propList);
    range<2> global(imageInput.rows, imageInput.cols);
    auto image_width=imageInput.rows;
    auto image_height=imageInput.cols;
    auto image_size=image_width*image_height;
    // 定义一个buffer来存储图像数据和高斯数组
    buffer imageInputBuffer(imageInput.data, global);
    
     auto event =q.submit([&](handler &h) {
        // 获取缓冲区访问权限
        auto imageInputAccessor = imageInputBuffer.get_access(h);

        h.parallel_for(range<2>(image_width-2,image_height-2), [=](item<2> item) {
            int i = item.get_id(0)+1;
            int j = item.get_id(1)+1;
           	if(imageInputAccessor[i][j] > lowThreshold && imageInputAccessor[i][j] < 255)
            {
                bool hasNeighbor255 = false;
                for(int dx = -1; dx <= 1; dx++)
                {
                    for(int dy = -1; dy <= 1; dy++)
                    {
                        if(imageInputAccessor[i + dx][ j + dy] == 255)
                        {
                            hasNeighbor255 = true;
                            break;
                        }
                    }
                    if(hasNeighbor255)
                        break;
                }
                if(hasNeighbor255)
                {
                    imageInputAccessor[i][j] = 255;
                }
                else
                {
                    imageInputAccessor[i][j] = 0;
                }
            }
        });
    });
    q.wait();
    double gpu_time_ns = event.get_profiling_info<info::event_profiling::command_end>() - event.get_profiling_info<info::event_profiling::command_start>();
    // 报告执行时间
    std::cout << "DoubleThresholdLink function time...: " << gpu_time_ns / 1000000 << "milliseconds" << std::endl;
}
 

参考博客

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

Mediapipe人像抠图实战:Selfie Segmentation手把手教程

2023-11-21 12:21:14

AI教程

C4.5算法深度解析:原理、实现流程、应用场景

2023-11-21 12:27:00

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索