diff --git a/.gitignore b/.gitignore index 064a80d..4e83edc 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,4 @@ *.py *.txt -/okano_3_0 diff --git a/okano_3_0/AffineTrans.cu b/okano_3_0/AffineTrans.cu new file mode 100644 index 0000000..bcd2821 --- /dev/null +++ b/okano_3_0/AffineTrans.cu @@ -0,0 +1,557 @@ +// AffineTrans.cu +// 实现图像的旋转变换 + +#include "AffineTrans.h" + +#include +#include +#include +using namespace std; + +#include +#include +#include +#include + +#include "ErrorCode.h" +#include "FanczosIpl.h" + +// 宏:M_PI +// π值。对于某些操作系统,M_PI可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// 结构体:AffineTransParam(旋转仿射变换的内部参数) +// 该结构体定义了旋转仿射变换的内部参数。它的作用在于简化参数传递的形式,在调用 +// 算法函数的时候,Host 代码会首先根据类的成员变量和用户的参数计算出这个形似的 +// 内部参数,然后再将这个内部参数传递给 Kernel,进而由 Kernel 完成对图像的并行 +// 处理。 +typedef struct AffineTransParam_st { + float x0, y0; // 旋转前平移的向量 + float cosalpha, sinalpha; // 旋转角度对应的余弦和正弦值 + float x1, y1; // 旋转后平移的向量 +} AffineTransParam; + +// 全局变量:_hardIplInimgTex(作为输入图像的纹理内存引用) +// 纹理内存只能用于全局变量,因此将硬件插值的旋转变换的 Kernel 函数的输入图像列 +// 于此处。 +static texture _hardIplInimgTex; + +// Kernel 函数:_hardRotateKer(利用硬件插值实现的旋转变换) +// 利用纹理内存提供的硬件插值功能,实现的并行旋转变换。没有输入图像的参数,是因 +// 为输入图像通过纹理内存来读取数据,纹理内存只能声明为全局变量。 +static __global__ void // Kernel 函数无返回值。 +_hardRotateKer( + ImageCuda outimg, // 输出图像 + AffineTransParam param // 旋转变换的参数 +); + +// Kernel 函数:_softRotateKer(利用软件件插值实现的旋转变换) +// 利用 Fanczos 软件硬件插值算法,实现的并行旋转变换。 +static __global__ void // Kernel 函数无返回值。 +_softRotateKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + AffineTransParam param // 旋转变换的参数 +); + +// Host 函数:_rotateNpp(基于 NPP 的旋转变换实现) +// 由于调用 NPP 支持库中的函数同 Runtime API 的 CUDA Kernel 调用具有较大的差 +// 别,这里我们单独将 NPP 的旋转变换实现单独提出来作为一个函数以方便代码阅读。 +// 注意,这个函数没有对输入输出图像进行前后处理工作,因此,必须要求输入输出图像 +// 在当前 Device 上合法可用的数据空间,否则会带来不可预知的错误。 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 +_rotateNpp( + Image *inimg, // 输入图像,要求该图像必须在当前 Device 上有数 + // 据。 + Image *outimg, // 输出图像,要求该图像必须在当前 Device 上有数 + // 据。 + AffineTransParam atp // 旋转变换参数 +); + +// Host 函数:_rotateGeneral(通用旋转变换) +// 作为整个旋转仿射变换的枢纽函数,所有的上层函数调用都会汇聚于此,并游该函数分 +// 配调度下一层调度。在这个函数中包含了如下的功能:(1)对输入和输出图像进行数 +// 据的准备工作,包括申请当前 Device 存储空间等;(2)针对不同的实现类型,对图 +// 像数据进行个别的加工,如对于 NPP 实现调用 _rotateNpp 函数,对于硬插值实现调 +// 纹理内存绑定操作等。 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR。 +_rotateGeneral( + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + AffineTransParam atp, // 旋转变换参数 + int imptype // 实现方式 +); + +// 函数:_calcRotateCenterParam(按照中心点旋转计算内部参数) +// 根据给定的 AffineTrans 类,根据其中的成员变量,计算处内部的参数,内部参数随 +// 后用于调用 Kernel 函数。 +static __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR。 +_calcRotateCenterParam( + AffineTrans *at, // 输入参数,需要计算内部参数的类 + Image *inimg, // 输入参数,用于考虑 ROI 子图像的问题。 + Image *outimg, // 输出参数,用于考虑 ROI 子图像的问题。 + AffineTransParam *atp // 输出参数,转换出来的内部参数,参数中原来的数 + // 据将会被抹除。 +); + +// 函数:calcRotateShiftParam(按照平移旋转计算内部参数) +// 根据给定的 AffineTrans 类,根据其中的成员变量,计算处内部的参数,内部参数随 +// 后用于调用 Kernel 函数。 +static __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR。 +_calcRotateShiftParam( + AffineTrans *at, // 输入参数,需要计算内部参数的类 + Image *inimg, // 输入参数,用于考虑 ROI 子图像的问题。 + Image *outimg, // 输出参数,用于考虑 ROI 子图像的问题。 + AffineTransParam *atp // 输出参数,转换出来的内部参数,参数中原来的数 +); + + +// Kernel 函数:_hardRotateKer(利用硬件插值实现的旋转变换) +static __global__ void _hardRotateKer(ImageCuda outimg, AffineTransParam param) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * outimg.pitchBytes + dstc; + + // 声明目标图像输出像素对应的源图像中的坐标点,由于计算会得到小数结果,因此 + // 使用浮点型存储该做标。 + float srcc, srcr; + + // 由于是通过目标坐标点反推回源图像中的坐标点,因此这里实用的是逆向的旋转变 + // 换。首先进行的是旋转后的平移,由于是逆向操作,这里是减法。 + int tmpc = dstc - param.x1; + int tmpr = dstr - param.y1; + // 利用旋转矩阵,进行旋转变换,由于是逆向操作,这里的旋转矩阵也是正向变换的 + // 旋转矩阵的逆矩阵。最后,进行旋转前的平移,同样也是逆向操作,故用减法。 + srcc = tmpc * param.cosalpha - tmpr * param.sinalpha - param.x0; + srcr = tmpc * param.sinalpha + tmpr * param.cosalpha - param.y0; + + // 通过上面的步骤,求出了第一个输出坐标对应的源图像坐标。这里利用纹理内存的 + // 硬件插值功能,直接使用浮点型的坐标读取相应的源图像“像素”值,并赋值给目 + // 标图像。这里没有进行对源图像读取的越界检查,这是因为纹理内存硬件插值功能 + // 可以处理越界访问的情况,越界访问会按照事先的设置得到一个相对合理的像素颜 + // 色值,不会引起错误。 + outimg.imgMeta.imgData[dstidx] = tex2D(_hardIplInimgTex, srcc, srcr); + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++dstr >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + dstidx += outimg.pitchBytes; + + // 根据上一个源坐标位置计算当前的源坐标位置。由于只有 y 分量增加 1,因 + // 此,对应的源坐标只有在涉及到 dstr 的项上有变化,从而消除了一系列乘法 + // 计算,而通过两个源坐标的差值进行简单的加减法而得。 + srcc -= param.sinalpha; + srcr += param.cosalpha; + + // 将对应的源坐标位置出的插值像素写入到目标图像的当前像素点中。 + outimg.imgMeta.imgData[dstidx] = tex2D(_hardIplInimgTex, srcc, srcr); + } +} + +// Kernel 函数:_softRotateKer(利用软件插值实现的旋转变换) +static __global__ void _softRotateKer(ImageCuda inimg, ImageCuda outimg, + AffineTransParam param) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * outimg.pitchBytes + dstc; + + // 声明目标图像输出像素对应的源图像中的坐标点,由于计算会得到小数结果,因此 + // 使用浮点型存储该做标。 + float srcc, srcr; + + // 由于是通过目标坐标点反推回源图像中的坐标点,因此这里实用的是逆向的旋转变 + // 换。首先进行的是旋转后的平移,由于是逆向操作,这里是减法。 + int tmpc = dstc - param.x1; + int tmpr = dstr - param.y1; + // 利用旋转矩阵,进行旋转变换,由于是逆向操作,这里的旋转矩阵也是正向变换的 + // 旋转矩阵的逆矩阵。最后,进行旋转前的平移,同样也是逆向操作,故用减法。 + srcc = tmpc * param.cosalpha - tmpr * param.sinalpha - param.x0; + srcr = tmpc * param.sinalpha + tmpr * param.cosalpha - param.y0; + + // 调用 Fanczos 软件插值算法实现,获得源图像中对应坐标下的插值值。由于插值 + // 算法实现函数处理了越界的情况,因此这里可以安全的把一些问题丢给插值算法实 + // 现函数来处理。 + outimg.imgMeta.imgData[dstidx] = _fanczosInterpoDev(inimg, srcc, srcr); + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++dstr >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + dstidx += outimg.pitchBytes; + + // 根据上一个源坐标位置计算当前的源坐标位置。由于只有 y 分量增加 1,因 + // 此,对应的源坐标只有在涉及到 dstr 的项上有变化,从而消除了一系列乘法 + // 计算,而通过两个源坐标的差值进行简单的加减法而得。 + srcc -= param.sinalpha; + srcr += param.cosalpha; + + // 将对应的源坐标位置出的插值像素写入到目标图像的当前像素点中。 + outimg.imgMeta.imgData[dstidx] = _fanczosInterpoDev(inimg, srcc, srcr); + } +} + +// Host 函数:_rotateNpp(基于 NPP 的旋转变换实现) +static __host__ int _rotateNpp(Image *inimg, Image *outimg, + AffineTransParam atp) +{ + // 检查输入和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 获得输入输出图像对应的 ImageCuda 型指针。 + ImageCuda *inimgCud = IMAGE_CUDA(inimg); + ImageCuda *outimgCud = IMAGE_CUDA(outimg); + + NppiRect srcroi; // 输入图像的 ROI + srcroi.x = inimg->roiX1; + srcroi.y = inimg->roiY1; + srcroi.width = inimg->roiX2 - inimg->roiX1; + srcroi.height = inimg->roiY2 - inimg->roiY1; + + // 计算出 4 个基准点的坐标变换,这四个基本坐标选择了源图像 ROI 上的四个角 + // 点。 + double afquad[4][2]; // 存放 4 个基准点在目标图像中的坐标。 + // NPP 函数仅支持旋转后图像平移,因此需要根据内部参数中的旋转前平移和旋转后 + // 平移推算出整体的平移量。 + double xshift = atp.x0 * atp.cosalpha + atp.y0 * atp.sinalpha + atp.x1; + double yshift = atp.y0 * atp.cosalpha - atp.x0 * atp.sinalpha + atp.y1; + // 旋转角度,为了软件工程的美观,以及 double 类型的需求,这里是通过计算重新 + // 计算角度,当然,也可以通过改造 AffineTransParam 来减去这一计算。 + double alpha = asin(atp.sinalpha) * 180.0f / M_PI; + // 调用 NPP 函数获取 ROI 四点对应到目标图像中的坐标。 + NppStatus nppstatus; + nppstatus = nppiGetRotateQuad(srcroi, afquad, alpha, xshift, yshift); + if (nppstatus < NPP_SUCCESS) // 这里使用小于号的原因是 NPP 的错误码中,正数 + return CUDA_ERROR; // 表示无错误的警告,0 表示无错误,负数才表示 + // 真正发生了错误。 + + // 利用 NPP 函数求出旋转变换对应的仿射矩阵。利用这个矩阵可以将旋转变换的实 + // 现,转化为调用仿射函数实行仿射变换。 + double afcoeff[2][3]; + nppstatus = nppiGetAffineTransform(srcroi, afquad, afcoeff); + if (nppstatus < NPP_SUCCESS) + return CUDA_ERROR; + + // 为调用 NPP 仿射函数做一些数据准备工作。由于 NPP 很好的支持了 ROI,所以, + // 这里没有使用 ROI 子图像,而直接使用了整幅图像和 ROI 信息。 + Npp8u *psrc = (Npp8u *)(inimg->imgData); // 输入图像的指针 + Npp8u *pdst = (Npp8u *)(outimg->imgData); // 输出图像的指针 + + Npp32s srcstep = inimgCud->pitchBytes; // 输入图像的 Pitch + Npp32s dststep = outimgCud->pitchBytes; // 输出图像的 Pitch + NppiSize srcsize; // 输入图像的总尺寸 + srcsize.width = inimg->width; // 宽 + srcsize.height = inimg->height; // 高 + + NppiRect dstroi; // 输出图像的 ROI,这里输入图像的 ROI 已在前面完 + dstroi.x = outimg->roiX1; // 成了赋值,此处无需再赋值。 + dstroi.y = outimg->roiY1; + dstroi.width = outimg->roiX2 - outimg->roiX1; + dstroi.height = outimg->roiY2 - outimg->roiY1; + + int iplmode = NPPI_INTER_LINEAR; // 插值方式(这里我们采用了线性插值) + + // 调用 NPP 的仿射变换函数完成图像的旋转变换。 + nppstatus = nppiWarpAffine_8u_C1R(psrc, srcsize, srcstep, srcroi, + pdst, dststep, dstroi, + afcoeff, iplmode); + // 现已确定,现行的 NPP 版本并不算稳定,在某些 ROI 的情况下会出现莫名其妙的 + // 无法处理的情况,这时会报告 NPP_ERROR 错误码,但是这个错误码的具体含义 + // NVIDIA 并未给出一个明确的说法。显然这时由于 NPP 内部不稳定造成的。在 NPP + // 文档的第 644 页关于函数 nppiWarpAffine_8u_C1R 的介绍中并未说明该函数会产 + // 生 NPP_ERROR 的错误。希望未来的 NPP 版本可以解决不稳定的问题。 + if (nppstatus < NPP_SUCCESS) + return CUDA_ERROR; + + // 处理完毕返回。 + return NO_ERROR; +} + +// Host 函数:_rotateGeneral(通用旋转变换) +static __host__ int _rotateGeneral(Image *inimg, Image *outimg, + AffineTransParam atp, int imptype) +{ + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图像 + // 尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->width, inimg->height); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 如果实现方式为调用 NPP 支持库,由于实现方式同其他 CUDA Kernel 的实现法方 + // 法差别较大,则在此直接转入 NPP 处理函数。 + if (imptype == AFFINE_NVIDIA_LIB) + return _rotateNpp(inimg, outimg, atp); + + // 提取输入图像的 ROI 子图像。 + ImageCuda *inimgCud = IMAGE_CUDA(inimg); + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 针对不同的实现类型,选择不同的路径进行处理。 + cudaError_t cuerrcode; + switch (imptype) { + // 使用硬件插值实现旋转变换: + case AFFINE_HARD_IPL: + // 设置数据通道描述符,因为只有一个颜色通道(灰度图),因此描述符中只有 + // 第一个分量含有数据。概述据通道描述符用于纹理内存的绑定操作。 + struct cudaChannelFormatDesc chndesc; + chndesc = cudaCreateChannelDesc(sizeof (unsigned char) * 8, 0, 0, 0, + cudaChannelFormatKindUnsigned); + + // 将输入图像的 ROI 子图像绑定到纹理内存。 + cuerrcode = cudaBindTexture2D( + NULL, &_hardIplInimgTex, inimg->imgData, &chndesc, + inimg->width, inimg->height, inimgCud->pitchBytes); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + // 调用 Kernel 函数,完成实际的图像旋转变换。 + _hardRotateKer<<>>(outsubimgCud, atp); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + break; + + // 使用软件插值实现旋转变换: + case AFFINE_SOFT_IPL: + // 调用 Kernel 函数,完成实际的图像旋转变换。 + _softRotateKer<<>>(*inimgCud, outsubimgCud, + atp); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + break; + + // 其他方式情况下,直接返回非法数据错误。由于 NPP 实现已在前面跳转入了相应 + // 的其他函数,该 switch-case 语句中未包含对 NPP 实现的处理。 + default: + return INVALID_DATA; + } + + // 处理完毕,退出。 + return NO_ERROR; +} + +// 函数:calcRotateCenterParam(按照中心点旋转计算内部参数) +static __host__ __device__ int _calcRotateCenterParam( + AffineTrans *at, Image *inimg, Image *outimg, AffineTransParam *atp) +{ + // 如果两个参数都为 NULL 则报错。如果 at 为 NULL,则无法计算;如果 atp 为 + // NULL,则无法保存计算结果。 + if (at == NULL || atp == NULL || inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 获取图像旋转的中心点 + int xc = at->getX(); + int yc = at->getY(); + + // 如果旋转中心点角度在输入图像之外,则报错退出。 + if (xc < 0 || xc >= inimg->width || yc < 0 || yc >= inimg->height) + return INVALID_DATA; + + // 设置旋转前平移向量。基于中心的旋转相当于先将旋转中心移动到原点,在旋转后 + // 再将图像移动回去。 + atp->x0 = -xc; + atp->y0 = -yc; + + // 计算旋转角度的余弦和正弦的值。 + float alpharad = at->getAlpha() * M_PI / 180.0f; + atp->cosalpha = cos(alpharad); + atp->sinalpha = sin(alpharad); + + // 设置旋转后平移向量。 + atp->x1 = xc ; + atp->y1 = yc; + + // 针对 ROI 信息调整平移, AffineTrans 中的基准坐标是相对于整幅图像而言的, + // 因此这里需要进行一下差值,从相对于整幅图像的基准坐标计算得到相对于 ROI + // 子图像的坐标。注意,在 NPP 实现中,由于 NPP 具有对 ROI 的处理能力,因 + // 此,对于 NPP 实现不需要对旋转中心点作出调整。 + if (at->getImpType() != AFFINE_NVIDIA_LIB) { + // 由于输入图像不需要考虑 ROI 范围,因此,我们在实现过程中,撇开了输入 + // 图像的 ROI 区域,直接使用更加容易计算的整幅图像作为数据来源。 + //atp->x0 += inimg->roiX1; + //atp->y0 += inimg->roiY1; + atp->x1 -= outimg->roiX1; + atp->y1 -= outimg->roiY1; + } + + // 处理完毕,成功返回。 + return NO_ERROR; +} + +// Host 成员方法:rotateCenter(基于中心的旋转) +__host__ int AffineTrans::rotateCenter(Image *inimg, Image *outimg) +{ + // 检查输入和输出图像,若有一个为 NULL,则报错。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 转换参数,将类内的参数转换成 Kernel 函数使用的内部参数。 + AffineTransParam atp; + int errcode; + errcode = _calcRotateCenterParam(this, inimg, outimg, &atp); + if (errcode != NO_ERROR) { + // 如果在参数转换过程中发生错误,则记录 stateFlag 并退出。 + this->stateFlag = errcode; + return errcode; + } + + // 交由枢纽函数 _rotateGeneral 进行后续的实际旋转变换。 + errcode = _rotateGeneral(inimg, outimg, atp, this->impType); + if (errcode != NO_ERROR) { + // 如果在枢纽函数处理过程中发生错误,则记录 stateFlag 并退出。 + this->stateFlag = errcode; + return errcode; + } + + // 处理完毕退出。 + return NO_ERROR; +} + +// 函数:calcRotateShiftParam(按照平移旋转计算内部参数) +static __host__ __device__ int _calcRotateShiftParam( + AffineTrans *at, Image *inimg, Image *outimg, AffineTransParam *atp) +{ + // 如果两个参数都为 NULL 则报错。如果 at 为 NULL,则无法计算;如果 atp 为 + // NULL,则无法保存计算结果。 + if (at == NULL || atp == NULL || inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 获取图像旋转的中心点 + int xc = inimg->width / 2; + int yc = inimg->height / 2; + + // 设置旋转前平移向量。基于中心的旋转相当于先将旋转中心移动到原点,在旋转后 + // 再将图像移动回去。 + atp->x0 = -xc + at->getX(); + atp->y0 = -yc + at->getY(); + + // 计算旋转角度的余弦和正弦的值。 + float alpharad = at->getAlpha() * M_PI / 180.0f; + atp->cosalpha = cos(alpharad); + atp->sinalpha = sin(alpharad); + + // 设置旋转后平移向量。 + atp->x1 = xc ; + atp->y1 = yc; + + // 针对 ROI 信息调整平移, AffineTrans 中的基准坐标是相对于整幅图像而言的, + // 因此这里需要进行一下差值,从相对于整幅图像的基准坐标计算得到相对于 ROI + // 子图像的坐标。注意,在 NPP 实现中,由于 NPP 具有对 ROI 的处理能力,因 + // 此,对于 NPP 实现不需要对旋转中心点作出调整。 + if (at->getImpType() != AFFINE_NVIDIA_LIB) { + atp->x0 += inimg->roiX1; + atp->y0 += inimg->roiY1; + atp->x1 -= outimg->roiX1; + atp->y1 -= outimg->roiY1; + } + + // 处理完毕,成功返回。 + return NO_ERROR; +} + +// Host 成员方法:rotateShift(基于平移的旋转) +__host__ int AffineTrans::rotateShift(Image *inimg, Image *outimg) +{ + // 检查输入和输出图像,若有一个为 NULL,则报错。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 转换参数,将类内的参数转换成 Kernel 函数使用的内部参数。 + AffineTransParam atp; + int errcode; + errcode = _calcRotateShiftParam(this, inimg, outimg, &atp); + if (errcode != NO_ERROR) { + // 如果在参数转换过程中发生错误,则记录 stateFlag 并退出。 + this->stateFlag = errcode; + return errcode; + } + + // 交由枢纽函数 _rotateGeneral 进行后续的实际旋转变换。 + errcode = _rotateGeneral(inimg, outimg, atp, this->impType); + if (errcode != NO_ERROR) { + // 如果在枢纽函数处理过程中发生错误,则记录 stateFlag 并退出。 + this->stateFlag = errcode; + return errcode; + } + + // 处理完毕退出。 + return NO_ERROR; +} diff --git a/okano_3_0/AffineTrans.h b/okano_3_0/AffineTrans.h new file mode 100644 index 0000000..d14c8ad --- /dev/null +++ b/okano_3_0/AffineTrans.h @@ -0,0 +1,253 @@ +// AffineTrans.h +// 创建人:于玉龙 +// +// 旋转仿射变换(Affine Transform) +// 功能说明:实现图像的旋转变换,旋转变换不改变原有图像的尺寸,旋转后,超出输出 +// 图片的部分结果会被裁剪调。该变换具有三种不同的实现:(1)基于纹理 +// 内存的硬件插值功能实现的旋转变换;(2)使用软件实现的 Fanczos 插值 +// 实现的旋转变换;(3)封装 NPP 支持库实现的旋转变换。 +// +// 修订历史: + +#ifndef __AFFINETRANS_H__ +#define __AFFINETRANS_H__ + +#include "ErrorCode.h" +#include "Image.h" + + +// 宏:AFFINE_SOFT_IPL +// 用于设置 AffineTrans 类中的 impType 成员变量,告知类的实例选用软件实现的 +// Fanczos 插值实现旋转变换。 +#define AFFINE_SOFT_IPL 1 + +// 宏:AFFINE_HARD_IPL +// 用于设置 AffineTrans 类中的 impType 成员变量,告知类的实例选用基于纹理内存的 +// 硬件插值功能实现的旋转变换。 +#define AFFINE_HARD_IPL 2 + +// 宏:AFFINE_NVIDIA_LIB +// 用于设置 AffineTrans 类中的 impType 成员变量,告知类的实例选用封装 NPP 支持 +// 库实现的旋转变换。 +#define AFFINE_NVIDIA_LIB 3 + + +// 类:AffineTrans(旋转仿射变换) +// 继承自:无 +// 实现图像的旋转变换,旋转变换不改变原有图像的尺寸,旋转后,超出输出图片的部分 +// 结果会被裁剪调。该变换具有三种不同的实现:(1)基于纹理内存的硬件插值功能实 +// 现的旋转变换;(2)使用软件实现的 Fanczos 插值实现的旋转变换;(3)封装 NPP +// 支持库实现的旋转变换。 +class AffineTrans { + +protected: + + // 成员变量:impType(实现类型) + // 设定三种实现类型中的一种,在调用仿射变换函数的时候,使用对应的实现方式。 + int impType; + + // 成员变量:x 和 y(基准坐标) + // 用于设定基准坐标,在 rotateCenter 成员方法中,它作为旋转变换的中心点;在 + // rotateShift 成员方法中,它作为旋转前的平移量。 + int x, y; + + // 成员变量:alpha(旋转角度) + // 指定旋转变换的角度,单位是“度”(°)。 + float alpha; + + // 成员变量:stateFlag(调用状态) + // 输出参数,其值为上一次调用 rotateCenter 或 rotateShift 方法的返回的错误 + // 码,如果实例还没有调用过这两个成员方法,则默认值为 NO_ERROR。 + int stateFlag; + +public: + + // 构造函数:AffineTrans + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + AffineTrans() + { + // 使用默认值为类的各个成员变量赋值。 + impType = AFFINE_HARD_IPL; // 实现类型的默认值为“硬件插值” + x = y = 0; // 基准坐标的默认值为 (0, 0) + alpha = 0.0f; // 旋转角度的默认值为 0° + stateFlag = NO_ERROR; // 调用状态的默认值为“无错误” + } + + // 构造函数:AffineTrans + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + AffineTrans ( + int imptype, // 实现类型(具体解释见成员变量) + int x, int y, // 基准坐标(具体解释见成员变量) + float alpha // 旋转角度(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->impType = AFFINE_HARD_IPL; // 实现类型的默认值为“硬件插值” + this->x = this->y = 0; // 基准坐标的默认值为 (0, 0) + this->alpha = 0.0f; // 旋转角度的默认值为 0° + this->stateFlag = NO_ERROR; // 调用状态的默认值为“无错误” + + // 根据参数列表中的值设定成员变量的初值 + this->setImpType(imptype); + this->setX(x); + this->setY(y); + this->setAlpha(alpha); + + // 将调用状态赋值为默认的“无错误” + this->stateFlag = NO_ERROR; + } + + // 成员方法:getImpType(读取实现类型) + // 读取 impType 成员变量的值。 + __host__ __device__ int // 返回值:当前 impType 成员变量的值。 + getImpType() const + { + // 返回 impType 成员变量的值。 + return this->impType; + } + + // 成员方法:setImpType(设置实现类型) + // 设置 impType 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setImpType( + int imptype // 新的实现类型 + ) { + // 检查输入参数是否合法 + if (imptype != AFFINE_HARD_IPL && imptype != AFFINE_SOFT_IPL && + imptype != AFFINE_NVIDIA_LIB) + return INVALID_DATA; + + // 将 impType 成员变量赋成新值 + this->impType = imptype; + return NO_ERROR; + } + + // 成员方法:getX(读取基准坐标 X 分量) + // 读取 x 成员变量的值。 + __host__ __device__ int // 返回值:当前 x 成员变量的值。 + getX() const + { + // 返回 x 成员变量的值。 + return this->x; + } + + // 成员方法:setX(设置基准坐标 X 分量) + // 设置 x 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setX( + int x // 新的基准坐标 X 分量 + ) { + // 将 x 成员变量赋成新值 + this->x = x; + return NO_ERROR; + } + + // 成员方法:getY(读取基准坐标 Y 分量) + // 读取 y 成员变量的值。 + __host__ __device__ int // 返回值:当前 y 成员变量的值。 + getY() const + { + // 返回 y 成员变量的值。 + return this->y; + } + + // 成员方法:setY(设置基准坐标 Y 分量) + // 设置 y 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setY( + int y // 新的基准坐标 Y 分量 + ) { + // 将 y 成员变量赋成新值 + this->y = y; + return NO_ERROR; + } + + // 成员方法:getAlpha(读取旋转角度) + // 读取 alpha 成员变量的值。 + __host__ __device__ float // 返回值:当前 alpha 成员变量的值。 + getAlpha() const + { + // 返回 alpha 成员变量的值。 + return this->alpha; + } + + // 成员方法:setAlpha(设置旋转角度) + // 设置 alpha 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setAlpha( + float alpha // 新的旋转角度 + ) { + // 为了方便计算,首先将符号和数字剥离,这样 alpha 为正数。 + bool insign = (alpha >= 0); + if (!insign) + alpha = -alpha; + + // 角度是以 360°为一个轮回,因此,去除多余的 360°,使角度归一化到 + // ±360°之间。 + float purealpha = alpha - floor(alpha / 360.0f) * 360.0f; + if (!insign) + purealpha = -purealpha; + + // 由于 ±360°之间有 720°,因此进一步归一化,使角度归一化到 ±360°之 + // 间。这样旋转后角的边落入一、二象限时,角度为正;边落入三、四象限时, + // 角度为负。 + if (purealpha > 180.0f) { + purealpha -= 360.0f; + } else if (purealpha <= -180.0f) { + purealpha += 360.0f; + } + + // 将 alpha 成员变量赋值成新值 + this->alpha = purealpha; + return NO_ERROR; + } + + // 成员方法:getStateFlag(读取调用状态) + // 读取 stateFlag 成员变量的值。 + __host__ __device__ int // 返回值:当前 stateFlag 成员变量的值。 + getStateFlag() const + { + // 返回 stateFlag 成员变量的值。 + return this->stateFlag; + } + + // 成员方法:resetStateFlag(恢复调用状态) + // 清除前次调用记录在 stateFlag 中的错误码,使其恢复到 NO_ERROR。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + resetStateFlag() + { + // 将 stateFlag 恢复为默认值 NO_ERROR。 + this->stateFlag = NO_ERROR; + return NO_ERROR; + } + + // Host 成员方法:rotateCenter(基于中心的旋转) + // 将基准坐标作为旋转中心的旋转仿射变换。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + rotateCenter( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // Host 成员方法:rotateShift(基于平移的旋转) + // 将基准坐标作为旋转前的平移向量的旋转仿射变换,在该成员方法中,旋转的中心 + // 点为输入图像的中心点。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + rotateShift( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + + +#endif diff --git a/okano_3_0/BilateralFilter.cu b/okano_3_0/BilateralFilter.cu new file mode 100644 index 0000000..751f77a --- /dev/null +++ b/okano_3_0/BilateralFilter.cu @@ -0,0 +1,346 @@ +// BilateralFilter.cu +// 实现图像的双边滤波 + +#include "BilateralFilter.h" + +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 纹理内存只能用于全局变量,使用全局存储时需要加入边界判断,经测试效率不及 +// 纹理内存 +static texture _bilateralInimgTex; + +// Host 函数:initTexture(初始化纹理内存) +// 将输入图像数据绑定到纹理内存 +static __host__ int // 返回值:若正确执行返回 NO_ERROR +_initTexture( + Image *insubimg // 输入图像 +); + +// Kernel 函数:_bilateralFilterKer(使用 ImageCuda 实现的双边滤波) +// 空域参数只影响高斯表,在调用该方法前初始化高斯表即可 +static __global__ void // kernel 函数无返回值 +_bilateralFilterKer( + ImageCuda outimg, // 输出图像 + int radius, // 双边滤波半径 + TemplateCuda gauCud, // 高斯表 + TemplateCuda euCud // 欧氏距离表 +); + +// Host 函数:initTexture(初始化纹理内存) +static __host__ int _initTexture(Image *inimg) +{ + cudaError_t cuerrcode; + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 设置数据通道描述符,因为只有一个颜色通道(灰度图),因此描述符中只有第一 + // 个分量含有数据。概述据通道描述符用于纹理内存的绑定操作。 + struct cudaChannelFormatDesc chndesc; + chndesc = cudaCreateChannelDesc(sizeof (unsigned char) * 8, 0, 0, 0, + cudaChannelFormatKindUnsigned); + + // 将输入图像数据绑定至纹理内存(texture) + cuerrcode = cudaBindTexture2D( + NULL, &_bilateralInimgTex, insubimgCud.imgMeta.imgData, &chndesc, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height, + insubimgCud.pitchBytes); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + return NO_ERROR; +} + +// Kernel 函数:_bilateralFilterKer(使用 ImageCuda 实现的双边滤波) +static __global__ void _bilateralFilterKer( + ImageCuda outimg, int radius, TemplateCuda gauCud, TemplateCuda euCud) +{ + // 给定半径不在范围内时直接跳出 + if (radius <= 0|| radius > DEF_FILTER_RANGE) + return; + // 半径对应的高斯表数组的下标 + int gi = (2 * radius + 1) * (2 * radius + 1); + + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * outimg.pitchBytes + dstc; + + // 邻域像素与参数乘积的累加值 + float sum[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + // 存储参数的临时变量 + float factor; + // 邻域参数的累加值 + float t[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // 获取当前处理点的像素值,即为中心点,取同一列的四个点 + unsigned char center[4]; + // 第一个中心点 + center[0] = tex2D(_bilateralInimgTex, dstc, dstr); + // 第二个中心点,位于第一个中心点下方 + center[1] = tex2D(_bilateralInimgTex, dstc, dstr + 1); + // 处于同列的第三个中心点 + center[2] = tex2D(_bilateralInimgTex, dstc, dstr + 2); + // 处于同列的第四个中心点 + center[3] = tex2D(_bilateralInimgTex, dstc, dstr + 3); + + for (int col = 0; col <= gi; col++) + { + // 获取当前处理点的横纵坐标 + int i = gauCud.tplMeta.tplData[2 * col], + j = gauCud.tplMeta.tplData[2 * col + 1]; + // 获取当前处理点的像素值 + unsigned char curPix = tex2D(_bilateralInimgTex, + dstc + j, dstr + i); + // 计算当前点与中心点的像素差值 + unsigned char euindex = curPix > center[0] ? curPix - center[0] : + center[0] - curPix; + // 欧氏距离与高斯距离的乘积 + factor = gauCud.attachedData[col] * euCud.attachedData[euindex]; + t[0] += factor * curPix; + sum[0] += factor; + + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点不变,由于是位于循环体内部不可直接进行 ++ 运算,且当列超出时也 + // 不能进行 return,否则邻域扫描将终止,且输出图像不能赋值 + if (dstr + 1 >= outimg.imgMeta.height) + continue; + + // 获取当前处理点的像素值 + curPix = tex2D(_bilateralInimgTex, dstc + j, dstr + i); + // 计算当前点与中心点的像素差值 + euindex = curPix > center[1] ? curPix - center[1] : + center[1] - curPix; + // 欧氏距离与高斯距离的乘积 + factor = gauCud.attachedData[col] * euCud.attachedData[euindex]; + t[1] += factor * curPix; + sum[1] += factor; + + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点不变,由于是位于循环体内部不可直接进行 ++ 运算,且当列超出时也 + // 不能进行 return,否则邻域扫描将终止,且输出图像不能赋值 + if (dstr + 2 >= outimg.imgMeta.height) + continue; + + // 获取当前处理点的像素值 + curPix = tex2D(_bilateralInimgTex, dstc + j, dstr + i); + // 计算当前点与中心点的像素差值 + euindex = curPix > center[2] ? curPix - center[2] : + center[2] - curPix; + // 欧氏距离与高斯距离的乘积 + factor = gauCud.attachedData[col] * euCud.attachedData[euindex]; + t[2] += factor * curPix; + sum[2] += factor; + + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点不变,由于是位于循环体内部不可直接进行 ++ 运算,且当列超出时也 + // 不能进行 return,否则邻域扫描将终止,且输出图像不能赋值 + if (dstr + 3 >= outimg.imgMeta.height) + continue; + + // 获取当前处理点的像素值 + curPix = tex2D(_bilateralInimgTex, dstc + j, dstr + i); + // 计算当前点与中心点的像素差值 + euindex = curPix > center[3] ? curPix - center[3] : + center[3] - curPix; + // 欧氏距离与高斯距离的乘积 + factor = gauCud.attachedData[col] * euCud.attachedData[euindex]; + t[3] += factor * curPix; + sum[3] += factor; + } + // 对第一列的点进行赋值 + outimg.imgMeta.imgData[dstidx] = (unsigned char)(t[0] / sum[0]); + // 若列超出范围,此处可直接使用 return 直接跳出 + if (++dstr >= outimg.imgMeta.height) + return; + // 将对应数据的下标加一行 + dstidx += outimg.pitchBytes; + // 对第二列的点进行赋值 + outimg.imgMeta.imgData[dstidx] = (unsigned char)(t[1] / sum[1]); + // 准备处理第三列 + if (++dstr >= outimg.imgMeta.height) + return; + dstidx += outimg.pitchBytes; + outimg.imgMeta.imgData[dstidx] = (unsigned char)(t[2] / sum[2]); + // 处理第四列 + if (++dstr >= outimg.imgMeta.height) + return; + dstidx += outimg.pitchBytes; + outimg.imgMeta.imgData[dstidx] = (unsigned char)(t[3] / sum[3]); +} + +// Host 成员方法:doFilter(执行滤波) +__host__ int BilateralFilter::doFilter(Image *inoutimg) +{ + // 给定半径不在范围内时直接跳出 + if (radius <= 0 && radius > DEF_FILTER_RANGE) + return INVALID_DATA; + // 若滤波的重复次数为 0 ,则不进行滤波返回正确执行 + if (repeat <= 0) + return INVALID_DATA; + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inoutimg == NULL) + return NULL_POINTER; + // 检查模板数据 + if (gaussian == NULL || euclid == NULL) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + // 初始化纹理内存,将输入图像与之绑定 + _initTexture(inoutimg); + + // 将高斯模板数据拷贝至 Device 端避免核函数中无法访问 + errcode = TemplateBasicOp::copyToCurrentDevice(gaussian); + if (errcode != NO_ERROR) + return errcode; + + // 将欧式距离模板数据拷贝至 Device 端避免核函数中无法访问 + errcode = TemplateBasicOp::copyToCurrentDevice(euclid); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda inoutsubimgCud; + errcode = ImageBasicOp::roiSubImage(inoutimg, &inoutsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (inoutsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (inoutsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 进行重复滤波以提高质量 + for (int i = 0; i < repeat; i++) { + // 调用核函数进行滤波 + _bilateralFilterKer<<>>( + inoutsubimgCud, radius, *TEMPLATE_CUDA(gaussian), + *TEMPLATE_CUDA(euclid)); + } + + return NO_ERROR; +} + +// Host 成员方法:doFilter(执行滤波) +__host__ int BilateralFilter::doFilter(Image *inimg, Image *outimg) +{ + // 给定半径不在范围内时直接跳出 + if (radius <= 0 && radius > DEF_FILTER_RANGE) + return INVALID_DATA; + // 若滤波的重复次数为 0 ,则不进行滤波返回正确执行 + if (repeat <= 0) + return INVALID_DATA; + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + // 检查模板数据 + if (gaussian == NULL || euclid == NULL) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + + // 初始化纹理内存,将输入图像与之绑定,需将第一次运行结果保存至 outimg , + // 之后的重复则相当于在 outimg 上的 inplace 版本,这样保证了 inimg 中的数据 + // 一致性 + _initTexture(inimg); + + // 将高斯模板数据拷贝至 Device 端避免核函数中无法访问 + errcode = TemplateBasicOp::copyToCurrentDevice(gaussian); + if (errcode != NO_ERROR) + return errcode; + + // 将欧式距离模板数据拷贝至 Device 端避免核函数中无法访问 + errcode = TemplateBasicOp::copyToCurrentDevice(euclid); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数进行滤波 + _bilateralFilterKer<<>>( + outsubimgCud, radius, *TEMPLATE_CUDA(gaussian), + *TEMPLATE_CUDA(euclid)); + + // 进行重复滤波以提高质量 + for (int i = 1; i < repeat; i++) { + // 调用核函数进行滤波 + _bilateralFilterKer<<>>( + outsubimgCud, radius, *TEMPLATE_CUDA(gaussian), + *TEMPLATE_CUDA(euclid)); + } + + return NO_ERROR; +} + diff --git a/okano_3_0/BilateralFilter.h b/okano_3_0/BilateralFilter.h new file mode 100644 index 0000000..a474314 --- /dev/null +++ b/okano_3_0/BilateralFilter.h @@ -0,0 +1,250 @@ +// BilateralFilter.h +// 创建人:邓建平 +// +// 双边滤波(BilateralFilter) +// 功能说明:根据给定的空域参数求出高斯距离表,同时根据范围参数(颜色域参数)求 +// 出 0 到 255 的欧氏距离表。对处于滤波半径内的邻域像素进行扫描实现双 +// 边滤波 +// +// 修订历史: +// 2012年10月22日(邓建平) +// 初始版本 +// 2012年11月06日(邓建平、于玉龙) +// 增加了滤波半径参数,加入了表空间释放函数,并修正了一些格式错误 +// 2012年12月27日(邓建平、于玉龙) +// 将高斯表和欧氏距离存储在模板中,减少开销,提高模板数据的复用性 + +#ifndef __BILATERALFILTER_H__ +#define __BILATERALFILTER_H__ + +#include "ErrorCode.h" +#include "Image.h" +#include "TemplateFactory.h" + +// 宏:DEF_FILTER_RADIUS +// 定义了默认的双边滤波半径 +#define DEF_FILTER_RANGE 20 + +// 类:BilateralFilter(双边滤波) +// 继承自:无 +// 根据给定的空域参数求出高斯距离表,同时根据范围参数(颜色域参数)求出 0 到 +// 255 的欧氏距离表。对处于滤波半径内的邻域像素进行扫描实现双边滤波 +class BilateralFilter { + +protected: + // 成员变量:gaussian(高斯表),euclid(欧氏距离表) + // 高斯表和欧氏距离表数据均保存在模板中 + Template *gaussian, *euclid; + + // 成员变量:sigmaSpace(空域参数),sigmaRange(颜色域参数) + // 空域参数采用高斯核来计算,颜色域参数是基于像素与中心像素的亮度差的差值的 + // 加权,相似的像素赋给较大的权值,不相似的赋予较小的权值 + float sigmaSpace, sigmaRange; + + // 成员变量:radius(滤波半径) + int radius; + + // 成员变量:repeat(重复次数) + // 该参数默认为 0 ,当设置的参数大于 0 时务必确保空域参数与颜色域参数均设置 + // 正确,若设置为非正数,则不对图像进行双边滤波 + int repeat; + +public: + + // 构造函数:BilateralFilter + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + BilateralFilter() + { + // 使用默认值为类的各个成员变量赋值。 + sigmaSpace = 0.0f; // 空域参数的默认值为 0 + sigmaRange = 0.0f; // 颜色域参数的默认值为 0 + radius = 0; // 滤波半径的默认值为 0 + repeat = 0; // 滤波重复次数默认为 0 + gaussian = NULL; // 高斯表模板指针默认为 NULL + euclid = NULL; // 欧氏距离表模板指针默认为 NULL + } + + // 构造函数:BilateralFilter + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ + BilateralFilter( + float sigmaspace, // 空域参数(具体解释见成员变量) + float sigmarange, // 颜色域参数(具体解释见成员变量) + int radius, // 滤波半径(具体解释见成员变量) + int repeat // 重复次数(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->sigmaSpace = 0.0f; // 空域参数的默认值为 0 + this->sigmaRange = 0.0f; // 颜色域参数的默认值为 0 + this->radius = 0; // 滤波半径的默认值为 0 + this->repeat = 0; // 滤波重复次数默认为 0 + this->gaussian = NULL; // 高斯表模板指针默认为 NULL + this->euclid = NULL; // 欧氏距离表模板指针默认为 NULL + + // 根据参数列表中的值设定成员变量的初值 + this->setSigmaSpace(sigmaspace); + this->setSigmaRange(sigmarange); + this->setRadius(radius); + this->setRepeat(repeat); + } + + // 析构函数:~BilateralFilter + // 类的析构函数,在类销毁时被调用,在类销毁时将模板数据放回模板池 + __host__ + ~BilateralFilter() + { + // 若模板数据不为空,将其放回 + if (gaussian != NULL) + TemplateFactory::putTemplate(gaussian); + + // 若模板数据不为空,将其放回 + if (euclid != NULL) + TemplateFactory::putTemplate(euclid); + } + + // 成员方法:getSigmaSpace(读取空域参数) + // 读取 sigmaSpace 成员变量的值。 + __host__ float // 返回: 当前 sigmaSpace 成员变量的值 + getSigmaSpace() const + { + // 返回 sigmaSpace 成员变量的值 + return this->sigmaSpace; + } + + // 成员方法:setSigmaSpace(设置空域参数) + // 设置 sigmaSpace 成员变量的值。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setSigmaSpace( + float sigmaspace // 空域参数 + ) { + + // 若参数不合法,返回对应的错误代码并退出 + if (sigmaspace <= 0) + return INVALID_DATA; + // 将 sigmaSpace 成员变量赋成新值 + this->sigmaSpace = sigmaspace; + + // 若模板数据不为空,先将其放回再获取新模板 + if (gaussian != NULL) + TemplateFactory::putTemplate(gaussian); + // 获取高斯模板数据,由于模板采用环形存储,一次性获取最大可能的数据 + int errcode = TemplateFactory::getTemplate(&gaussian, TF_SHAPE_GAUSS, + 2 * DEF_FILTER_RANGE + 1, + &sigmaSpace); + if (errcode != NO_ERROR) + return errcode; + + return NO_ERROR; + } + + // 成员方法:getSigmaRange(读取颜色域参数) + // 读取 sigmaRange 成员变量的值。 + __host__ float // 返回: 当前 sigmaRange 成员变量的值 + getSigmaRange() const + { + // 返回 sigmaRange 成员变量的值 + return this->sigmaRange; + } + + // 成员方法:setSigmaRange(设置颜色域参数) + // 设置 sigmaRange 成员变量的值。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setSigmaRange( + float sigmarange // 颜色域参数 + ) { + + // 若参数不合法,返回对应的错误代码并退出 + if (sigmarange <= 0) + return INVALID_DATA; + // 将 sigmaRange 成员变量赋成新值 + this->sigmaRange = sigmarange; + + // 若模板数据不为空,先将其放回再获取新模板 + if (euclid != NULL) + TemplateFactory::putTemplate(euclid); + + // 获取欧式距离模板数据,由于像素值的差值在 0 - 255 范围内,其大小固定为 + // 256,该模板中的点信息即表示所有可能的像素值差值 + int errcode = TemplateFactory::getTemplate(&euclid, TF_SHAPE_EUCLIDE, + 256, &sigmaRange); + if (errcode != NO_ERROR) + return errcode; + + return NO_ERROR; + } + + // 成员方法:getRadius(读取滤波半径) + // 读取 radius 成员变量的值。 + __host__ __device__ int // 返回: 当前 radius 成员变量的值 + getRadius() const + { + // 返回 radius 成员变量的值 + return this->radius; + } + + // 成员方法:setRadius(设置滤波半径) + // 设置 radius 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setRadius( + int radius // 滤波半径 + ) { + + // 若半径超出允许范围不赋值直接退出 + if (radius <= 0 || radius > DEF_FILTER_RANGE) + return INVALID_DATA; + // 将 radius 成员变量赋成新值 + this->radius = radius; + return NO_ERROR; + } + + // 成员方法:getRepeat(读取迭代次数) + // 读取 repeat 成员变量的值。 + __host__ __device__ int // 返回: 当前 repeat 成员变量的值 + getRepeat() const + { + // 返回 repeat 成员变量的值 + return this->repeat; + } + + // 成员方法:setRepeat(设置迭代次数) + // 设置 repeat 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setRepeat( + int repeat // 迭代次数 + ) { + // 若迭代次数超出允许范围不赋值直接退出 + if (repeat <= 0) + return INVALID_DATA; + // 将 repeat 成员变量赋成新值 + this->repeat = repeat; + return NO_ERROR; + } + + // Host 成员方法:doFilter(执行滤波) + // 对图像进行双边滤波,inplace 版本 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + doFilter( + Image *inoutimg // 输入输出图像 + ); + + // Host 成员方法:doFilter(执行滤波) + // 对图像进行双边滤波,outplace 版本 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + doFilter( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + +}; + +#endif + diff --git a/okano_3_0/BilinearInterpolation.cu b/okano_3_0/BilinearInterpolation.cu new file mode 100644 index 0000000..78ed1db --- /dev/null +++ b/okano_3_0/BilinearInterpolation.cu @@ -0,0 +1,164 @@ +// BilinearInterpolation.cu +// 实现图像的双线性插值 + +#include "BilinearInterpolation.h" +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 全局变量:_bilInterInimgTex(作为输入图像的纹理内存引用) +// 纹理内存只能用于全局变量,此处是为了使用纹理内存提供的硬件插值功能 +static texture _bilInterInimgTex; + +// Host 函数:initTexture(初始化纹理内存) +// 将输入图像数据绑定到纹理内存 +static __host__ int // 返回值:若正确执行返回 NO_ERROR +initTexture( + Image* inimg // 输入图像 +); + +// Kernel 函数:_bilInterpolKer(使用 ImageCuda 实现的双边滤波) +// 空域参数只影响高斯表,在调用该方法前初始化高斯表即可 +static __global__ void // kernel 函数无返回值 +_bilInterpolKer( + ImageCuda outimg, // 输出图像 + float scaleinverse // 双边滤波半径 +); + + +// Host 函数:initTexture(初始化纹理内存) +static __host__ int initTexture(Image* inimg) +{ + cudaError_t cuerrcode; + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 设置数据通道描述符,因为只有一个颜色通道(灰度图),因此描述符中只有第一 + // 个分量含有数据。概述据通道描述符用于纹理内存的绑定操作。 + struct cudaChannelFormatDesc chndesc; + chndesc = cudaCreateChannelDesc(sizeof (unsigned char) * 8, 0, 0, 0, + cudaChannelFormatKindUnsigned); + // 将输入图像数据绑定至纹理内存(texture) + cuerrcode = cudaBindTexture2D( + NULL, &_bilInterInimgTex, insubimgCud.imgMeta.imgData, &chndesc, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height, + insubimgCud.pitchBytes); + + // 将纹理内存(texture)的过滤模式设置为线性插值模式 + // (cudaFilterModeLinear) + _bilInterInimgTex.filterMode = cudaFilterModeLinear; + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + return NO_ERROR; +} + +// Kernel 函数:_bilInterpolKer(使用 ImageCuda 实现的双线性插值) +static __global__ void _bilInterpolKer(ImageCuda outimg, float scaleinverse) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * outimg.pitchBytes + dstc; + + // 将放大后图像的坐标点映射到源图像中的坐标点 + float oldx = dstc * scaleinverse, oldy = dstr * scaleinverse; + + // 使用 texture 硬件插值得到归一化的 [0, 1] 的 float 归一化数据,再将数据转 + // 换为对应的 8 位 unsigned char 数据 + outimg.imgMeta.imgData[dstidx] = (unsigned char)0xFF * + tex2D(_bilInterInimgTex, oldx, oldy); + + // 处理剩下的 3 个点 + for (int i = 0; i <= 3; i++) + { + if (++dstr >= outimg.imgMeta.height) + return ; + // 由于只有纵坐标加 1,故映射左边只需加上 scale 的倒数即可 + oldy += scaleinverse; + // 将数据指针移至下一行像素点 + dstidx += outimg.pitchBytes; + // 使用 texture 进行硬件插值 + outimg.imgMeta.imgData[dstidx] = (unsigned char) 0xFF * + tex2D(_bilInterInimgTex, oldx, oldy); + } +} + +// Host 成员方法:doInterpolation(执行插值) +__host__ int BilinearInterpolation::doInterpolation(Image *inimg, Image *outimg) +{ + // 若图像的放大倍数为 0 ,则不进行插值返回正确执行 + if (scale <= 0) + return NO_ERROR; + + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + // 初始化纹理内存,将输入图像与之绑定 + initTexture(inimg); + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 拷贝失败时需要在host端创建放大后大小的图像空间 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, scale * (inimg->roiX2 - inimg->roiX1), + scale * (inimg->roiY2 - inimg->roiY1)); + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 由于输出图像是输入图像的 scale 倍,此处不进行 ROI 的大小调整 + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + // gridsize 由 blocksize 对应大小决定 + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 放大倍数的倒数,将核函数内的除法运算变成乘法运算,同时减少除运算的次数 + float scaleInverse = 1.0f / scale; + + // 调用核函数进行插值 + _bilInterpolKer<<>>(outsubimgCud, scaleInverse); + + return NO_ERROR; +} + diff --git a/okano_3_0/BilinearInterpolation.h b/okano_3_0/BilinearInterpolation.h new file mode 100644 index 0000000..12049f6 --- /dev/null +++ b/okano_3_0/BilinearInterpolation.h @@ -0,0 +1,93 @@ +// BilinearInterpolation.h +// 创建人:邓建平 +// +// 双线性插值(BilinearInterpolation) +// 功能说明:根据给定的放大倍数对放大后的图像的未知点进行双线性插值,保 +// 证放大后的图像比较平滑 +// +// 修订历史: +// 2012年10月30日(邓建平) +// 初始版本 +// 2102年11月07日(邓建平、于玉龙) +// 修正了 texture 的使用方式,修正了一些格式错误 + +#ifndef __BILINEARINTERPOLATION_H__ +#define __BILINEARINTERPOLATION_H__ + +#include "ErrorCode.h" +#include "Image.h" + + +// 类:BilinearInterpolation(双线性插值) +// 继承自:无 +// 根据给定的放大倍数对放大后的图像的未知点进行双线性插值,保证放大后的 +// 图像比较平滑 +class BilinearInterpolation { + +protected: + + // 成员变量:scale(放大倍数) + // 该参数默认为 0 ,当设置的参数大于 0 时则对图像进行对应倍数的放大 + int scale; + +public: + + // 构造函数:BilinearInterpolation + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + BilinearInterpolation() + { + // 使用默认值为类的各个成员变量赋值。 + scale = 0; // 放大倍数默认为 0 + } + + // 构造函数:BilinearInterpolation + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + BilinearInterpolation( + int scale // 放大倍数(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->scale = 0; // 放大倍数默认为 0 + + // 根据参数列表中的值设定成员变量的初值 + this->setScale(scale); + } + + // 成员方法:getScale(读取放大倍数) + // 读取 scale 成员变量的值。 + __host__ __device__ int // 返回: 当前 scale 成员变量的值 + getScale() const + { + // 返回 scale 成员变量的值 + return this->scale; + } + + // 成员方法:setScale(设置迭代次数) + // 设置 scale 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setScale( + int scale // 放大倍数 + ) { + // 将 scale 成员变量赋成新值 + this->scale = scale; + return NO_ERROR; + } + + // Host 成员方法:doInterpolation(执行滤波) + // 对图像进行双边滤波,outplace 版本,由于输出图像与输入图像大小不一,只提 + // 供 outplace 版本 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + doInterpolation( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + +}; + +#endif + diff --git a/okano_3_0/Binarization.cu b/okano_3_0/Binarization.cu new file mode 100644 index 0000000..5c82115 --- /dev/null +++ b/okano_3_0/Binarization.cu @@ -0,0 +1,272 @@ +// Binarization.cu +// 实现图像的多阈值二值化图像生成操作 + +#include "Binarization.h" +#include "Binarize.h" + +#include +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:BINARIZE_PACK_LEVEL +// 定义了一个线程中计算的像素点个数,若该值为4,则在一个线程中计算2 ^ 4 = 16 +// 个像素点 +#define BINARIZE_PACK_LEVEL 7 + +#define BINARIZE_PACK_NUM (1 << BINARIZE_PACK_LEVEL) +#define BINARIZE_PACK_MASK (BINARIZE_PACK_LEVEL - 1) + +#if (BINARIZE_PACK_LEVEL < 1 || BINARIZE_PACK_LEVEL > 8) +# error Unsupport BINARIZE_PACK_LEVEL Value!!! +#endif + +// 宏: DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数:_computeAreaKer +// 利用共享内存和原子操作计算输入图像分别以1-255为阈值进行二值化之后的面积值,// 并将面积值保存在数组 result 中。 +static __global__ void +_computeAreaKer( + ImageCuda inimg, // 输入图像 + int *result // 面积值数组 +); +// Kernel 函数:_computeAreaKer(计算图像的255种面积值) +static __global__ void _computeAreaKer( + ImageCuda inimg, int *result) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,默认令一个线程处理 16 个输出像素,这四个像素位于统一列 + // 的相邻 16 行上,因此,对于 r 需要进行右移计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) << BINARIZE_PACK_LEVEL; + int z = blockIdx.z; + int inidx = r * inimg.pitchBytes + c; + int cursum = 0; + int threshold = z + 1; + do { + // 线程中处理第一个点。 + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (r >= inimg.imgMeta.height || c >= inimg.imgMeta.width) + break; + // 得到第一个输入坐标点对应的标记值。 + //curlabel = label[inidx]; + if (inimg.imgMeta.imgData[inidx] >= threshold) + cursum++; + + // 处理第二个点。 + // 此后的像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= inimg.imgMeta.height) + break; + // 得到第二个点的像素值。 + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计算。 + inidx += inimg.pitchBytes; + if (inimg.imgMeta.imgData[inidx] >= threshold) + cursum++; + + // 宏:BINARIZE_KERNEL_MAIN_PHASE + // 定义计算下一个像素点的程序片段。使用这个宏可以实现获取下一个点的像素 + // 值,并累加到共享内存,并且简化编码量 +#define BINARIZE_KERNEL_MAIN_PHASE \ + if (++r >= inimg.imgMeta.height) \ + break; \ + inidx += inimg.pitchBytes; \ + if (inimg.imgMeta.imgData[inidx] >= threshold) \ + cursum++; + + +#define BINARIZE_KERNEL_MAIN_PHASEx2 \ + BINARIZE_KERNEL_MAIN_PHASE \ + BINARIZE_KERNEL_MAIN_PHASE + +#define BINARIZE_KERNEL_MAIN_PHASEx4 \ + BINARIZE_KERNEL_MAIN_PHASEx2 \ + BINARIZE_KERNEL_MAIN_PHASEx2 + +#define BINARIZE_KERNEL_MAIN_PHASEx8 \ + BINARIZE_KERNEL_MAIN_PHASEx4 \ + BINARIZE_KERNEL_MAIN_PHASEx4 + +#define BINARIZE_KERNEL_MAIN_PHASEx16 \ + BINARIZE_KERNEL_MAIN_PHASEx8 \ + BINARIZE_KERNEL_MAIN_PHASEx8 + +#define BINARIZE_KERNEL_MAIN_PHASEx32 \ + BINARIZE_KERNEL_MAIN_PHASEx16 \ + BINARIZE_KERNEL_MAIN_PHASEx16 + +#define BINARIZE_KERNEL_MAIN_PHASEx64 \ + BINARIZE_KERNEL_MAIN_PHASEx32 \ + BINARIZE_KERNEL_MAIN_PHASEx32 + +// 对于不同的 BINARIZE_PACK_LEVEL ,定义不同的执行次数,从而使一个线程内部 +// 实现对多个点的像素值的统计。 +#if (BINARIZE_PACK_LEVEL >= 2) + BINARIZE_KERNEL_MAIN_PHASEx2 +# if (BINARIZE_PACK_LEVEL >= 3) + BINARIZE_KERNEL_MAIN_PHASEx4 +# if (BINARIZE_PACK_LEVEL >= 4) + BINARIZE_KERNEL_MAIN_PHASEx8 +# if (BINARIZE_PACK_LEVEL >= 5) + BINARIZE_KERNEL_MAIN_PHASEx16 +# if (BINARIZE_PACK_LEVEL >= 6) + BINARIZE_KERNEL_MAIN_PHASEx32 +# if (BINARIZE_PACK_LEVEL >= 7) + BINARIZE_KERNEL_MAIN_PHASEx64 +# endif +# endif +# endif +# endif +# endif +#endif + +// 取消前面的宏定义。 +#undef BINARIZE_KERNEL_MAIN_PHASEx64 +#undef BINARIZE_KERNEL_MAIN_PHASEx32 +#undef BINARIZE_KERNEL_MAIN_PHASEx16 +#undef BINARIZE_KERNEL_MAIN_PHASEx8 +#undef BINARIZE_KERNEL_MAIN_PHASEx4 +#undef BINARIZE_KERNEL_MAIN_PHASEx2 +#undef BINARIZE_KERNEL_MAIN_PHASE + } while (0); + + // 使用原子操作来保证操作的正确性 + if (cursum != 0) + atomicAdd(&result[threshold - 1], cursum); +} + + +// Host 成员方法:binarization(多阈值二值化处理) +__host__ int Binarization::binarization(Image *inimg, + Image *outimg, + float areaRatio) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + // 创建 host 端和 device 端的存放面积值的数组。 + int *devResult; + int *hostResult; + hostResult = new int[254]; + cudaError_t cudaerrcode; + + // 为标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)&devResult, 254 * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + cudaFree(devResult); + return cudaerrcode; + } + cudaerrcode = cudaMemset(devResult, 0, 254 * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + cudaFree(devResult); + return cudaerrcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y ; + gridsize.z = 254; + + // 设定 gridsize.z 的大小为 254,存储 1 - 254 的阈值。 + //gridsize.z = 254; + + _computeAreaKer<<>>(insubimgCud, devResult); + + + cudaerrcode = cudaMemcpy(hostResult, devResult, 254 * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + cudaFree(devResult); + return cudaerrcode; + } + // 通过遍历面积值数组找到与标准面积差别最小的面积值,并获取其对应的最佳阈值。 + // 初始化最佳二值化结果的阈值为0, 图像的面积值与标准面积的最小差值初始化为1000。 + int bestnum = 0; + float min = 1000; + + // |标准面积-TEST图像上的OBJECT面积| / 标准面积 < areaRatio的二值化结果 + for (int i = 0; i < 254; i++) { + float tmpnum = areaRatio - (float)(fabs(normalArea - hostResult[i]) + / normalArea); + if (tmpnum > 0) { + if (tmpnum < min) { + min = tmpnum; + bestnum = i+1; + } + } + } + + // 选出最佳面积比的二值化结果后,设定成员变量 area 的值。 + this->area = hostResult[bestnum - 1]; + + // 调用 Binarize 函数使用最佳阈值对输入图像进行二值化。 + Binarize b; + b.setThreshold(bestnum); + b.binarize(inimg,outimg); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/Binarization.h b/okano_3_0/Binarization.h new file mode 100644 index 0000000..c4b09d5 --- /dev/null +++ b/okano_3_0/Binarization.h @@ -0,0 +1,119 @@ +// Binarization.h +// 创建人:仲思惠 +// +// 多阈值二值化图像处理(Binarization) +// 功能说明:对灰度图像进行多阈值二值化处理,以 1 - 254 内的所有的灰度为阈值, +// 同时生成 254 个 2 值化结果(0 - 1)图像,并根据一定的基准选择最佳二值化结果。 +// +// 修订历史: +// 2012年10月19日(仲思惠) +// 初始版本 +// 2012年10月20日(王媛媛、仲思惠) +// 去除了多余的成员变量 +// 2012年10月25日(于玉龙、王媛媛) +// 解决了同时生成 254 幅图像时图像无法输出的问题 +// 2012年10月27日(仲思惠) +// 修正了代码的格式,添加了一些注释 +// 2012年11月12日(仲思惠) +// 添加了对254幅图像进行选择的功能 +// 2012年11月20日(侯怡婷、王媛媛) +// 修改了图像复制的问题 +// 2012年12月15日(仲思惠) +// 修改了文件名称,由 MultiThreshold 改为 Binarization。 +// 2013年01月03日(仲思惠) +// 根据新版本的需求分析,修改了选择最佳面积比的方法。 +// 2013年01月04日(王媛媛、仲思惠) +// 修正了选择最佳面积比的方法。 +// 2013年01月07日(侯怡婷、王媛媛) +// 使用共享内存和原子操作获取面积值,提高了算法的效率。 +// 2013年05月16日(仲思惠) +// 添加了代码的注释,修改了格式错误。 +#ifndef __BINARIZATION_H__ +#define __BINARIZATION_H__ + +#include "Image.h" + +// 类:Binarization +// 继承自:无 +// 对灰度图像进行多阈值二值化处理,以 1 - 254 内的所有的灰度为阈值,同时生成 +// 254 个 2 值化结果(0 - 1)图像,并根据一定的基准选择最佳二值化结果。 +class Binarization { + +protected: + + // 成员变量:area(最佳面积) + // 求得的最佳二值化结果的面积值。 + long long area; + + // 成员变量:normalArea(标准面积) + // 由用户指定的标准面积值,选择最佳面积比结果的基准。 + long long normalArea; + +public: + + // 构造函数:Binarization + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。该函数没有任何内 + // 容。 + __host__ __device__ + Binarization(){ // 使用默认值为类的各个成员变量赋值。 + + // 标准面积的默认值为 70000。 + this->normalArea = 70000; + } + + // 构造函数:Binarization + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + Binarization( + long long normalArea + ) { + this->normalArea = 70000; // 标准面积(具体解释见成员变量) + + // 根据参数列表中的值设定成员变量的初值 + setNormalArea(normalArea); + } + + // 成员方法:getArea(读取最佳面积) + // 读取 area 成员变量的值。 + __host__ __device__ long long + getArea() const + { + return this->area; + } + + // 成员方法:getNormalArea(读取标准面积) + // 读取 normalArea 成员变量的值。 + __host__ __device__ long long + getNormalArea() const + { + return this->normalArea; + } + + // 成员方法:setNormalArea(设置标准面积) + // 设置 normalArea 成员变量的值。 + __host__ __device__ int + setNormalArea( + long long normalArea + ) { + // 检查输入参数是否合法 + if (normalArea < 0) + return INVALID_DATA; + this->normalArea = normalArea; + return NO_ERROR; + } + + // Host 成员方法:binarization(多阈值二值化处理flag=1) + // 对输入图像进行多阈值二值化处理。以 1 - 254 内的所有的灰度为阈值,同时 + // 生成 254 个 2 值化结果(0 - 1)图像,并在254个结果中选择只满足 + // |标准面积-TEST图像上的OBJECT面积| / 标准面积 < areaRatio的二值化结果 + __host__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 + binarization( + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + float areaRatio // 面积比 + ); +}; + +#endif diff --git a/okano_3_0/BoundingRect.cu b/okano_3_0/BoundingRect.cu new file mode 100644 index 0000000..03904b9 --- /dev/null +++ b/okano_3_0/BoundingRect.cu @@ -0,0 +1,1326 @@ +// BoundingRect.cu +// 找出图像中给定点集的包围矩形 + +#include "BoundingRect.h" + +#include +#include +#include +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_SHARED_LENGTH +// 定义了核函数中共享内存的长度 +#define DEF_SHARED_LENGTH(sharedarray) (DEF_BLOCK_X * DEF_BLOCK_Y * \ + sizeof (sharedarray)) + +// 宏:BR_LARGE_ENOUGH +// 定义了计算共享内存的函数中循环的上界。 +#define BR_LARGE_ENOUGH ((1 << 30) - 1) + +// 宏:FINDPIXEL_PACK_LEVEL +// 定义了 FINDPIXEL 核函数中一个线程中计算的像素点个数,若该值为 5,则在一个线 +// 程中计算 2 ^ 5 = 32 个像素点。根据实验结果,5 是最好的选择。 +#define FINDPIXEL_PACK_LEVEL 5 + +// 宏:FINDPIXEL_PACK_NUM +// 定义了计算每个线程中计算的次数。 +#define FINDPIXEL_PACK_NUM (1 << FINDPIXEL_PACK_LEVEL) + +// 宏:FINDPIXEL_PACK_MASK +// 定义了计算线程时向上取整的MASK。 +#define FINDPIXEL_PACK_MASK (FINDPIXEL_PACK_NUM - 1) + +// 列出了计算范围,如果超出范围,返回错误码。 +#if (FINDPIXEL_PACK_LEVEL < 1 || FINDPIXEL_PACK_LEVEL > 5) +# error Unsupport FINDPIXEL_PACK_LEVEL Value!!! +#endif + +// 宏:COMPUTECOV_PACK_LEVEL +// 定义了 COMPUTECOV 核函数中一个线程中计算的像素点个数,若该值为 5,则在一个线 +// 程中计算 2 ^ 5 = 32个像素点。根据实验结果,5 是最好的选择。 +#define COMPUTECOV_PACK_LEVEL 5 + +// 宏:COMPUTECOV_PACK_NUM +// 定义了计算每个线程中计算的次数。 +#define COMPUTECOV_PACK_NUM (1 << COMPUTECOV_PACK_LEVEL) + +// 宏:COMPUTECOV_PACK_MASK +// 定义了计算线程时向上取整的MASK +#define COMPUTECOV_PACK_MASK (COMPUTECOV_PACK_NUM - 1) + +// 列出了计算范围,如果超出范围,返回错误码。 +#if (COMPUTECOV_PACK_LEVEL < 1 || COMPUTECOV_PACK_LEVEL > 5) +# error Unsupport COMPUTECOV_PACK_LEVEL Value!!! +#endif + +// 宏:EXTREAMPOINT_PACK_LEVEL +// 定义了 EXTREAMPOINT 核函数中一个线程中计算的像素点个数,若该值为 5,则在一个 +// 线程中计算 2 ^ 5 = 32个像素点。根据实验结果,5 是最好的选择。 +#define EXTREAMPOINT_PACK_LEVEL 5 + +// 宏:EXTREAMPOINT_PACK_NUM +// 定义了计算每个线程中计算的次数。 +#define EXTREAMPOINT_PACK_NUM (1 << EXTREAMPOINT_PACK_LEVEL) + +// 宏:EXTREAMPOINT_PACK_MASK +// 定义了计算线程时向上取整的MASK +#define EXTREAMPOINT_PACK_MASK (EXTREAMPOINT_PACK_NUM - 1) + +// 列出了计算范围,如果超出范围,返回错误码。 +#if (EXTREAMPOINT_PACK_LEVEL < 1 || EXTREAMPOINT_PACK_LEVEL > 5) +# error Unsupport EXTREAMPOINT_PACK_LEVEL Value!!! +#endif + +// 结构体:ObjPixelPosSumInfoInner(符合条件的对象的像素点信息) +// 该结构体定义了图像中符合条件的对象的像素点信息,其中包含了像素点数量,x 坐标 +// 总和, y 坐标总和。该结构的使用可以减少数据的申请和释放。 +typedef struct ObjPixelPosSumInfoInner_st { + unsigned long long int pixelCount; // 符合条件的像素点数量 + unsigned long long int posSumX; // 符合条件的像素点的 x 坐标总和 + unsigned long long int posSumY; // 符合条件的像素点的 y 坐标总和 +} ObjPixelPosSumInfoInner; + +// 结构体:CovarianceMatrix(协方差矩阵) +// 该结构体定义了 2 维的协方差矩阵的数据结构。协方差矩阵中第二个和第三个元素相 +// 等,所以忽略第三个元素计算。 +typedef struct CovarianceMatrix_st{ + float a11; // 协方差矩阵给的第一个元素 Covariance11 = + // E{[X-E(X)][X-E(X)]}。 + float a12; // 协方差矩阵给的第二个,第三个元素 Covariance1 = + // E{[X-E(X)][Y-E(Y)]}。 + //float a21; // 协方差矩阵给的第三个元素,等于上值,忽略 Covariance21 = + // E{[Y-E(Y)][X-E(X)]}。 + float a22; // 协方差矩阵给的第四个元素 Covariance12 = + // E{[Y-E(Y)][Y-E(Y)]}。 +} CovarianceMatrix; + +// 结构体:Coordinate(点的坐标) +// 该结构体定义了点的坐标。坐标的数据类型为 float。 +typedef struct Coordinate_st +{ + float x; // x 坐标。 + float y; // y 坐标。 +} Coordinate; + +// 结构体:CoordinateInt(点的坐标) +// 该结构体定义了点的坐标。坐标的数据类型为 int。 +typedef struct CoordinateInt_st +{ + int x; // x 坐标。 + int y; // y 坐标。 +} CoordinateInt; + +// Kernel 函数: _objCalcPixelInfoKer(计算符合条件的对象的像素信息) +// 计算符合条件的对象的像素点的信息,包括像素点个数,横纵坐标总和。 +static __global__ void // Kernel 函数无返回值 +_objCalcPixelInfoKer( + ImageCuda inimg, // 输入图像 + unsigned char value, // 对象的像素值 + int blksize, // 块大小,等于 blocksize.x * + // blocksize.y * blocksize.z。 + int blksize2p, // 优化的块大小,方便规约方法。 + ObjPixelPosSumInfoInner *suminfo // 对象的像素信息。 +); + +// Host 函数:_objCalcPixelInfo(计算符合条件的对象的像素信息) +// 计算符合条件的对象的像素点的信息,包括像素点个数,横纵坐标总和。该函数在 +// Host 端由 CPU 串行实现。 +static __host__ void // 该函数无返回值 +_objCalcPixelInfo( + ImageCuda *insubimg, // 输入图像 + unsigned char value, // 对象的像素值 + ObjPixelPosSumInfoInner *suminfo // 返回的对象像素信息。 +); + +// Kernel 函数: _objCalcCovMatrixKer(计算符合条件的对象的协方差矩阵) +// 根据符合条件的像素点的信息和中心值,计算对象的协方差矩阵。 +static __global__ void // Kernel 函数无返回值 +_objCalcCovMatrixKer( + ImageCuda inimg, // 输入图像 + Coordinate *expcenter, // 像素坐标的期望 + unsigned char value, // 对象的像素值 + int blksize, // 块大小,等于 blocksize.x * + // blocksize.y * blocksize.z。 + int blksize2p, // 优化的块大小,方便规约方法。 + CovarianceMatrix *covmatrix // 协方差矩阵 +); + +// Kernel 函数: _brCalcExtreamPointKer(计算对象包围矩形的边界点) +// 根据对象的旋转信息和中心点,通过逐次比较,找出对象的包围矩形的边界点。 +static __global__ void // Kernel 函数无返回值 +_brCalcExtreamPointKer( + ImageCuda inimg, // 输入图像 + unsigned char value, // 对象的像素值 + int blksize, // 块大小,等于 blocksize.x * + // blocksize.y * blocksize.z。 + int blksize2p, // 优化的块大小,方便规约方法。 + CoordinateInt *expcenter, // 像素坐标的期望, 类型为 int + RotationInfo *rtinfo, // 旋转矩阵信息 + BoundBoxInt *boundbox // 包围矩形信息 +); + +// 函数:_calcBoundingRectParam(计算包围矩形的参数) +// 计算BoundingRect使用到的参数,计算结果用于随后的成员方法,这样做的目的是简 +// 化代码,维护方便。 +static __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 +_calcBoundingRectParam( + Image *inimg, // 输入图像 + unsigned char value, // 对象的像素值 + RotationInfo *rotateinfo, // 旋转信息 + BoundBoxInt *boundboxint // 包围矩形的四个点 +); + +// Host 函数: brCelling2PInner(计算适合规约方法的共享内存长度) +// 这个函数的目的是通过迭代的方法找出不小于 n 的最大的 2^n。 +// 结果是 n2p,用来作为规约方法的共享内存的长度。 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR。 +brCelling2PInner( + int n, // 块的大小。 + int *n2p // 计算的适合规约方法的共享内存长度。 +); + +// Kernel 函数: _objCalcExpectedCenterKer(计算符合条件的对象的中心点坐标) +static __global__ void // Kernel 函数无返回值。 +_objCalcExpectedCenterKer( + ObjPixelPosSumInfoInner *pixelsuminfo, // 对象的像素信息。 + Coordinate *expcenter // 对象的中心点坐标。 +); + +// Kernel 函数:_brCalcParamforExtreamPointKer +//(为核函数 _brCalcExtreamPointKer 计算参数) +static __global__ void // Kernel 函数无返回值 +_brCalcParamforExtreamPointKer( + CovarianceMatrix *covmatrix, // 协方差矩阵 + RotationInfo *rtinfo, // 旋转信息 + BoundBoxInt *bboxint, // 包围矩形 + Coordinate *expcenter, // 中心点坐标 + Coordinate *rtexpcenter, // 旋转后的中心点坐标(float 类型) + CoordinateInt *expcenterint // 旋转后中心点坐标(int 类型) +); + +// Host 函数: brCelling2PInner(计算适合规约方法的共享内存长度) +// 这个函数的目的是通过迭代的方法找出不小于 n 的最大的 2^n。 +// 结果是 n2p,用来作为规约方法的共享内存的长度。 +__host__ int brCelling2PInner(int n, int *n2p) +{ + // 局部变量 i。 + int i; + + // 检查输出指针是否为 NULL。 + if (n2p == NULL) + return NULL_POINTER; + + // 计算找出不小于 n 的最大的 2^n。 + for (i = 1; i < BR_LARGE_ENOUGH; i <<= 1) { + // 如果找到了,就返回正确。 + if (i >= n) { + *n2p = i; + return NO_ERROR; + } + } + + // 如果找不到,就返回错误。 + return UNKNOW_ERROR; +} + +// Kernel 函数: _objCalcPixelInfoKer(计算符合条件的对象的像素信息) +static __global__ void _objCalcPixelInfoKer( + ImageCuda inimg, unsigned char value, + int blksize, int blksize2p, + ObjPixelPosSumInfoInner *suminfo) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理多个输出像素,这多个像素位于统一列的相邻多行 + // 上,因此,对于 r 需要进行乘以 FINDPIXEL_PACK_LEVEL 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) << FINDPIXEL_PACK_LEVEL; + + // 本地变量。inidx 为块内索引。 + int inidx = threadIdx.y * blockDim.x + threadIdx.x; + int inidx2; + int currdsize; + ObjPixelPosSumInfoInner blksuminfo_temp; + + // 声明共享内存。 + extern __shared__ ObjPixelPosSumInfoInner blksuminfo[]; + + // 初始化。 + blksuminfo_temp.pixelCount = 0UL; + blksuminfo_temp.posSumX = 0UL; + blksuminfo_temp.posSumY = 0UL; + + // 找到图像中符合条件的像素点,计算像素点的数量和坐标总和。 + do { + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inindex = r * inimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inindex]; + + // 如果当前的像素值为 value,那么像素点的计数加 1,x 和 y 的坐标总和累 + // 加。由于 c 和 r 只是 ROI 图像中的坐标索引,所以需加上 ROI 的边界值。 + if (intemp == value) { + blksuminfo_temp.pixelCount++; + blksuminfo_temp.posSumX += c; + blksuminfo_temp.posSumY += r; + } + + // 宏:FINDPIXEL_KERNEL_MAIN_PHASE + // 该宏定义了一个线程中进行的计算,计算下一个像素点和对应的操作。 + // 使用宏定义简化代码 +#define FINDPIXEL_KERNEL_MAIN_PHASE \ + if (++r >= inimg.imgMeta.height) \ + break; \ + inindex += inimg.pitchBytes; \ + intemp = inimg.imgMeta.imgData[inindex]; \ + if (intemp == value) { \ + blksuminfo_temp.pixelCount++; \ + blksuminfo_temp.posSumX += c; \ + blksuminfo_temp.posSumY += r; \ + } + +#define FINDPIXEL_KERNEL_MAIN_PHASEx2 \ + FINDPIXEL_KERNEL_MAIN_PHASE \ + FINDPIXEL_KERNEL_MAIN_PHASE + +#define FINDPIXEL_KERNEL_MAIN_PHASEx4 \ + FINDPIXEL_KERNEL_MAIN_PHASEx2 \ + FINDPIXEL_KERNEL_MAIN_PHASEx2 + +#define FINDPIXEL_KERNEL_MAIN_PHASEx8 \ + FINDPIXEL_KERNEL_MAIN_PHASEx4 \ + FINDPIXEL_KERNEL_MAIN_PHASEx4 + +#define FINDPIXEL_KERNEL_MAIN_PHASEx16 \ + FINDPIXEL_KERNEL_MAIN_PHASEx8 \ + FINDPIXEL_KERNEL_MAIN_PHASEx8 + + // 对于线程中的最后一个像素处理操作。 + FINDPIXEL_KERNEL_MAIN_PHASE + + // 根据不同的 FINDPIXEL_PACK_LEVEL 定义,进行不同的线程操作 +#if (FINDPIXEL_PACK_LEVEL >= 2) + FINDPIXEL_KERNEL_MAIN_PHASEx2 +# if (FINDPIXEL_PACK_LEVEL >= 3) + FINDPIXEL_KERNEL_MAIN_PHASEx4 +# if (FINDPIXEL_PACK_LEVEL >= 4) + FINDPIXEL_KERNEL_MAIN_PHASEx8 +# if (FINDPIXEL_PACK_LEVEL >= 5) + FINDPIXEL_KERNEL_MAIN_PHASEx16 +# endif +# endif +# endif +#endif + +#undef FINDPIXEL_KERNEL_MAIN_PHASEx16 +#undef FINDPIXEL_KERNEL_MAIN_PHASEx8 +#undef FINDPIXEL_KERNEL_MAIN_PHASEx4 +#undef FINDPIXEL_KERNEL_MAIN_PHASEx2 +#undef FINDPIXEL_KERNEL_MAIN_PHASE + } while (0); + + // 将线程中计算得到的临时变量赋给共享内存。 + blksuminfo[inidx].pixelCount = blksuminfo_temp.pixelCount; + blksuminfo[inidx].posSumX = blksuminfo_temp.posSumX; + blksuminfo[inidx].posSumY = blksuminfo_temp.posSumY; + + __syncthreads(); + + // 对于 blksize2p 长度的值进行折半累加。 + currdsize = (blksize2p >> 1); + inidx2 = inidx + currdsize; + if (inidx2 < blksize) { + blksuminfo[inidx].pixelCount += blksuminfo[inidx2].pixelCount ; + blksuminfo[inidx].posSumX += blksuminfo[inidx2].posSumX; + blksuminfo[inidx].posSumY += blksuminfo[inidx2].posSumY; + } + __syncthreads(); + + // 使用规约的方法,累加像素信息值到共享内存的开头。 + for (currdsize >>= 1; currdsize > 0; currdsize >>= 1) { + if (inidx < currdsize) { + inidx2 = inidx + currdsize; + blksuminfo[inidx].pixelCount += blksuminfo[inidx2].pixelCount; + blksuminfo[inidx].posSumX += blksuminfo[inidx2].posSumX; + blksuminfo[inidx].posSumY += blksuminfo[inidx2].posSumY; + } + __syncthreads(); + } + + // 把共享内存的像素信息值累加到总和上。每个线程块第一个线程会进行这个操作。 + if (inidx == 0 && blksuminfo[0].pixelCount != 0) { + atomicAdd(&(suminfo->pixelCount), blksuminfo[0].pixelCount); + atomicAdd(&(suminfo->posSumX), blksuminfo[0].posSumX); + atomicAdd(&(suminfo->posSumY), blksuminfo[0].posSumY); + } +} + +// Host 函数:_objCalcPixelInfo(计算符合条件的对象的像素信息) +static __host__ void _objCalcPixelInfo( + ImageCuda *insubimg, unsigned char value, + ObjPixelPosSumInfoInner *suminfo) +{ + // 检查输入指针的合法性。 + if (insubimg == NULL || suminfo == NULL) + return /*NULL_POINTER*/; + + // 初始化返回值,为下面的累加做准备。 + suminfo->pixelCount = 0UL; + suminfo->posSumX = 0UL; + suminfo->posSumY = 0UL; + + // 迭代图像内所有的像素点,判断每个像素点的像素值,如果像素值满足要求,则累 + // 加相应的计数信息。 + for (int r = 0; r < insubimg->imgMeta.height; r++) { + int inidx = r * insubimg->pitchBytes; + for (int c = 0; c < insubimg->imgMeta.width; c++) { + unsigned char inpixel = insubimg->imgMeta.imgData[inidx]; + if (inpixel == value) { + suminfo->pixelCount += 1; + suminfo->posSumX += c; + suminfo->posSumY += r; + } + inidx++; + } + } + //return NO_ERROR; +} + + +// Kernel 函数: _objCalcExpectedCenterKer(计算符合条件的对象的中心点坐标) +static __global__ void _objCalcExpectedCenterKer( + ObjPixelPosSumInfoInner *pixelsuminfo, Coordinate *expcenter) +{ + // 利用符合条件的像素点的坐标总和与坐标个数,计算对象的中心点坐标。 + expcenter->x = (float)pixelsuminfo->posSumX / + (float)pixelsuminfo->pixelCount; + expcenter->y = (float)pixelsuminfo->posSumY / + (float)pixelsuminfo->pixelCount; +} + +// Host 函数: _objCalcExpectedCenter(计算符合条件的对象的中心点坐标) +static __host__ void _objCalcExpectedCenter( + ObjPixelPosSumInfoInner *pixelsuminfo, Coordinate *expcenter) +{ + // 利用符合条件的像素点的坐标总和与坐标个数,计算对象的中心点坐标。 + expcenter->x = (float)pixelsuminfo->posSumX / + (float)pixelsuminfo->pixelCount; + expcenter->y = (float)pixelsuminfo->posSumY / + (float)pixelsuminfo->pixelCount; +} + +// Kernel 函数: _objCalcCovMatrixKer(计算符合条件的对象的协方差矩阵) +static __global__ void _objCalcCovMatrixKer( + ImageCuda inimg, Coordinate *expcenter, + unsigned char value, int blksize, int blksize2p, + CovarianceMatrix *covmatrix) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理多个输出像素,这多个像素位于统一列的相邻多行 + // 上,因此,对于 r 需要进行乘以 COMPUTECOV_PACK_LEVEL 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) << + COMPUTECOV_PACK_LEVEL; + + // 局部变量 + int inidx = threadIdx.y * blockDim.x + threadIdx.x; + int inidx2, currdsize; + float dx, dy, dxx, dxy, dyy; + CovarianceMatrix cov_temp; + + // 声明共享内存 + extern __shared__ CovarianceMatrix shdcov[]; + + // 临时变量初始化 + cov_temp.a11 = 0.0f; + cov_temp.a12 = 0.0f; + cov_temp.a22 = 0.0f; + + // 找到图像中符合条件的像素点,计算对象的协方差矩阵。 + do { + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inindex = r * inimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inindex]; + + // 计算坐标值减去中心点坐标值。 + dx = c - expcenter->x; + dy = r - expcenter->y; + + // 计算协方差矩阵的各个值. + dxx = dx * dx; + dxy = dx * dy; + dyy = dy * dy; + + // 如果当前点的像素值符合要求,那么累加到协方差元素中。 + if (intemp == value) { + cov_temp.a11 += dxx; + cov_temp.a12 += dxy; + cov_temp.a22 += dyy; + } + + // 宏:COMPUTECOV_KERNEL_MAIN_PHASE + // 该宏定义了一个线程中进行的计算,计算下一个像素点和对应的操作。 + // 使用宏定义简化代码 +#define COMPUTECOV_KERNEL_MAIN_PHASE \ + if (++r >= inimg.imgMeta.height) \ + break; \ + dxy += dx; \ + dyy += dy + dy + 1.0f; \ + dy += 1.0f; \ + inindex += inimg.pitchBytes; \ + intemp = inimg.imgMeta.imgData[inindex]; \ + if (intemp == value) { \ + cov_temp.a11 += dxx; \ + cov_temp.a12 += dxy; \ + cov_temp.a22 += dyy; \ + } + +#define COMPUTECOV_KERNEL_MAIN_PHASEx2 \ + COMPUTECOV_KERNEL_MAIN_PHASE \ + COMPUTECOV_KERNEL_MAIN_PHASE + +#define COMPUTECOV_KERNEL_MAIN_PHASEx4 \ + COMPUTECOV_KERNEL_MAIN_PHASEx2 \ + COMPUTECOV_KERNEL_MAIN_PHASEx2 + +#define COMPUTECOV_KERNEL_MAIN_PHASEx8 \ + COMPUTECOV_KERNEL_MAIN_PHASEx4 \ + COMPUTECOV_KERNEL_MAIN_PHASEx4 + +#define COMPUTECOV_KERNEL_MAIN_PHASEx16 \ + COMPUTECOV_KERNEL_MAIN_PHASEx8 \ + COMPUTECOV_KERNEL_MAIN_PHASEx8 + + // 对于线程中的最后一个像素处理操作。 + COMPUTECOV_KERNEL_MAIN_PHASE + + // 根据不同的 COMPUTECOV_PACK_LEVEL 定义,进行不同的线程操作 +#if (COMPUTECOV_PACK_LEVEL >= 2) + COMPUTECOV_KERNEL_MAIN_PHASEx2 +# if (COMPUTECOV_PACK_LEVEL >= 3) + COMPUTECOV_KERNEL_MAIN_PHASEx4 +# if (COMPUTECOV_PACK_LEVEL >= 4) + COMPUTECOV_KERNEL_MAIN_PHASEx8 +# if (COMPUTECOV_PACK_LEVEL >= 5) + COMPUTECOV_KERNEL_MAIN_PHASEx16 +# endif +# endif +# endif +#endif + +#undef COMPUTECOV_KERNEL_MAIN_PHASEx16 +#undef COMPUTECOV_KERNEL_MAIN_PHASEx8 +#undef COMPUTECOV_KERNEL_MAIN_PHASEx4 +#undef COMPUTECOV_KERNEL_MAIN_PHASEx2 +#undef COMPUTECOV_KERNEL_MAIN_PHASE + } while (0); + + // 累加线程计算得到的临时变量到共享内存内。 + shdcov[inidx].a11 = cov_temp.a11; + shdcov[inidx].a12 = cov_temp.a12; + shdcov[inidx].a22 = cov_temp.a22; + + __syncthreads(); + + // 对于 blksize2p 长度的值进行折半累加 + currdsize = (blksize2p >> 1); + inidx2 = inidx + currdsize; + if (inidx2 < blksize) { + shdcov[inidx].a11 += shdcov[inidx2].a11; + shdcov[inidx].a12 += shdcov[inidx2].a12; + shdcov[inidx].a22 += shdcov[inidx2].a22; + } + __syncthreads(); + + // 使用规约的方法,累加像素信息值到共享内存的开头。 + for (currdsize >>= 1; currdsize > 0; currdsize >>= 1) { + if (inidx < currdsize) { + inidx2 = inidx + currdsize; + shdcov[inidx].a11 += shdcov[inidx2].a11; + shdcov[inidx].a12 += shdcov[inidx2].a12; + shdcov[inidx].a22 += shdcov[inidx2].a22; + } + __syncthreads(); + } + + // 把共享内存的像素信息值累加到总和上。每个线程块的第一个线程会进行这个操 + // 作。 + if (inidx == 0) { + atomicAdd(&(covmatrix->a11), shdcov[0].a11); + atomicAdd(&(covmatrix->a12), shdcov[0].a12); + atomicAdd(&(covmatrix->a22), shdcov[0].a22); + } +} + +// Host 函数: _objCalcCovMatrix(计算符合条件的对象的协方差矩阵) +static __host__ void _objCalcCovMatrix( + ImageCuda *insubimg, Coordinate *expcenter, unsigned char value, + CovarianceMatrix *covmatrix) +{ + // 检查输入指针的合法性。 + if (insubimg == NULL || expcenter == NULL || covmatrix == NULL) + return /*NULL_POINTER*/; + + // 初始化返回值,为下面的累加做准备。 + covmatrix->a11 = 0.0f; + covmatrix->a12 = 0.0f; + covmatrix->a22 = 0.0f; + + // 迭代图像内所有的像素点,判断每个像素点的像素值,如果像素值满足要求,则累 + // 加相应的计数信息。 + for (int r = 0; r < insubimg->imgMeta.height; r++) { + int inidx = r * insubimg->pitchBytes; + + // 计算坐标值减去中心点坐标值。 + float dx = 0.0f - expcenter->x; + float dy = r - expcenter->y; + + // 计算协方差矩阵的各个值. + float dxx = dx * dx; + float dxy = dx * dy; + float dyy = dy * dy; + + for (int c = 0; c < insubimg->imgMeta.width; c++) { + unsigned char inpixel = insubimg->imgMeta.imgData[inidx]; + // 如果当前坐标满足要求,则进行偏移量的累加。 + if (inpixel == value) { + covmatrix->a11 += dxx; + covmatrix->a12 += dxy; + covmatrix->a22 += dyy; + } + // 利用两个点之间坐标相关性减少一部分计算。 + inidx++; + dxx += 2 * dx + 1.0f; + dxy += dy; + dx += 1.0f; + } + } + //return NO_ERROR; +} + +// 函数:_brCalcParamforExtreamPointIn(为核函数 _brCalcExtreamPointKer +// 计算参数) +static __host__ __device__ void _brCalcParamforExtreamPointIn( + CovarianceMatrix *covmatrix, + RotationInfo *rtinfo, + Coordinate *expcenter, + Coordinate *rtexpcenter) +{ + // 局部变量。 + float apd, amd, bmc, det; + float eigen, solx, soly, soldt; + + // 为计算矩阵的特征值做准备计算。 + apd = covmatrix->a11 + covmatrix->a22; + amd = covmatrix->a11 - covmatrix->a22; + bmc = covmatrix->a12 * covmatrix->a12; + + // 计算矩阵的特征值。 + det = sqrt(4.0f * bmc + amd * amd); + eigen = (apd + det) / 2.0f; + + // 计算旋转角度通过 asin()。 + // 求解方程式 (covmatrix - eigen * E) * sol = 0 + solx = covmatrix->a12 + covmatrix->a22 - eigen; + soly = eigen - covmatrix->a11 - covmatrix->a12; + soldt = sqrt(solx * solx + soly * soly); + + // 如果解的的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (solx < 0) { + solx = -solx; + soly = -soly; + } + + // 计算旋转角度信息。 + rtinfo->sin = soly / soldt; + rtinfo->cos = solx / soldt; + rtinfo->radian = asin(rtinfo->sin); + + // 当旋转角度为负时,进行调整操作。 + if (rtinfo->radian < 0) { + (rtinfo)->radian = -(rtinfo)->radian; + (rtinfo)->sin = -(rtinfo)->sin; + (rtinfo)->cos = (rtinfo)->cos; + } + + // 根据旋转信息,计算中心点 expcenter 旋转后的坐标 rtexpcenter。 + rtexpcenter->x = expcenter->x * rtinfo->cos - expcenter->y * rtinfo->sin; + rtexpcenter->y = expcenter->x * rtinfo->sin + expcenter->y * rtinfo->cos; +} + +// Kernel 函数:_brCalcParamforExtreamPointKer(为核函数 _brCalcExtreamPointKer +// 计算参数) +static __global__ void _brCalcParamforExtreamPointKer( + CovarianceMatrix *covmatrix, + RotationInfo *rtinfo, + BoundBoxInt *bboxint, + Coordinate *expcenter, + Coordinate *rtexpcenter, + CoordinateInt *expcenterint) +{ + _brCalcParamforExtreamPointIn(covmatrix, rtinfo, expcenter, rtexpcenter); + + // 初始化包围矩形的信息,边界的四个值全部用中心点的值来初始化。 + // 初始化中心点坐标。 + expcenterint->x = (int)rtexpcenter->x; + expcenterint->y = (int)rtexpcenter->y; + + // 初始化包围矩形的边界。 + bboxint->bottom = expcenterint->y; + bboxint->top = expcenterint->y; + bboxint->left = expcenterint->x; + bboxint->right = expcenterint->x; +} + +// Host 函数:_brCalcParamforExtreamPoint(计算旋转角度与初始化) +static __host__ void _brCalcParamforExtreamPoint( + CovarianceMatrix *covmatrix, + RotationInfo *rtinfo, + BoundBox *bbox, + Coordinate *expcenter, + Coordinate *rtexpcenter) +{ + _brCalcParamforExtreamPointIn(covmatrix, rtinfo, expcenter, rtexpcenter); + + // 初始化包围矩形的边界。 + bbox->bottom = rtexpcenter->y; + bbox->top = rtexpcenter->y; + bbox->left = rtexpcenter->x; + bbox->right = rtexpcenter->x; +} + +// Kernel 函数: _brCalcExtreamPointKer(计算对象包围盒的边界点) +static __global__ void _brCalcExtreamPointKer( + ImageCuda inimg, unsigned char value, + int blksize, int blksize2p, + CoordinateInt *expcenter, RotationInfo *rtinfo, + BoundBoxInt *boundbox) +{ + // 局部变量 + int ptsor[2]; + float pt[2]; + int x, y, x_, y_; + + // 块内索引。 + int inidx = threadIdx.y * blockDim.x + threadIdx.x; + int inidx2, currdsize; + BoundBoxInt bbox; + + // 声明共享内存。 + extern __shared__ int shdbbox[]; + int *shdbboxLeft = shdbbox; + int *shdbboxRight = shdbboxLeft + blksize; + int *shdbboxTop = shdbboxRight + blksize; + int *shdbboxBottom = shdbboxTop + blksize; + + // 计算线程对应的输出点的位置,其中 ptsor[0] 和 ptsor[1] 分别表示线程处理的 + // 像素点的坐标的 x 和 y 分量(其中,ptsor[1] 表示 column;ptsor[1] 表示 + // row)。由于我们采用了并行度缩减的策略,令一个线程处理多个输出像素,这多 + // 个像素位于统一列的相邻多行上,因此,对于 r 需要进行乘以 + // EXTREAMPOINT_PACK_LEVEL 计算。 + ptsor[0] = blockIdx.x * blockDim.x + threadIdx.x; + ptsor[1] = (blockIdx.y * blockDim.y + threadIdx.y) << + EXTREAMPOINT_PACK_LEVEL; + + // 初始化包围矩形 + bbox.left = expcenter->x; + bbox.right = expcenter->x; + bbox.top = expcenter->y; + bbox.bottom = expcenter->y; + + // 找到图像中符合条件的像素点,计算对象的包围矩形的边界。 + do { + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (ptsor[0] >= inimg.imgMeta.width || + ptsor[1] >= inimg.imgMeta.height) + break; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inindex = ptsor[1] * inimg.pitchBytes + ptsor[0]; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inindex]; + + // 如果像素值符合要求,计算旋转以后的点,比较找出边界值。 + if (intemp == value) { + RECT_ROTATE_POINT(ptsor, pt, *rtinfo); + x = (int)(pt[0]); + x_ = x + 1; + y = (int)(pt[1]); + y_ = y + 1; + + // 比较当前像素坐标和初始值。 + bbox.left = min(bbox.left, x); + bbox.right = max(bbox.right, x_); + bbox.bottom = min(bbox.bottom, y); + bbox.top = max(bbox.top, y_); + } + + // 宏:EXTREAMPOINT_KERNEL_MAIN_PHASE + // 该宏定义了一个线程中进行的计算,计算下一个像素点和对应的操作。 + // 使用宏定义简化代码 +#define EXTREAMPOINT_KERNEL_MAIN_PHASE \ + if (++ptsor[1] >= inimg.imgMeta.height) \ + break; \ + inindex = ptsor[1] * inimg.pitchBytes + ptsor[0]; \ + intemp = inimg.imgMeta.imgData[inindex]; \ + if (intemp == value) { \ + RECT_ROTATE_POINT(ptsor, pt, *rtinfo); \ + x = (int)(pt[0]); \ + x_ = x + 1; \ + y = (int)(pt[1]); \ + y_ = y + 1; \ + bbox.left = min(bbox.left, x); \ + bbox.right = max(bbox.right, x_); \ + bbox.bottom = min(bbox.bottom, y); \ + bbox.top = max(bbox.top, y_); \ + } + +#define EXTREAMPOINT_KERNEL_MAIN_PHASEx2 \ + EXTREAMPOINT_KERNEL_MAIN_PHASE \ + EXTREAMPOINT_KERNEL_MAIN_PHASE + +#define EXTREAMPOINT_KERNEL_MAIN_PHASEx4 \ + EXTREAMPOINT_KERNEL_MAIN_PHASEx2 \ + EXTREAMPOINT_KERNEL_MAIN_PHASEx2 + +#define EXTREAMPOINT_KERNEL_MAIN_PHASEx8 \ + EXTREAMPOINT_KERNEL_MAIN_PHASEx4 \ + EXTREAMPOINT_KERNEL_MAIN_PHASEx4 + +#define EXTREAMPOINT_KERNEL_MAIN_PHASEx16 \ + EXTREAMPOINT_KERNEL_MAIN_PHASEx8 \ + EXTREAMPOINT_KERNEL_MAIN_PHASEx8 + + // 对于线程中的最后一个像素处理操作。 + EXTREAMPOINT_KERNEL_MAIN_PHASE + + // 根据不同的 EXTREAMPOINT_PACK_LEVEL 定义,进行不同的线程操作 +#if (EXTREAMPOINT_PACK_LEVEL >= 2) + EXTREAMPOINT_KERNEL_MAIN_PHASEx2 +# if (EXTREAMPOINT_PACK_LEVEL >= 3) + EXTREAMPOINT_KERNEL_MAIN_PHASEx4 +# if (EXTREAMPOINT_PACK_LEVEL >= 4) + EXTREAMPOINT_KERNEL_MAIN_PHASEx8 +# if (EXTREAMPOINT_PACK_LEVEL >= 5) + EXTREAMPOINT_KERNEL_MAIN_PHASEx16 +# endif +# endif +# endif +#endif + +#undef EXTREAMPOINT_KERNEL_MAIN_PHASEx16 +#undef EXTREAMPOINT_KERNEL_MAIN_PHASEx8 +#undef EXTREAMPOINT_KERNEL_MAIN_PHASEx4 +#undef EXTREAMPOINT_KERNEL_MAIN_PHASEx2 +#undef EXTREAMPOINT_KERNEL_MAIN_PHASE + } while (0); + + // 比较结果存入共享内存中。 + shdbboxLeft[inidx] = bbox.left; + shdbboxRight[inidx] = bbox.right + 1; + shdbboxBottom[inidx] = bbox.bottom; + shdbboxTop[inidx] = bbox.top; + + __syncthreads(); + + // 对于 blksize2p 长度的值进行折半比较包围矩形的边界值。 + currdsize = (blksize2p >> 1); + inidx2 = inidx + currdsize; + if (inidx2 < blksize) { + atomicMin(&(shdbboxLeft[inidx]), shdbboxLeft[inidx2]); + atomicMax(&(shdbboxRight[inidx]), shdbboxRight[inidx2]); + atomicMin(&(shdbboxBottom[inidx]), shdbboxBottom[inidx2]); + atomicMax(&(shdbboxTop[inidx]), shdbboxTop[inidx2]); + } + __syncthreads(); + + // 使用规约的方法,把比较的结果保存到共享内存的开头。 + for (currdsize >>= 1; currdsize > 0; currdsize >>= 1) { + if (inidx < currdsize) { + inidx2 = inidx + currdsize; + atomicMin(&(shdbboxLeft[inidx]), shdbboxLeft[inidx2]); + atomicMax(&(shdbboxRight[inidx]), shdbboxRight[inidx2]); + atomicMin(&(shdbboxBottom[inidx]), shdbboxBottom[inidx2]); + atomicMax(&(shdbboxTop[inidx]), shdbboxTop[inidx2]); + } + __syncthreads(); + } + + // 比较共享内存里的边界值和初始值,更新边界值。每个线程块的第一个线程会进行 + // 这个操作。 + if (inidx == 0) { + if (shdbboxLeft[0] != expcenter->x) + atomicMin(&(boundbox->left), shdbboxLeft[0]); + if (shdbboxRight[0] != expcenter->x) + atomicMax(&(boundbox->right), shdbboxRight[0]); + if (shdbboxBottom[0] != expcenter->y) + atomicMin(&(boundbox->bottom), shdbboxBottom[0]); + if (shdbboxTop[0] != expcenter->y) + atomicMax(&(boundbox->top), shdbboxTop[0]); + } +} + +// Host 函数: _brCalcExtreamPoint(计算对象包围盒的边界点) +static __host__ void _brCalcExtreamPoint( + ImageCuda *insubimg, unsigned char value, + Coordinate *expcenter, RotationInfo *rtinfo, + BoundBox *boundbox) +{ + // 检查输入指针的合法性。 + if (insubimg == NULL || expcenter == NULL || + rtinfo == NULL || boundbox == NULL) + return /*NULL_POINTER*/; + + // 迭代图像内所有的像素点,判断每个像素点的像素值,如果像素值满足要求,则累 + // 加相应的计数信息。 + int ptsor[2]; + float pt[2]; + for (ptsor[1] = 0; ptsor[1] < insubimg->imgMeta.height; ptsor[1]++) { + int inidx = ptsor[1] * insubimg->pitchBytes; + for (ptsor[0] = 0; ptsor[0] < insubimg->imgMeta.width; ptsor[0]++) { + unsigned char inpixel = insubimg->imgMeta.imgData[inidx]; + if (inpixel == value) { + RECT_ROTATE_POINT(ptsor, pt, *rtinfo); + boundbox->left = min(boundbox->left, pt[0]); + boundbox->right = max(boundbox->right, pt[0]); + boundbox->bottom = min(boundbox->bottom, pt[1]); + boundbox->top = max(boundbox->top, pt[1]); + } + inidx++; + } + } + //return NO_ERROR; + +} + +// 函数:_calcBoundingRectParam(计算包围矩形的参数) +static __host__ int _calcBoundingRectParam(Image *inimg, unsigned char value, + RotationInfo *rotateinfo, + BoundBoxInt *boundboxint) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 局部变量 + CoordinateInt *rtexpcenterint_dev; + Coordinate *expcenter_dev, *rtexpcenter_dev; + CovarianceMatrix *covmatrix_dev; + RotationInfo *rtinfo_dev, *rtinfon_dev; + BoundBoxInt *bdboxint_dev; + ObjPixelPosSumInfoInner *pixelsuminfo_dev; + float *temp_dev; + + // 在设备端申请内存,然后分配给各个变量。 + cudaError_t cuerrcode; + cuerrcode = cudaMalloc((void **)&temp_dev, + sizeof (ObjPixelPosSumInfoInner) + + 2 * sizeof (Coordinate) + + 2 * sizeof (RotationInfo) + + sizeof (CovarianceMatrix) + + sizeof (CoordinateInt) + + sizeof (BoundBoxInt)); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + // 为变量分配内存。 + pixelsuminfo_dev = (ObjPixelPosSumInfoInner*)(temp_dev); + expcenter_dev = (Coordinate*)(pixelsuminfo_dev + 1); + rtexpcenter_dev = expcenter_dev + 1; + rtinfo_dev = (RotationInfo*)(rtexpcenter_dev + 1); + rtinfon_dev = rtinfo_dev + 1; + covmatrix_dev = (CovarianceMatrix*)(rtinfon_dev + 1); + rtexpcenterint_dev = (CoordinateInt*)(covmatrix_dev + 1); + bdboxint_dev = (BoundBoxInt*)(rtexpcenterint_dev + 1); + + // 初始化存放像素信息的数组。 + cuerrcode = cudaMemset(pixelsuminfo_dev, 0, + sizeof (ObjPixelPosSumInfoInner)); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + // 设置存放协方差矩阵的数组初始化全为 0。 + cuerrcode = cudaMemset(covmatrix_dev, 0, sizeof (CovarianceMatrix)); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + // 计算调用 _objCalcPixelInfoKer 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + int height = (insubimgCud.imgMeta.height + + FINDPIXEL_PACK_MASK) / FINDPIXEL_PACK_NUM; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 计算 _objCalcPixelInfoKer 共享内存的长度 blksize2p。 + int blkthdcnt, blksize2p; + blkthdcnt = blocksize.x * blocksize.y * blocksize.z; + brCelling2PInner(blkthdcnt, &blksize2p); + + // 计算对象的像素信息。 + _objCalcPixelInfoKer<<>>( + insubimgCud, value, + blkthdcnt, blksize2p, pixelsuminfo_dev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 计算中心点。 + _objCalcExpectedCenterKer<<<1, 1>>>(pixelsuminfo_dev, expcenter_dev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 计算调用 _objCalcCovMatrixKer 函数的线程块的尺寸和线程块的数量。 + height = (insubimgCud.imgMeta.height + + COMPUTECOV_PACK_MASK) / COMPUTECOV_PACK_NUM; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 计算 _objCalcCovMatrixKer 共享内存的长度 blksize2p。 + blkthdcnt = blocksize.x * blocksize.y * blocksize.z; + brCelling2PInner(blkthdcnt, &blksize2p); + + // 计算协方差矩阵。 + _objCalcCovMatrixKer<<>>( + insubimgCud, expcenter_dev, value, blkthdcnt, blksize2p, + covmatrix_dev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 计算 _brCalcExtreamPointKer 需要用到的一些参数 + _brCalcParamforExtreamPointKer<<<1, 1>>>(covmatrix_dev, rtinfo_dev, + bdboxint_dev, expcenter_dev, + rtexpcenter_dev, + rtexpcenterint_dev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 计算调用 _brCalcExtreamPointKer 函数的线程块的尺寸和线程块的数量。 + height = (insubimgCud.imgMeta.height + EXTREAMPOINT_PACK_MASK) / + EXTREAMPOINT_PACK_NUM; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 计算 _brCalcExtreamPointKer 共享内存的长度 blksize2p。 + blkthdcnt = blocksize.x * blocksize.y * blocksize.z; + brCelling2PInner(blkthdcnt, &blksize2p); + + // 计算包围矩形的边界点。 + _brCalcExtreamPointKer<<>>( + insubimgCud, value, blkthdcnt, blksize2p, + rtexpcenterint_dev, rtinfo_dev, + bdboxint_dev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 从设备端拷贝回主存。 + cuerrcode = cudaMemcpy(boundboxint, bdboxint_dev, + sizeof (BoundBoxInt), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + cuerrcode = cudaMemcpy(rotateinfo, rtinfo_dev, + sizeof (RotationInfo), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + cudaFree(temp_dev); + return NO_ERROR; +} + +// 函数:_calcBoundingRectParamHost(计算包围矩形的参数) +static __host__ int _calcBoundingRectParamHost( + Image *inimg, unsigned char value, + RotationInfo *rotateinfo, BoundBox *boundbox) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || rotateinfo == NULL || boundbox == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToHost(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 局部变量 + Coordinate expcenter, rtexpcenter; + CovarianceMatrix covmatrix; + ObjPixelPosSumInfoInner pixelsuminfo; + + // 计算对象的像素信息。 + _objCalcPixelInfo(&insubimgCud, value, &pixelsuminfo); + + // 计算中心点。 + _objCalcExpectedCenter(&pixelsuminfo, &expcenter); + + // 计算协方差矩阵。 + _objCalcCovMatrix(&insubimgCud, &expcenter, value, &covmatrix); + + // 计算 _brCalcExtreamPointKer 需要用到的一些参数 + _brCalcParamforExtreamPoint(&covmatrix, rotateinfo, + boundbox, &expcenter, &rtexpcenter); + + // 计算包围矩形的边界点。 + _brCalcExtreamPoint(&insubimgCud, value, &rtexpcenter, + rotateinfo, boundbox); + + return NO_ERROR; +} + + +// Host 成员方法:boundingRect(求像素值给定的对象的包围矩形) +__host__ int BoundingRect::boundingRect(Image *inimg, Quadrangle *outrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 局部变量 + RotationInfo rotateinfo; + BoundBoxInt bdboxint; + + // 调用函数_calcBoundingRectParam。 + errcode = _calcBoundingRectParam(inimg, value, &rotateinfo, &bdboxint); + if (errcode != NO_ERROR) + return errcode; + + // 计算包围矩形的角度。 + outrect->angle = RECT_RAD_TO_DEG(rotateinfo.radian); + + // 计算包围矩形的边界点值。 + Quadrangle temprect; + temprect.points[0][0] = bdboxint.left; + temprect.points[0][1] = bdboxint.top; + temprect.points[1][0] = bdboxint.right; + temprect.points[1][1] = bdboxint.top; + temprect.points[2][0] = bdboxint.right; + temprect.points[2][1] = bdboxint.bottom; + temprect.points[3][0] = bdboxint.left; + temprect.points[3][1] = bdboxint.bottom; + + // 计算旋转后的包围矩形的边界点值。即结果的边界点值。 + rotateinfo.sin = -rotateinfo.sin; + RECT_ROTATE_POINT(temprect.points[0], outrect->points[0], rotateinfo); + RECT_ROTATE_POINT(temprect.points[1], outrect->points[1], rotateinfo); + RECT_ROTATE_POINT(temprect.points[2], outrect->points[2], rotateinfo); + RECT_ROTATE_POINT(temprect.points[3], outrect->points[3], rotateinfo); + + return NO_ERROR; +} + +// Host 成员方法:boundingRectHost(求像素值给定的对象的包围矩形) +__host__ int BoundingRect::boundingRectHost(Image *inimg, Quadrangle *outrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 局部变量 + RotationInfo rotateinfo; + BoundBox bdbox; + + // 调用函数_calcBoundingRectParam。 + errcode = _calcBoundingRectParamHost(inimg, value, &rotateinfo, &bdbox); + if (errcode != NO_ERROR) + return errcode; + + // 计算包围矩形的角度。 + outrect->angle = RECT_RAD_TO_DEG(rotateinfo.radian); + + // 计算包围矩形的边界点值。 + Quadrangle temprect; + temprect.points[0][0] = bdbox.left; + temprect.points[0][1] = bdbox.top; + temprect.points[1][0] = bdbox.right; + temprect.points[1][1] = bdbox.top; + temprect.points[2][0] = bdbox.right; + temprect.points[2][1] = bdbox.bottom; + temprect.points[3][0] = bdbox.left; + temprect.points[3][1] = bdbox.bottom; + + // 计算旋转后的包围矩形的边界点值。即结果的边界点值。 + rotateinfo.sin = -rotateinfo.sin; + RECT_ROTATE_POINT(temprect.points[0], outrect->points[0], rotateinfo); + RECT_ROTATE_POINT(temprect.points[1], outrect->points[1], rotateinfo); + RECT_ROTATE_POINT(temprect.points[2], outrect->points[2], rotateinfo); + RECT_ROTATE_POINT(temprect.points[3], outrect->points[3], rotateinfo); + + return NO_ERROR; +} + +// Host 成员方法:boundingRect(求像素值给定的对象的包围矩形) +__host__ int BoundingRect::boundingRect(Image *inimg, DirectedRect *outrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 局部变量 + RotationInfo rotateinfo; + BoundBoxInt bdboxint; + float boxcenter[2]; + + // 调用函数_calcBoundingRectParam。 + errcode = _calcBoundingRectParam(inimg, value, &rotateinfo, &bdboxint); + if (errcode != NO_ERROR) + return errcode; + + // 计算旋转角。 + outrect->angle = RECT_RAD_TO_DEG(rotateinfo.radian); + + // 计算中心坐标。 + boxcenter[0] = (bdboxint.left + bdboxint.right) / 2.0f; + boxcenter[1] = (bdboxint.top + bdboxint.bottom) / 2.0f; + RECT_ROTATE_POINT(boxcenter, outrect->centerPoint, rotateinfo); + + // 计算矩形的长宽。 + outrect->length1 = bdboxint.right - bdboxint.left; + outrect->length2 = bdboxint.top - bdboxint.bottom; + + // 选择长的作为矩形的长。 + if (outrect->length1 < outrect->length2) { + int length_temp; + length_temp = outrect->length1; + outrect->length1 = outrect->length2; + outrect->length2 = length_temp; + } else { + // 对于旋转角度出现负值的情况,进行处理。 + if (outrect->angle < 0.0f) + outrect->angle += 90.0f; + else + outrect->angle -= 90.0f; + } + + return NO_ERROR; +} + +// Host 成员方法:boundingRectHost(求像素值给定的对象的包围矩形) +__host__ int BoundingRect::boundingRectHost(Image *inimg, DirectedRect *outrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 局部变量 + RotationInfo rotateinfo; + BoundBox bdbox; + float boxcenter[2]; + + // 调用函数_calcBoundingRectParam。 + errcode = _calcBoundingRectParamHost(inimg, value, &rotateinfo, &bdbox); + if (errcode != NO_ERROR) + return errcode; + + // 计算旋转角。 + outrect->angle = RECT_RAD_TO_DEG(rotateinfo.radian); + + // 计算中心坐标。 + boxcenter[0] = (bdbox.left + bdbox.right) / 2.0f; + boxcenter[1] = (bdbox.top + bdbox.bottom) / 2.0f; + RECT_ROTATE_POINT(boxcenter, outrect->centerPoint, rotateinfo); + + // 计算矩形的长宽。 + outrect->length1 = bdbox.right - bdbox.left; + outrect->length2 = bdbox.top - bdbox.bottom; + + // 选择长的作为矩形的长。 + if (outrect->length1 < outrect->length2) { + int length_temp; + length_temp = outrect->length1; + outrect->length1 = outrect->length2; + outrect->length2 = length_temp; + } else { + // 对于旋转角度出现负值的情况,进行处理。 + if (outrect->angle < 0.0f) + outrect->angle += 90.0f; + else + outrect->angle -= 90.0f; + } + + return NO_ERROR; +} + diff --git a/okano_3_0/BoundingRect.h b/okano_3_0/BoundingRect.h new file mode 100644 index 0000000..3c6373c --- /dev/null +++ b/okano_3_0/BoundingRect.h @@ -0,0 +1,138 @@ +// BoundingRect.h +// 创建人:刘瑶 +// +// 包围矩形(Bounding Rectangle) +// 功能说明:找出图像中给定点集的包围矩形,根据给定点集的协方差矩阵,找出主方 +// 向,求得包围矩形及相应参数。 +// +// 修订历史: +// 2012年09月06日(刘瑶) +// 初始版本 +// 2012年09月16日(刘瑶) +// 修改了一些错误和注释 +// 2012年10月25日(刘瑶) +// 按照最新版的编码规范对代码进行了调整,并修正了一些之前未发现的格式错误 +// 2012年11月23日(刘瑶) +// 添加对 *outrect 指针的有效性进行检查 +// 2013年03月19日(刘瑶) +// 修改了计算旋转角度的错误,将误加的弧度值改为角度值。 +// 2013年06月26日(刘瑶) +// 修正了单线程处理多个点的一处 Bug。 +// 2013年09月07日(于玉龙) +// 增加了串行的包围矩形算法实现。 +// 2013年09月22日(于玉龙) +// 修正了角度旋转过程中的计算 BUG。 + +#ifndef __BOUNDINGRECT_H__ +#define __BOUNDINGRECT_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "Rectangle.h" + +// 类:BoundingRect +// 继承自:无 +// 根据给定的对象的像素值,找出图像中的所要包围的对象,根据给定对象点集的协方差 +// 矩阵,找出主方向,求得包围矩形的相应参数。 +class BoundingRect { + +protected: + + // 成员变量:value(对象的像素值) + // 找出图像中符合条件的点集的像素值,范围是 [0, 255]。 + unsigned char value; + + // 成员变量:pixelCount(符合条件的像素点数量) + unsigned long long int pixelCount; + +public: + + // 构造函数:BoundingRect + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + BoundingRect() + { + // 使用默认值为类的各个成员变量赋值。 + this->value = 128; // 对象的像素值默认为 128。 + } + + // 构造函数:BoundingRect + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + BoundingRect( + unsigned char value // 对象的像素值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->value = 128; // 对象的像素值默认为 128。 + + // 根据参数列表中的值设定成员变量的初值 + setValue(value); + } + + // 成员方法:getValue(获取对象的像素值) + // 获取成员变量 value 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 value 的值 + getValue() const + { + // 返回 value 成员变量的值。 + return this->value; + } + + // 成员方法:setValue(设置对象的像素值) + // 设置成员变量 value 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setValue( + unsigned char value // 设定新的对象的像素值 + ) { + // 将 value 成员变量赋成新值。 + this->value = value; + + return NO_ERROR; + } + + // Host 成员方法:boundingRect(求像素值给定的对象的包围矩形) + // 根据给定的像素值在图像中找出对象,根据协方差的方法,找出对象的包围矩形, + // 输出为求得的包围矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + boundingRect( + Image *inimg, // 输入图像 + Quadrangle *outrect // 包围矩形 + ); + + // Host 成员方法:boundingRectHost(求像素值给定的对象的包围矩形) + // 根据给定的像素值在图像中找出对象,根据协方差的方法,找出对象的包围矩形, + // 输出为求得的包围矩形。该函数在 Host 端由 CPU 串行计算。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + boundingRectHost( + Image *inimg, // 输入图像 + Quadrangle *outrect // 包围矩形 + ); + + // Host 成员方法:boundingRect(求像素值给定的对象的包围矩形) + // 根据给定的像素值在图像中找出对象,根据协方差的方法,找出对象的包围矩形, + // 输出为求得的包围矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + boundingRect( + Image *inimg, // 输入图像 + DirectedRect *outrect // 包围矩形 + ); + + // Host 成员方法:boundingRectHost(求像素值给定的对象的包围矩形) + // 根据给定的像素值在图像中找出对象,根据协方差的方法,找出对象的包围矩形, + // 输出为求得的包围矩形。该函数在 Host 端由 CPU 串行计算。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + boundingRectHost( + Image *inimg, // 输入图像 + DirectedRect *outrect // 包围矩形 + ); +}; + +#endif + diff --git a/okano_3_0/ClusterLocalGray.cu b/okano_3_0/ClusterLocalGray.cu new file mode 100644 index 0000000..bf6ec43 --- /dev/null +++ b/okano_3_0/ClusterLocalGray.cu @@ -0,0 +1,286 @@ +// ClusterLocalGray.cu +// 实现图像的分类降噪操作 + +#include "ClusterLocalGray.h" +#include +#include + +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 16 +#define DEF_BLOCK_Y 16 + +// 宏:MAX_NB_SIDE_SPAN +// 像素点最大处理范围。 +#define MAX_NB_SIDE_SPAN 16 + +// 宏:IMG_SMEM_SPAN +// 一个 block 对应的有效共享内存大小。 +#define IMG_SMEM_SPAN 48 + +// 宏:GRAY_RESULTION +// 每一个 bin 的大小。 +#define GRAY_RESULTION 4 + +// 宏:GRAYBIN_NUM +// 将 256 个灰度值按 4 个为一组进行划分,划分成 64 个 bin。 +#define GRAYBIN_NUM 64 + +// 宏:CHECK_SHARED_MOMORY_DEBUG +// 条件编译开关。 +// #define CHECK_SHARED_MOMORY_DEBUG + +// __device__ 函数:checkSharedMemory(判断内存拷贝是否成功) +// 条件编译执行函数。判断 block 内是否正确地将图像相应位置拷贝到共享内存中。 +// 成功返回 0,不成功则返回 1。 +__device__ int checkSharedMemory( + unsigned char *imgSharedMem, // 共享内存数组 + ImageCuda inimg, // 输入图像 + int imgX, // block 对应的图像范围的开始 x 坐标 + int imgY, // block 对应的图像范围的开始 x 坐标 + int sharedMenSpan // 共享内存的大小 +); + +// __device__ 函数:checkSharedMemory(判断内存拷贝是否成功) +__device__ int checkSharedMemory(unsigned char *imgSharedMem, ImageCuda inimg, + int imgX, int imgY, int sharedMenSpan) +{ + for (int y = 0; y < sharedMenSpan; y++) { + for (int x = 0; x < sharedMenSpan; x++) { + // 如果共享内存与所对应的图像上区域有像素点值不同,则认为拷贝失败。 + if (imgSharedMem[y * sharedMenSpan + x] != + inimg.imgMeta.imgData[(imgY + y) * + inimg.pitchBytes + imgX + x]) { + + // 打印错误点的坐标。 + printf("%d, %d ", imgSharedMem[y * sharedMenSpan + x], + inimg.imgMeta.imgData[(imgY + y) * inimg.pitchBytes + + imgX + x]); + printf("%d, %d %d, %d \n", x, y, + imgX + x, (imgY + y)); + // return 1; + } + } + } + + return 0; +} + +// Kernel 函数:_clusterLocalGrayKer(图像的分类降噪) +// 每一个 block 的大小为 32 * 32, 在一个 block 内需要将 64 * 64 大小的图像 +// 拷贝到共享内存中,即一个线程拷贝 4 个像素点。再根据输入参数 neighborsSideSpan +// 统计像素点领域内像素点的个数,将其分成 64 个 bin,然后再根据 hGrayPercentTh +// 和 lGrayPercentTh 计算出当前像素点高像素比例和低像素比例之间的差值,与 +// grayGapTh 进行对比从而选择对该点进行增强、降低、中庸操作。 +static __global__ void _clusterLocalGrayKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char neighborsSideSpan, // 领域大小 + unsigned char hGrayPercentTh, // 高像素比例 + unsigned char lGrayPercentTh, // 低像素比例 + unsigned char grayGapTh // 外部参数 +); + +// Kernel 函数:_clusterLocalGrayKer(图像的分类降噪) +static __global__ void _clusterLocalGrayKer(ImageCuda inimg, ImageCuda outimg, + unsigned char neighborsSideSpan, + unsigned char hGrayPercentTh, + unsigned char lGrayPercentTh, + unsigned char grayGapTh) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量(其中, + // c 表示 column, r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + + // 获取当前像素点在暂存图像中的相对位置。 + int curpos = (dstr + MAX_NB_SIDE_SPAN) * inimg.pitchBytes + dstc + + MAX_NB_SIDE_SPAN; + + // 为 block 内的线程需要处理的图像开辟共享内存,大小为 48 * 48。 + __shared__ unsigned char imgSharedMem[2304]; + + // 将本 block 对应在图像上的区域拷贝到共享内存中,考虑合并访问的问题, + // 不是拷贝一个方块,而是尽量同一行。同时将一些变量先计算出来以便减少 + // 计算量。 + unsigned char *tempImgData = inimg.imgMeta.imgData; + + int temp1 = threadIdx.y * IMG_SMEM_SPAN + threadIdx.x; + int temp2 = dstr * inimg.pitchBytes + dstc; + imgSharedMem[temp1] = tempImgData[temp2]; + imgSharedMem[temp1 + MAX_NB_SIDE_SPAN] = + tempImgData[temp2 + MAX_NB_SIDE_SPAN]; + imgSharedMem[temp1 + MAX_NB_SIDE_SPAN * 2] = + tempImgData[temp2 + MAX_NB_SIDE_SPAN * 2]; + + temp1 = (threadIdx.y + MAX_NB_SIDE_SPAN) * IMG_SMEM_SPAN; + temp2 = (dstr + MAX_NB_SIDE_SPAN) * inimg.pitchBytes + dstc; + imgSharedMem[temp1 + threadIdx.x] = tempImgData[temp2]; + imgSharedMem[temp1 + threadIdx.x + MAX_NB_SIDE_SPAN] = + tempImgData[temp2 + MAX_NB_SIDE_SPAN]; + imgSharedMem[temp1 + threadIdx.x + MAX_NB_SIDE_SPAN * 2] = + tempImgData[temp2 + MAX_NB_SIDE_SPAN * 2]; + + temp1 = (threadIdx.y + MAX_NB_SIDE_SPAN * 2) * IMG_SMEM_SPAN; + temp2 = (dstr + MAX_NB_SIDE_SPAN * 2) * inimg.pitchBytes + dstc; + imgSharedMem[temp1 + threadIdx.x] = tempImgData[temp2]; + imgSharedMem[temp1 + threadIdx.x + MAX_NB_SIDE_SPAN] = + tempImgData[temp2 + MAX_NB_SIDE_SPAN]; + imgSharedMem[temp1 + threadIdx.x + MAX_NB_SIDE_SPAN * 2] = + tempImgData[temp2 + MAX_NB_SIDE_SPAN * 2]; + + __syncthreads(); + + #ifdef CHECK_SHARED_MOMORY_DEBUG + // 在每一个块的第一个线程内判断内存拷贝是否成功。 + if (threadIdx.x == 0 && threadIdx.y == 0) { + if (checkSharedMemory(imgSharedMem, inimg, dstc, dstr, IMG_SMEM_SPAN) == 0) { + printf("blockIdx.x = %d, blockIdx.y = %d, copy sharedMen successful!\n", + blockIdx.x, blockIdx.y); + } else { + printf("blockIdx.x = %d, blockIdx.y = %d, copy sharedMen failed!\n", + blockIdx.x, blockIdx.y); + } + } + + #endif + + // 存放该点处理区域范围内像素值的分布,根据 bin 进行划分。 + unsigned short cp[GRAYBIN_NUM]; + + // 共享内存初始化 + for (int i = 0; i < GRAYBIN_NUM; ++i) + cp[i] = 0; + + // 获得该像素点处理范围的边长。 + unsigned short neighborsSpan = neighborsSideSpan * 2 + 1; + + // 获得该像素点处理范围的面积。 + unsigned short neighborsArea = neighborsSpan * neighborsSpan; + + // 对该像素点处理范围内的点根据 bin 进行基数排序。 + for (int i = 0; i < neighborsArea; ++i) + { + cp[imgSharedMem[threadIdx.x + MAX_NB_SIDE_SPAN - neighborsSideSpan + i % + neighborsSpan + + (threadIdx.y + MAX_NB_SIDE_SPAN - neighborsSideSpan + i / + neighborsSpan) + * IMG_SMEM_SPAN] >> 2] += 1; + } + + // 比例范围内点的总数。 + int m = 0; + + // 比例范围内像素点的灰度累计值。 + int gSum = 0; + + // 根据处理范围和低处理比例获得低处理点的数量。 + int lNumTh = lGrayPercentTh * neighborsArea / 100; + for ( int n = 0; m < lNumTh && n < GRAYBIN_NUM; n++) { + // 点数累积。 + m += cp[n]; + + // 灰度累积。 + gSum += n * cp[n]; + } + + // 计算低处理比例内的平均灰度值。 + unsigned char aveLg = (unsigned char)(GRAY_RESULTION * gSum / m + 2 + 0.5f); + + // 重新赋值为初值。 + m = 0; + gSum = 0; + + // 根据处理范围和高处理比例获得高处理点的数量。 + int hNumTh = hGrayPercentTh * neighborsArea / 100; + for (int n= 64 - 1; m < hNumTh && n >= 0; n--) { + // 点数累积。 + m += cp[n]; + + // 灰度累积。 + gSum += n * cp[n]; + } + + // 计算高处理比例内的平均灰度值。 + unsigned char aveHg = (unsigned char)(GRAY_RESULTION * gSum / m + 2 + 0.5f); + + // 当前 pixel 的 gray 值。 + unsigned char gc = imgSharedMem[IMG_SMEM_SPAN * (threadIdx.y + MAX_NB_SIDE_SPAN) + threadIdx.x + MAX_NB_SIDE_SPAN]; + + // 计算平庸值。 + unsigned char ag = (aveHg + aveLg) >> 1; + + // 根据 grayGapTh 外部参数决定对当前点进行何种处理。 + if ((aveHg - aveLg) < grayGapTh) + outimg.imgMeta.imgData[curpos] = ag; + else if (gc >= aveHg ) + outimg.imgMeta.imgData[curpos] = fmin(aveHg * 1.03f, 255); // Enhancing high gray. + else if ( gc <= aveLg ) + outimg.imgMeta.imgData[curpos] = aveLg * 0.98f; // Depressing low gray. + else if ( gc >= ag ) + outimg.imgMeta.imgData[curpos] = (aveHg + ag) >> 1; + else + outimg.imgMeta.imgData[curpos] = (aveLg + ag) >> 1; +} + +// 成员方法:clusterLocalGray(图像分类降噪处理) +__host__ int ClusterLocalGray::clusterLocalGray(Image *inimg, Image *outimg) +{ + // 局部变量,错误码。 + int errcode; + + // 检查输入图像,输出图像是否为空。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输入图像复制到 device + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将输入图像 inimg 完全拷贝到输出图像 outimg ,并将 outimg 拷贝到 + // device 端。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg, outimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + + // 这里需要减去边界的宽度来计算图像的有效位置。 + gridsize.x = (outsubimgCud.imgMeta.width - MAX_NB_SIDE_SPAN * 2 + blocksize.x - 1) / + blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height - MAX_NB_SIDE_SPAN * 2 + blocksize.y - 1) / + blocksize.y; + + // 调用核函数,开始第一步细化操作。 + _clusterLocalGrayKer<<>>(insubimgCud, outsubimgCud, + this->getNeighborsSideSpan(), + this->getHGrayPercentTh(), + this->getLGrayPercentTh(), + this->getGrayGapTh()); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错,结束迭代函数。 + return CUDA_ERROR; + } + + return NO_ERROR; +} + diff --git a/okano_3_0/ClusterLocalGray.h b/okano_3_0/ClusterLocalGray.h new file mode 100644 index 0000000..90ad3bd --- /dev/null +++ b/okano_3_0/ClusterLocalGray.h @@ -0,0 +1,193 @@ +// ClusterLocalGray.h +// 创建人:杨伟光 +// +// 图像分类降噪平滑算法(ClusterLocalGray) +// 功能说明:对灰度图像进行分类处理,使高像素更亮,低像素更暗,中间值更平庸。 +// 有图像平滑和消除毛刺、噪声的效果。 + +// 修订历史: +// 2014年08月21日(杨伟光) +// 初始版本。 +// 2014年08月30日(杨伟光) +// 功能完成后第一次规范代码,初始版本完成。 +// 2014年09月17日(杨伟光) +// 代码注释进行了一些完善。 +// 2014年09月21日(杨伟光) +// 对代码逻辑进行了改进,速度提升了一倍多。 +// 2014年09月25日(杨伟光) +// 根据河边老师要求替换了一处判断逻辑。 +// 2014年09月28日(杨伟光) +// 修改了几处注释。 + +#ifndef __CLUSTERLOCALGRAY_H__ +#define __CLUSTERLOCALGRAY_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:ClusterLocalGray(图像分类降噪平滑算法) +// 继承自:无。 +// 实现了图像分类降噪平滑算法,根据当前像素点周围一定领域内像素点 +// 的特征,依据不同的输入参数对图像进行不同程度的分类降噪平滑处理。 +class ClusterLocalGray { + +protected: + + // 成员变量:neighborsSideSpan(单像素点处理范围的宽度) + // 以像素点为中心,上下左右 neighborsSideSpan 宽度的范围为处理范围, + // 值域为 0 - 15。 + unsigned char neighborsSideSpan; + + // 成员变量:hGrayPercentTh(高像素比例) + // 统计像素点处理范围内点的像素值,使 hGrayPercentTh 比例内的点更亮。 + // 建议值域为 5 - 30。 + unsigned char hGrayPercentTh; + + // 成员变量:lGrayPercentTh(低像素比例) + // 统计像素点处理范围内点的像素值,使 lGrayPercentTh 比例内的点更暗。 + // 建议值域为 5 - 30。 + unsigned char lGrayPercentTh; + + // 成员变量:grayGapTh(临界值) + // 根据 hGrayPercentTh 和 hGrayPercentTh 计算出平均高和平均低,然后 + // 判断跟临界值的关系从而进行相应的操作。建议值域为 50 - 150。 + unsigned char grayGapTh; + +public: + + // 构造函数:ClusterLocalGray + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + ClusterLocalGray() + { + this->neighborsSideSpan = 2; // 单像素点处理范围的宽度 默认为 2。 + this->hGrayPercentTh = 20; // 高像素比例默认为 20。 + this->lGrayPercentTh = 20; // 低像素比例默认为 20。 + } + + // 构造函数:ClusterLocalGray + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + ClusterLocalGray( + unsigned char neighborsSideSpan, + unsigned char hGrayPercentTh, + unsigned char lGrayPercentTh + ) { + this->neighborsSideSpan = 2; // 单像素点处理范围的宽度 默认为 2。 + this->hGrayPercentTh = 20; // 高像素比例默认为 20。 + this->lGrayPercentTh = 20; // 低像素比例默认为 20。 + + // 根据参数列表中的值设定成员变量的初值。 + this->setNeighborsSideSpan(neighborsSideSpan); + this->setHGrayPercentTh(hGrayPercentTh); + this->setLGrayPercentTh(lGrayPercentTh); + } + + // 成员函数:getNeighborsSideSpan(获取单像素点处理范围的宽度的值) + // 获取成员变量 neighborsSideSpan 的值。 + __host__ __device__ unsigned char // 返回值:返回 neighborsSideSpan 的值。 + getNeighborsSideSpan() const + { + // 返回单像素点处理范围的宽度的值。 + return this->neighborsSideSpan; + } + + // 成员函数:setNeighborsSideSpan(设定单像素点处理范围的宽度的值) + // 设定成员变量 neighborsSideSpan 的值。 + __host__ __device__ int // 返回值:若函数正确执行, + // 返回 NO_ERROR。 + setNeighborsSideSpan( + unsigned char neighborsSideSpan // 单像素点处理范围的宽度。 + ) { + // 判断设定值是否大于值域范围。 + if (neighborsSideSpan >= 16) + return INVALID_DATA; + + // 设定 neighborsSideSpan 的值。 + this->neighborsSideSpan = neighborsSideSpan; + + return NO_ERROR; + } + + // 成员函数:getHGrayPercentTh(获取高像素比例的值) + // 获取成员变量 hGrayPercentTh 的值。 + __host__ __device__ unsigned char // 返回值:返回 hGrayPercentTh 的值。 + getHGrayPercentTh() const + { + // 返回成员变量 hGrayPercentTh 的值。 + return this->hGrayPercentTh; + } + + // 成员函数:setHGrayPercentTh(设置高像素比例的值) + // 设定成员变量 hGrayPercentTh 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 + // NO_ERROR。 + setHGrayPercentTh( + unsigned char hGrayPercentTh // 高像素比例。 + ) { + // 设定成员变量 hGrayPercentTh 的值。 + this->hGrayPercentTh = hGrayPercentTh; + + return NO_ERROR; + } + + // 成员函数:getLGrayPercentTh(获取低像素比例的值) + // 获取成员变量 lGrayPercentTh 的值。 + __host__ __device__ unsigned char // 返回值:返回 lGrayPercentTh 的值。 + getLGrayPercentTh() const + { + // 返回成员变量 lGrayPercentTh 的值。 + return this->lGrayPercentTh; + } + + // 成员函数:setLGrayPercentTh(设置低像素比例的值) + // 设定成员变量 lGrayPercentTh 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 + // NO_ERROR。 + setLGrayPercentTh( + unsigned char lGrayPercentTh // 低像素比例。 + ) { + // 设定成员变量 lGrayPercentTh 的值。 + this->lGrayPercentTh = lGrayPercentTh; + + return NO_ERROR; + } + + // 成员函数:getGrayGapTh(获取临界值的值) + // 获取成员变量 grayGapTh 的值。 + __host__ __device__ unsigned char // 返回值:返回 grayGapTh 的值。 + getGrayGapTh() const + { + // 返回成员变量 grayGapTh 的值。 + return this->grayGapTh; + } + + // 成员函数:setGrayGapTh(设定临界值的值) + // 设定成员变量 grayGapTh 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setGrayGapTh( + unsigned char grayGapTh // 临界值 + ) { + // 设定成员变量 grayGapTh 的值。 + this->grayGapTh = grayGapTh; + + return NO_ERROR; + } + + // 成员方法:clusterLocalGray(图像分类降噪处理) + // 实现了图像分类降噪平滑算法。主要思想为根据当前像素点周围一定领域 + // (领域大小由 neighborsSideSpan 决定)内像素点的特征,对像素点进行统计, + // 再依据不同的输入参数(高像素点比例和低像素点比例)对图像进行不同程度的 + // 分类降噪平滑处理。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + clusterLocalGray( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + + diff --git a/okano_3_0/Compact.cu b/okano_3_0/Compact.cu new file mode 100644 index 0000000..152d0b7 --- /dev/null +++ b/okano_3_0/Compact.cu @@ -0,0 +1,173 @@ +// Compact.cu +// 对数组进行压缩,只保留合法元素 + +#include "Compact.h" +#include +using namespace std; + +#define BLOCK_SIZE 1024 + +// Kernel 函数: _compactDataKer +// 每个线程处理一个元素,实现数组的压缩 +__global__ void _compactDataKer( + int *d_indices, // exclusive 结果数组 + int *d_isValid, // 合法性判断数组(只有 1 和 0) + int *d_in, // 输入数组 + int numElements, // 数组元素个数 + int *d_out, // 输出数组 + int *d_numValidElements // 合法点的个数 +); + +// Kernel 函数: _compactDataKer(数组压缩) +__global__ void _compactDataKer(int *d_indices, int *d_isValid, int *d_in, + int numElements, int *d_out, + int *d_numValidElements) +{ + // 块内第一个线程处理合法元素计数 + if (threadIdx.x == 0) + d_numValidElements[0] = d_isValid[numElements-1] + + d_indices[numElements-1]; + + // 线程处理的当前元素的全局索引 + unsigned int iglobal = blockIdx.x * blockDim.x + threadIdx.x; + // 当前元素没有越界且合法时,向输出数组对应位置赋值 + if (iglobal < numElements && d_isValid[iglobal] > 0) + d_out[d_indices[iglobal]] = d_in[iglobal]; +} + +// 宏:COMPACT_FREE +// 如果出错,就释放之前申请的内存。 +#define COMPACT_FREE do { \ + if (allDev != NULL) \ + cudaFree(allDev); \ + } while (0) + +// Host 成员方法:compactDataGPU(int 型的数组压缩) +__host__ int Compact::compactDataGPU(int *indices, int *d_isValid, int *d_in, + int numElements, int *d_out, + int *d_numValidElements) +{ + // 检查输入和输出以及合法判断数组是否为 NULL,如果为 NULL 直接报错返回。 + if (d_in == NULL || d_out == NULL || d_isValid == NULL || indices == NULL || + d_numValidElements == NULL) + return NULL_POINTER; + + // 对数组长度必须加以判断控制。 + if (numElements < 0) + return INVALID_DATA; + + // 定义运算类型为加法 + add_class add; + // 调用 scan exclusive 函数 + sa.scanArrayExclusive(d_isValid, indices, numElements, add); + + // 定义计算数组有效元素数量数组 + int *d_num = NULL; + // 局部变量,错误码 + cudaError_t cuerrcode; + + // 定义设备端的输入输出数组,合法判定数组,scan-ex 数组指针以及设备端内存引用 + // 指针,当输入输出指针在 Host 端时,在设备端申请对应大小的数组。 + int *d_inDev = NULL; + int *d_outDev = NULL; + int *d_isValidDev = NULL; + int *indicesDev = NULL; + int *allDev = NULL; + + // 这里 compact 实现只支持单个线程块的计算,这里的 gridsize 可以设置的大于 + // 1,从而让多个线程块都运行相同程序来测速。计算调用 Kernel 函数的线程块的 + // 尺寸和线程块的数量。 + int gridsize; + int blocksize; + + // 在设备端统一申请内存 + cuerrcode = cudaMalloc((void **)&allDev, + sizeof (int) * (numElements * 4 + 1)); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + // 确定各数组在设备内存上的地址。 + d_num = allDev; + d_inDev = allDev + 1; + d_isValidDev = d_inDev + numElements; + indicesDev = d_isValidDev + numElements; + d_outDev = indicesDev + numElements; + + // 为数量统计数组赋初值为 0 + cuerrcode = cudaMemset(d_num, 0, sizeof (int)); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + // 将输入数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(d_inDev, d_in, sizeof (int) * numElements, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + COMPACT_FREE; + return cuerrcode; + } + + // 将判定数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(d_isValidDev, d_isValid, sizeof (int) * numElements, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + COMPACT_FREE; + return cuerrcode; + } + + // 将 scan-ex 数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(indicesDev, indices, sizeof (int) * numElements, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + COMPACT_FREE; + return cuerrcode; + } + + // 为输出数组赋初值为 0 + cuerrcode = cudaMemset(d_outDev, 0, sizeof (int) * numElements); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 简单版本每个线程处理一个元素。 + blocksize = BLOCK_SIZE; + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numElements + blocksize - 1) / blocksize); + + // 调用 Kernel 函数,完成实际的数组压缩。 + _compactDataKer<<>>( + indicesDev, d_isValidDev, d_inDev, numElements, d_outDev, d_num); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + COMPACT_FREE; + return CUDA_ERROR; + } + + // 将结果从设备端内存拷贝到输出数组。 + cuerrcode = cudaMemcpy(d_out, d_outDev, sizeof (int) * numElements, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + COMPACT_FREE; + return cuerrcode; + } + + // 将计数结果从设备端内存拷贝到输出数组。 + cuerrcode = cudaMemcpy(d_numValidElements, d_num, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + COMPACT_FREE; + return cuerrcode; + } + + // 释放内存。 + cudaFree(allDev); + + // 处理完毕退出。 + return NO_ERROR; +} + +// 取消前面的宏定义。 +#undef COMPACT_FREE diff --git a/okano_3_0/Compact.h b/okano_3_0/Compact.h new file mode 100644 index 0000000..b2b6dd2 --- /dev/null +++ b/okano_3_0/Compact.h @@ -0,0 +1,182 @@ +// Compact.h +// 创建人:王春峰 +// +// 数组压缩(Compact) +// 功能说明:对一个数组进行压缩,删除不合法元素,按顺序输出合法元素。 +// 举例:数组 A, x, B, C, x, D 进行压缩的结果是:A, B, C, D。 +// +// 修订历史: +// 2013年05月20日(王春峰) +// 初始版本 +// 2013年05月21日(王春峰) +// 添加了数组压缩的 GPU 版本。 + +#ifndef __COMPACT_H__ +#define __COMPACT_H__ + +#include "ErrorCode.h" +#include "OperationFunctor.h" +#include "ScanArray.h" +#include +using namespace std; + +// 类:Compact +// 继承自:无 +// 对一个数组进行压缩,对所有元素进行合法性判断。分别实现了 CPU 端和 GPU 端的压 +// 缩函数。 +class Compact { + +protected: + + // 成员变量:ScanArray 类对象 sa(用以调用 exclusive 版本函数)。 + ScanArray sa; + +public: + + // 构造函数:Compact + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + Compact() + { + // 设置 sa 默认扫描类型 + sa.setScanType(NAIVE_SCAN); + } + + // 构造函数:Compact + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + Compact( + int scanType // 扫描类型 + ) { + // 设置 sa 默认扫描类型 + sa.setScanType(NAIVE_SCAN); + // 根据参数列表中的值设定成员变量的初值 + this->setSaType(scanType); + } + + // 成员方法:getSa(读取 ScanArray 对象) + // 读取 sa 成员变量的值。 + __host__ __device__ ScanArray // 返回值:当前 sa 成员变量的值。 + getSa() const + { + // 返回 ScanArray 成员变量的值。 + return this->sa; + } + + // 成员方法:setSaType(设置扫描类型) + // 设置 sa 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setSaType( + int scanType // 扫描类型 + ) { + // 检查输入参数是否合法 + if (scanType != NAIVE_SCAN && scanType != EFFICIENT_SCAN && + scanType != OPTIMIZE_SCAN && scanType != BETTER_SCAN && + scanType != CPU_IN_SCAN) + return INVALID_DATA; + + // 将 sa 成员变量赋成新值 + sa.setScanType(scanType); + return NO_ERROR; + } + + // Host 成员方法:compactDataCPU + // CPU 端对数组进行串行压缩。 + __host__ int + compactDataCPU( + int *indices, // scan-ex 结果数组 + int *d_isValid, // 合法性判断数组(只有 1 和 0) + int *d_in, // 输入数组 + int numElements, // 数组元素个数 + int *d_out, // 输出数组 + int *d_numValidElements // 合法点的个数 + ) { + // 定义运算类型为加法 + add_class add; + // 调用 scan exclusive 函数 + sa.scanArrayExclusive(d_isValid, indices, numElements, add); + + // 根据 scan exclusive 结果进行输出数组的赋值。 + for(int i = 0; i < numElements; i++) { + // 如果不合法,则跳过判断下一元素 + if (d_isValid[i] == 0) + continue; + // 将输入数组中对应的合法元素输出到输出数组的对应位置上。 + d_out[indices[i]] = d_in[i]; + // 合法点数加一 + d_numValidElements[0]++; + } + + // 运行结束 + return NO_ERROR; + } + + // Host 成员方法:compactDataGPU + // GPU 端对数组进行并行压缩。 + __host__ int + compactDataGPU( + int *indices, // scan-ex 结果数组 + int *d_isValid, // 合法性判断数组(只有 1 和 0) + int *d_in, // 输入数组 + int numElements, // 数组元素个数 + int *d_out, // 输出数组 + int *d_numValidElements // 合法点的个数 + ); + + // Host 成员方法:compactCorrectCheck(GPU 端的数组压缩结果检测) + // 对两个数组进行压缩,对所有元素依次进行比较,得出检测结果。 + inline __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 -1。若检测结果出错,返 + // 回错误位置。若合法点计数结果不一致返回 + // -2。 + compactCorrectCheck( + int *indices, // scan-ex 结果数组 + int *d_isValid, // 合法性判断数组(只有 1 和 0) + int *d_in, // 输入数组 + int numElements, // 数组元素个数 + int *d_outC, // CPU 输出数组 + int *d_outG, // GPU 输出数组 + int *d_numValidElementsC, // CPU 端合法点的个数 + int *d_numValidElementsG // GPU 端合法点的个数 + ) { + // 错误码 + int errcode; + + // 调用 CPU 端压缩数组的函数 + errcode = Compact::compactDataCPU(indices, d_isValid, d_in, numElements, + d_outC, d_numValidElementsC); + + // 出错则返回错误码。 + if (errcode != NO_ERROR) + return errcode; + + // 调用 GPU 端压缩数组的函数 + errcode = Compact::compactDataGPU(indices, d_isValid, d_in, numElements, + d_outG, d_numValidElementsG); + + // 出错则返回错误码。 + if (errcode != NO_ERROR) + return errcode; + + // 首先比较合法点个数是否一致 + if (d_numValidElementsC[0] != d_numValidElementsG[0]) + return -2; + + // 计数器变量 + unsigned int i; + + // 两端的数组进行一一比对,得出检测结果。 + for (i = 0; i < d_numValidElementsC[0]; ++i) { + if (d_outC[i] != d_outG[i]) + return i + 1; + } + + // 执行结束 + return -1; + } +}; + +#endif + diff --git a/okano_3_0/ConnectRegion.cu b/okano_3_0/ConnectRegion.cu new file mode 100644 index 0000000..273531a --- /dev/null +++ b/okano_3_0/ConnectRegion.cu @@ -0,0 +1,1044 @@ +// ConnectRegion.cu +// 实现图像的连通区域操作 + +#include "ConnectRegion.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:CONNREGION_PACK_LEVEL +// 定义了一个线程中计算的像素点个数,若该值为4,则在一个线程中计算2 ^ 4 = 16 +// 个像素点 +#define CONNREGION_PACK_LEVEL 5 + +#define CONNREGION_PACK_NUM (1 << CONNREGION_PACK_LEVEL) +#define CONNREGION_PACK_MASK (CONNREGION_PACK_LEVEL - 1) + +#if (CONNREGION_PACK_LEVEL < 1 || CONNREGION_PACK_LEVEL > 5) +# error Unsupport CONNREGION_PACK_LEVEL Value!!! +#endif + +// 宏:CONNREGION_DIFF_INT +// 比较两个值的绝对值之差是否小于给定值,若是返回1,若不是,返回0 +#define CONNREGION_DIFF_INT(p1, p2, t1,t2) ((p1 >= t1 && p1 <= t2) && (p2 >= t1 && p2 <=t2)) + +// 宏:CONNREGION_INI_IFI +// 定义了一个无穷大 +#define CONNREGION_INI_IFI 0x7fffffff + +// Device 子程序: _findRootDev +// 查找根节点标记值算法,根据给定的 label 数组和坐标值 +// 返回该坐标对应的根节点坐标值。该函数是为了便于其他 Kernel 函数调用。 +static __device__ int // 返回值:根节点标记值 +_findRootDev( + int *label, // 输入的标记数组 + int idx // 输入点的标记值 +); + +// Device 子程序: _unionDev +// 合并两个不同像素点以使它们位于同一连通区域中 +static __device__ void // Device 程序无返回值 +_unionDev( + int *label, // 标记值数组 + unsigned char elenum1, // 第一个像素点灰度值 + unsigned char elenum2, // 第二个像素点灰度值 + int elelabel1, // 第一个像素点标记值 + int elelabel2, // 第二个像素点标记值 + int mingray, int maxgray, // 给定阈值 + int *flag // 变换标记,当这两个输入像素点被合并到一个 + // 区域后,该标记值将被设为 1。 +); + +// Device 子程序: _findePreNumDev +// 计算当前行前所有行中的根节点个数。 +static __device__ int // 返回值:根节点个数 +_findPreNumDev( + int rownum, // 当前行号 + int *elenum // 一个长度与总行数一致的一维数组,用来记录每行中根节 + // 点个数。 +); + +// Kernel 函数: _initLabelPerBlockKer (初始化每个块内像素点的标记值) +// 初始化每个线程块内点的标记值。该过程主要分为两个部分,首先,每个节点的标记值为 +// 其在源图像中的索引值,如对于坐标为 (c, r) 点,其初始标记值为 r * width + c , +// 其中 width 为图像宽;然后,将各点标记值赋值为该点满足阈值关系的八邻域点中的最 +// 小标记值。该过程在一个线程块中进行。 +static __global__ void // Kernel 函数无返回值 +_initLabelPerBlockKer( + ImageCuda inimg, // 输入图像 + int *label, // 输入标记数组 + int mingray, int maxgray // 指定阈值 +); + +// Kernel 函数: _mergeBordersKer (合并不同块内像素点的标记值) +// 不同线程块的合并过程。该过程主要合并每两个线程块边界的点, +// 在这里我们主要采用每次合并 4 × 4 个线程块的策略。 +static __global__ void // Kernel 函数无返回值 +_mergeBordersKer( + ImageCuda inimg, // 输入图像 + int *label, // 输入标记数组 + int blockw, // 应合并线程块的长度 + int blockh, // 应合并线程块的宽度 + int threadz_z, // 合并水平方向线程块时,z 向线程最大值 + int threadz_y, // 合并竖直方向线程块时,z 向线程最大值 + int mingray, int maxgray // 指定阈值 +); + +// Kernel 函数: _preComputeAreaKer (计算面积预处理) +// 为每个节点找到其对应的根节点标记值。为下一步计算面积做准备。 +static __global__ void // Kernel 函数无返回值 +_perComputeAreaKer( + int *label, // 输入标记数组 + int width, // 输入图像长度 + int height // 输入图像宽度 +); + +// Kernel 函数: _computeAreaKer (计算区域面积) +// 计算各个区域的面积值。 +static __global__ void // Kernel 函数无返回值 +_computeAreaKer( + int *label, // 输入标记数组 + int *area, // 输出各区域面积值数组 + int width, // 输入图像长度 + int height // 输入图像宽度 +); + +// Kernel 函数: _areaAnalysisKer (区域面积大小判断) +// 进行区域面积大小判断。其中不满足给定范围的区域的根节点标记值将被赋值为 -1。 +static __global__ void // Kernel 函数无返回值 +_areaAnalysisKer( + int *label, // 输入标记数组 + int *area, // 输入面积数组 + int width, // 输入图像长度 + int height, // 输入图像宽度 + int minArea, // 区域最小面积 + int maxArea // 区域最大面积 +); + +// Kernel 函数: _findRootLabelKer (寻找根节点标记值) +// 经过面积判断后,为每个节点找到其根节点。其中区域面积超出范围的 +// 所有点标记值将被置为 -1。 +static __global__ void // Kernel 函数无返回值 +_findRootLabelKer( + int *label, // 输入标记数组 + int *tmplabel, // 输入存储临时标记数组 + int width, // 输入图像长度 + int height // 输入图像宽度 +); + +// Kernel 函数: _reIndexKer (根据最终结果重新标记图像) +// 将输入标记数组中每行中的根节点个数输出到 elenum 数组中。 +static __global__ void // Kernel 函数无返回值 +_reIndexKer( + int *label, // 输入标记数组 + int *labelri, // 记录重新标记前标记值的数组 + int *elenum, // 记录各行根节点个数的数组 + int width, // 输入图像长度 + int height // 输入图像宽度 +); + +// Kernel 函数: _reIndexFinalKer () +// 进行区域标记值的重新赋值。 +static __global__ void // Kernel 函数无返回值 +_reIndexFinalKer( + int *label, // 输入标记数组 + int *labelri, // 记录重新标记前标记值的数组 + int *elenum, // 记录各行根节点个数的数组 + int width, // 输入图像长度 + int height // 输入图像宽度 +); + +// Kernel 函数: _markFinalLabelKer (将最终标记结果输出到一幅灰度图像上) +// 将最终标记值输出到目标图像上。 +static __global__ void // Kernel 函数无返回值 +_markFinalLabelKer( + ImageCuda outimg, // 输出图像 + int *label, // 标记数组 + int *tmplabel // 临时标记数组 +); + +// Device 子程序:_findRootDev (查找根节点标记值) +static __device__ int _findRootDev(int *label, int idx) +{ + // 在 label 数组中查找 idx 下标对应的最小标记值, + // 并将该值作为返回值。 + int nexidx; + do { + nexidx = idx; + idx = label[nexidx]; + } while (idx < nexidx); + + // 处理完毕,返回根节点标记值。 + return idx; +} + +// Kernel 函数:_initLabelPerBlockKer (初始化各线程块内像素点的标记值) +static __global__ void _initLabelPerBlockKer(ImageCuda inimg, int *label, + int mingray, int maxgray) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量 (其中, c 表示 column; r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + int i, j, k; + // 计算输入坐标点在label数组中对应的数组下标 + int idx = r * inimg.imgMeta.width + c; + // 计算输入坐标点对应的图像数据数组下标 + int inidx = r * inimg.pitchBytes + c, newidx; + + // 计算应申请的 shared memory 的步长 + int spitch = blockDim.x + 2; + // 计算当前坐标点在 shared memory 中对应的下标 + int localidx = (threadIdx.y + 1) * spitch + threadIdx.x + 1; + + // oldlabel 用来记录当前点未经过八邻域判断前的标记值, + // newlabel 用来记录经过一轮判断后当前点的最新标记值, + // 当一个点的 oldlabel 与 newlabel 一致时,当前点对应的标记值为最终标记 + // 初始时,每个点的标记值设为其在 shared memory 中的对应下标 + int oldlabel, newlabel = localidx; + // curvalue 用来记录当前点的灰度值,newvalue 用来记录其八邻域点的灰度值 + unsigned char curvalue, newvalue; + curvalue = inimg.imgMeta.imgData[inidx]; + + // 共享内存数据区,该部分包含了存储在共享内存中的像素点的标记值。 + // 由于未对 Kernel 的尺寸做出假设,这里使用动态申请的 Shared + // Memory(共享内存)。 + extern __shared__ int slabel[]; + // 共享变量 sflag 数组用来存储是否应停止循环信息。 + // 当 sflag[0] 的值为 0 时,表示块内的迭代已经完成。 + __shared__ int sflag[1]; + + // 由于 shared memory 的大小为 (blockDim.x + 2) * (blockDim.y + 2) + // 在这里将 shared memory 的边界点(即 shared memory 中超出线程块的点) + // 的标记值设为无穷大。 + if (threadIdx.x == 0) + slabel[localidx - 1] = CONNREGION_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx + 1] = CONNREGION_INI_IFI; + if (threadIdx.y == 0) { + slabel[localidx - spitch] = CONNREGION_INI_IFI; + if (threadIdx.x == 0) + slabel[localidx - spitch - 1] = CONNREGION_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx - spitch + 1] = CONNREGION_INI_IFI; + } + if (threadIdx.y == blockDim.y - 1) { + slabel[localidx + spitch] = CONNREGION_INI_IFI; + if (threadIdx.x == 0) + slabel[localidx + spitch - 1] = CONNREGION_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx + spitch + 1] = CONNREGION_INI_IFI; + } + + while (1) { + // 将当前点的标记值设为其在 shared memory 中的数组下标 + slabel[localidx] = newlabel; + // 将 sflag[0] 标记值设为 0 + if ((threadIdx.x | threadIdx.y) == 0) + sflag[0] = 0; + // 初始时,将 newlabel 值赋给 oldlabel + oldlabel = newlabel; + __syncthreads(); + + // 在当前点的八邻域范围内查找与其灰度值之差的绝对值小于阈值的点, + // 并将这些点的最小标记值赋予记录在 newlabel 中 + for (i = r - 1;i <= r + 1;i++) { + for (j = c - 1;j <= c + 1;j++) { + if (j == c && i == r) + continue; + newidx = i * inimg.pitchBytes + j; + newvalue = inimg.imgMeta.imgData[newidx]; + if ((i >= 0 && i < inimg.imgMeta.height + && j >= 0 && j < inimg.imgMeta.width) + && (CONNREGION_DIFF_INT(curvalue, newvalue, mingray, maxgray))) { + k = localidx + (i - r) * spitch + j - c; + newlabel = min(newlabel, slabel[k]); + } + } + } + __syncthreads(); + + // 若当前点的 oldlabel 值大于 newlabel 值, + // 表明当前点的标记值不是最终的标记值 + // 则将 sflag[0] 值设为 1,来继续进行循环判断,并通过原子操作 + // 将 newlabel 与 slabel[oldlabel] 的较小值赋予 slabel[oldlabel] + if (oldlabel > newlabel) { + atomicMin(&slabel[oldlabel], newlabel); + sflag[0] = 1; + } + __syncthreads(); + + // 当线程块内所有像素点对应的标记值不再改变, + // 即 sflag[0] 的值为 0 时,循环结束。 + if (sflag[0] == 0) break; + + // 计算 newlabel 对应的根节点标记值,并将该值赋给 newlabel + newlabel = _findRootDev(slabel, newlabel); + __syncthreads(); + } + + // 将 newlabel 的值转换为其在 label 数组中的数组下标 + j = newlabel / spitch; + i = newlabel % spitch; + i += blockIdx.x * blockDim.x - 1; + j += blockIdx.y * blockDim.y - 1; + newlabel = j * inimg.imgMeta.width + i; + label[idx] = newlabel; +} + +// Device 子程序:_unionDev (合并两个不同像素点以使它们位于同一连通区域中) +static __device__ void _unionDev( + int *label, unsigned char elenum1, unsigned char elenum2, + int label1, int label2, int mingray, int maxgray, int *flag) +{ + int newlabel1, newlabel2; + + // 比较两个输入像素点的灰度值是否满足给定的阈值范围 + if (CONNREGION_DIFF_INT(elenum1, elenum2, mingray, maxgray)) { + // 若两个点满足指定条件,则分别计算这两个点的根节点标记值 + // 计算第一个点的根节点标记值 + newlabel1 = _findRootDev(label, label1); + // 计算第二个点的根节点标记值 + newlabel2 = _findRootDev(label, label2); + // 将较小的标记值赋值给另一点在标记数组中的值 + // 并将 flag[0] 置为 1 + if (newlabel1 > newlabel2) { + // 使用原子操作以保证操作的唯一性与正确性 + atomicMin(&label[newlabel1], newlabel2); + flag[0] = 1; + } else if (newlabel2 > newlabel1) { + atomicMin(&label[newlabel2], newlabel1); + flag[0] = 1; + } + } +} + +static __global__ void _mergeBordersKer( + ImageCuda inimg, int *label, + int blockw, int blockh, + int threadz_x, int threadz_y, int mingray, int maxgray) +{ + int idx, iterateTimes, i; + int x, y; + int curidx, newidx; + unsigned char curvalue, newvalue; + + // 在这里以每次合并 4 * 4 = 16 个线程块的方式合并线程块 + // 分别计算待合并线程块在 GRID 中的 x 和 y 向分量 + int threadidx_x = blockDim.x * blockIdx.x + threadIdx.x; + int threadidx_y = blockDim.y * blockIdx.y + threadIdx.y; + + // 共享数组变量,只含有一个元素,每当有两个像素点合并时,该数组 + // 变量值置为 1。 + __shared__ int sflag[1]; + + while (1) { + // 设置 sflag[0] 的值为 0。 + if ((threadIdx.x | threadIdx.y | threadIdx.z) == 0) + sflag[0] = 0; + __syncthreads(); + + // 合并上下相邻线程块的水平方向边界点 + // 由于位于 GRID 中最后一行的线程块向下没有待合并的线程块 + // 因而这里不处理最后一行的线程块 + if ((threadIdx.y < blockDim.y - 1)) { + // 计算为了合并一行线程块的迭代次数 + iterateTimes = blockw / threadz_x; + + // 计算待合并像素点在源图像中的像素点坐标 + x = threadidx_x * blockw + threadIdx.z; + y = threadidx_y * blockh + blockh - 1; + + for (i = 0; i < iterateTimes; i++) { + if (threadIdx.z < threadz_x && x < inimg.imgMeta.width && + y < inimg.imgMeta.height) { + idx = y * inimg.imgMeta.width + x; + + // 计算当前像素点灰度值 + curidx = y * inimg.pitchBytes + x; + curvalue = inimg.imgMeta.imgData[curidx]; + // 计算位于当前像素点下方像素点的灰度值, + // 其坐标值为 (x, y + 1)。 + newidx = curidx + inimg.pitchBytes; + newvalue = inimg.imgMeta.imgData[newidx]; + + // 合并这两个像素点 + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width, mingray, maxgray, sflag); + + // 若当前像素点不为最左侧像素点时,即 x != 0 时,合并 + // 位于当前像素点左下方像素点,其坐标值为 (x - 1, y + 1)。 + if (x - 1 >= 0) { + newidx -= 1; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width - 1, + mingray, maxgray, sflag); + } + + // 若当前像素点不为最右侧像素点时,x != inimg.imgMeta.width + // 时,合并位于当前像素点右下方像素点,其坐标值为 + // (x + 1, y + 1)。 + if (x + 1 < inimg.imgMeta.width) { + newidx += 2; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width + 1, + mingray, maxgray, sflag); + } + } + // 计算下次迭代的起始像素点的 x 坐标 + x += threadz_x; + } + } + + // 合并左右相邻线程块的竖直方向边界点 + // 由于位于 GRID 中最后一列的线程块向右没有待合并的线程块 + // 因而这里不处理最后一列的线程块 + if ((threadIdx.x < blockDim.x - 1)) { + // 计算为了合并一列线程块的迭代次数 + iterateTimes = blockh / threadz_y; + + // 计算待合并像素点在源图像中的像素点坐标, + // 由于处理的是每个线程块的最右一列像素点, + // 因此 x 坐标值因在原基础上加上线程块宽度 - 1 + x = threadidx_x * blockw + blockw - 1; + y = threadidx_y * blockh + threadIdx.z; + + for (i = 0;i < iterateTimes;i++) { + if (threadIdx.z < threadz_y && x < inimg.imgMeta.width && + y < inimg.imgMeta.height) { + idx = y * inimg.imgMeta.width + x; + + // 计算当前像素点灰度值 + curidx = y * inimg.pitchBytes + x; + curvalue = inimg.imgMeta.imgData[curidx]; + // 计算位于当前像素点右侧像素点的灰度值, + // 其坐标值为 (x + 1, y)。 + newidx = curidx + 1; + newvalue = inimg.imgMeta.imgData[newidx]; + // 合并这两个像素点 + _unionDev(label, curvalue, newvalue, + idx, idx + 1, mingray, maxgray, sflag); + + // 若当前像素点不为最上侧像素点时,即 y != 0 时,合并 + // 位于当前像素点右上方像素点,其坐标值为 (x + 1, y - 1)。 + if (y - 1 >= 0) { + newidx -= inimg.pitchBytes; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, idx, + idx - inimg.imgMeta.width + 1, + mingray, maxgray, sflag); + } + + // 若当前像素点不为最下侧像素点时,y != inimg.imgMeta.height + // 时,合并位于当前像素点右下方像素点,其坐标值为 + // (x + 1, y + 1)。 + if (y + 1 < inimg.imgMeta.height) { + newidx = curidx + inimg.pitchBytes + 1; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width + 1, + mingray, maxgray, sflag); + } + } + // 计算下次迭代的起始像素点的 y 坐标 + y += threadz_y; + } + } + __syncthreads(); + if (sflag[0] == 0) break; + } +} + +static __global__ void _perComputeAreaKer( + int *label, int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量 (其中, c 表示 column; r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算输入坐标点在label数组中对应的数组下标 + int inidx = r * width + c; + + // 计算当前像素点的标记值 + int curlabel = label[inidx]; + // 将当前像素点标记值的根节点值赋给原像素点 + int newlabel = _findRootDev(label, curlabel); + label[inidx] = newlabel; +} + +static __global__ void _computeAreaKer( + int *label, int *area, int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,默认令一个线程处理 16 个输出像素,这四个像素位于统一列 + // 的相邻 16 行上,因此,对于 r 需要进行右移计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) << CONNREGION_PACK_LEVEL; + int inidx = r * width + c; + int curlabel, nexlabel; + int cursum = 0; + + do { + // 线程中处理第一个点。 + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (r >= height || c >= width) + break; + // 得到第一个输入坐标点对应的标记值。 + curlabel = label[inidx]; + cursum = 1; + + // 处理第二个点。 + // 此后的像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= height) + break; + // 得到第二个点的像素值。 + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计算。 + inidx += width; + nexlabel = label[inidx]; + // 若当前第二个点的标记值不等于前一个,把当前临时变量 cursum 中的统计结 + // 果增加到共享内存中的相应区域;若该值等于前一个点的标记值,则临时变量 + // cursum 加 1,继续检查下一个像素点。 + if (curlabel != nexlabel) { + atomicAdd(&area[curlabel], cursum); + curlabel = nexlabel; + } else { + cursum++; + } + + // 宏:CONNREGION_KERNEL_MAIN_PHASE + // 定义计算下一个像素点的程序片段。使用这个宏可以实现获取下一个点的像素 + // 值,并累加到共享内存,并且简化编码量 +#define CONNREGION_KERNEL_MAIN_PHASE \ + if (++r >= height) \ + break; \ + inidx += width; \ + nexlabel = label[inidx]; \ + if (curlabel != nexlabel) { \ + atomicAdd(&area[curlabel], cursum); \ + curlabel = nexlabel; \ + cursum = 1; \ + } else { \ + cursum++; \ + } + +#define CONNREGION_KERNEL_MAIN_PHASEx2 \ + CONNREGION_KERNEL_MAIN_PHASE \ + CONNREGION_KERNEL_MAIN_PHASE + +#define CONNREGION_KERNEL_MAIN_PHASEx4 \ + CONNREGION_KERNEL_MAIN_PHASEx2 \ + CONNREGION_KERNEL_MAIN_PHASEx2 + +#define CONNREGION_KERNEL_MAIN_PHASEx8 \ + CONNREGION_KERNEL_MAIN_PHASEx4 \ + CONNREGION_KERNEL_MAIN_PHASEx4 + +#define CONNREGION_KERNEL_MAIN_PHASEx16 \ + CONNREGION_KERNEL_MAIN_PHASEx8 \ + CONNREGION_KERNEL_MAIN_PHASEx8 + +// 对于不同的 CONNREGION_PACK_LEVEL ,定义不同的执行次数,从而使一个线程内部 +// 实现对多个点的像素值的统计。 +#if (CONNREGION_PACK_LEVEL >= 2) + CONNREGION_KERNEL_MAIN_PHASEx2 +# if (CONNREGION_PACK_LEVEL >= 3) + CONNREGION_KERNEL_MAIN_PHASEx4 +# if (CONNREGION_PACK_LEVEL >= 4) + CONNREGION_KERNEL_MAIN_PHASEx8 +# if (CONNREGION_PACK_LEVEL >= 5) + CONNREGION_KERNEL_MAIN_PHASEx16 +# endif +# endif +# endif +#endif + +// 取消前面的宏定义。 +#undef CONNREGION_KERNEL_MAIN_PHASEx16 +#undef CONNREGION_KERNEL_MAIN_PHASEx8 +#undef CONNREGION_KERNEL_MAIN_PHASEx4 +#undef CONNREGION_KERNEL_MAIN_PHASEx2 +#undef CONNREGION_KERNEL_MAIN_PHASE + } while (0); + + // 使用原子操作来保证操作的正确性 + if (cursum != 0) + atomicAdd(&area[curlabel], cursum); +} + +static __global__ void _areaAnalysisKer( + int *label, int *area, int width, int height, + int minArea, int maxArea) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算坐标点对应的 label 数组下标 + int idx = r * width + c; + + // 若面积值大于最大面积值或小于指定最小面积值,则将当前点的标记值设为 -1 + if (area[idx]) { + if (area[idx] < minArea || area[idx] > maxArea) + label[idx] = -1; + } +} + +static __global__ void _findRootLabelKer( + int *label, int *tmplabel, int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算坐标点对应的 label 数组下标 + int idx = r * width + c; + // 计算当前点根节点的标记值 + int nexidx = label[idx]; + + // 将根节点值为 -1 的点的标记值设为 -1,表明该点不应被标记 + if (nexidx >= 0 && label[nexidx] == -1) + label[idx] = -1; + // 将像素点最终标记值赋值到 tmplabel 数组中 + tmplabel[idx] = label[idx]; +} + +static __device__ int _findPreNumDev(int rownum, int *elenum) +{ + int n = rownum; + // 将最终值初始化为 0 + int finalnum = 0; + // 计算由第 0 行至第 n-1 行内根节点的总数和,并将其值赋值给 finalnum。 + while (--n >= 0) { + finalnum += elenum[n]; + } + return finalnum; +} + +static __global__ void _reIndexKer( + int *label, int *labelri, int *elenum, int width, int height) +{ + // 计算线程对应的点位置,其中 colnum 和 rownum 分别表示线程处理的像素点的 + // 列号和行号。 + int rownum = blockIdx.y, colnum = threadIdx.x; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (colnum >= width || rownum >= height) + return; + + // 当输入图像的宽度大于 1024,即给定线程块时, + // 应采取迭代的方式完成该步骤标记值的重新赋值 + // 计算迭代次数,采用向上取整的方式 + int iteratetimes = (width - 1) / blockDim.x + 1; + int idx, rankidx = 0, i, j; + + // 共享内存数据区,该部分包含了记录点是否为根节点信息,当一个点是根节点时, + // 对应元素值为 1,否则值为 0。由于未对 Kernel 的尺寸做出假设, + // 这里使用动态申请的 Shared Memory(共享内存)。 + extern __shared__ int srowinfo[]; + // 用来记录每个线程块(即一行)中根节点的总个数 + __shared__ int selenum[1]; + + if(colnum == 0) + selenum[0] = 0; + __syncthreads(); + + i = colnum; + // 计算每行中根节点的个数 + for (j = 0;j < iteratetimes;j++) { + if (i < width) { + // 计算 labelri 数组下标 + idx = rownum * width + i; + // 将 labelri 中所有元素值为 -1 + labelri[idx] = -1; + // 将当前点是否为根节点的信息返回至 srowinfo 数组中 + srowinfo[i] = (label[idx] == idx); + // 若当前点为根节点则使用原子操作使得 selenum[0] 值加 1 + if (srowinfo[i] == 1) + atomicAdd(&selenum[0], 1); + } + i += 1024; + } + __syncthreads(); + // 将根节点信息存入 elenum 数组中 + if (colnum == 0) + elenum[rownum] = selenum[0]; + //__syncthreads(); + + // 计算每个根节点在它所在行中属于第几个根节点 + for (j = 0;j < iteratetimes;j++) { + // 若当前点是根节点则进行如下判断 + if ((colnum < width) && (srowinfo[colnum] == 1)) { + rankidx = 1; + idx = rownum * width + colnum; + // 计算在当前行,根节点前的其他根节点的总个数 + for (i = 0;i < colnum;i++) { + // 若点为根节点,则使得 rankidx 值加 1。 + if (srowinfo[i] == 1) + rankidx++; + } + // 将根节点索引值返回至数组 labelri 中 + labelri[idx] = rankidx - 1; + } + colnum += 1024; + } +} + +static __global__ void _reIndexFinalKer( + int *label, int *labelri, int *elenum, int width, int height) +{ + // 计算线程对应的点位置,其中 colnum 和 rownum 分别表示线程处理的像素点的 + // 列号和行号。 + int rownum = blockIdx.y, colnum = threadIdx.x; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (colnum >= width || rownum >= height) + return; + + int idx, i, j; + // 当输入图像的宽度大于 1024,即给定线程块时, + // 应采取迭代的方式完成该步骤标记值的重新赋值 + // 计算迭代次数,采用向上取整的方式 + int iteratetimes = (width - 1) / blockDim.x + 1; + i = colnum; + // 将第 256 及以后根节点的标记值设为 -1。 + for (j = 0;j < iteratetimes;j++) { + if (i < width) { + idx = rownum * width + i; + if (labelri[idx] >= 0) { + // 计算根节点的标记值 + label[idx] = labelri[idx] + _findPreNumDev(rownum, elenum); + // 若标记值大于 256,则将根节点标记值设为 -1,表示该点不应被标记 + if (label[idx] >= 256) + label[idx] = -1; + } + } + i += 1024; + } +} + +static __global__ void _markFinalLabelKer( + ImageCuda outimg, int *label, int *tmplabel) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= outimg.imgMeta.width || r >= outimg.imgMeta.height) + return; + + // 计算坐标点对应的 label 数组下标 + int inidx = r * outimg.imgMeta.width + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + + // 计算每个像素点的最终标记值 + if (tmplabel[inidx] != -1) { + tmplabel[inidx] = label[tmplabel[inidx]]; + } + + // 由于标记值应由 1 开始,而在 tmplabel 中未标记区域的标记值为 -1。 + // 因此输出图像的标记值为 tmplabel 在该位置的标记值加 1。 + outimg.imgMeta.imgData[outidx] = tmplabel[inidx] + 1; +} + +// Host 成员方法:connectRegion(连通区域) +__host__ int ConnectRegion::connectRegion(Image *inimg, Image *outimg) +{ + int mingray = 10; + int maxgray = 250; + // 检查输入输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 计算初始化块内内存时,共享内存的大小。 + int smsize = sizeof (int) * (blocksize.x + 2) * (blocksize.y + 2); + // 计算各标记数组的存储数据大小 + int data_size = insubimgCud.imgMeta.width * insubimgCud.imgMeta.height * + sizeof (int); + + // 存储中间标记值的数组,其大小与输入图像大小一致 + int *devtmplabel; + // 存储输入图像各行根节点数目的数组,其下标表示图像行号,元素值为 + // 各行根节点个数,数组大小与图像总行数一致 + int *devElenumPerRow; + // 存储最终标记值的数组,其大小与输入图像大小一致 + int *devLabel; + // 存储记录重新标记前标记值的数组,其大小与输入图像大小一致 + int *devlabelri; + // 存储各区域面积大小的数组,其大小与输入图像大小一致,其中各区域 + // 面积记录在根节点对应元素中。 + int *devArea; + + cudaError_t cudaerrcode; + + // 为标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)&devLabel, data_size); + if (cudaerrcode != cudaSuccess) { + cudaFree(devLabel); + return cudaerrcode; + } + + // 为记录各行根节点个数的数组分配大小。 + cudaerrcode = cudaMalloc((void **)&devElenumPerRow, + insubimgCud.imgMeta.height * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + cudaFree(devElenumPerRow); + return cudaerrcode; + } + + // 为临时标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)(&devtmplabel), data_size); + if (cudaerrcode != cudaSuccess) { + cudaFree(devtmplabel); + return cudaerrcode; + } + + // 为记录重新标记前的标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)(&devlabelri), data_size); + if (cudaerrcode != cudaSuccess) { + cudaFree(devlabelri); + return cudaerrcode; + } + + // 为面积数组分配大小。 + cudaerrcode = cudaMalloc((void **)(&devArea), data_size); + if (cudaerrcode != cudaSuccess) { + cudaFree(devArea); + return cudaerrcode; + } + + // 将面积数组中所有面积值初始化为 0. + cudaerrcode = cudaMemset(devArea, 0, data_size); + if (cudaerrcode != cudaSuccess) { + cudaFree(devArea); + return cudaerrcode; + } + + // 调用核函数,初始化每个线程块内标记值 + _initLabelPerBlockKer<<>>( + insubimgCud, devLabel, mingray, maxgray); + + // 合并线程块时每次合并线程块的长、宽和高 + int blockw, blockh, blockz; + // 计算第一次合并时,应合并线程块的长、宽和高 + // 第一次合并时,应合并线程块的长应为初始线程块长,宽为初始线程块宽 + blockw = blocksize.x; + blockh = blocksize.y; + // 由于这里采用的是 3 维线程块,线程块的高设为初始线程块长和宽的较大者。 + blockz = blockw; + if (blockw < blockh) + blockz = blockh; + + // 计算每次合并的线程块个数,在这里我们采用的是每次合并 4 × 4 的线程块, + // 由于采用这种方式合并所需的迭代次数最少。 + int xtiles = 4, ytiles = 4; + // 计算合并线程块前 GRID 的长 + int tilesizex = gridsize.x; + // 计算合并线程块前 GRID 的宽 + int tilesizey = gridsize.y; + + // 定义为进行线程块合并而采用的线程块与网格。 + dim3 blockformerge, gridformerge; + + // 由于每个线程块的大小限制为 1024,而 tilesizex * tilesizey * blockz 的值 + // 为每次用来进行合并操作的三维线程块的最大大小,因此当该值不大于 1024 时, + // 可将所有线程块放在一个三维线程块中合并,这样,我们就可以以该值是否 + // 不大于 1024 来作为是否终止循环的判断条件。 + while (tilesizex * tilesizey * blockz > 1024) { + + // 计算每次合并线程块时 GRID 的长,这里采用向上取整的方式 + tilesizex = (tilesizex - 1) / xtiles + 1; + // 计算每次合并线程块时 GRID 的宽,这里采用向上取整的方式 + tilesizey = (tilesizey - 1) / ytiles + 1; + + // 设置为了合并而采用的三维线程块大小,这里采用的是 4 × 4 的方式, + // 因此线程块的长为 4,宽也为 4,高则为 32。 + blockformerge.x = xtiles; blockformerge.y = ytiles; + blockformerge.z = blockz; + // 设置为了合并而采用的二维网格的大小。 + gridformerge.x = tilesizex; gridformerge.y = tilesizey; + gridformerge.z = 1; + // 调用核函数,每次合并4 × 4 个线程块内的标记值 + _mergeBordersKer<<>>( + insubimgCud, devLabel, blockw, blockh, + blocksize.x, blocksize.y, mingray, maxgray); + // 在每次迭代后,修改应合并线程块的长和宽,因为每次合并 4 * 4 个线程块, + // 因此,经过迭代后,应合并线程块的长和宽应分别乘 4。 + blockw *= xtiles; + blockh *= ytiles; + } + + // 进行最后一轮线程块的合并 + // 计算该轮应采用的三维线程块大小 + blockformerge.x = tilesizex; blockformerge.y = tilesizey; + blockformerge.z = blockz; + // 设置该论应采用的网格大小,长宽高分别为1。 + gridformerge.x = 1; gridformerge.y = 1;gridformerge.z = 1; + // 调用核函数,进行最后一轮线程块合并 + _mergeBordersKer<<>>( + insubimgCud, devLabel, blockw, blockh, + blocksize.x, blocksize.y, mingray,maxgray); + + // 调用核函数,进行计算面积前的预处理,即找出每个结点对应的标记值, + // 其中根节点的标记值与其自身在数组中的索引值一致 + _perComputeAreaKer<<>>( + devLabel, insubimgCud.imgMeta.width, insubimgCud.imgMeta.height); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blockforcalarea, gridforcalarea; + int height = (insubimgCud.imgMeta.height + CONNREGION_PACK_MASK) / + CONNREGION_PACK_NUM; + blockforcalarea.x = DEF_BLOCK_X; + blockforcalarea.y = DEF_BLOCK_Y; + gridforcalarea.x = (insubimgCud.imgMeta.width + blockforcalarea.x - 1) / + blockforcalarea.x; + gridforcalarea.y = (height + blockforcalarea.y - 1) / blockforcalarea.y; + + // 调用核函数,计算各个区域的面积大小 + _computeAreaKer<<>>( + devLabel, devArea, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height); + + // 调用核函数,进行面积大小判断,面积大小超过给定范围的区域 + // 标记值将被置为 -1。 + _areaAnalysisKer<<>>( + devLabel, devArea, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height, + minArea, maxArea); + + // 调用核函数,进行各区域的最终根节点查找 + _findRootLabelKer<<>>( + devLabel, devtmplabel, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height); + + // 为标记值的重新排序预设线程块与网格的大小,这里采用一个线程块处理一行 + // 的方式进行计算,由于线程块的大小限制为 1024,因此线程块的长设为 1024 + blockforcalarea.x = 1024; blockforcalarea.y = 1; blockforcalarea.z = 1; + gridforcalarea.x = 1; gridforcalarea.y = insubimgCud.imgMeta.height; + gridforcalarea.z = 1; + + // 调用核函数,计算各行所含根节点个数,并将结果返回 devElenumPerRow 数组中 + _reIndexKer<<>>( + devLabel, devlabelri, devElenumPerRow, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height); + + // 调用核函数,计算各个区域的最终有序标记值 + _reIndexFinalKer<<>>( + devLabel, devlabelri, devElenumPerRow, + insubimgCud.imgMeta.width, insubimgCud.imgMeta.height); + + // 调用核函数,将最终标记值传到输出图像中 + _markFinalLabelKer<<>>( + outsubimgCud, devLabel, devtmplabel); + + // 释放已分配的数组内存,避免内存泄露 + cudaFree(devtmplabel); + cudaFree(devArea); + cudaFree(devLabel); + cudaFree(devlabelri); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/ConnectRegion.h b/okano_3_0/ConnectRegion.h new file mode 100644 index 0000000..a1654b5 --- /dev/null +++ b/okano_3_0/ConnectRegion.h @@ -0,0 +1,153 @@ +// ConnectRegion.h +// 创建人:王媛媛 +// +// 连通区域(ConnectRegion) +// 功能说明:根据指定阈值将输入图像分割成若干区域,并根据给定最大最小面积 +// 将满足条件的区域按序进行标记(1,2……255),最多标记255个区域, +// 其中未被标记的区域的标记值将被置为0。 +// +// 修订历史: +// 2012年08月31日(王媛媛) +// 初始版本 +// 2012年09月12日(王媛媛) +// 实现了并行的旋转表计算。 +// 2012年09月13日(王媛媛) +// 更正了代码的一些错误和注释规范。 +// 2012年10月25日(王媛媛) +// 更正了代码的一些注释规范。 + +#ifndef __CONNECTREGION_H__ +#define __CONNECTREGION_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:ConnectRegion +// 继承自:无 +// 根据参数 threshold,给定区域面积的最大最小值,将满足条件的形状区域进行按序标记 +// 后拷贝到输出图像中。 +class ConnectRegion { + +protected: + + // 成员变量:threshold(给定阈值) + // 进行区域连通的给定值,当两个点满足八邻域关系, + // 且灰度值之差的绝对值小于该值时,这两点属于同一区域。 + int threshold; + + // 成员变量:maxArea和 minArea(区域面积的最小和最大值) + // 进行区域面积判断时的面积值最大最小的范围。 + int maxArea, minArea; + +public: + // 构造函数:ConnectRegion + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值 + __host__ __device__ + ConnectRegion() { + // 使用默认值为类的各个成员变量赋值 + this->threshold = 0; // 给定阈值默认为0 + this->maxArea = 100000; // 区域最大面积默认为100000 + this->minArea = 100; // 区域最小面积默认为100 + } + + // 构造函数:ConnectRegion + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + ConnectRegion( + int threshold, // 给定的标记阈值 + int maxArea, int minArea // 区域面积的最大值和最小值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法的初 + // 始值而使系统进入一个未知的装填 + this->threshold = 0; + this->maxArea = 100000; + this->minArea = 100; + + // 根据参数列表中的值设定成员变量的初值 + setThreshold(threshold); + setMaxArea(maxArea); + setMinArea(minArea); + } + + // 成员方法:getThreshold(读取阈值) + // 读取 threshold 成员变量的值。 + __host__ __device__ int // 返回值:当前 threshold 成员变量的值。 + getThreshold() const + { + // 返回 threshold 成员变量的值。 + return this->threshold; + } + + // 成员方法:setThreshold(设置阈值) + // 设置 threshold 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setThreshold( + int threshold // 指定的阈值大小。 + ) { + // 将 threshold 成员变量赋值成新值 + this->threshold = threshold; + + return NO_ERROR; + } + + // 成员方法:getMinArea(读取进行区域面积判断时的最小面积值) + // 读取 minArea 成员变量的值。 + __host__ __device__ int // 返回值:当前 minArea 成员变量的值。 + getMinArea() const + { + // 返回 minArea 成员变量的值。 + return this->minArea; + } + + // 成员方法:setMinArea(设置进行区域面积判断时的最小面积值) + // 设置 minArea 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMinArea( + int minArea // 指定的进行区域面积判断时的最小面积值。 + ) { + // 将 minArea 成员变量赋成新值 + this->minArea = minArea; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getMaxArea(读取进行区域面积判断时的最小面积值) + // 读取 maxArea 成员变量的值。 + __host__ __device__ int // 返回值:当前 maxArea 成员变量的值。 + getMaxArea() const + { + // 返回 maxArea 成员变量的值。 + return this->maxArea; + } + + // 成员方法:setMaxArea(设置进行区域面积判断时的最大面积值) + // 设置 maxArea 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMaxArea( + int maxArea // 指定的进行区域面积判断时的最大面积值。 + ) { + // 将 maxArea 成员变量赋成新值 + this->maxArea = maxArea; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // Host 成员方法:connectRegion(连通区域的提取与标记) + // 根据参数 threshold,给定区域面积的最大最小值,将满足条件的形状区域进行按序标记 + // 后拷贝到输出图像中。如果输出图像 outimg 为空,则返回错误。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + connectRegion( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); +}; + +#endif + diff --git a/okano_3_0/ConnectRegionNew.cu b/okano_3_0/ConnectRegionNew.cu new file mode 100644 index 0000000..bc150c2 --- /dev/null +++ b/okano_3_0/ConnectRegionNew.cu @@ -0,0 +1,890 @@ +// ConnectRegionNew.cu +// 实现图像的连通区域操作 + +#include "ConnectRegionNew.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_H +// 定义了默认的一个线程块处理图像的行数。 +#define DEF_BLOCK_H 4 + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 4 +#define DEF_BLOCK_Y 2 + +// 宏:CONNREGION_INI_IFI +// 定义了一个无穷大 +#define CONNREGION_INI_IFI 0x7fffffff + +// 宏:CONNREGION_INI_SMA +// 定义了一个无穷小 +#define CONNREGION_INI_SMA 0 +// 宏:BEFORESCAN +// 定义开始扫描前的线程块状态 +#define BEFORESCAN 0 + +// 宏:AFTERFIRSTSCAN +// 定义进行第一次扫描后的线程块状态 +#define AFTERFIRSTSCAN 1 + +// 宏:AFTERUNIFIED +// 定义统一标记后的线程块状态 +#define AFTERUNIFIED 2 + +// 宏:CONNREGION_PACK_LEVEL +// 定义了一个线程中计算的像素点个数,若该值为4,则在一个线程中计算2 ^ 4 = 16 +// 个像素点 +#define CONNREGION_PACK_LEVEL 4 + +#define CONNREGION_PACK_NUM (1 << CONNREGION_PACK_LEVEL) +#define CONNREGION_PACK_MASK (CONNREGION_PACK_LEVEL - 1) + +#if (CONNREGION_PACK_LEVEL < 1 || CONNREGION_PACK_LEVEL > 5) +# error Unsupport CONNREGION_PACK_LEVEL Value!!! +#endif + + +// Device 子程序:_findMinLabel +// 将标记值设为连通数组中的值 +static __device__ int // 返回值:根标记,即最小标记值 +_findMinLabel( + int *connector, // 连通数组 + int blockx, // 线程块索引在本程序中代表处理灰度数组的索引 + int pitch, // 等距步长 + int label // 标记值 +); + +// Device 子程序:_spin +// 保证各线程块进行同步 +static __device__ void // 无返回值 +_spin( + int n, // 线程块要同步的状态标记 + int m, // 线程块的数量 + int *state // 线程块标记数组 +); + +// Kernel 函数: _labelingConnectRegionKer (标记连通区域) +// 标记连通区域。该过程主要分为三个部分,首先,扫描第一遍,从第一个图像的标记值 +// 为其在源图像中的索引值,其后标记值为对其邻域对比后得到的标记值,如果存在领域, +// 更新为最小的邻域标记值,否则标记值加1,然后,把每个线程块的标记结果统一,最后 +// 通过找到根标记,实现最终标记。 +// Kernel 函数:_labelingConnectRegionKer (标记连通区域) +static __global__ void // 无返回值 +_labelingConnectRegionKer(ImageCuda inimg, // 输入图像 + int *devIndGray, // 需要处理的灰度范围数组 + int indGrayNum, // 需要处理的灰度组数 + LabelMaps *devLabelM, // 输出区域集 + int *lastLineLabelofBlock, // 存储每一个处理线程块的 + // 最后一行标记值 + int *connector, // 连通数组 + int *bState // 线程块状态数组 +); + +// Kernel 函数:_computeAreaKer +// 计算连通区域的面积 +static __global__ void // 无返回值 +_computeAreaKer( + ImageCuda inimg, // 输入图像 + LabelMaps *devLabelM // 输出区域集 +); + +// Kernel 函数:_filterAreaKer (筛选出面积大小符合要求的连通区域) +static __global__ void // Kernel 函数无返回值 +_filterAreaKer( + ImageCuda inimg, // 输入图像 + LabelMaps * devLabelM, // 输入区域集 + int *devIndGray, // 需要处理的灰度范围数组 + int *frSize, // 各区域集中连通区域的个数数组 + int minArea, //区域面积的最小值 + int maxArea //区域面积的最大值 +); + +// Kernel 函数:_getFilteredRegionKer (得到符合要求的面积区域的相关信息) +static __global__ void +_getFilteredRegionKer( + ImageCuda inimg, // 输入图像 + LabelMaps * devLabelM, // 输入区域集 + int *devIndGray, // 需要处理的灰度范围数组 + int *frSize, // 各区域集中连通区域的个数数组 + int minArea, //区域面积的最小值 + int maxArea, //区域面积的最大值 + int indGrayNum // 需要处理的灰度组数 +); + +// Device 子程序:_findMinLabel(中继处理) +static __device__ int _findMinLabel(int* connector, int blockx, int pitch, + int label) +{ + int lab1 = label,lab2; + // 循环查找并查集的根,即最小标记值。 + while ((lab2 = connector[blockx * pitch + lab1]) >= 0) { + lab1 = lab2; + } + return lab1; +} + +// Device 子程序:_spin (线程块同步) +static __device__ void _spin(int n, int m, int *state) +{ + int counter = 0; + do { + for (int i = 0; i < m; i++) { + if (state[i] >= n) + counter++; + } + } while (counter < m); +} + +// Kernel 函数:_labelingConnectRegionKer (标记连通区域) +static __global__ void _labelingConnectRegionKer(ImageCuda inimg, + int *devIndGray, + int indGrayNum, + LabelMaps *devLabelM, + int *lastLineLabelofBlock, + int *connector, int *bState) +{ + // 用来表示线程块的大小。 + int bSize = DEF_BLOCK_H * inimg.imgMeta.width; + // 动态申请共享内存,使用时共享内存大小为线程块的大小,用于保存扫描后的标记 + // 值,并且数组的第一个元素初始化为所处理的第一个像素在图像中的索引值。 + extern __shared__ int bLabel[]; + // 每个线程块中的每一个元素对应的索引值。 + int bMinLabel = blockIdx.y * (bSize - inimg.imgMeta.width); + int min = devIndGray[2 * blockIdx.x]; + int max = devIndGray[2 * blockIdx.x + 1]; + int pitch = inimg.imgMeta.width * inimg.imgMeta.height; + + // 记录连通数目,初始为所处理的第一个像素在图像中的索引值。 + int labelCounter = bMinLabel + 1; + // 标记数组的第一个元素初始化为所处理的第一个像素在图像中的索引值。 + bLabel[0] = 0; + if (inimg.imgMeta.imgData[blockIdx.y * (DEF_BLOCK_H - 1) * inimg.pitchBytes] + >= min && + inimg.imgMeta.imgData[blockIdx.y * (DEF_BLOCK_H - 1) * inimg.pitchBytes] + <= max ) + bLabel[0] = labelCounter++; + + // 线程块数。 + int countofBlock = gridDim.x * gridDim.y; + // 标记线程块的状态,便于同步 + bState[blockIdx.y * gridDim.x + blockIdx.x] = BEFORESCAN; + // 实现各线程块同步。 + _spin(BEFORESCAN, countofBlock, bState); + + int i; + // 当前处理图像的像素的下标。 + int curidx; + // 标记图像位置的列坐标和行坐标。 + int cur_x, cur_y; + + // 从图像的第 blockIdx.y BLOCK的第一行的第二个PIXEL开始,由左向右进行扫描。 + for (i = 1; i < inimg.imgMeta.width; i++) { + cur_x = i; + cur_y = blockIdx.y * (DEF_BLOCK_H - 1); + curidx = cur_y * inimg.pitchBytes + cur_x; + bLabel[i] = 0; + // 如果该图像像素和左侧像素点连通,则将本线程块中对就位置的标记值设为左 + // 侧像素点对应位置的标记值。 + if (inimg.imgMeta.imgData[curidx] >= min && + inimg.imgMeta.imgData[curidx] <= max) { + if (inimg.imgMeta.imgData[curidx - 1] >= min && + inimg.imgMeta.imgData[curidx - 1] <= max) { + bLabel[i] = bLabel[i - 1]; + }else + bLabel[i] = labelCounter++; + } + } + + // 从对应图像BLOCK的第二行的第一个PIXEL开始(最左), + // 由左向右,由上向下地进行扫描。 + for (; i < bSize; i++) { + // 得到对应像素在图中的位置索引值。 + cur_x = i % inimg.imgMeta.width; + cur_y = i / inimg.imgMeta.width + blockIdx.y * (DEF_BLOCK_H - 1); + curidx = cur_y * inimg.pitchBytes + cur_x; + // 对应的正上方的像素的索引值。 + int upidx = i - inimg.imgMeta.width; + // 初始化标志值以区分该像素是否已被处理过。 + bLabel[i] = 0; + + // 如果该位置的像素在所要处理的灰度范围内,则对该像素进行处理并进行标记 + // 标记原则为:从左侧的对应像素开始处理,比对这个左侧像素是否也在所要处 + // 理的灰度范围内,如果也在,将该像素位置的对应的标记值更新为较小的标记 + // 值,并更新连通数组。右上方和正上方以及左上方的处理方式类似。 + if (inimg.imgMeta.imgData[curidx] >= min && + inimg.imgMeta.imgData[curidx] <= max) { + // 先处理左方像素。 + int leftLabel; + if (cur_x > 0 && + inimg.imgMeta.imgData[curidx - 1] >= min && + inimg.imgMeta.imgData[curidx - 1] <= max) { + leftLabel = bLabel[i - 1]; + }else + leftLabel = -2; + // 依次处理右上方,正上方,左上方的像素,并检查是否也在所要处理的 + // 灰度范围内判断是否和该像素连通。 + for (int times = 0;times < 4;times++) { + // 处理右上方的像素,检查是否也在所要处理的灰度范围内以判断是否 + // 和该像素连通。 + if (times == 0 && cur_x < inimg.imgMeta.width - 1) { + if (inimg.imgMeta.imgData[curidx - inimg.pitchBytes + 1] >= + min && + inimg.imgMeta.imgData[curidx - inimg.pitchBytes + 1] <= + max) + bLabel[i] = bLabel[upidx + 1]; + } + // 处理正上方的像素,检查是否也在所要处理的灰度范围内以判断是否 + // 和该像素连通。 + if (times == 1) { + if (inimg.imgMeta.imgData[curidx - inimg.pitchBytes] >= min + && + inimg.imgMeta.imgData[curidx - inimg.pitchBytes] <= + max) { + if (bLabel[i] != 0) { + int a = _findMinLabel(connector, blockIdx.x, pitch, + bLabel[i]); + int b = _findMinLabel(connector, blockIdx.x, pitch, + bLabel[upidx]); + if (a != b) { + int c = (a > b) ? a : b; + connector[blockIdx.x * pitch + c] = a + b - c; + } + }else + bLabel[i] = bLabel[upidx]; + } + } + // 处理左上方的像素,检查是否也在所要处理的灰度范围内以判断是否 + // 和该像素连通。 + if (times == 2 && cur_x > 0) { + if (inimg.imgMeta.imgData[curidx - inimg.pitchBytes - 1] >= + min && + inimg.imgMeta.imgData[curidx - inimg.pitchBytes - 1] <= + max) { + if (bLabel[i] != 0) { + int a = _findMinLabel(connector, blockIdx.x, pitch, + bLabel[i]); + int b = _findMinLabel(connector, blockIdx.x, pitch, + bLabel[upidx - 1]); + if (a != b) { + int c = (a > b) ? a : b; + connector[blockIdx.x * pitch + c] = a + b - c; + } + }else + bLabel[i] = bLabel[upidx - 1]; + } + } + // 如果该像素的左侧像素和右上方像素都连通,则更新连通区域使左侧 + // 像素对应位置的标志值与右上方保持一致。左侧像素与正上方像素及 + // 左上方像素和右上方像素,左侧像素与正上方像素同理保持一致。 + if (times == 3) { + if (leftLabel != -2) { + if (bLabel[i] != 0) { + int a = _findMinLabel(connector, blockIdx.x, pitch, + bLabel[i]); + int b = _findMinLabel(connector, blockIdx.x, pitch, + leftLabel); + if (a != b) { + int c = (a > b) ? a : b; + connector[blockIdx.x * pitch + c] = a + b - c; + } + }else + bLabel[i] = leftLabel; + } + } + } + if (bLabel[i] == 0) + bLabel[i] = (leftLabel != -2) ? leftLabel : labelCounter++; + } + } + + // 进行bh行扫描后,将bLabel的最后一行的内容copy到 + // lastLineLabelofBlock [blockIdx.y][]中。 + for (i = 0; i < inimg.imgMeta.width; i++) { + if (blockIdx.y < gridDim.y - 1) { + lastLineLabelofBlock[blockIdx.x * inimg.imgMeta.width * + (gridDim.y - 1) + blockIdx.y * inimg.imgMeta.width + i] = + bLabel[bSize - inimg.imgMeta.width + i]; + } + } + + // 更新各大线程块的状态。 + bState[blockIdx.y * gridDim.x + blockIdx.x] = AFTERFIRSTSCAN; + // 实现各线程块同步。 + _spin(AFTERFIRSTSCAN, countofBlock, bState); + + // 根据连通数组更新标记值实现不同的线程块处理的同一行的像素值对应的标记值相 + // 同。 + if (blockIdx.y > 0) { + for (i = 0; i < inimg.imgMeta.width; i++) { + if (bLabel[i] != 0) { + int a = _findMinLabel(connector, blockIdx.x, pitch, bLabel[i]); + int b = _findMinLabel(connector, blockIdx.x, pitch, + lastLineLabelofBlock[blockIdx.x * + inimg.imgMeta.width * (gridDim.y - 1) + + (blockIdx.y - 1) * inimg.imgMeta.width + + i]); + if (a != b) { + int c = (a > b) ? a : b; + connector[blockIdx.x * pitch + c] = a + b - c; + } + } + } + } + + // 更新各大线程块的状态。 + bState[blockIdx.y * gridDim.x + blockIdx.x] = AFTERUNIFIED; + // 实现各线程块同步。 + _spin(AFTERUNIFIED, countofBlock, bState); + + // 将最终结果输出到输出标记值数组中。 + int gbMinLabel = blockIdx.y * (DEF_BLOCK_H - 1) * inimg.imgMeta.width; + + for (i = 0; i < bSize; i++) { + // 找到最小的标记值。 + int trueLabel = _findMinLabel(connector, blockIdx.x, pitch, bLabel[i]); + devLabelM[blockIdx.x].gLabel[gbMinLabel + i] = trueLabel; + } +} + +// Kernel 函数:_computeAreaKer (计算连通区域的面积) +static __global__ void +_computeAreaKer(ImageCuda inimg, LabelMaps *devLabelM) +{ + // 所要处理的图们大小对应的宽度。 + int width = inimg.imgMeta.width; + // 所要处理的图们大小对应的高度。 + int height = inimg.imgMeta.height; + // 初始化区域个数。 + devLabelM[blockIdx.z].regionCount = 0; + + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,默认令一个线程处理 16 个输出像素,这四个像素位于统一列 + // 的相邻 16 行上,因此,对于 r 需要进行右移计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) << CONNREGION_PACK_LEVEL; + int inidx = r * width + c; + int curlabel, nexlabel; + int cursum = 0; + + do { + // 线程中处理第一个点。 + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (r >= height || c >= width) + break; + // 得到第一个输入坐标点对应的标记值。 + curlabel = devLabelM[blockIdx.z].gLabel[inidx]; + // if (blockIdx.z == 1) +//printf("curlabel = %d\n", curlabel); + cursum = 1; + + // 处理第二个点。 + // 此后的像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= height) + break; + // 得到第二个点的像素值。 + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计算。 + inidx += width; + nexlabel = devLabelM[blockIdx.z].gLabel[inidx]; + + // 若当前第二个点的标记值不等于前一个,把当前临时变量 cursum 中的统计结 + // 果增加到共享内存中的相应区域;若该值等于前一个点的标记值,则临时变量 + // cursum 加 1,继续检查下一个像素点。 + if (curlabel != nexlabel) { + atomicAdd(&devLabelM[blockIdx.z].area[curlabel], cursum); + curlabel = nexlabel; + } else { + cursum++; + } + + // 宏:CONNREGION_KERNEL_MAIN_PHASE + // 定义计算下一个像素点的程序片段。使用这个宏可以实现获取下一个点的像素 + // 值,并累加到共享内存,并且简化编码量 +#define CONNREGION_KERNEL_MAIN_PHASE \ + if (++r >= height) \ + break; \ + inidx += width; \ + nexlabel = devLabelM[blockIdx.z].gLabel[inidx]; \ + if (curlabel != nexlabel) { \ + atomicAdd(&devLabelM[blockIdx.z].area[curlabel], cursum); \ + curlabel = nexlabel; \ + cursum = 1; \ + } else { \ + cursum++; \ + } + +#define CONNREGION_KERNEL_MAIN_PHASEx2 \ + CONNREGION_KERNEL_MAIN_PHASE \ + CONNREGION_KERNEL_MAIN_PHASE + +#define CONNREGION_KERNEL_MAIN_PHASEx4 \ + CONNREGION_KERNEL_MAIN_PHASEx2 \ + CONNREGION_KERNEL_MAIN_PHASEx2 + +#define CONNREGION_KERNEL_MAIN_PHASEx8 \ + CONNREGION_KERNEL_MAIN_PHASEx4 \ + CONNREGION_KERNEL_MAIN_PHASEx4 + +#define CONNREGION_KERNEL_MAIN_PHASEx16 \ + CONNREGION_KERNEL_MAIN_PHASEx8 \ + CONNREGION_KERNEL_MAIN_PHASEx8 + +// 对于不同的 CONNREGION_PACK_LEVEL ,定义不同的执行次数,从而使一个线程内部 +// 实现对多个点的像素值的统计。 +#if (CONNREGION_PACK_LEVEL >= 2) + CONNREGION_KERNEL_MAIN_PHASEx2 +# if (CONNREGION_PACK_LEVEL >= 3) + CONNREGION_KERNEL_MAIN_PHASEx4 +# if (CONNREGION_PACK_LEVEL >= 4) + CONNREGION_KERNEL_MAIN_PHASEx8 +# if (CONNREGION_PACK_LEVEL >= 5) + CONNREGION_KERNEL_MAIN_PHASEx16 +# endif +# endif +# endif +#endif + +// 取消前面的宏定义。 +#undef CONNREGION_KERNEL_MAIN_PHASEx16 +#undef CONNREGION_KERNEL_MAIN_PHASEx8 +#undef CONNREGION_KERNEL_MAIN_PHASEx4 +#undef CONNREGION_KERNEL_MAIN_PHASEx2 +#undef CONNREGION_KERNEL_MAIN_PHASE + } while (0); + + // 使用原子操作来保证操作的正确性 + if (cursum != 0) + atomicAdd(&devLabelM[blockIdx.z].area[curlabel], cursum); +} + +// Kernel 函数:_filterAreaKer (筛选出面积大小符合要求的连通区域) +static __global__ void +_filterAreaKer(ImageCuda inimg, LabelMaps * devLabelM, int *devIndGray, + int *frSize, int minArea, int maxArea) +{ + int imgSize = inimg.imgMeta.width * inimg.imgMeta.height; + // 初始化 regionCount 的大小为0. + devLabelM[blockIdx.x].regionCount = 0; + + for (int i = 0; i < imgSize; i++) { + // 遍历每一个标记值对应的连通区域对应的面积的大小,如果面积大小在要求范 + // 围内, regionCount 的大小加1,否则,将面积值置为0,得到区域的个数 + if (devLabelM[blockIdx.x].area[i] > minArea && + devLabelM[blockIdx.x].area[i] < maxArea) + devLabelM[blockIdx.x].regionCount++; + else devLabelM[blockIdx.x].area[i] = 0; + } + + // 把 regionCount (区域个数) 赋给 frSize 数组,用于开辟保存区域的具体信息的结 + // 构体的空间 + frSize[blockIdx.x] = devLabelM[blockIdx.x].regionCount; +} + +// Kernel 函数:_getFilteredRegionKer (得到符合要求的面积区域的相关信息) +static __global__ void +_getFilteredRegionKer(ImageCuda inimg, LabelMaps * devLabelM, int *devIndGray, + int *frSize, int minArea, int maxArea, int indGrayNum) +{ + // 图像大小。 + int imgSize = inimg.imgMeta.width * inimg.imgMeta.height; + int currentLabel; + // 要筛选的连通区域的最小值。 + int min = devIndGray[2 * blockIdx.x]; + // 要筛选的连通区域的最大值。 + int max = devIndGray[2 * blockIdx.x + 1]; + // 对应的 LABEL MEMORY 号(BLOCK列index)。 + int index = (min + max) / 2; + + // 初始化每个连通区域的索引值和标记值,索引值即为对应的 LABEL MEMORY 号。 + for (int i = 0,k = 0; k < frSize[blockIdx.x]; i++) + if (devLabelM[blockIdx.x].area[i] != 0) { + devLabelM[blockIdx.x].fr[k].index = index; + devLabelM[blockIdx.x].fr[k++].labelMapNum = i; + } + + // 初始化每个连通区域的左上角坐标和右下角坐标。(左上角坐标初始化为无限大,右 + // 下角坐标初始化为无限小) + for (int i = 0;i < devLabelM[blockIdx.x].regionCount;i++) { + + devLabelM[blockIdx.x].fr[i].regionX1 = CONNREGION_INI_IFI; + devLabelM[blockIdx.x].fr[i].regionY1 = CONNREGION_INI_IFI; + devLabelM[blockIdx.x].fr[i].regionX2 = CONNREGION_INI_SMA; + devLabelM[blockIdx.x].fr[i].regionY2 = CONNREGION_INI_SMA; + } + + // 遍历每一个标记值,找到其对应的连通区域,通过比较得到左上角坐标和右下角坐 + // 标(因为已经将每一个连通区域的左上角和右下角初始化,在遍历的过程对,将每一 + // 个标记值对应的在图像中的坐标找到,如果当前坐标值的横坐标小于 regionX1,则 + // 更新为当前的坐标的横坐标,regionY1,regionX1,regionY2 的更新同理。) + for (int i = 0; i < imgSize; i++) { + currentLabel = devLabelM[blockIdx.x].gLabel[i]; + if (devLabelM[blockIdx.x].gLabel[i] != 0 ) { + // 得到当前位置对应的图像中的纵坐标值。 + int y = i / inimg.imgMeta.width; + // 得到当前位置对应的图像中的横坐标值。 + int x = i % inimg.imgMeta.width; + // 得到当前位置对应的标记值 。 + currentLabel = devLabelM[blockIdx.x].gLabel[i]; + for (int i = 0;i < devLabelM[blockIdx.x].regionCount;i++) { + // 找到存储对应的连通区域信息的结构体。 + if (currentLabel == devLabelM[blockIdx.x].fr[i].labelMapNum) { + // 更新左上角的横坐标,取最小值。 + if (x < devLabelM[blockIdx.x].fr[i].regionX1) + devLabelM[blockIdx.x].fr[i].regionX1 = x; + // 更新左上角的纵坐标,取最小值。 + if (x > devLabelM[blockIdx.x].fr[i].regionX2) + devLabelM[blockIdx.x].fr[i].regionX2 = x; + // 更新右下角的横坐标,取最大值。 + if (y devLabelM[blockIdx.x].fr[i].regionY2) + devLabelM[blockIdx.x].fr[i].regionY2 = y; + } + } + } + } +} + +// 宏:FAIL_CONNECT_REGION_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_CONNECT_REGION_NEW_FREE do { \ + if (devtmplabel != NULL) \ + cudaFree(devtmplabel); \ + if (devLabel != NULL) \ + cudaFree(devLabel); \ + if (connector != NULL) \ + cudaFree(connector); \ + if (bState != NULL) \ + cudaFree(bState); \ + if (devFrsize != NULL) \ + cudaFree(devFrsize); \ + if (devIndGray != NULL) \ + cudaFree(devIndGray); \ + if (devLabelM != NULL) \ + cudaFree(devLabelM); \ + } while (0) + +// Host 成员方法:connectRegionNew(连通区域新方法) +__host__ int ConnectRegionNew::connectRegionNew(Image *inimg, int * indGray, + int indGrayNum, LabelMaps *labelM) +{ + // 检查输入输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + cudaError_t cudaerrcode; + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算各标记数组的存储数据大小 + int data_size = insubimgCud.imgMeta.width * insubimgCud.imgMeta.height * + sizeof (int); + + // 存储中间标记值的数组,其大小与输入图像大小一致 + int *devtmplabel; + // 存储最终标记值的数组,其大小与输入图像大小一致 + int *devLabel; + // 存储连通数组,其大小与输入图像大小 一致。 + int *connector; + // 存储线程块状态数组,其大小等于线程块数。 + int *bState; + // 存储连通区域个数的数组,其大小等于需要处理的灰度组数。 + int *hstFrsize = new int[indGrayNum]; + // 在设备端存储连通区域个数的数组。 + int *devFrsize; + // 需要处理的灰度范围数组。 + int * devIndGray; + // device 端区域集,用于处理信息。 + LabelMaps *devLabelM; + // 临时的区域集,用于在 host 端开辟空间。 + LabelMaps *tmpMaps = new LabelMaps[indGrayNum]; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = 1; + blocksize.y = 1; + gridsize.x = indGrayNum; + // 每个线程格中的总线程块数为把图像以 DEF_BLOCK_H 为一个线程块分割,并保证上 + // 下两个线程处理的数据有一个重合的行。 + gridsize.y = (insubimgCud.imgMeta.height + DEF_BLOCK_H - 3) / + (DEF_BLOCK_H - 1); + + // 给设备端连通区域个数的数组分配空间。 + cudaerrcode = cudaMalloc((void **)&devFrsize, indGrayNum * sizeof(int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 为标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)&devLabel, data_size); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 为临时标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)(&devtmplabel), + indGrayNum * insubimgCud.imgMeta.width * (gridsize.y - 1) * + sizeof(int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 为连通数组分配大小。 + cudaerrcode = cudaMalloc((void **)(&connector), + indGrayNum * insubimgCud.imgMeta.width * + insubimgCud.imgMeta.height * sizeof(int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 为线程块状态数组分配大小。 + cudaerrcode = cudaMalloc((void **)(&bState), + gridsize.x * gridsize.y * sizeof(int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 将线程块状态数组中所有值初始化为 0。 + cudaerrcode = cudaMemset(bState, 0, gridsize.x * gridsize.y * sizeof(int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 将连通数组初始化为 -1。 + cudaerrcode = cudaMemset(connector, -1, indGrayNum * + insubimgCud.imgMeta.width * + insubimgCud.imgMeta.height * sizeof(int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 核函数中使用的共享内存的大小。 + int bsize = sizeof (int) * DEF_BLOCK_H * insubimgCud.imgMeta.width; + + // 为要处理的灰度范围数组开辟空间,大小为 2 * indGrayNum。 + cudaerrcode = cudaMalloc((void **)&devIndGray, + 2 * indGrayNum * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 为 device 端区域集开辟空间,大小为 indGryaNum 个区域集结构体。 + cudaerrcode = cudaMalloc((void **)&devLabelM, + indGrayNum * sizeof (LabelMaps)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + + // 用于方向区域集的下标变量。 + int lmsize; + + // 为每一个区域集中的标记值数组和面积数组开辟空间。大小和图像大小一致。 + for (lmsize = 0; lmsize < indGrayNum; lmsize++) { + cudaerrcode = cudaMalloc((void **)&(tmpMaps[lmsize].gLabel), data_size); + cudaerrcode = cudaMalloc((void **)&(tmpMaps[lmsize].area), data_size); + cudaerrcode = cudaMemset(tmpMaps[lmsize].area, 0, data_size); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + } + + // 把输入的灰度数组拷贝到设备端。 + cudaMemcpy(devIndGray, indGray, 2 * indGrayNum * sizeof (int), + cudaMemcpyHostToDevice); + // 把输入区域集拷贝到设备端。 + cudaMemcpy(devLabelM, tmpMaps, indGrayNum * sizeof (LabelMaps), + cudaMemcpyHostToDevice); + + // 调用核函数,初始化每个线程块内标记值。并将结果计算出来,划分出连通区域。 + _labelingConnectRegionKer<<>>( + insubimgCud, devIndGray, indGrayNum, devLabelM, devtmplabel, + connector, bState); + + // 计算调用计算面积的 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blockforcalarea, gridforcalarea; + int height = (insubimgCud.imgMeta.height + CONNREGION_PACK_MASK) / + CONNREGION_PACK_NUM; + blockforcalarea.x = DEF_BLOCK_X; + blockforcalarea.y = DEF_BLOCK_Y; + gridforcalarea.x = (insubimgCud.imgMeta.width + blockforcalarea.x - 1) / + blockforcalarea.x; + gridforcalarea.y = (height + blockforcalarea.y - 1) / blockforcalarea.y; + gridforcalarea.z = indGrayNum; + // 计算每一个区域的面积 + _computeAreaKer<<>>(insubimgCud, + devLabelM); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错,结束迭代函数,释放申请的变量空间。 + cout << "error" << endl; + + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 mblocksize, mgridsize; + mblocksize.x = 1; + mblocksize.y = 1; + mgridsize.x = indGrayNum; + mgridsize.y = 1; + + // 根据各区域的大小筛选出符合面积要求的连通区域,并将区域个数存储在 devFrsize + // 数组中,用于开辟新的空间存放连通区域的具体信息 + _filterAreaKer<<>>(insubimgCud,devLabelM, devIndGray, + devFrsize, minArea, maxArea); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错,结束迭代函数,释放申请的变量空间。 + cout << "error" << endl; + + return CUDA_ERROR; + } + + // 将连通区域的数组个数数组数据从 device 端拷到 host 端。 + cudaMemcpy(hstFrsize, devFrsize, indGrayNum * sizeof (int), + cudaMemcpyDeviceToHost); + + // 将连通区域集的结果从设备端拷到主机端。 + cudaMemcpy(tmpMaps, devLabelM, indGrayNum * sizeof (LabelMaps), + cudaMemcpyDeviceToHost); + + // 为记录连通区域具体作息的结构体分配空间。 + for (lmsize = 0; lmsize < indGrayNum; lmsize++) { + cudaerrcode = cudaMalloc((void **)&(tmpMaps[lmsize].fr), + hstFrsize[lmsize] * sizeof(FilteredRegions)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_CONNECT_REGION_NEW_FREE; + return cudaerrcode; + } + } + + // 把分配好空间的区域集从 host 端拷到 device端。 + cudaMemcpy(devLabelM, tmpMaps, indGrayNum * sizeof (LabelMaps), + cudaMemcpyHostToDevice); + + // 得到符合条件的连通区域的相关信息:左上角坐标值、右下角坐标值、标记值、对 + // 应的 LABEL MEMORY 号(BLOCK列index) + _getFilteredRegionKer<<>>(insubimgCud,devLabelM, devIndGray, + devFrsize, minArea, maxArea, + indGrayNum); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错,结束迭代函数,释放申请的变量空间。 + cout << "error" << endl; + + return CUDA_ERROR; + } + + // 进行最后拷贝,把区域集的完整信息从 device 端拷贝到 host 端 + cudaMemcpy(hstFrsize, devFrsize, indGrayNum * sizeof (int), + cudaMemcpyDeviceToHost); + + // 保存标记值数组的指针值。 + int *devGlabel; + // 保存区域结构体数组的指针值。 + FilteredRegions *devFr; + // 将区域集的指针从 device 端拷贝到 host 端。 + cudaMemcpy(labelM, devLabelM, indGrayNum * sizeof (LabelMaps), + cudaMemcpyDeviceToHost); + // 通过拷贝得到的指针得到区域集的完整信息。 + for (lmsize = 0; lmsize < indGrayNum; lmsize++) { + devGlabel =labelM[lmsize].gLabel; + devFr = labelM[lmsize].fr; + labelM[lmsize].gLabel = new int[insubimgCud.imgMeta.width * + insubimgCud.imgMeta.height]; + labelM[lmsize].fr = new FilteredRegions[hstFrsize[lmsize]]; + labelM[lmsize].regionCount = hstFrsize[lmsize]; + cudaMemcpy(labelM[lmsize].gLabel, devGlabel, insubimgCud.imgMeta.width * + insubimgCud.imgMeta.height * sizeof(int), + cudaMemcpyDeviceToHost); + cudaMemcpy(labelM[lmsize].fr, devFr, hstFrsize[lmsize] * + sizeof(FilteredRegions), cudaMemcpyDeviceToHost); + cudaFree(devFr); + cudaFree(devGlabel); + } + +// 以下程序段用于检测输出结果 + for (lmsize = 0; lmsize < indGrayNum; lmsize++) { + printf("labelM[%d].regionCount = %d\n", lmsize, labelM[lmsize].regionCount); + for (int i = 0; i < insubimgCud.imgMeta.width * + insubimgCud.imgMeta.height; i++) { + if (i % insubimgCud.imgMeta.width == 0) + printf("\n"); + printf("%4d",labelM[lmsize].gLabel[i]); + } + printf("\n"); + for (int i = 0; i < labelM[lmsize].regionCount; i++) { + printf("labelM[%d}.fr[%d].index = %d\n",lmsize, + i,labelM[lmsize].fr[i].index); + printf("labelM[%d}.fr[%d].labelMapNum = %d\n", lmsize, i, + labelM[lmsize].fr[i].labelMapNum); + printf("labelM[%d}.fr[%d].X1 = %d\n",lmsize, i, + labelM[lmsize].fr[i].regionX1); + printf("labelM[%d}.fr[%d].Y1 = %d\n",lmsize, i, + labelM[lmsize].fr[i].regionY1); + printf("labelM[%d}.fr[%d].X2 = %d\n",lmsize, i, + labelM[lmsize].fr[i].regionX2); + printf("labelM[%d}.fr[%d].Y2 = %d\n",lmsize, i, + labelM[lmsize].fr[i].regionY2); + } + } + + // 释放已分配的数组内存,避免内存泄露。 + delete []tmpMaps; + cudaFree(devFrsize); + cudaFree(devIndGray); + cudaFree(devLabelM); + cudaFree(devtmplabel); + cudaFree(devLabel); + cudaFree(connector); + cudaFree(bState); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/ConnectRegionNew.h b/okano_3_0/ConnectRegionNew.h new file mode 100644 index 0000000..c51b67f --- /dev/null +++ b/okano_3_0/ConnectRegionNew.h @@ -0,0 +1,176 @@ +// ConnectRegionNew.h +// 创建人:孙慧琪 +// +// 连通区域新方法(ConnectRegionNew) +// 功能说明:根据指定阈值将输入图像分割成若干区域。 +// +// 修订历史: +// 2013年03月28日(王媛媛) +// 初始版本 +// 2013年06月10日(孙慧琪) +// 每个线程块处理一定行图像数据,对每个线程块一共处理三次,分别进行第一次扫 +// 描,标记统一和最终标记。将三次处理分别在三个核函数中实现。 +// 2013年05月28日(孙慧琪) +// 增加新的处理操作,解决对于 U 型图像的处理不准确问题。 +// 2013年06月20日(王媛媛、孙慧琪) +// 合并三个核函数,加入 _spin 操作实现线程块同步。对小型测试数据能出现正确结 +// 果,大图像由于有序标记问题还不能出现正确预期结果。 +// 2013年06月24日(孙慧琪) +// 将旧的方法和新方法和并,在主函数中由用户选择所要使用的什么方法。 +// 2013年06月26日(王媛媛、孙慧琪) +// 删除参数 threshold,添加新参数 indGray 以及 indGrayNum,并添加结构体 +// FilteredRegions,LabelMaps。 +// 2013年07月01日(孙慧琪) +// 增加对 LabelMaps 的相关处理。在 device 端开辟空间。 +// 2013年07月05日(孙慧琪) +// 修改原来的核函数的相关细节,将原来根据指定灰度值得到连通区域的算法改为根 +// 据当前灰度值是否在需要处理的灰度范围内得到连通区域结果。 +// 2013年07月10日(孙慧琪) +// 增加面积的相关处理,并根据面积是否在规定的范围内得到每个区域集中的连通区 +// 域的个数。 +// 2013年07月14日(王媛媛) +// 优化相关面积算法,增加面积数组,存储每个连通区域的面积信息,使用三维 +// block 优化对面积的计算和比较。 +// 2013年07月17日(孙慧琪) +// 增加区域结构体的处理函数,通过迭代比较得到各个区域结构体的具体信息,增加 +// 对区域结构体的成员变量的初始化赋值,并通过对各个位置的遍历,最终得到每个 +// 区域的左上角坐标和右下角坐标。 +// 2013年07月22日(孙慧琪) +// 修改程序中的一些小错误,将原来的算法继续优化。 +// 2013年07月30日(孙慧琪) +// 将区域的结果从 device 端拷回到 host 端。 +// 2013年08月15日(孙慧琪) +// 解决了区域集结构体中的区域结构体指针的多层拷贝问题,将最终结果全部拷回到 +// host 端。 + +#ifndef __CONNECTREGIONNEW_H__ +#define __CONNECTREGIONNEW_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 结构体:FilteredRegions(区域) +// 该结构体定义了区域的数据结构,其中包含了区域属性的描述。 +typedef struct FilteredRegions_st { + int regionX1; // 区域左上角点的横坐标,要求 0 <= regionX1 < + // regionWidth + int regionY1; // 区域左上角点的纵坐标,要求 0 <= regionY1 < + // regionHeight + int regionX2; // 区域右下角点的横坐标,要求 regionX1 <= + // regionX2 < regionWidth + int regionY2; // 区域右下角点的纵坐标,要求 regionY1 <= + // regionY2 < regionHeight + int labelMapNum; // 在区域集合中的标记值 + int index; // 索引值 +} FilteredRegions; + +// 结构体:LabelMaps(区域集) +// 该结构体定义了区域集的数据结构,其中包含了区域集的描述,如区域个数,区域属性。 +typedef struct LabelMaps_st +{ + int regionCount; // 区域集中所含区域个数 + FilteredRegions *fr; // 各区域的信息 + int *gLabel; // 区域集对应的标记值数组 + int *area; // 各标记值对应的连通区域对应的面积值 +}LabelMaps; + +// 类:ConnectRegionNew +// 继承自:无 +// 根据参数 threshold,将满足条件的形状区域进行按序标记后拷贝到输出图像中。 +class ConnectRegionNew { + +protected: + + // 成员变量:maxArea和 minArea(区域面积的最小和最大值) + // 进行区域面积判断时的面积值最大最小的范围。 + int maxArea, minArea; + +public: + // 构造函数:ConnectRegionNew + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值 + __host__ __device__ + ConnectRegionNew() { + // 使用默认值为类的各个成员变量赋值 + this->maxArea = 100000; // 区域最大面积默认为100000 + this->minArea = 100; // 区域最小面积默认为100 + } + + // 构造函数:ConnectRegionNew + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + ConnectRegionNew( + int maxArea, int minArea // 区域面积的最大值和最小值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了 + // 非法的初始值而使系统进入一个未知的装填 + this->maxArea = 100000; + this->minArea = 100; + + // 根据参数列表中的值设定成员变量的初值 + setMaxArea(maxArea); + setMinArea(minArea); + } + + // 成员方法:getMinArea(读取进行区域面积判断时的最小面积值) + // 读取 minArea 成员变量的值。 + __host__ __device__ int // 返回值:当前 minArea 成员变量的值。 + getMinArea() const + { + // 返回 minArea 成员变量的值。 + return this->minArea; + } + + // 成员方法:setMinArea(设置进行区域面积判断时的最小面积值) + // 设置 minArea 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMinArea( + int minArea // 指定的进行区域面积判断时的最小面积值。 + ) { + // 将 minArea 成员变量赋成新值 + this->minArea = minArea; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getMaxArea(读取进行区域面积判断时的最小面积值) + // 读取 maxArea 成员变量的值。 + __host__ __device__ int // 返回值:当前 maxArea 成员变量的值。 + getMaxArea() const + { + // 返回 maxArea 成员变量的值。 + return this->maxArea; + } + + // 成员方法:setMaxArea(设置进行区域面积判断时的最大面积值) + // 设置 maxArea 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMaxArea( + int maxArea // 指定的进行区域面积判断时的最大面积值。 + ) { + // 将 maxArea 成员变量赋成新值 + this->maxArea = maxArea; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // Host 成员方法:connectRegionNew(连通区域的标记的新方法) + // 根据参数 indGray 和 indGrayNum,将满足条件的形状区域进行按序标记后拷贝到 + // 输出区域集中。 + // 如果输出区域集 labelM 为空,则返回错误。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + connectRegionNew( + Image *inimg, // 输入图像。 + int * indGray, // 需要处理的灰度范围数组 + int indGrayNum, // 所处理的灰度范围的组数 + LabelMaps *labelM // 输出区域集信息,共有 indGrayNum 个 + ); +}; + +#endif + diff --git a/okano_3_0/ConsolidateAndIdentifyContours.cu b/okano_3_0/ConsolidateAndIdentifyContours.cu new file mode 100644 index 0000000..fdc4acd --- /dev/null +++ b/okano_3_0/ConsolidateAndIdentifyContours.cu @@ -0,0 +1,444 @@ +// ConsolidateAndIdentifyContours.cu +// 利用图像中图形的轮廓信息检测物体。 + +#include "ConsolidateAndIdentifyContours.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" +#include "Template.h" +#include "TemplateFactory.h" + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:RED_MAC_COUNT +// 定义了重复进行边缘检测的次数。 +#define RED_MAC_COUNT 4 + +// 宏:DILATE_TPL_SHAPE 和 SEARCH_TPL_SHAPE +// 定义了膨胀操作和搜索标准轮廓时的临域模板形状 +#define DILATE_TPL_SHAPE TF_SHAPE_CIRCLE +#define SEARCH_TPL_SHAPE TF_SHAPE_BOX + +// 宏:OBJ_IDX_OFFSET +// 定义了输出到标记轮廓中的标号偏移量。 +#define OBJ_IDX_OFFSET 100 + + +// 全局变量:_redMacDiffSize +// 不同的边缘检测器的检测半径。 +static unsigned _redMacDiffSize[RED_MAC_COUNT] = { 3, 5, 7, 9 }; + +// 静态成员变量:redMachine(边缘检测处理机) +RobustEdgeDetection *ConsolidateAndIdentifyContours::redMachine = NULL; + + +// Kernel 函数:_searchPrimitiveContourKer(匹配并标记轮廓) +// 从检测中的轮廓图像中匹配标准轮廓图像中的相关轮廓。被匹配上的边缘将被标记成对 +// 应的标号信息,未匹配上的轮廓被标记为异常点。 +static __global__ void +_searchPrimitiveContourKer( + ImageCuda inimg, // 轮廓输入图像 + ImageCuda outimg, // 标记后的输出图像 + ImageCuda abnorimg, // 异常点图像 + ImageCuda prmtcont, // 标准轮廓图像 + ImageCuda prmtreg, // 物体区域图像 + unsigned trackrad // 搜索半径 +); + + +// Host 成员方法:initRedMachine(初始化边缘检测处理机) +__host__ int ConsolidateAndIdentifyContours::initRedMachine() +{ + // 如果 redMachine 不为 NULL,则说明已经初始化过了。 + if (redMachine != NULL) + return NO_ERROR; + + // 申请指定个数的边缘检测器。 + redMachine = new RobustEdgeDetection[RED_MAC_COUNT]; + if (redMachine == NULL) + return OUT_OF_MEM; + + // 迭代设定各个边缘检测器的检测半径。 + int errcode = NO_ERROR; + for (int i = 0; i < RED_MAC_COUNT; i++) { + int curerrcode = redMachine[i].setDiffsize(_redMacDiffSize[i]); + + // 最终返回的错误码应该是更加严重的错误。 + if (curerrcode < errcode) + errcode = curerrcode; + } + + // 初始化完毕,返回赋值过程中累积下来的错误码。 + return errcode; +} + +// Host 成员方法:initMorphMachine(初始化膨胀处理机) +__host__ int ConsolidateAndIdentifyContours::initMorphMachine() +{ + // 取出膨胀处理机中原来的模板 + Template *oldtpl = morphMachine.getTemplate(); + + // 通过模版工厂生成新的模板,这里暂时适用方形模板。 + int errcode; + Template *curtpl = NULL; + size_t boxsize = this->dilationRad * 2 + 1; + errcode = TemplateFactory::getTemplate(&curtpl, DILATE_TPL_SHAPE, boxsize); + if (errcode != NO_ERROR) + return errcode; + + // 将新生成的模板放入膨胀处理机中 + errcode = morphMachine.setTemplate(curtpl); + if (errcode != NO_ERROR) + return errcode; + + // 如果原始的模板不为 NULL,则需要释放对该模板的占用。 + if (oldtpl != NULL) + TemplateFactory::putTemplate(oldtpl); + + // 处理完毕,返回。 + return NO_ERROR; +} + +// Host 成员方法:getCsldtContoursImg(获取轮廓图像) +__host__ int ConsolidateAndIdentifyContours::getCsldtContoursImg( + Image *inimg, Image *outimg) +{ + // 检查输入输出图像是否为 NULL。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 由于后续的失败处理要清除所申请的临时图像,因此设计一个宏来简化代码,方便 + // 代码维护。 +#define CAIC_GETCONT_ERRFREE(errcode) do { \ + for (int _i_cge = 0; _i_cge < RED_MAC_COUNT; _i_cge++) { \ + if (edgetmpimg[_i_cge] != NULL) \ + ImageBasicOp::deleteImage(edgetmpimg[_i_cge]); \ + } \ + return (errcode); \ + } while (0) + + // 该迭代完成两件事情:第一是完成边缘检测输出图像的创建;另一件则是调用边缘 + // 检测算法完成边缘检测。 + int errcode = NO_ERROR; + Image *edgetmpimg[RED_MAC_COUNT] = { NULL }; + + for (int i = 0; i < RED_MAC_COUNT; i++) { + // 创建边缘检测的输出图像。 + errcode = ImageBasicOp::newImage(edgetmpimg + i); + if (errcode != NO_ERROR) + CAIC_GETCONT_ERRFREE(errcode); + //cout << "AA" << i << endl; + + // 调用边缘检测方法,获得边缘图像。 + errcode = redMachine[i].detectEdgeSA(inimg, edgetmpimg[i], NULL); + if (errcode != NO_ERROR) + CAIC_GETCONT_ERRFREE(errcode); + //cout << "BB" << i << endl; + } + + // 合并在不同参数下边缘检测的结果。 + errcode = combineMachine.combineImageMax(edgetmpimg, RED_MAC_COUNT, outimg); + if (errcode != NO_ERROR) + CAIC_GETCONT_ERRFREE(errcode); + + // 对边缘进行膨胀操作,以连接一些断线的点 + errcode = morphMachine.dilate(outimg, edgetmpimg[0]); + if (errcode != NO_ERROR) + CAIC_GETCONT_ERRFREE(errcode); + + // 对膨胀后的边缘进行细化操作,在此恢复其单一线宽。 + errcode = thinMachine.thinMatlabLike(edgetmpimg[0], outimg); + if (errcode != NO_ERROR) + CAIC_GETCONT_ERRFREE(errcode); + + // 由于边缘检测算法输出的图像为二值图像,因此不需要再进行二值化处理了 + //errcode = binMachine.binarize(outimg); + //if (errcode != NO_ERROR) + // CAIC_GETCONT_ERRFREE(errcode); + + // 处理完毕返回。 + CAIC_GETCONT_ERRFREE(NO_ERROR); +#undef CAIC_GETCONT_ERRFREE +} + +// Kernel 函数:_searchPrimitiveContourKer(匹配并标记轮廓) +__global__ void _searchPrimitiveContourKer( + ImageCuda inimg, ImageCuda outimg, ImageCuda abnorimg, + ImageCuda prmtcont, ImageCuda prmtreg, unsigned trackrad) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + unsigned c = blockIdx.x * blockDim.x + threadIdx.x; + unsigned r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算输入输出图像的访存下标。 + unsigned inidx = r * inimg.pitchBytes + c; + unsigned outidx = r * outimg.pitchBytes + c; + unsigned abnoridx = r * abnorimg.pitchBytes + c; + + // 读取输入图像中对应的像素值。 + unsigned char inpixel = inimg.imgMeta.imgData[inidx]; + + // 如果该点像素值为0,即当前点不在检出的轮廓上,则在输出图像中直接赋 0 值, + // 不进行后续的搜索处理。 + if (inpixel == 0) { + outimg.imgMeta.imgData[outidx] = 0; + abnorimg.imgMeta.imgData[abnoridx] = 0; + return; + } + + // 按照由中心向周围的方式搜索当前点对应的物体标记值。最先判断当前点位置在标 + // 准轮廓图像中是否有对应的轮廓标记。如果存在对应的轮廓标记,通过后续的循环 + // 条件中的 prmtcontpxl == 0 则会略过整个后续搜索。 + unsigned prmtcontidx = r * prmtcont.pitchBytes + c; + unsigned char prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + + // 由近及远搜索当前位置的临近位置,查看是否可以命中标准轮廓上的点。 + int curr, curc; + // 外层循环处理半径 + for (int currad = 1; currad <= trackrad && prmtcontpxl == 0; currad++) { + // 迭代各个半径下的点,由中点向对角点检测。当发现某一标准轮廓点时,退出 + // 循环,不再进一步搜索。 + for (int i = 0; i < trackrad && prmtcontpxl == 0; i++) { + // 检测上方右侧点 + curc = c + i; + curr = r - currad; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc < prmtcont.imgMeta.width || curr >= 0) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 检测下方右侧点 + curc = c + i; + curr = r + currad; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc < prmtcont.imgMeta.width || curr < prmtcont.imgMeta.height) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 检测左方下侧点 + curc = c - currad; + curr = r + i; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc >= 0 || curr < prmtcont.imgMeta.height) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 检测右方下侧点 + curc = c + currad; + curr = r + i; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc < prmtcont.imgMeta.width || curr < prmtcont.imgMeta.height) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 根据计算公式,左侧点(上侧点)要比右侧点(下侧点)更加外围一些, + // 故而统一在稍候检测左侧系列点。 + // 检测上方左侧点 + curc = c - i - 1; + curr = r - currad; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc >= 0 || curr >= 0) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 检测下方左侧点 + curc = c - i - 1; + curr = r + currad; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc >= 0 || curr < prmtcont.imgMeta.height) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 检测左方上侧点 + curc = c - currad; + curr = r - i - 1; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc >= 0 || curr >= 0) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + + // 检测右方上侧点 + curc = c + currad; + curr = r - i - 1; + prmtcontidx = curr * prmtcont.pitchBytes + curc; + if (curc < prmtcont.imgMeta.width || curr >= 0) + prmtcontpxl = prmtcont.imgMeta.imgData[prmtcontidx]; + if (prmtcontpxl != 0) + break; + } + } + + // 根据是否找到标准轮廓点作出输出动作 + if (prmtcontpxl != 0) { + // 当匹配到标准轮廓点时,标记输出图像,但不标记异常点图像。 + outimg.imgMeta.imgData[outidx] = prmtcontpxl + OBJ_IDX_OFFSET; + abnorimg.imgMeta.imgData[abnoridx] = 0; + } else { + // 当匹配标准轮廓点失败时,标记该点为异常点,写入异常点图像。 + outimg.imgMeta.imgData[outidx] = 0; + abnorimg.imgMeta.imgData[abnoridx] = + prmtreg.imgMeta.imgData[r * prmtreg.pitchBytes + c]; + } +} + +// Host 成员方法:searchPrimitiveContour(匹配并标记轮廓) +__host__ int ConsolidateAndIdentifyContours::searchPrimitiveContour( + Image *inimg, Image *outimg, Image *abnormalimg) +{ + // 当标准轮廓点和标准区域点未设置时,报错返回。 + if (this->primitiveContour == NULL || this->primitiveRegion == NULL) + return OP_OVERFLOW; + + // 当输入的参数含有 NULL 指针时,报错返回。 + if (inimg == NULL || outimg == NULL || abnormalimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + // 局部变量,本次操作的图像尺寸 + size_t imgw = inimg->roiX2 - inimg->roiX1; + size_t imgh = inimg->roiY2 - inimg->roiY1; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将标准轮廓图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(this->primitiveContour); + if (errcode != NO_ERROR) + return errcode; + + // 根据标准轮廓图像的 ROI 区域尺寸调整计算尺寸。 + if (imgw > this->primitiveContour->roiX2 - this->primitiveContour->roiX1) + imgw = this->primitiveContour->roiX2 - this->primitiveContour->roiX1; + if (imgh > this->primitiveContour->roiY2 - this->primitiveContour->roiY1) + imgh = this->primitiveContour->roiY2 - this->primitiveContour->roiY1; + + // 将标准区域图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(this->primitiveRegion); + if (errcode != NO_ERROR) + return errcode; + + // 根据标准区域图像的 ROI 区域尺寸调整计算尺寸。 + if (imgw > this->primitiveRegion->roiX2 - this->primitiveRegion->roiX1) + imgw = this->primitiveRegion->roiX2 - this->primitiveRegion->roiX1; + if (imgh > this->primitiveRegion->roiY2 - this->primitiveRegion->roiY1) + imgh = this->primitiveRegion->roiY2 - this->primitiveRegion->roiY1; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice(outimg, imgw, imgh); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } else { + // 如果输出图片已经含有数据,则用这个数据更新最终参与计算的尺寸 + if (imgw > outimg->roiX2 - outimg->roiX1) + imgw = outimg->roiX2 - outimg->roiX1; + if (imgh > outimg->roiY2 - outimg->roiY1) + imgh = outimg->roiY2 - outimg->roiY1; + } + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(abnormalimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice(abnormalimg, imgw, imgh); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } else { + // 如果输出图片已经含有数据,则用这个数据更新最终参与计算的尺寸 + if (imgw > abnormalimg->roiX2 - abnormalimg->roiX1) + imgw = abnormalimg->roiX2 - abnormalimg->roiX1; + if (imgh > abnormalimg->roiY2 - abnormalimg->roiY1) + imgh = abnormalimg->roiY2 - abnormalimg->roiY1; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取标准轮廓图像的 ROI 子图像。 + ImageCuda prmtcontsubimgCud; + errcode = ImageBasicOp::roiSubImage(this->primitiveContour, &prmtcontsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取标准区域图像的 ROI 子图像。 + ImageCuda prmtregsubimgCud; + errcode = ImageBasicOp::roiSubImage(this->primitiveRegion, &prmtregsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取异常点图像的 ROI 子图像。 + ImageCuda abnorsubimgCud; + errcode = ImageBasicOp::roiSubImage(abnormalimg, &abnorsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据之前得到的计算区域尺寸调整子图像的尺寸。 + insubimgCud.imgMeta.width = prmtcontsubimgCud.imgMeta.width = + prmtregsubimgCud.imgMeta.width = + outsubimgCud.imgMeta.width = + abnorsubimgCud.imgMeta.width = imgw; + insubimgCud.imgMeta.height = prmtcontsubimgCud.imgMeta.height = + prmtregsubimgCud.imgMeta.height = + outsubimgCud.imgMeta.height = + abnorsubimgCud.imgMeta.height = imgh; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用核函数,根据阈值 threshold 进行二值化处理。 + _searchPrimitiveContourKer<<>>( + insubimgCud, outsubimgCud, abnorsubimgCud, + prmtcontsubimgCud, prmtregsubimgCud, this->trackRad); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/ConsolidateAndIdentifyContours.h b/okano_3_0/ConsolidateAndIdentifyContours.h new file mode 100644 index 0000000..3efdfd6 --- /dev/null +++ b/okano_3_0/ConsolidateAndIdentifyContours.h @@ -0,0 +1,246 @@ +// ConsolidateAndIdentifyContours.h +// 创建人:于玉龙 +// +// 实例检出(ConsolidateAndIdentifyContours) +// 功能说明:利用图像中图形的轮廓信息检测物体。 +// +// 修订历史: +// 2014年09月26日(于玉龙) +// 初始版本 +// 2014年09月28日(于玉龙) +// 实现了对图像的前处理。 +// 2014年09月29日(于玉龙) +// 实现了根据标准轮廓图像和标准区域图像标记检出轮廓的处理步骤。 + + +#ifndef __CONSOLIDATEANDIDENTIFYCONTOURS_H__ +#define __CONSOLIDATEANDIDENTIFYCONTOURS_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "Morphology.h" +#include "CombineImage.h" +#include "RobustEdgeDetection.h" +#include "Thinning.h" +#include "Binarize.h" + + +// 类:ConsolidateAndIdentifyContours +// 继承自:无 +// 对图像进行边缘检测,利用边缘信息,匹配已知物体的边缘轮廓信息。 +class ConsolidateAndIdentifyContours +{ +protected: + // 成员变量:dilationRad(膨胀处理半径) + // 图像提取边缘后需要通过膨胀和细化处理取得连续的轮廓信息,这个变量记录了 + // 进行膨胀操作的半径。 + unsigned dilationRad; + + // 成员变量:trackRad(搜索半径) + // 检出的边缘需要匹配标准物体的边缘,这里是进行匹配过程中容许的误差半径。 + unsigned trackRad; + + // 成员变量:primitiveContour(标准轮廓图像) + // 标准轮廓图像,其中不同物体的轮廓通过标记值 1、2、3 等记录于图像中。 + Image *primitiveContour; + + // 成员变量:primitiveRegion(标准区域图像) + // 标准区域图像,其中不同物体的区域通过标记值 1、2、3 等记录于图像中。 + Image *primitiveRegion; + + // 静态成员变量:redMachine(边缘检测处理机) + // 进行边缘检测的算法。 + static RobustEdgeDetection *redMachine; + + // 成员变量:morphMachine(膨胀处理机) + // 进行膨胀处理的算法。 + Morphology morphMachine; + + // 成员变量:combineMachine(图像合并处理机) + // 用于合并有多个边缘检测处理机检测出来的 + CombineImage combineMachine; + + // 成员变量:thinMachine(细化处理机) + // 用于将边缘检测的合成输出图像进行细化处理 + Thinning thinMachine; + + // 成员变量:binMachine(二值化处理机) + // 用于将边缘处理得到的最终图像转化为二值图像,以方便进行后续的匹配处理。 + //Binarize binMachine; + + // Host 成员方法:initRedMachine(初始化边缘检测处理机) + // 对边缘检测处理机进行初始化。考虑到边缘检测在整个系统范围内采用了同样的 + // 参数,因此这里用静态成员,避免导致重复的资源申请与释放的操作。 + __host__ int // 返回值:错误码,如果处理正确返回 NO_ERROR。 + initRedMachine(); + + // Host 成员方法:initMorphMachine(初始化膨胀处理机) + // 对膨胀处理机进行初始化。需要将膨胀处理参数从模版工厂中找出对应的模版, + // 赋给膨胀处理机。 + __host__ int // 返回值:错误码,如果处理正确返回 NO_ERROR。 + initMorphMachine(); +public: + // Host 成员方法:getCsldtContoursImg(获取轮廓图像) + // 在进行轮廓匹配前调用该函数,从输入图片生成相对标准的轮廓图像。生成该轮 + // 廓图像的步骤包括:(1)首先利用多种不同参数进行边缘检测,并合并这些检测 + // 结果,形成比较粗糙的边缘;(2)然后通过膨胀连接边缘的断线;(3)之后, + // 通过细化算法将较粗的边缘转换为单像素线宽的曲线线条。 + __host__ int // 返回值:错误码,如果处理正确返回 NO_ERROR。 + getCsldtContoursImg( + Image *inimg, // 输入图像,通常为原始图像 + Image *outimg // 输出图像,有单像素线宽的曲线线条构成的图像 + ); + + // Host 成员方法:searchPrimitiveContour(匹配并标记轮廓) + // 从检测中的轮廓图像中匹配标准轮廓图像中的相关轮廓。被匹配上的边缘将被标记 + // 成对应的标号信息,未匹配上的轮廓被标记为异常点。 + __host__ int + searchPrimitiveContour( + Image *inimg, // 输入图像,已检测出来的边缘图像 + Image *outimg, // 输出图像,匹配并被标记出来的轮廓图像。 + Image *abnormalimg // 异常点图像 + ); + +public: + // 构造函数:ConsolidateAndIdentifyContours + // 无参数版本的构造函数,各种处理半径默认值为 1。 + __host__ + ConsolidateAndIdentifyContours() + { + // 向各个参数赋默认值 + dilationRad = 1; + trackRad = 1; + primitiveContour = NULL; + primitiveRegion = NULL; + + // 初始化各个算法的处理器 + initRedMachine(); + initMorphMachine(); + } + + // 构造函数:ConsolidateAndIdentifyContours + // 传递参数包括膨胀半径和搜索半径,默认值为 1。 + __host__ __device__ + ConsolidateAndIdentifyContours( + unsigned dilationrad, // 膨胀处理半径 + unsigned trackrad, // 搜索半径 + Image *prmtcont = NULL, // 标准轮廓图像 + Image *prmtreg = NULL // 标准区域图像 + ) { + // 向各个参数赋默认值 + dilationRad = 1; + trackRad = 1; + primitiveContour = NULL; + primitiveRegion = NULL; + + // 初始化各个算法的处理器 + initRedMachine(); + initMorphMachine(); + + // 向各个参数赋指定值 + setDilationRad(dilationRad); + setTrackRad(trackRad); + } + + // 成员方法:getDilationRad(获取膨胀处理半径) + // 获取膨胀处理半径。 + __host__ __device__ unsigned // 返回值:膨胀处理半径 + getDilationRad() + { + // 直接返回膨胀处理半径参数。 + return this->dilationRad; + } + + // 成员方法:setDilationRad(设置膨胀处理半径) + // 设置膨胀处理半径。 + __host__ __device__ int // 返回值:错误码,如果处理正确返回 + // NO_ERROR。 + setDilationRad( + unsigned dilationrad // 新的膨胀处理半径 + ) { + // 取出原来的膨胀处理半径,已被模板生成失败时可以回滚到原来的参数。 + unsigned oldrad = this->dilationRad; + + // 由于膨胀半径可以为任何非负整数,因此所有的无符号数都是合法的输入。 + this->dilationRad = dilationrad; + + // 如果新设置的半径和原来的半径是一样的,则不需要申请新的模板,直接返 + // 回即可。 + if (oldrad == this->dilationRad) + return NO_ERROR; + + // 根据新设置的半径,更新膨胀处理器中对应的模板。 + int errcode; + errcode = initMorphMachine(); + + // 如果申请新模板失败,则回滚到原来的半径参数。 + if (errcode != NO_ERROR) + this->dilationRad = oldrad; + + // 处理完毕,返回。 + return errcode; + } + + // 成员方法:getTrackRad(获取搜索半径) + // 获取搜索半径。 + __host__ __device__ unsigned // 返回值:搜索半径 + getTrackRad() + { + // 直接返回搜索半径参数。 + return this->trackRad; + } + + // 成员方法:setTrackRad(设置搜索半径) + // 设置搜索半径。 + __host__ __device__ int // 返回值:错误码,如果处理正确返回 + // NO_ERROR。 + setTrackRad( + unsigned trackrad // 新的搜索半径 + ) { + // 由于搜索半径可以为任何非负整数,因此所有的无符号数都是合法的输入。 + this->trackRad = trackrad; + return NO_ERROR; + } + + // 成员方法:getPrimitiveContour(获取标准轮廓图像) + // 获取标准轮廓图像。 + __host__ __device__ Image * // 返回值:标准轮廓图像 + getPrimitiveContour() + { + // 直接返回标准轮廓图像参数。 + return this->primitiveContour; + } + + // 成员方法:setPrimitiveContour(设置标准轮廓图像) + // 设置标准轮廓图像。 + __host__ __device__ int // 返回值:错误码,如果处理正确返回 NO_ERROR。 + setPrimitiveContour( + Image *prmtcont // 新的标准轮廓图像 + ) { + // 适用新的标准轮廓图像替换原来的图像 + this->primitiveContour = prmtcont; + return NO_ERROR; + } + + // 成员方法:getPrimitiveRegion(获取标准区域图像) + // 获取标准区域图像。 + __host__ __device__ Image * // 返回值:标准区域图像 + getPrimitiveRegion() + { + // 直接返回标准区域图像参数。 + return this->primitiveRegion; + } + + // 成员方法:setPrimitiveRegion(设置标准区域图像) + // 设置标准轮廓图像。 + __host__ __device__ int // 返回值:错误码,如果处理正确返回 NO_ERROR。 + setPrimitiveRegion( + Image *prmtreg // 新的标准区域图像 + ) { + // 适用新的标准区域图像替换原来的图像 + this->primitiveRegion = prmtreg; + return NO_ERROR; + } +}; + +#endif diff --git a/okano_3_0/ContourMatch.cu b/okano_3_0/ContourMatch.cu new file mode 100644 index 0000000..b2e959a --- /dev/null +++ b/okano_3_0/ContourMatch.cu @@ -0,0 +1,241 @@ +#include "ContourMatch.h" +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 4 +#define DEF_BLOCK_Y 2 + +// Device 子程序: _getDistanceDev(得到距离) +// 试算两点间的距离的平方值。 +static __device__ int // 返回值: 两点间的距离的平方值 +_getDistanceDev( + int x1, // 第一个点的横坐标 + int y1, // 第一个点的纵坐标 + int x2, // 第二个点的横坐标 + int y2 // 第二个点的纵坐标 +); + +// Device 子程序: _ifInCurDev(是否在闭合轮廓内) +// 判断一个点是否在闭合轮廓曲线内。 +static __device__ bool // 返回值: 是否在闭合轮廓曲线内 +_ifInCurDev( + Curve curve, // 输入的闭合轮廓曲线 + int x, // 被判断点的横坐标 + int y // 被判断点的纵坐标 +); + +// Kernel 函数: _conMatKer(轮廓匹配) +// 按宽度objCurve->curveBandWidth对轮廓曲线周围设定,设定值为 +// objCurve->ownerObjectsIndices[0] +100,如果曲线是闭合轮廓,则将内部设定为 +// objCurve->ownerObjectsIndices[0]的值。 +static __global__ void // 无返回值 +_conMatKer( + Curve curve, // 输入曲线 + Image outimg, // 输出图像 + ImageCuda inimg // 输入图像 +); + + +// Device 子程序: _getDistanceDev(得到距离) +static __device__ int _getDistanceDev(int x1, int y1, int x2, int y2) +{ + // 返回两个点间距离的平方值 + return ((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + +} + +// Device 子程序: _ifInCurDev(是否在闭合轮廓内) +static __device__ bool _ifInCurDev(Curve curve,int xidx,int yidx) +{ + + // 寄存器变量,用来记录该点上方做出的射线与曲线的交点数。 + int downcount1 = 0; + // 寄存器变量,用来记录该点下方做出的射线与曲线的交点数。 + int downcount2 = 0; + + // 曲线上点的个数。 + int length = curve.curveLength; + + // 遍历曲线上的各点,和该点做出的射线做比较。 + for (int i = 0; i < length; i++) { + // 取得当前要比较的曲线上的点的横坐标。 + int x = curve.crvData[2 * i]; + // 取得当前要比较的曲线上的点的纵坐标。 + int y = curve.crvData[2 * i + 1]; + + // 曲线中的下一个点的位置。 + int j = (i + 1) % length; + int x2 = curve.crvData[2 * j]; + + // 曲线中上一个点的位置。 + int k = (i - 1 + length) % length; + int x3 = curve.crvData[2 * k]; + + // 曲线上的第 i 个点与当前点在同一列上 + if (x == xidx) { + // 向下方竖起做一条射线, + // 当曲线的上一个点和下一点在两侧时,或是上一个点在一侧,下一个点在一条线上时 + // 寄存器变量加1。 + if (y < yidx) { + if (((x3 - x) * (x2 - x) < 0) || ((x2 != x)&& (x3 == x))) { + downcount1++; + } + } + + // 向上方竖起做一条射线, + // 当曲线的上一个点和下一点在两侧时,或是下一个点在一侧,上一个点在一条线上时 + // 寄存器变量加1。 + if (y > yidx) { + if (((x3 - x) * (x2 - x) < 0)|| ((x3 != x)&& (x2 == x))) { + downcount2++; + } + } + } + + } + + // 交点数均为奇数则判定在曲线内部 + if ((downcount1 % 2 == 1)&&(downcount2 % 2 == 1)) { + return true; + } + + // 如果交点数有偶数,说明不在轮廓内。 + return false; +} + +// Kernel 函数: _conMatKer(轮廓匹配) +static __global__ void _conMatKer(Curve curve,Image outimg, ImageCuda inimg) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= outimg.width || r >= outimg.height) + return; + + // 计算对应的图像数据数组下标。 + int idx = r * inimg.pitchBytes + c; + + // 曲线上点的个数。 + int length = curve.curveLength; + + // 全局变量,当i值大于等于曲线点的个数时,说明已经遍历所有曲线上的点,且该点符合条件。 + int i; + + + outimg.imgData[idx] = 0; + + // 遍历曲线上的各点判断该点是否符合条件,即该点与轮廓曲线的最短距离小于 curveBandWidth。 + for (i = 0; i < length; i++) { + + // 当发现该点距离某个曲线上的点满足条件时,对该位置设定值 + // ownerObjectsIndices[0] +100,并跳出循环。 + if(_getDistanceDev(c, r, curve.crvData[2 * i], + curve.crvData[2 * i + 1]) < 100/* curve.curveBandWidth * curve.crvMeta.curveBandWidth*/) { + outimg.imgData[idx] = 200/*curve.ownerObjectsIndices[0] + 100*/; + break; + } + } + + // 当曲线是闭合轮廓曲线时,则对轮廓内且不在轮廓边缘处的点设定值ownerObjectsIndices[0]。 + if (i >= length && (curve.closed == 1)) { + if(_ifInCurDev(curve, c, r)) { + outimg.imgData[idx] = 100/*curve.ownerObjectsIndices[0]*/; + } + + } +} + +__host__ int ContourMatch::contourMatch(Image *inimg, Image **outimg, Curve** curve, int num) +{ + // 检查输入输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 判断输入曲线是否为空 + if (curve == NULL) + return NULL_POINTER; + + // 检查输入参数是否有数据 + //if (curve->curveLength <= 0) + //return INVALID_DATA; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + + for (int i = 0; i < num; ++i) { + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg[i]); + if (errcode != NO_ERROR) + { + cout << "errcode = ImageBasicOp::copyToCurrentDevice(outimg[i]);" << endl; + return CUDA_ERROR; + } + + // 将输入曲线拷贝到 Device 内存中。 + errcode = CurveBasicOp::copyToCurrentDevice(curve[i]); + if (errcode != NO_ERROR) + { + cout << "CurveBasicOp::copyToCurrentDevice(curve[i]);" << endl; + return CUDA_ERROR; + } + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize,gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 创建流 + cudaStream_t *stream = new cudaStream_t[num]; + for (int i = 0; i < num; ++i) { + cudaStreamCreate(&stream[i]); + } + + // 在流中执行核函数。 + for (int i = 0; i < num; ++i) { + _conMatKer<<>>(*curve[i], *outimg[i],insubimgCud); + + if (cudaGetLastError() != cudaSuccess) { + + cout << "kernel error" << endl; + return CUDA_ERROR; + } + } + + // 销毁流 + for (int i = 0; i < num; ++i) { + cudaStreamDestroy(stream[i]); + } + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; + +} diff --git a/okano_3_0/ContourMatch.h b/okano_3_0/ContourMatch.h new file mode 100644 index 0000000..3344fad --- /dev/null +++ b/okano_3_0/ContourMatch.h @@ -0,0 +1,64 @@ +// ContourMatch.h +// 创建者:孙慧琪 +// +// 轮廓匹配(ContourMatch) +// 功能说明:根据曲线对轮廓进行匹配,按宽度objCurve->curveBandWidth对轮廓曲线周围设定,设 +// 定值为 objCurve->ownerObjectsIndices[0] +100,如果曲线是闭合轮廓,则将内部设定为 +// objCurve->ownerObjectsIndices[0]的值。 + + +// 修订历史: +// 2014年8月28日(孙慧琪) +// 初始版本,函数方法类初步实现。 +// 2014年9月02日(孙慧琪) +// 实现对轮廓周围曲线的设定。 +// 2014年9月07日 (孙慧琪) +// 添加判断点在曲线轮廓内的函数功能。 +// 2014年9月12日 (孙慧琪) +// 修改完善关于判断一个点是否在曲线轮廓内的算法的实现。 +// 2014年9月18日 (孙慧琪) +// 使用多曲线测试,并尝试加入对多曲线处理的功能。 +// 2014年9月22日 (孙慧琪) +// 增加流操作,实现多曲线的操作处理。 + + +#ifndef __CONTOURMATCH_H__ +#define __CONTOURMATCH_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "Curve.h" + + +// 类:ContourMatch(轮廓匹配) +// 继承自:无 +// 根据曲线对轮廓进行匹配,按宽度objCurve->curveBandWidth对轮廓曲线周围设定,设 +// 定值为 objCurve->ownerObjectsIndices[0] +100,如果曲线是闭合轮廓,则将内部设定为 +// objCurve->ownerObjectsIndices[0]的值。 + +class ContourMatch { + +public: + + // 构造函数: ContourMatch + // 无参数版本的构造函数,因为该类没有成员变量,所以该构造函数为空。 + // 没有需要设置默认值的成员变量。 + __host__ __device__ + ContourMatch() {} + + // Host 成员方法:contourMatch(轮廓匹配) + // 根据曲线对轮廓进行匹配。 + + + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + contourMatch( + Image *inimg, // 输入图像 + Image **outimg, // 输出图像组 + Curve **curve, // 输入曲线组 + int num // 曲线个数 + ); +}; + +#endif + diff --git a/okano_3_0/ConvexHull.cu b/okano_3_0/ConvexHull.cu new file mode 100644 index 0000000..63701aa --- /dev/null +++ b/okano_3_0/ConvexHull.cu @@ -0,0 +1,3599 @@ +// ConvexHull.cu +// 凸壳算法实现。 + +#include "ConvexHull.h" + +#include + +// 宏:CH_DEBUG_KERNEL_PRINT(Kernel 调试打印开关) +// 打开该开关则会在 Kernel 运行时打印相关的信息,以参考调试程序;如果注释掉该 +// 宏,则 Kernel 不会打印这些信息,但这会有助于程序更快速的运行。 +//#define CH_DEBUG_KERNEL_PRINT + +// 宏:CH_DEBUG_CPU_PRINT(CPU版本 调试打印开关) +// 打开该开关则会在 CPU 版本运行时打印相关的信息,以参考调试程序;如果注释掉该 +// 宏,则 CPU 不会打印这些信息,但这会有助于程序更快速的运行。 +//#define CH_DEBUG_CPU_PRINT + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的 2D Block 尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLOCK_1D +// 定义了默认的 1D Block 尺寸。 +#define DEF_BLOCK_1D 512 + +// 宏:DEF_WARP_SIZE +// 定义了 Warp 尺寸 +#define DEF_WARP_SIZE 32 + +// 宏:CH_LARGE_ENOUGH +// 定义了一个足够大的正整数,该整数在使用过程中被认为是无穷大。 +#define CH_LARGE_ENOUGH ((1 << 30) - 1) + +// Kernel 函数:_initiateImgKer(实现将图像初始化为 threshold 算法) +// 图像的 threshold 由用户决定,所以在初始化输入图像时 +// 将输入图像内各个点的像素值置为 threshold。 +static __global__ void +_initiateImgKer( + ImageCuda inimg, // 输入图像 + unsigned char threshold // 阈值 +); + +// Kernel 函数:_initLabelAryKer(初始化 LABEL 数组) +// 在凸壳迭代前初始化 LABEL 数组,初始化后的 LABEL 数组要求除最后一个元素为 1 +// 以外,其他的元素皆为 0。 +static __global__ void // Kernel 函数无返回值 +_initLabelAryKer( + int label[], // 待初始化的 LABEL 数组。 + int cstcnt // LABEL 数组长度。 +); + +// Kernel 函数:_swapEdgePointKer(寻找最左最右点) +// 寻找一个坐标点集中的最左和最右两侧的点,并将结果存入一个整型数组中。如果存在 +// 多个最左点或者多个最右点的情况时,则以 y 值最大者为准。找到最左最右点后将直 +// 接修改输入坐标点集,使最左点处于坐标点集的第一个点,输出点处于坐标点集的最末 +// 一点。 +static __global__ void // Kernel 函数无返回值 +_swapEdgePointKer( + CoordiSetCuda cst, // 待寻找的坐标点集 + int edgecst[4], // 寻找到的最左最右点的坐标。其中下标 0 和 1 表示最 + // 左点的下标,下标 2 和 3 表示最右点的下标。该参数 + // 可以为 NULL。 + int edgeidx[2] // 寻找到的最左点和最右点对应于输入坐标点集的下标。 + // 该参数可以为 NULL。 +); + +// Kernel 函数: _updateDistKer(更新点集的垂距信息) +// 根据目前已知的凸壳上的点集和区域的标签值,找出当前每个点所在区域的最左最右 +// 点,根据点到直线的垂距公式,计算点集的附带数据:点到当前所在区域的最左最右点 +// 构成的直线的垂直距离。并且将垂距为负的点标记一下。 +static __global__ void // Kernel 函数无返回值 +_updateDistKer( + CoordiSetCuda cst, // 输入点集,也是输出点集,更新点集的 + // attachData,也就是垂距的信息。 + CoordiSetCuda convexcst, // 目前已知凸壳上的点集,即每段的最值点信息。 + int label[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 输入,当前点的数量。 + int negdistflag[] // 输出,当前点垂距为负的标志数组。如果当前点 + // 垂距为负,则对应的标志位为 1。 +); + +// Kernel 函数: _updateFoundInfoKer(更新新发现凸壳点信息) +// 根据分段扫描后得到的点集信息,更新当前区域是否有新发现的凸壳上的点,更新目前 +// 已知的凸壳上的点的位置索引。 +static __global__ void // Kernel 函数无返回值 +_updateFoundInfoKer( + int label[], // 输入,当前点集的区域标签值数组。 + float dist[], // 输入数组,所有点的垂距,即坐标点集数据结构中的 + // attachedData 域。 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目前已知的最 + // 大垂距点的位置索引数组。 + int cstcnt, // 坐标点的数量。 + int foundflag[], // 输出数组,如果当前区域内找到新的凸壳上的点,标志 + // 位置 1。 + int startidx[] // 输出,目前已知的凸壳上的点的位置索引数组,也相当 + // 于当前每段上的起始位置的索引数组。 +); + +// Kernel 函数: _updateConvexCstKer(生成新凸壳点集) +// 根据分段扫描后得到的点集信息,和每段上是否发现新凸壳点的信息,生成新的凸壳点 +// 集。 +static __global__ void // Kernel 函数无返回值 +_updateConvexCstKer( + CoordiSetCuda cst, // 输入点集 + CoordiSetCuda convexcst, // 输入,现有的凸壳上的点集。 + int foundflag[], // 输入,当前区域内有新发现点的标志位数组, + // 如果当前区域内找到新的凸壳上的点,标志位 + // 置 1。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现点 + // 的标志位的累加值。用来计算新添加的 + // 凸壳点的存放位置的偏移量。 + int startidx[], // 输入,目前已知的凸壳上点的位置索引数组, + // 也相当于当前每段上的起始位置的索引数组。 + int maxdistidx[], // 输入,分段扫描后,当前位置记录 + // 的本段目前已知的最大垂距点的位置索引数组 + int convexcnt, // 当前凸壳点的数量。 + CoordiSetCuda newconvexcst // 输出,更新后的目前已知凸壳上的点集, + // 即每段的最值点信息。 +); + +// Kernel 函数: _markLeftPointsKer(标记左侧点) +// 根据目前每段上是否有新发现凸壳点的标志,标记在新发现点左侧的点,记录到标记数 +// 组。 +static __global__ void // Kernel 函数无返回值 +_markLeftPointsKer( + CoordiSetCuda cst, // 输入点集,也是输出点集 + CoordiSetCuda newconvexcst, // 输入,更新后的目前已知凸壳上的点集,即 + // 每段的最值点信息。 + int negdistflag[], // 输入,负垂距标记值。 + int label[], // 输入,当前点集的区域标签值数组。 + int foundflag[], // 输入,当前区域内有新发现点的标志位数 + // 组,如果当前区域内找到新的凸壳上的点, + // 标志位置 1 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现 + // 点的标志位的累加值。用来计算新添加的凸 + // 壳点的存放位置的偏移量。 + int cstcnt, // 坐标点的数量。 + int leftflag[] // 输出,当前点在目前区域中新发现凸壳点的 + // 左侧的标志数组,如果在左侧,则置为 1。 +); + +// Kernel 函数: _updatePropertyKer(计算新下标) +// 计算原坐标点集中各个坐标点的新位置和新的 LABEL 值。这些值将在下一步中用来并 +// 行生成新的坐标点集。 +static __global__ void // Kernel 函数无返回值 +_updatePropertyKer( + int leftflag[], // 输入,当前点在目前区域中新发现凸壳点的左侧的标志 + // 数组,如果在左侧,则置为 1。 + int leftacc[], // 输入,偏移量数组,即当前点在目前区域中新发现凸壳 + // 点的左侧的标志的累加值。 + int negdistflag[], // 输入,垂距为负的标志数组。如果当前点垂距为负,则 + // 对应的标志位为 1。 + int negdistacc[], // 输入,垂距为正的标志的累加值数组。 + int startidx[], // 输入,目前已知的凸壳上的点的位置索引数组,也相当 + // 于当前每段上的起始位置的索引数组。 + int label[], // 输入,当前点集的区域标签值数组。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现点的标志位 + // 的累加值。用来计算新添加的凸壳点的存放位置的偏移 + // 量。 + int cstcnt, // 坐标点的数量。 + int newidx[], // 输出,每个点的新的索引值数组。 + int tmplabel[] // 输出,当前点集更新后的区域标签值数组。该数组可以 + // 和 label 数组是同一个数组,即可以进行 In-place + // 操作。 +); + +// Kernel 函数: _arrangeCstKer(形成新坐标点集) +// 根据已知信息,形成新的坐标点集和对应的 LABEL 数组,这些数据将在下一轮迭代中 +// 作为输入信息。 +static __global__ void // Kernel 函数无返回值 +_arrangeCstKer( + CoordiSetCuda cst, // 输入点集。 + int negdistflag[], // 输入,垂距为负的标志数组。如果当前点垂距为 + // 负,则对应的标志位为 1。 + int newidx[], // 输入,每个点的新的索引值数组。 + int tmplabel[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 输入,坐标点的数量。 + CoordiSetCuda newcst, // 输出,更新元素位置后的新点集。 + int newlabel[] // 输出,当前点集更新后的区域标签值数组。 +); + +// Kernel 函数:_flipWholeCstKer(整体翻转坐标点集) +// 将坐标点集由第一象限翻转到第四象限,原来 (x, y) 坐标反转后为 (-x, -y)。该步 +// 骤用来求解上半凸壳,因为翻转后的点集的下半凸壳恰好是源点集的下半凸壳的相反 +// 数。 +static __global__ void // Kernel 函数无返回值 +_flipWholeCstKer( + CoordiSetCuda incst, // 输入坐标点集,该坐标点集为只读点集 + CoordiSetCuda outcst // 输出坐标点集,该坐标点集可以和输入坐标点集相 + // 同,可进行 In-place 操作。 +); + +// Kernel 函数:_joinConvexKer(合并凸壳点) +// 将通过迭代求得的两个凸壳点集(下半凸壳点集和上半凸壳点集)合并成一个完整的凸 +// 壳点集。合并过程中两侧若有重复点需要去掉。 +static __global__ void // Kernel 函数无返回值 +_joinConvexKer( + CoordiSetCuda lconvex, // 下半凸壳 + CoordiSetCuda uconvex, // 上半凸壳 + CoordiSetCuda convex, // 整合后的凸壳 + int *convexcnt // 整合后凸壳点集的数量 +); + +// Kernel 函数:_initLabelAryKer(初始化 LABEL 数组) +static __global__ void _initLabelAryKer(int label[], int cstcnt) +{ + // 计算当前 Thread 对应的数组下标。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前下标处理的是越界数据,则直接退出。 + if (idx >= cstcnt) + return; + + // 在 LABEL 数组中,将最后一个变量写入 1,其余变量写入 0。 + if (idx == cstcnt - 1) + label[idx] = 1; + else + label[idx] = 0; +} + +// Host 成员方法:initLabelAry(初始化 LABEL 数组) +__host__ int ConvexHull::initLabelAry(int label[], int cstcnt) +{ + // 检查输入的数组是否为 NULL。 + if (label == NULL) + return NULL_POINTER; + + // 检查数组长度是否大于等于 2。 + if (cstcnt < 2) + return INVALID_DATA; + + // 计算启动 Kernel 函数所需要的 Block 尺寸与数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + + // 启动 Kernel 函数,完成计算。 + _initLabelAryKer<<>>(label, cstcnt); + + // 检查 Kernel 函数执行是否正确。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,返回。 + return NO_ERROR; +} + +// Host 成员方法:initLabelAryCpu(初始化 LABEL 数组) +__host__ int ConvexHull::initLabelAryCpu(int label[], int cstcnt) +{ + // 检查输入的数组是否为 NULL。 + if (label == NULL) + return NULL_POINTER; + + // 检查数组长度是否大于等于 2。 + if (cstcnt < 2) + return INVALID_DATA; + + // 在 LABEL 数组中,将最后一个变量写入 1,其余变量写入 0。 + for (int i = 0; i < cstcnt - 1; i++) { + label[i] = 0; + // 打印信息检查 +#ifdef CH_DEBUG_CPU_PRINT + cout << "[initLabelAryCpu]label " << i << " is " << label[i] << endl; +#endif + } + label[cstcnt - 1] = 1; + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[initLabelAryCpu]label " << cstcnt - 1 << " is " << + label[cstcnt - 1] << endl; + cout << endl; +#endif + + // 处理完毕,返回。 + return NO_ERROR; +} + +// Kernel 函数:_swapEdgePointKer(寻找最左最右点) +static __global__ void _swapEdgePointKer( + CoordiSetCuda cst, int edgecst[4], int edgeidx[2]) +{ + // 计算当前线程的下标,该 Kernel 必须以单 Block 运行,因此不涉及到 Block 相 + // 关的变量。 + int idx = threadIdx.x; + + // 当前 Thread 处理的若干个点中找到的局部最左最右点。 + int curleftx = CH_LARGE_ENOUGH, curlefty = -CH_LARGE_ENOUGH; + int currightx = -CH_LARGE_ENOUGH, currighty = -CH_LARGE_ENOUGH; + + // 当前 Thread 处理的若干个点中找到的局部最左做有点的下标。 + int curleftidx = -1, currightidx = -1; + + // 处于下标为 idx 处的坐标点坐标值。 + int curx, cury; + + // 迭代处理该线程所要处理的所有坐标点,这些坐标点是间隔 blockDim.x 个的各个 + // 坐标点。 + while (idx < cst.tplMeta.count) { + // 从 Global Memory 中读取坐标值。 + curx = cst.tplMeta.tplData[2 * idx]; + cury = cst.tplMeta.tplData[2 * idx + 1]; + + // 判断该坐标值的大小,和已经找到的最左最优值做比较,更新最左最右点。 + // 首先对最左点进行更新。 + if (curx < curleftx || (curx == curleftx && cury > curlefty)) { + curleftx = curx; + curlefty = cury; + curleftidx = idx; + } + + // 然后对最右点进行更新。 + if (curx > currightx || (curx == currightx && cury > currighty)) { + currightx = curx; + currighty = cury; + currightidx = idx; + } + + // 更新 idx,在下一轮迭代时计算下一个点。 + idx += blockDim.x; + } + + // 至此,所有 Thread 都得到了自己的局部最左最右点,现在需要将这些点放入 + // Shared Memory 中,以便下一步进行归约处理。 + + // 声明 Shared Memory,并分配各个指针。 + extern __shared__ int shdmem[]; + int *leftxShd = &shdmem[0]; + int *leftyShd = &leftxShd[blockDim.x]; + int *leftidxShd = &leftyShd[blockDim.x]; + int *rightxShd = &leftidxShd[blockDim.x]; + int *rightyShd = &rightxShd[blockDim.x]; + int *rightidxShd = &rightyShd[blockDim.x]; + + // 将局部结果拷贝到 Shared Memory 中。 + idx = threadIdx.x; + leftxShd[idx] = curleftx; + leftyShd[idx] = curlefty; + leftidxShd[idx] = curleftidx; + rightxShd[idx] = currightx; + rightyShd[idx] = currighty; + rightidxShd[idx] = currightidx; + + // 同步所有线程,使初始化Shared Memory 的结果对所有线程可见。 + __syncthreads(); + + // 下面进行折半归约迭代。这里要求 blockDim.x 必须为 2 的整数次幂。 + int curstep = blockDim.x / 2; + for (/*curstep*/; curstep >= 1; curstep /= 2) { + // 每一轮迭代都只有上一轮一半的点在参与。直到最后剩下一个线程。 + if (idx < curstep) { + // 将两个局部结果归约成一个局部结果。 + // 首先处理最左点。 + if (leftxShd[idx] > leftxShd[idx + curstep] || + (leftxShd[idx] == leftxShd[idx + curstep] && + leftyShd[idx] < leftyShd[idx + curstep])) { + leftxShd[idx] = leftxShd[idx + curstep]; + leftyShd[idx] = leftyShd[idx + curstep]; + leftidxShd[idx] = leftidxShd[idx + curstep]; + } + + // 之后处理最右点。 + if (rightxShd[idx] < rightxShd[idx + curstep] || + (rightxShd[idx] == rightxShd[idx + curstep] && + rightyShd[idx] < rightyShd[idx + curstep])) { + rightxShd[idx] = rightxShd[idx + curstep]; + rightyShd[idx] = rightyShd[idx + curstep]; + rightidxShd[idx] = rightidxShd[idx + curstep]; + } + } + + // 同步线程,使本轮迭代归约的结果对所有线程可见。 + __syncthreads(); + } + + // 下面进行一些零碎的收尾工作。由于访问 Shared Memory 不同部分,造成 Bank + // Conflict 的概率很大,因此没有采用并行处理(此时即便是并行代码,硬件上也 + // 会串行处理) + if (idx == 0) { + // 如果 edgecst 不为 NULL,则将找到的最左最右点坐标拷贝其中。 + if (edgecst != NULL) { + edgecst[0] = leftxShd[0]; + edgecst[1] = leftyShd[0]; + edgecst[2] = rightxShd[0]; + edgecst[3] = rightyShd[0]; + } + } else if (idx == DEF_WARP_SIZE) { + // 如果 edgeidx 不为 NULL,则将找到的最左最右点下标拷贝其中。 + if (edgeidx != NULL) { + edgeidx[0] = leftidxShd[0]; + edgeidx[1] = rightidxShd[0]; + } + } else if (idx == DEF_WARP_SIZE * 2) { + // 将最左点交换到坐标点集的首部。 + if (leftidxShd[0] > 0) { + curx = cst.tplMeta.tplData[0]; + cury = cst.tplMeta.tplData[1]; + cst.tplMeta.tplData[0] = leftxShd[0]; + cst.tplMeta.tplData[1] = leftyShd[0]; + cst.tplMeta.tplData[2 * leftidxShd[0]] = curx; + cst.tplMeta.tplData[2 * leftidxShd[0] + 1] = cury; + } + } else if (idx == DEF_WARP_SIZE * 3) { + // 将最右点交换到坐标点集的尾部。 + if (rightidxShd[0] < cst.tplMeta.count - 1) { + curx = cst.tplMeta.tplData[2 * (cst.tplMeta.count - 1)]; + cury = cst.tplMeta.tplData[2 * (cst.tplMeta.count - 1) + 1]; + cst.tplMeta.tplData[2 * (cst.tplMeta.count - 1)] = rightxShd[0]; + cst.tplMeta.tplData[2 * (cst.tplMeta.count - 1) + 1] = + rightyShd[0]; + cst.tplMeta.tplData[2 * rightidxShd[0]] = curx; + cst.tplMeta.tplData[2 * rightidxShd[0] + 1] = cury; + } + } +} + +// Host 成员方法:swapEdgePoint(寻找最左最右点) +__host__ int ConvexHull::swapEdgePoint(CoordiSet *cst, CoordiSet *convexcst) +{ + // 判断函数参数是否为 NULL。 + if (cst == NULL || convexcst == NULL) + return NULL_POINTER; + + // 判断参数是否包含了足够多的坐标点。 + if (cst->count < 2 || cst->tplData == NULL || + convexcst->count < 2 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) + return errcode; + + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + //CoordiSetCuda *convexcstCud = COORDISET_CUDA(convexcst); + + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = 1; + size_t sharedsize = 6 * blocksize * sizeof (int); + + _swapEdgePointKer<<>>( + *cstCud, convexcst->tplData, NULL); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// Host 成员方法:swapEdgePointCpu(寻找最左最右点) +__host__ int ConvexHull::swapEdgePointCpu(CoordiSet *cst, CoordiSet *convexcst) +{ + // 判断函数参数是否为 NULL。 + if (cst == NULL || convexcst == NULL) + return NULL_POINTER; + + // 判断参数是否包含了足够多的坐标点。 + if (cst->count < 2 || cst->tplData == NULL || + convexcst->count < 2 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 拷贝到 Host 端 + errcode = CoordiSetBasicOp::copyToHost(cst); + if (errcode != NO_ERROR) + return errcode; + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) + return errcode; + + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + + // 记录当前最左最右点,初始化为第一个点。 + int curleftx = (*cstCud).tplMeta.tplData[2 * 0]; + int curlefty = (*cstCud).tplMeta.tplData[2 * 0 + 1]; + int currightx = curleftx; + int currighty = curlefty; + int curleftidx = 0; + int currightidx = 0; + + int id; + int curx, cury; + + // 寻找最左最右点 + for(id = 1; id < (*cstCud).tplMeta.count; id++) { + // 当前点 + curx = (*cstCud).tplMeta.tplData[2 * id]; + cury = (*cstCud).tplMeta.tplData[2 * id + 1]; + + // 首先对最左点进行更新。 + if (curx < curleftx || (curx == curleftx && cury > curlefty)) { + curleftx = curx; + curlefty = cury; + curleftidx = id; + } + + // 然后对最右点进行更新。 + if (curx > currightx || (curx == currightx && cury > currighty)) { + currightx = curx; + currighty = cury; + currightidx = id; + } + } + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[swapEdgePointCpu]the left point is " << curleftidx << + " the right point is " << currightidx << endl; +#endif + + // 下面进行一些零碎的收尾工作。 + // 将找到的最左最右点坐标拷贝到凸壳点集。 + convexcst->tplData[0] = curleftx; + convexcst->tplData[1] = curlefty; + convexcst->tplData[2] = currightx; + convexcst->tplData[3] = currighty; + + // 将最左点交换到坐标点集的首部。 + if (curleftidx > 0) { + curx = (*cstCud).tplMeta.tplData[0]; + cury = (*cstCud).tplMeta.tplData[1]; + (*cstCud).tplMeta.tplData[0] = curleftx; + (*cstCud).tplMeta.tplData[1] = curlefty; + (*cstCud).tplMeta.tplData[2 * curleftidx] = curx; + (*cstCud).tplMeta.tplData[2 * curleftidx + 1] = cury; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[swapEdgePointCpu]first cst x is " << + (*cstCud).tplMeta.tplData[0] << endl; + cout << "[swapEdgePointCpu]first cst y is " << + (*cstCud).tplMeta.tplData[1] << endl; + cout << "[swapEdgePointCpu]former leftest cst x now is " << + (*cstCud).tplMeta.tplData[2 * curleftidx] << endl; + cout << "[swapEdgePointCpu]former leftest cst y now is " << + (*cstCud).tplMeta.tplData[2 * curleftidx + 1] << endl; +#endif + } + + // 将最右点交换到坐标点集的尾部。 + if (currightidx < cst->count - 1) { + curx = (*cstCud).tplMeta.tplData[2 * (cst->count - 1)]; + cury = (*cstCud).tplMeta.tplData[2 * (cst->count - 1) + 1]; + (*cstCud).tplMeta.tplData[2 * (cst->count - 1)] = currightx; + (*cstCud).tplMeta.tplData[2 * (cst->count - 1) + 1] = currighty; + (*cstCud).tplMeta.tplData[2 * currightidx] = curx; + (*cstCud).tplMeta.tplData[2 * currightidx + 1] = cury; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[swapEdgePointCpu]last cst x is " << + (*cstCud).tplMeta.tplData[2 * (cst->count - 1)] << endl; + cout << "[swapEdgePointCpu]last cst y is " << + (*cstCud).tplMeta.tplData[2 * (cst->count - 1) + 1] << endl; + cout << "[swapEdgePointCpu]former rightest cst x now is " << + (*cstCud).tplMeta.tplData[2 * currightidx] << endl; + cout << "[swapEdgePointCpu]former rightest cst y now is " << + (*cstCud).tplMeta.tplData[2 * currightidx + 1] << endl; +#endif + } + + return NO_ERROR; +} + +// Kernel 函数: _updateDistKer(更新点集的垂距信息) +static __global__ void _updateDistKer( + CoordiSetCuda cst, CoordiSetCuda convexcst, int label[], + int cstcnt, int negdistflag[]) +{ + // 记录了本 Kernel 所使用到的共享内存中各个下标所存储的数据的含义。其中, + // SIDX_BLK_CNT 表示当前 Block 所需要处理的坐标点的数量,由于坐标点的数量不 + // 一定能够被 BlockDim 整除,因此最后一个 Block 所处理的坐标点的数量要小于 + // BlockDim。 + // SIDX_BLK_LABEL_LOW 和 SIDX_BLK_LABEL_UP 用来存当前 Block 中所加载的点集 + // 的区域标签值的上下界。根据这个上下界,可以计算出当前点所在区域的最左最右 + // 点,从而根据这两点确定的直线计算当前点的垂距。 + // 从下标为 SIDX_BLK_CST 开始的其后的所有共享内存空间存储了当前 Block 中的 + // 点集坐标。坐标集中第 i 个点对应的数组下标为 2 * i 和 2 * i + 1,其中下标 + // 为 2 * i 的数据表示该点的横坐标,下标为 2 * i + 1 的数据表示该点的纵坐 + // 标。 +#define SIDX_BLK_CNT 0 +#define SIDX_BLK_LABEL_LOW 1 +#define SIDX_BLK_LABEL_UP 2 +#define SIDX_BLK_CONVEX 3 + + // 共享内存的声明。 + extern __shared__ int shdmem[]; + + // 基准索引。表示当前 Block 的起始位置索引。 + int baseidx = blockIdx.x * blockDim.x; + // 全局索引。 + int idx = baseidx + threadIdx.x; + + // 当前 Block 的第 0 个线程来处理共享内存中彼此共享的数据的初始化工作。 + if (threadIdx.x == 0) { + // 计算当前 Block 所要处理的坐标点的数量。默认情况下该值等于 BlockDim, + // 但对于最后一个 Block 来说,在坐标点总数量不能被 BlockDim 所整除的时 + // 候,需要处理的坐标点数量会小于 BlockDim。 + if (baseidx + blockDim.x <= cstcnt) + shdmem[SIDX_BLK_CNT] = blockDim.x; + else + shdmem[SIDX_BLK_CNT] = cstcnt - baseidx; + + // 计算当前 Block 所处理的坐标点中起始的 LABEL 编号。 + shdmem[SIDX_BLK_LABEL_LOW] = label[baseidx]; + + // 计算当前 Block 索要处理的坐标点中最大的 LABEL 编号。由于考虑到根据两 + // 点计算直线方程,因此所谓的最大 LABEL 编号其实是 + if (baseidx + shdmem[SIDX_BLK_CNT] <= cstcnt) + shdmem[SIDX_BLK_LABEL_UP] = + label[baseidx + shdmem[SIDX_BLK_CNT] - 1] + 1; + else + shdmem[SIDX_BLK_LABEL_UP] = label[cstcnt - 1]; + } + + // Block 内部同步,使得上一步的初始化对 Block 内的所有 Thread 可见。 + __syncthreads(); + + // 将当前 Block 处理的 LABEL 值上下界加载到寄存器,该步骤没有逻辑上的含义, + // 只是为了 GPU 处理速度更快。 + int labellower = shdmem[SIDX_BLK_LABEL_LOW]; + int labelupper = shdmem[SIDX_BLK_LABEL_UP]; + + // 为了方便代码编写,这里单独提出一个 blockcstShd 指针,指向当前 Block 所对 + // 应的点集数据的共享内存空间。 + int *convexShd = &shdmem[SIDX_BLK_CONVEX]; + + // 加载当前 Block 中所用到的 LABEL 所谓应的凸壳点,两个相邻 LABEL 的凸壳点 + // 构成的直线可用来衡量各点的垂距并以此推算出下一轮的凸壳点。将所用到的凸壳 + // 点加载的 Shared Memory 中也没有逻辑上的目的,仅仅是为了下一步计算时访存 + // 时间的缩短。 + if (threadIdx.x < labelupper - labellower + 1) { + convexShd[2 * threadIdx.x] = + convexcst.tplMeta.tplData[2 * (labellower + threadIdx.x)]; + convexShd[2 * threadIdx.x + 1] = + convexcst.tplMeta.tplData[2 * (labellower + threadIdx.x) + 1]; + } + + // Block 内部同步,使得上面所有的数据加载对 Block 内的所有 Thread 可见。下 + // 面的代码就正式的投入计算了。 + __syncthreads(); + + // 如果当前线程的全局下标越界,则直接返回,因为他没有对应的所要处理坐标点。 + if (idx >= cstcnt) + return; + + // 对于最后一个点(其实是坐标集中的最右点)是一个特殊的点,它独自处于一个 + // LABEL,因此对于它不需要进行计算,直接赋值就行了。 + if (idx == cstcnt - 1) { + cst.attachedData[idx] = 0.0f; + negdistflag[idx] = 0; + return; + } + + // 计算当前点的坐标和区域标签值。 + int curx = cst.tplMeta.tplData[2 * idx]; + int cury = cst.tplMeta.tplData[2 * idx + 1]; + int curlabelidx = 2 * (label[idx] - labellower); + + // 计算当前 LABEL 区域的最左点的坐标。 + int leftx = convexShd[curlabelidx++]; + int lefty = convexShd[curlabelidx++]; + + // 计算当前 LABEL 区域的最右点的坐标。 + int rightx = convexShd[curlabelidx++]; + int righty = convexShd[curlabelidx ]; + + // 如果当前点就是凸壳点,那么不需要计算直接赋值退出就可以了。 + if ((curx == leftx && cury == lefty) || + (curx == rightx && cury == righty)) { + cst.attachedData[idx] = 0.0f; + negdistflag[idx] = 0; + return; + } + + // 计算垂距,该计算通过最左点和最右点形成的直线作为垂距求解的依据,但实际求 + // 解过程中并不是真正的垂距,而是垂直于 y 轴的距离。当点在直线之下时,具有 + // 正垂距,当点在直线之上时,具有负垂距。 + // y ^ right + // | + + // | / + // | / -- + // | /| ^ + // | / | | dist + // | / | v + // | / * -- + // | + cur + // O| left x + // --+-----------------> + // | + float s = (float)(righty - lefty) / (rightx - leftx); + float dist = (cury - righty) - (curx - rightx) * s; + + // 将垂距信息更新到 Global 内存中作为输出。 + cst.attachedData[idx] = dist; + + // 当垂距为负值时,在负数标记数组中标记之,因为这样的点将在下一轮迭代的时候 + // 删除,以加快处理速度。 + negdistflag[idx] = ((dist < 1.0e-6f) ? 1 : 0); + + // 调试打印 +#ifdef CH_DEBUG_KERNEL_PRINT + printf("Kernel[updateDist]: (%3d, %3d) Dist %7.3f, " + "Line: (%3d, %3d) - (%3d, %3d) Label %2d\n", + curx, cury, dist, leftx, lefty, rightx, righty, label[idx]); +#endif + + // 清除对 Shared Memory 下标含义的定义,因为在其他的函数中不同的下标会有不 + // 同的含义。 +#undef SIDX_BLK_CNT +#undef SIDX_BLK_LABEL_LOW +#undef SIDX_BLK_LABEL_UP +#undef SIDX_BLK_CONVEX +} + +// 成员方法:updateDist(更新坐标点集垂距) +__host__ int ConvexHull::updateDist( + CoordiSet *cst, CoordiSet *convexcst, int label[], + int cstcnt, int negdistflag[]) +{ + // 检查输入坐标集,输出坐标集是否为空。 + if (convexcst == NULL || cst == NULL || label == NULL || + negdistflag == NULL) + return NULL_POINTER; + + // 检查当前点的数量,小于等于 0 则无效数据。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将输入坐标集拷贝到 device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 将输入输出坐标集拷贝到 device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + // 坐标集的 CUDA 相关数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *convexcstCud = COORDISET_CUDA(convexcst); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量,以及所需要的 Shared + // Memory 的数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (3 + 2 * blocksize) * sizeof (int); + + // 调用更新点集的垂距信息的核函数,计算每个点的垂距,更新负垂距标志数组。 + _updateDistKer<<>>( + *cstCud, *convexcstCud, label, cstcnt, negdistflag); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 成员方法:updateDistCpu(更新坐标点集垂距) +__host__ int ConvexHull::updateDistCpu( + CoordiSet *cst, CoordiSet *convexcst, int label[], + int cstcnt, int negdistflag[]) +{ + // 检查输入坐标集,输出坐标集是否为空。 + if (convexcst == NULL || cst == NULL || label == NULL || + negdistflag == NULL) + return NULL_POINTER; + + // 检查当前点的数量,小于等于 0 则无效数据。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将输入坐标集拷贝到 host 端。 + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 将输入输出坐标集拷贝到 host 端。 + errcode = CoordiSetBasicOp::copyToHost(cst); + if (errcode != NO_ERROR) + return errcode; + + // 坐标集的 CUDA 相关数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *convexcstCud = COORDISET_CUDA(convexcst); + + // 初始化末位 + (*cstCud).attachedData[cstcnt - 1] = 0.0f; + negdistflag[cstcnt - 1] = 0; + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateDistCpu]id " << cstcnt - 1 << " dist is "<< + (*cstCud).attachedData[cstcnt - 1] << endl; + cout << "[updateDistCpu]id " << cstcnt - 1 << " negdistflag is " << + negdistflag[cstcnt - 1] << endl; +#endif + + // 本地变量 + int curx, cury; + int leftx, lefty; + int rightx, righty; + float s, dist; + int id; + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateDistCpu]update cstcnt is " << cstcnt << endl; +#endif + + // 计算每个点对应的垂距 + for (id = 0; id < cstcnt - 1; id++) { +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateDistCpu]update dist id " << id << endl; +#endif + // 读取当前点坐标 + curx = (*cstCud).tplMeta.tplData[2 * id]; + cury = (*cstCud).tplMeta.tplData[2 * id + 1]; + + // 记录当前区域的最左点最右点 + if (id == 0 || label[id] != label[id - 1]) { + leftx = (*convexcstCud).tplMeta.tplData[2 * label[id]]; + lefty = (*convexcstCud).tplMeta.tplData[2 * label[id] + 1]; + rightx = (*convexcstCud).tplMeta.tplData[2 * (label[id] + 1)]; + righty = (*convexcstCud).tplMeta.tplData[2 * (label[id] + 1) + 1]; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateDistCpu]leftest x is " << leftx << endl; + cout << "[updateDistCpu]leftest y is " << lefty << endl; + cout << "[updateDistCpu]rightest x is " << rightx << endl; + cout << "[updateDistCpu]rightest x is " << righty << endl; +#endif + } + + // 如果当前点就是凸壳点,那么不需要计算直接赋值退出就可以了。 + if ((curx == leftx && cury == lefty) || + (curx == rightx && cury == righty)) { + (*cstCud).attachedData[id] = 0.0f; + negdistflag[id] = 0; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateDistCpu]id " << id << " dist is "<< + (*cstCud).attachedData[id] << endl; + cout << "[updateDistCpu]id " << id << " negdistflag is " << + negdistflag[id] << endl; +#endif + // 计算垂距,该计算通过最左点和最右点形成的直线作为垂距求解的依据,但实 + // 际求解过程中并不是真正的垂距,而是垂直于 y 轴的距离。当点在直线之下 + // 时,具有正垂距,当点在直线之上时,具有负垂距。 + // y ^ right + // | + + // | / + // | / -- + // | /| ^ + // | / | | dist + // | / | v + // | / * -- + // | + cur + // O| left x + // --+-----------------> + // | + } else { + s = (float)(righty - lefty) / (rightx - leftx); + dist = (cury - righty) - (curx - rightx) * s; + + // 将垂距信息更新到 输出。 + (*cstCud).attachedData[id] = dist; + + // 当垂距为负值时,在负数标记数组中标记之,因为这样的点将在下一轮 + // 迭代的时候删除,以加快处理速度。 + negdistflag[id] = ((dist < 1.0e-6f) ? 1 : 0); +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateDistCpu]id " << id << " dist is "<< + (*cstCud).attachedData[id] << endl; + cout << "[updateDistCpu]id " << id << " negdistflag is " << + negdistflag[id] << endl; +#endif + } + } + + return NO_ERROR; +} + +// Kernel 函数: _updateFoundInfoKer(更新新发现凸壳点信息) +static __global__ void _updateFoundInfoKer( + int *label, float *dist, int *maxdistidx, int cstcnt, + int *foundflag, int *startidx) +{ + // 共享内存,用来存放当前 Block 处理的 LABEL 值,其长度为 BlockDim + 1,因 + // 为需要加载下一 Blcok 的第一个 LABEL 值。 + extern __shared__ int labelShd[]; + + // 基准索引。表示当前 Block 的起始位置索引 + int baseidx = blockIdx.x * blockDim.x; + + // 全局索引。 + int idx = baseidx + threadIdx.x; + + // 初始化 Shared Memory,将当前 Block 所对应的坐标点的 LABEL 值赋值给 + // Shared Memroy,为了程序健壮性的考虑,我们将处理越界数据的那些 Thread 所 + // 对应的 LABEL 值赋值为最后一个点的 LABEL 值。 + if (idx < cstcnt) + labelShd[threadIdx.x] = label[idx]; + else + labelShd[threadIdx.x] = label[cstcnt - 1]; + + // 使用每个 Block 中第 0 个 Thread 来初始化多出来的那个 LABEL 值,初始化的 + // 规则同上面的规则一样,也做了健壮性的考量。 + if (threadIdx.x == 0) { + if (baseidx + blockDim.x < cstcnt) + labelShd[blockDim.x] = label[baseidx + blockDim.x]; + else + labelShd[blockDim.x] = label[cstcnt - 1]; + + // 如果是第一块的话,起始索引更新。 + if (blockIdx.x == 0) + startidx[0] = 0; + } + + // 块内的线程同步 + __syncthreads(); + + // 对于处理越界数据的 Thread 直接进行返回操作,不进行任何处理。 + if (idx >= cstcnt) + return; + + // 当前 Thread 处理坐标点的 LABEL 值。 + int curlabel = labelShd[threadIdx.x]; + + // 对于单独处于一个 LABEL 区域的最后一个点,该点不需要做任何查找操作,直接 + // 赋值为未找到新的凸壳点。 + if (idx == cstcnt - 1) { + foundflag[curlabel] = 0; + return; + } + + // 本函数只针对处于 LABEL 区域边界的点进行处理,对于不处于区域边界的点则直 + // 接返回。 + if (curlabel == labelShd[threadIdx.x + 1]) + return; + + // 读取当前 LABEL 区域的最大垂距和最大垂距所对应的下标和该最大垂距的值。 + int curmaxdistidx = maxdistidx[idx]; + float curmaxdist = dist[curmaxdistidx]; + + // 如果当前 LABEL 区域的最大垂距点的垂距值大于 0,则说明了在当前的 LABEL 区 + // 域内发现了凸壳点。为了健壮性的考虑,这里将 0 写为 1.0e-6。 + foundflag[curlabel] = (curmaxdist > 1.0e-6f) ? 1 : 0; + + // 更新下一个 LABEL 区域的起始下标。由于当前 Thread 是当前 LABEL 区域的最后 + // 一个,因此下一个 LABEL 区域的起始下标为当前 Thread 全局索引加 1。 + startidx[curlabel + 1] = idx + 1; + + // 调试打印 +#ifdef CH_DEBUG_KERNEL_PRINT + printf("Kernel[FoundInfo]: Label %2d - Found %1d (%7.3f at %3d), " + "End Idx %3d\n", + curlabel, foundflag[curlabel], curmaxdist, curmaxdistidx, idx); +#endif +} + +// 成员方法: updateFoundInfo(更新新发现凸壳点信息) +__host__ int ConvexHull::updateFoundInfo( + int label[], float dist[], int maxdistidx[], + int cstcnt, int foundflag[], int startidx[]) +{ + // 检查所有的输入指针或数组是否为 NULL,如果存在一个为 NULL 则报错退出。 + if (label == NULL || dist == NULL || maxdistidx == NULL || + foundflag == NULL || startidx == NULL) + return NULL_POINTER; + + // 检查坐标点的数量是否小于等于 0,若是则报错推出。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量,以及所需要的 Shared + // Memory 的数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (blocksize + 1) * sizeof (int); + + // 调用 Kernel 函数,完成计算。 + _updateFoundInfoKer<<>>( + label, dist, maxdistidx, cstcnt, foundflag, startidx); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 成员方法: updateFoundInfoCpu(更新新发现凸壳点信息) +__host__ int ConvexHull::updateFoundInfoCpu( + int label[], float dist[], int maxdistidx[], + int cstcnt, int foundflag[], int startidx[]) +{ + // 检查所有的输入指针或数组是否为 NULL,如果存在一个为 NULL 则报错退出。 + if (label == NULL || dist == NULL || maxdistidx == NULL || + foundflag == NULL || startidx == NULL) + return NULL_POINTER; + + // 检查坐标点的数量是否小于等于 0,若是则报错推出。 + if (cstcnt <= 0) + return INVALID_DATA; + + int id; + // 如果是首末位,起始索引更新。 + startidx[0] = 0; + + // 对于单独处于一个 LABEL 区域的最后一个点,该点不需要做任何查找操作,直接 + // 赋值为未找到新的凸壳点。 + foundflag[label[cstcnt - 1]] = 0; + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateFoundInfoCpu]label " << label[cstcnt - 1] << " found " << + foundflag[label[cstcnt - 1]] << endl; + cout << "[updateFoundInfoCpu]startidx " << label[0] << " is " << + startidx[label[0]] << endl; +#endif + + // 循环,更新新发现凸壳点信息,不处理第一个点 + for (id = 1; id < cstcnt; id++) { + // 处理新区域 + if (label[id] != label[id - 1]) { +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateFoundInfoCpu]label different " << endl; +#endif + // 记录新区域的起始位置 + startidx[label[id]] = id; + + // 如果当前 LABEL 区域的最大垂距点的垂距值大于 0,则说明了在当前的 + // LABEL 区域内发现了凸壳点。为了健壮性的考虑,这里将 0 写为 1.0e-6 + foundflag[label[id - 1]] = + (dist[maxdistidx[id - 1]] > 1.0e-6f) ? 1 : 0; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[updateFoundInfoCpu]label " << label[id - 1] << + " found " << foundflag[label[id - 1]] << endl; + cout << "[updateFoundInfoCpu]startidx " << label[id] << " is " << + startidx[label[id]] << endl; +#endif + } + } + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _updateConvexCstKer(生成新凸壳点集) +static __global__ void _updateConvexCstKer( + CoordiSetCuda cst, CoordiSetCuda convexcst, int foundflag[], + int foundacc[], int startidx[], int maxdistidx[], int convexcnt, + CoordiSetCuda newconvexcst) +{ + // 计算当前 Thread 的全局索引。本 Kernel 中,每个线程都对应于一个 LABEL 区 + // 域,对于发现了新凸壳点的 LABEL 区域,则需要将原来这个 LABEL 区域内的凸壳 + // 点和新发现的凸壳点同时拷贝到新的凸壳点集中。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果该 Thread 对应的时越界数据,则直接返回,不进行任何处理。 + if (idx >= convexcnt) + return; + + // 计算原来的凸壳点在新凸壳点集中的下标,由于前面的 LABEL 区域共产生了 + // foundacc[idx] 个凸壳点,因此,下标应相较于原来的下标(idx)增加了相应的 + // 数量。 + int newidx = idx + foundacc[idx]; + + // 将这个凸壳点的坐标从原来的凸壳点集中拷贝到新的凸壳点集中。 + newconvexcst.tplMeta.tplData[2 * newidx] = + convexcst.tplMeta.tplData[2 * idx]; + newconvexcst.tplMeta.tplData[2 * newidx + 1] = + convexcst.tplMeta.tplData[2 * idx + 1]; + + // 调试打印 +#ifdef CH_DEBUG_KERNEL_PRINT + printf("Kernel[UpdateConvex]: Add Old (%3d, %3d) - " + "%3d => %3d, Label %2d\n", + convexcst.tplMeta.tplData[2 * idx], + convexcst.tplMeta.tplData[2 * idx + 1], + idx, newidx, idx); +#endif + + // 如果当前 LABEL 区域中没有发现新的凸壳点,则只需要拷贝原有的凸壳点到新的 + // 凸壳点集中就可以了,不需要再进行后面的操作。 + if (foundflag[idx] == 0) + return; + + // 计算新发现的凸壳点在凸壳点集中的下标(就把它放在原来凸壳点集的后面)和该 + // 凸壳点对应的坐标点集中的下标(就是该 LABEL 区域最大垂距点的下标)。由于 + // 最大垂距点下标数组是记录的 Scanning 操作的结果,因此正确的结果存放再该 + // LABEL 区域最后一个下标处。 + newidx++; + int cstidx = maxdistidx[startidx[idx + 1] - 1]; + + // 将新发现的凸壳点从坐标点集中拷贝到新的凸壳点集中。 + newconvexcst.tplMeta.tplData[2 * newidx] = + cst.tplMeta.tplData[2 * cstidx]; + newconvexcst.tplMeta.tplData[2 * newidx + 1] = + cst.tplMeta.tplData[2 * cstidx + 1]; + + // 调试打印 +#ifdef CH_DEBUG_KERNEL_PRINT + printf("Kernel[UpdateConvex]: Add New (%3d, %3d) - " + "%3d => %3d, Label %2d\n", + cst.tplMeta.tplData[2 * cstidx], + cst.tplMeta.tplData[2 * cstidx + 1], + cstidx, newidx, idx); +#endif +} + +// Host 成员方法:updateConvexCst(生成新凸壳点集) +__host__ int ConvexHull::updateConvexCst( + CoordiSet *cst, CoordiSet *convexcst, int foundflag[], + int foundacc[], int startidx[], int maxdistidx[], int convexcnt, + CoordiSet *newconvexcst) +{ + // 检查参数中所有的指针和数组是否为空。 + if (cst == NULL || convexcst == NULL || foundacc == NULL || + foundflag == NULL || startidx == NULL || maxdistidx == NULL || + newconvexcst == NULL) + return NULL_POINTER; + + // 检查当前凸壳点的数量,小于等于 0 则无效数据。 + if (convexcnt <= 0) + return INVALID_DATA; + + int errcode; + + // 将输入坐标集拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + // 将输入凸壳点集拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 将输出的新的凸壳集拷贝到当前 Device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(newconvexcst); + if (errcode != NO_ERROR) + return errcode; + + // 获取各个坐标集的 CUDA 型数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *convexcstCud = COORDISET_CUDA(convexcst); + CoordiSetCuda *newconvexcstCud = COORDISET_CUDA(newconvexcst); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 矩阵方法分段扫描版本线程块大小。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (convexcnt + blocksize - 1) / blocksize; + + // 调用 Kernel 函数完成计算。 + _updateConvexCstKer<<>>( + *cstCud, *convexcstCud, foundflag, foundacc, startidx, + maxdistidx, convexcnt, *newconvexcstCud); + + // 判断 Kernel 函数是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:updateConvexCstCpu(生成新凸壳点集) +__host__ int ConvexHull::updateConvexCstCpu( + CoordiSet *cst, CoordiSet *convexcst, int foundflag[], + int foundacc[], int startidx[], int maxdistidx[], int convexcnt, + CoordiSet *newconvexcst) +{ + // 检查参数中所有的指针和数组是否为空。 + if (cst == NULL || convexcst == NULL || foundacc == NULL || + foundflag == NULL || startidx == NULL || maxdistidx == NULL || + newconvexcst == NULL) + return NULL_POINTER; + + // 检查当前凸壳点的数量,小于等于 0 则无效数据。 + if (convexcnt <= 0) + return INVALID_DATA; + + // 错误码 + int errcode; + + // 将输入坐标集拷贝到 Host。 + errcode = CoordiSetBasicOp::copyToHost(cst); + if (errcode != NO_ERROR) + return errcode; + + // 将输入凸壳点集拷贝到当前 Host。 + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 将输出的新的凸壳集拷贝到当前 Host。 + errcode = CoordiSetBasicOp::copyToHost(newconvexcst); + if (errcode != NO_ERROR) + return errcode; + + // 获取各个坐标集的 CUDA 型数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *convexcstCud = COORDISET_CUDA(convexcst); + CoordiSetCuda *newconvexcstCud = COORDISET_CUDA(newconvexcst); + + // 本地变量 + int newid; + int cstidx; + + // 循环处理凸壳点集 + for (int id = 0; id < convexcnt; id++) { + // 计算原来的凸壳点在新凸壳点集中的下标,由于前面的 LABEL 区域共产生了 + // foundacc[idx] 个凸壳点,因此,下标应相较于原来的下标(idx)增加了相 + // 应的数量。 + newid = id + foundacc[id]; + + // 将这个凸壳点的坐标从原来的凸壳点集中拷贝到新的凸壳点集中。 + (*newconvexcstCud).tplMeta.tplData[2 * newid] = + (*convexcstCud).tplMeta.tplData[2 * id]; + (*newconvexcstCud).tplMeta.tplData[2 * newid + 1] = + (*convexcstCud).tplMeta.tplData[2 * id + 1]; + +#ifdef CH_DEBUG_CPU_PRINT + printf("[updateConvexCstCpu]: Add Old (%3d, %3d) - " + "%3d => %3d\n", + (*convexcstCud).tplMeta.tplData[2 * id], + (*convexcstCud).tplMeta.tplData[2 * id + 1], + id, newid); +#endif + + // 计算新发现的凸壳点在凸壳点集中的下标(就把它放在原来凸壳点集的后面) + // 和该凸壳点对应的坐标点集中的下标(就是该 LABEL 区域最大垂距点的下标) + // 由于最大垂距点下标数组是记录的 Scanning 操作的结果,因此正确的结果存 + // 放再该 LABEL 区域最后一个下标处。 + if (foundflag[id]) { + newid++; + cstidx = maxdistidx[startidx[id + 1] - 1]; + + + // 将新发现的凸壳点从坐标点集中拷贝到新的凸壳点集中。 + (*newconvexcstCud).tplMeta.tplData[2 * newid] = + (*cstCud).tplMeta.tplData[2 * cstidx]; + (*newconvexcstCud).tplMeta.tplData[2 * newid + 1] = + (*cstCud).tplMeta.tplData[2 * cstidx + 1]; +#ifdef CH_DEBUG_CPU_PRINT + printf("[updateConvexCstCpu]: Add New (%3d, %3d) - " + "%3d => %3d\n", + (*cstCud).tplMeta.tplData[2 * cstidx], + (*cstCud).tplMeta.tplData[2 * cstidx + 1], + cstidx, newid); +#endif + } + } + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _markLeftPointsKer(标记左侧点) +static __global__ void _markLeftPointsKer( + CoordiSetCuda cst, CoordiSetCuda newconvexcst, int negdistflag[], + int label[], int foundflag[], int foundacc[], int cstcnt, + int leftflag[]) +{ + // 记录了本 Kernel 所使用到的共享内存中各个下标所存储的数据的含义。其中, + // SIDX_BLK_LABEL_LOW 和 SIDX_BLK_LABEL_UP 用来存当前 Block 中所加载的点集 + // 的区域标签值的上下界。根据这个上下界,可以计算出当前点所在区域的最左最右 + // 点,从而根据这两点确定的直线计算当前点的垂距。 + // 从下标为 SIDX_BLK_CONVEX_X 开始的其后的所有共享内存空间存储了当前 Block + // 所处理的所有的新的凸壳点的 X 坐标。 +#define SIDX_BLK_LABEL_LOW 0 +#define SIDX_BLK_LABEL_UP 1 +#define SIDX_BLK_CONVEX_X 2 + + // 共享内存的声明。 + extern __shared__ int shdmem[]; + + // 基准下标。表示当前 Block 第一个 Thread 所处理的下标。 + int baseidx = blockIdx.x * blockDim.x; + + // 当前 Thread 的全局下标。 + int idx = baseidx + threadIdx.x; + + // 初始化共享内存中的公共数据,为了防止写入冲突,这里只使用每个 Block 的第 + // 一个 Thread 处理初始化工作。 + if (threadIdx.x == 0) { + // 读取当前 Block 所处理的所有坐标点中最小的 LABEL 值。 + shdmem[SIDX_BLK_LABEL_LOW] = label[baseidx]; + + // 计算当前 Block 所处理的所有坐标点中最大的 LABEL 值。 + if (baseidx + blockDim.x <= cstcnt) + shdmem[SIDX_BLK_LABEL_UP] = label[baseidx + blockDim.x - 1]; + else + shdmem[SIDX_BLK_LABEL_UP] = label[cstcnt - 1]; + } + + // 同步 Block 内的所有 Thread,使得上述初始化对所有的 Thread 都可见。 + __syncthreads(); + + // 从 Shared Memory 中读取当前 Block 所处理的 LABEL 值范围。这一步骤没有实 + // 际的逻辑含义,将数据从共享内存搬入寄存器仅仅是为了加快处理速度。 + int labellower = shdmem[SIDX_BLK_LABEL_LOW]; + int labelupper = shdmem[SIDX_BLK_LABEL_UP]; + + // 定义中心点(即新增加的凸壳点)的横坐标的哑值。这是由于并不是所有的 LABEL + // 区域都会在该论迭代中发现新的凸壳点。该值要求非常的大,因为没有发现新凸壳 + // 点的区域,相当于所有的坐标点放在左侧。 +#define LP_DUMMY_CVXX CH_LARGE_ENOUGH + + // 将新凸壳点的 X 坐标存储 Shared Memory 提取出,用一个指针来表示,这样的写 + // 法是为了代码更加易于理解。 + int *newcvxxShd = &shdmem[SIDX_BLK_CONVEX_X]; + + // 在 Shared Memory 中初始化新凸壳点(中心点)的 X 坐标。 + if (threadIdx.x < labelupper - labellower + 1) { + // 计算新凸壳点在新的凸壳点集中的下标。 + int labelidx = threadIdx.x + labellower; + int newconvexidx = labelidx + foundacc[labelidx] + 1; + + // 初始化 Shared Memory 中的数据,对于没有产生新的凸壳点的 LABEL 区域来 + // 说,该值直接赋哑值。 + if (foundflag[labelidx]) + newcvxxShd[threadIdx.x] = + newconvexcst.tplMeta.tplData[2 * newconvexidx]; + else + newcvxxShd[threadIdx.x] = LP_DUMMY_CVXX; + } + + // 同步 Block 内的所有 Thread,是的上述所有初始化计算对所有 Thread 可见。 + __syncthreads(); + + // 如果当前 Thread 处理的是越界范围,则直接返回不进行任何处理。 + if (idx >= cstcnt) + return; + + // 读取当前坐标点所对应的 LABEL 值(经过校正的,表示 Shared Memory 中的下 + // 标)。 + int curlabel = label[idx] - labellower; + + // 读取当前坐标点的 x 坐标和该点的垂距值。 + int curx = cst.tplMeta.tplData[2 * idx]; + int curnegflag = negdistflag[idx]; + + // 对于所有垂距大于等于 0,且 x 坐标小于中心点坐标时认为该点在中心点左侧。 + // (因为所有垂距小于 0 的点将在下一轮迭代中被排除,因此,这里没有将垂距小 + // 于 0 的点设置左侧标志位) + if (curx < newcvxxShd[curlabel] && curnegflag == 0) + leftflag[idx] = 1; + else + leftflag[idx] = 0; + + // 调试打印 +#ifdef CH_DEBUG_KERNEL_PRINT + printf("Kernel[LeftPoint]: (%3d, %3d) d=%8.3f, " + "Label %2d ( NC.x %3d ) Left %1d\n", + cst.tplMeta.tplData[2 * idx], cst.tplMeta.tplData[2 * idx + 1], + cst.attachedData[idx], curlabel + labellower, + newcvxxShd[curlabel], leftflag[idx]); +#endif + + // 清除函数内部的宏定义,防止同后面的函数造成冲突。 +#undef LP_TMPX_DUMMY +#undef SIDX_BLK_LABEL_LOW +#undef SIDX_BLK_LABEL_UP +#undef SIDX_BLK_CONVEX_X +} + +// Host 成员方法:markLeftPoints(标记左侧点) +__host__ int ConvexHull::markLeftPoints( + CoordiSet *cst, CoordiSet *newconvexcst, int negdistflag[], + int label[], int foundflag[], int foundacc[], int cstcnt, + int leftflag[]) +{ + // 检查参数中所有的指针和变量是否为空。 + if (cst == NULL || newconvexcst == NULL || label == NULL || + foundacc == NULL || foundflag == NULL || leftflag == NULL) + return NULL_POINTER; + + // 检查当前点的数量,小于等于 0 则无效数据。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将输入坐标点集拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + // 将新的凸壳点集拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(newconvexcst); + if (errcode != NO_ERROR) + return errcode; + + // 获取坐标点集和凸壳点集的 CUDA 相关数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *newconvexcstCud = COORDISET_CUDA(newconvexcst); + + // 计算 Kernel 函数所需要的 Block 尺寸和数量,以及每个 Block 所使用的 + // Shared Memory 的数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (2 + blocksize) * sizeof (int); + + // 调用 Kernel 函数,完成计算。 + _markLeftPointsKer<<>>( + *cstCud, *newconvexcstCud, negdistflag, label, + foundflag, foundacc, cstcnt, leftflag); + + // 判断 Kernel 函数运行是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:markLeftPointsCpu(标记左侧点) +__host__ int ConvexHull::markLeftPointsCpu( + CoordiSet *cst, CoordiSet *newconvexcst, int negdistflag[], + int label[], int foundflag[], int foundacc[], int cstcnt, + int leftflag[]) +{ + // 检查参数中所有的指针和变量是否为空。 + if (cst == NULL || newconvexcst == NULL || label == NULL || + foundacc == NULL || foundflag == NULL || leftflag == NULL) + return NULL_POINTER; + + // 检查当前点的数量,小于等于 0 则无效数据。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将输入坐标点集拷贝到 Host。 + errcode = CoordiSetBasicOp::copyToHost(cst); + if (errcode != NO_ERROR) + return errcode; + + // 将新的凸壳点集拷贝到当前 Host。 + errcode = CoordiSetBasicOp::copyToHost(newconvexcst); + if (errcode != NO_ERROR) + return errcode; + + // 获取坐标点集和凸壳点集的 CUDA 相关数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *newconvexcstCud = COORDISET_CUDA(newconvexcst); + + // 本地变量 + int newconvexcstx; + int newconvexidx; + + // 定义中心点(即新增加的凸壳点)的横坐标的哑值。这是由于并不是所有的 LABEL + // 区域都会在该论迭代中发现新的凸壳点。该值要求非常的大,因为没有发现新凸壳 + // 点的区域,相当于所有的坐标点放在左侧。 +#define LP_DUMMY_CVXX_CPU CH_LARGE_ENOUGH + + for (int id = 0; id < cstcnt; id++) { + // 计算新凸壳点在新的凸壳点集中的下标。 + newconvexidx = label[id] + foundacc[label[id]] + 1; + + // 初始化新凸壳点 x 坐标,对于没有产生新的凸壳点的 LABEL 区域来 + // 说,该值直接赋哑值。 + if (foundflag[label[id]]) + newconvexcstx = + (*newconvexcstCud).tplMeta.tplData[2 * newconvexidx]; + else + newconvexcstx = LP_DUMMY_CVXX_CPU; + + // 对于所有垂距大于等于 0,且 x 坐标小于中心点坐标时认为该点在中心点左侧。 + // (因为所有垂距小于 0 的点将在下一轮迭代中被排除,因此,这里没有将垂距小 + // 于 0 的点设置左侧标志位) + if ((*cstCud).tplMeta.tplData[2 * id] < newconvexcstx && + negdistflag[id] == 0) + leftflag[id] = 1; + else + leftflag[id] = 0; + +#ifdef CH_DEBUG_CPU_PRINT + printf("[markLeftPointsCpu]: (%3d, %3d) d=%8.3f, " + "Label %2d ( NC.x %3d ) Left %1d\n", + (*cstCud).tplMeta.tplData[2 * id], + (*cstCud).tplMeta.tplData[2 * id + 1], + (*cstCud).attachedData[id], label[id], + newconvexidx, leftflag[id]); +#endif + } + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _updatePropertyKer(计算新下标) +static __global__ void _updatePropertyKer( + int leftflag[], int leftacc[], int negdistflag[], int negdistacc[], + int startidx[], int label[], int foundacc[], int cstcnt, + int newidx[], int tmplabel[]) +{ + // 记录了本 Kernel 所使用到的共享内存中各个下标所存储的数据的含义。其中, + // SIDX_BLK_LABEL_LOW 和 SIDX_BLK_LABEL_UP 用来存当前 Block 中所加载的点集 + // 的区域标签值的上下界。根据这个上下界,可以计算出当前点所在区域的最左最右 + // 点,从而根据这两点确定的直线计算当前点的垂距。 + // SIDX_BLK_START_IDX 表示当前 Block 所囊括的所有 LABEL 区域对应的起始下 + // 标。 + // SIDX_BLK_NEG_ACC 表示当前 Block 所囊括的所有 LABEL 区域对应的负垂距累加 + // 值,该值用于计算坐标点在下一轮迭代中所对应的新下标值。 + // SIDX_BLK_LEFT_ACC 表示当前 Block 所囊括的所有 LABEL 区域对应的左侧点累加 + // 值,该值用于计算坐标点在下一轮迭代中所对应的新下标值。 + // SIDX_BLK_NEG_ACC 表示当前 Block 所囊括的所有 LABEL 区域对应的新发现凸壳 + // 点的累加值。该值用来计算本轮结束后目前所有找到的凸壳点在新凸壳点集中的下 + // 标值。 +#define SIDX_BLK_LABEL_LOW 0 +#define SIDX_BLK_LABEL_UP 1 +#define SIDX_BLK_START_IDX (2 + 0 * blockDim.x) +#define SIDX_BLK_NEG_ACC (2 + 1 * blockDim.x) +#define SIDX_BLK_LEFT_ACC (2 + 2 * blockDim.x) +#define SIDX_BLK_FOUND_ACC (2 + 3 * blockDim.x) + + // 共享内存的声明。 + extern __shared__ int shdmem[]; + + // 基准下标。当前 Block 所有线程的起始全局下标。 + int baseidx = blockIdx.x * blockDim.x; + + // 当前 Thread 的全局下标 + int idx = baseidx + threadIdx.x; + + // 初始化 Shared Memory 公用部分。只需要一个线程来做这件事情即可。 + if (threadIdx.x == 0) { + // 计算当前 Block 处理的最小的 LABEL 区域。 + shdmem[SIDX_BLK_LABEL_LOW] = label[baseidx]; + + // 计算当前 Block 处理的最大的 LABEL 区域。这里针对最后一个 Block,需要 + // 考虑越界读取数据的情况。 + if (baseidx + blockDim.x < cstcnt) + shdmem[SIDX_BLK_LABEL_UP] = label[baseidx + blockDim.x - 1] + 1; + else + shdmem[SIDX_BLK_LABEL_UP] = label[cstcnt - 1]; + } + + // 针对上面的初始化进行同步,使其结果对所有 Thread 可见。 + __syncthreads(); + + // 将共享内存的各个数组指针取出。这一步骤没有逻辑上的实际意义,只是为了后续 + // 步骤表达方便。 + int *startidxShd = &shdmem[SIDX_BLK_START_IDX]; + int *negdistaccShd = &shdmem[SIDX_BLK_NEG_ACC]; + int *leftaccShd = &shdmem[SIDX_BLK_LEFT_ACC]; + int *foundaccShd = &shdmem[SIDX_BLK_FOUND_ACC]; + + // 将存放于 Shared Memory 中的 LABEL 区域上下界转存到寄存器中。该步骤也没有 + // 实际的逻辑意义,目的在于使程序运行更加高效。 + int labellower = shdmem[SIDX_BLK_LABEL_LOW]; + int labelupper = shdmem[SIDX_BLK_LABEL_UP]; + + // 初始化 Shared Memory 中的各个数组的值。 + if (threadIdx.x < labelupper - labellower + 1) { + // 从 Global Memory 中读取各个 LABEL 的起始下标。 + startidxShd[threadIdx.x] = startidx[threadIdx.x + labellower]; + + // 根据起始下标,计算各个 LABEL 区域所对应的负垂距和左侧点累加值。 + negdistaccShd[threadIdx.x] = negdistacc[startidxShd[threadIdx.x]]; + leftaccShd[threadIdx.x] = leftacc[startidxShd[threadIdx.x]]; + + // 从 Global Memory 中读取新凸壳点的累加值。 + foundaccShd[threadIdx.x] = foundacc[threadIdx.x + labellower]; + } + + // 针对上面的初始化进行 Block 内部的同步,是的这些初始化结果对所有的 + // Thread 可见。 + __syncthreads(); + + // 若当前 Thread 处理的是越界数据,则直接退出。 + if (idx >= cstcnt) + return; + + // 若当前 Thread 处理的坐标点具有负垂距,则直接退出,因为负垂距坐标点再下一 + // 轮迭代的过程中则不再使用。 + if (negdistflag[idx] == 1) + return; + + // 读取当前坐标点的 LABEL 值,由于后面只使用 Shared Memory 中的数据,因此这 + // 里直接将其转换为 Shared Memory 所对应的下标。 + int curlabel = label[idx] - labellower; + + // 宏:CHNI_ENABLE_FAST_CALC(下标值快速计算开关) + // 在下面的代码中新下标值的计算可分为快速和普通两种方式。如果开启该定义,则 + // 使用快速下标值计算;如果关闭,则是用普通下标值计算。无论是快速计算还是普 + // 通计算,两者在计算公式上归根结底是一样的,只是为了减少计算量,某些变量被 + // 合并同类项后消掉了。因此快速下标值计算的公式不易理解,普通计算的公式易于 + // 理解。快速计算仅仅是普通计算的推导结果。 +#define CHNI_ENABLE_FAST_CALC + + // 针对当前在新发现的凸壳点的左侧还有右侧,需要进行不同的计算公式来确定其在 + // 下一轮迭代中的 LABEL 值和在坐标点集中的下标值。 + if (leftflag[idx] == 1) { + // 对于当前点在新的凸壳点的左侧,计算新的 LABEL 值。这里 foundacc 的物 + // 理含义是当前 LABEL 值之前的各个 LABEL 区域中总共找到的新增凸壳点的数 + // 量。 + tmplabel[idx] = label[idx] + foundaccShd[curlabel]; + + // 对于当前点在新的凸壳点的左侧,计算坐标点在新一轮迭代中的下标值。这里 + // 首先确定当前 LABEL 的新的起始下标值,然后再加上该点在其 LABEL 区域内 + // 其前面的左侧点的数量,就得到了其新的下标值。 +#ifndef CHNI_ENABLE_FAST_CALC + int basenewidx = startidxShd[curlabel] - negdistaccShd[curlabel]; + int innernewidx = leftacc[idx] - leftaccShd[curlabel]; + newidx[idx] = basenewidx + innernewidx; +#else + newidx[idx] = startidxShd[curlabel] - negdistaccShd[curlabel] + + leftacc[idx] - leftaccShd[curlabel]; +#endif + } else { + // 对于当前点在新的凸壳点的右侧,计算新的 LABEL 值。这里 foundacc 的物 + // 理含义是当前 LABEL 值之前的各个 LABEL 区域中总共找到的新增凸壳点的数 + // 量。 + tmplabel[idx] = label[idx] + foundaccShd[curlabel] + 1; + + // 对于当前点在新的凸壳点的右侧,计算坐标点在新一轮迭代中的下标值。计算 + // 该值,首先计算右侧构成的新的 LABEL 区域的起始位置,这部分只需要在左 + // 侧起始下标处加上当前 LABEL 区域总共检出的左侧坐标的数量即可;之后, + // 需要计算该坐标点在新的区域内部的偏移量,即内部的原来下标值,减去其前 + // 面的负垂距点数量和左侧点数量。 +#ifndef CHNI_ENABLE_FAST_CALC + int leftcnt = leftaccShd[curlabel + 1] - leftaccShd[curlabel]; + int basenewidx = startidxShd[curlabel] - negdistaccShd[curlabel] + + leftcnt; + int inidx = idx - startidxShd[curlabel]; + int innegacc = negdistacc[idx] - negdistaccShd[curlabel]; + int inleftacc = leftacc[idx] - leftaccShd[curlabel]; + int innernewidx = inidx - innegacc - inleftacc; + newidx[idx] = basenewidx + innernewidx; +#else + newidx[idx] = idx - negdistacc[idx] + + leftaccShd[curlabel + 1] - leftacc[idx]; +#endif + } + + // 调试打印 +#ifdef CH_DEBUG_KERNEL_PRINT + printf("Kernel[NewLabel]: Label %2d => %2d, " + "Idx %2d => %2d, Left %1d\n", + label[idx], tmplabel[idx], idx, newidx[idx], leftflag[idx]); +#endif + + // 消除本 Kernel 函数内部的宏定义,防止后面的函数使用造成冲突。 +#ifdef CHNI_ENABLE_FAST_CALC +# undef CHNI_ENABLE_FAST_CALC +#endif + +#undef SIDX_BLK_LABEL_LOW +#undef SIDX_BLK_LABEL_UP +#undef SIDX_BLK_START_IDX +#undef SIDX_BLK_NEG_ACC +#undef SIDX_BLK_LEFT_ACC +#undef SIDX_BLK_FOUND_ACC +} + +// 成员方法:updateProperty(计算新下标) +__host__ int ConvexHull::updateProperty( + int leftflag[], int leftacc[], int negdistflag[], int negdistacc[], + int startidx[], int label[], int foundacc[], int cstcnt, + int newidx[], int newlabel[]) +{ + // 检查所有参数中的指针和数组,是否为 NULL。 + if (leftflag == NULL || leftacc == NULL || negdistflag == NULL || + negdistacc == NULL || startidx == NULL || label == NULL || + foundacc == NULL || newidx == NULL || newlabel == NULL) + return NULL_POINTER; + + // 如果坐标点集的数量小于等于 0,则报错退出。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 计算调用 Kernel 函数所需要的 Grid 和 Block 尺寸,以及每个 Block 所使用的 + // Shared Memory 的字节数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (2 + blocksize * 4) * sizeof (int); + + // 调用 Kernel 函数, 完成计算。 + _updatePropertyKer<<>>( + leftflag, leftacc, negdistflag, negdistacc, startidx, + label, foundacc, cstcnt, newidx, newlabel); + + // 判断 Kernel 函数的执行是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 成员方法:updatePropertyCpu(计算新下标) +__host__ int ConvexHull::updatePropertyCpu( + int leftflag[], int leftacc[], int negdistflag[], int negdistacc[], + int startidx[], int label[], int foundacc[], int cstcnt, + int newidx[], int newlabel[]) +{ + // 检查所有参数中的指针和数组,是否为 NULL。 + if (leftflag == NULL || leftacc == NULL || negdistflag == NULL || + negdistacc == NULL || startidx == NULL || label == NULL || + foundacc == NULL || newidx == NULL || newlabel == NULL) + return NULL_POINTER; + + // 如果坐标点集的数量小于等于 0,则报错退出。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 宏:CHNI_ENABLE_FAST_CALC(下标值快速计算开关) + // 在下面的代码中新下标值的计算可分为快速和普通两种方式。如果开启该定义,则 + // 使用快速下标值计算;如果关闭,则是用普通下标值计算。无论是快速计算还是普 + // 通计算,两者在计算公式上归根结底是一样的,只是为了减少计算量,某些变量被 + // 合并同类项后消掉了。因此快速下标值计算的公式不易理解,普通计算的公式易于 + // 理解。快速计算仅仅是普通计算的推导结果。 +#define CHNI_ENABLE_FAST_CALC + + for (int idx = 0; idx < cstcnt; idx++) { + if (negdistflag[idx] == 0) { + // 针对当前在新发现的凸壳点的左侧还有右侧,需要进行不同的计算公式来 + // 确定其在下一轮迭代中的 LABEL 值和在坐标点集中的下标值。 + if (leftflag[idx] == 1) { + // 对于当前点在新的凸壳点的左侧,计算新的 LABEL 值。这里 + // foundacc 的物理含义是当前 LABEL 值之前的各个 LABEL 区域中总 + // 共找到的新增凸壳点的数量。 + newlabel[idx] = label[idx] + foundacc[label[idx]]; + + // 对于当前点在新的凸壳点的左侧,计算坐标点在新一轮迭代中的下标 + // 值。这里首先确定当前 LABEL 的新的起始下标值,然后再加上该点在 + // 其 LABEL 区域内其前面的左侧点的数量,就得到了其新的下标值。 +#ifndef CHNI_ENABLE_FAST_CALC + int basenewidx = startidx[label[idx]] - + negdistacc[startidx[label[idx]]]; + int innernewidx = leftacc[idx] - leftacc[startidx[label[idx]]]; + newidx[idx] = basenewidx + innernewidx; +#else + newidx[idx] = startidx[label[idx]] - + negdistacc[startidx[label[idx]]] + leftacc[idx] - + leftacc[startidx[label[idx]]]; +#endif + } else { + // 对于当前点在新的凸壳点的右侧,计算新的 LABEL 值。这里 + // foundacc 的物理含义是当前 LABEL 值之前的各个 LABEL 区域中总共 + // 找到的新增凸壳点的数量。 + newlabel[idx] = label[idx] + foundacc[label[idx]] + 1; + + // 对于当前点在新的凸壳点的右侧,计算坐标点在新一轮迭代中的下标 + // 值。计算该值,首先计算右侧构成的新的 LABEL 区域的起始位置,这 + // 部分只需要在左侧起始下标处加上当前 LABEL 区域总共检出的左侧坐 + // 标的数量即可;之后,需要计算该坐标点在新的区域内部的偏移量, + // 即内部的原来下标值,减去其前 面的负垂距点数量和左侧点数量。 +#ifndef CHNI_ENABLE_FAST_CALC + int leftcnt = leftacc[startidx[label[idx] + 1]] - + leftacc[startidx[label[idx]]]; + int basenewidx = startidx[label[idx]] - + negdistacc[startidx[label[idx]]] + + leftcnt; + int inidx = idx - startidx[label[idx]]; + int innegacc = negdistacc[idx] - + negdistacc[startidx[label[idx]]]; + int inleftacc = leftacc[idx] - leftacc[startidx[label[idx]]]; + int innernewidx = inidx - innegacc - inleftacc; + newidx[idx] = basenewidx + innernewidx; +#else + newidx[idx] = idx - negdistacc[idx] + + leftacc[startidx[label[idx] + 1]] - leftacc[idx]; +#endif + } + + } + } + +// 消除本 Kernel 函数内部的宏定义,防止后面的函数使用造成冲突。 +#ifdef CHNI_ENABLE_FAST_CALC +# undef CHNI_ENABLE_FAST_CALC +#endif + + return NO_ERROR; +} + +// Kernel 函数: _arrangeCstKer(生成新坐标点集) +static __global__ void _arrangeCstKer( + CoordiSetCuda cst, int negdistflag[], int newidx[], int tmplabel[], + int cstcnt, CoordiSetCuda newcst, int newlabel[]) +{ + // 计算当前 Thread 的全局索引。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 若当前 Thread 处理的是越界数据,则直接返回 + if (idx >= cstcnt) + return; + + // 如果当前 Thread 对应的坐标点是应该在下一轮计算中被排除的负垂距点,那么 + // 该 Thread 直接退出。 + if (negdistflag[idx] == 1) + return; + + // 读取当前线程所处理的 + int newindex = newidx[idx]; + + // 将坐标集按照新的位置拷贝到新的坐标集中。由于并行拷贝,无法保证顺序,因此 + // 这里使用了两个数组。 + newcst.tplMeta.tplData[2 * newindex] = cst.tplMeta.tplData[2 * idx]; + newcst.tplMeta.tplData[2 * newindex + 1] = + cst.tplMeta.tplData[2 * idx + 1]; + + // 将新的 LABEL 标记从原来的下标处拷贝到新的下标处,由于并行拷贝,无法保证 + // 顺序,因此这里使用了两个数组。 + newlabel[newindex] = tmplabel[idx]; +} + +// 成员方法:arrangeCst(生成新坐标点集) +__host__ int ConvexHull::arrangeCst( + CoordiSet *cst, int negdistflag[], int newidx[], int tmplabel[], + int cstcnt, CoordiSet *newcst, int newlabel[]) +{ + // 检查参数中所有的指针和数组是否为空。 + if (cst == NULL || newcst == NULL || negdistflag == NULL || + tmplabel == NULL || newidx == NULL || newlabel == NULL) + return NULL_POINTER; + + // 检查坐标点数量,必须要大于 0。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将输入坐标集拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + // 将输出坐标集拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(newcst); + if (errcode != NO_ERROR) + return errcode; + + // 坐标集的 CUDA 相关数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *newcstCud = COORDISET_CUDA(newcst); + + // 计算调用 Kernel 函数 Block 尺寸和 Block 数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + + // 调用 Kernel 函数,完成计算。 + _arrangeCstKer<<>>( + *cstCud, negdistflag, newidx, tmplabel, cstcnt, + *newcstCud, newlabel); + + // 判断 Kernel 函数执行是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 成员方法:arrangeCstCpu(生成新坐标点集) +__host__ int ConvexHull::arrangeCstCpu( + CoordiSet *cst, int negdistflag[], int newidx[], int tmplabel[], + int cstcnt, CoordiSet *newcst, int newlabel[]) +{ + // 检查参数中所有的指针和数组是否为空。 + if (cst == NULL || newcst == NULL || negdistflag == NULL || + tmplabel == NULL || newidx == NULL || newlabel == NULL) + return NULL_POINTER; + + // 检查坐标点数量,必须要大于 0。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将输入坐标集拷贝到当前 Host。 + errcode = CoordiSetBasicOp::copyToHost(cst); + if (errcode != NO_ERROR) + return errcode; + + // 将输出坐标集拷贝到当前 Host。 + errcode = CoordiSetBasicOp::copyToHost(newcst); + if (errcode != NO_ERROR) + return errcode; + + // 坐标集的 CUDA 相关数据 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *newcstCud = COORDISET_CUDA(newcst); + + for (int idx = 0; idx < cstcnt; idx++) { + if (negdistflag[idx] == 0) { + int newindex = newidx[idx]; + + // 将坐标集按照新的位置拷贝到新的坐标集中。由于并行拷贝,无法保证顺 + // 序,因此这里使用了两个数组。 + (*newcstCud).tplMeta.tplData[2 * newindex] = + (*cstCud).tplMeta.tplData[2 * idx]; + (*newcstCud).tplMeta.tplData[2 * newindex + 1] = + (*cstCud).tplMeta.tplData[2 * idx + 1]; + + // 将新的 LABEL 标记从原来的下标处拷贝到新的下标处,由于并行拷贝, + // 无法保证顺序,因此这里使用了两个数组。 + newlabel[newindex] = tmplabel[idx]; + } + } + return NO_ERROR; +} + +// Kernel 函数:_flipWholeCstKer(整体翻转坐标点集) +static __global__ void _flipWholeCstKer( + CoordiSetCuda incst, CoordiSetCuda outcst) +{ + // 计算当前 Thread 的全局下标 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前 Thread 处理的是越界数据,则直接退出。 + if (idx >= incst.tplMeta.count) + return; + + // 将 x 和 y 坐标的相反数赋值给输出坐标点集。 + outcst.tplMeta.tplData[2 * idx] = + -incst.tplMeta.tplData[2 * idx]; + outcst.tplMeta.tplData[2 * idx + 1] = + -incst.tplMeta.tplData[2 * idx + 1]; +} + +// Host 成员方法:flipWholeCstCpu(整体翻转坐标点集) +__host__ int ConvexHull::flipWholeCstCpu(CoordiSet *incst, CoordiSet *outcst) +{ + // 检查输入坐标点集是否为 NULL。 + if (incst == NULL) + return NULL_POINTER; + + // 检查输入坐标点集是否包含有效的坐标点。 + if (incst->count <= 0 || incst->tplData == NULL) + return INVALID_DATA; + + // 如果输出点集为 NULL,则函数会进行 In-Place 操作,即将输出点集赋值为输入 + // 点集。 + if (outcst == NULL) + outcst = incst; + + // 声明局部变量,错误码。 + int errcode; + + // 将输入坐标点集拷贝到当前 Host 中。 + errcode = CoordiSetBasicOp::copyToHost(incst); + if (errcode != NO_ERROR) + return errcode; + + // 对于 Out-Place 方法还需要对输出坐标点集进行初始化操作。 + if (incst != outcst) { + // 将输出坐标集拷贝入 Host 内存。 + errcode = CoordiSetBasicOp::copyToHost(outcst); + if (errcode != NO_ERROR) { + // 如果输出坐标集无数据(故上面的拷贝函数会失败),则会创建一个和 + // 输入坐标集寸相同的图像。 + errcode = CoordiSetBasicOp::makeAtHost( + outcst, incst->count); + // 如果创建坐标集也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + } + + // 取出两个坐标点集对应的 CUDA 型变量。 + CoordiSetCuda *incstCud = COORDISET_CUDA(incst); + CoordiSetCuda *outcstCud = COORDISET_CUDA(outcst); + + // 为了防止越界访存,这里临时将输入点集的尺寸切换为输入和输出点集中较小的那 + // 个。当然,在操作后还需要将点集的数量恢复,因此,通过另一个变量保存原始的 + // 坐标点数量。 + int incstcntorg = incst->count; + if (incst->count > outcst->count) + incst->count = outcst->count; + + for (int idx = 0; idx < (*incstCud).tplMeta.count; idx++) { + // 将 x 和 y 坐标的相反数赋值给输出坐标点集。 + (*outcstCud).tplMeta.tplData[2 * idx] = + -(*incstCud).tplMeta.tplData[2 * idx]; + (*outcstCud).tplMeta.tplData[2 * idx + 1] = + -(*incstCud).tplMeta.tplData[2 * idx + 1]; + } + + // 回复输入坐标点集中坐标点的数量。 + incst->count = incstcntorg; + + // 处理完毕退出。 + return NO_ERROR; +} + + +// Host 成员方法:flipWholeCst(整体翻转坐标点集) +__host__ int ConvexHull::flipWholeCst(CoordiSet *incst, CoordiSet *outcst) +{ + // 检查输入坐标点集是否为 NULL。 + if (incst == NULL) + return NULL_POINTER; + + // 检查输入坐标点集是否包含有效的坐标点。 + if (incst->count <= 0 || incst->tplData == NULL) + return INVALID_DATA; + + // 如果输出点集为 NULL,则函数会进行 In-Place 操作,即将输出点集赋值为输入 + // 点集。 + if (outcst == NULL) + outcst = incst; + + // 声明局部变量,错误码。 + int errcode; + + // 将输入坐标点集拷贝到当前 Device 中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(incst); + if (errcode != NO_ERROR) + return errcode; + + // 对于 Out-Place 方法还需要对输出坐标点集进行初始化操作。 + if (incst != outcst) { + // 将输出坐标集拷贝入 Device 内存。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(outcst); + if (errcode != NO_ERROR) { + // 如果输出坐标集无数据(故上面的拷贝函数会失败),则会创建一个和 + // 输入坐标集寸相同的图像。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice( + outcst, incst->count); + // 如果创建坐标集也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + } + + // 取出两个坐标点集对应的 CUDA 型变量。 + CoordiSetCuda *incstCud = COORDISET_CUDA(incst); + CoordiSetCuda *outcstCud = COORDISET_CUDA(outcst); + + // 为了防止越界访存,这里临时将输入点集的尺寸切换为输入和输出点集中较小的那 + // 个。当然,在操作后还需要将点集的数量恢复,因此,通过另一个变量保存原始的 + // 坐标点数量。 + int incstcntorg = incst->count; + if (incst->count > outcst->count) + incst->count = outcst->count; + + // 计算启动 Kernel 函数所需要的 Thread 数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (incst->count + blocksize - 1) / blocksize; + + // 启动 Kernel 函数完成计算。 + _flipWholeCstKer<<>>(*incstCud, *outcstCud); + + // 回复输入坐标点集中坐标点的数量。 + incst->count = incstcntorg; + + // 检查 Kernel 函数是否执行正确。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 宏:FAIL_CONVEXHULL_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_CONVEXHULL_FREE do { \ + if (tmpmem != NULL) \ + delete tmpmem; \ + if (tmpcstin != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpcstin); \ + if (tmpcstout != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpcstout); \ + if (tmpconvexin != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpconvexin); \ + if (tmpconvexout != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpconvexout); \ + } while (0) + +// 成员方法:convexHullIter(迭代法求凸壳上的点集) +__host__ int ConvexHull::convexHullIterCpu( + CoordiSet *inputcst, CoordiSet *convexcst, bool lowerconvex) +{ + // 检查输入坐标集,输出坐标集是否为空。 + if (inputcst == NULL || convexcst == NULL) + return NULL_POINTER; + + // 如果输入点集中不含有任何的坐标点,则直接退出。 + if (inputcst->count < 1 || inputcst->tplData == NULL) + return INVALID_DATA; + + // 如果输入点集中点的数量少于 2 个点时,则不需要任何求解过程,直接将输入点 + // 集拷贝到输出点集即可。虽然当坐标点集中仅包含两个点时也可以直接判定为凸壳 + // 点,但考虑到顺序问题,代码还是让仅有两个点的情况走完整个流程。 + if (inputcst->count < 2) + return CoordiSetBasicOp::copyToHost(inputcst, convexcst); + + // 局部变量 + int errcode; + + // 定义扫描所用的二元操作符。 + add_class add; + + // 采用 CPU scan + this->aryScan.setScanType(CPU_IN_SCAN); + + int cstcnt = inputcst->count; // 坐标点集中点的数量。这里之所以将其使用另 + // 外一个变量保存出来是因为这个值随着迭代会 + // 变化,如果直接使用 CoordiSet 中的 count + // 域会带来内存管理上的不便。 + int convexcnt = 2; // 当前凸壳点的数量,由于迭代开始时,已经实 + // 现找到了点集中的最左和最有两点作为凸壳 + // 点,因此这里直接赋值为 2。 + int foundcnt; // 当前迭代时找到的新凸壳点的数量,这一数量 + // 并不包含往次所找到的凸壳点。 + int negdistcnt; // 当前负垂距点的数量。 + //int itercnt = 0; // 迭代次数记录器。 + + int *tmpmem = NULL; // 存放中间变量的内存空间。 + CoordiSet *tmpcstin = NULL; // 每次迭代中作为输入坐标点集的临时坐标点 + // 集。 + CoordiSet *tmpcstout = NULL; // 每次迭代中作为输出坐标点击的临时坐标点 + // 集。 + CoordiSet *tmpconvexin = NULL; // 每次迭代中作为输入凸壳点集的临时坐标点 + // 集。 + CoordiSet *tmpconvexout = NULL; // 每次迭代中作为输出凸壳点集(新凸壳点 + // 集)的临时坐标点集。 + + size_t datacnt = 0; // 所需要的数据元素的数量。 + size_t datasize = 0; // 书需要的数据元素的字节尺寸。 + + // 宏:CHI_DATA_DECLARE(中间变量声明器) + // 为了消除中间变量声明过程中大量的重复代码,这里提供了一个宏,使代码看起来 + // 整洁一些。 +#define CHI_DATA_DECLARE(dataname, type, count) \ + type *dataname = NULL; \ + size_t dataname##cnt = (count); \ + datacnt += dataname##cnt; \ + datasize += dataname##cnt * sizeof (type) + + // 声明各个中间变量的 Device 数组。 + CHI_DATA_DECLARE(label, int, // 记录当前迭代中每个像素点所在的 + inputcst->count); // LABEL 区域。 + CHI_DATA_DECLARE(negdistflag, int, // 记录当前迭代中每个像素点是否具有 + inputcst->count); // 负垂距。 + CHI_DATA_DECLARE(negdistacc, int, // 记录当前迭代中具有负垂距点的累加 + inputcst->count + 1); // 和,其物理含义是在当前点之前存在 + // 多少个负垂距点,其最后一个元素表 + // 示当前迭代共找到了多少个负垂距 + // 点。 + CHI_DATA_DECLARE(maxdistidx, int, // 记录当前迭代中每个坐标点前面的所 + inputcst->count); // 有点中和其在同一个 LABEL 区域的 + // 所有点中具有最大垂距的下标。 + CHI_DATA_DECLARE(foundflag, int, // 记录当前迭代中各个 LABEL 区域是 + inputcst->count); // 否找到了新的凸壳点。 + CHI_DATA_DECLARE(foundacc, int, // 记录当前迭代中每个 LABEL 区域其 + inputcst->count + 1); // 前面的所有 LABEL 区域共找到的新 + // 的凸壳点的数量。该值用于计算各个 + // 凸壳点(无论是旧的还是新的)在新 + // 的凸壳点集中的新下标。 + CHI_DATA_DECLARE(leftflag, int, // 记录当前的坐标点是否处于新发现的 + inputcst->count); // 坐标点的左侧 + CHI_DATA_DECLARE(leftacc, int, // 记录当前的坐标点之前的左侧点的数 + inputcst->count + 1); // 量。该数组用于计算坐标点在下一轮 + // 计算中的下标。 + CHI_DATA_DECLARE(startidx, int, // 记录每个 LABEL 区域在坐标点集中 + inputcst->count); // 的起始下标 + CHI_DATA_DECLARE(newidx, int, // 记录当前坐标点集中各个坐标点在下 + inputcst->count); // 一轮迭代中的新的下标。 + CHI_DATA_DECLARE(tmplabel, int, + inputcst->count); + CHI_DATA_DECLARE(newlabel, int, // 记录当前坐标点集中各个坐标点在下 + inputcst->count); // 一轮迭代中新的 LABEL 区域。 + + // 消除中间变量声明器这个宏,防止后续步骤的命名冲突。 +#undef CHI_DATA_DECLARE + + // 中间变量申请 Host 内存空间,并将这些空间分配给各个中间变量。 + tmpmem = new int[datasize]; + + // 为各个中间变量分配内存空间,采用这种一次申请一个大空间的做法是为了减少申 + // 请内存的开销,同时也减少因内存对齐导致的内存浪费。 + label = tmpmem; + negdistflag = label + labelcnt; + negdistacc = negdistflag + negdistflagcnt; + maxdistidx = negdistacc + negdistacccnt; + foundflag = maxdistidx + maxdistidxcnt; + foundacc = foundflag + foundflagcnt; + leftflag = foundacc + foundacccnt; + leftacc = leftflag + leftflagcnt; + startidx = leftacc + leftacccnt; + newidx = startidx + startidxcnt; + newlabel = newidx + newidxcnt; + tmplabel = newlabel + newlabelcnt; + + // 宏:CHI_USE_SYS_FUNC + // 该开关宏用于指示是否在后续步骤中尽量使用 CUDA 提供的函数,而不是启动由开 + // 发方自行编写的 Kernel 函数完成操作。 +//#define CHI_USE_SYS_FUNC + + // 初始化 LABEL 数组。 +#ifdef CHI_USE_SYS_FUNC + // 首先将 LABEL 数组中所有内存元素全部置零。 + memset(label, 0, labelcnt * sizeof (int)); + + // 将 LABEL 数组中最后一个元素置 1。 + label[cstcnt - 1] = 1; +#else + // 调用 LABEL 初始化函数,完成 LABEL 初始化。初始化后,除最后一个元素为 1 + // 外,其余元素皆为 0。 + errcode = this->initLabelAryCpu(label, cstcnt); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#endif + + // 初始化迭代过程中使用的坐标点集,这里一共需要使用到两个坐标点集,为了不破 + // 坏输入坐标点集,这里在迭代过程中我们使用内部申请的坐标点集。 + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]init CoordiSet" << endl; +#endif + // 初始化第一个坐标点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcstin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 将输入坐标点集中的数据从输入点集中拷贝到第一个坐标点集中。此后所有的操作 + // 仅在临时坐标点集中处理,不再碰触输入坐标点集。这里如果是求解上半凸壳,则 + // 直接调用翻转坐标点的函数。 + if (lowerconvex) + errcode = CoordiSetBasicOp::copyToHost(inputcst, tmpcstin); + else + errcode = this->flipWholeCst(inputcst, tmpcstin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 初始化第二个坐标点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcstout); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 在 Device 内存中初始化第二个坐标点集,为其申请足够长度的内存空间。 + errcode = CoordiSetBasicOp::makeAtHost(tmpcstout, inputcst->count); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 初始化迭代过程中使用到的凸壳点集,这里一共需要两个凸壳点集。我们不急于更 + // 新输出参数 convexcst,是因为避免不必要的麻烦,等到凸壳计算完毕后,再将凸 + // 壳内容拷贝到输出参数中。 + + // 初始化第一个凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpconvexin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 在 Device 内存中初始化第一个凸壳点集,为其申请足够长度的内存空间。 + errcode = CoordiSetBasicOp::makeAtHost(tmpconvexin, + inputcst->count); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 初始化第二个凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpconvexout); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 在 Device 内存中初始化第二个凸壳点集,为其申请足够长度的内存空间。 + errcode = CoordiSetBasicOp::makeAtHost(tmpconvexout, + inputcst->count); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]init CoordiSet finish" << endl; +#endif + + // 寻找最左最右点,并利用这两个点初始化输入点集和凸壳点集。初始化后,输入点 + // 集的第一个点为最左点,最后一个点为最右点;凸壳点集中仅包含最左最右两个 + // 点。 + errcode = swapEdgePointCpu(tmpcstin, tmpconvexin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]swap finish" << endl; + cout << "[convexHullIterCpu]interation begin" << endl; +#endif + // 所有的初始化过程至此全部完毕,开始进行迭代。每次迭代都需要重新计算坐标点 + // 在其 LABEL 区域内的垂距,然后根据垂距信息判断每个 LABEL 区域内是否存在新 + // 的凸壳点(如果有需要确定是哪一个点),之后根据这个新发现的凸壳点,计算所 + // 有坐标点在下一轮迭代中的下标。计算后的下标要求属于一个 LABEL 的点都在一 + // 起,并且排除所有具有负垂距的点,因为这些点在下一轮迭代中已经毫无意义。迭 + // 代的过程知道无法在从当前所有的 LABEL 区域内找到新的凸壳点为止。此处循环 + // 的判断条件只是一个防护性措施,若坐标点集的数量同凸壳点相等,那就说明没有 + // 任何可能在找到新的凸壳点了。 + while (cstcnt >= convexcnt) { +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]new: convexcnt is " << convexcnt << endl; + cout << endl; + cout << "[convexHullIterCpu]updatedist begin" << endl; + cout << "[convexHullIterCpu]cstcnt is " << cstcnt << endl; +#endif + // 调用更新垂距函数。更新点集中每个点的垂距值和负垂距标志数组。 + errcode = this->updateDistCpu(tmpcstin, tmpconvexin, label, + cstcnt, negdistflag); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + +#ifdef CH_DEBUG_CPU_PRINT + cout << endl; + cout << "[convexHullIterCpu]segscan begin" << endl; +#endif + // 利用分段扫描得到各个 LABEL 区域的最大垂距,记忆最大垂距坐标点的下标 + // 值。 + errcode = this->segScan.segmentedScanCpu( + ATTACHED_DATA(tmpcstin), label, + ATTACHED_DATA(tmpcstout), maxdistidx, cstcnt, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]segscan end" << endl; + cout << "[convexHullIterCpu]updateFoundInfoCpu begin" << endl; +#endif + // 根据所求出来的垂距信息判断各个 LABEL 区域是否有新的凸壳点存在。 + errcode = this->updateFoundInfoCpu( + label, ATTACHED_DATA(tmpcstin), maxdistidx, + cstcnt, foundflag, startidx); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]updateFoundInfoCpu end" << endl; + cout << "[convexHullIterCpu]scan foudnd begin" << endl; +#endif + // 通过扫描,计算出 LABEL 区域新发现凸壳点标记值对应的累加值。 + errcode = this->aryScan.scanArrayExclusive(foundflag, foundacc, + convexcnt, add, + false, true, true); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]scan found end" << endl; +#endif + // 将新凸壳点标记累加值的最后一个拷贝到 Host 内存中,这个累加值的含义是 + // 当前迭代下所有新发现的凸壳点的数量。 + foundcnt = foundacc[convexcnt]; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]foundcnt now is " << foundcnt << endl; +#endif + + // 如果新发现的凸壳点的数量小于等于 0,则说明说有的凸壳点都已经被找到, + // 没有必要在继续做下去了,因此退出迭代。 + if (foundcnt <= 0) + break; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]updateConvexCstCpu begin" << endl; +#endif + // 更新凸壳点集,将新发现的凸壳点集更新到凸壳点集中。 + errcode = this->updateConvexCstCpu( + tmpcstin, tmpconvexin, foundflag, foundacc, startidx, + maxdistidx, convexcnt, tmpconvexout); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]updateConvexCstCpu end" << endl; +#endif + // 更新凸壳点集中点的数量。 + convexcnt += foundcnt; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]convexcnt now is " << convexcnt << endl; + cout << "[convexHullIterCpu]markLeftPointsCpu begin" << endl; +#endif + // 标记左侧点。所谓左侧点是在某 LABEL 区域内处于新发现的凸壳点左侧的 + // 点。 + errcode = this->markLeftPointsCpu( + tmpcstin, tmpconvexout, negdistflag, label, + foundflag, foundacc, cstcnt, leftflag); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]markLeftPointsCpu end" << endl; + cout << "[convexHullIterCpu]scanArrayExclusive neg begin" << endl; +#endif + // 通过扫描,计算出负垂距点标记数组对应的累加数组。negdistflagDev 实在 + // 第一步更新垂距的时候获得的,之所以这么晚才计算其对应的累加数组,是因 + // 为在前面检查 foundcnt 退出循环之前不需要这个数据,这样,如果真的在该 + // 处退出,则程序进行了多余的计算,为了避免这一多余计算,我们延后计算 + // negdistaccDev 至此处。 + errcode = this->aryScan.scanArrayExclusive( + negdistflag, negdistacc, cstcnt, add, + false, true, true); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]scanArrayExclusive neg end" << endl; +#endif + + // 将负垂距点累加总和拷贝出来,用来更新下一轮循环的坐标点数量值。 + negdistcnt = negdistacc[cstcnt]; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]negdistcnt now is " << negdistcnt << endl; + cout << "[convexHullIterCpu]scanArrayExclusive left begin" << endl; +#endif + // 通过扫描计算处左侧点标记数组对应的累加数组。 + errcode = this->aryScan.scanArrayExclusive( + leftflag, leftacc, cstcnt, add, + false, true, true); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]scanArrayExclusive left end" << endl; + cout << "[convexHullIterCpu]updatePropertyCpu begin" << endl; +#endif + // 计算各个坐标点在下一轮迭代中的新下标。 + errcode = this->updatePropertyCpu( + leftflag, leftacc, negdistflag, negdistacc, + startidx, label, foundacc, cstcnt, + newidx, tmplabel); + + // Merlin debug + cudaDeviceSynchronize(); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]updatePropertyCpu end" << endl; + cout << "[convexHullIterCpu]arrangeCstCpu begin" << endl; +#endif + // 根据上一步计算得到的新下标,生成下一轮迭代所需要的坐标点集。 + errcode = this->arrangeCstCpu( + tmpcstin, negdistflag, newidx, tmplabel, + cstcnt, tmpcstout, newlabel); +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]arrangeCstCpu end" << endl; +#endif + + // 交还部分中间变量,将本轮迭代得到的结果给到下一轮迭代的参数。 + int *labelswptmp = label; + label = newlabel; + newlabel = labelswptmp; + + CoordiSet *cstswptmp = tmpcstin; + tmpcstin = tmpcstout; + tmpcstout = cstswptmp; + + cstswptmp = tmpconvexin; + tmpconvexin = tmpconvexout; + tmpconvexout = cstswptmp; + + cstcnt -= negdistcnt; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullIterCpu]cstcnt now is " << cstcnt << endl; +#endif + // 一轮迭代到此结束。 + } + + // 将计算出来的凸壳点拷贝到输出点集中。迭代完成后,tmpconvexin 保存有最后的 + // 结果。如果在 while 判断条件处退出迭代,则上一轮求出的凸壳点集是最终结 + // 果,此时在上一轮末,由于交换指针,使得原本存放在tmpconvexout 的最终结果 + // 变为了存放在 tmpconvexin 中;如果迭代实在判断有否新发现点处退出,则说明 + // 当前并未发现新的凸壳点,那么 tmpconvexin 和 tmpconvexout 内容应该是一致 + // 的,但本着稳定的原则,应该取更早形成的变量,即 tmpconvexin。 + + // 首先临时将这个存放结果的点集的点数量修改为凸壳点的数量。 + tmpconvexin->count = convexcnt; + + // 然后,将计算出来的凸壳点拷贝到输出参数中。如果是求解上半凸壳点,则需要将 + // 结果翻转后输出,但是由于翻转函数不能改变输出点集的点的数量,因此,这里还 + // 需要先使用拷贝函数,调整输出点的数量(好在,通常凸壳点的数量不错,这一步 + // 骤不会造成太能的性能下降,若日后发现有严重的性能下降,还需要额外写一个更 + // 加复杂一些的翻转函数。) + errcode = CoordiSetBasicOp::copyToHost(tmpconvexin, convexcst); + if (errcode != NO_ERROR) { + tmpconvexin->count = inputcst->count; + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 最后,为了程序稳定性的考虑,回复其凸壳点的数量。 + tmpconvexin->count = inputcst->count; + + // 释放内存 + delete tmpmem; + // cudaFree(tmpmemDev); + CoordiSetBasicOp::deleteCoordiSet(tmpcstin); + CoordiSetBasicOp::deleteCoordiSet(tmpcstout); + CoordiSetBasicOp::deleteCoordiSet(tmpconvexin); + CoordiSetBasicOp::deleteCoordiSet(tmpconvexout); + + // 最后,如果所求点是上半凸壳,则还需要翻转所有凸壳点。 + if (!lowerconvex) { + errcode = this->flipWholeCstCpu(convexcst, convexcst); + if (errcode != NO_ERROR) + return errcode; + } + + // 操作完毕,退出。 + return NO_ERROR; +} +#undef FAIL_CONVEXHULL_FREE + +// 宏:FAIL_CONVEXHULL_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_CONVEXHULL_FREE do { \ + if (tmpmemDev != NULL) \ + cudaFree(tmpmemDev); \ + if (tmpcstin != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpcstin); \ + if (tmpcstout != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpcstout); \ + if (tmpconvexin != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpconvexin); \ + if (tmpconvexout != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tmpconvexout); \ + } while (0) + +// 成员方法:convexHullIter(迭代法求凸壳上的点集) +__host__ int ConvexHull::convexHullIter( + CoordiSet *inputcst, CoordiSet *convexcst, bool lowerconvex) +{ + // 检查输入坐标集,输出坐标集是否为空。 + if (inputcst == NULL || convexcst == NULL) + return NULL_POINTER; + + // 如果输入点集中不含有任何的坐标点,则直接退出。 + if (inputcst->count < 1 || inputcst->tplData == NULL) + return INVALID_DATA; + + // 如果输入点集中点的数量少于 2 个点时,则不需要任何求解过程,直接将输入点 + // 集拷贝到输出点集即可。虽然当坐标点集中仅包含两个点时也可以直接判定为凸壳 + // 点,但考虑到顺序问题,代码还是让仅有两个点的情况走完整个流程。 + if (inputcst->count < 2) + return CoordiSetBasicOp::copyToCurrentDevice(inputcst, convexcst); + + // 局部变量 + cudaError_t cuerrcode; // CUDA 函数调用返回的错误码 + int errcode; // 调用函数返回的错误码 + + // 定义扫描所用的二元操作符。 + add_class add; + + int cstcnt = inputcst->count; // 坐标点集中点的数量。这里之所以将其使用另 + // 外一个变量保存出来是因为这个值随着迭代会 + // 变化,如果直接使用 CoordiSet 中的 count + // 域会带来内存管理上的不便。 + int convexcnt = 2; // 当前凸壳点的数量,由于迭代开始时,已经实 + // 现找到了点集中的最左和最有两点作为凸壳 + // 点,因此这里直接赋值为 2。 + int foundcnt; // 当前迭代时找到的新凸壳点的数量,这一数量 + // 并不包含往次所找到的凸壳点。 + int negdistcnt; // 当前负垂距点的数量。 + //int itercnt = 0; // 迭代次数记录器。 + + int *tmpmemDev = NULL; // 存放中间变量的 Device 内存空间。 + CoordiSet *tmpcstin = NULL; // 每次迭代中作为输入坐标点集的临时坐标点 + // 集。 + CoordiSet *tmpcstout = NULL; // 每次迭代中作为输出坐标点击的临时坐标点 + // 集。 + CoordiSet *tmpconvexin = NULL; // 每次迭代中作为输入凸壳点集的临时坐标点 + // 集。 + CoordiSet *tmpconvexout = NULL; // 每次迭代中作为输出凸壳点集(新凸壳点 + // 集)的临时坐标点集。 + + size_t datacnt = 0; // 所需要的数据元素的数量。 + size_t datasize = 0; // 书需要的数据元素的字节尺寸。 + + // 宏:CHI_DATA_DECLARE(中间变量声明器) + // 为了消除中间变量声明过程中大量的重复代码,这里提供了一个宏,使代码看起来 + // 整洁一些。 +#define CHI_DATA_DECLARE(dataname, type, count) \ + type *dataname##Dev = NULL; \ + size_t dataname##cnt = (count); \ + datacnt += dataname##cnt; \ + datasize += dataname##cnt * sizeof (type) + + // 声明各个中间变量的 Device 数组。 + CHI_DATA_DECLARE(label, int, // 记录当前迭代中每个像素点所在的 + inputcst->count); // LABEL 区域。 + CHI_DATA_DECLARE(negdistflag, int, // 记录当前迭代中每个像素点是否具有 + inputcst->count); // 负垂距。 + CHI_DATA_DECLARE(negdistacc, int, // 记录当前迭代中具有负垂距点的累加 + inputcst->count + 1); // 和,其物理含义是在当前点之前存在 + // 多少个负垂距点,其最后一个元素表 + // 示当前迭代共找到了多少个负垂距 + // 点。 + CHI_DATA_DECLARE(maxdistidx, int, // 记录当前迭代中每个坐标点前面的所 + inputcst->count); // 有点中和其在同一个 LABEL 区域的 + // 所有点中具有最大垂距的下标。 + CHI_DATA_DECLARE(foundflag, int, // 记录当前迭代中各个 LABEL 区域是 + inputcst->count); // 否找到了新的凸壳点。 + CHI_DATA_DECLARE(foundacc, int, // 记录当前迭代中每个 LABEL 区域其 + inputcst->count + 1); // 前面的所有 LABEL 区域共找到的新 + // 的凸壳点的数量。该值用于计算各个 + // 凸壳点(无论是旧的还是新的)在新 + // 的凸壳点集中的新下标。 + CHI_DATA_DECLARE(leftflag, int, // 记录当前的坐标点是否处于新发现的 + inputcst->count); // 坐标点的左侧 + CHI_DATA_DECLARE(leftacc, int, // 记录当前的坐标点之前的左侧点的数 + inputcst->count + 1); // 量。该数组用于计算坐标点在下一轮 + // 计算中的下标。 + CHI_DATA_DECLARE(startidx, int, // 记录每个 LABEL 区域在坐标点集中 + inputcst->count); // 的起始下标 + CHI_DATA_DECLARE(newidx, int, // 记录当前坐标点集中各个坐标点在下 + inputcst->count); // 一轮迭代中的新的下标。 + CHI_DATA_DECLARE(tmplabel, int, + inputcst->count); + CHI_DATA_DECLARE(newlabel, int, // 记录当前坐标点集中各个坐标点在下 + inputcst->count); // 一轮迭代中新的 LABEL 区域。 + + // 消除中间变量声明器这个宏,防止后续步骤的命名冲突。 +#undef CHI_DATA_DECLARE + + // 中间变量申请 Device 内存空间,并将这些空间分配给各个中间变量。 + cuerrcode = cudaMalloc((void **)&tmpmemDev, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_CONVEXHULL_FREE; + return CUDA_ERROR; + } + + // 为各个中间变量分配内存空间,采用这种一次申请一个大空间的做法是为了减少申 + // 请内存的开销,同时也减少因内存对齐导致的内存浪费。 + labelDev = tmpmemDev; + negdistflagDev = labelDev + labelcnt; + negdistaccDev = negdistflagDev + negdistflagcnt; + maxdistidxDev = negdistaccDev + negdistacccnt; + foundflagDev = maxdistidxDev + maxdistidxcnt; + foundaccDev = foundflagDev + foundflagcnt; + leftflagDev = foundaccDev + foundacccnt; + leftaccDev = leftflagDev + leftflagcnt; + startidxDev = leftaccDev + leftacccnt; + newidxDev = startidxDev + startidxcnt; + newlabelDev = newidxDev + newidxcnt; + tmplabelDev = newlabelDev + newlabelcnt; + + // 宏:CHI_USE_SYS_FUNC + // 该开关宏用于指示是否在后续步骤中尽量使用 CUDA 提供的函数,而不是启动由开 + // 发方自行编写的 Kernel 函数完成操作。 +//#define CHI_USE_SYS_FUNC + + // 初始化 LABEL 数组。 +#ifdef CHI_USE_SYS_FUNC + // 首先将 LABEL 数组中所有内存元素全部置零。 + cuerrcode = cudaMemset(labelDev, 0, labelcnt * sizeof (int)); + if (cuerrcode != cudaSuccess) { + FAIL_CONVEXHULL_FREE; + return CUDA_ERROR; + } + + // 将 LABEL 数组中最后一个元素置 1。 + int tmp_one = 1; + cuerrcode = cudaMemcpy(&labelDev[cstcnt - 1], &tmp_one, + sizeof (int), cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + FAIL_CONVEXHULL_FREE; + return CUDA_ERROR; + } +#else + // 调用 LABEL 初始化函数,完成 LABEL 初始化。初始化后,除最后一个元素为 1 + // 外,其余元素皆为 0。 + errcode = this->initLabelAry(labelDev, cstcnt); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#endif + + // 初始化迭代过程中使用的坐标点集,这里一共需要使用到两个坐标点集,为了不破 + // 坏输入坐标点集,这里在迭代过程中我们使用内部申请的坐标点集。 + + // 初始化第一个坐标点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcstin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 将输入坐标点集中的数据从输入点集中拷贝到第一个坐标点集中。此后所有的操作 + // 仅在临时坐标点集中处理,不再碰触输入坐标点集。这里如果是求解上半凸壳,则 + // 直接调用翻转坐标点的函数。 + if (lowerconvex) + errcode = CoordiSetBasicOp::copyToCurrentDevice(inputcst, tmpcstin); + else + errcode = this->flipWholeCst(inputcst, tmpcstin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 初始化第二个坐标点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcstout); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 在 Device 内存中初始化第二个坐标点集,为其申请足够长度的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(tmpcstout, + inputcst->count); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 初始化迭代过程中使用到的凸壳点集,这里一共需要两个凸壳点集。我们不急于更 + // 新输出参数 convexcst,是因为避免不必要的麻烦,等到凸壳计算完毕后,再将凸 + // 壳内容拷贝到输出参数中。 + + // 初始化第一个凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpconvexin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 在 Device 内存中初始化第一个凸壳点集,为其申请足够长度的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(tmpconvexin, + inputcst->count); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 初始化第二个凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpconvexout); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 在 Device 内存中初始化第二个凸壳点集,为其申请足够长度的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(tmpconvexout, + inputcst->count); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 寻找最左最右点,并利用这两个点初始化输入点集和凸壳点集。初始化后,输入点 + // 集的第一个点为最左点,最后一个点为最右点;凸壳点集中仅包含最左最右两个 + // 点。 + errcode = swapEdgePoint(tmpcstin, tmpconvexin); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 所有的初始化过程至此全部完毕,开始进行迭代。每次迭代都需要重新计算坐标点 + // 在其 LABEL 区域内的垂距,然后根据垂距信息判断每个 LABEL 区域内是否存在新 + // 的凸壳点(如果有需要确定是哪一个点),之后根据这个新发现的凸壳点,计算所 + // 有坐标点在下一轮迭代中的下标。计算后的下标要求属于一个 LABEL 的点都在一 + // 起,并且排除所有具有负垂距的点,因为这些点在下一轮迭代中已经毫无意义。迭 + // 代的过程知道无法在从当前所有的 LABEL 区域内找到新的凸壳点为止。此处循环 + // 的判断条件只是一个防护性措施,若坐标点集的数量同凸壳点相等,那就说明没有 + // 任何可能在找到新的凸壳点了。 + while (cstcnt >= convexcnt) { + // 调用更新垂距函数。更新点集中每个点的垂距值和负垂距标志数组。 + errcode = this->updateDist(tmpcstin, tmpconvexin, labelDev, + cstcnt, negdistflagDev); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 利用分段扫描得到各个 LABEL 区域的最大垂距,记忆最大垂距坐标点的下标 + // 值。 + errcode = this->segScan.segmentedScan( + ATTACHED_DATA(tmpcstin), labelDev, + ATTACHED_DATA(tmpcstout), maxdistidxDev, cstcnt, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 根据所求出来的垂距信息判断各个 LABEL 区域是否有新的凸壳点存在。 + errcode = this->updateFoundInfo( + labelDev, ATTACHED_DATA(tmpcstin), maxdistidxDev, + cstcnt, foundflagDev, startidxDev); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 通过扫描,计算出 LABEL 区域新发现凸壳点标记值对应的累加值。 + errcode = this->aryScan.scanArrayExclusive(foundflagDev, foundaccDev, + convexcnt, add, + false, false, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 将新凸壳点标记累加值的最后一个拷贝到 Host 内存中,这个累加值的含义是 + // 当前迭代下所有新发现的凸壳点的数量。 + cuerrcode = cudaMemcpy(&foundcnt, &foundaccDev[convexcnt], + sizeof (int), cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 如果新发现的凸壳点的数量小于等于 0,则说明说有的凸壳点都已经被找到, + // 没有必要在继续做下去了,因此退出迭代。 + if (foundcnt <= 0) + break; + + // 更新凸壳点集,将新发现的凸壳点集更新到凸壳点集中。 + errcode = this->updateConvexCst( + tmpcstin, tmpconvexin, foundflagDev, foundaccDev, startidxDev, + maxdistidxDev, convexcnt, tmpconvexout); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 更新凸壳点集中点的数量。 + convexcnt += foundcnt; + + // 标记左侧点。所谓左侧点是在某 LABEL 区域内处于新发现的凸壳点左侧的 + // 点。 + errcode = this->markLeftPoints( + tmpcstin, tmpconvexout, negdistflagDev, labelDev, + foundflagDev, foundaccDev, cstcnt, leftflagDev); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 通过扫描,计算出负垂距点标记数组对应的累加数组。negdistflagDev 实在 + // 第一步更新垂距的时候获得的,之所以这么晚才计算其对应的累加数组,是因 + // 为在前面检查 foundcnt 退出循环之前不需要这个数据,这样,如果真的在该 + // 处退出,则程序进行了多余的计算,为了避免这一多余计算,我们延后计算 + // negdistaccDev 至此处。 + errcode = this->aryScan.scanArrayExclusive( + negdistflagDev, negdistaccDev, cstcnt, add, + false, false, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 将负垂距点累加总和拷贝出来,用来更新下一轮循环的坐标点数量值。 + cuerrcode = cudaMemcpy(&negdistcnt, &negdistaccDev[cstcnt], + sizeof (int), cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 通过扫描计算处左侧点标记数组对应的累加数组。 + errcode = this->aryScan.scanArrayExclusive( + leftflagDev, leftaccDev, cstcnt, add, + false, false, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 计算各个坐标点在下一轮迭代中的新下标。 + errcode = this->updateProperty( + leftflagDev, leftaccDev, negdistflagDev, negdistaccDev, + startidxDev, labelDev, foundaccDev, cstcnt, + newidxDev, tmplabelDev); + cudaDeviceSynchronize(); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 根据上一步计算得到的新下标,生成下一轮迭代所需要的坐标点集。 + errcode = this->arrangeCst( + tmpcstin, negdistflagDev, newidxDev, tmplabelDev, + cstcnt, tmpcstout, newlabelDev); + + // 交还部分中间变量,将本轮迭代得到的结果给到下一轮迭代的参数。 + int *labelswptmp = labelDev; + labelDev = newlabelDev; + newlabelDev = labelswptmp; + + CoordiSet *cstswptmp = tmpcstin; + tmpcstin = tmpcstout; + tmpcstout = cstswptmp; + + cstswptmp = tmpconvexin; + tmpconvexin = tmpconvexout; + tmpconvexout = cstswptmp; + + cstcnt -= negdistcnt; + + // 一轮迭代到此结束。 + } + + // 将计算出来的凸壳点拷贝到输出点集中。迭代完成后,tmpconvexin 保存有最后的 + // 结果。如果在 while 判断条件处退出迭代,则上一轮求出的凸壳点集是最终结 + // 果,此时在上一轮末,由于交换指针,使得原本存放在tmpconvexout 的最终结果 + // 变为了存放在 tmpconvexin 中;如果迭代实在判断有否新发现点处退出,则说明 + // 当前并未发现新的凸壳点,那么 tmpconvexin 和 tmpconvexout 内容应该是一致 + // 的,但本着稳定的原则,应该取更早形成的变量,即 tmpconvexin。 + + // 首先临时将这个存放结果的点集的点数量修改为凸壳点的数量。 + tmpconvexin->count = convexcnt; + + // 然后,将计算出来的凸壳点拷贝到输出参数中。如果是求解上半凸壳点,则需要将 + // 结果翻转后输出,但是由于翻转函数不能改变输出点集的点的数量,因此,这里还 + // 需要先使用拷贝函数,调整输出点的数量(好在,通常凸壳点的数量不错,这一步 + // 骤不会造成太能的性能下降,若日后发现有严重的性能下降,还需要额外写一个更 + // 加复杂一些的翻转函数。) + errcode = CoordiSetBasicOp::copyToCurrentDevice(tmpconvexin, convexcst); + if (errcode != NO_ERROR) { + tmpconvexin->count = inputcst->count; + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 最后,为了程序稳定性的考虑,回复其凸壳点的数量。 + tmpconvexin->count = inputcst->count; + + // 释放内存 + cudaFree(tmpmemDev); + CoordiSetBasicOp::deleteCoordiSet(tmpcstin); + CoordiSetBasicOp::deleteCoordiSet(tmpcstout); + CoordiSetBasicOp::deleteCoordiSet(tmpconvexin); + CoordiSetBasicOp::deleteCoordiSet(tmpconvexout); + + // 最后,如果所求点是上半凸壳,则还需要翻转所有凸壳点。 + if (!lowerconvex) { + errcode = this->flipWholeCst(convexcst, convexcst); + if (errcode != NO_ERROR) + return errcode; + } + + // 操作完毕,退出。 + return NO_ERROR; +} +#undef FAIL_CONVEXHULL_FREE + +// Kernel 函数:_joinConvexKer(合并凸壳点集) +static __global__ void _joinConvexKer( + CoordiSetCuda lconvex, CoordiSetCuda uconvex, + CoordiSetCuda convex, int *convexcnt) +{ + // 共享内存,用来记录上下凸壳在最左最右点处是否重合,如果重合,应该在整合后 + // 的坐标点中排除重合的点。其中,[0] 表示最左点,[1] 表示最右点。用 1 表示 + // 有重合的点,用 0 表示没有重合的点。 + __shared__ int sameedge[2]; + + // 为了代码中的简化表示,这里将比较长的变量换成了比较短的变量。该语句在编译 + // 中不会带来额外的运行性能下降。 + int *ldata = lconvex.tplMeta.tplData; + int *udata = uconvex.tplMeta.tplData; + int lcnt = lconvex.tplMeta.count; + int ucnt = uconvex.tplMeta.count; + + // 由每个 Block 的第一个 Thread 计算是否存在重合的最左最右点。 + if (threadIdx.x == 0) { + // 判断最左点是否重合,对于上半凸壳,最左点存放在其首部,对于下半凸壳, + // 最左点存放在其尾部。 + if (ldata[0] == udata[2 * (ucnt - 1)] && + ldata[1] == udata[2 * (ucnt - 1) + 1]) { + sameedge[0] = 1; + } else { + sameedge[0] = 0; + } + + // 判断最右点是否重合,对于上半凸壳,最右点存放在其尾部,对于下半凸壳, + // 最右点存放在其首部。 + if (ldata[2 * (lcnt - 1)] == udata[0] && + ldata[2 * (lcnt - 1) + 1] == udata[1]) { + sameedge[1] = 1; + } else { + sameedge[1] = 0; + } + + // 根据对最左最右点的判断,就可以得到最终凸壳点集的数量,这里用整个 + // Kernel 的第一个 Thread 写入最终凸壳点集的数量。 + if (blockIdx.x == 0) + *convexcnt = lcnt + ucnt - sameedge[0] - sameedge[1]; + } + + // 同步 Block 内部的所有线程,使得求解结果对所有的 Thread 可见。 + __syncthreads(); + + // 计算当前线程的全局下标。该下标对应于输出凸壳点的下标。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 判断当前线程是对应于下半凸壳和上半凸壳。将上半凸壳放在输出凸壳的前半部 + // 分,下半凸壳放在其后半部分。 + if (idx >= lcnt) { + // 对于处理上半凸壳,首先计算出当前线程对应的上半凸壳下标,这里还需要经 + // 过最右点重合的校正。 + int inidx = idx - lcnt + sameedge[1]; + + // 如果对应的下标是越界的,则直接返回,这里仍需要经过最左点重合的校正。 + if (inidx >= ucnt - sameedge[0]) + return; + + // 将上半凸壳拷贝到整体的凸壳中。 + convex.tplMeta.tplData[2 * idx] = udata[2 * inidx]; + convex.tplMeta.tplData[2 * idx + 1] = udata[2 * inidx + 1]; + } else { + // 将下半凸壳拷贝到整体的凸壳中。由于上半凸壳内部坐标和整体凸壳的坐标是 + // 一致的,且越界情况通过上面的 if 语句已经屏蔽,故没有进行下标的计算和 + // 判断。 + convex.tplMeta.tplData[2 * idx] = ldata[2 * idx]; + convex.tplMeta.tplData[2 * idx + 1] = ldata[2 * idx + 1]; + } +} + +// 宏:FAIL_JOINCONVEX_FREE +// 该宏用于完成下面函数运行出现错误退出前的内存清理工作。 +#define FAIL_JOINCONVEX_FREE do { \ + if (tmpconvex != NULL && tmpconvex != convex) \ + CoordiSetBasicOp::deleteCoordiSet(tmpconvex); \ + if (convexcntDev != NULL) \ + cudaFree(convexcntDev); \ + } while (0) + +// Host 成员方法:joinConvex(合并凸壳点) +__host__ int ConvexHull::joinConvex( + CoordiSet *lconvex, CoordiSet *uconvex, CoordiSet *convex) +{ + // 检查指针性参数是否为 NULL。 + if (lconvex == NULL || uconvex == NULL || convex == NULL) + return NULL_POINTER; + + // 检查输入坐标点是否包含了有效的坐标点数量,如果输入坐标点中点数小于 2,则 + // 无法完成相应的计算工作。 + if (lconvex->count < 2 || lconvex->tplData == NULL || + uconvex->count < 2 || uconvex->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + cudaError_t cuerrcode; + + // 局部变量,输出凸壳点数量上限,即上下凸壳点数量加和。 + int tmptotal = lconvex->count + uconvex->count; + + // 将下半凸壳点数据拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(lconvex); + if (errcode != NO_ERROR) + return errcode; + + // 将上半凸壳点数据拷贝到当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(uconvex); + if (errcode != NO_ERROR) + return errcode; + + // 如果输出凸壳点是一个空点集,则为其开辟适当的内存空间,以用于存放最后的凸 + // 壳点。 + if (convex->tplData == NULL) { + errcode = CoordiSetBasicOp::makeAtCurrentDevice(convex, tmptotal); + if (errcode != NO_ERROR) + return errcode; + } + + // 局部变量。 + CoordiSet *tmpconvex = NULL; // 临时输出凸壳点集。由于参数给定输出用凸壳点 + // 集不一定具有合适数量的存储空间,因此,先用 + // 一个临时的凸壳点集存放 Kernel 返回的结果, + // 然后在归放到参数所对应的凸壳点集中。 + int *convexcntDev = NULL; // 用于存放 Kernel 返回的最终凸壳点数量。 + + // 给临时输出凸壳点集初始化。 + if (convex->count < tmptotal || convex->count >= tmptotal * 2) { + // 如果给定的输出凸壳点集点的数量不合适,则需要重新申请一个凸壳点集并赋 + // 值给临时凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpconvex); + if (errcode != NO_ERROR) + return errcode; + + // 申请后,还需要给该凸壳点集开辟合适的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(tmpconvex, tmptotal); + if (errcode != NO_ERROR) { + FAIL_JOINCONVEX_FREE; + return errcode; + } + } else { + // 如果输出土克点击中点的数量合适,则直接使用输出凸壳点集承接 Kernel 的 + // 输出。 + tmpconvex = convex; + } + + // 取出坐标点集对应的 CUDA 型数据。 + CoordiSetCuda *lconvexCud = COORDISET_CUDA(lconvex); + CoordiSetCuda *uconvexCud = COORDISET_CUDA(uconvex); + CoordiSetCuda *tmpconvexCud = COORDISET_CUDA(tmpconvex); + + // 为 Kernel 输出的凸壳点集数量开辟 Device 内存空间。 + cuerrcode = cudaMalloc((void **)&convexcntDev, sizeof (int)); + if (cuerrcode != cudaSuccess) { + FAIL_JOINCONVEX_FREE; + return CUDA_ERROR; + } + + // 计算启动 Kernel 所需要的 Block 尺寸和数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (tmptotal + blocksize - 1) / blocksize; + + // 启动 Kernel 完成计算。 + _joinConvexKer<<>>(*lconvexCud, *uconvexCud, + *tmpconvexCud, convexcntDev); + + // 检查 Kernel 函数是否执行正确。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_JOINCONVEX_FREE; + return CUDA_ERROR; + } + + // 从 Device 内存中读取 Kernel 返回的凸壳点数量,并将其赋值到临时凸壳点集的 + // 坐标点数量中。 + cuerrcode = cudaMemcpy(&(tmpconvex->count), convexcntDev, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_JOINCONVEX_FREE; + return CUDA_ERROR; + } + + // 如果使用的是临时凸壳点集,则需要将点集从临时凸壳点集中拷贝到输出凸壳点集 + // 中,在拷贝的过程中,输出凸壳点集的坐标点数量会被安全的重新定义。 + if (tmpconvex != convex) { + errcode = CoordiSetBasicOp::copyToCurrentDevice(tmpconvex, convex); + if (errcode != NO_ERROR) { + FAIL_JOINCONVEX_FREE; + return errcode; + } + + // 至此,临时凸壳点集的使命完成,清除其占用的内存空间。 + CoordiSetBasicOp::deleteCoordiSet(tmpconvex); + } + + // 释放 Device 内存空间。 + cudaFree(convexcntDev); + + // 操作结束,返回。 + return NO_ERROR; +} +#undef FAIL_JOINCONVEX_FREE + +// FAIL_JOINCONVEXCPU_FREE +// 该宏用于完成下面函数运行出现错误退出前的内存清理工作。 +#define FAIL_JOINCONVEXCPU_FREE do { \ + if (tmpconvex != NULL && tmpconvex != convex) \ + CoordiSetBasicOp::deleteCoordiSet(tmpconvex); \ + } while (0) + +// Host 成员方法:joinConvexCpu(合并凸壳点) +__host__ int ConvexHull::joinConvexCpu( + CoordiSet *lconvex, CoordiSet *uconvex, CoordiSet *convex) +{ + // 检查指针性参数是否为 NULL。 + if (lconvex == NULL || uconvex == NULL || convex == NULL) + return NULL_POINTER; + + // 检查输入坐标点是否包含了有效的坐标点数量,如果输入坐标点中点数小于 2,则 + // 无法完成相应的计算工作。 + if (lconvex->count < 2 || lconvex->tplData == NULL || + uconvex->count < 2 || uconvex->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 局部变量,输出凸壳点数量上限,即上下凸壳点数量加和。 + int tmptotal = lconvex->count + uconvex->count; + + // 将下半凸壳点数据拷贝到当前 Host。 + errcode = CoordiSetBasicOp::copyToHost(lconvex); + if (errcode != NO_ERROR) + return errcode; + + // 将上半凸壳点数据拷贝到 Host。 + errcode = CoordiSetBasicOp::copyToHost(uconvex); + if (errcode != NO_ERROR) + return errcode; + + // 如果输出凸壳点是一个空点集,则为其开辟适当的内存空间,以用于存放最后的凸 + // 壳点。 + if (convex->tplData == NULL) { + errcode = CoordiSetBasicOp::makeAtHost(convex, tmptotal); + if (errcode != NO_ERROR) + return errcode; + } + + // 局部变量。 + CoordiSet *tmpconvex = NULL; // 临时输出凸壳点集。由于参数给定输出用凸壳点 + // 集不一定具有合适数量的存储空间,因此,先用 + // 一个临时的凸壳点集存放 Kernel 返回的结果, + // 然后在归放到参数所对应的凸壳点集中。 + int convexcnt = 0; // 用于存放最终凸壳点数量。 + + // 给临时输出凸壳点集初始化。 + if (convex->count < tmptotal || convex->count >= tmptotal * 2) { + // 如果给定的输出凸壳点集点的数量不合适,则需要重新申请一个凸壳点集并赋 + // 值给临时凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&tmpconvex); + if (errcode != NO_ERROR) + return errcode; + + // 申请后,还需要给该凸壳点集开辟合适的内存空间。 + errcode = CoordiSetBasicOp::makeAtHost(tmpconvex, tmptotal); + if (errcode != NO_ERROR) { + FAIL_JOINCONVEXCPU_FREE; + return errcode; + } + } else { + // 如果输出土克点击中点的数量合适,则直接使用输出凸壳点集承接 Kernel 的 + // 输出。 + tmpconvex = convex; + } + + // 取出坐标点集对应的 CUDA 型数据。 + CoordiSetCuda *lconvexCud = COORDISET_CUDA(lconvex); + CoordiSetCuda *uconvexCud = COORDISET_CUDA(uconvex); + CoordiSetCuda *tmpconvexCud = COORDISET_CUDA(tmpconvex); + + // 共享内存,用来记录上下凸壳在最左最右点处是否重合,如果重合,应该在整合后 + // 的坐标点中排除重合的点。其中,[0] 表示最左点,[1] 表示最右点。用 1 表示 + // 有重合的点,用 0 表示没有重合的点。 + int sameedge[2]; + + // 为了代码中的简化表示,这里将比较长的变量换成了比较短的变量。该语句在编译 + // 中不会带来额外的运行性能下降。 + int *ldata = (*lconvexCud).tplMeta.tplData; + int *udata = (*uconvexCud).tplMeta.tplData; + int lcnt = (*lconvexCud).tplMeta.count; + int ucnt = (*uconvexCud).tplMeta.count; + + // 判断最左点是否重合,对于上半凸壳,最左点存放在其首部,对于下半凸壳, + // 最左点存放在其尾部。 + if (ldata[0] == udata[2 * (ucnt - 1)] && + ldata[1] == udata[2 * (ucnt - 1) + 1]) { + sameedge[0] = 1; + } else { + sameedge[0] = 0; + } + + // 判断最右点是否重合,对于上半凸壳,最右点存放在其尾部,对于下半凸壳, + // 最右点存放在其首部。 + if (ldata[2 * (lcnt - 1)] == udata[0] && + ldata[2 * (lcnt - 1) + 1] == udata[1]) { + sameedge[1] = 1; + } else { + sameedge[1] = 0; + } + + // 根据对最左最右点的判断,就可以得到最终凸壳点集的数量 + convexcnt = lcnt + ucnt - sameedge[0] - sameedge[1]; + + for (int idx = 0; idx < tmptotal; idx++) { + // 判断当前线程是对应于下半凸壳和上半凸壳。将上半凸壳放在输出凸壳的前半 + // 部分,下半凸壳放在其后半部分。 + if (idx >= lcnt) { + // 对于处理上半凸壳,首先计算出当前线程对应的上半凸壳下标,这里还需 + // 要经过最右点重合的校正。 + int inidx = idx - lcnt + sameedge[1]; + + // 如果对应的下标是不越界的 + if (inidx < ucnt - sameedge[0]) { + // 将上半凸壳拷贝到整体的凸壳中。 + (*tmpconvexCud).tplMeta.tplData[2 * idx] = udata[2 * inidx]; + (*tmpconvexCud).tplMeta.tplData[2 * idx + 1] = + udata[2 * inidx + 1]; + } + } else { + // 将下半凸壳拷贝到整体的凸壳中。由于上半凸壳内部坐标和整体凸壳的坐 + // 标是一致的,且越界情况通过上面的 if 语句已经屏蔽,故没有进行下标 + // 的计算和判断。 + (*tmpconvexCud).tplMeta.tplData[2 * idx] = ldata[2 * idx]; + (*tmpconvexCud).tplMeta.tplData[2 * idx + 1] = ldata[2 * idx + 1]; + } + } + + // 从 Device 内存中读取 Kernel 返回的凸壳点数量,并将其赋值到临时凸壳点集的 + // 坐标点数量中。 + tmpconvex->count = convexcnt; + + // 如果使用的是临时凸壳点集,则需要将点集从临时凸壳点集中拷贝到输出凸壳点集 + // 中,在拷贝的过程中,输出凸壳点集的坐标点数量会被安全的重新定义。 + if (tmpconvex != convex) { + errcode = CoordiSetBasicOp::copyToHost(tmpconvex, convex); + if (errcode != NO_ERROR) { + FAIL_JOINCONVEXCPU_FREE; + return errcode; + } + + // 至此,临时凸壳点集的使命完成,清除其占用的内存空间。 + CoordiSetBasicOp::deleteCoordiSet(tmpconvex); + } + + // 操作结束,返回。 + return NO_ERROR; +} +#undef FAIL_JOINCONVEXCPU_FREE + +// 宏:FAIL_CONVEXHULL_FREE +// 该宏用于完成下面函数运行出现错误退出前的内存清理工作。 +#define FAIL_CONVEXHULL_FREE do { \ + if (lconvex != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(lconvex); \ + if (uconvex != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(uconvex); \ + } while (0) + +// Host 成员方法:convexHullCpu(求一个点集对应的凸壳点集) +__host__ int ConvexHull::convexHullCpu(CoordiSet *inputcst, CoordiSet *convex) +{ + // 检查指针性参数是否为 NULL。 + if (inputcst == NULL || convex == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点,则报错退出。 + if (inputcst->count < 1 || inputcst->tplData == NULL) + return INVALID_DATA; + + // 如果输入点集中只有一个点,那么该点直接输出,作为凸壳点。 + if (inputcst->count == 1) + return CoordiSetBasicOp::copyToHost(inputcst, convex); + + // 如果输入点集中只有两个点,则直接将其下半凸壳输出(显然此时上半凸壳也是这 + // 两个点) + if (inputcst->count == 2) + return this->convexHullIterCpu(inputcst, convex, true); + + // 局部变量,错误码。 + int errcode; + + // 局部变量,下半凸壳和上半凸壳点集变量。 + CoordiSet *lconvex = NULL; + CoordiSet *uconvex = NULL; + + // 申请一个临时点集,用来存放下半凸壳。 + errcode = CoordiSetBasicOp::newCoordiSet(&lconvex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 申请一个临时点集,用来存放上半凸壳。 + errcode = CoordiSetBasicOp::newCoordiSet(&uconvex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + cout << endl; +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullCpu]convexHullIterCpu upper begin" << endl; +#endif + // 调用凸壳迭代,求输入点集的下半凸壳。 + errcode = this->convexHullIterCpu(inputcst, lconvex, true); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullCpu]convexHullIterCpu upper end" << endl; + cout << endl; + cout << "[convexHullCpu]convexHullIterCpu lower begin" << endl; +#endif + + // 调用凸壳迭代,求输入点集的上半凸壳。 + errcode = this->convexHullIterCpu(inputcst, uconvex, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + +#ifdef CH_DEBUG_CPU_PRINT + cout << "[convexHullCpu]convexHullIterCpu lower end" << endl; + cout << endl; +#endif + + // 调用合并两个凸壳的函数,将下半凸壳和上半凸壳粘在一起。 + errcode = this->joinConvexCpu(lconvex, uconvex, convex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 清除临时申请的两个坐标点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(lconvex); + CoordiSetBasicOp::deleteCoordiSet(uconvex); + + // 处理完毕,退出 + return NO_ERROR; +} + +// Host 成员方法:convexHull(求一个点集对应的凸壳点集) +__host__ int ConvexHull::convexHull(CoordiSet *inputcst, CoordiSet *convex) +{ + // 检查指针性参数是否为 NULL。 + if (inputcst == NULL || convex == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点,则报错退出。 + if (inputcst->count < 1 || inputcst->tplData == NULL) + return INVALID_DATA; + + // 如果输入点集中只有一个点,那么该点直接输出,作为凸壳点。 + if (inputcst->count == 1) + return CoordiSetBasicOp::copyToCurrentDevice(inputcst, convex); + + // 如果输入点集中只有两个点,则直接将其下半凸壳输出(显然此时上半凸壳也是这 + // 两个点) + if (inputcst->count == 2) + return this->convexHullIter(inputcst, convex, true); + + cout << "GPU convex 1" << endl; + // 局部变量,错误码。 + int errcode; + + // 局部变量,下半凸壳和上半凸壳点集变量。 + CoordiSet *lconvex = NULL; + CoordiSet *uconvex = NULL; + + // 申请一个临时点集,用来存放下半凸壳。 + errcode = CoordiSetBasicOp::newCoordiSet(&lconvex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 申请一个临时点集,用来存放上半凸壳。 + errcode = CoordiSetBasicOp::newCoordiSet(&uconvex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + cout << "GPU convex lower" << endl; + // 调用凸壳迭代,求输入点集的下半凸壳。 + errcode = this->convexHullIter(inputcst, lconvex, true); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + cout << "GPU convex lower cnt is " << lconvex->count << endl; + + cout << "GPU convex up" << endl; + // 调用凸壳迭代,求输入点集的上半凸壳。 + errcode = this->convexHullIter(inputcst, uconvex, false); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + cout << "GPU convex up cnt is " << uconvex->count << endl; + + cout << "GPU joinConvex" << endl; + // 调用合并两个凸壳的函数,将下半凸壳和上半凸壳粘在一起。 + errcode = this->joinConvex(lconvex, uconvex, convex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULL_FREE; + return errcode; + } + + // 清除临时申请的两个坐标点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(lconvex); + CoordiSetBasicOp::deleteCoordiSet(uconvex); + + // 处理完毕,退出 + return NO_ERROR; +} +#undef FAIL_CONVEXHULL_FREE + +// 宏:FAIL_CONVEXHULLONIMG_FREE +// 该宏用于完成下面函数运行出现错误退出前的内存清理工作。 +#define FAIL_CONVEXHULLONIMG_FREE do { \ + if (cst != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(cst); \ + } while (0) + +// Host 成员方法:convexHullCpu(求图像中阈值给定的对象对应的凸壳点集) +__host__ int ConvexHull::convexHullCpu(Image *inimg, CoordiSet *convex) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || convex == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + + // 新建点集。 + CoordiSet *cst; + + // 构造点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&cst); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULLONIMG_FREE; + return errcode; + } + + // 调用图像转点集的函数。 + errcode = this->imgCvt.imgConvertToCst(inimg, cst); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULLONIMG_FREE; + return errcode; + } + + // 调用求给定点集的凸壳函数。 + errcode = convexHullCpu(cst, convex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULLONIMG_FREE; + return errcode; + } + + // 清除点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(cst); + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:convexHull(求图像中阈值给定的对象对应的凸壳点集) +__host__ int ConvexHull::convexHull(Image *inimg, CoordiSet *convex) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || convex == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + + // 新建点集。 + CoordiSet *cst; + + // 构造点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&cst); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULLONIMG_FREE; + return errcode; + } + + // 调用图像转点集的函数。 + errcode = this->imgCvt.imgConvertToCst(inimg, cst); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULLONIMG_FREE; + return errcode; + } + + // 调用求给定点集的凸壳函数。 + errcode = convexHull(cst, convex); + if (errcode != NO_ERROR) { + FAIL_CONVEXHULLONIMG_FREE; + return errcode; + } + + // 清除点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(cst); + + // 退出。 + return NO_ERROR; +} +#undef FAIL_CONVEXHULLONIMG_FREE + diff --git a/okano_3_0/ConvexHull.h b/okano_3_0/ConvexHull.h new file mode 100644 index 0000000..83d48ff --- /dev/null +++ b/okano_3_0/ConvexHull.h @@ -0,0 +1,547 @@ +// ConvexHull.h +// 创建者:刘瑶 +// +// 计算点集的凸壳(ConvexHull) +// 功能说明:对于已知点集,计算点集的凸壳,并最终输出凸壳上的点集。 +// 核心算法采用 QuickHull 算法,通过迭代求凸壳。 +// 参考文献:[1] S Srungarapu, DP Reddy, K Kothapalli, and etc. Fast Two +// Dimensional Convex Hull on the GPU. WAINA, 2011, pp. 7-12. +// [2] S Srungarapu, DP Reddy, K Kothapalli and etc. Parallelizing two +// dimensional convex hull on NVIDIA GPU and Cell BE. HIPC, 2009. +// [3] CB Barber, DP Dobkin, and H Huhdanpaa. The quickhull algorithm +// for convex hulls. ACM TOMS, pp. 469-483. +// +// 修订历史: +// 2013年01月02日(刘瑶) +// 初始版本。搭建框架。 +// 2013年01月03日(刘瑶) +// 修改部分接口和注释。 +// 2013年01月06日(刘瑶) +// 增加了更新垂距的核函数和成员方法。 +// 2013年01月07日(刘瑶) +// 增加了更新区域发现新凸壳点的函数和更新新凸壳点集的函数。 +// 2013年01月08日(刘瑶,刘婷) +// 增加了标记新发现凸壳点左侧标志的函数。 +// 2013年01月09日(刘瑶,王雪菲) +// 增加了图像转成点集的函数。 +// 2013年01月11日(刘瑶,王雪菲) +// 增加了图像初始化函数和点集转成图像的函数。 +// 2013年01月14日(刘瑶,刘婷) +// 增加了更新点集属性的函数和重新排列点集的函数。完成半凸壳的首次迭代过程。 +// 2013年03月06日(刘瑶) +// 修改了迭代过程的部分变量,修正了内存越界访问的错误。 +// 2013年03月09日(于玉龙) +// 翻新了更新垂距的 Kernel,修正了其中多处注释错误,修正了一处严重的 Bug。 +// 2013年03月10日(于玉龙) +// 翻新了两个 Kernel。修正了其中多处注释错误,修正了多处严重的 Bug。 +// 2013年03月11日(于玉龙) +// 修正了代码中所有的逻辑错误,可以完成下半凸壳的求解工作,目前正在修改代码 +// 格式。 +// 2013年03月12日(于玉龙) +// 修正了代码中的格式问题,以及代码中的一处潜在的 Bug。 +// 2013年03月13日(于玉龙) +// 修正了代码中的格式问题。 +// 翻新了主函数,修正了其中潜在的 Bug。 +// 2013年03月14日(于玉龙) +// 增加了寻找最左最右点的函数。 +// 增加了初始化 LABEL 数组的函数,进一步整理了凸壳迭代函数。 +// 增加了翻转坐标点集的函数,为求解上凸壳点做准备。 +// 完成了上半凸壳的求解。 +// 2013年03月15日(于玉龙) +// 增加了合并凸壳点的函数,为求解整个凸壳点做准备。 +// 实现了完整的凸壳求解。 +// 2013年03月16日(于玉龙) +// 完善了算法中的一个步骤。 +// 2013年03月22日(刘瑶) +// 根据图像转换点集的函数接口,修改了部分代码,修改了构造函数,添加了 get +// 与 set 函数。添加了输入图像求凸壳的函数接口实现。 +// 2013年03月24日(刘瑶) +// 根据修改的图像转换点集的函数接口,修改对应的调用代码。 +// 2013年05月14日(刘瑶) +// 根据 ScanArray 的修改,修改了成员方法中对于 ScanArray 的调用。 +// 2013年09月21日(于玉龙) +// 修正了代码中调用 SCAN 算法的 BUG。 +// 修正了计算 VALUE 区间的 BUG。 +// 2013年12月23日(刘瑶) +// 添加了串行版本的凸壳算法接口及对应的各个函数串行接口。 + +#ifndef __CONVEXHULL_H__ +#define __CONVEXHULL_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "CoordiSet.h" +#include "ScanArray.h" +#include "SegmentedScan.h" +#include "ImgConvert.h" +#include "OperationFunctor.h" + +// 类:ConvexHull +// 继承自:无 +// 功能说明:对于输入的已知点集,计算点集的凸壳,并最终输出凸壳上的点集。 +// 核心算法采用 QuickHull 算法,通过迭代求凸壳。即在目前已知凸壳的每个边外侧, +// 找垂距最大的点,记录下来,并更新凸壳点集。 +class ConvexHull { + +protected: + + // 成员变量:segScan(分段扫描器) + // 完成分段扫描,主要在迭代计算凸壳点的过程中用于计算每个 LABEL 区域的最大 + // 垂距点。 + SegmentedScan segScan; + + // 成员变量:aryScan(扫描累加器) + // 完成非分段扫描,主要在迭代计算凸壳点的过程中用于计算各个标记值所对应的累 + // 加值。 + ScanArray aryScan; + + // 成员变量:imgCvt(图像与坐标集转换) + // 根据给定的图像和阈值,转换成坐标集形式。 + ImgConvert imgCvt; + + // 成员变量:value(图像转换点集的像素阈值) + // 用于图像转换点集的像素阈值。 + unsigned char value; + + // Host 成员方法:initLabelAry(初始化 LABEL 数组) + // 在进行凸壳点迭代之前初始化 LABEL 数组,初始化后该数组最后一个元素为 1, + // 其余元素皆为 0。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执行,返 + // 回 NO_ERROR。 + initLabelAry( + int label[], // 待初始化的 LABEL 数组。 + int cstcnt // 数组长度。 + ); + + // Host 成员方法:initLabelAryCpu(初始化 LABEL 数组) + // 在进行凸壳点迭代之前初始化 LABEL 数组,初始化后该数组最后一个元素为 1, + // 其余元素皆为 0。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执行,返 + // 回 NO_ERROR。 + initLabelAryCpu( + int label[], // 待初始化的 LABEL 数组。 + int cstcnt // 数组长度。 + ); + + // Host 成员方法:swapEdgePoint(寻找最左最右点) + // 寻找 cst 中的最左和最右点,并将最左点交换到 cst 的首部,最右点交换到 cst + // 的尾部,同时将最左最右点放入 convexcst 的头两个坐标点中,但还函数不会改 + // 变两个坐标点集的 count 域。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + swapEdgePoint( + CoordiSet *cst, // 输入点集,从函数退出后其首个坐标点为最左 + // 点,末个坐标点为最右点。原来存放于首末位置 + // 的点分别被移位到原来放置最左最右点的位置。 + CoordiSet *convexcst // 存放输出最左最右点的集合,这个点集在求凸壳 + // 的过程中被用来作为初始凸壳点集。 + ); + + // Host 成员方法:swapEdgePointCpu(寻找最左最右点) + // 寻找 cst 中的最左和最右点,并将最左点交换到 cst 的首部,最右点交换到 cst + // 的尾部,同时将最左最右点放入 convexcst 的头两个坐标点中,但还函数不会改 + // 变两个坐标点集的 count 域。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + swapEdgePointCpu( + CoordiSet *cst, // 输入点集,从函数退出后其首个坐标点为最左 + // 点,末个坐标点为最右点。原来存放于首末位置 + // 的点分别被移位到原来放置最左最右点的位置。 + CoordiSet *convexcst // 存放输出最左最右点的集合,这个点集在求凸壳 + // 的过程中被用来作为初始凸壳点集。 + ); + + // Host 成员方法:updateDist(计算各点垂距) + // 根据目前已知的凸壳上的点集和区域的标签值,根据点到直线的垂距公式,计算点 + // 集的附带数据:点到当前所在区域的最左最右点构成的直线的垂直距离。并且标记 + // 垂距为负的点。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正 + // 确执行,返回 NO_ERROR。 + updateDist( + CoordiSet *cst, // 输入点集,也是输出点集,更新点集的 + // attachData,也就是垂距的信息。 + CoordiSet *convexcst, // 目前已知凸壳上的点集,即每段的最值点信息 + int label[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 输入,当前点的数量。 + int negdistflag[] // 输出,当前点垂距为负的标志数组。 + // 如果当前点垂距为负,则对应的标志位为 1。 + ); + + // Host 成员方法:updateDistCpu(计算各点垂距) + // 根据目前已知的凸壳上的点集和区域的标签值,根据点到直线的垂距公式,计算点 + // 集的附带数据:点到当前所在区域的最左最右点构成的直线的垂直距离。并且标记 + // 垂距为负的点。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正 + // 确执行,返回 NO_ERROR。 + updateDistCpu( + CoordiSet *cst, // 输入点集,也是输出点集,更新点集的 + // attachData,也就是垂距的信息。 + CoordiSet *convexcst, // 目前已知凸壳上的点集,即每段的最值点信息 + int label[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 输入,当前点的数量。 + int negdistflag[] // 输出,当前点垂距为负的标志数组。 + // 如果当前点垂距为负,则对应的标志位为 1。 + ); + + // Host 成员方法: updateFoundInfo(更新新发现凸壳点信息) + // 根据分段扫描后得到的点集信息,更新各个区域是否有新发现的凸壳上的点,更新 + // 目前已知的凸壳上的点的位置索引。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执 + // 行,返回 NO_ERROR。 + updateFoundInfo( + int label[], // 输入,当前点集的区域标签值数组。 + float dist[], // 输入数组,分段扫描后,当前位置记录的本段目前 + // 已知的最大垂距。 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目前已知 + // 的最大垂距点的位置索引数组。 + int cstcnt, // 当前点的数量。 + int foundflag[], // 输出数组,如当前区域内找到新的凸壳上的点,标 + // 志位置 1。 + int startidx[] // 输出,目前已知的凸壳上的点的位置索引数组,也 + // 相当于当前每段上的起始位置的索引数组。 + ); + + // Host 成员方法: updateFoundInfoCpu(更新新发现凸壳点信息) + // 根据分段扫描后得到的点集信息,更新各个区域是否有新发现的凸壳上的点,更新 + // 目前已知的凸壳上的点的位置索引。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执 + // 行,返回 NO_ERROR。 + updateFoundInfoCpu( + int label[], // 输入,当前点集的区域标签值数组。 + float dist[], // 输入数组,分段扫描后,当前位置记录的本段目前 + // 已知的最大垂距。 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目前已知 + // 的最大垂距点的位置索引数组。 + int cstcnt, // 当前点的数量。 + int foundflag[], // 输出数组,如当前区域内找到新的凸壳上的点,标 + // 志位置 1。 + int startidx[] // 输出,目前已知的凸壳上的点的位置索引数组,也 + // 相当于当前每段上的起始位置的索引数组。 + ); + + // Host 成员方法:updateConvexCst(生成新的凸壳点集) + // 根据分段扫描后得到的点集信息,和每段上是否发现新凸壳点的信息,构造新的凸 + // 壳点集。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + updateConvexCst( + CoordiSet *cst, // 输入点集 + CoordiSet *convexcst, // 输入,现有的凸壳上的点集。 + int foundflag[], // 输入,当前区域内有新发现点的标志数组, + // 如果当前区域内找到新的凸壳上的点, + // 标志位置 1。 + int foundacc[], // 输入,偏移量数组,当前区域内有新发现点 + // 的标志位的累加值。用来计算新添加的凸壳 + // 点的存放位置的偏移量。 + int startidx[], // 输入,目前已知的凸壳上的点的索引数组, + // 也相当于当前每段上的起始位置的索引数组 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目 + // 前已知的最大垂距点的位置索引数组。 + int num, // 当前凸壳点的数量。 + CoordiSet *newconvexcst // 输出,更新后目前已知凸壳上的点集,即每 + // 段的最值点信息。 + ); + + // Host 成员方法:updateConvexCstCpu(生成新的凸壳点集) + // 根据分段扫描后得到的点集信息,和每段上是否发现新凸壳点的信息,构造新的凸 + // 壳点集。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + updateConvexCstCpu( + CoordiSet *cst, // 输入点集 + CoordiSet *convexcst, // 输入,现有的凸壳上的点集。 + int foundflag[], // 输入,当前区域内有新发现点的标志数组, + // 如果当前区域内找到新的凸壳上的点, + // 标志位置 1。 + int foundacc[], // 输入,偏移量数组,当前区域内有新发现点 + // 的标志位的累加值。用来计算新添加的凸壳 + // 点的存放位置的偏移量。 + int startidx[], // 输入,目前已知的凸壳上的点的索引数组, + // 也相当于当前每段上的起始位置的索引数组 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目 + // 前已知的最大垂距点的位置索引数组。 + int num, // 当前凸壳点的数量。 + CoordiSet *newconvexcst // 输出,更新后目前已知凸壳上的点集,即每 + // 段的最值点信息。 + ); + + // Host 成员方法:markLeftPoints(标记左侧标点) + // 根据目前每段上是否有新发现凸壳点的标志,标记在新发现的凸壳点点左侧的点, + // 记录到标记数组。左侧标记在其后的计算过程中可以用来计算在下一轮迭代中坐标 + // 点的新位置。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + markLeftPoints( + CoordiSet *cst, // 输入点集,也是输出点集 + CoordiSet *newconvexcst, // 输入,更新后的目前已知凸壳上的点集,即 + // 每段的最值点信息。 + int negdistflag[], // 输入,负垂距标记值。 + int label[], // 输入,当前点集的区域标签值数组。 + int foundflag[], // 输入,当前区域内有新发现点的标志位数 + // 组,如果当前区域内找到新的凸壳上的点, + // 标志位置 1。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现 + // 点的标志位的累加值。用来计算新添加的凸 + // 壳点的存放位置的偏移量。 + int cstcnt, // 当前点的数量。 + int leftflag[] // 输出,当前点在目前区域中新发现凸壳点的 + // 左侧的标志数组,如果在左侧,则置为 1。 + ); + + // Host 成员方法:markLeftPointsCpu(标记左侧标点) + // 根据目前每段上是否有新发现凸壳点的标志,标记在新发现的凸壳点点左侧的点, + // 记录到标记数组。左侧标记在其后的计算过程中可以用来计算在下一轮迭代中坐标 + // 点的新位置。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + markLeftPointsCpu( + CoordiSet *cst, // 输入点集,也是输出点集 + CoordiSet *newconvexcst, // 输入,更新后的目前已知凸壳上的点集,即 + // 每段的最值点信息。 + int negdistflag[], // 输入,负垂距标记值。 + int label[], // 输入,当前点集的区域标签值数组。 + int foundflag[], // 输入,当前区域内有新发现点的标志位数 + // 组,如果当前区域内找到新的凸壳上的点, + // 标志位置 1。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现 + // 点的标志位的累加值。用来计算新添加的凸 + // 壳点的存放位置的偏移量。 + int cstcnt, // 当前点的数量。 + int leftflag[] // 输出,当前点在目前区域中新发现凸壳点的 + // 左侧的标志数组,如果在左侧,则置为 1。 + ); + + // Host 成员方法:updateProperty(更新点集的属性) + // 根据已知信息,更新当前点集的区域标签和位置索引。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + updateProperty( + int leftflag[], // 输入,当前点在目前区域中新发现凸壳点的 + // 左侧的标志数组,如在左侧,则置为 1 + int leftacc[], // 输入,偏移量数组,即当前点在目前区域中 + // 新发现凸壳点的左侧的标志的累加值。 + int negdistflag[], // 输入,垂距为负的标志数组。如果当前点垂 + // 距为负,则对应的标志位为 1。 + int negdistacc[], // 输入,垂距为正的标志的累加值数组。 + int startidx[], // 输入,目前已知的凸壳上的点的位置索引数 + // 组,也相当于当前每段上的起始位置的索引 + // 数组。 + int label[], // 输入,当前点集的区域标签值数组。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现 + // 凸壳点的标志位的累加值。用来计算新添加 + // 的凸壳点的存放位置的偏移量。 + int cstcnt, // 坐标点的数量。 + int newidx[], // 输出,每个点的新的索引值数组。 + int tmplabel[] // 输出,当前点集更新后的区域标签值数组。 + ); + + // Host 成员方法:updateProperty(更新点集的属性) + // 根据已知信息,更新当前点集的区域标签和位置索引。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + updatePropertyCpu( + int leftflag[], // 输入,当前点在目前区域中新发现凸壳点的 + // 左侧的标志数组,如在左侧,则置为 1 + int leftacc[], // 输入,偏移量数组,即当前点在目前区域中 + // 新发现凸壳点的左侧的标志的累加值。 + int negdistflag[], // 输入,垂距为负的标志数组。如果当前点垂 + // 距为负,则对应的标志位为 1。 + int negdistacc[], // 输入,垂距为正的标志的累加值数组。 + int startidx[], // 输入,目前已知的凸壳上的点的位置索引数 + // 组,也相当于当前每段上的起始位置的索引 + // 数组。 + int label[], // 输入,当前点集的区域标签值数组。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现 + // 凸壳点的标志位的累加值。用来计算新添加 + // 的凸壳点的存放位置的偏移量。 + int cstcnt, // 坐标点的数量。 + int newidx[], // 输出,每个点的新的索引值数组。 + int tmplabel[] // 输出,当前点集更新后的区域标签值数组。 + ); + + // Host 成员方法:arrangeCst(生成下一轮迭代的坐标点集) + // 根据所求出的新下标,生成下一轮迭代中的新的坐标点集。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + arrangeCst( + CoordiSet *cst, // 输入点集。 + int negdistflag[], // 输入,垂距为负的标志数组。 如果当前点垂距为 + // 负,则对应的标志位为1。 + int newidx[], // 输入,每个点的新的索引值数组。 + int tmplabel[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 坐标点数量。 + CoordiSet *newcst, // 输出,更新元素位置后的新点集。 + int newlabel[] // 输出,当前点集更新后的区域标签值数组。 + ); + + // Host 成员方法:arrangeCstCpu(生成下一轮迭代的坐标点集) + // 根据所求出的新下标,生成下一轮迭代中的新的坐标点集。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + arrangeCstCpu( + CoordiSet *cst, // 输入点集。 + int negdistflag[], // 输入,垂距为负的标志数组。 如果当前点垂距为 + // 负,则对应的标志位为1。 + int newidx[], // 输入,每个点的新的索引值数组。 + int tmplabel[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 坐标点数量。 + CoordiSet *newcst, // 输出,更新元素位置后的新点集。 + int newlabel[] // 输出,当前点集更新后的区域标签值数组。 + ); + + // Host 成员方法:flipWholeCst(整体翻转坐标点集) + // 将坐标点集由第一象限翻转到第四象限,原来 (x, y) 坐标反转后为 (-x, -y)。 + // 该步骤用来求解上半凸壳,因为翻转后的点集的下半凸壳恰好是源点集的下半凸壳 + // 的相反数。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执 + // 行,返回 NO_ERROR。 + flipWholeCst( + CoordiSet *incst, // 输入坐标点集,该坐标点集为只读点集 + CoordiSet *outcst // 输出坐标点集,该坐标点集可以和输入坐标点集相 + // 同,可进行 In-place 操作。 + ); + + // Host 成员方法:flipWholeCstCpu(整体翻转坐标点集) + // 将坐标点集由第一象限翻转到第四象限,原来 (x, y) 坐标反转后为 (-x, -y)。 + // 该步骤用来求解上半凸壳,因为翻转后的点集的下半凸壳恰好是源点集的下半凸壳 + // 的相反数。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执 + // 行,返回 NO_ERROR。 + flipWholeCstCpu( + CoordiSet *incst, // 输入坐标点集,该坐标点集为只读点集 + CoordiSet *outcst // 输出坐标点集,该坐标点集可以和输入坐标点集相 + // 同,可进行 In-place 操作。 + ); + + // Host 成员方法:convexHullIter(迭代法求凸壳点) + // 采用 Quick Hull 迭代的方法,输出给定点集的上半或下半凸壳点。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + convexHullIter( + CoordiSet *inputcst, // 输入点集。 + CoordiSet *convexcst, // 凸壳点集。 + bool lowerconvex = true // 开关变量,用来确定是求解下半凸壳 + // (true)还是上半凸壳(false) + ); + + // Host 成员方法:convexHullIterCpu(迭代法求凸壳点) + // 采用 Quick Hull 迭代的方法,输出给定点集的上半或下半凸壳点。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + convexHullIterCpu( + CoordiSet *inputcst, // 输入点集。 + CoordiSet *convexcst, // 凸壳点集。 + bool lowerconvex = true // 开关变量,用来确定是求解下半凸壳 + // (true)还是上半凸壳(false) + ); + + // Host 成员方法:joinConvex(合并凸壳点) + // 将通过迭代求得的两个凸壳点集(下半凸壳点集和上半凸壳点集)合并成一个完整 + // 的凸壳点集。合并过程中两侧若有重复点需要去掉。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + joinConvex( + CoordiSet *lconvex, // 下半凸壳 + CoordiSet *uconvex, // 上半凸壳 + CoordiSet *convex // 整合后的凸壳 + ); + + // Host 成员方法:joinConvexCpu(合并凸壳点) + // 将通过迭代求得的两个凸壳点集(下半凸壳点集和上半凸壳点集)合并成一个完整 + // 的凸壳点集。合并过程中两侧若有重复点需要去掉。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + joinConvexCpu( + CoordiSet *lconvex, // 下半凸壳 + CoordiSet *uconvex, // 上半凸壳 + CoordiSet *convex // 整合后的凸壳 + ); + +public: + + // 构造函数:ConvexHull + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + ConvexHull() + { + // 配置扫描器。 + this->aryScan.setScanType(NAIVE_SCAN); + + // 使用默认值为类的各个成员变量赋值。 + this->imgCvt.clearAllConvertFlags(); // 清除所有的转换标志位 + this->value = 128; // 设置转换阈值。 + this->imgCvt.setConvertFlags(this->value, 255); // 置位某范围 + // 内的转换标志位。 + } + + // 成员方法:getValue(获取图像转换点集的像素阈值) + // 获取图像转换点集的像素阈值。 + __host__ __device__ unsigned char // 返回值:图像转换点集的像素阈值。 + getValue() const + { + // 返回图像转换点集的像素阈值。 + return this->value; + } + + // 成员方法:setValue(设置图像转换点集的像素阈值) + // 设置图像转换点集的像素阈值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setValue( + unsigned char value // 设定新的图像转换点集的像素阈值。 + ) { + // 根据阈值设置转换标志位。 + if (value < this->value) + this->imgCvt.setConvertFlags(value, this->value - 1); + else if (value > this->value) + this->imgCvt.clearConvertFlags(this->value, value - 1); + + // 将图像转换点集的像素阈值赋成新值。 + this->value = value; + + return NO_ERROR; + } + + // Host 成员方法:convexHull(求一个点集对应的凸壳点集) + // 求出一个点集所对应的凸壳点集,所求得的凸壳点集从左下角点其,按逆时针顺序 + // 排列。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + convexHull( + CoordiSet *inputcst, // 输入点集 + CoordiSet *convex // 凸壳点集 + ); + + // Host 成员方法:convexHullCpu(求一个点集对应的凸壳点集) + // 求出一个点集所对应的凸壳点集,所求得的凸壳点集从左下角点其,按逆时针顺序 + // 排列。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + convexHullCpu( + CoordiSet *inputcst, // 输入点集 + CoordiSet *convex // 凸壳点集 + ); + + // Host 成员方法:convexHull(求图像中阈值给定的对象对应的凸壳点集) + // 根据给定的阈值在图像中找出对象,所求得的凸壳点集从左下角点起,按逆时针 + // 顺序排列。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + convexHull( + Image *inimg, // 输入图像。 + CoordiSet *convex // 凸壳点集 + ); + + // Host 成员方法:convexHullCpu(求图像中阈值给定的对象对应的凸壳点集) + // 根据给定的阈值在图像中找出对象,所求得的凸壳点集从左下角点起,按逆时针 + // 顺序排列。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + // 执行,返回 NO_ERROR。 + convexHullCpu( + Image *inimg, // 输入图像。 + CoordiSet *convex // 凸壳点集 + ); +}; + +#endif + diff --git a/okano_3_0/CovarianceMatrix.cu b/okano_3_0/CovarianceMatrix.cu new file mode 100644 index 0000000..9f68e32 --- /dev/null +++ b/okano_3_0/CovarianceMatrix.cu @@ -0,0 +1,310 @@ +#include +#include "CovarianceMatrix.h" +#include "ErrorCode.h" +using namespace std; +#define DIMENSION 3 + +// 函数:_calAveMatrix(求输入样本矩阵的每一维的平均值) +// 求协方差矩阵的辅助函数,根据输入的样本矩阵(inputsample),求出每一维 +// (即每一列)的平均值并将其存储在 avematrix 一维数组中,长度为输入矩阵宽度。 +// 注意 inputsample 是以一维方式存储,函数中会根据用户输入的矩阵宽度和高度 +// 将其转换为二维,不需用户考虑。 +// avematrix 需要用户先开辟空间再传入该函数,长度为输入矩阵宽度。 +static __host__ int //返回值:函数是否正确运行, + //若函数正确运行,返回NO_ERROR。 +_calAveMatrix( + MyType *inputsample, // 输入样本矩阵(以一维方式存储) + float *avematrix, // 输出每一维的平均值数组 + int dimension, // 样本维度(矩阵宽度) + int samplenum // 样本数量(矩阵高度) +); + +// 函数:_cofactor(求输入矩阵的指定位置的元素的余子式) +// 求逆矩阵的辅助函数,在输入矩阵(inputmatrix)中删除目标位置所在的行和列,所 +// 得矩阵即为该输入矩阵目标位置的余子式,余子式存储在 cofactor 中,余子式宽度 +// 和高度各为输入矩阵的宽度和高度减一。注意 inputmatrix 和 cofactor 以一维方式 +// 存储,函数中会根据用户输入的矩阵宽度和高度,将其转换为二维,不需用户考虑。 +// cofactor 需要用户先开辟空间再传入该函数,宽度和高度各为输入矩阵的 +// 宽度和高度减一。 +static __host__ int //返回值:函数是否正确运行, + //若函数正确运行,返回NO_ERROR。 +_cofactor( + MyType *inputmatrix, // 输入矩阵(以一维方式存储) + MyType *cofactor, // 输出指定元素的余子式 + int width, // 矩阵宽度 + int height, // 矩阵高度 + int target_x, // 目标位置横坐标 + int target_y // 目标位置纵坐标 +); + +// 函数:_determinant(求得输入矩阵的行列式) +// 求逆矩阵的辅助函数,将输入矩阵(inputmatrix)以递归方式按第一行展开,直到将 +// 矩阵降为2阶,从而算出矩阵行列式。注意 inputmatrix 以一维方式存储,函数中会 +// 根据用户输入的矩阵宽度和高度,将其转换为二维,不需用户考虑。 +static __host__ float //行列式的值 +_determinant( + MyType *inputmatrix, // 输入矩阵(以一维方式存储) + int width, // 矩阵宽度 + int height // 矩阵高度 +); + +// 函数:_transpose(将给定矩阵的转置,只针对float类型的矩阵) +// 求逆矩阵的辅助函数,将输入矩阵(inputmatrix)沿主对角线交换,所得矩阵即为 +// 输入矩阵的转置矩阵,转置矩阵保存在输入矩阵空间中。注意 inputmatrix 以一维 +// 方式存储,函数中会根据用户输入的矩阵宽度和高度,将其转换为二维,不需用户 +// 考虑。 +static __host__ int //返回值:函数是否正确运行, + //若函数正确运行,返回NO_ERROR。 +_transpose( + float *inputmatrix, // 输入矩阵(以一维方式存储) + int width, // 矩阵宽度 + int height // 矩阵高度 +); + +// 函数:_calAdjointMatrix(求给定矩阵的伴随矩阵) +// 求逆矩阵的辅助函数,求出输入矩阵(inputmatrix)中每一个元素的代数余子式, +// 代数余子式为该元素对应的余子式的行列式和对应符号位的乘积,这些代数余子式 +// 组成的矩阵即为伴随矩阵,伴随矩阵保存在 adjointmatrix 中,伴随矩阵宽度和高度 +// 和输入矩阵一致。注意 inputmatrix 和 adjointmatrix 以一维方式存储,函数中 +// 会根据用户输入的矩阵宽度和高度,将其转换为二维,不需用户考虑。 +// adjointmatrix 需要用户先开辟空间再传入该函数,宽度和高度和输入矩阵一致。 +static __host__ int //返回值:函数是否正确运行, + //若函数正确运行,返回NO_ERROR。 +_calAdjointMatrix( + MyType *inputmatrix, // 输入矩阵(以一维方式存储) + float *adjointmatrix, // 输出伴随矩阵 + int width, // 矩阵宽度 + int height // 矩阵高度 +); + +// 函数:_calAveMatrix(求输入样本矩阵的每一维的平均值) +static __host__ int _calAveMatrix(MyType *inputsample, float *avematrix, + int dimension, int samplenum) +{ + //判断输入指针是否有效 + if (inputsample == NULL || avematrix == NULL) + return NULL_POINTER; + //判断输入是否有效 + if (dimension <= 0 || samplenum <= 0) + return INVALID_DATA; + //局部变量 i,j,用于循环 + int i, j; + //循环算出每一维的平均值 + for (i = 0; i < dimension; i++) { + //局部变量sum,用于保存第i维中样本之和 + float sum = 0.0; + //循环对第i维样本求和 + for(j = 0; j < samplenum; j++) + sum += inputsample[j * dimension + i]; + //求第i维的平均值 + avematrix[i] = sum / samplenum; + } + return NO_ERROR; +} + +// 函数:calCovMatrix(求给定矩阵的协方差矩阵) +__host__ int calCovMatrix(MyType *inputsample, float *covmatrix, + int dimension, int samplenum) +{ + //判断输入指针是否有效 + if (inputsample == NULL || covmatrix == NULL) + return NULL_POINTER; + //判断输入是否有效 + if (dimension <= 0 || samplenum <= 0) + return INVALID_DATA; + //局部变量,用于保存错误码 + int errcode; + //局部变量 i,j,k,用于循环 + int i, j, k; + //为每一维的平均值申请空间,保存在以维度为大小的数组中 + float *avematrix = new float[dimension]; + //调用 _calAveMatrix 求得 avematrix + errcode = _calAveMatrix(inputsample, avematrix, + dimension, samplenum); + //如果没错,则继续执行 + if (errcode == NO_ERROR) { + //循环算出协方差矩阵 + for (i = 0; i < dimension; i++) { + for (j = 0; j < dimension; j++) { + //局部变量sum + float sum = 0.0; + for (k = 0; k < samplenum; k++) { + sum += (inputsample[k * dimension + j] - avematrix[j]) * + (inputsample[k * dimension + i] - avematrix[i]); + } + //根据公式算出协方差 + covmatrix[j * dimension + i] = sum / (samplenum - 1); + } + } + } + //释放 avematrix + delete avematrix; + return errcode; +} + +// 函数:_cofactor(求输入矩阵的指定位置的元素的余子式) +static __host__ int _cofactor(MyType *inputmatrix, MyType *cofactor, int width, + int height, int target_x, int target_y) +{ + //判断输入指针是否有效 + if (inputmatrix == NULL || cofactor == NULL) + return NULL_POINTER; + //判断输入是否有效 + if (width <= 0 || height <= 0) + return INVALID_DATA; + //判断输入位置是否有效 + if (target_x >= width || target_y >= height) + return INVALID_DATA; + //局部变量 p_cofactor,用于遍历cofactor数组 + MyType *p_cofactor=cofactor; + //局部变量 i,j,用于循环 + int i, j; + //循环求得余子式 + for (j = 0; j < height; j++) { + if (j != target_y) { + for (i = 0; i < width; i++) { + if (i != target_x) { + *p_cofactor = inputmatrix[j * width + i]; + p_cofactor++; + } + } + } + } + return NO_ERROR; +} + +// 函数:_determinant(求得输入矩阵的行列式) +static __host__ float _determinant(MyType *inputmatrix, int width, int height) +{ + //判断是否为方阵 + if (width != height) + return 0.0; //处理不太好 + //判断矩阵大小是否合法 + if (width <= 0) + return 0.0; //处理不太好 + //如果矩阵大小为1,则矩阵行列式为矩阵元素,直接返回矩阵元素 + else if (width == 1) + return *inputmatrix; + //如果矩阵大小为2,则矩阵行列式为主对角线乘积减去副对角线乘积 + else if (width == 2) + return (inputmatrix[0] * inputmatrix[3] - + inputmatrix[1] * inputmatrix[2]); + //如果矩阵大小大于2,则将矩阵按第一行展开,递归实现 + else { + //为余子式申请空间 + MyType *cofactor = new MyType[(width - 1) * (height - 1)]; + //局部变量, + float sum = 0.0; + //局部变量 mark,符号标志位 + int mark = 1; + //循环将第一行的每一个元素和它的代数余子式的乘积累加 + for (int i = 0; i < width; i++) { + _cofactor(inputmatrix, cofactor, width, height, i, 0); + sum += mark * inputmatrix[0 * width+i] * + _determinant(cofactor, width - 1, height - 1); + //符号位反转 + mark *= -1; + } + //释放 cofactor + delete cofactor; + return sum; + } +} + +// 函数:_transpose(将给定矩阵的转置,只针对float类型的矩阵) +static __host__ int _transpose(float *inputmatrix, int width, int height) +{ + //判断输入指针是否有效 + if (inputmatrix == NULL) + return NULL_POINTER; + //判断矩阵是否为方阵和判断输入是否有效 + if (width != height || width <= 0 || height <= 0) + return INVALID_DATA; + //局部变量 temp,用于数据交换时保存临时数据 + float temp; + //局部变量 i,j,用于循环 + int i, j; + //矩阵转置本质将元素按对角线交换 + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + //只对右上角矩阵(不含对角线)处理 + if (i > j) { + temp = inputmatrix[j * width + i]; + inputmatrix[j * width + i] = inputmatrix[i * width + j]; + inputmatrix[i * width + j] = temp; + } + } + } + return NO_ERROR; +} + +// 函数:_calAdjointMatrix(求给定矩阵的伴随矩阵) +static __host__ int _calAdjointMatrix(MyType *inputmatrix, float *adjointmatrix, + int width, int height) +{ + //判断输入指针是否有效 + if (inputmatrix == NULL || adjointmatrix == NULL) + return NULL_POINTER; + //判断是否为方阵和判断输入是否有效 + if (width != height || width <= 0 || height <= 0) + return INVALID_DATA; + //局部变量 i,j,用于循环 + int i, j; + //局部变量 mask,用于符号标志位 + int mask = 1; + //为余子式申请空间 + MyType *cofactor = new MyType[(width - 1) * (height - 1)]; + //计算矩阵每一个元素的代数余子式 + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + _cofactor(inputmatrix, cofactor, width, height, i, j); + //如果(i+j)是偶数,mask 置1,否则置-1 + mask = ((i + j) % 2 == 0) ? 1 : -1; + adjointmatrix[j * width + i] = mask * _determinant(cofactor, + width - 1, height - 1); + } + } + //释放 cofactor + delete cofactor; + //将矩阵转置 + _transpose(adjointmatrix, width, height); + return NO_ERROR; +} + +// 函数:calInverseMatrix(求给定矩阵的逆矩阵) +__host__ int calInverseMatrix(MyType *inputmatrix, float *inversematrix, + int width, int height) +{ + //判断输入指针是否有效 + if (inputmatrix == NULL || inversematrix == NULL) + return NULL_POINTER; + //判断是否为方阵和判断输入是否有效 + if (width != height || width <= 0 || height <= 0) + return INVALID_DATA; + //局部变量,用于保存错误码 + int errcode; + //为伴随矩阵申请空间 + float* adjointmatrix = new float[width * height]; + //局部变量i,j,用于循环 + int i, j; + //求矩阵行列式 + float det = _determinant(inputmatrix, width, height); + //如果行列式为0,则没有逆矩阵 + if (det == 0) + return INVALID_DATA; + //求输入矩阵对应的伴随矩阵 + errcode = _calAdjointMatrix(inputmatrix, adjointmatrix, width, height); + if (errcode == NO_ERROR) { + //将伴随矩阵中的每一个元素除以行列式 + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + inversematrix[j * width + i] = adjointmatrix[j * width + i] / + det; + } + } + } + //释放adjintmatrix + delete adjointmatrix; + return errcode; +} + diff --git a/okano_3_0/CovarianceMatrix.h b/okano_3_0/CovarianceMatrix.h new file mode 100644 index 0000000..29083c9 --- /dev/null +++ b/okano_3_0/CovarianceMatrix.h @@ -0,0 +1,46 @@ +// CovarianceMatrix.h +// 创建人:丁燎原 +// +// 协方差矩阵和逆矩阵 +// 功能:定义了两个函数接口,分别可求矩阵的协方差矩阵和逆矩阵 +// (矩阵以一维方式输入) +// +// 修订历史: +// 2014年9月5日(丁燎原) +// 初始版本 + +#ifndef __COVARIANCEMATRIX_H__ +#define __COVARIANCEMATRIX_H__ + +// 自定义类型,方便更换类型 +typedef float MyType; + +// 函数:calCovMatrix(求给定样本矩阵的协方差矩阵) +// 根据输入样本矩阵(inputsample),通过协方差公式算出协方差矩阵,协方差矩阵保 +// 存在 covmatrix 中。注意 inputmatrix 和 covmatrix 以一维方式存储,函数中 +// 会根据用户输入的矩阵宽度和高度,将其转换为二维,不需用户考虑。 +// covmatrix 需要用户先开辟空间再传入该函数,宽度和高度都为样本维数。 +__host__ int //返回值:函数是否正确运行, + //若函数正确运行,返回NO_ERROR。 +calCovMatrix( + MyType *inputsample, //输入样本矩阵(以一维方式存储) + float *covmatrix, //输出协方差矩阵(以一维方式存储) + int dimension, //样本维数(矩阵宽度) + int samplenum //样本数量(矩阵高度) +); + +// 函数:calInverseMatrix(求给定矩阵的逆矩阵) +// 根据输入矩阵(inputmatrix),通过伴随矩阵法算出矩阵逆矩阵,逆矩阵保存在 +// inversematrix 中。注意 inputmatrix 和 inversematrix 以一维方式存储,函数中会 +// 根据用户输入的矩阵宽度和高度,将其转换为二维,不需用户考虑。 +// inversematrix 需要用户先开辟空间再传入该函数,宽度和高度和输入矩阵一致。 +__host__ int //返回值:函数是否正确运行, + //若函数正确运行,返回NO_ERROR。 +calInverseMatrix( + MyType *inputmatrix, //输入矩阵(以一维方式存储) + float *inversematrix, //输出逆矩阵(以一维方式存储) + int width, //矩阵宽度 + int height); //矩阵高度 + +#endif + \ No newline at end of file diff --git a/okano_3_0/CurveConverter.cu b/okano_3_0/CurveConverter.cu new file mode 100644 index 0000000..d91b179 --- /dev/null +++ b/okano_3_0/CurveConverter.cu @@ -0,0 +1,231 @@ +// CurveConverter.h +// 创建人:曹建立 +// +// 结构体 curve 和 Image 之间相互转换(CurveConverter) + +#include "CurveConverter.h" +#include + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLOCK_1D +// 定义一维线程块尺寸。 +#define DEF_BLOCK_1D 512 + +// --------------------------内核方法实现------------------------------------- +// Kernel 函数:_initiateImgKer(实现将图像初始化为 lowpixel 算法) +static __global__ void _initiateImgKer(ImageCuda inimg, unsigned char lowpixel) +{ + // 计算线程对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点 + // 的坐标的 x 和 y 分量(其中,dstc 表示 column;dstr 表示 row)。由于我们 + // 采用了并行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一 + // 列的相邻 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int index = dstr * inimg.pitchBytes + dstc; + + // 将目标点赋值为 lowpixel。 + inimg.imgMeta.imgData[index] = lowpixel; + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++dstr >= inimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + index += inimg.pitchBytes; + + // 将目标点赋值为 lowpixel。 + inimg.imgMeta.imgData[index] = lowpixel; + } +} + + +// Kernel 函数:_Cur2ImgKer(实现将坐标集转化为图像算法) +static __global__ void _curve2ImgKer(int length, + int * curveDatadev, + ImageCuda inimg, + unsigned char higpixel) +{ + // index 表示线程处理的 Curve 中一个像素点的坐标。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查坐标点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (index >= length) + return; + + // 获得目标点在图像中的对应位置。y*w+x + int imgpos = curveDatadev[index*2+1] * inimg.pitchBytes + +curveDatadev[index*2]; + + // 将坐标集中坐标在图像中对应的像素点的像素值置为 higpixel。 + inimg.imgMeta.imgData[imgpos] = higpixel; +} +// --------------------------成员方法实现------------------------------------- + +#define FREE_MEMORY if (curveDatadev != NULL) cudaFree(curveDatadev); + +// 成员方法:Curve2Img(把 Curve 中的点集合绘制到指定图像上) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + CurveConverter::curve2Img( + Curve *curv, // 输入的结构体 + Image *outimg // 输出的图像,需要事先分配好空间 + ){ + // 局部变量,错误码。 + int errcode; + + // 检查输入坐标集,输出图像是否为空。 + if (curv == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输出图像拷贝到 device 端。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 初始化输入图像内所有的像素点的的像素值为 lowpixel,为转化做准备。 + _initiateImgKer<<>>(outsubimgCud, bkcolor); + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。调用一维核函数, + // 在这里设置线程块内的线程数为 512,用 DEF_BLOCK_1D 表示。 + // 每个线程处理 curv 中一个点 + size_t blocksize1, gridsize1; + blocksize1 = DEF_BLOCK_1D; + gridsize1 = (curv->curveLength + blocksize1 - 1) / blocksize1; + + // 把需要使用的 curv 中的部分数据拷贝到 device 内存中 + + int *curveDatadev; + + cudaError_t cuerrcode; + + + // 计算字节数 + int datasize=(curv->curveLength*2)*sizeof (int); + + // 申请空间。 + cuerrcode = cudaMalloc((void **)&curveDatadev, datasize); + if (cuerrcode != cudaSuccess) { + return CUDA_ERROR; + } + + cuerrcode = cudaMemcpy(curveDatadev, curv->crvData, + datasize, + cudaMemcpyHostToDevice); + if (cudaGetLastError() != cudaSuccess) { + FREE_MEMORY + return CUDA_ERROR; + } + + // 将输入坐标集转化为输入图像图像,即将坐标集内点映射在图像上点的 + // 像素值置为 highpixel。 + _curve2ImgKer<<>>(curv->curveLength, + curveDatadev, + outsubimgCud, + bordercolor); + if (cudaGetLastError() != cudaSuccess){ + FREE_MEMORY + return CUDA_ERROR; + } + // 处理完毕,退出。 + FREE_MEMORY + return NO_ERROR; +} + +#undef FREE_MEMORY + +// 成员方法:Img2Curve(把图像上的点保存到 Curve 结构体中) +// 只对curve的minX,minY,maxX,maxY,curveCoordiX,curveCoordiY,length域赋值。 +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + CurveConverter::img2Curve( + Image *img, // 输入的图像 + Curve *cur // 输出的结构体 + ){ + ImageBasicOp::copyToHost(img); + if(img==NULL || cur==NULL) + return 1; + int w,h; + w=img->width; + h=img->height; + int imgsize=w*h; + // 每个点(x,y)占用两个整数存放,按最大容量分配空间 + int *curData=new int[2*imgsize]; + + // 把图像前景点放入临时数组中 + int count=0; + // 最值初始化,最小值初始化为最大,最大值初始化为最小。 + cur->minCordiX=w; cur->maxCordiX=0; + cur->minCordiY=h; cur->maxCordiY=0; + for(int j=0;jimgData[j*w+i]; + if(curpix==bordercolor ){ + curData[count]=i; + count++; + curData[count]=j; + count++; + // 记录最值 + if(iminCordiX)cur->minCordiX=i; + if(i>cur->maxCordiX)cur->maxCordiX=i; + if(jminCordiY)cur->minCordiY=j; + if(j>cur->maxCordiY)cur->maxCordiY=j; + } + } + + // 给curve的长度变量、数据数组分配空间并赋值 + cur->curveLength=count/2;//count表示整数的个数,长度应为count的一半 + cur->crvData=new int[count]; + memcpy(cur->crvData,curData,count*sizeof(int)); + + delete[] curData; + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] img to coor " << runTime << " ms" << endl; + #endif + + return NO_ERROR; +} + + + diff --git a/okano_3_0/CurveConverter.h b/okano_3_0/CurveConverter.h new file mode 100644 index 0000000..a59eae2 --- /dev/null +++ b/okano_3_0/CurveConverter.h @@ -0,0 +1,44 @@ +// CurveConverter.h +// 创建人:曹建立 +// +// 结构体 curve 和 Image 之间相互转换(CurveConverter) + +// 修订历史 +// 2013年9月20日(曹建立) +// 初始版本 +#ifndef __CURVECONVERTER_H__ +#define __CURVECONVERTER_H__ + +#include "Image.h" +#include "Curve.h" +#include "ErrorCode.h" + + + +class CurveConverter +{ +public: + int bordercolor; + int bkcolor; + CurveConverter(int border,int bk){ + bordercolor=border; + bkcolor=bk; + } + // 成员方法:Curve2Img(把 Curve 中的点集合绘制到指定图像上) + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + curve2Img( + Curve *curve, // 输入的结构体 + Image *img // 输出的图像 + ); + + // 成员方法:Img2Curve(把图像上的点保存到 Curve 结构体中) + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + img2Curve( + Image *img, // 输入的图像 + Curve *curve // 输出的结构体 + ); +}; + +#endif diff --git a/okano_3_0/CurveFluctuPropers.cu b/okano_3_0/CurveFluctuPropers.cu new file mode 100644 index 0000000..19026f8 --- /dev/null +++ b/okano_3_0/CurveFluctuPropers.cu @@ -0,0 +1,280 @@ +// CurveFluctuPropers.cu +// 创建人:邱孝兵 + +#include "CurveFluctuPropers.h" +#include "ErrorCode.h" + + +// 宏 DELETE_CURVEFLUCTUPROPERS_HOST(删除 Host 端曲线波动特征) +// 根据给定的 CurveFluctuPropers 分别清除它在主机端包含的五个指针空间 +// 所占用的内存 +#define DELETE_CURVEFLUCTUPROPERS_HOST(cfp) do { \ + if (((CurveFluctuPropers *)(cfp))->maxFluctu != NULL) \ + delete [] ((CurveFluctuPropers *)(cfp))->maxFluctu; \ + if (((CurveFluctuPropers *)(cfp))->maxFluctuX != NULL) \ + delete [] ((CurveFluctuPropers *)(cfp))->maxFluctuX; \ + if (((CurveFluctuPropers *)(cfp))->maxFluctuY != NULL) \ + delete [] ((CurveFluctuPropers *)(cfp))->maxFluctuY; \ +} while (0) + +// 宏 DELETE_CURVEFLUCTUPROPERS_DEVICE(删除 Device 端曲线波动特征) +// 根据给定的 CurveFluctuPropers 分别清除它在设备端包含的五个指针空间 +// 所占用的内存 +#define DELETE_CURVEFLUCTUPROPERS_DEVICE(cfp) do { \ + if (((CurveFluctuPropers *)(cfp))->maxFluctu != NULL) \ + cudaFree(((CurveFluctuPropers *)(cfp))->maxFluctu); \ + if (((CurveFluctuPropers *)(cfp))->maxFluctuX != NULL) \ + cudaFree(((CurveFluctuPropers *)(cfp))->maxFluctuX); \ + if (((CurveFluctuPropers *)(cfp))->maxFluctuY != NULL) \ + cudaFree(((CurveFluctuPropers *)(cfp))->maxFluctuY); \ +} while (0) + + +// Host 静态方法:makeAtHost(在 Host 内存中构件数据) +__host__ int CurveFluctuPropersBasicOp::makeAtHost(CurveFluctuPropers + *cfp, int maxFluctuNum) +{ + // 检查输入波动特征是否为 NULL + if (cfp == NULL) + return NULL_POINTER; + + // 检查参数的合法性 + if (maxFluctuNum < 1) + return INVALID_DATA; + + // 为目标曲线波动特征申请 Host 内存空间 + cfp->maxFluctu = new int[maxFluctuNum]; + cfp->maxFluctuX = new int[maxFluctuNum]; + cfp->maxFluctuY = new int[maxFluctuNum]; + + // 对于波动特征的元数据进行赋值 + cfp->maxFluctuNum = maxFluctuNum; + + // 处理完毕,返回 + return NO_ERROR; +} + +// Host 静态方法:makeAtCurrentDevice(在当前 Device 内存中构件数据) +__host__ int CurveFluctuPropersBasicOp::makeAtCurrentDevice(CurveFluctuPropers + *cfp, int maxFluctuNum) +{ + // 检查输入波动特征是否为 NULL + if (cfp == NULL) + return NULL_POINTER; + + // 检查参数的合法性 + if (maxFluctuNum < 1) + return INVALID_DATA; + + cudaError_t cuerrcode; // CUDA Error Code + + // 在当前的 Device 上申请存储 maxFluctu 的空间 + cuerrcode = cudaMalloc((void **)&(cfp->maxFluctu), + maxFluctuNum * sizeof(int)); + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(cfp); + return CUDA_ERROR; + } + + // 在当前的 Device 上申请存储 maxFluctuX 的空间 + cuerrcode = cudaMalloc((void **)&(cfp->maxFluctuX), + maxFluctuNum * sizeof(int)); + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(cfp); + return CUDA_ERROR; + } + + // 在当前的 Device 上申请存储 maxFluctuY 的空间 + cuerrcode = cudaMalloc((void **)&(cfp->maxFluctuY), + maxFluctuNum * sizeof(int)); + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(cfp); + return CUDA_ERROR; + } + + // 对于波动特征的元数据进行赋值 + cfp->maxFluctuNum = maxFluctuNum; + + // 返回 + return NO_ERROR; +} + +// Host 静态方法:copyToHost(将当前 Device 内存中数据拷贝到 Host 端) +__host__ int CurveFluctuPropersBasicOp::copyToHost(CurveFluctuPropers *srccfp, + CurveFluctuPropers *dstcfp) +{ + // 检查输入参数是否为 NULL + if (srccfp == NULL || dstcfp == NULL) + return NULL_POINTER; + + // 如果源曲线特征为空,则不进行任何操作,直接报错 + if (srccfp->maxFluctuNum == 0) + return INVALID_DATA; + + // 将目标曲线特征单值数据设为源曲线特征的单值数据 + dstcfp->smNieghbors = srccfp->smNieghbors; + dstcfp->maxFluctuNum = srccfp->maxFluctuNum; + dstcfp->aveFluctu = srccfp->aveFluctu; + dstcfp->xyAveFluctu = srccfp->xyAveFluctu; + + // 为目标曲线波动特征申请 Host 内存空间 + dstcfp->maxFluctu = new int[dstcfp->maxFluctuNum]; + dstcfp->maxFluctuX = new int[dstcfp->maxFluctuNum]; + dstcfp->maxFluctuY = new int[dstcfp->maxFluctuNum]; + + cudaError_t cuerrcode; // CUDA 调用返回的错误码。 + + // 拷贝 maxFluctu + cuerrcode = cudaMemcpy(dstcfp->maxFluctu, srccfp->maxFluctu, + srccfp->maxFluctuNum * sizeof (int), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_CURVEFLUCTUPROPERS_HOST(dstcfp); + return CUDA_ERROR; + } + + // 拷贝 maxFluctuX + cuerrcode = cudaMemcpy(dstcfp->maxFluctuX, srccfp->maxFluctuX, + srccfp->maxFluctuNum * sizeof (int), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_CURVEFLUCTUPROPERS_HOST(dstcfp); + return CUDA_ERROR; + } + + // 拷贝 maxFluctuY + cuerrcode = cudaMemcpy(dstcfp->maxFluctuY, srccfp->maxFluctuY, + srccfp->maxFluctuNum * sizeof (int), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_CURVEFLUCTUPROPERS_HOST(dstcfp); + return CUDA_ERROR; + } + + // 处理完毕退出 + return NO_ERROR; +} + +// Host 静态方法:copytToCurrentDevice 方法(将 Host 中的数据拷贝到 device 端) + __host__ int copyToCurrentDevice(CurveFluctuPropers *srccfp, + CurveFluctuPropers *dstcfp) +{ + // 检查输入参数是否为 NULL + if (srccfp == NULL || dstcfp == NULL) + return NULL_POINTER; + + // 如果源曲线特征为空,则不进行任何操作,直接报错 + if (srccfp->maxFluctuNum == 0) + return INVALID_DATA; + + // 将目标曲线特征单值数据设为源曲线特征的单值数据 + dstcfp->smNieghbors = srccfp->smNieghbors; + dstcfp->maxFluctuNum = srccfp->maxFluctuNum; + dstcfp->aveFluctu = srccfp->aveFluctu; + dstcfp->xyAveFluctu = srccfp->xyAveFluctu; + + cudaError_t cuerrcode; // CUDA ERROR CODE + + // 为 maxFluctu 申请设备端内存 + cuerrcode = cudaMalloc((void **)&(dstcfp->maxFluctu), + dstcfp->maxFluctuNum * sizeof(int)); + + // 如果申请失败返回 CUDA_ERROR + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(dstcfp); + return CUDA_ERROR; + } + + // 拷贝 maxFluctu 数据到设备端内存 + cuerrcode = cudaMemcpy(dstcfp->maxFluctu, srccfp->maxFluctu, + srccfp->maxFluctuNum * sizeof (int), + cudaMemcpyHostToDevice); + + // 如果拷贝失败返回 CUDA_ERROR + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(dstcfp); + return CUDA_ERROR; + } + + // 为 maxFluctuX 申请设备端内存 + cuerrcode = cudaMalloc((void **)&(dstcfp->maxFluctuX), + dstcfp->maxFluctuNum * sizeof(int)); + + // 如果申请失败返回 CUDA_ERROR + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(dstcfp); + return CUDA_ERROR; + } + + // 拷贝 maxFluctuX 数据到设备端内存 + cuerrcode = cudaMemcpy(dstcfp->maxFluctuX, srccfp->maxFluctuX, + srccfp->maxFluctuNum * sizeof (int), + cudaMemcpyHostToDevice); + + // 如果拷贝失败返回 CUDA_ERROR + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(dstcfp); + return CUDA_ERROR; + } + + // 为 maxFluctuY 申请设备端内存 + cuerrcode = cudaMalloc((void **)&(dstcfp->maxFluctuY), + dstcfp->maxFluctuNum * sizeof(int)); + + // 如果申请失败返回 CUDA_ERROR + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(dstcfp); + return CUDA_ERROR; + } + + // 拷贝 maxFluctuY 数据到设备端内存 + cuerrcode = cudaMemcpy(dstcfp->maxFluctuY, srccfp->maxFluctuY, + srccfp->maxFluctuNum * sizeof (int), + cudaMemcpyHostToDevice); + + // 如果拷贝失败返回 CUDA_ERROR + if (cuerrcode != cudaSuccess) { + DELETE_CURVEFLUCTUPROPERS_DEVICE(dstcfp); + return CUDA_ERROR; + } + + // 操作完毕返回 + return NO_ERROR; +} + +// Host 静态方法:deleteFromHost(销毁 Host 端曲线波动特征属性) +__host__ int deleteFromHost(CurveFluctuPropers *srccfp) +{ + // 检查输入参数是否为 NULL,如果是直接返回 + if (srccfp == NULL) + return NULL_POINTER; + + // 调用宏删除 Host 端曲线波动特征属性 + DELETE_CURVEFLUCTUPROPERS_HOST(srccfp); + + return NO_ERROR; +} + +// Host 静态方法:deleteFromDevice(销毁当前设备端曲线波动特征属性) +__host__ int deleteFromCurrentDevice(CurveFluctuPropers *srccfp ) +{ + // 检查输入参数是否为 NULL,如果是直接返回 + if (srccfp == NULL) + return NULL_POINTER; + + // 调用宏删除 Device 端曲线波动特征属性 + DELETE_CURVEFLUCTUPROPERS_DEVICE(srccfp); + + return NO_ERROR; +} + + \ No newline at end of file diff --git a/okano_3_0/CurveFluctuPropers.h b/okano_3_0/CurveFluctuPropers.h new file mode 100644 index 0000000..a3cfa47 --- /dev/null +++ b/okano_3_0/CurveFluctuPropers.h @@ -0,0 +1,104 @@ +// CurveFluctuPropers.h +// 创建人:邱孝兵 +// +// 曲线波动特征属性(CurveFluctuPropers) +// 功能说明:定义了曲线的波动特征属性,主要包含了该曲线中若干个最大偏移 +// 距离(相对于平滑曲线的偏移距离),出现这些最大偏移距离的点的横纵坐标 +// 以及这些偏移的平均值。 +// 修订历史: +// 2013年08月26日(邱孝兵) +// 初始版本 + + +#ifndef __CURVEFLUCTUPROPERS_H__ +#define __CURVEFLUCTUPROPERS_H__ + + +// 结构体:CurveFluctuPropers(曲线波动特征属性) +// 该结构体定义了曲线的波动特征属性,主要包含了该曲线中若干个最大偏移 +// 距离(相对于平滑曲线的偏移距离),出现这些最大偏移距离的点的横纵坐标 +// 以及这些偏移的平均值。 +typedef struct CurveFluctuPropers_st{ + int smNieghbors; // 外部设定参数,曲线平滑邻域大小 + int maxFluctuNum; // 外部设定参数,统计的最大偏移点的个数 + int *maxFluctu; // 统计得出的 maxFluctuNum 个最大偏移距离 + int *maxFluctuX; // maxFluctuNum 个最大偏移点的横坐标 + int *maxFluctuY; // maxFluctuNum 个最大偏移点的纵坐标 + int aveFluctu; // 偏移距离均值 + int xyAveFluctu; // 偏移坐标均值 +} CurveFluctuPropers; + + +// 类:CurveFluctuPropersBasicOp(曲线波动特征属性基本操作) +// 继承自:无 +// 该类包含了对于曲线波动特征属性的基本操作,如其的创建与销毁,在 +// 主机端和设备端的互相拷贝。要求所有的曲线波动特征属性的基本操作都要通过 +// 该类创建,否则将会导致系统运行的紊乱。 +class CurveFluctuPropersBasicOp { + +public: + + // Host 静态方法:makeAtHost(在 Host 内存中构建数据) + // 针对空向量组,在 Host 内存中为其申请一段指定的大小的空间,这段空间 + // 中的数据是未被赋值的混乱数据。 + static __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR + makeAtHost( + CurveFluctuPropers *cfp, // 曲线波动特征,要求为空 + int maxFluctuNum // 统计的最大偏移点的个数 + ); + + // Host 静态方法:makeAtCurrentDevice(在当前 Device 内存中构建数据) + // 针对空向量组,在当前 Device 内存中为其申请一段指定的大小的空间,这段空间 + // 中的数据是未被赋值的混乱数据。 + static __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR + makeAtCurrentDevice( + CurveFluctuPropers *cfp, // 曲线波动特征,要求为空 + int maxFluctuNum // 统计的最大偏移点的个数 + ); + + // Host 静态方法:copytToHost 方法(将当前 Device 内存中数据拷贝到 Host 端) + // 这是一个 Out-Place 形式的拷贝。会为 dstcfp 分配响应大小的空间,然后把 + // srccfp 中的数据拷贝到 dstcfp 中。这里只针对 CurveFluctuPropers 的数组字段 + // 进行拷贝,其他单值字段始终只做 Host 端的互相拷贝,并不涉及主机设备之间的 + // 传输 + static __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + copyToHost( + CurveFluctuPropers *srccfp, // 源波动特征,要求其中必须有数据。 + CurveFluctuPropers *dstcfp // 目标波动特征。 + ); + + // Host 静态方法:copytToCurrentDevice 方法(将 Host 中的数据拷贝到 + // device 端) + // 这是一个 Out-Place 形式的拷贝。会为 dstcfp 分配响应大小的空间,然后把 + // srccfp 中的数据拷贝到 dstcfp 中。这里只针对 CurveFluctuPropers 的数组字段 + // 进行拷贝,其他单值字段始终只做 Host 端的互相拷贝,并不涉及主机设备之间的 + // 传输 + static __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + copyToCurrentDevice( + CurveFluctuPropers *srccfp, // 源波动特征,要求其中必须有数据。 + CurveFluctuPropers *dstcfp // 目标波动特征。 + ); + + // Host 静态方法:deleteFromHost(销毁 Host 端曲线波动特征属性) + // 在 Host 端销毁一个不再使用的曲线波动特征属性。 + static __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + deleteFromHost( + CurveFluctuPropers *srccfp // 输入参数,不再需要使用的曲线波动特征。 + ); + + // Host 静态方法:deleteFromCurrentDevice(销毁当前设备端曲线波动特征属性) + // 在当前设备端销毁一个不再使用的曲线波动特征属性。 + static __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + deleteFromCurrentDevice( + CurveFluctuPropers *srccfp // 输入参数,不再需要使用的曲线波动特征。 + ); +}; + +#endif + \ No newline at end of file diff --git a/okano_3_0/CurveFluctuation.cu b/okano_3_0/CurveFluctuation.cu new file mode 100644 index 0000000..442f1d1 --- /dev/null +++ b/okano_3_0/CurveFluctuation.cu @@ -0,0 +1,185 @@ +// CurveFluctuation.cu +// 创建人:邱孝兵 + +#include "CurveFluctuation.h" +#include "ErrorCode.h" +#include +using namespace std; + +// 宏:DEF_BLOCK_1D +// 定义了默认的 1D 线程块的尺寸。 +#define DEF_BLOCK_1D 256 + +// Kernel 函数:_calcCurveFluctuKer(计算曲线波动特征) +// 首先并行计算出每个点对应平滑后的点坐标,然后计算二者的偏移距离,块内同步 +// 后再统计出偏移距离最大的若干个点,记录这些点的坐标和偏移距离,作为曲线的 +// 波动特征,同时还需要统计出曲线上每个点的平均偏移距离和偏移坐标。 +__global__ void // Kernel 函数无返回值 +_calcCurveFluctuKer( + CurveCuda_st incurve, // 输入曲线 + int smWindowSize, // 平滑邻域宽度 + int *outdxy // 每个点的偏移距离 +); + + +// Kernel 函数:_calcCurveFluctuKer(计算曲线波动特征) +__global__ void _calcCurveFluctuKer(CurveCuda_st incurve, + int smWindowSize, int *outdxy) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 判断当前线程是否超过了曲线中点的个数 + if (idx >= incurve.crvMeta.curveLength) { + return; + } + + // 定义一些寄存器变量 + int length = incurve.crvMeta.curveLength; // 曲线内点的个数 + float smx = 0.0; // 平滑后的横坐标 + float smy = 0.0; // 平滑后的纵坐标 + int count = 0; // 参与平滑的点个数 + + // 当前点邻域坐标求和 + for (int i = idx - smWindowSize; i < idx + smWindowSize; i++) { + if (i < 0 || i >= length) + continue; + smx += incurve.crvMeta.crvData[2 * i]; + smy += incurve.crvMeta.crvData[2 * i + 1]; + count++; + } + + // 利用平均值进行平滑 + if (count > 0) { + smx /= count; + smy /= count; + } + + // 计算当前点和其对应的平滑点之间的偏移距离 + float dx = incurve.crvMeta.crvData[2 * idx] - smx; + float dy = incurve.crvMeta.crvData[2 * idx + 1] - smy; + outdxy[idx] = (int)(sqrt(dx * dx + dy * dy) + 0.5); +} + + +// 宏:FREE_LOCAL_MEMORY_CALC_FLUCTU(清理局部申请的设备端或者主机端内存) +// 该宏用于清理在 calcCurveFluctu 过程中申请的设备端或者主机端内存空间。 +#define FREE_LOCAL_MEMORY_CALC_FLUCTU do { \ + if ((dxyhost) != NULL) \ + delete [] (dxyhost); \ + if ((dxydevice) != NULL) \ + cudaFree((dxydevice)); \ + } while (0) + +// Host 成员方法:calcCurveFluctu(计算曲线波动特征) +__host__ int CurveFluctuation::calcCurveFluctu(Curve *incurve, + CurveFluctuPropers *inoutcfp) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回 + if (incurve == NULL || inoutcfp == NULL) + return NULL_POINTER; + + // 检查输入和输出的数据是否合法,如果不合法直接返回报错 + if (incurve->curveLength <= 0 || inoutcfp->maxFluctuNum <= 0 || + inoutcfp->smNieghbors <= 0) { + return INVALID_DATA; + } + + // 将 Curve 拷贝到当前设备中 + CurveBasicOp::copyToCurrentDevice(incurve); + + // 根据输入 Curve 指针,获取 Curve_st 类型 + CurveCuda *cucurve = CURVE_CUDA(incurve); + + // 申请存储每个点和平滑点距离的数组 Host 内存空间 + int length = incurve->curveLength; // 曲线中点的个数 + int *dxyhost = new int[length]; + + // 申请存储每个点和平滑点距离的数组的 Device 内存空间 + int *dxydevice; + cudaError_t cuerrcode = cudaMalloc((void **)&dxydevice, + sizeof(int) * length); + + // 申请失败,返回错误 + if (cuerrcode != cudaSuccess) { + FREE_LOCAL_MEMORY_CALC_FLUCTU; + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_1D; + blocksize.y = 1; + gridsize.x = length / blocksize.x + 1; + gridsize.y = 1; + + // 调用核函数,计算每个点和其对应平滑点的距离 + _calcCurveFluctuKer<<>>(*cucurve, + inoutcfp->smNieghbors, + dxydevice); + + // 将标记值拷贝到主机端。 + cuerrcode = cudaMemcpy(dxyhost, dxydevice, length * sizeof(int), + cudaMemcpyDeviceToHost); + if (cuerrcode != NO_ERROR) { + FREE_LOCAL_MEMORY_CALC_FLUCTU; + return CUDA_ERROR; + } + + int maxnum = inoutcfp->maxFluctuNum; // 需要统计的最大距离点的个数 + int sumdxy = 0; // 所有点偏移距离的和 + + // 将 cfp 中的 maxFluctu 均初始化为 0 + for (int i = 0; i < maxnum; i++){ + inoutcfp->maxFluctu[i] = 0; + inoutcfp->maxFluctuX[i] = 0; + inoutcfp->maxFluctuY[i] = 0; + } + + // 将 Curve 拷贝到 Host 端 + CurveBasicOp::copyToHost(incurve); + + // 遍历所有距离,找出最大的 maxnum 个 + for (int i = 0; i < length; i++){ + // 将距离进行加和 + sumdxy += dxyhost[i]; + + // 比较当前距离和 maxFluctu 中最小的(第 maxnum 个) + if (dxyhost[i] > inoutcfp->maxFluctu[maxnum - 1]) { + // 将 maxFluctu 最后一个值赋为当前的距离值 + inoutcfp->maxFluctu[maxnum - 1] = dxyhost[i]; + + // 同时将对应的横纵坐标也赋值 + inoutcfp->maxFluctuX[maxnum - 1] = incurve->crvData[2 * i]; + inoutcfp->maxFluctuY[maxnum - 1] = incurve->crvData[2 * i + 1]; + + // 重新调整 maxFluctu 数组 + for (int j = maxnum - 2; j >= 0; j--){ + // 如果 j + 1 对应的 maxFluctu 值 大于 j 则交换二者的位置 + if (inoutcfp->maxFluctu[j + 1] > inoutcfp->maxFluctu[j]) { + int tmp = inoutcfp->maxFluctu[j + 1]; + int tmpx = inoutcfp->maxFluctuX[j + 1]; + int tmpy = inoutcfp->maxFluctuY[j + 1]; + inoutcfp->maxFluctu[j + 1] = inoutcfp->maxFluctu[j]; + inoutcfp->maxFluctuX[j + 1] = inoutcfp->maxFluctuX[j]; + inoutcfp->maxFluctuY[j + 1] = inoutcfp->maxFluctuY[j]; + inoutcfp->maxFluctu[j] = tmp; + inoutcfp->maxFluctuX[j] = tmpx; + inoutcfp->maxFluctuY[j] = tmpy; + } else { + break; + } + + } + } + } + // 计算偏移均值 aveFluctu + inoutcfp->aveFluctu = (int)(sumdxy / length + 0.5f); + + // 计算坐标偏移均值 xyAveFluctu + inoutcfp->xyAveFluctu = inoutcfp->aveFluctu; + + // 处理完毕返回 + return NO_ERROR; +} + \ No newline at end of file diff --git a/okano_3_0/CurveFluctuation.h b/okano_3_0/CurveFluctuation.h new file mode 100644 index 0000000..61b89a7 --- /dev/null +++ b/okano_3_0/CurveFluctuation.h @@ -0,0 +1,41 @@ +// CurveFluctuation.h +// 创建人:邱孝兵 +// +// 曲线波动计算(CurveFluctuation) +// 功能说明:对一个曲线(Curve)的坐标集,根据外部设定参数,计算其波动特征。 +// 该波动特征由偏离其平滑坐标最远的几个坐标点来确定。 +// +// 修订历史: +// 2013年08月26日(邱孝兵) +// 初始版本 + +#ifndef __CURVEFLUCTUATION_H__ +#define __CURVEFLUCTUATION_H__ + + +#include "Curve.h" +#include "CurveFluctuPropers.h" + + +// 类:CurveFluctuation +// 继承自:无 +// 对于给定的曲线(Curve)计算其波动特征,该波动特征使用曲线上偏离平滑曲线最远的 +// 的若干个点来表征,计算结果包括这若干个点的横纵坐标和偏移的距离,同时还统计出 +// 了所有点的偏移平均距离以及平均的偏移坐标等。 +class CurveFluctuation { + +public: + + // Host 成员方法:calcCurveFluctu(计算曲线波动特征) + // 算法的主函数,首先并行计算出每个点偏离平滑后的曲线上对应点的偏移距离 + // 然后统计出偏移距离最大的若干个点,记录这些点的坐标和偏移距离,作为曲线的 + // 波动特征,同时还需要统计出曲线上每个点的平均偏移距离和偏移坐标。 + __host__ int // 返回值,函数是否正确执行,若函数 + // 正确执行返回 NO_ERROR + calcCurveFluctu( + Curve *incurve, // 输入曲线 + CurveFluctuPropers *inoutcfp // 曲线波动特征属性 + ); +}; + +#endif diff --git a/okano_3_0/CurveTopology.cu b/okano_3_0/CurveTopology.cu new file mode 100644 index 0000000..8159ac6 --- /dev/null +++ b/okano_3_0/CurveTopology.cu @@ -0,0 +1,422 @@ +// CurveTopology +// 曲线间的相位关系 + +#include "CurveTopology.h" +#include "Image.h" +#include +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:IN_LABEL 和 OUT_LABEL +// 定义了曲线内的点和曲线外的点标记值 +#define IN_LABEL 255 +#define OUT_LABEL 0 + +// Kernel 函数:_setCloseAreaKer(将封闭曲线包围的内部区域的值变为白色) +// 引用邱孝兵实现的 _labelCloseAreaKer(标记封闭区域),详见 HistogramDifference +// 该核函数使用著名的射线法确定点和一个封闭曲线的位置关系,即如果由当前点引射线, +// 与曲线有奇数个交点则在内部,如果有偶数个交点,则在曲线外部( 0 属于偶数), +// 为了提高准确度,在实际实现的时候,我们使用了由当前点向上下左右四个方向引射线 +// 来确定位置关系。引用该算法实现将封闭曲线包围的内部区域的值变为白色,并且需要 +// 得到闭合曲线包围的点的个数,用于后续处理 +static __global__ void // Kernel 函数无返回值 +_setCloseAreaKer( + CurveCuda curve, // 输入曲线 + ImageCuda maskimg, // 输出标记结果 + int *count // 闭合曲线包围点的个数 +); + +// Kernel 函数:_matdotKer(得到两幅图像数据的点积) +// 该核函数实现了两幅图像的点积,两图像其实可以看做两个矩阵,这样就转化成两矩阵 +// 的点积,由于得到是 0-255 的二值图像,当对应位置灰度值都为 255 时候,结果可以 +// 认为 1 * 1,返回一个 1。最后把得到的所有 1 相加得到点积 +static __global__ void // Kernel 函数无返回值 +_matdotKer( + ImageCuda inimg1, // 标记图像1 + ImageCuda inimg2, // 标记图像2 + int *partial_sum // 点积结果 +); + +// Kernel 函数:_intersecNumtKer(得到两个曲线的交点个数) +// 输入两个曲线,得到两个曲线的交点,根据第一条曲线进行并行划分,并行得到第一条 +// 曲线的坐标点,根据这个坐标点去循环查询第二条曲线是否有相等的坐标点,最终得到 +// 两个曲线的交点个数,返回在下面的部分和中。 +static __global__ void // Kernel 函数无返回值 +_intersecNumtKer( + CurveCuda curve1, // 输入曲线1 + CurveCuda curve2, // 输入曲线2 + int *sectnum // 部分和 +); + + +// Kernel 函数:_setCloseAreaKer(将封闭曲线包围的内部区域的值变为白色) +static __global__ void _setCloseAreaKer(CurveCuda curve, ImageCuda maskimg, + int *count) +{ + // 计算当前线程的索引 + int xidx = blockIdx.x * blockDim.x + threadIdx.x; + int yidx = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断当前线程是否越过输入图像尺寸 + if (xidx >= maskimg.imgMeta.width || yidx >= maskimg.imgMeta.height) + return; + + // 定义部分寄存器变量 + int downcount = 0; // 向下引射线和曲线的交点个数 + int length = curve.crvMeta.curveLength; // 曲线上的点的个数 + int outpitch = maskimg.pitchBytes; // 输出标记图像的 pitch + + // 首先将所有点标记为曲线外的点 + maskimg.imgMeta.imgData[yidx * outpitch+ xidx] = OUT_LABEL; + + int flag = 0; // 判断是否进入切线区域 + + // 遍历曲线,统计上述各个寄存器变量的值 + for (int i = 0; i < length; i++) { + int x = curve.crvMeta.crvData[2 * i]; + int y = curve.crvMeta.crvData[2 * i + 1]; + + // 曲线中的下一个点的位置 + int j = (i + 1) % length; + int x2 = curve.crvMeta.crvData[2 * j]; + + // 曲线中上一个点的位置 + int k = (i - 1 + length) % length; + int x3 = curve.crvMeta.crvData[2 * k]; + + // 曲线上的第 i 个点与当前点在同一列上 + if (x == xidx) { + if (y == yidx) { + // 当前点在曲线上,此处把曲线上的点也作为曲线内部的点 + maskimg.imgMeta.imgData[yidx * outpitch+ xidx] = IN_LABEL; + return; + } + + // 交点在当前点的下方 + if (y > yidx) { + // 曲线上下一个也在射线上时,避免重复统计,同时设置 flag + // 标记交点行开始。如果下一个点不在射线上,通过 flag 判断到 + // 底是交点行结束还是单点相交,如果是单点相交判断是否为突出点 + // 如果是交点行结束判断是否曲线在交点行同侧,以上都不是统计值 + // 加一. + if (x2 == xidx) { + if (flag == 0) + flag = x3 - x; + } else { + if (flag == 0) { + if ((x3 - x) * (x2 - x) <= 0) + downcount++; + } else { + if (flag * (x2 - x) < 0) + downcount++; + flag = 0; + } + } + } + } + } + + // 交点数均为奇数则判定在曲线内部 + if (downcount % 2 == 1) { + maskimg.imgMeta.imgData[yidx * outpitch + xidx] = IN_LABEL; + atomicAdd(count, 1); + } +} + +// Kernel 函数:_matdotKer(得到两幅图像数据的点积) +static __global__ void _matdotKer(ImageCuda inimg1, ImageCuda inimg2, + int *partial_sum) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg1.imgMeta.width || r >= inimg1.imgMeta.height) + return; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg1.pitchBytes + c; + + // 如果图像矩阵对应位置的像素值都不为 0,则给点积加 1 处理 + if (inimg1.imgMeta.imgData[inidx] && inimg2.imgMeta.imgData[inidx]) { + atomicAdd(partial_sum, 1); + } +} + +// Kernel 函数:_intersecNumtKer(得到两个曲线的交点个数) +static __global__ void _intersecNumtKer(CurveCuda curve1, CurveCuda curve2, + int *sectnum) +{ + // index 表示线程处理的像素点的坐标。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + int length1 = curve1.crvMeta.curveLength; // 曲线1上的点的个数 + + // 检查坐标点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (index >= length1) + return; + + int length2 = curve2.crvMeta.curveLength; // 曲线2上的点的个数 + + // 得到该线程第一条曲线的坐标点数据 + int x1 = curve1.crvMeta.crvData[2 * index]; + int y1 = curve1.crvMeta.crvData[2 * index + 1]; + + int x2, y2; // 临时变量,存储第二条曲线的坐标点 + + // 循环查找第二条曲线的坐标点是否有相等的点 + for (int i = 0; i < length2; i++) { + // 得到该线程第二条曲线的坐标点数据 + x2 = curve2.crvMeta.crvData[2 * i]; + y2 = curve2.crvMeta.crvData[2 * i + 1]; + // 如果找到,则交点加 1 + if ((x1 == x2) && (y1 == y2)) { + atomicAdd(sectnum, 1); + } + } +} + +// 宏:FREE_CURVE_TOPOLOGY(清理局部申请的设备端或者主机端内存) +// 该宏用于清理在 curveTopology 过程中申请的设备端或者主机端内存空间。 +#define FREE_CURVE_TOPOLOGY do { \ + if (maskimg1 != NULL) \ + ImageBasicOp::deleteImage(maskimg1); \ + if (maskimg2 != NULL) \ + ImageBasicOp::deleteImage(maskimg2); \ + if (temp_dev != NULL) \ + cudaFree(temp_dev); \ + } while (0) + + +// Host 成员方法:curveTopology(曲线相位关系) +__host__ int CurveTopology::curveTopology(Curve *curve1, Curve *curve2, + CurveRelation *crvrelation, + int width, int height) +{ + // 判断输入曲线是否为空 + if (curve1 == NULL || curve2 == NULL) + return NULL_POINTER; + + // 检查输入参数是否有数据 + if (curve1->curveLength <= 0 || curve2->curveLength <= 0 || + width <= 0 || height <= 0) + return INVALID_DATA; + + // 检查输入曲线是否为封闭曲线,如果不是封闭曲线返回错误 + if (!curve1->closed || !curve2->closed) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + cudaError_t cuerrcode; + + // 将曲线拷贝到 Device 内存中 + errcode = CurveBasicOp::copyToCurrentDevice(curve1); + if (errcode != NO_ERROR) + return errcode; + + // 将曲线拷贝到 Device 内存中 + errcode = CurveBasicOp::copyToCurrentDevice(curve2); + if (errcode != NO_ERROR) + return errcode; + + // 获取 CurveCuda 指针 + CurveCuda *curvecud1 = CURVE_CUDA(curve1); + CurveCuda *curvecud2 = CURVE_CUDA(curve2); + + // 定义临时变量,统计两个曲线包围的点数,包括曲线上的点 + int count1; + int count2; + // 定义点积 + int result; + // 定义交点个数 + int sectnum; + + // 定义局部变量,用于多份数据的一份申请 + int *temp_dev = NULL; + + // 定义临时标记图像指针 + Image *maskimg1 = NULL; + Image *maskimg2 = NULL; + + // 给 temp_dev 在设备申请空间 + cuerrcode = cudaMalloc((void**)&temp_dev, sizeof (int) * 4); + if (cuerrcode != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 给 temp_dev 的内容初始化为 0 + cuerrcode = cudaMemset(temp_dev, 0, sizeof (int) * 4); + if (cuerrcode != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 定义设备指针,存储两个曲线包围的点数、点积和交点个数 + int *dev_count1 = temp_dev; + int *dev_count2 = dev_count1 + 1; + int *dev_sum = dev_count2 + 1; + int *dev_sectnum = dev_sum + 1; + + // 给临时标记图像1在设备申请空间 + ImageBasicOp::newImage(&maskimg1); + if (errcode != NO_ERROR) + return errcode; + errcode = ImageBasicOp::makeAtCurrentDevice(maskimg1, width, height); + if (errcode != NO_ERROR) { + FREE_CURVE_TOPOLOGY; + return errcode; + } + + // 给临时标记图像2在设备申请空间 + ImageBasicOp::newImage(&maskimg2); + if (errcode != NO_ERROR) { + FREE_CURVE_TOPOLOGY; + return errcode; + } + errcode = ImageBasicOp::makeAtCurrentDevice(maskimg2, width, height); + if (errcode != NO_ERROR) { + FREE_CURVE_TOPOLOGY; + return errcode; + } + + // 获取 ImageCuda 指针 + ImageCuda *maskimgcud1 = IMAGE_CUDA(maskimg1); + ImageCuda *maskimgcud2 = IMAGE_CUDA(maskimg2); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 调用核函数,将封闭曲线包围的内部区域的值变为白色,并且得到包围点的个数 + _setCloseAreaKer<<>>( + *curvecud1, *maskimgcud1, dev_count1); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 拷贝 dev_count1 到 Host 端 + cuerrcode = cudaMemcpy(&count1, dev_count1, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 得到第一条曲线包围的点的个数,包括曲线上的点 + count1 += curvecud1->capacity; + + // 调用核函数,将封闭曲线包围的内部区域的值变为白色,并且得到包围点的个数 + _setCloseAreaKer<<>>( + *curvecud2, *maskimgcud2, dev_count2); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 拷贝 dev_count2 到 Host 端 + cuerrcode = cudaMemcpy(&count2, dev_count2, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 得到第二条曲线包围的点的个数,包括曲线上的点 + count2 += curvecud2->capacity; + // 调用核函数,得到两幅图像矩阵的点积 + _matdotKer<<>>(*maskimgcud1, *maskimgcud2, dev_sum); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 拷贝 dev_sum 到 Host 端 + cuerrcode = cudaMemcpy(&result, dev_sum, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 如果两个图像矩阵点积为 0,则表示两个曲线没有包含、被包含和相交关系,是 + // 属于除上述三种外的其他关系,设置曲线关系为其他关系 + if (result == 0) { + crvrelation->relation = CURVE_OTHERSHIP; + crvrelation->internum = 0; + } + + // 如果两个图像矩阵点积为第一条曲线的包围点的个数,则曲线1被包含在曲线2中, + // 设置曲线关系为被包含 + else if (result == count1) { + crvrelation->relation = CURVE_INCLUDED; + crvrelation->internum = 0; + } + + // 如果两个图像矩阵点积为第二条曲线的包围点的个数,则曲线1包含在曲线2中,设 + // 置曲线关系为包含 + else if (result == count2) { + crvrelation->relation = CURVE_INCLUDE; + crvrelation->internum = 0; + } + + // 除上面三种情况外,则两个曲线相交,设置曲线相位关系为相交 + else + crvrelation->relation = CURVE_INTERSECT; + + // 如果曲线是相交关系,则开始求交点个数 + if (crvrelation->relation == CURVE_INTERSECT) { + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。这里使用一维线程块 + int blocksize1, gridsize1; + blocksize1 = DEF_BLOCK_X * DEF_BLOCK_Y; + gridsize1 = (curvecud1->capacity + blocksize1 - 1) / blocksize1; + + // 调用核函数,得到两条曲线的交点个数 + _intersecNumtKer<<>>( + *curvecud1, *curvecud2, dev_sectnum); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 拷贝 dev_sectnum 到 Host 端 + cuerrcode = cudaMemcpy(§num, dev_sectnum, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 得到曲线的交点数目 + crvrelation->internum = sectnum; + } + + // 释放临时申请的空间 + FREE_CURVE_TOPOLOGY; + + // 程序执行结束,返回 + return NO_ERROR; +} diff --git a/okano_3_0/CurveTopology.h b/okano_3_0/CurveTopology.h new file mode 100644 index 0000000..5a4283e --- /dev/null +++ b/okano_3_0/CurveTopology.h @@ -0,0 +1,80 @@ +// CurveTopology.h +// 创建者:欧阳翔 +// +// 曲线间的相位关系(CurveTopology) +// 功能说明:判断两个闭合曲线的关系,其中包括包含关系、被包含关系、相交关系和 +// 无上述三种关系的其他关系,如果相交得到交点个数。 + +// 修订历史: +// 2013年10月27日(欧阳翔) +// 初始版本,曲线间的相位关系初步实现。 +// 2013年11月10日(欧阳翔) +// 修复了设置曲线内部为白色的核函数的一处 Bug + +#ifndef __CURVETOPOLOGY_H__ +#define __CURVETOPOLOGY_H__ + +#include "Curve.h" +#include "ErrorCode.h" + +// 宏:CURVE_INCLUDE +// 表示包含关系 +#define CURVE_INCLUDE 0 + +// 宏:CURVE_INCLUDE +// 表示被包含关系 +#define CURVE_INCLUDED 1 + +// 宏:CURVE_INTERSECT +// 表示相交关系 +#define CURVE_INTERSECT 2 + +// 宏:CURVE_OTHERSHIP +// 表示除上述三种外的其他关系 +#define CURVE_OTHERSHIP 3 + + +// 结构体:CurveRelation(曲线间的相位关系) +// 该结构体包含了两曲线之间的相位关系,如果相交还含有交点个数描述 +typedef struct CurveRelation_st { + int relation; // 表示两条曲线的相位关系,通过计算得到的曲线之间的相位关系, + // 用宏来区分不同的关系,当最后关系是包含时设置 relation 为 + // CURVE_INCLUDE,是被包含时设置为 CURVE_INCLUDED,是相交 + // 关系时设置为 CURVE_INTERSECT,没有上述三种关系时设置为 + // CURVE_OTHERSHIP,并且程序开始运行时,默认设置为 + // CURVE_OTHERSHIP。 + int internum; // 如果曲线相交,得到交点个数,如果不是相交关系,则设置为 0, + // 程序初始默认设置为 0 +} CurveRelation; + + +// 类:CurveTopology(曲线间的相位关系) +// 继承自:无 +// 曲线间的相位关系:判断两个闭合曲线的关系,其中包括包含关系、被包含关系、 +// 相交关系和无上述三种关系的其他关系,如果相交得到交点个数。默认是相对于第二条 +// 曲线,第一条曲线的关系,表示包含关系时是第一条曲线包含第二条曲线;表示被包含 +// 关系时是第二条曲线包含第一条曲线 +class CurveTopology { + +public: + + // 构造函数:CurveTopology + // 无参数版本的构造函数,因为该类没有成员变量,所以该构造函数为空。 + // 没有需要设置默认值的成员变量。 + __host__ __device__ + CurveTopology() {} + + // Host 成员方法:curveTopology(曲线相位关系) + // 对图像进行曲线跟踪,得到非闭合曲线和闭合曲线的有序序列 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + curveTopology( + Curve *curve1, // 输入第一条曲线 + Curve *curve2, // 输入第二条曲线 + CurveRelation *crvrelation, // 曲线相位关系 + int width, // 曲线对应的图像宽度 + int height // 曲线对应的图像高度 + ); +}; + +#endif diff --git a/okano_3_0/CurveTracing.cu b/okano_3_0/CurveTracing.cu new file mode 100644 index 0000000..15f0665 --- /dev/null +++ b/okano_3_0/CurveTracing.cu @@ -0,0 +1,2941 @@ +// CurveTracing +// 实现的曲线跟踪 + +#include "CurveTracing.h" + +#include +#include +#include + +using namespace std; + +#include "Template.h" +#include "TemplateFactory.h" + +// 宏:CURVE_VALUE(曲线最大数目) +// 设置图像能获得的曲线最大数目 +#define CURVE_VALUE 1000 + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// Kernel 函数:_traverseKer(并行遍历图像得到端点数组和交点数组,并且得到去掉 +// 交点后的输出图像) +// 遍历图像,得到曲线的所有端点坐标和交点坐标,并且得到去掉交点后的输出图像, +// 对每个像素点取其周围八领域像素点,如果八领域像素点的个数为 1,则这个为端点, +// 若八领域像素点的个数为大于等于 3,则认为这个点作为伪交点,存储起来,这些伪 +// 交点中有部分是真正的交点,后面计算需要从一堆伪交点中得到真正的交点。 +static __global__ void // Kernel 函数无返回值 +_traverseKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 去掉交点后的输出图像 + int *array1_dev, // 存储端点的数组 + int *array2_dev, // 存储交点的数组 + Template boxtpl // 3 * 3 领域模板 +); + +// Kernel 函数:_traverseKerNew(遍历图像,得到图像上所有的像素点) +// 遍历图像,保存图像上所有灰度值不为 0 的像素点,主要用于 CPU 串行代码中第二次 +// 遍历的实现 +static __global__ void // Kernel 函数无返回值 +_traverseKerNew( + ImageCuda inimg, // 输入图像 + int *array1_dev // 存储端点的数组 +); + +// Host 函数:traverse(遍历图像,得到端点坐标、交点坐标及去掉交点后的图像) +// 遍历图像,得到曲线的所有端点坐标和交点坐标,并且得到去掉交点后的输出图像, +// 对每个像素点取其周围八领域像素点,如果八领域像素点的个数为 1,则这个为端点, +// 若八领域像素点的个数为大于等于 3,则认为这个点作为伪交点,存储起来,这些伪 +// 交点中有部分是真正的交点,后面计算需要从一堆伪交点中得到真正的交点。主要是 +// 用于 CPU 串行代码的实现中处理 +static __host__ void // 无返回值 +traverse( + DynamicArrays &Vertex, // 存储端点的动态数组 + DynamicArrays &Intersect, // 存储伪交点的动态数组 + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + int *tpl // 八领域模板 +); + +// Host 函数:traverseNew(遍历图像,得到图像上所有的像素点) +// 遍历图像,保存图像上所有灰度值不为 0 的像素点,主要用于 CPU 串行代码中第二次 +// 遍历的实现 +static __host__ void // 无返回值 +traverseNew( + DynamicArrays &array, //存储点的坐标 + Image *inimg // 输入图像 +); + +// Host 函数:getCurve(得到去掉交点后的所有曲线段) +// 递归调用函数,得到去掉交点后的所有曲线段,并且这些曲线段都是非闭合曲线 +static __host__ void // 无返回值 +getCurve( + DynamicArrays *pcurve, // 存储所有提取到的非闭合曲线 + int &test, // 检测某端点开始的曲线是否已提取过 + int count, // 曲线条数 + Image *img, // 输入图像,待提取曲线的图像 + int *mark, // 标志数组,大小为图像大小,表示像素点是否 + // 访问,初始都为 0,如果访问则对应位置设为 1 + int *tpl, // 八领域模板 + int Vx, // 提取的曲线起点 x 坐标 + int Vy // 提取的曲线起点 y 坐标 +); + +// Host 函数:insectClassify(从得到的一堆交点中,进行分类,确定交点个数) +// 递归调用函数,实现原先得到的一堆交点进行分类,每一类是一部分点集,并且同一类 +// 的点集是连通的,这些点集中可以找到一个合适的交点,同时根据分类的结果可以得到 +// 交点的个数,有多少类就有多少交点 +static __host__ void // 无返回值 +insectClassify( + int x, // 点的 x 坐标 + int y, // 点的 y 坐标 + DynamicArrays &Intersect, // 存储交点的动态数组 + DynamicArrays *insect, // 存储分类的结果 + int sectnum, // 交点个数,即分类的类数 + int *tpl // 八领域模板 +); + +// Host 函数:makeCur(根据两点坐标得到一条曲线) +// 根据两点坐标得到一条曲线,两个点的连线方式为从第一个点开始,先从对角线往 +// 第二个点移动,如果第二个点的 x 或者 y 坐标的值与对角线 45° 移动的对应坐标值 +// 一样,则沿着 x 或者 y 坐标移动直到重合,从而得到一条简短曲线 +static __host__ void // 无返回值 +makeCur( + DynamicArrays &cur, // 存储得到的曲线 + int dx1, // 曲线第一个点的 x 坐标 + int dy1, // 曲线第一个点的 y 坐标 + int dx2, // 曲线第一个点的 x 坐标 + int dy2 // 曲线第一个点的 y 坐标 +); + +// Host 函数:interAssemble(交点曲线与原先得到的曲线进行重组,得到重组后曲线) +// 根据得到的交点扩散出的曲线和原先得到的非闭合曲线进行重组,得到重组后曲线, +// 以便之后的曲线还原 +static __host__ void // 无返回值 +interAssemble( + DynamicArrays *pcurve, // 非闭合曲线集 + int count, // 曲线条数 + DynamicArrays *insect, // 交点分类的结果 + int sectnum, // 交点曲线条数 + DynamicArrays realsect, // 真正的交点数组 + int *tpl // 八领域模板 +); + +// Host 函数:bpConnect(根据用户输入的半径得到近域点集) +// 根据用户输入的半径得到近域点集,并且更新端点动态数组 +static __host__ void +bpConnect( + DynamicArrays *pcurve, // 输入的曲线集 + int count, // 输入的曲线集条数 + int radius, // 半径大小参数 + DynamicArrays *psect, // 得到新增加的近域点集 + int *pcount, // 得到新增加的交点个数 + DynamicArrays &Vertex // 存储端点的动态数组 +); + +// Host 函数:AidNorm(判断两个端点之间距离是否在领域大小内,若在则添加到点集中) +// 判断两个端点之间距离是否在领域大小内,如果在领域半径大小内,则把找到的端点加 +// 入到新增加的近域点集 +static __host__ bool // 返回值为 bool 型,如果表示相同就返回 true, + // 否则返回 false +AidNorm( + DynamicArrays *pcurve, // 输入的曲线集 + int i, // 从编号为 i 的曲线往后搜索 + int count, // 输入的曲线集条数 + DynamicArrays *psect, // 新增加的近域点集 + int pcount, // 新增加的交点个数 + int radius, // 半径大小参数 + DynamicArrays &Vertex, // 存储端点的动态数组 + int x, int y // 曲线的端点坐标 +); + +// Host 函数:pcurveAcord(根据坐标得到曲线的编号) +// 根据曲线的端点坐标查找曲线的编号,遍历所有曲线的端点,查找是否存在和给定的坐 +// 标相等的点,则得到相应的返回结果。 +static __host__ int // 返回值,如果找到的是曲线首部返回 0, + // 如果找到的是曲线尾部则返回 1,否则返回 -1。 +pcurveAcord( + DynamicArrays *pcurve, // 输入的曲线集 + int count, // 输入的曲线集条数 + int &location, // 得到曲线的编号 + int x, int y // 端点坐标 +); + +// Host 函数:verAssemble(断点重组,根据近域点集重组曲线集) +// 根据近域点集重组曲线集,根据近域的每个集合里的那些点,进行计算,得到其中 +// 最合适的点作为中心点,这个中心点也即是一个新产生的交点,然后发散出去多条曲线, +// 把这些曲线更新到原来的曲线集中。更抽象成层的含义,断点的重组,根据用户输入的 +// 半径进行曲线端点组合,如果两个端点离得太近就变成一段连续的曲线, +// 达到的端点的连接性。 +static __host__ void +verAssemble( + DynamicArrays *pcurve, // 曲线集 + int count, // 曲线集的条数 + DynamicArrays *psect, // 近域点集 + int pcount, // 近域交点个数 + DynamicArrays &realsect // 更新交点集合 +); + +// Host 函数:IsFindPoint(判断坐标是不是坐标集动态数组里的点) +static __host__ bool // 返回值为 bool 型,如果表示相同就返回 true, + // 否则返回 false +IsFindPoint( + DynamicArrays &array, // 判断该坐标是不是动态数组里的点集 + int x, int y // 坐标 +); + +// Host 函数:makeNode(根据曲线的起点和终点,以及边的情况,得到曲线的编号) +// 根据曲线的起点和终点,以及边的情况,从而得到曲线的编号,并且编号的起点和终点 +// 是唯一的,也不会和边的编号重复,为之后构图提供方便 +static __host__ void // 无返回值 +makeNode( + DynamicArrays *pcurve, // 输入的曲线集 + int count, // 曲线的条数 + DynamicArrays *pcurno // 存储曲线的编号 +); + +// Host 函数:openCurvePath(得到非闭合曲线编号序列) +// 根据图的搜索得到非闭合曲线对应的编号序列,用于得到从 start 到 end 的所有路径 +static __host__ void // 无返回值 +openCurvePath( + DynamicArrays *opencurnode, // 存储非闭合曲线编号集 + int *openNum, // 得到非闭合曲线的条数 + Graph *G, // 曲线构建的图 + int start, // 搜索的起点 + int end // 搜索的终点 +); + +// Host 函数:closeCurvePath(得到闭合曲线编号序列) +// 根据图的搜索得到闭合曲线对应的编号序列,用于得到从 start 到 end 的所有路径 +static __host__ void // 无返回值 +closeCurvePath( + DynamicArrays *closecurnode, // 存储闭合曲线编号集 + int *closeNum, // 得到闭合曲线的条数 + Graph *G, // 曲线构建的图 + int insect // 搜索的起点,闭合曲线起点和终点一样 +); + +// Host 函数:IsArrayEqual(判断两个动态数组表示的曲线是否表示同一条曲线) +// 判断两个动态数组表示的曲线是否表示同一条曲线,首先得到的是曲线编号,且不会 +// 出现编号顺序一致的数组,可能会出现数量和编号一样但是顺序不一样的数组,排序后 +// 比较结果,主要用于闭合曲线的提取,由于闭合曲线头尾编号一样,排序比较的时候 +// 不算最后编号数 +static __host__ bool // 返回值为 bool 型,如果表示相同就返回 true, + // 否则返回 false +IsArrayEqual( + DynamicArrays object1, // 动态数组1 + DynamicArrays object2 // 动态数组2 +); + +// Host 函数:getPointNo(根据坐标对得到数组内对应编号) +// 通过得到的曲线序列,及首尾编号,得到点坐标对的数组对应编号 +static __host__ void // 无返回值 +getPointNo( + DynamicArrays *pcurve, // 提取的曲线序列 + int count, // 曲线数目 + DynamicArrays *pcurno, // 与曲线序列相对应的首尾编号 + DynamicArrays &array, // 点坐标对数组 + DynamicArrays &arrayno // 存储得到的对应编号 +); + +// Host 函数:getCurveNonFormat(得到非格式化输出曲线数据有序序列) +// 通过曲线编号集合和首尾编号集得到非格式化输出曲线数据有序序列 +static __host__ void // 无返回值 +getCurveNonFormat( + DynamicArrays *curnode, // 曲线编号集 + DynamicArrays *pcurve, // 提取的曲线序列 + int count, // 提取的曲线序列的数量 + DynamicArrays *pcurno, // 与曲线序列相对应的首尾编号 + DynamicArrays *cur, // 最终得到的曲线非格式输出数据 + int num, // 曲线的数量 + bool close = false // 标志闭合还是非闭合曲线,默认为非闭合 +); + + +// Host 函数:traverse(遍历图像,得到端点坐标、交点坐标及去掉交点后的图像) +static __host__ void traverse(DynamicArrays &Vertex, DynamicArrays &Intersect, + Image *inimg, Image *outimg,int *tpl) +{ + // 定义临时变量,用于循环 + int i, j, k; + // 定义临时变量,存储八领域的值 + int dx, dy; + + // 对每一个像素值不为 0 的像素点进行八领域处理 + for (i = 0; i < inimg->height; i++) { + for(j = 0; j < inimg->width; j++) { + // 如果该像素点为 0 则扫描下一个像素点 + if (inimg->imgData[i * inimg->width + j] == 0) { + outimg->imgData[i * inimg->width + j] = 0; + continue; + } + // 定义变量并且初始化为 0,用于取八领域下标 + int m = 0; + // 定义变量并且初始化为 0,用于得到八领域内有多少个像素值不为 0 的点 + int flag = 0; + for(k = 0; k < 8; k++) { + dx = j + tpl[m++]; + dy = i + tpl[m++]; + // 符合要求的八领域内的点的像素值如果不为 0,就累加到 flag 中 + if (dx >= 0 && dx < inimg->width && + dy >= 0 && dy < inimg->height) { + if (inimg->imgData[dy * inimg->width + dx] != 0) { + flag++; + } + } + } + // 如果 flag 为 0,表示该像素八领域没有不为 0 的像素点,则该点是 + // 孤立点,则给对应输出图像在该处赋值为 0 + if (flag == 0) { + outimg->imgData[i * inimg->width + j] = 0; + // 如果 flag 为 1,表示该像素八领域有一个不为 0 的像素点,则该点是 + // 曲线端点,并给对应输出图像在该处赋值原图像对应点像素值 + } else if (flag == 1) { + Vertex.addElem(j); + Vertex.addElem(i); + outimg->imgData[i * inimg->width + j] = + inimg->imgData[i * inimg->width + j]; + // 如果 flag 大于等于 3,表示该像素点作为曲线交点,则给对应输出图像 + // 在该处赋值为 0 + } else if (flag >= 3) { + Intersect.addElem(j); + Intersect.addElem(i); + outimg->imgData[i * inimg->width + j] = 0; + // 否则flag则为 2,表示该像素点作为曲线上的点,并给对应输出图像在该处 + // 赋值原图像对应点像素值 + } else { + outimg->imgData[i * inimg->width + j] = + inimg->imgData[i * inimg->width + j]; + } + } + } +} + +// Host 函数:traverseNew(遍历图像,得到图像上所有的像素点) +static __host__ void traverseNew(DynamicArrays &array, Image *inimg) +{ + + // 定义临时变量,用于循环 + int i, j; + + // 对每一个像素值不为 0 的像素点进行八领域处理 + for (i = 0; i < inimg->height; i++) { + for(j = 0; j < inimg->width; j++) { + // 如果该像素点不为 0 则保存 + if (inimg->imgData[i * inimg->width + j] != 0) { + // 得到所有灰度值不为 0 的像素点 + array.addElem(j); + array.addElem(i); + } + } + } +} + +// Host 函数:getCurve(得到去掉交点后的所有曲线段) +static __host__ void getCurve(DynamicArrays *pcurve, int &test, int count, + Image *img, int *mark, int *tpl, int Vx, int Vy) +{ + // 标志点是否已经访问过,如果访问过,test 加 1,并且退出,主要是判断该端点 + // 是否和另一个端点是同一条曲线,如果是就不需要再重复提取 + if (mark[Vy * img->width + Vx] == 1) { + test++; + return; + } + // 定义临时变量,存储八领域的值 + int dx, dy; + int j = 0; // 定义变量,用于循环 + // 定义标志,表示八领域是否还有像素值不为 0 的点 + int flag = 0; + + // 把该点的坐标值加入第 count 条曲线中,并且设置标志该点已经访问过 + pcurve[count].addElem(Vx); + pcurve[count].addElem(Vy); + mark[Vy * img->width + Vx] = 1; + + // 按顺时针访问八领域的像素点 + for(int i = 0; i < 8; i++) { + dx = Vx + tpl[j++]; + dy = Vy + tpl[j++]; + // 得到第一个不为 0 并且没有访问过的像素点就退出循环,并且标志 flag 为 1 + if (img->imgData[dy * img->width + dx] != 0 && + mark[dy * img->width + dx] != 1) { + flag = 1; + break; + } + } + // 如果 flag 为 1,说明找到了一个曲线上的点,以该点递归调用函数 + if (flag == 1) { + getCurve(pcurve, test, count, img, mark, tpl, dx, dy); + } + // 如果找不到了,说明已经全部搜索完,退出 + return; +} + +// Host 函数:insectClassify(从得到的一堆交点中,进行分类,确定交点个数) +static __host__ void insectClassify(int x, int y, DynamicArrays &Intersect, + DynamicArrays *insect, int sectnum, + int *tpl) +{ + // 把 x,y 坐标加入交点曲线中 + insect[sectnum - 1].addElem(x); + insect[sectnum - 1].addElem(y); + // 加入完后就删除交点数组中的 x,y 坐标 + Intersect.delElem(x, y); + // + if (Intersect.getSize() == 0) + return; + // 定义临时变量,存储八领域的坐标点 + int dx, dy; + for(int i = 0; i < 16; i += 2) { + dx = x + tpl[i]; + dy = y + tpl[i + 1]; + // 寻找到交点中是否有和八领域一样的坐标点,若有,则递归调用函数 + for(int j = 0; j < Intersect.getSize(); j += 2) { + if (dx == Intersect[j] && dy == Intersect[j + 1]) { + insectClassify(dx, dy, Intersect, insect, sectnum, tpl); + } + } + } + // 返回 + return; +} + +// Host 函数:makeCur(根据两点坐标得到一条曲线) +static __host__ void makeCur(DynamicArrays &cur, + int dx1, int dy1, int dx2, int dy2) +{ + // 定义临时变量,存储坐标值 + int x, y; + // 首先把起始点加入临时曲线中 + cur.addElem(dx1); + cur.addElem(dy1); + + // 如果两坐标值一样,则返回,无须后续步骤 + if (dx1 == dx2 && dy1 == dy2) + return; + + // 分别计算两坐标值的差 + int m = dx1 - dx2, n = dy1 - dy2; + + // 设置起始点 + x = dx1; + y = dy1; + + // 通过差值开始给交点曲线赋值,首先通过差值相对可以分成四个象限,第一、 + // 第二、第三、第四象限,并且以第一个点为中心开始。 + // 如果 m >= 0 并且 n >= 0,则表示第二个点相对第一个点在第一象限或者坐标轴 + if (m >= 0 && n >= 0) { + // 计算坐标差值的差 + int d = m - n; + // 根据差值的差给交点曲线赋值 + if (d >= 0) { + for (int c = 0; c < n; c++) { + x--; + y--; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < d; c++) { + x--; + cur.addElem(x); + cur.addElem(y); + } + } else { + for (int c = 0; c < m; c++) { + x--; + y--; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < -d; c++) { + y--; + cur.addElem(x); + cur.addElem(y); + } + + } + // 如果 m >= 0 并且 n < 0,则表示第二个点相对第一个点在第四象限或者坐标轴 + } else if (m >= 0 && n < 0) { + n = -n; + int d = m - n; + if (d >= 0) { + for (int c = 0; c < n; c++) { + x--; + y++; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < d; c++) { + x--; + cur.addElem(x); + cur.addElem(y); + } + } else { + for (int c = 0; c < m; c++) { + x--; + y++; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < -d; c++) { + y++; + cur.addElem(x); + cur.addElem(y); + } + } + // 如果 m < 0 并且 n >= 0,则表示第二个点相对第一个点在第二象限或者坐标轴 + } else if (m < 0 && n >= 0) { + m = -m; + int d = m - n; + if (d >= 0) { + for (int c = 0; c < n; c++) { + x++; + y--; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < d; c++) { + x++; + cur.addElem(x); + cur.addElem(y); + } + } else { + for (int c = 0; c < m; c++) { + x++; + y--; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < -d; c++) { + y--; + cur.addElem(x); + cur.addElem(y); + } + + } + // 否则 m < 0 并且 n < 0,则表示第二个点相对第一个点在第三象限 + } else { + m = -m; n = -n; + int d = m - n; + if (d >= 0) { + for (int c = 0; c < n; c++) { + x++; + y++; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < d; c++) { + x++; + cur.addElem(x); + cur.addElem(y); + } + } else { + for (int c = 0; c < m; c++) { + x++; + y++; + cur.addElem(x); + cur.addElem(y); + } + for (int c = 0; c < -d; c++) { + y++; + cur.addElem(x); + cur.addElem(y); + } + + } + + } +} + +// Host 函数:interAssemble(交点曲线与原先得到的曲线进行重组,得到重组后曲线) +static __host__ void interAssemble(DynamicArrays *pcurve, int count, + DynamicArrays *insect, int sectnum, + DynamicArrays realsect, int *tpl) +{ + // 如果没有交点则直接返回 + if (realsect.getSize() == 0) + return; + + // 定义临时变量 + int i, j, k, x1, y1, x2, y2, dx1, dy1, dx2, dy2, num, num2; + int mark1, mark2, flag1, flag2; + // 对每一条得到的曲线,先得到其首尾端点,进行八领域寻找交点曲线的尾端点, + // 如果找到就把交点曲线添加到原曲线中,实现交点曲线与原先得到的曲线重组 + for(i = 0; i < count; i++) { + // 初始化首尾都没有找到交点曲线的尾端点 + flag1 = 0; flag2 = 0; + // 初始化找到的交点曲线的曲线下标为 -1 + mark1 = -1; mark2 = -1; + // 得到原曲线动态数组的大小 + num = pcurve[i].getSize(); + // 得到原曲线的首尾端点坐标 + x1 = pcurve[i][0]; + y1 = pcurve[i][1]; + x2 = pcurve[i][num - 2]; + y2 = pcurve[i][num - 1]; + // 首先对原曲线的首端点开始进行查找 + for (j = 0; j < 16; j += 2) { + // 得到八领域的坐标 + dx1 = x1 + tpl[j]; + dy1 = y1 + tpl[j + 1]; + // 进行查找,找到退出循环 + for (k = 0; k < sectnum; k++) { + // 得到交点曲线的动态数组的大小 + num2 = insect[k].getSize(); + // 找到就相应赋值,并且退出循环 + for (int m = 0; m < num2; m += 2) { + if (dx1 == insect[k][m] && dy1 == insect[k][m + 1]) { + mark1 = k; flag1 += 1; break; + } + } + // 找到退出循环 + if (flag1) { + break; + } + } + // 找到退出循环 + if (flag1) { + break; + } + } + + // 对原曲线的尾端点开始进行查找 + for (j = 0; j < 16; j += 2) { + // 得到八领域的坐标 + dx2 = x2 + tpl[j]; + dy2 = y2 + tpl[j + 1]; + // 进行查找,找到退出循环 + for (k = 0; k < sectnum; k++) { + // 得到交点曲线的动态数组的大小 + num2 = insect[k].getSize(); + // 找到就相应赋值,并且退出循环 + for (int m = 0; m < num2; m += 2) { + if (dx2 == insect[k][m] && dy2 == insect[k][m + 1]) { + mark2 = k; flag2 += 1; break; + } + } + // 找到退出循环 + if (flag2) { + break; + } + } + // 找到退出循环 + if (flag2) { + break; + } + } + + // 如果没有找到可以组合的交点曲线,则进行下一个循环 + if (mark1 < 0 && mark2 < 0) { + continue; + } + + // 如果首部找到了可以组合的交点曲线,尾部没有,则原曲线反转,然后把交点 + // 曲线添加到反转后的曲线后边 + if (mark1 >= 0 && mark2 < 0) { + // 曲线反转 + pcurve[i].reverse(); + // 构造曲线加入到当前曲线中 + DynamicArrays temp; + makeCur(temp, dx1, dy1, + realsect[2 * mark1], realsect[2 * mark1 + 1]); + pcurve[i].addArray(temp); + + // 如果尾部找到了可以组合的交点曲线,首部没有,直接把交点曲线添加到原来 + // 曲线后边 + } else if (mark1 < 0 && mark2 >= 0) { + // 构造曲线加入到当前曲线中 + DynamicArrays temp; + makeCur(temp, dx2, dy2, + realsect[2 * mark2], realsect[2 * mark2 + 1]); + pcurve[i].addArray(temp); + + // 如果首部和尾部都找到了可以组合的交点曲线,先把尾部找到的交点曲线添加 + // 到原来曲线后边,然后反转曲线,然后把首部找到的交点曲线添加到反转后的 + // 曲线后边 + } else { + // 构造曲线加入到当前曲线中 + DynamicArrays temp; + makeCur(temp, dx2, dy2, + realsect[2 * mark2], realsect[2 * mark2 + 1]); + pcurve[i].addArray(temp); + + // 清空得到的曲线 + temp.clear(); + + // 曲线反转 + pcurve[i].reverse(); + // 构造曲线加入到当前曲线中 + makeCur(temp, dx1, dy1, + realsect[2 * mark1], realsect[2 * mark1 + 1]); + pcurve[i].addArray(temp); + } + + } +} + +// Host 函数:pcurveAcord(根据坐标得到曲线的编号) +static __host__ int pcurveAcord(DynamicArrays *pcurve, int count, int &location, + int x, int y) +{ + // 定义临时变量 + int i, dx1, dy1, dx2, dy2; + // 根据输入坐标查找曲线集中对应的曲线编号 location + for (i = 0; i < count; i++) { + // 得到曲线的两个端点 + dx1 = pcurve[i][0]; + dy1 = pcurve[i][1]; + dx2 = pcurve[i][pcurve[i].getSize() - 2]; + dy2 = pcurve[i][pcurve[i].getSize() - 1]; + // 根据端点查找对应的曲线,如果找到则返回首尾情况,表示端点是曲线的首部 + // 还是尾部, 0 表示曲线首部,1 表示尾部 + if ((dx1 == x) && (dy1 == y)) { + location = i; + return 0; + } + if ((dx2 == x) && (dy2 == y)) { + location = i; + return 1; + } + } + // 如果没有找到则返回 -1 + return -1; +} + +// Host 函数:verAssemble(根据近域点集重组曲线集) +static __host__ void verAssemble(DynamicArrays *pcurve, int count, + DynamicArrays *psect, int pcount, + DynamicArrays &realsect) +{ + // 定义临时变量 + int i, j, dx, dy, mark, location; + int cen_x, cen_y; + + // 计算得到每个点集中的最中心点,加入到交点集合中 + for (i = 0; i < pcount; i++) { + cen_x = 0; + cen_y = 0; + for (j = 0; j < psect[i].getSize();) { + cen_x += psect[i][j++]; + cen_y += psect[i][j++]; + } + // 得到最中心点 + cen_x = cen_x * 2 / j; + cen_y = cen_y * 2 / j; + realsect.addTail(cen_x, cen_y); + // 组合曲线,更新曲线集合和交点动态数组 + for (j = 0; j < psect[i].getSize();) { + dx = psect[i][j++]; + dy = psect[i][j++]; + if ((mark = pcurveAcord(pcurve, count, location, dx, dy)) != -1) { + if(!mark) { + pcurve[location].reverse(); + } + DynamicArrays temp; + makeCur(temp, dx, dy, cen_x, cen_y); + temp.delElemXY(dx, dy); + pcurve[location].addArray(temp); + } + } + } +} + +// Host 函数:IsFindPoint(判断坐标是不是坐标集动态数组里的点) +static __host__ bool IsFindPoint(DynamicArrays &array, int x, int y) +{ + // 遍历动态数组里的点 + for (int i = 0; i < array.getSize(); i += 2) { + // 找到就返回 true + if (array[i] == x && array[i + 1] == y) + return true; + } + // 没有找到则返回 false + return false; +} + +// Host 函数:AidNorm(判断两个端点之间距离是否在领域大小内,若在则添加到点集中) +static __host__ bool AidNorm(DynamicArrays *pcurve, int i, int count, + DynamicArrays *psect, int pcount, int radius, + DynamicArrays &Vertex, int x, int y) +{ + // 定义临时变量 + int j, dx1, dy1, dx2, dy2; + int dis1, dis2; + bool mark1, mark2; + bool find = false; + // 查找编号 i 之后的曲线端点是否存在距离小于半径的端点 + for (j = i + 1; j < count; j++) { + // 得到曲线的两个端点坐标 + dx1 = pcurve[j][0]; + dy1 = pcurve[j][1]; + dx2 = pcurve[j][pcurve[j].getSize() - 2]; + dy2 = pcurve[j][pcurve[j].getSize() - 1]; + mark1 = false; + mark2 = false; + // 查找第一个端点到曲线 i 端点的距离是否小于 radius + if (IsFindPoint(Vertex, dx1, dy1)) { + // 得到两点之间的距离并且向上取整 + dis1 = (int)floor(sqrt((dx1 - x) * (dx1 - x) + + (dy1 - y) * (dy1 - y))); + + if (dis1 <= radius) { + mark1 = true; + } + } + // 查找第二个端点到曲线 i 端点的距离是否小于 radius + if(IsFindPoint(Vertex, dx2, dy2)) { + // 得到两点之间的距离并且向上取整 + dis2 = (int)floor(sqrt((dx2 - x) * (dx2 - x) + + (dy2 - y) * (dy2 - y))); + if (dis2 <= radius) { + mark2 = true; + } + } + // 找到两个端点中到到曲线 i 端点的距离最小的端点进行处理 + if (mark1 && mark2) { + if (dis1 <= dis2) { + psect[pcount].addTail(dx1, dy1); + Vertex.delElem(dx1, dy1); + } else { + psect[pcount].addTail(dx2, dy2); + Vertex.delElem(dx2, dy2); + } + find = true; + } else if (mark1 && !mark2) { + psect[pcount].addTail(dx1, dy1); + Vertex.delElem(dx1, dy1); + find = true; + } else if (!mark1 && mark2) { + psect[pcount].addTail(dx2, dy2); + Vertex.delElem(dx2, dy2); + find = true; + } + } + // 返回值 find + return find; +} + +// Host 函数:bpConnect(断点的重组,根据用户输入的半径进行曲线端点组合) +static __host__ void bpConnect(DynamicArrays *pcurve, int count, int radius, + DynamicArrays *psect, int *pcount, + DynamicArrays &Vertex) +{ + // 定义临时变量 + int i, num; + int x1, y1, x2, y2; + bool find; + // 初始化为新增加的交点数为 0 + *pcount = 0; + // 循环遍历每条曲线的两个端点 + for (i = 0; i < count - 1; i++) { + num = pcurve[i].getSize(); + // 得到曲线的端点坐标 + x1 = pcurve[i][0]; + y1 = pcurve[i][1]; + x2 = pcurve[i][num - 2]; + y2 = pcurve[i][num - 1]; + find = false; + // 判断原先是不是从端点点集得到的端点 + if (IsFindPoint(Vertex, x1, y1)) { + // 从编号往后的曲线中找到符合条件的端点 + find = AidNorm(pcurve, i, count, psect, *pcount, radius, Vertex, + x1, y1); + // 如果找到,从端点数组中删除这个端点,增加到编号为 *pcount 的 + // 近域点集中 + if (find) { + Vertex.delElem(x1, y1); + psect[*pcount].addTail(x1, y1); + *pcount += 1; + } + } + find = false; + // 判断原先是不是从端点点集得到的端点 + if (IsFindPoint(Vertex, x2, y2)) { + // 从编号往后的曲线中找到符合条件的端点 + find = AidNorm(pcurve, i, count, psect, *pcount, radius, Vertex, + x2, y2); + // 如果找到,从端点数组中删除这个端点,增加到编号为 *pcount 的 + // 近域点集中 + if (find) { + Vertex.delElem(x2, y2); + psect[*pcount].addTail(x2, y2); + *pcount += 1; + } + } + } +} + +// Host 函数:makeNode(根据曲线的起点和终点,以及边的情况,得到曲线的编号) +static __host__ void makeNode(DynamicArrays *pcurve, int count, + DynamicArrays *pcurno) +{ + // 定义临时变量 + int num1 = 0, num2 = 1, num = 0; + int i, j, size1, size2; + int x1, y1, x2, y2; + // 定义 bool 型变量,表示查找是否之前相同的端点出现过 + bool find1, find2; + // 给第一条曲线,添加首尾端点编号为 0 1 + pcurno[0].addTail(0, 1); + // 接下来的端点编号从 2 开始 + num = 2; + // 循环给剩下的曲线端点编号,并且编号不能重复 + for (i = 1; i < count; i++) { + // 初始化没有找到 + find1 = find2 = false; + // 得到当前曲线的动态数组长度 + size2 = pcurve[i].getSize(); + // 查找之前的曲线端点 + for (j = i - 1; j >= 0; j--) { + // 得到当前曲线的动态数组长度 + size1 = pcurve[j].getSize(); + // 得到当前曲线的首尾端点坐标 + x1 = pcurve[j][0]; + y1 = pcurve[j][1]; + x2 = pcurve[j][size1 - 2]; + y2 = pcurve[j][size1 - 1]; + // 如果找到了首端点编号,得到当前编号值 + if (pcurve[i][0] == x1 && pcurve[i][1] == y1) { + num1 = pcurno[j][0]; + find1 = true; + } else if (pcurve[i][0] == x2 && pcurve[i][1] == y2) { + num1 = pcurno[j][1]; + find1 = true; + } + // 如果找到了尾端点编号,得到当前编号值 + if (pcurve[i][size2 - 2] == x1 && pcurve[i][size2 - 1] == y1) { + num2 = pcurno[j][0]; + find2 = true; + } else if (pcurve[i][size2 - 2] == x2 && pcurve[i][size2 - 1] == y2) { + num2 = pcurno[j][1]; + find2 = true; + } + } + // 如果首尾端点都找到了,则把之前得到的编号赋给当前曲线 + if (find1 && find2) { + pcurno[i].addTail(num1, num2); + // 如果仅仅首端点找到了,则把之前得到的编号赋给当前曲线 + } else if (find1 && !find2) { + pcurno[i].addTail(num1, num); + num++; + // 如果仅仅尾端点找到了,则把之前得到的编号赋给当前曲线 + } else if (!find1 && find2) { + pcurno[i].addTail(num, num2); + num++; + // 如果首尾端点都没有找到,则重新累加赋值 + } else { + pcurno[i].addTail(num, num + 1); + num += 2; + } + } + // 曲线端点编号结束后,给曲线的边赋值,也不会重复 + for (i = 0; i < count; i++) { + pcurno[i].addElem(num++); + } +} + +// Host 函数:openCurvePath(得到非闭合曲线编号序列) +static __host__ void openCurvePath(DynamicArrays *opencurnode, int *openNum, + Graph *G, int start, int end) +{ + // 定义动态数组变量,表示边栈和点栈 + DynamicArrays edgestack, vexstack; + // 定义点栈顶和边栈顶数,并且初始化 + int vtop = -1, etop = -1; + // 定义点栈和边栈的大小 + int vstacksize, estacksize; + // 定义临时变量 + int curnode; + // 定义临时边指针 + Edge *cur; + // 首端点入栈 + vexstack.addElem(start); + // 复位所有当前要访问的边 + G->resetCurrent(); + + // 循环,用于得到从起点到终点的所有路径 + while (vexstack.getSize() != 0) { + // 得到当前栈的大小 + vstacksize = vexstack.getSize(); + estacksize = edgestack.getSize(); + // 如果栈顶的值为终点 + if (vexstack[vstacksize - 1] == end) { + // 得到一条从起点到终点的路径并且保存。即添加端点编号和边编号 + for (int i = 0; i < estacksize; i++) { + opencurnode[*openNum].addTail(vexstack[i], edgestack[i]); + } + // 添加终点编号 + opencurnode[*openNum].addElem(end); + // 曲线条数增加 1 + *openNum += 1; + // 删除点栈顶和边栈顶的端点,搜索下一条可能的路径 + vexstack.delTail(vtop); + edgestack.delTail(etop); + // 如果栈顶的值不是终点,则继续搜索可能的路径 + } else { + // 得到当前栈顶的值 + curnode = vexstack[vstacksize - 1]; + // 得到图的当前点要访问的边 + cur = G->vertexlist[curnode].current; + // 如果当前要访问的边不为空 + if (cur != NULL) { + // 得到当前边的另一个顶点,如果该顶点不在点栈中,当前边也不在边 + // 栈中,则把当前点和边分别入栈,把当前要访问的边指针指向下一条 + // 边。判断是为了确保路径的点和边不能重复 + if (!edgestack.findElem(cur->eno) && + !vexstack.findElem(cur->jvex)) { + vexstack.addElem(cur->jvex); + edgestack.addElem(cur->eno); + } + G->vertexlist[curnode].current = cur->link; + // 如果当前要访问的边为空,则当前点连接的边都访问过,删除点栈顶和 + // 边栈顶的端点,重新设置当前栈顶端点的当前要访问的边 + } else { + vexstack.delTail(vtop); + edgestack.delTail(etop); + // 如果点栈顶的值等于起始点,则退出循环 + if (vtop == start) + break; + // 设置当前栈顶端点的当前要访问的边为第一条边 + G->vertexlist[vtop].current = G->vertexlist[vtop].firstedge; + } + } + } +} + +// Host 函数:closeCurvePath(得到闭合曲线编号序列) +static __host__ void closeCurvePath(DynamicArrays *closecurnode, int *closeNum, + Graph *G, int insect) +{ + // 定义动态数组变量,表示边栈和点栈 + DynamicArrays edgestack, vexstack; + // 定义点栈顶和边栈顶数,并且初始化 + int vtop = -1, etop = -1; + // 定义点栈和边栈的大小 + int vstacksize, estacksize; + // 定义临时变量 + int curnode; + // 是否找到一样的路径,尽管顺序不一样 + bool isFind; + // 定义临时边指针 + Edge *cur; + // 路径起始端点入栈 + vexstack.addElem(insect); + // 闭合曲线数量 + int num = *closeNum; + // 复位所有当前要访问的边 + G->resetCurrent(); + while (vexstack.getSize() != 0) { + // 得到当前栈的大小 + vstacksize = vexstack.getSize(); + estacksize = edgestack.getSize(); + // 初始化 isFind 为 false + isFind = false; + // 当边栈不为空,且点栈栈顶元素值为起点,则保存一条得到的闭合路径 + if (estacksize != 0 && vexstack[vstacksize - 1] == insect) { + for (int i = 0; i < estacksize; i++) { + closecurnode[num].addTail(vexstack[i], edgestack[i]); + } + closecurnode[num].addElem(insect); + // 查找是否和之前得到的路径表示是同一条路径 + for (int j = 0; j < num; j++) { + if (IsArrayEqual(closecurnode[j], closecurnode[num])) { + isFind = true; + break; + } + } + // 如果找到了一样的路径,就清空当前得到的闭合路径 + if (isFind) { + closecurnode[num].clear(); + // 如果没有找到,则保存当前得到的闭合路径,并且路径数量加 1 + } else { + num++; + } + + // 删除点栈顶和边栈顶的端点,搜索下一条可能的路径 + vexstack.delTail(vtop); + edgestack.delTail(etop); + // 栈顶不是起点,则继续搜索可能的路径 + } else { + // 得到当前栈顶的值 + curnode = vexstack[vstacksize - 1]; + + // 得到图的当前点要访问的边 + cur = G->vertexlist[curnode].current; + // 如果当前要访问的边不为空 + if (cur != NULL) { + // 得到当前边的另一个顶点,如果当前边不在边栈中,则把当前点和边 + // 分别入栈,把当前要访问的边指针指向下一条边。 + if (!edgestack.findElem(cur->eno)) { + if ((cur->jvex == insect)|| !vexstack.findElem(cur->jvex)) { + vexstack.addElem(cur->jvex); + edgestack.addElem(cur->eno); + } + } + G->vertexlist[curnode].current = cur->link; + + // 如果当前要访问的边为空,则当前点连接的边都访问过,删除点栈顶和 + // 边栈顶的端点,重新设置当前栈顶端点的当前要访问的边 + } else { + vexstack.delTail(vtop); + edgestack.delTail(etop); + // 如果点栈顶的值等于起始点,则退出循环 + if (vtop == insect) + break; + // 设置设置当前栈顶端点的当前要访问的边为第一条边 + G->vertexlist[vtop].current = G->vertexlist[vtop].firstedge; + } + } + } + // 得到闭合曲线的数量 + *closeNum = num; +} + +// Host 函数:IsArrayEqual(判断两个动态数组表示的曲线是否表示同一条曲线) +static __host__ bool IsArrayEqual(DynamicArrays object1, DynamicArrays object2) +{ + // 两个动态数组的大小不一致,则直接返回 false + if (object1.getSize() != object2.getSize()) { + return false; + // 否则看排序后结果是否一样,如果一样,则返回 true,否则返回 false + // 由于处理的是闭合曲线编号,头尾是一致的,则排序比较不包括最后一个编号 + } else { + // 得到曲线编号动态数组大小 + int size = object1.getSize(); + // 定义临时指针变量,得到第一个动态数组的整型指针 + int *p = object1.getCrvDatap(); + // 定义临时变量,用于交换数据 + int temp; + // 临时变量 + int min; + // 排序第一个动态数组的数据 + for (int i = 0; i < size - 2; i++) { + min = i; + for (int j = i + 1; j < size - 1; j++) { + if (p[j] < p[min]) { + min = j; + } + } + // 如果找到其他最小的则交换 + if (min != i) { + temp = p[i]; + p[i] = p[min]; + p[min] = temp; + } + } + // 定义临时指针变量,得到第二个动态数组的整型指针 + int *q = object2.getCrvDatap(); + // 排序第二个动态数组的数据 + for (int i = 0; i < size - 2; i++) { + min = i; + for (int j = i + 1; j < size - 1; j++) { + if (q[j] < q[min]) { + min = j; + } + } + // 如果找到其他最小的则交换 + if (min != i) { + temp = q[i]; + q[i] = q[min]; + q[min] = temp; + } + } + + // 排序结果如果不一样,则返回 false + for (int i = 0; i < size - 1; i++) { + if (p[i] != q[i]) { + return false; + } + } + + // 表示同一条路径,返回 true + return true; + } +} + +// Host 函数:getPointNo(根据坐标对得到数组内对应编号) +static __host__ void getPointNo(DynamicArrays *pcurve, int count, + DynamicArrays *pcurno, DynamicArrays &array, + DynamicArrays &arrayno) + { + // 临时变量,用于循环计数 + int i, j; + // 定义临时变量,存储坐标 + int dx, dy; + + // 循环得到数组内坐标对编号 + for (i = 0; i < array.getSize();) { + // 得到数组的 x,y 坐标 + dx = array[i++]; + dy = array[i++]; + + // 根据得到的曲线头尾坐标得到相应编号 + for (j = 0; j < count; j++) { + // 如果为曲线首部 + if (dx == pcurve[j][0] && dy == pcurve[j][1]) { + arrayno.addElem(pcurno[j][0]); + break; + // 如果为曲线尾部 + } else if (dx == pcurve[j][pcurve[j].getSize() - 2] && + dy == pcurve[j][pcurve[j].getSize() - 1]) { + arrayno.addElem(pcurno[j][1]); + break; + } + } + } + } + +// Host 函数:getCurveNonFormat(得到非格式化输出曲线数据有序序列) +static __host__ void getCurveNonFormat(DynamicArrays *curnode, + DynamicArrays *pcurve, int count, + DynamicArrays *pcurno, + DynamicArrays *cur, int num, bool close) +{ + // 临时变量,存储曲线编号数组的大小 + int nodesize; + + // 临时变量,存储得到的端点编号值 + int inode; + + // 临时变量,得到点的数目 + int vnum = pcurno[count - 1][2] - count + 1; + + // 临时变量,存储得到的曲线下标 + int icur; + + // 定义循环计数变量 + int i, j; + + // 临时变量,作为函数参数得到曲线的末尾坐标 + int xtop, ytop; + + // 根据得到的曲线编号集获得对应曲线 + for (i = 0; i < num; i++) { + // 得到曲线编号数组的大小 + nodesize = curnode[i].getSize(); + // 循环得到曲线端点和边编号并且得到组合曲线 + for (j = 0; j < nodesize;) { + // 得到点编号 + inode = curnode[i][j++]; + // 如果超过大小,则推出循环 + if (j >= nodesize) break; + // 根据边编号得到曲线下标 + icur = curnode[i][j++] - vnum; + + // 点编号和曲线下标,得到组合曲线 + if (inode == pcurno[icur][0]) { + cur[i].addArray(pcurve[icur]); + if (j != nodesize - 1) { + cur[i].delTail(ytop); + cur[i].delTail(xtop); + } + + } else if (inode == pcurno[icur][1]) { + pcurve[icur].reverse(); + cur[i].addArray(pcurve[icur]); + if (j != nodesize - 1) { + cur[i].delTail(ytop); + cur[i].delTail(xtop); + } + pcurve[icur].reverse(); + } + } + + // 如果为闭合曲线就删除末尾坐标 + if (close) { + // 由于末尾坐标和起始一样,删除末尾坐标 + cur[i].delTail(ytop); + cur[i].delTail(xtop); + } + } +} + +// Host 函数:freeCurve(释放曲线申请的空间) +void freeCurve(Curve ***curveList, int count) +{ + if (curveList == NULL) + return; + // 循环释放空间 + for (int i = 0; i < count; i++) { + CurveBasicOp::deleteCurve((*curveList)[i]); + } + delete [](*curveList); +} + +// Kernel 函数:_traverseKer(并行遍历图像得到端点数组和交点数组,并且得到去掉 +// 交点后的输出图像) +static __global__ void _traverseKer(ImageCuda inimg, ImageCuda outimg, + int *array1_dev, int *array2_dev, + Template boxtpl) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算输出坐标点对应的图像数据数组下标。 + int outidx = r * inimg.imgMeta.width + c; + + // 如果当前像素点为 0,则输出图像对应位置零,并且返回。 + if (inimg.imgMeta.imgData[inidx] == 0) { + outimg.imgMeta.imgData[inidx] = 0; + return; + } + + int tmpidx; // 临时变量,存储模板其他点的图像数据数组下标 + int count = 0; // 临时变量,存储灰度不为 0 的个数 + int dx, dy; // 临时变量,存储模板坐标 + int *p = boxtpl.tplData; // 临时变量,得到模板指针 + + // 扫描该点模版范围内有多少个灰度值不为 0 的点 + for (int i = 0; i < boxtpl.count; i++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个下标的 + // 数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = c + *(p++); + dy = r + *(p++); + // 如果是当前点则理下一个点 + if (dx == c && dy == r) + continue; + + // 计算坐标点对应的图像数据数组下标。 + tmpidx = dy * inimg.pitchBytes + dx; + // 得到当前点 8 领域内的非零像素点个数 + if (inimg.imgMeta.imgData[tmpidx] != 0) { + count++; + } + } + // 如果 count 为 0,表示该像素八领域没有不为 0 的像素点,则该点是 + // 孤立点,则给对应输出图像在该处赋值为 0 + if (count == 0) { + outimg.imgMeta.imgData[inidx] = 0; + return; + // 如果 flag 大于等于 3,表示该像素点作为曲线交点,则给对应输出图像 + // 在该处赋值为 0 + } else if (count >= 3) { + array2_dev[2 * outidx] = c; + array2_dev[2 * outidx + 1] = r; + outimg.imgMeta.imgData[inidx] = 0; + // 如果 count 为 1,表示该像素八领域有一个不为 0 的像素点,则该点是 + // 曲线端点,并给对应输出图像在该处赋值原图像对应点像素值 + } else if (count == 1) { + array1_dev[2 * outidx] = c; + array1_dev[2 * outidx + 1] = r; + outimg.imgMeta.imgData[inidx] = inimg.imgMeta.imgData[inidx]; + // 否则flag则为 2,表示该像素点作为曲线上的点,并给对应输出图像在该处 + // 赋值原图像对应点像素值 + } else { + outimg.imgMeta.imgData[inidx] = inimg.imgMeta.imgData[inidx]; + } + +} + +// Kernel 函数:_traverseKerNew(遍历图像,得到图像上所有的像素点) +static __global__ void _traverseKerNew(ImageCuda inimg, int *array1_dev) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算输出坐标点对应的图像数据数组下标。 + int outidx = r * inimg.imgMeta.width + c; + + // 如果当前像素点不为 0,则得到该坐标 + if (inimg.imgMeta.imgData[inidx] != 0) { + array1_dev[2 * outidx] = c; + array1_dev[2 * outidx + 1] = r; + } +} + +// 宏:FAIL_CURVETRACING_FREE +// 当下面函数运行出错时,使用该宏清除内存,防止内存泄漏。 +#define FAIL_CURVETRACING_FREE do { \ + if (outimg1 != NULL) { \ + ImageBasicOp::deleteImage(outimg1); \ + outimg1 = NULL; \ + } \ + if (outimg2 != NULL) { \ + ImageBasicOp::deleteImage(outimg2); \ + outimg2 = NULL; \ + } \ + if (tmpdev != NULL) { \ + cudaFree(tmpdev); \ + tmpdev = NULL; \ + } \ + if (array1 != NULL) { \ + delete []array1; \ + array1 = NULL; \ + } \ + if (array2 != NULL) { \ + delete []array2; \ + array2 = NULL; \ + } \ + if (boxtpl != NULL) { \ + TemplateFactory::putTemplate(boxtpl); \ + boxtpl = NULL; \ + } \ + if (mark != NULL) { \ + delete []mark; \ + mark = NULL; \ + } \ + if (pcurve != NULL) { \ + delete []pcurve; \ + pcurve = NULL; \ + } \ + if (insect != NULL) { \ + delete []insect; \ + insect = NULL; \ + } \ + if (psect != NULL) { \ + delete []psect; \ + psect = NULL; \ + } \ + if (pcurno != NULL) { \ + delete []pcurno; \ + pcurno = NULL; \ + } \ + if (opencur != NULL) { \ + delete []opencur; \ + opencur = NULL; \ + } \ + if (closecur != NULL) { \ + delete []closecur; \ + closecur = NULL; \ + } \ + if (G != NULL) { \ + delete G; \ + G = NULL; \ + } \ + } while (0) + +// Host 成员方法:curveTracing(曲线跟踪) +// 对图像进行曲线跟踪,得到非闭合曲线和闭合曲线的有序序列 +__host__ int CurveTracing::curveTracing(Image *inimg, Curve ***curveList, + int *openNum, int *closeNum) +{ + // 如果输入图像指针为空或者输出的曲线集指针为空,错误返回 + if (inimg == NULL || curveList == NULL) + return NULL_POINTER; + + // 定义错误码变量 + int errcode; + cudaError_t cuerrcode; + + // 定义输出图像 1 和 2 + Image *outimg1 = NULL; + Image *outimg2 = NULL; + + // 定义指针 tmpdev 给设备端端点数组和交点数组创建存储空间 + int *tmpdev = NULL; + + // 定义 CPU 端端点数组和交点数组 + int *array1 = NULL; + int *array2 = NULL; + + // 定义模板 boxtpl 用于获取模板 + Template *boxtpl = NULL; + + // 定义标志数组,标志图像上非零点的访问情况 + int *mark = NULL; + // 定义曲线数组,存储得到的曲线 + DynamicArrays *pcurve = NULL; + // 定义交点分类的动态数组,存储分类的结果 + DynamicArrays *insect = NULL; + // 定义近域点集动态数组,用于断点连续的处理 + DynamicArrays *psect = NULL; + // 定义变量,存储曲线的编号; + DynamicArrays *pcurno = NULL; + // 定义非闭合曲线 + DynamicArrays *opencur = NULL; + // 定义闭合曲线 + DynamicArrays *closecur = NULL; + + // 定义图类的指针变量 + Graph *G = NULL; + + // 给输出图像构建空间 + ImageBasicOp::newImage(&outimg1); + ImageBasicOp::makeAtHost(outimg1, inimg->width, inimg->height); + + ImageBasicOp::newImage(&outimg2); + ImageBasicOp::makeAtHost(outimg2, inimg->width, inimg->height); + + // 将图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + errcode = ImageBasicOp::copyToCurrentDevice(outimg1); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + errcode = ImageBasicOp::copyToCurrentDevice(outimg2); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 提取输出图像 1 的 ROI 子图像。 + ImageCuda outsubimgCud1; + errcode = ImageBasicOp::roiSubImage(outimg1, &outsubimgCud1); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + // 提取输出图像 2的 ROI 子图像。 + ImageCuda outsubimgCud2; + errcode = ImageBasicOp::roiSubImage(outimg2, &outsubimgCud2); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + // 定义八领域模板 + int tpl[16] = { -1, -1, 0, -1, 1, -1, 1, 0, 1, 1, 0, 1, -1, 1, -1, 0 }; + // 定义变量,用于循环 + int i, j, k; + // 定义临时变量,得到第一次遍历得到的端点和交点动态数组大小 + int num1 = 0, num2 = 0; + // 定义临时变量存储坐标值 + int dx, dy; + // 计算数据尺寸。 + int arraysize = inimg->width * inimg->height * 2; + int datasize = arraysize * 2 * sizeof(int); + + // 在当前设备上申请坐标数据的空间。 + cuerrcode = cudaMalloc((void **)(&tmpdev), datasize); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 给该空间内容全部赋值为 -1 + cuerrcode = cudaMemset(tmpdev, -1, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 定义设备端端点数组和交点数组 + int *array1_dev = tmpdev; + int *array2_dev = tmpdev + arraysize; + + // 定义模板的尺寸 + dim3 boxsize(3, 3, 1); + + // 通过模板工厂得到圆形领域模板 + errcode = TemplateFactory::getTemplate(&boxtpl, TF_SHAPE_BOX, + boxsize, NULL); + + // 检查模板是否为 NULL,如果为 NULL 直接报错返回。 + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 将模板拷贝到 Device 内存中 + errcode = TemplateBasicOp::copyToCurrentDevice(boxtpl); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 计算调用第一个 Kernel 所需要的线程块尺寸。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用第一个 Kernel 生成图像标志位数组。 + _traverseKer<<>>(insubimgCud, outsubimgCud1, + array1_dev, array2_dev, *boxtpl); + if (cudaGetLastError() != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + + // 给 CPU 端端点数组和交点数组申请空间 + array1 = new int[arraysize]; + array2 = new int[arraysize]; + + // 把两个数组拷贝到 Host 端 + cuerrcode = cudaMemcpy(array1, array1_dev, arraysize * sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + cuerrcode = cudaMemcpy(array2, array2_dev, arraysize * sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + + // 定义端点动态数组和交点动态数组 + DynamicArrays Vertex, Intersect; + // 把得到的端点和交点数组的非 -1 值赋值给端点动态数组和交点动态数组 + for (i = 0; i < arraysize; i++) { + if (array1[i] != -1) { + Vertex.addElem(array1[i]); + } + } + for (i = 0; i < arraysize; i++) { + if (array2[i] != -1) { + Intersect.addElem(array2[i]); + } + } + + // 得到第一次遍历得到的端点和交点动态数组大小 + num1 = Vertex.getSize(); + num2 = Intersect.getSize(); + + // 如果图像上曲线有端点和交点时,说明有曲线相交,可能有闭合和非闭合曲线, + // 如果图像上曲线有端点没有交点时,但是经过断续连接有可能产生闭合和 + // 非闭合曲线 + if ((num1 && num2) || (num1 && !num2)) { + // 重新给该空间内容全部赋值为 -1,用于第二次遍历 + cuerrcode = cudaMemset(tmpdev, -1, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 第二次并行遍历 + _traverseKer<<>>(outsubimgCud1, outsubimgCud2, + array1_dev, array2_dev, *boxtpl); + if (cudaGetLastError() != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 把端点数组拷贝到 Host 端 + cuerrcode = cudaMemcpy(array1, array1_dev, arraysize * sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 定义第二次遍历要得到的端点动态数组 + DynamicArrays Vertex1; + for (i = 0; i < arraysize; i++) { + if (array1[i] != -1) { + Vertex1.addElem(array1[i]); + } + } + + // 将图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToHost(outimg1); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 申请标志数组的空间,大小和图像一样 + mark = new int[arraysize / 2]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * arraysize / 2); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 申请曲线数组空间,曲线最多数目是端点的个数 + pcurve = new DynamicArrays [Vertex1.getSize() / 2]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < Vertex1.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + Vertex1[i], Vertex1[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 定义临时变量存储坐标值 + int x, y; + // 定义变量,存储交点的个数 + int sectnum = 0; + // 申请交点分类的动态数组空间 + insect = new DynamicArrays [num2 / 2]; + + // 循环得到交点分类动态数组值 + while (Intersect.getSize()) { + x = Intersect[0]; + y = Intersect[1]; + sectnum++; + insectClassify(x, y, Intersect, insect, sectnum, tpl); + } + + // 定义真正的交点数组,得到的是唯一确定的交点,与交点曲线方向动态数组集 + // 相对应,大小其实为交点个数。从分类的交点集中取领域数最大的点作为交点 + DynamicArrays realsect; + + // 循环得到交点曲线方向动态数组集和真正的交点数组 + for (i = 0; i < sectnum; i++) { + // 定义变量,存储领域数最大的点标记值,并初始化为 0 + int maxvalue = 0; + // 定义变量,存储坐标值,初始化为第一条曲线第一个点的坐标值 + int insect_x = insect[i][0], insect_y = insect[i][1]; + // 根据之前的分类结果,循环得到交点曲线方向动态数组 + for (j = 0; j < insect[i].getSize(); j += 2) { + x = insect[i][j]; + y = insect[i][j + 1]; + // 定义临时变量,存储分类集合中的点的八领域内有多少个点 + int value = 0; + for (k = 0; k < 16; k += 2) { + dx = x + tpl[k]; + dy = y + tpl[k + 1]; + // 遍历点周围有多少个点 + for (int s = 0; s < insect[i].getSize(); s += 2) { + if (dx == insect[i][s] && dy == insect[i][s + 1]) { + value++; + } + } + } + // 找到最中心的交点 + if (value > maxvalue) { + maxvalue = value; + insect_x = x; + insect_y = y; + } + } + // 得到交点坐标值 + realsect.addElem(insect_x); + realsect.addElem(insect_y); + } + + // 调用函数得到重组后的曲线,还是存储于 pcurve 中 + interAssemble(pcurve, count, insect, sectnum, realsect, tpl); + + // 定义近域点集的个数,得到新产生的交点个数 + int pcount = 0; + // 给近域点集申请最大空间 + psect = new DynamicArrays[Vertex.getSize() / 2]; + + // 根据用户输入的半径得到近域点集,并且更新端点动态数组 + bpConnect(pcurve, count, radius, psect, &pcount, Vertex); + + // 断点重组,根据用户输入的半径进行曲线断点组合,更新交点动态数组 + verAssemble(pcurve, count, psect, pcount, realsect); + + // 存储曲线的编号,空间大小和之前提取的曲线一样 + pcurno = new DynamicArrays[count]; + + // 调用函数得到曲线编号集合 + makeNode(pcurve, count, pcurno); + + // 定义变量,存储图的边数,并且赋值 + int edgenum = count; + // 定义变量,存储图的点数,并且赋值 + int vexnum = pcurno[count - 1][2] - edgenum + 1; + // 给图申请空间,根据边数和点数,初始化图 + G = new Graph(vexnum, edgenum); + + // 根据曲线编号集,给图设置相应的边 + for (i = 0; i < count; i++) { + G->setEdge(pcurno[i][0], pcurno[i][1], pcurno[i][2]); + } + + // 定义曲线编号集数组,分为非闭合曲线和闭合曲线 + DynamicArrays opencurnode[CURVE_VALUE], closecurnode[CURVE_VALUE]; + + // 定义端点编号数组和交点编号数组,分别得到端点和交点的坐标对应的编号数 + DynamicArrays vertexno; + DynamicArrays intersectno; + + // 调用函数得到数组端点的编号 + getPointNo(pcurve, count, pcurno, Vertex, vertexno); + + // 调用函数得到数组交点的编号 + if (realsect.getSize() > 0) + getPointNo(pcurve, count, pcurno, realsect, intersectno); + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + // 循环得到非闭合曲线的路径编号 + for (i = 0; i < vertexno.getSize(); i++) { + // 定义起始点 + int start, end; + start = vertexno[i]; + for (j = i + 1; j < vertexno.getSize(); j++) { + end = vertexno[j]; + // 调用函数,得到非闭合曲线编号序列集 + openCurvePath(opencurnode, openNum, G, start, end); + } + } + + // 循环得到闭合曲线的路径编号 + for (i = 0; i < intersectno.getSize(); i++) { + // 调用函数,得到闭合曲线编号序列集 + closeCurvePath(closecurnode, closeNum, G, intersectno[i]); + } + + // 申请非闭合曲线空间 + opencur = new DynamicArrays[*openNum]; + + // 申请闭合曲线空间 + closecur = new DynamicArrays[*closeNum]; + + // 调用函数得到非格式输出的非闭合曲线 + getCurveNonFormat(opencurnode, pcurve, count, pcurno, opencur, + *openNum, false); + + // 调用函数得到非格式输出的闭合曲线 + getCurveNonFormat(closecurnode, pcurve, count, pcurno, closecur, + *closeNum, true); + + // 定义曲线总数 + int total = *openNum + *closeNum; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出非闭合曲线 + for (i = 0; i < *openNum; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE; + return OUT_OF_MEM; + } + // 得到曲线长度 + curveLength = (size_t)(opencur[i].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = opencur[i].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return errcode; + } + } + + // 循环得到输出闭合曲线 + for (; i < total; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE; + return OUT_OF_MEM;; + } + // 得到曲线长度 + curveLength = (size_t)(closecur[i - *openNum].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = closecur[i - *openNum].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return errcode; + } + } + } + + // 如果图像上没有端点只有交点时候,说明是闭合曲线相交 + else if (!num1 && num2) + { + // 重新给该空间内容全部赋值为 -1,用于第二次遍历 + cuerrcode = cudaMemset(tmpdev, -1, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 第二次并行遍历 + _traverseKer<<>>(outsubimgCud1, outsubimgCud2, + array1_dev, array2_dev, *boxtpl); + if (cudaGetLastError() != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 把端点数组拷贝到 Host 端 + cuerrcode = cudaMemcpy(array1, array1_dev, arraysize * sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 定义第二次遍历要得到的端点动态数组 + DynamicArrays Vertex1; + for (i = 0; i < arraysize; i++) { + if (array1[i] != -1) { + Vertex1.addElem(array1[i]); + } + } + + // 将图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToHost(outimg1); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 申请标志数组的空间,大小和图像一样 + mark = new int[arraysize / 2]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * arraysize / 2); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 申请曲线数组空间,曲线最多数目是端点的个数 + pcurve = new DynamicArrays [Vertex1.getSize() / 2]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < Vertex1.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + Vertex1[i], Vertex1[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 定义临时变量存储坐标值 + int x, y; + // 定义变量,存储交点的个数 + int sectnum = 0; + // 申请交点分类的动态数组空间 + insect = new DynamicArrays [num2 / 2]; + + // 循环得到交点分类动态数组值 + while (Intersect.getSize()) { + x = Intersect[0]; + y = Intersect[1]; + sectnum++; + insectClassify(x, y, Intersect, insect, sectnum, tpl); + } + + // 定义真正的交点数组,得到的是唯一确定的交点,与交点曲线方向动态数组集 + // 相对应,大小其实为交点个数。从分类的交点集中取领域数最大的点作为交点 + DynamicArrays realsect; + + // 循环得到交点曲线方向动态数组集和真正的交点数组 + for (i = 0; i < sectnum; i++) { + // 定义变量,存储领域数最大的点标记值,并初始化为 0 + int maxvalue = 0; + // 定义变量,存储坐标值,初始化为第一条曲线第一个点的坐标值 + int insect_x = insect[i][0], insect_y = insect[i][1]; + // 根据之前的分类结果,循环得到交点曲线方向动态数组 + for (j = 0; j < insect[i].getSize(); j += 2) { + x = insect[i][j]; + y = insect[i][j + 1]; + // 定义临时变量,存储分类集合中的点的八领域内有多少个点 + int value = 0; + for (k = 0; k < 16; k += 2) { + dx = x + tpl[k]; + dy = y + tpl[k + 1]; + // 遍历点周围有多少个点 + for (int s = 0; s < insect[i].getSize(); s += 2) { + if (dx == insect[i][s] && dy == insect[i][s + 1]) { + value++; + } + } + } + // 找到最中心的交点 + if (value > maxvalue) { + maxvalue = value; + insect_x = x; + insect_y = y; + } + } + // 得到交点坐标值 + realsect.addElem(insect_x); + realsect.addElem(insect_y); + } + + // 调用函数得到重组后的曲线,还是存储于 pcurve 中 + interAssemble(pcurve, count, insect, sectnum, realsect, tpl); + + // 申请曲线编号大小,空间大小和之前提取的曲线一样 + pcurno = new DynamicArrays[count]; + + // 调用函数得到曲线编号集合 + makeNode(pcurve, count, pcurno); + + // 定义变量,存储图的边数,并且赋值 + int edgenum = count; + // 定义变量,存储图的点数,并且赋值 + int vexnum = pcurno[count - 1][2] - edgenum + 1; + // 根据边数和点数,初始化图 + G = new Graph(vexnum, edgenum); + + // 根据曲线编号集,给图设置相应的边 + for (i = 0; i < count; i++) { + G->setEdge(pcurno[i][0], pcurno[i][1], pcurno[i][2]); + } + + // 定义曲线编号集数组,只有闭合曲线 + DynamicArrays closecurnode[CURVE_VALUE]; + + // 定义交点编号数组,得到端点坐标对应的编号数 + DynamicArrays intersectno; + + // 调用函数得到数组交点的编号 + getPointNo(pcurve, count, pcurno, realsect, intersectno); + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + // 循环得到闭合曲线的路径编号 + for (i = 0; i < intersectno.getSize(); i++) { + // 调用函数,得到闭合曲线编号序列集 + closeCurvePath(closecurnode, closeNum, G, intersectno[i]); + } + + // 申请闭合曲线空间 + closecur = new DynamicArrays[*closeNum]; + + // 调用函数得到非格式输出的闭合曲线 + getCurveNonFormat(closecurnode, pcurve, count, pcurno, closecur, + *closeNum, true); + + // 定义曲线总数 + int total = *openNum + *closeNum; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出闭合曲线 + for (i = 0; i < total; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE; + return OUT_OF_MEM;; + } + // 得到曲线长度 + curveLength = (size_t)(closecur[i - *openNum].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = closecur[i - *openNum].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return errcode; + } + } + } + // 否则只有闭合曲线,且闭合曲线之间没有相交 + else + { + // 重新给该空间内容全部赋值为 -1,用于第二次遍历 + cuerrcode = cudaMemset(array1_dev, -1, arraysize * sizeof (int)); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 第二次并行遍历,得到曲线上所有点集 + _traverseKerNew<<>>(outsubimgCud1, array1_dev); + if (cudaGetLastError() != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 把端点数组拷贝到 Host 端 + cuerrcode = cudaMemcpy(array1, array1_dev, arraysize * sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CURVETRACING_FREE; + return CUDA_ERROR; + } + // 定义第二次遍历要得到的点集 + DynamicArrays point; + + // 把得到的端点和交点数组的非 -1 值赋值给端点动态数组和交点动态数组 + for (i = 0; i < arraysize; i++) { + if (array1[i] != -1) { + point.addElem(array1[i]); + } + } + + // 将图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToHost(outimg1); + if (errcode != NO_ERROR) { + FAIL_CURVETRACING_FREE; + return errcode; + } + + // 申请标志数组的空间,大小和图像一样 + mark = new int[arraysize / 2]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * arraysize / 2); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 申请曲线数组空间,曲线最多数目是端点的个数 + pcurve = new DynamicArrays [point.getSize() / 2]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < point.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + point[i], point[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + *closeNum = count; + // 定义曲线总数 + int total = count; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出闭合曲线 + for (i = 0; i < total; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE; + return OUT_OF_MEM;; + } + // 得到曲线长度 + curveLength = (size_t)(pcurve[i].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = pcurve[i].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE; + return errcode; + } + } + } + + // 释放动态申请的空间 + FAIL_CURVETRACING_FREE; + // 函数执行完毕,返回 + return NO_ERROR; +} + +// 宏:FAIL_CURVETRACING_FREE_CPU +// 当下面函数运行出错时,使用该宏清除内存,防止内存泄漏。 +#define FAIL_CURVETRACING_FREE_CPU do { \ + if (outimg1 != NULL) { \ + ImageBasicOp::deleteImage(outimg1); \ + outimg1 = NULL; \ + } \ + if (outimg2 != NULL) { \ + ImageBasicOp::deleteImage(outimg2); \ + outimg2 = NULL; \ + } \ + if (mark != NULL) { \ + delete []mark; \ + mark = NULL; \ + } \ + if (pcurve != NULL) { \ + delete []pcurve; \ + pcurve = NULL; \ + } \ + if (insect != NULL) { \ + delete []insect; \ + insect = NULL; \ + } \ + if (pcurno != NULL) { \ + delete []pcurno; \ + pcurno = NULL; \ + } \ + if (opencur != NULL) { \ + delete []opencur; \ + opencur = NULL; \ + } \ + if (closecur != NULL) { \ + delete []closecur; \ + closecur = NULL; \ + } \ + if (G != NULL) { \ + delete G; \ + G = NULL; \ + } \ + } while (0) + +// Host 成员方法:curveTracingCPU(曲线跟踪) +// 对图像进行曲线跟踪,得到非闭合曲线和闭合曲线的有序序列 +__host__ int CurveTracing::curveTracingCPU(Image *inimg, Curve ***curveList, + int *openNum, int *closeNum) +{ + // 如果输入图像指针为空或者输出的曲线集指针为空,错误返回 + if (inimg == NULL || curveList == NULL) + return NULL_POINTER; + + // 定义错误码变量 + int errcode; + + // 定义输出图像 1 和 2 + Image *outimg1 = NULL; + Image *outimg2 = NULL; + + // 定义标志数组,标志图像上非零点的访问情况 + int *mark = NULL; + // 定义曲线数组,存储得到的曲线 + DynamicArrays *pcurve = NULL; + // 定义交点分类的动态数组,存储分类的结果 + DynamicArrays *insect = NULL; + // 定义变量,存储曲线的编号; + DynamicArrays *pcurno = NULL; + // 定义非闭合曲线 + DynamicArrays *opencur = NULL; + // 定义闭合曲线 + DynamicArrays *closecur = NULL; + + // 定义图类的指针变量 + Graph *G = NULL; + + // 构建输出图像 1 和 2 + ImageBasicOp::newImage(&outimg1); + ImageBasicOp::makeAtHost(outimg1, inimg->width, inimg->height); + + ImageBasicOp::newImage(&outimg2); + ImageBasicOp::makeAtHost(outimg2, inimg->width, inimg->height); + + // 定义八领域模板 + int tpl[16] = { -1, -1, 0, -1, 1, -1, 1, 0, 1, 1, 0, 1, -1, 1, -1, 0 }; + + // 定义临时变量,得到第一次遍历得到的端点和交点动态数组大小 + int num1 = 0, num2 = 0; + + // 定义第一次遍历要得到的端点动态数组和交点动态数组 + DynamicArrays Vertex; + DynamicArrays Intersect; + + // 定义变量,用于循环 + int i, j, k; + + // 定义临时变量存储坐标值 + int dx, dy; + + // 遍历图像,得到端点和交点的动态数组 + traverse(Vertex, Intersect, inimg, outimg1, tpl); + + // 得到第一次遍历得到的端点和交点动态数组大小 + num1 = Vertex.getSize(); + num2 = Intersect.getSize(); + + // 如果图像上曲线有端点和交点时,说明有曲线相交,可能有闭合和非闭合曲线 + if (num1 && num2) { + // 定义第二次遍历要得到的端点动态数组和交点动态数组 + DynamicArrays Vertex1, Intersect1; + + // 第二次遍历图像,得到端点和交点的动态数组 + traverse(Vertex1, Intersect1, outimg1, outimg2, tpl); + + // 定义变量得到输入图像的像素点数目 + int maxnum = inimg->width * inimg->height; + // 申请标志数组的空间 + mark = new int[maxnum]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * maxnum); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 定义曲线数组,并且申请空间,曲线最多数目是端点的个数 + DynamicArrays *pcurve = new DynamicArrays [Vertex1.getSize() / 2]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < Vertex1.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + Vertex1[i], Vertex1[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 定义临时变量存储坐标值 + int x, y; + // 定义变量,存储交点的个数 + int sectnum = 0; + // 定义交点分类的动态数组,存储分类的结果,并且申请空间 + insect = new DynamicArrays [num2 / 2]; + + // 循环得到交点分类动态数组值 + while (Intersect.getSize()) { + x = Intersect[0]; + y = Intersect[1]; + sectnum++; + insectClassify(x, y, Intersect, insect, sectnum, tpl); + + } + + // 定义真正的交点数组,得到的是唯一确定的交点,与交点曲线方向动态数组集 + // 相对应,大小其实为交点个数。从分类的交点集中取领域数最大的点作为交点 + DynamicArrays realsect; + + // 循环得到交点曲线方向动态数组集和真正的交点数组 + for (i = 0; i < sectnum; i++) { + // 定义变量,存储领域数最大的点标记值,并初始化为 0 + int maxvalue = 0; + // 定义变量,存储坐标值,初始化为第一条曲线第一个点的坐标值 + int insect_x = insect[i][0], insect_y = insect[i][1]; + // 根据之前的分类结果,循环得到交点曲线方向动态数组 + for (j = 0; j < insect[i].getSize(); j += 2) { + x = insect[i][j]; + y = insect[i][j + 1]; + // 定义临时变量,存储分类集合中的点的八领域内有多少个点 + int value = 0; + for (k = 0; k < 16; k += 2) { + dx = x + tpl[k]; + dy = y + tpl[k + 1]; + // 遍历点周围有多少个点 + for (int s = 0; s < insect[i].getSize(); s += 2) { + if (dx == insect[i][s] && dy == insect[i][s + 1]) { + value++; + } + } + } + // 找到最中心的交点 + if (value > maxvalue) { + maxvalue = value; + insect_x = x; + insect_y = y; + } + } + // 得到交点坐标值 + realsect.addElem(insect_x); + realsect.addElem(insect_y); + } + + // 调用函数得到重组后的曲线,还是存储于 pcurve 中 + interAssemble(pcurve, count, insect, sectnum, realsect, tpl); + + // 定义变量,存储曲线的编号,空间大小和之前提取的曲线一样 + pcurno = new DynamicArrays[count]; + + // 调用函数得到曲线编号集合 + makeNode(pcurve, count, pcurno); + + // 定义变量,存储图的边数,并且赋值 + int edgenum = count; + // 定义变量,存储图的点数,并且赋值 + int vexnum = pcurno[count - 1][2] - edgenum + 1; + // 定义图的指针变量,根据边数和点数,初始化图 + G = new Graph(vexnum, edgenum); + + // 根据曲线编号集,给图设置相应的边 + for (i = 0; i < count; i++) { + G->setEdge(pcurno[i][0], pcurno[i][1], pcurno[i][2]); + } + + // 定义曲线编号集数组,分为非闭合曲线和闭合曲线 + DynamicArrays opencurnode[CURVE_VALUE], closecurnode[CURVE_VALUE]; + + // 定义端点编号数组和交点编号数组,分别得到顶点和端点的坐标对应的编号数 + DynamicArrays vertexno; + DynamicArrays intersectno; + + // 调用函数得到数组端点的编号 + getPointNo(pcurve, count, pcurno, Vertex, vertexno); + + // 调用函数得到数组交点的编号 + getPointNo(pcurve, count, pcurno, realsect, intersectno); + + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + // 循环得到非闭合曲线的路径编号 + for (i = 0; i < vertexno.getSize(); i++) { + // 定义起始点 + int start, end; + start = vertexno[i]; + for (j = i + 1; j < vertexno.getSize(); j++) { + end = vertexno[j]; + // 调用函数,得到非闭合曲线编号序列集 + openCurvePath(opencurnode, openNum, G, start, end); + } + } + + // 循环得到闭合曲线的路径编号 + for (i = 0; i < intersectno.getSize(); i++) { + // 调用函数,得到闭合曲线编号序列集 + closeCurvePath(closecurnode, closeNum, G, intersectno[i]); + } + + // 定义非闭合曲线,并且申请空间 + opencur = new DynamicArrays[*openNum]; + + // 定义闭合曲线,并且申请大小空间 + closecur = new DynamicArrays[*closeNum]; + + // 调用函数得到非格式输出的非闭合曲线 + getCurveNonFormat(opencurnode, pcurve, count, pcurno, opencur, + *openNum, false); + + // 调用函数得到非格式输出的闭合曲线 + getCurveNonFormat(closecurnode, pcurve, count, pcurno, closecur, + *closeNum, true); + + // 定义曲线总数 + int total = *openNum + *closeNum; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出非闭合曲线 + for (i = 0; i < *openNum; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE_CPU; + return OUT_OF_MEM; + } + // 得到曲线长度 + curveLength = (size_t)(opencur[i].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = opencur[i].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return errcode; + } + } + + // 循环得到输出闭合曲线 + for (; i < total; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE_CPU; + return OUT_OF_MEM;; + } + // 得到曲线长度 + curveLength = (size_t)(closecur[i - *openNum].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = closecur[i - *openNum].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return errcode; + } + } + + } + + // 如果图像上没有端点只有交点时候,说明是闭合曲线相交 + else if (num1 && !num2) { + // 定义变量得到输入图像的像素点数目 + int maxnum = inimg->width * inimg->height; + // 定义标志数组,并且申请和图像大小的空间 + mark = new int[maxnum]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * maxnum); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 定义曲线数组,并且申请空间,曲线最多数目是端点的个数 + DynamicArrays *pcurve = new DynamicArrays [Vertex.getSize() / 2]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < Vertex.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + Vertex[i], Vertex[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 定义变量,存储曲线的编号,空间大小和之前提取的曲线一样 + pcurno = new DynamicArrays[count]; + + // 调用函数得到曲线编号集合 + makeNode(pcurve, count, pcurno); + + // 定义变量,存储图的边数,并且赋值 + int edgenum = count; + // 定义变量,存储图的点数,并且赋值 + int vexnum = pcurno[count - 1][2] - edgenum + 1; + // 定义图的指针变量,根据边数和点数,初始化图 + G = new Graph(vexnum, edgenum); + + // 根据曲线编号集,给图设置相应的边 + for (i = 0; i < count; i++) { + G->setEdge(pcurno[i][0], pcurno[i][1], pcurno[i][2]); + } + + // 定义曲线编号集数组,只有非闭合曲线 + DynamicArrays opencurnode[CURVE_VALUE]; + + // 定义端点编号数组和交点编号数组,分别得到顶点和端点的坐标对应的编号数 + DynamicArrays vertexno; + + // 调用函数得到数组端点的编号 + getPointNo(pcurve, count, pcurno, Vertex, vertexno); + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + // 循环得到非闭合曲线的路径编号 + for (i = 0; i < vertexno.getSize(); i++) { + // 定义起始点 + int start, end; + start = vertexno[i]; + for (j = i + 1; j < vertexno.getSize(); j++) { + end = vertexno[j]; + // 调用函数,得到非闭合曲线编号序列集 + openCurvePath(opencurnode, openNum, G, start, end); + } + } + + // 定义非闭合曲线,并且申请空间 + opencur = new DynamicArrays[*openNum]; + + // 调用函数得到非格式输出的非闭合曲线 + getCurveNonFormat(opencurnode, pcurve, count, pcurno, opencur, + *openNum, false); + + // 定义曲线总数 + int total = *openNum; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出非闭合曲线 + for (i = 0; i < *openNum; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE_CPU; + return OUT_OF_MEM; + } + // 得到曲线长度 + curveLength = (size_t)(opencur[i].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = opencur[i].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return errcode; + } + } + } + + // 如果图像上没有端点只有交点时候,说明是闭合曲线相交 + else if (!num1 && num2) + { + // 定义第二次遍历要得到的端点动态数组和交点动态数组 + DynamicArrays Vertex1, Intersect1; + + // 第二次遍历图像,得到端点和交点的动态数组 + traverse(Vertex1, Intersect1, outimg1, outimg2, tpl); + + // 定义变量得到输入图像的像素点数目 + int maxnum = inimg->width * inimg->height; + // 定义标志数组,并且申请和图像大小的空间 + mark = new int[maxnum]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * maxnum); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 定义曲线数组,并且申请空间,曲线最多数目是端点的个数 + DynamicArrays *pcurve = new DynamicArrays [Vertex1.getSize() / 2]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < Vertex1.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + Vertex1[i], Vertex1[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 定义临时变量存储坐标值 + int x, y; + // 定义变量,存储交点的个数 + int sectnum = 0; + // 定义交点分类的动态数组,存储分类的结果,并且申请空间 + insect = new DynamicArrays [num2 / 2]; + + // 循环得到交点分类动态数组值 + while (Intersect.getSize()) { + x = Intersect[0]; + y = Intersect[1]; + sectnum++; + insectClassify(x, y, Intersect, insect, sectnum, tpl); + } + + // 定义真正的交点数组,得到的是唯一确定的交点,与交点曲线方向动态数组集 + // 相对应,大小其实为交点个数。从分类的交点集中取领域数最大的点作为交点 + DynamicArrays realsect; + + // 循环得到交点曲线方向动态数组集和真正的交点数组 + for (i = 0; i < sectnum; i++) { + // 定义变量,存储领域数最大的点标记值,并初始化为 0 + int maxvalue = 0; + // 定义变量,存储坐标值,初始化为第一条曲线第一个点的坐标值 + int insect_x = insect[i][0], insect_y = insect[i][1]; + // 根据之前的分类结果,循环得到交点曲线方向动态数组 + for (j = 0; j < insect[i].getSize(); j += 2) { + x = insect[i][j]; + y = insect[i][j + 1]; + // 定义临时变量,存储分类集合中的点的八领域内有多少个点 + int value = 0; + for (k = 0; k < 16; k += 2) { + dx = x + tpl[k]; + dy = y + tpl[k + 1]; + // 遍历点周围有多少个点 + for (int s = 0; s < insect[i].getSize(); s += 2) { + if (dx == insect[i][s] && dy == insect[i][s + 1]) { + value++; + } + } + } + // 找到最中心的交点 + if (value > maxvalue) { + maxvalue = value; + insect_x = x; + insect_y = y; + } + } + // 得到交点坐标值 + realsect.addElem(insect_x); + realsect.addElem(insect_y); + } + + // 调用函数得到重组后的曲线,还是存储于 pcurve 中 + interAssemble(pcurve, count, insect, sectnum, realsect, tpl); + + // 定义变量,存储曲线的编号,空间大小和之前提取的曲线一样 + pcurno = new DynamicArrays[count]; + + // 调用函数得到曲线编号集合 + makeNode(pcurve, count, pcurno); + + // 定义变量,存储图的边数,并且赋值 + int edgenum = count; + // 定义变量,存储图的点数,并且赋值 + int vexnum = pcurno[count - 1][2] - edgenum + 1; + // 定义图的指针变量,根据边数和点数,初始化图 + G = new Graph(vexnum, edgenum); + + // 根据曲线编号集,给图设置相应的边 + for (i = 0; i < count; i++) { + G->setEdge(pcurno[i][0], pcurno[i][1], pcurno[i][2]); + } + + // 定义曲线编号集数组,只有闭合曲线 + DynamicArrays closecurnode[CURVE_VALUE]; + + // 定义交点编号数组,得到端点坐标对应的编号数 + DynamicArrays intersectno; + + // 调用函数得到数组交点的编号 + getPointNo(pcurve, count, pcurno, realsect, intersectno); + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + // 循环得到闭合曲线的路径编号 + for (i = 0; i < intersectno.getSize(); i++) { + // 调用函数,得到闭合曲线编号序列集 + closeCurvePath(closecurnode, closeNum, G, intersectno[i]); + } + + // 定义闭合曲线,并且申请大小空间 + closecur = new DynamicArrays[*closeNum]; + + // 调用函数得到非格式输出的闭合曲线 + getCurveNonFormat(closecurnode, pcurve, count, pcurno, closecur, + *closeNum, true); + + // 定义曲线总数 + int total = *openNum + *closeNum; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出闭合曲线 + for (i = 0; i < total; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE_CPU; + return OUT_OF_MEM;; + } + // 得到曲线长度 + curveLength = (size_t)(closecur[i - *openNum].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = closecur[i - *openNum].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return errcode; + } + } + } + + // 否则只有闭合曲线,且闭合曲线之间没有相交 + else + { + // 定义第二次遍历要得到的点集 + DynamicArrays point; + + // 第二次遍历图像,得到端点和交点的动态数组 + traverseNew(point, outimg1); + + // 定义变量得到输入图像的像素点数目 + int maxnum = inimg->width * inimg->height; + // 定义标志数组,并且申请和图像大小的空间 + mark = new int[maxnum]; + // 初始化标志数组的值为 0 + memset(mark, 0, sizeof(int) * maxnum); + // 定义变量 count 表示得到的曲线输量 + int count = 0; + // 标志曲线跟踪的端点是否已经在曲线中,用于 getCurve 函数调用 + int test = 0; + // 定义曲线数组,并且申请空间,曲线最多数目是端点的个数 + pcurve = new DynamicArrays [point.getSize()]; + + // 循环调用 getCurve 函数得到非闭合曲线的有序序列 + for(i = 0; i < point.getSize(); i += 2) { + getCurve(pcurve, test, count, outimg1, mark, tpl, + point[i], point[i + 1]); + // 如果 test 不为 0,则 count 不加 1,继续循环,否则曲线数目加 1 + if (test) { + test = 0; + continue; + } + count++; + } + + // 起始闭合和非闭合曲线的数目都设置为 0 + *openNum = 0; + *closeNum = 0; + + *closeNum = count; + // 定义曲线总数 + int total = *openNum + *closeNum; + + // 给输出结果赋值,首先申请空间大小 + *curveList = new Curve *[total]; + + // 定义变量,表示曲线长度 + size_t curveLength; + + // 定义变量,表示动态数组里的整型指针 + int *crvData; + + // 循环得到输出闭合曲线 + for (i = 0; i < total; i++) { + // 申请曲线空间 + errcode = CurveBasicOp::newCurve(&((*curveList)[i])); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i); + FAIL_CURVETRACING_FREE_CPU; + return OUT_OF_MEM;; + } + // 得到曲线长度 + curveLength = (size_t)(pcurve[i].getSize() / 2); + // 得到动态数组里的整型指针 + crvData = pcurve[i].getCrvDatap(); + if (crvData == NULL) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return NULL_POINTER; + } + // 在 CPU 端构建曲线值 + errcode = CurveBasicOp::makeAtHost((*curveList)[i], curveLength, + crvData); + if (errcode != NO_ERROR) { + // 释放动态申请的空间 + freeCurve(curveList, i + 1); + FAIL_CURVETRACING_FREE_CPU; + return errcode; + } + } + } + + // 释放动态申请的空间 + FAIL_CURVETRACING_FREE_CPU; + // 函数执行完毕,返回 + return NO_ERROR; +} diff --git a/okano_3_0/CurveTracing.h b/okano_3_0/CurveTracing.h new file mode 100644 index 0000000..87d99e1 --- /dev/null +++ b/okano_3_0/CurveTracing.h @@ -0,0 +1,117 @@ +// CurveTracing.h +// 创建者:欧阳翔 +// +// 曲线跟踪(CurveTracing) +// 功能说明:对于给定的一副单一像素宽度的二值图像,跟踪图像上的所有曲线,得到 +// 闭合曲线和非闭合曲线的有序序列,如果图中有曲线相交情况,就会产生 +// 更多的闭合和非闭合曲线,需要得到所有曲线组合的情况。 + +// 修订历史: +// 2013年9月10日(欧阳翔) +// 初始版本,曲线跟踪初步实现。 +// 2013年10月10日(欧阳翔) +// 修改了曲线交点处理的方法。 +// 2013年10月15日(欧阳翔) +// 曲线跟踪的串行和并行的完全实现 +// 2013年11月10日(欧阳翔) +// 修改了一些格式上的错误,减少了部分代码的冗余情况 +// 2013年12月01日(欧阳翔) +// 增加了断点连接的曲线跟踪实现 +// 2013年12月10日(欧阳翔) +// 修改了断点连接的方法,使得断点连接的处理结果相对自然 + +#ifndef __CURVETRACING_H__ +#define __CURVETRACING_H__ + +#include "Curve.h" +#include "Graph.h" +#include "Image.h" +#include "DynamicArrays.h" +#include "ErrorCode.h" + + +// 类:CurveTracing(曲线跟踪) +// 继承自:无 +// 曲线跟踪:对于给定的一副单一像素宽度的二值图像,跟踪图像上的所有曲线,得到 +// 闭合曲线和非闭合曲线的有序序列,如果图中有曲线相交情况,就会产生更多的闭合 +// 和非闭合曲线,需要得到所有曲线组合的情况。 +class CurveTracing { + +protected: + + // 成员变量:radius(圆形邻域参数) + // 外部设定的圆形领域的半径大小。用于判断端点和交点可否连接,如果在某一端点 + // 领域内有其他端点,则这些端点可以认为是连着的 + int radius; + +public: + + // 构造函数:CurveTracing + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + CurveTracing() { + // 使用默认值为类的各个成员变量赋值。 + radius = 1; // 默认圆的半径大小为 1 + } + + // 构造函数:CurveTracing + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + CurveTracing( + int radius // 圆形邻域参数(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->radius = 1; // 默认圆的半径大小为 1 + + // 根据参数列表中的值设定成员变量的初值 + this->setRadius(radius); + } + + // 成员方法:getRadius(读取圆形邻域参数) + // 读取 radius 成员变量的值。 + __host__ __device__ int // 返回值:当前 radius 成员变量的值。 + getRadius() const + { + // 返回 radius 成员变量的值。 + return this->radius; + } + + // 成员方法:setRadius(设置圆形邻域参数) + // 设置 radius 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setRadius( + int radius // 新的圆形邻域参数 + ) { + // 将 radius 成员变量赋成新值 + this->radius = radius; + return NO_ERROR; + } + + // Host 成员方法:curveTracing(曲线跟踪) + // 对图像进行曲线跟踪,得到非闭合曲线和闭合曲线的有序序列,根据输入参数设置 + // 大小具有一定的断点连接功能。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + curveTracing( + Image *inimg, // 输入为单一像素宽的二值图像 + Curve ***curveList, // 输出结果,得到的曲线序列 + int *openNum, // 得到非闭合曲线数量 + int *closeNum // 得到闭合曲线数量 + ); + + // Host 成员方法:curveTracingCPU(曲线跟踪串行方法) + // 对图像进行曲线跟踪,得到非闭合曲线和闭合曲线的有序序列,没有实现断点连接 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + curveTracingCPU( + Image *inimg, // 输入为单一像素宽的二值图像 + Curve ***curveList, // 输出结果,得到的曲线序列 + int *openNum, // 得到非闭合曲线数量 + int *closeNum // 得到闭合曲线数量 + ); +}; + +#endif diff --git a/okano_3_0/DiffPattern.cu b/okano_3_0/DiffPattern.cu new file mode 100644 index 0000000..a76ff13 --- /dev/null +++ b/okano_3_0/DiffPattern.cu @@ -0,0 +1,484 @@ +// DiffPattern.cu +// 图像局部特异检出 + +#include "DiffPattern.h" +#include +#include +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 8 +#define DEF_BLOCK_Y 8 + +// 结构体:PatternData( 每个 pattern 的详细数据) +// 描述每个 pattern 的数据集合,包括走向角、中心坐标、和最小外接有向矩形的长边 +// 及短边等 +typedef struct PatternData_st { + float angel; // 走向角 + float ss; // 短边 + float ls; // 长边 + float csX; // 中心点横坐标 + float csY; // 中心点纵坐标 +} PatternData; + + +// 结构体:PatternDesc( 每个 pattern 中区域的分布信息) +// 描述每个 pattern 中红色区域和紫色区域的分布信息,由于 19 个 pattern 的大小 +// 均在 8 x 8 的方形区域内,因此使用位图记录,r 表示红色区域,p 表示紫色区域 +// r 和 p 都是单字节,字节中二进制位为 1 的区域代表被使用,0 表示未使用。 +// 包围每个 pattern 的矩形的左上角顶点与 8 x 8 方形区域左上顶点对齐,坐标方向 +// 与 pattern 方向一致 +typedef struct PatternDesc_st { + unsigned char r[8]; // 红色区域,每个字节表示一行的 8 个像素点位置 + unsigned char p[8]; // 紫色区域,每个字节表示一行的 8 个像素点位置 + + int pCount; // 紫色区块数目 + int rCount; // 红色区块数目 + int xinCord; // 区域中心点相对横坐标 + int yinCord; // 区域中心点相对纵坐标 +} PatternDesc; + +// 各个 pattern 的数据 +PatternData _patData[19] = { { 0, 1, 1, 0, 0 }, // pattern1 + { 0.25f, 1.414f, 4.242f, 0, 0 }, // pattern2 + { 0.75f, 1.414f, 4.242f, 0, 0 }, // pattern3 + { 0, 1, 3, 0, 0 }, // pattern4 + { 0.5f, 1, 3, 0, 0 }, // pattern5 + { 0.25f, 1.414f, 2.828f, 0.5, 0.5 }, // pattern6 + { 0.75f, 1.414f, 2.828f, -0.5, 0.5 }, // pattern7 + { 0, 1, 2, 0.5, 0 }, // pattern8 + { 0.5, 1, 2, 0, 0.5 }, // pattern9 + { 0, 2, 2, 0.5, 0.5 }, // pattern10 + { 0, 2, 2, 0.5, 0.5 }, // pattern11 + { 0, 2, 3, 0, 0.5 }, // pattern12 + {0.5, 2, 3, 0.5, 0 }, // pattern13 + {0.25, 2, 5, 0, 0.5 }, // pattern14 + {0.75, 2, 5, 0, 0.5 }, // pattern15 + {0, 3, 3, 0, 0 }, // pattern16 + {0, 3, 3, 0, 0 }, // pattern17 + {0.25, 4.242, 4.242, 0, 0 }, // pattern18 + {0.75, 4.242, 4.242, 0, 0} // pattern19 + }; + +// 每个 pattern 的区域分布数据 +static __device__ PatternDesc _pd[19] = { + { // [0] + // r[8] + { 0x0E, 0x1F, 0x1B, 0x1F, 0x0E, 0x00, 0x00, 0x00 }, + // p[8] + { 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 1, 20, + // xinCord, yinCord + 2, 2 + }, + { // [1] + // r[8] + { 0x1B, 0x36, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 3, 12, + // xinCord, yinCord + 3, 1 + }, + { // [2] + // r[8] + { 0x6C, 0x36, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 3, 12, + // xinCord, yinCord + 3, 1 + }, + { // [3] + // r[8] + { 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00 }, + // p[8] + { 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 3, 12, + // xinCord, yinCord + 1, 2 + }, + { // [4] + // r[8] + { 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 3, 12, + // xinCord, yinCord + 2, 1 + }, + { // [5] + // r[8] + { 0x1B, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 3, 12, + // xinCord, yinCord + 2, 1 + }, + { // [6] + // r[8] + { 0x6C, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 2, 8, + // xinCord, yinCord + 3, 0 + }, + { // [7] + // r[8] + { 0x03, 0x03, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00 }, + // p[8] + { 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 2, 8, + // xinCord, yinCord + 0, 2 + }, + { // [8] + // r[8] + { 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 2, 8, + // xinCord, yinCord + 2, 0 + }, + { // [9] + // r[8] + { 0x03, 0x03, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00 }, + // p[8] + { 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 4, 8, + // xinCord, yinCord + 0, 2 + }, + { // [10] + // r[8] + { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 4, 8, + // xinCord, yinCord + 2, 0 + }, + { // [11] + // r[8] + { 0x07, 0x07, 0x00, 0x00, 0x07, 0x07, 0x00, 0x00 }, + // p[8] + { 0x00, 0x00, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 6, 12, + // xinCord, yinCord + 1, 2 + }, + { // [12] + // r[8] + { 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x0C, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 6, 12, + // xinCord, yinCord + 2, 1 + }, + { // [13] + // r[8] + { 0x01, 0x03, 0x06, 0x04, 0x01, 0x03, 0x06, 0x04 }, + // p[8] + { 0x00, 0x00, 0x01, 0x03, 0x06, 0x04, 0x00, 0x00 }, + // pCount, rCount + 6, 12, + // xinCord, yinCord + 1, 3 + }, + { // [14] + // r[8] + { 0x04, 0x06, 0x03, 0x01, 0x04, 0x06, 0x03, 0x01 }, + // p[8] + { 0x00, 0x00, 0x04, 0x06, 0x03, 0x01, 0x00, 0x00 }, + // pCount, rCount + 6, 12, + // xinCord, yinCord + 1, 3 + }, + { // [15] + // r[8] + { 0x07, 0x07, 0x00, 0x00, 0x00, 0x07, 0x07, 0x00 }, + // p[8] + { 0x00, 0x00, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00 }, + // pCount, rCount + 9, 12, + // xinCord, yinCord + 1, 3 + }, + { // [16] + // r[8] + { 0x63, 0x63, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // p[8] + { 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00 }, + // pCount, rCount + 9, 12, + // xinCord, yinCord + 3, 1 + }, + { // [17] + // r[8] + { 0x38, 0x70, 0x60, 0x41, 0x03, 0x07, 0x0E, 0x00 }, + // p[8] + { 0x00, 0x08, 0x1C, 0x3E, 0x1C, 0x08, 0x00, 0x00 }, + // pCount, rCount + 13, 18, + // xinCord, yinCord + 3, 3 + }, + { // [18] + // r[8] + { 0x0E, 0x07, 0x03, 0x41, 0x60, 0x70, 0x38, 0x00 }, + // p[8] + { 0x00, 0x08, 0x1C, 0x3E, 0x1C, 0x08, 0x00, 0x00 }, + // pCount, rCount + 13, 18, + // xinCord, yinCord + 3, 3 + } +}; + +// 宏:GET_BIT +// 获取某一行数据的中指定列的 2 进制位 +#define GET_BIT(row, x) row == 0 ? 0 : (row >> x) % 2 + +// Kernel 函数:_diffPatternKer(根据各个 pattern 对检查是否局部特异) +// 根据 patterns 参数指定的 pattern 序号,计算对应的 pattern 是否特异,若特异则 +// 修改 patterns 数组中的值进行标记 +static __global__ void // kernel 函数无返回值 +_diffPatternKer( + ImageCuda inimg, // 输入图像 + int centerx, // 中心点横坐标 + int centery, // 中心点纵坐标 + int patcount, // 差分 pattern 对的数目 + int *patterns, // 差分 pattern 序号数组 + float *avgs // 每个 pattern 中紫色区域像素平均值 +); + + +// Kernel 函数:_diffPatternKer(根据各个 pattern 对检查是否局部特异) +static __global__ void _diffPatternKer( + ImageCuda inimg, int centerX, int centerY, int patcount, int *patterns, + float *avgs) +{ + // 如果 z 序号超出差分 pattern 总对数则直接退出 + if (threadIdx.z >= patcount) + return; + // 将 pattern 对编号与数组编号对应 + int couple = patterns[threadIdx.z] - 1; + if (couple < 0) + return; + + // 申明动态共享内存 + extern __shared__ unsigned short pixels[]; + // 获取第 1 个 pattern 红色区域数据指针 + unsigned short *red1 = &pixels[256 * threadIdx.z]; + // 获取第 1 个 pattern 紫色区域数据指针 + unsigned short *pur1 = &pixels[256 * threadIdx.z + 64]; + // 获取第 2 个 pattern 红色区域数据指针 + unsigned short *red2 = &pixels[256 * threadIdx.z + 128]; + // 获取第 2 个 pattern 紫色区域数据指针 + unsigned short *pur2 = &pixels[256 * threadIdx.z + 192]; + + // 计算对应的图像位置下标 + int pidx1 = couple == 0 ? 0 : 2 * couple - 1, pidx2 = 2 * couple; + int idx1 = (centerY - _pd[pidx1].yinCord) * inimg.pitchBytes + centerX + + threadIdx.x - _pd[pidx1].xinCord; + int idx2 = (centerY - _pd[pidx2].yinCord) * inimg.pitchBytes + centerX + + threadIdx.x - _pd[pidx2].xinCord; + int tid = threadIdx.y * blockDim.x + threadIdx.x; + + // 将对应的区域的图像数据复制到共享内存 + red1[tid] = GET_BIT(_pd[pidx1].r[threadIdx.y], threadIdx.x) * + inimg.imgMeta.imgData[idx1]; + pur1[tid] = GET_BIT(_pd[pidx1].p[threadIdx.y], threadIdx.x) * + inimg.imgMeta.imgData[idx1]; + red2[tid] = GET_BIT(_pd[pidx2].r[threadIdx.y], threadIdx.x) * + inimg.imgMeta.imgData[idx2]; + pur2[tid] = GET_BIT(_pd[pidx2].p[threadIdx.y], threadIdx.x) * + inimg.imgMeta.imgData[idx2]; + __syncthreads(); + + // 使用 reduction 对各个区域内进行求和 + if (tid < 32) { + red1[tid] += red1[tid + 32]; + pur1[tid] += pur1[tid + 32]; + red2[tid] += red2[tid + 32]; + pur2[tid] += pur2[tid + 32]; + __syncthreads(); + + red1[tid] += red1[tid + 16]; + pur1[tid] += pur1[tid + 16]; + red2[tid] += red2[tid + 16]; + pur2[tid] += pur2[tid + 16]; + __syncthreads(); + + red1[tid] += red1[tid + 8]; + pur1[tid] += pur1[tid + 8]; + red2[tid] += red2[tid + 8]; + pur2[tid] += pur2[tid + 8]; + __syncthreads(); + + red1[tid] += red1[tid + 4]; + pur1[tid] += pur1[tid + 4]; + red2[tid] += red2[tid + 4]; + pur2[tid] += pur2[tid + 4]; + __syncthreads(); + + red1[tid] += red1[tid + 2]; + pur1[tid] += pur1[tid + 2]; + red2[tid] += red2[tid + 2]; + pur2[tid] += pur2[tid + 2]; + __syncthreads(); + + red1[tid] += red1[tid + 1]; + pur1[tid] += pur1[tid + 1]; + red2[tid] += red2[tid + 1]; + pur2[tid] += pur2[tid + 1]; + __syncthreads(); + + } + + // 计算最终结果 + if (tid == 0) { + // 记录第一个 pattern 的紫色区域像素平均值 + avgs[pidx1] = pur1[0] * 1.0f / _pd[pidx1].pCount; + // 保存第二个 pattern 的紫色区域像素平均值 + avgs[pidx2] = pur2[0] * 1.0f / _pd[pidx2].pCount; + // 计算第 1 个 pattern 红色区域像素平均值和紫色区域像素平均值的差值 + float comp1 = red1[0] * 1.0f / _pd[pidx1].rCount - avgs[pidx1]; + // 计算第 2 个 pattern 红色区域像素平均值和紫色区域像素平均值的差值 + float comp2 = red2[0] * 1.0f / _pd[pidx2].rCount - avgs[pidx2]; + // 若两个 pattern 都满足同样的不等关系则将该 pattern 对序号标记为 0 + if ((comp1 > 0 && comp2 > 0) || (comp1 < 0 && comp2 < 0)) { + patterns[threadIdx.z] = 0; + } + } +} + + +// Host 方法:doDiffPattern(检出图像特异的 pattern 信息) +__host__ int DiffPattern::doDiffPattern(Image *inimg, int *counter, + float *result) +{ + // 数据指针为空时返回空指针异常 + if (inimg == NULL || counter == NULL || result == NULL || indice == NULL) + return NULL_POINTER; + // 差分 pattern 对数为 0 返回数据异常 + if (patCount == 0 ) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 差分 pattern 对的序号数组,设备端使用的指针 + int *patterns; + errcode = cudaMalloc(&patterns, patCount * sizeof (int)); + if (errcode != cudaSuccess) + return errcode; + // 初始数据与 indice 数组中的相同 + errcode = cudaMemcpy(patterns, indice, patCount * sizeof (int), + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) + return errcode; + + // 所有 19 个 pattern 的紫色区域像素平均值 + float *avgs = new float[19], *dev_avgs; + // 数组置 0 + memset(avgs, 0, 19 * sizeof(float)); + errcode = cudaMalloc(&dev_avgs, 19 * sizeof (float)); + if (errcode != cudaSuccess) + return errcode; + errcode = cudaMemcpy(dev_avgs, avgs, 19 * sizeof (float), + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = patCount; + gridsize.x = 1; + gridsize.y = 1; + int sharedSize = 256 * patCount * sizeof (unsigned short); + + // 调用核函数 + _diffPatternKer<<>> + (insubimgCud, 10, 10, patCount, patterns, dev_avgs); + + // 保存运算后的 pattern 对数组 + int *comp = new int[patCount]; + errcode = cudaMemcpy(comp, patterns, patCount * sizeof (int), + cudaMemcpyDeviceToHost); + if (errcode != cudaSuccess) + return errcode; + errcode = cudaMemcpy(avgs, dev_avgs, 19 * sizeof (float), + cudaMemcpyDeviceToHost); + if (errcode != cudaSuccess) + return errcode; + + // 差异 pattern 对的数目的计数器 + *counter = 0; + for (int i = 0; i < patCount; i++) { + if (comp[i] != indice[i]) { + int idx = 2 * indice[i] - 3; + if (idx < 0) + continue; + + // 把有差异的 pattern 信息保存至数据指针 + result[6 * (*counter)] = _patData[idx].csX; + result[6 * (*counter) + 1] = _patData[idx].csY; + result[6 * (*counter) + 2] = _patData[idx].angel; + result[6 * (*counter) + 3] = _patData[idx].ss; + result[6 * (*counter) + 4] = _patData[idx].ls; + result[6 * (*counter) + 5] = avgs[idx]; + (*counter)++; + + idx = 2 * indice[i] - 1; + if (idx < 0) + continue; + result[6 * (*counter)] = _patData[idx].csX; + result[6 * (*counter) + 1] = _patData[idx].csY; + result[6 * (*counter) + 2] = _patData[idx].angel; + result[6 * (*counter) + 3] = _patData[idx].ss; + result[6 * (*counter) + 4] = _patData[idx].ls; + result[6 * (*counter) + 5] = avgs[idx]; + (*counter)++; + } + } + + // 释放 Host 端内存空间 + delete []comp; + delete []avgs; + // 释放 Device 端内存空间 + cudaFree(patterns); + cudaFree(dev_avgs); + return NO_ERROR; +} + diff --git a/okano_3_0/DiffPattern.h b/okano_3_0/DiffPattern.h new file mode 100644 index 0000000..9fca9ce --- /dev/null +++ b/okano_3_0/DiffPattern.h @@ -0,0 +1,175 @@ +// DiffPattern.h +// 创建人:邓建平 +// +// 局部特异检出(DiffPattern) +// 功能说明:按照给定的 pattern 检测图像的局部区域的像素点是否有异常。若有异常, +// 则将出现异常的 pattern 信息以数组形式返回 +// +// 修订历史: +// 2013年07月12日(邓建平) +// 初始版本。 + +#ifndef __DIFFPATTERN_H__ +#define __DIFFPATTERN_H__ + +#include "ErrorCode.h" +#include "Image.h" + + +// 类:DiffPattern(局部特异检出) +// 继承自:无 +// 该类通过一个数组来指定局部特异部分检测需要用到的 pattern 种类,一共包含 19 +// 个共 10 对 pattern 且都是静态的,数组中的元素即 pattern 对的标号。若 pattern +// 对中的像素点平均值都满足同一个不等关系,则将这个 pattern 对的信息返回。 +class DiffPattern { + +protected: + // 成员变量:centerX(中心点横坐标),centerY(中心点纵坐标) + // 图像的中心点坐标是与 pattern 中的 anchor 对齐的像素点的坐标 + int centerX, centerY; + + // 成员变量:patCount(pattern 对的数目) + // 检测异常需要的差分 pattern 的对数 + int patCount; + + // 成员变量:indice(pattern 对的序号数组) + // 差分 pattern 的序号集合,一共包含 patCount 个不同的标号 + int *indice; + +public: + // 构造函数:DiffPattern + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + DiffPattern() + { + // 使用默认值为类的各个成员变量赋值。 + centerX = 0; // 中心点横坐标默认为 0 + centerY = 0; // 中心点纵坐标默认为 0 + patCount = 0; // pattern 对的数目默认为 0 + indice = NULL; // pattern 对序号数组默认为 NULL + } + + // 构造函数:DiffPattern + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + DiffPattern( + int centerx, // 中心点横坐标(具体解释见成员变量) + int centery, // 中心点纵坐标(具体解释见成员变量) + int patcount, // pattern 对的数目(具体解释见成员变量) + int *indice // pattern 对的序号数组(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->centerX = 0; // 中心点横坐标默认为 0 + this->centerY = 0; // 中心点纵坐标默认为 0 + this->patCount = 0; // pattern 对的数目默认为 0 + this->indice = NULL; // pattern 对序号数组默认为 NULL + + // 根据参数列表中的值设定成员变量的初值 + this->setCenterX(centerx); + this->setCenterY(centery); + this->setPatCount(patcount); + this->setIndice(indice); + } + + // 成员方法:getCenterX(读取中心点横坐标) + // 读取 centerX 成员变量的值。 + __host__ __device__ int // 返回: 当前 centerX 成员变量的值 + getCenterX() const + { + // 返回 centerX 成员变量的值 + return this->centerX; + } + + // 成员方法:setCenterX(设置中心点横坐标) + // 设置 centerX 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setCenterX( + int centerx // 中心点横坐标 + ) { + // 若参数不合法,返回对应的错误代码并退出 + if (centerX <= 0) + return INVALID_DATA; + // 将 centerX 成员变量赋成新值 + this->centerX = centerx; + return NO_ERROR; + } + + // 成员方法:getCenterY(读取中心点纵坐标) + // 读取 centerY 成员变量的值。 + __host__ __device__ int // 返回: 当前 centerY 成员变量的值 + getCenterY() const + { + // 返回 centerY 成员变量的值 + return this->centerY; + } + + // 成员方法:setCenterY(设置中心点纵坐标) + // 设置 centerY 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setCenterY( + int centery // 中心点纵坐标 + ) { + // 若参数不合法,返回对应的错误代码并退出 + if (centerY <= 0) + return INVALID_DATA; + // 将 centerX 成员变量赋成新值 + this->centerY = centery; + return NO_ERROR; + } + + // 成员方法:getPatCount(读取 pattern 对的数目) + // 读取 patCount 成员变量的值。 + __host__ __device__ int // 返回: 当前 patCount 成员变量的值 + getPatCount() const + { + // 返回 centerY 成员变量的值 + return this->patCount; + } + + // 成员方法:setPatCount(设置 pattern 对的数目) + // 设置 patCount 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setPatCount( + int patcount // pattern 对的数目 + ) { + // 若参数不合法,返回对应的错误代码并退出 + if (patcount <= 0) + return INVALID_DATA; + // 将 patCount 成员变量赋成新值 + this->patCount = patcount; + return NO_ERROR; + } + + // 成员方法:setIndice(设置 pattern 对的序号数组) + // 设置 indice 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setIndice( + int *indice // pattern 对的序号数组 + ) { + // 若参数不合法,返回对应的错误代码并退出 + if (indice == NULL) + return NULL_POINTER; + // 将 indice 成员变量赋成新值 + this->indice = indice; + return NO_ERROR; + } + + // Host 成员方法:doDiffPattern(检测异常) + // 根据输入图像,计算异常 pattern 对的数目,并将这些 pattern 的信息返回 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR。 + doDiffPattern( + Image *inimg, // 输入图像 + int *counter, // 异常 pattern 计数器 + float *result // 返回 pattern 信息的指针 + ); + +}; + +#endif + diff --git a/okano_3_0/DouglasPeucker.cu b/okano_3_0/DouglasPeucker.cu new file mode 100644 index 0000000..832a6fa --- /dev/null +++ b/okano_3_0/DouglasPeucker.cu @@ -0,0 +1,821 @@ +// DouglasPeucker.cu +// 道格拉斯普克算法简化曲线上的点。 + +#include "DouglasPeucker.h" + +// 宏:DEF_BLOCK_1D +// 定义了默认的 1D Block 尺寸。 +#define DEF_BLOCK_1D 512 + +// 宏:CH_LARGE_ENOUGH +// 定义了一个足够大的正整数,该整数在使用过程中被认为是无穷大。 +#define CH_LARGE_ENOUGH ((1 << 30) - 1) + +// Kernel 函数:_initLabelAryKer(初始化 LABEL 数组) +// 在迭代前初始化 LABEL 数组,初始化后的 LABEL 数组要求除最后一个元素为 1 +// 以外,其他的元素皆为 0。 +static __global__ void // Kernel 函数无返回值 +_initLabelAryKer( + int label[], // 待初始化的 LABEL 数组。 + int cstcnt // LABEL 数组长度。 +); + +// Kernel 函数: _updateDistKer(更新点集的垂距信息) +// 根据目前已知的结果集上的点集和区域的标签值,找出当前每个点所在区域的起止点, +// 根据点到直线的垂距公式,计算点集的附带数据:点到当前所在区域的起止点构成的直 +// 线的垂直距离。 +static __global__ void // Kernel 函数无返回值 +_updateDistKer( + int cst[], // 输入点集,也是输出点集,更新点集的 + // attachData,也就是垂距的信息。 + int cornercst[], // 目前已知结果点集,即每段的最值点信息。 + int label[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 输入,当前点的数量。 + float dis[] // 记录每一个点的垂距 +); + +// Kernel 函数: _updateFoundInfoKer(更新新发现角点信息) +// 根据分段扫描后得到的点集信息,更新当前区域是否有新发现的角点,更新目前 +// 已知的结果集的点的位置索引。 +static __global__ void // Kernel 函数无返回值 +_updateFoundInfoKer( + int label[], // 输入,当前点集的区域标签值数组。 + float dist[], // 输入数组,所有点的垂距,即坐标点集数据结构中的 + // attachedData 域。 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目前已知的最 + // 大垂距点的位置索引数组。 + int cstcnt, // 坐标点的数量。 + int foundflag[], // 输出数组,如果当前区域内找到新的点,标志位置 1。 + int startidx[], // 输出,目前已知的结果点集中点的位置索引数组,也相当 + // 于当前每段上的起始位置的索引数组。 + float threshold, // 垂距的阈值 + int foundidx[] // 新找到的角点的一维索引 +); + +// Kernel 函数: _updateCornerCstKer(生成新结果点集) +// 根据分段扫描后得到的点集信息,和每段上是否发现新点的信息,生成新点集。 +static __global__ void // Kernel 函数无返回值 +_updateCornerCstKer( + int cst[], // 输入点集 + int cornercst[], // 目前已知结果点集,即每段的最值点信息。 + int foundflag[], // 输入,当前区域内有新发现点的标志位数组,如果当前 + // 区域内找到新的点,标志位置 1。 + int foundacc[], // 输入,偏移量数组,即当前区域内有新发现点的标志位 + // 的累加值。 + int startidx[], // 输入,目前已知的点的位置索引数组, + // 也相当于当前每段上的起始位置的索引数组。 + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目前已知的最 + // 大垂距点的位置索引数组 + int cornercnt, // 当前点的数量。 + int newcornercst[] // 输出,更新后的目前已知结果点集即每段的最值点信息。 +); + +// Kernel 函数: _updateLabelKer(更新 Label 值) +// 根据已得到的结果点,更新 Label 值。 +static __global__ void // Kernel 函数无返回值 +_updateLabelKer( + int label[], // 输入,当前点集的区域标签值数组。 + int cstcnt, // 坐标点的数量。 + int foundidx[], // 新找到的点的一维索引 + int foundacc[], // 输入,偏移量数组即当前区域内新发现点的标志位累加值。 + int tmplabel[] // 新的标签值 +); + + +// Kernel 函数:_initLabelAryKer(初始化 LABEL 数组) +static __global__ void _initLabelAryKer(int label[], int cstcnt) +{ + // 计算当前 Thread 对应的数组下标。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前下标处理的是越界数据,则直接退出。 + if (idx >= cstcnt) + return; + + // 在 LABEL 数组中,将最后一个变量写入 1,其余变量写入 0。 + if (idx == cstcnt - 1) + label[idx] = 1; + else + label[idx] = 0; +} + +// Host 成员方法:initLabelAry(初始化 LABEL 数组) +__host__ int DouglasPeucker::initLabelAry(int label[], int cstcnt) +{ + // 检查输入的数组是否为 NULL。 + if (label == NULL) + return NULL_POINTER; + + // 检查数组长度是否大于等于 2。 + if (cstcnt < 2) + return INVALID_DATA; + + // 计算启动 Kernel 函数所需要的 Block 尺寸与数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + + // 启动 Kernel 函数,完成计算。 + _initLabelAryKer<<>>(label, cstcnt); + + // 检查 Kernel 函数执行是否正确。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,返回。 + return NO_ERROR; +} + + +// Kernel 函数: _updateDistKer(更新点集的垂距信息) +static __global__ void _updateDistKer( + int cst[], int cornercst[], int label[], + int cstcnt, /*int foundflagDev[], int oldlabelDev[], */float dis[]) +{ + // 记录了本 Kernel 所使用到的共享内存中各个下标所存储的数据的含义。其中, + // SIDX_BLK_CNT 表示当前 Block 所需要处理的坐标点的数量,由于坐标点的数量不 + // 一定能够被 BlockDim 整除,因此最后一个 Block 所处理的坐标点的数量要小于 + // BlockDim。 + // SIDX_BLK_LABEL_LOW 和 SIDX_BLK_LABEL_UP 用来存当前 Block 中所加载的点集 + // 的区域标签值的上下界。根据这个上下界,可以计算出当前点所在区域的起止 + // 点,从而根据这两点确定的直线计算当前点的垂距。 + // 从下标为 SIDX_BLK_CST 开始的其后的所有共享内存空间存储了当前 Block 中的 + // 点集坐标。坐标集中第 i 个点对应的数组下标为 2 * i 和 2 * i + 1,其中下标 + // 为 2 * i 的数据表示该点的横坐标,下标为 2 * i + 1 的数据表示该点的纵坐 + // 标。 +#define SIDX_BLK_CNT 0 +#define SIDX_BLK_LABEL_LOW 1 +#define SIDX_BLK_LABEL_UP 2 +#define SIDX_BLK_CORNER 3 + + // 共享内存的声明。 + extern __shared__ int shdmem[]; + + // 基准索引。表示当前 Block 的起始位置索引。 + int baseidx = blockIdx.x * blockDim.x; + // 全局索引。 + int idx = baseidx + threadIdx.x; + // 如果当前线程的全局下标越界,则直接返回,因为他没有对应的所要处理坐标点。 + if (idx >= cstcnt) + return; + + // 当前 Block 的第 0 个线程来处理共享内存中彼此共享的数据的初始化工作。 + if (threadIdx.x == 0) { + // 计算当前 Block 所要处理的坐标点的数量。默认情况下该值等于 BlockDim, + // 但对于最后一个 Block 来说,在坐标点总数量不能被 BlockDim 所整除的时 + // 候,需要处理的坐标点数量会小于 BlockDim。 + if (baseidx + blockDim.x <= cstcnt) + shdmem[SIDX_BLK_CNT] = blockDim.x; + else + shdmem[SIDX_BLK_CNT] = cstcnt - baseidx; + + // 计算当前 Block 所处理的坐标点中起始的 LABEL 编号。 + shdmem[SIDX_BLK_LABEL_LOW] = label[baseidx]; + + // 计算当前 Block 索要处理的坐标点中最大的 LABEL 编号。由于考虑到根据两 + // 点计算直线方程,因此所谓的最大 LABEL 编号其实是 + if (baseidx + shdmem[SIDX_BLK_CNT] <= cstcnt) + shdmem[SIDX_BLK_LABEL_UP] = + label[baseidx + shdmem[SIDX_BLK_CNT] - 1] + 1; + else + shdmem[SIDX_BLK_LABEL_UP] = label[cstcnt - 1]; + } + + // Block 内部同步,使得上一步的初始化对 Block 内的所有 Thread 可见。 + __syncthreads(); + + // 将当前 Block 处理的 LABEL 值上下界加载到寄存器,该步骤没有逻辑上的含义, + // 只是为了 GPU 处理速度更快。 + int labellower = shdmem[SIDX_BLK_LABEL_LOW]; + int labelupper = shdmem[SIDX_BLK_LABEL_UP]; + + // 为了方便代码编写,这里单独提出一个 blockcstShd 指针,指向当前 Block 所对 + // 应的点集数据的共享内存空间。 + int *cornerShd = &shdmem[SIDX_BLK_CORNER]; + + // 加载当前 Block 中所用到的 LABEL 所对应的起止点,这两个点构成的直线可用来 + // 衡量各点的垂距并以此推算出下一轮的角点。将所用到的点加载的 Shared Memory + // 中也没有逻辑上的目的,仅仅是为了下一步计算时访存时间的缩短。 + if (threadIdx.x < labelupper - labellower + 1) { + cornerShd[2 * threadIdx.x] = + cornercst[2 * (labellower + threadIdx.x)]; + cornerShd[2 * threadIdx.x + 1] = + cornercst[2 * (labellower + threadIdx.x) + 1]; + } + + // Block 内部同步,使得上面所有的数据加载对 Block 内的所有 Thread 可见。下 + // 面的代码就正式的投入计算了。 + __syncthreads(); + + if (idx == cstcnt - 1) { + dis[idx] = 0.0f; + return; + } + // 计算当前点的坐标和区域标签值。 + int curx = cst[2 * idx]; + int cury = cst[2 * idx + 1]; + int curlabelidx = 2 * (label[idx] - labellower); + + // 计算当前 LABEL 区域的最左点的坐标。 + int leftx = cornerShd[curlabelidx++]; + int lefty = cornerShd[curlabelidx++]; + + // 计算当前 LABEL 区域的最右点的坐标。 + int rightx = cornerShd[curlabelidx++]; + int righty = cornerShd[curlabelidx ]; + + // 如果当前点就是角点,那么不需要计算直接赋值退出就可以了。 + if ((curx == leftx && cury == lefty) || + (curx == rightx && cury == righty)) { + dis[idx] = 0.0f; + return; + } + + // 计算垂距,该计算通过起止形成的直线作为垂距求解的依据 + float k, dist,b, temp; + if (rightx == leftx) { + dist = fabsf(curx - leftx); + } else { + k = (righty - lefty) * 1.0f / (rightx - leftx); + b = lefty - k * leftx; + temp = fabsf(k * curx - cury + b); + dist = temp / sqrtf(k * k + 1); + } + + // 将垂距信息更新到 Global 内存中作为输出。 + dis[idx] = dist; + +#undef SIDX_BLK_CNT +#undef SIDX_BLK_LABEL_LOW +#undef SIDX_BLK_LABEL_UP +#undef SIDX_BLK_CORNER +} + +// 成员方法:updateDist(更新坐标点集垂距) +__host__ int DouglasPeucker::updateDist( + int *cst, int *cornercst, int label[], int cstcnt, float dis[]) +{ + // 检查输入坐标集,输出坐标集是否为空。 + if (cornercst == NULL || cst == NULL || label == NULL || dis == NULL) + return NULL_POINTER; + + // 检查当前点的数量,小于等于 0 则无效数据。 + if (cstcnt <= 0) + return INVALID_DATA; + + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量,以及所需要的 Shared + // Memory 的数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (3 + 2 * blocksize) * sizeof (int); + + // 调用更新点集的垂距信息的核函数,计算每个点的垂距,更新负垂距标志数组。 + _updateDistKer<<>>( + cst, cornercst, label, cstcnt,/* foundflagDev, oldlabelDev, */dis); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _updateFoundInfoKer(更新新发现点信息) +static __global__ void _updateFoundInfoKer( + int *label, float *dist, int *maxdistidx, int cstcnt, + int *foundflag, int *startidx, float threshold, int *foundidx) +{ + // 共享内存,用来存放当前 Block 处理的 LABEL 值,其长度为 BlockDim + 1,因 + // 为需要加载下一 Blcok 的第一个 LABEL 值。 + extern __shared__ int labelShd[]; + + // 基准索引。表示当前 Block 的起始位置索引 + int baseidx = blockIdx.x * blockDim.x; + + // 全局索引。 + int idx = baseidx + threadIdx.x; + + // 初始化 Shared Memory,将当前 Block 所对应的坐标点的 LABEL 值赋值给 + // Shared Memroy,为了程序健壮性的考虑,我们将处理越界数据的那些 Thread 所 + // 对应的 LABEL 值赋值为最后一个点的 LABEL 值。 + if (idx < cstcnt) + labelShd[threadIdx.x] = label[idx]; + else + labelShd[threadIdx.x] = label[cstcnt - 1]; + + // 使用每个 Block 中第 0 个 Thread 来初始化多出来的那个 LABEL 值,初始化的 + // 规则同上面的规则一样,也做了健壮性的考量。 + if (threadIdx.x == 0) { + if (baseidx + blockDim.x < cstcnt) + labelShd[blockDim.x] = label[baseidx + blockDim.x]; + else + labelShd[blockDim.x] = label[cstcnt - 1]; + + // 如果是第一块的话,起始索引更新。 + if (blockIdx.x == 0) + startidx[0] = 0; + } + + // 块内的线程同步 + __syncthreads(); + + // 对于处理越界数据的 Thread 直接进行返回操作,不进行任何处理。 + if (idx >= cstcnt) + return; + + // 当前 Thread 处理坐标点的 LABEL 值。 + int curlabel = labelShd[threadIdx.x]; + + // 对于单独处于一个 LABEL 区域的最后一个点,该点不需要做任何查找操作,直接 + // 赋值为未找到新的凸壳点。 + if (idx == cstcnt - 1) { + foundflag[curlabel] = 0; + foundidx[curlabel] = CH_LARGE_ENOUGH; + return; + } + + // 本函数只针对处于 LABEL 区域边界的点进行处理,对于不处于区域边界的点则直 + // 接返回。 + if (curlabel == labelShd[threadIdx.x + 1]) + return; + + // 读取当前 LABEL 区域的最大垂距和最大垂距所对应的下标和该最大垂距的值。 + int curmaxdistidx = maxdistidx[idx]; + float curmaxdist = dist[curmaxdistidx]; + + // 如果当前 LABEL 区域的最大垂距点的垂距值大于 0,则说明了在当前的 LABEL 区 + // 域内发现了点。为了健壮性的考虑,这里将 0 写为 1.0e-6。 + foundflag[curlabel] = (curmaxdist >= threshold) ? 1 : 0; + foundidx[curlabel] = (foundflag[curlabel] == 1 ? + curmaxdistidx : CH_LARGE_ENOUGH); + + // 更新下一个 LABEL 区域的起始下标。由于当前 Thread 是当前 LABEL 区域的最后 + // 一个,因此下一个 LABEL 区域的起始下标为当前 Thread 全局索引加 1。 + startidx[curlabel + 1] = idx + 1; +} + +// 成员方法: updateFoundInfo(更新新发现点信息) +__host__ int DouglasPeucker::updateFoundInfo( + int label[], float dist[], int maxdistidx[], + int cstcnt, int foundflag[], int startidx[], + float threshold, int foundidx[]) +{ + // 检查所有的输入指针或数组是否为 NULL,如果存在一个为 NULL 则报错退出。 + if (label == NULL || dist == NULL || maxdistidx == NULL || + foundflag == NULL || startidx == NULL || foundidx == NULL) + return NULL_POINTER; + + // 检查坐标点的数量是否小于等于 0,若是则报错推出。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量,以及所需要的 Shared + // Memory 的数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (blocksize + 1) * sizeof (int); + + // 调用 Kernel 函数,完成计算。 + _updateFoundInfoKer<<>>( + label, dist, maxdistidx, cstcnt, foundflag, + startidx, threshold, foundidx); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + + +// Kernel 函数: _updateCornerCstKer(生成新点集) +static __global__ void _updateCornerCstKer( + int cst[], int cornercst[], int foundflag[], + int foundacc[], int startidx[], int maxdistidx[], int cornercnt, + int newcornercst[]) +{ + // 计算当前 Thread 的全局索引。本 Kernel 中,每个线程都对应于一个 LABEL 区 + // 域,对于发现了新点的 LABEL 区域,则需要将原来这个 LABEL 点和新发现的点 + // 同时拷贝到新的点集中。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果该 Thread 对应的时越界数据,则直接返回,不进行任何处理。 + if (idx >= cornercnt) + return; + + // 计算原来的角点在新角点集中的下标,由于前面的 LABEL 区域共产生了 + // foundacc[idx] 个角点,因此,下标应相较于原来的下标(idx)增加了相应的 + // 数量。 + int newidx = idx + foundacc[idx]; + + // 将这个点的坐标从原来的点集中拷贝到新的点集中。 + newcornercst[2 * newidx] = cornercst[2 * idx]; + newcornercst[2 * newidx + 1] = cornercst[2 * idx + 1]; + + // 如果当前 LABEL 区域中没有发现新的点,则只需要拷贝原有的点到新的点集中。 + if (foundflag[idx] == 0) + return; + + // 计算新发现的点在点集中的下标和该点对应的坐标点集中的下标。由于最大垂距点 + // 下标数组是记录的 Scanning 操作的结果,因此正确的结果存放再该LABEL 区域 + // 最后一个下标处。 + newidx++; + int cstidx = maxdistidx[startidx[idx + 1] - 1]; + + // 将新发现的凸壳点从坐标点集中拷贝到新的凸壳点集中。 + newcornercst[2 * newidx] = cst[2 * cstidx]; + newcornercst[2 * newidx + 1] = cst[2 * cstidx + 1]; +} + +// Host 成员方法:updateCornerCst(生成新点集) +__host__ int DouglasPeucker::updateCornerCst( + int *cst, int *cornercst, int foundflag[], + int foundacc[], int startidx[], int maxdistidx[], int cornercnt, + int *newcornercst) +{ + // 检查参数中所有的指针和数组是否为空。 + if (cst == NULL || cornercst == NULL || foundacc == NULL || + foundflag == NULL || startidx == NULL || maxdistidx == NULL || + newcornercst == NULL) + return NULL_POINTER; + + // 检查当前角点的数量,小于等于 0 则无效数据。 + if (cornercnt <= 0) + return INVALID_DATA; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 矩阵方法分段扫描版本线程块大小。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cornercnt + blocksize - 1) / blocksize; + + // 调用 Kernel 函数完成计算。 + _updateCornerCstKer<<>>( + cst, cornercst, foundflag, foundacc, startidx, + maxdistidx, cornercnt, newcornercst); + // 判断 Kernel 函数是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _updateLabelKer(更新标签值) +static __global__ void _updateLabelKer( + int label[], int cstcnt, + int foundidx[], int foundacc[], int tmplabel[]) +{ + // 记录了本 Kernel 所使用到的共享内存中各个下标所存储的数据的含义。其中, + // SIDX_BLK_LABEL_LOW 和 SIDX_BLK_LABEL_UP 用来存当前 Block 中所加载的点集 + // 的区域标签值的上下界。根据这个上下界,可以计算出当前点所在区域的起止 + // 点,从而根据这两点确定的直线计算当前点的垂距。 + // 从下标为 SIDX_BLK_CORNER_X 开始的其后的所有共享内存空间存储了当前 Block + // 所处理的所有的新点的 X 坐标。 +#define SIDX_BLK_LABEL_LOW 0 +#define SIDX_BLK_LABEL_UP 1 +#define SIDX_BLK_CORNER_X 2 +#define SIDX_BLK_FOUND_ACC 2 + blockDim.x + // 共享内存的声明。 + extern __shared__ int shdmem[]; + + // 基准下标。表示当前 Block 第一个 Thread 所处理的下标。 + int baseidx = blockIdx.x * blockDim.x; + + // 当前 Thread 的全局下标。 + int idx = baseidx + threadIdx.x; + + // 初始化共享内存中的公共数据,为了防止写入冲突,这里只使用每个 Block 的第 + // 一个 Thread 处理初始化工作。 + if (threadIdx.x == 0) { + // 读取当前 Block 所处理的所有坐标点中最小的 LABEL 值。 + shdmem[SIDX_BLK_LABEL_LOW] = label[baseidx]; + + // 计算当前 Block 所处理的所有坐标点中最大的 LABEL 值。 + if (baseidx + blockDim.x <= cstcnt) + shdmem[SIDX_BLK_LABEL_UP] = label[baseidx + blockDim.x - 1]; + else + shdmem[SIDX_BLK_LABEL_UP] = label[cstcnt - 1]; + } + + // 同步 Block 内的所有 Thread,使得上述初始化对所有的 Thread 都可见。 + __syncthreads(); + + // 从 Shared Memory 中读取当前 Block 所处理的 LABEL 值范围。这一步骤没有实 + // 际的逻辑含义,将数据从共享内存搬入寄存器仅仅是为了加快处理速度。 + int labellower = shdmem[SIDX_BLK_LABEL_LOW]; + int labelupper = shdmem[SIDX_BLK_LABEL_UP]; + + // 并不是所有的 LABEL 区域都会在该论迭代中发现新点。该值要求非常的大,因为没 + // 有发现新凸壳点的区域,相当于所有的坐标点放在左侧。 +#define LP_DUMMY_CVXX CH_LARGE_ENOUGH + + // 将新点的 X 坐标存储 Shared Memory 提取出,用一个指针来表示,这样的写 + // 法是为了代码更加易于理解。 + int *newidx = &shdmem[SIDX_BLK_CORNER_X]; + int *foundaccShd = &shdmem[SIDX_BLK_FOUND_ACC]; + // 在 Shared Memory 中初始化新点(中心点)的 X 坐标。 + if (threadIdx.x < labelupper - labellower + 1) { + // 计算新点在新的点集中的下标。 + int labelidx = threadIdx.x + labellower; + newidx[threadIdx.x] = foundidx[labelidx]; + // 从 Global Memory 中读取新点的累加值。 + foundaccShd[threadIdx.x] = foundacc[threadIdx.x + labellower]; + } + + // 同步 Block 内的所有 Thread,是的上述所有初始化计算对所有 Thread 可见。 + __syncthreads(); + + // 如果当前 Thread 处理的是越界范围,则直接返回不进行任何处理。 + if (idx >= cstcnt) + return; + + // 读取当前坐标点所对应的 LABEL 值(经过校正的,表示 Shared Memory 中的下 + // 标)。 + int curlabel = label[idx] - labellower; + + // 对于所有垂距大于等于 0,且 x 坐标小于中心点坐标时认为该点在中心点左侧。 + if (idx < newidx[curlabel]) { + tmplabel[idx] = label[idx] + foundaccShd[curlabel]; + } + else + tmplabel[idx] = label[idx] + foundaccShd[curlabel] + 1; + + // 清除函数内部的宏定义,防止同后面的函数造成冲突。 +#undef LP_TMPX_DUMMY +#undef SIDX_BLK_LABEL_LOW +#undef SIDX_BLK_LABEL_UP +#undef SIDX_BLK_CORNER_X +#undef SIDX_BLK_FOUND_ACC +} + +// Host 成员方法:updateLabel(标记左侧点) +__host__ int DouglasPeucker::updateLabel( + int label[], int cstcnt, int foundidx[], int foundacc[], int tmplabel[]) +{ + // 检查参数中所有的指针和变量是否为空。 + if (label == NULL || foundacc == NULL || foundidx == NULL || tmplabel == NULL) + return NULL_POINTER; + + // 检查当前点的数量,小于等于 0 则无效数据。 + if (cstcnt <= 0) + return INVALID_DATA; + + // 计算 Kernel 函数所需要的 Block 尺寸和数量,以及每个 Block 所使用的 + // Shared Memory 的数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (cstcnt + blocksize - 1) / blocksize; + size_t sharedsize = (2 + 2 * blocksize) * sizeof (int); + + // 调用 Kernel 函数,完成计算。 + _updateLabelKer<<>>( + label, cstcnt, foundidx, foundacc, tmplabel); + + // 判断 Kernel 函数运行是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 宏:FAIL_CORNER_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_CORNER_FREE do { \ + if (tmpmemDev != NULL) \ + cudaFree(tmpmemDev); \ + } while (0) + +// 成员方法:cornerHullIter(迭代法求凸壳上的点集) +__host__ int DouglasPeucker::douglasIter( + int *inputcst, int *cornercst, float threshold, int count, int *cornerpnt) +{ + // 检查输入坐标集,输出坐标集是否为空。 + if (inputcst == NULL || cornercst == NULL) + return NULL_POINTER; + + // 局部变量 + cudaError_t cuerrcode; // CUDA 函数调用返回的错误码 + int errcode; // 调用函数返回的错误码 + + // 定义扫描所用的二元操作符。 + add_class add; + + int cornercnt = 2; // 当前角点的数量,由于迭代开始时,已经实 + // 现找到了点集中的最左和最有两点作为角 + // 点,因此这里直接赋值为 2。 + int foundcnt; // 当前迭代时找到的新点的数量,这一数量 + // 并不包含往次所找到的点。 + + int *tmpmemDev = NULL; // 存放中间变量的 Device 内存空间。 + size_t datacnt = 0; // 所需要的数据元素的数量。 + size_t datasize = 0; // 书需要的数据元素的字节尺寸。 + + // 宏:CHI_DATA_DECLARE(中间变量声明器) + // 为了消除中间变量声明过程中大量的重复代码,这里提供了一个宏,使代码看起来 + // 整洁一些。 +#define CHI_DATA_DECLARE(dataname, type, count) \ + type *dataname##Dev = NULL; \ + size_t dataname##cnt = (count); \ + datacnt += dataname##cnt; \ + datasize += dataname##cnt * sizeof (type) + + // 声明各个中间变量的 Device 数组。 + CHI_DATA_DECLARE(label, int, // 记录当前迭代中每个像素点所在的 + count); // LABEL 区域。 + CHI_DATA_DECLARE(maxdistidx, int, // 记录当前迭代中每个坐标点前面的所 + count); // 有点中和其在同一个 LABEL 区域的 + // 所有点中具有最大垂距的下标。 + CHI_DATA_DECLARE(foundflag, int, // 记录当前迭代中各个 LABEL 区域是 + count); // 否找到了新点。 + CHI_DATA_DECLARE(foundidx, int, // 记录当前迭代中各个 LABEL 区域是 + count); // 否找到了新点。 + CHI_DATA_DECLARE(foundacc, int, // 记录当前迭代中每个 LABEL 区域其 + count + 1); // 前面的所有 LABEL 区域共找到的新 + // 点的数量。该值用于计算各个点(无 + // 论是旧的还是新的)在新点集中的新下标。 + + CHI_DATA_DECLARE(startidx, int, // 记录每个 LABEL 区域在坐标点集中 + count); // 的起始下标 + CHI_DATA_DECLARE(tmplabel, int, // 记录新的标签值 + count); + CHI_DATA_DECLARE(tmpcstin, int, // 迭代过程中的临时数组,存放输入点集。 + count * 2); + CHI_DATA_DECLARE(tmpcornerin, int, // 迭代过程中的临时数组,存放截止上次迭 + count * 2); // 代已经找到的结果点集。 + CHI_DATA_DECLARE(tmpcornerout, int, // 迭代过程中的临时数组,存放本次找到的 + count * 2); // 结果点集。 + CHI_DATA_DECLARE(dist, float, // 存放垂距的数组 + count); + CHI_DATA_DECLARE(tmpmaxdist, float, // 存放分段扫描的结果值。 + count * 2); + // 消除中间变量声明器这个宏,防止后续步骤的命名冲突。 +#undef CHI_DATA_DECLARE + // 中间变量申请 Device 内存空间,并将这些空间分配给各个中间变量。 + cuerrcode = cudaMalloc((void **)&tmpmemDev, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_CORNER_FREE; + return CUDA_ERROR; + } + + // 为各个中间变量分配内存空间,采用这种一次申请一个大空间的做法是为了减少申 + // 请内存的开销,同时也减少因内存对齐导致的内存浪费。 + labelDev = tmpmemDev; + maxdistidxDev = labelDev + labelcnt; + foundflagDev = maxdistidxDev + maxdistidxcnt; + foundidxDev = foundflagDev + foundflagcnt; + foundaccDev = foundidxDev + foundidxcnt; + startidxDev = foundaccDev + foundacccnt; + tmplabelDev = startidxDev + startidxcnt; + tmpcstinDev = tmplabelDev + tmplabelcnt; + tmpcornerinDev = tmpcstinDev + tmpcstincnt; + tmpcorneroutDev = tmpcornerinDev + tmpcornerincnt; + distDev = (float *)tmpcorneroutDev + tmpcorneroutcnt; + tmpmaxdistDev = distDev + distcnt; + + // 调用 LABEL 初始化函数,完成 LABEL 初始化。初始化后,除最后一个元素为 1 + // 外,其余元素皆为 0。 + errcode = this->initLabelAry(labelDev, count); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + + // 初始化迭代过程中使用的坐标点集,这里一共需要使用到两个坐标点集,为了不破 + // 坏输入坐标点集,这里在迭代过程中我们使用内部申请的坐标点集。 + + // 一个临时数组,存放的是曲线的首尾坐标,用来初始化结果点集。 + int temp[4]= {inputcst[0], inputcst[1], + inputcst[2 * (count - 1)], + inputcst[2 * (count - 1) + 1]}; + // 为 tmpcstinDev 赋初值。 + cudaMemcpy(tmpcstinDev, inputcst, count * sizeof(int) * 2, + cudaMemcpyHostToDevice); + + // 初始化结果点集。 + cuerrcode = cudaMemcpy(tmpcornerinDev, temp, + sizeof (int) * 4, cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + FAIL_CORNER_FREE; + return CUDA_ERROR; + } + // 所有的初始化过程至此全部完毕,开始进行迭代。每次迭代都需要重新计算坐标点 + // 在其 LABEL 区域内的垂距,然后根据垂距信息判断每个 LABEL 区域内是否存在新 + // 的凸壳点(如果有需要确定是哪一个点),之后根据这个新发现的角点点,计算所 + // 有坐标点在下一轮迭代中的下标。迭代的过程知道无法在从当前所有的 LABEL 区域 + // 内找到新的点为止。 + + while (count >= cornercnt) { + // 调用更新垂距函数。更新点集中每个点的垂距值。 + errcode = this->updateDist(tmpcstinDev, tmpcornerinDev, labelDev, count, + distDev); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + + // 利用分段扫描得到各个 LABEL 区域的最大垂距,记忆最大垂距坐标点的下标 + // 值 + errcode = this->segScan.segmentedScan( + distDev, labelDev, tmpmaxdistDev, maxdistidxDev, count, false); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + + // 根据所求出来的垂距信息判断各个 LABEL 区域是否有新的点存在。 + errcode = this->updateFoundInfo( + labelDev, distDev, maxdistidxDev, + count, foundflagDev, startidxDev, threshold, foundidxDev); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + + // 通过扫描,计算出 LABEL 区域新发现点标记值对应的累加值。 + errcode = this->aryScan.scanArrayExclusive(foundflagDev, foundaccDev, + cornercnt, add, + false, false, false); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + + // 将新点标记累加值的最后一个拷贝到 Host 内存中,这个累加值的含义是 + // 当前迭代下所有新发现点的数量。 + cuerrcode = cudaMemcpy(&foundcnt, &foundaccDev[cornercnt], + sizeof (int), cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_CORNER_FREE; + return errcode; + } + + // 如果新发现点的数量小于等于 0,则说明所有的角点都已经被找到, + // 没有必要在继续做下去了,因此退出迭代。 + if (foundcnt <= 0) + break; + + // 更新点集 + errcode = this->updateCornerCst( + tmpcstinDev, tmpcornerinDev, foundflagDev, foundaccDev, startidxDev, + maxdistidxDev, cornercnt, tmpcorneroutDev); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + // 更新角点点集中点的数量。 + cornercnt += foundcnt; + *cornerpnt = cornercnt; + // 标记左侧点。所谓左侧点是在某 LABEL 区域内处于新发现的点左侧的点。 + errcode = this->updateLabel(labelDev, count, foundidxDev, + foundaccDev, tmplabelDev); + if (errcode != NO_ERROR) { + FAIL_CORNER_FREE; + return errcode; + } + + // 交还部分中间变量,将本轮迭代得到的结果给到下一轮迭代的参数。 + labelDev = tmplabelDev; + int *cstswptmp = tmpcornerinDev; + tmpcornerinDev = tmpcorneroutDev; + tmpcorneroutDev = cstswptmp; + // 一轮迭代到此结束。 + } + + // 将结果点集拷贝到 cornercst中 + cuerrcode = cudaMemcpy( + cornercst, tmpcornerinDev, cornercnt * sizeof(int) * 2, + cudaMemcpyDeviceToHost); + + // 释放内存 + cudaFree(tmpmemDev); + + // 操作完毕,退出。 + return NO_ERROR; +} +#undef FAIL_CORNER_FREE + +// Host 成员方法:douglasPeucker(道格拉斯算法简化曲线) +__host__ int DouglasPeucker::douglasPeucker( + Curve *incur, Curve *outcur) +{ + // 检查指针性参数是否为 NULL。 + if (incur == NULL || outcur == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + int point = 0; + // 调用凸壳迭代,求输入点集的下半凸壳。 + errcode = this->douglasIter(incur->crvData, outcur->crvData, this->threshold, + incur->curveLength, &point); + outcur->curveLength = point; + if (errcode != NO_ERROR) + return errcode; + // 处理完毕,退出 + return NO_ERROR; +} + + + diff --git a/okano_3_0/DouglasPeucker.h b/okano_3_0/DouglasPeucker.h new file mode 100644 index 0000000..1a58a3c --- /dev/null +++ b/okano_3_0/DouglasPeucker.h @@ -0,0 +1,329 @@ +// DouglasPeucker.h + +// 创建者:刘婷 + +// 道格拉斯普克算法(DouglasPeucker) +// 功能说明:利用 GPU CUDA 并行实现 DouglasPeucker 算法,利用迭代求角点。首先曲 +// 线的起始点和最末一个点自动视为结果点集中的点,利用这两个点生成直线,计算其他 +// 点到该直线的距离,如果最大的距离大于了阈值(参数)则将其加入到结果点集中,并 +// 将原曲线分解为两条曲线,再以此迭代下去,直到找不到符合条件的点。 + +// 修订历史: + +// 2013年11月02日(刘婷) +// 完成头文件和核函数的设计。 +// 2013年11月04日(刘婷) +// 实现核函数 _initLabelAryKer +// 2013年11月05日(刘婷) +// 实现核函数 _updateDistKer +// 2013年11月06日(刘婷) +// 实现核函数 _updateFoundInfoKer +// 2013年11月07日(刘婷) +// 实现核函数 _updateCornerCstKer +// 2013年11月09日(刘婷) +// 实现核函数 _markLeftPointsKer +// 2013年11月10日(刘婷) +// 实现核函数 _updatePropertyKer +// 2013年11月12日(刘婷) +// 实现核函数 _arrangeCstKer +// 2013年11月15日(刘婷) +// 调试检查代码 +// 2013年11月15日(刘婷) +// 测试代码,代码规范 +// 2013年11月23日(刘婷) +// 更改结果点集初始化方式,直接将存放起点和终点的数组拷贝至 GPU 端的结果点集 +// 中。 +// 2013年12月05日(刘婷) +// 整理代码。 +// 2013年12月06日(刘婷) +// 将主函数的参数改为 Curve 类型。 + +#ifndef __DOUGLASPEUCKER_H__ +#define __DOUGLASPEUCKER_H__ + +#include "Curve.h" + +#include "ErrorCode.h" + +#include "ScanArray.h" + +#include "SegmentedScan.h" + +#include "OperationFunctor.h" + + + +// 类:DouglasPeucker + +// 继承自:无 + +// 功能说明:利用 GPU CUDA 并行实现 DouglasPeucker 算法,利用迭代求角点。首先曲 +// 线的起始点和最末一个点自动视为结果点集中的点,利用这两个点生成直线,计算其他 +// 点到该直线的距离,如果最大的距离大于了阈值(参数)则将其加入到结果点集中,并 +// 将原曲线分解为两条曲线,再以此迭代下去,直到找不到符合条件的点。 + +class DouglasPeucker { + +protected: + + + // 成员变量:segScan(分段扫描器) + // 完成分段扫描,主要在迭代过程中用于计算每个 LABEL 区域的最大垂距点。 + SegmentedScan segScan; + + // 成员变量:aryScan(扫描累加器) + + // 完成非分段扫描,主要在迭代过程中用于计算各个标记值所对应的累加值。 + ScanArray aryScan; + + // 成员变量:threshold(与垂距相关的阈值) + // 用于与 label 内最大垂距做比较的阈值 + float threshold; + + // Host 成员方法:initLabelAry(初始化 LABEL 数组) + // 在迭代之前初始化 LABEL 数组,初始化后该数组最后一个元素为 1,其余元素皆为 + // 0, 用此来划分区域,及将曲线分解为多条曲线。 + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执行,返 + // 回 NO_ERROR。 + + initLabelAry( + + int label[], // 待初始化的 LABEL 数组。 + + int cstcnt // 数组长度。 + + ); + + // Host 成员方法:updateDist(计算各点垂距) + + // 根据目前已知结果点集和区域的标签值,根据点到直线的垂距公式,计算点 + + // 集的附带数据:点到当前所在区域的最左最右点构成的直线的垂直距离。 + + __host__ int // 返回值:函数是否正确执行,如果函数能够正 + + // 确执行,返回 NO_ERROR。 + + updateDist( + + int *cst, // 输入点集坐标 + + int *cornercst, // 目前已知结果点集,即每段的最值点信息 + + int label[], // 输入,当前点集的区域标签值数组。 + + int cstcnt, // 输入,当前点的数量。 + + float dis[] // 记录每一个点的垂距 + + + ); + + // Host 成员方法: updateFoundInfo(更新新发现的结果点集) + + // 根据分段扫描后得到的点集信息,更新各个区域是否有新发现的点,更新这些点的 + + // 位置索引。 + + __host__ int // 返回值:函数是否正确执行,如果函数能够正确执 + + // 行,返回 NO_ERROR。 + + updateFoundInfo( + + int label[], // 输入,当前点集的区域标签值数组。 + + float dist[], // 输入数组,分段扫描后,当前位置记录的本段目前 + + // 已知的最大垂距。 + + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目前已知 + + // 的最大垂距点的位置索引数组。 + + int cstcnt, // 当前点的数量。 + + int foundflag[], // 输出数组,如当前区域内找到新的凸壳上的点,标 + + // 志位置 1。 + + int startidx[], // 输出,目前已知的凸壳上的点的位置索引数组,也 + + // 相当于当前每段上的起始位置的索引数组。 + float thread, + int foundidx[] // 记录找到的点的一维索引 + + ); + + // Host 成员方法:updateCornerCst(生成新结果点集,以下用角点简称) + + // 根据分段扫描后得到的点集信息,和每段上是否发现新凸壳点的信息,构造新的结 + + // 果点集。 + + __host__ int // 返回值:函数是否正确执行,如果函数能够 + + // 正确执行,返回 NO_ERROR。 + + updateCornerCst( + + int *cst, // 输入点集 + + int *cornercst, // 输入,现有的结果点集。 + + int foundflag[], // 输入,当前区域内有新发现点的标志数组, + + // 如果当前区域内找到新的点,标志位置 1。 + + int foundacc[], // 输入,偏移量数组,当前区域内有新发现点 + + // 的标志位的累加值。用来计算新添加的点的存 + + // 放位置的偏移量。 + + int startidx[], // 输入,目前已知的结果点集的点索引数组, + + // 也相当于当前每段上的起始位置的索引数组 + + int maxdistidx[], // 输入,分段扫描后,当前位置记录的本段目 + + // 前已知的最大垂距点的位置索引数组。 + + int num, // 当前找到的角点的数量。 + + int *newcornercst // 输出,更新后目前已知角点的点集坐标 + + ); + + + + // Host 成员方法:updateLabel(更新标签值) + + // 根据目前每段上是否有新发现角点的标志,更新点的标签值。 + + __host__ int // 返回值:函数是否正确执行,如果函数能够 + + // 正确执行,返回 NO_ERROR。 + + updateLabel( + + int label[], // 输入,当前点集的区域标签值数组。 + + + int cstcnt, // 输入,当前输入点集的数量。 + int foundidx[], // 输入,已经找到的角点在原输入点集中的索引 + int foundacc[], // 输入,用来找到当前点之前有多少个已经找到的角点。 + int tmplabel[] // 输出,新的标签值。 + + ); + + + + // Host 成员方法:douglasIter(迭代法求曲线点) + + // 采用迭代的方法找到曲线的角点 + + __host__ int // 返回值:函数是否正确执行,如果函数能够 + + // 正确执行,返回 NO_ERROR。 + + douglasIter( + + int *inputcst, // 输入点集。 + int *corner , // 输出角点点集 + float threshold, // 用来判断是否为新发现点的阈值 + int count, // 输入点集的点的个数 + int *cornerpnt // 得到的结果点的个数 + ); + + +public: + // 构造函数:DouglasPeucker + + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + + __host__ __device__ + + DouglasPeucker() + + { + + // 配置扫描器。 + + this->aryScan.setScanType(NAIVE_SCAN); + + + + // 使用默认值为类的各个成员变量赋值。 + + this->threshold = 0.3f; // 设置垂距阈值 + + } + + // 成员方法:getThreshold(获取垂距阈值) + + // 获取与最大垂距相关的阈值。 + + __host__ __device__ float // 返回值:与最大垂距相关的阈值。 + + getThreshold() const + + { + + // 返回与最大垂距相关的阈值。 + + return this->threshold; + + } + + + + // 成员方法:setThreshold(设置垂距阈值) + + // 设置垂距阈值。 + + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + + // 返回 NO_ERROR。 + + setThreshold( + + float threshold // 设定新的垂距阈值。 + + ) { + + // 根据阈值设置转换标志位。 + + if (threshold < 0) + + return INVALID_DATA; + + + + // 将图像转换点集的像素阈值赋成新值。 + + this->threshold = threshold; + return NO_ERROR; + + } + + + // Host 成员方法:douglasPeucker(利用 Douglas Peucker 算法求解角点) + + // 利用 Douglas Peucker 算法求解角点 + + __host__ int // 返回值:函数是否正确执行,如果函数能够正确 + + // 执行,返回 NO_ERROR。 + + douglasPeucker( + + Curve *incur, // 输入曲线 + + Curve *outcur // 结果曲线 + + ); + +}; + +#endif diff --git a/okano_3_0/DynamicArrays.cu b/okano_3_0/DynamicArrays.cu new file mode 100644 index 0000000..13a8f7b --- /dev/null +++ b/okano_3_0/DynamicArrays.cu @@ -0,0 +1,277 @@ +// DynamicArrays +// 实现动态数组 + +#include "DynamicArrays.h" + + +// 成员方法:addElem(往动态数组增加元素) +// 往动态数组末尾增加一个元素 +__host__ int DynamicArrays::addElem(int elem) +{ + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 如果当前大小大于等于最大容量,重新修改最大容量值,把需要的值添加进数组里 + if (size >= maxsize) { + int sz = maxsize; + maxsize = maxsize * 2; + int *tmp = new int[maxsize]; + int i; + for (i = 0; i < sz; i++) { + tmp[i] = array[i]; + } + tmp[i] = elem; + delete []array; + array = tmp; + size++; + return NO_ERROR; + } + // 直接把 elem 添加进数组里 + array[size++] = elem; + return NO_ERROR; +} + +// 成员方法:addTail(往动态数组末尾增加两个元素) +// 往动态数组末尾同时增加两个元素,赋值曲线坐标的 x 轴和 y 轴坐标 +__host__ int DynamicArrays::addTail(int x, int y) +{ + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 如果当前大小大于等于最大容量,重新修改最大容量值,把需要的值从尾部添加 + // 进数组里 + if (size >= maxsize) { + int sz = maxsize; + maxsize = maxsize * 2; + int *tmp = new int[maxsize]; + int i; + for (i = 0; i < sz; i++) + tmp[i] = array[i]; + tmp[i++] = x; + tmp[i++] = y; + delete []array; + array = tmp; + size += 2; + return NO_ERROR; + } + + // 直接把需要添加的两个数添加进数组,因为最大容量是偶数,当前数组大小也为 + // 偶数,所以可以直接添加,不会越界 + array[size++] = x; + array[size++] = y; + return NO_ERROR; +} + +// 成员方法:addHead(往动态数组头部增加两个元素) +// 往动态数组首部同时增加两个元素,赋值曲线坐标的 x 轴和 y 轴坐标 +__host__ int DynamicArrays::addHead(int x, int y) +{ + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 如果当前大小大于等于最大容量,重新修改最大容量值,把需要的值从首部添加 + // 进数组里 + if (size >= maxsize) { + int sz = maxsize; + maxsize = maxsize * 2; + int *tmp = new int[maxsize]; + int i; + for (i = 0; i < sz; i++) + tmp[i + 2] = array[i]; + tmp[0] = x; + tmp[1] = y; + delete []array; + array = tmp; + size += 2; + return NO_ERROR; + } + + // 直接把需要添加的两个数添加进数组 + int *temp = new int[maxsize]; + for (int j = 0; j < size; j++) { + temp[j + 2] = array[j]; + } + temp[0] = x; + temp[1] = y; + delete []array; + array = temp; + size += 2; + return NO_ERROR; +} + +// 成员方法:delElem(删除数组中相邻值为 x,y 的点) +// 曲线跟踪辅助函数,删除曲线中值为 x,y 的点,前提是数组里有这个点的坐标,这个 +// 需要编写代码的人自己把握,这个删除其实就是两个元素和最后的两个元素交换,动态 +// 数组大小减少 2 +__host__ int DynamicArrays::delElem(int x, int y) +{ + // 如果数组大小小于 2,则报错返回 + if (size < 2) + return UNIMPLEMENT; + + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 定义局部变量 + int j; + // 找到数组中值为 x,y 的点,并且删除,这里的删除不是真正意义的删除,为了 + // 加快速度是直接和末尾两个数交换,并且使当前数组大小减 2 + for (int i = 0; i < size;) { + if (array[i++] == x && array[i++] == y) { + j = i - 2; + array[j] = array[size - 2]; + array[j + 1] = array[size - 1]; + size = size - 2; + return NO_ERROR; + } + } + // 没有找到的话,就报错返回 + return UNIMPLEMENT; +} + +// 成员方法:delElemXY(删除数组中相邻值为 x,y 的点) +// 曲线跟踪辅助函数,删除曲线中值为 x,y 的点,前提是数组里有这个点的坐标,这个 +// 需要编写代码的人自己把握,删除后,后续元素往前移动两个位置,数组大小减少 2 +__host__ int DynamicArrays::delElemXY(int x, int y) +{ + // 如果数组大小小于 2,则报错返回 + if (size < 2) + return UNIMPLEMENT; + + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 定义局部变量 + int j; + // 找到数组中值为 x,y 的点,并且删除,后续元素往前移动两个位置,并且使当前 + // 数组大小减 2 + for (int i = 0; i < size;) { + if (array[i++] == x && array[i++] == y) { + j = i - 2; + for (; j < size - 2; j++) { + array[j] = array[j + 2]; + } + size = size - 2; + return NO_ERROR; + } + } + // 没有找到的话,就报错返回 + return UNIMPLEMENT; +} + +// 成员方法:delTail(删除末尾最后一个数) +// 实现动态数组实现栈的 pop 方式,并且得到栈顶元素 +__host__ int DynamicArrays::delTail(int &elem) +{ + // 如果数组大小为 0 报错返回 + if (size == 0) + return UNIMPLEMENT; + + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 得到最末尾的数值,即栈顶数 + elem = array[size - 1]; + + // 容量大小减 1 + size--; + + // 正确执行返回 + return NO_ERROR; +} + +// 成员方法:reverse(动态数组以成对坐标反转) +// 实现动态数组得到的曲线坐标进行点坐标的反转 +__host__ int DynamicArrays::reverse() +{ + // 如果指针为空,报错返回 + if (array == NULL) + return NULL_POINTER; + + // 定义临时局部变量 + int temp, j = 0; + // 定义循环次数 + int count = size / 4; + // 以坐标对为基数进行点坐标的反转 + for (int i = 0; i < count; i++) { + temp = array[j]; + array[j] = array[size - j - 2]; + array[size - j - 2] = temp; + j++; + temp = array[j]; + array[j] = array[size - j]; + array[size - j] = temp; + j++; + } + // 函数正确执行,返回 + return NO_ERROR; +} + +// 成员方法:findElem(查找动态数组里是否有元素 elem) +// 查找动态数组里是否有外界给定的元素 elem +__host__ bool DynamicArrays::findElem(int elem) +{ + // 找到返回 true + for (int i = 0; i < size; i++) { + if (elem == array[i]) + return true; + } + + // 没找到返回 false + return false; +} + +// 成员方法:findElem(查找动态数组里是否有要查找的坐标对) +__host__ bool DynamicArrays::findElemXY(int x, int y) +{ + // 找到返回 true + for (int i = 0; i < size / 2; i++) { + if ((x == array[2 * i]) && (y == array[2 * i + 1])) + return true; + } + + // 没找到返回 false + return false; +} + +// 成员方法:addArray(动态数组的连接) +// 连接两个动态数组,曲线跟踪辅助函数,实现两个曲线的连接 +__host__ int DynamicArrays::addArray(DynamicArrays &object) +{ + // 如果指针为空,报错返回 + if (object.getCrvDatap() == NULL) + return NULL_POINTER; + + // 重新申请一个大小为两个数组最大容量和的数组 ,并且拷贝当前数组的值 + int sz = this->size; + this->size = this->size + object.getSize(); + this->maxsize = this->maxsize + object.maxsize; + int *temp = new int[this->maxsize]; + memcpy(temp, array, sz * sizeof (int)); + delete []array; + array = temp; + + // 定义临时变量 + int i, j; + // 开始衔接数组,如果要连接的曲线坐标的首部和当前曲线尾部是一样的, + // 则从第二个点坐标开始连接,否则直接连接 + if ((array[sz - 2] == object[0]) && (array[sz - 1] == object[1])) { + // 跳过当前曲线坐标的尾部 + this->size = this->size - 2; + for (i = sz - 2, j = 0; i < this->size; i++, j++) { + array[i] = object[j]; + } + } else { + for (i = sz, j = 0; i < this->size; i++, j++) { + array[i] = object[j]; + } + } + // 函数正确执行返回 + return NO_ERROR; +} diff --git a/okano_3_0/DynamicArrays.h b/okano_3_0/DynamicArrays.h new file mode 100644 index 0000000..6c06fba --- /dev/null +++ b/okano_3_0/DynamicArrays.h @@ -0,0 +1,204 @@ +// DynamicArrays.h +// 创建者:欧阳翔 +// +// 动态数组(DynamicArrays) +// 功能说明:辅助性数据结构,为了便于管理内存分配和释放,主要是为了实现曲线跟踪 +// 设计的。其中也实现了栈的部分功能,当作为曲线跟踪存储点的坐标时, +// 是成对存在的,也就是说两个坐标时同时添加进去,任何时候,当前数组 +// 大小是偶数个。当作为栈时,不限制数组大小是奇数还是偶数。 +// +// 修订历史: +// 2013年08月01日(欧阳翔) +// 初始版本,动态数组的实现 +// 2013年09月10日(欧阳翔) +// 增加了 delElemXY 函数,用于删除动态数组中坐标为 x y 的点。 + +#ifndef _DYNAMICARRAYS_H +#define _DYNAMICARRAYS_H + +#include +#include "ErrorCode.h" + + +// 类:DynamicArrays(动态数组) +// 继承自:无 +// 广义的图像中值滤波。具有两种不同的方法实现:(1)根据给定半径 radius +// 辅助性数据结构,为了便于管理内存分配和释放,主要是为了实现曲线跟踪设计的。 +// 其中也实现了栈的部分功能,当作为曲线跟踪存储点的坐标时,成对存在的, +// 也就是说两个坐标时同时添加进去,任何时候,当前数组大小是偶数个。当作为栈时, +// 不限制数组大小是奇数还是偶数。 +class DynamicArrays { + +protected: + + // 成员变量:array(整型指针参数) + // 动态数组自己的内部指针 + int *array; + + // 成员变量:size(动态数组元素个数参数) + // 表示当前动态数组元素个数 + int size; + + // 成员变量:maxsize(动态数组最大容量参数) + // 表示当前动态数组最大容量数 + int maxsize; + +public: + + // 构造函数: DynamicArrays + // 当外界没有设置数组最大容量数时,选择默认值,否则设置最大容量数,并初始化 + // 其他成员变量 + __host__ __device__ + DynamicArrays( + int sz = 50 // 默认设置为 50 否则设置为外界给定的 sz + ) { + this->size = 0; // 初始时,动态数组没有元素,当前元素 + // 个数为 0 + this->maxsize = sz; // 设置当前容量大小 + array = new int[maxsize]; // 动态申请大小 + } + + // 复制构造函数: DynamicArrays + // 由于涉及到对象当做参数传递的情况,并且动态数组需要动态管理内存,需要实现 + // 复制构造函数,防止出现意外的错误 + __host__ __device__ + DynamicArrays(const DynamicArrays &object) { + // 复制一份新的内存空间,并且拷贝出一份完全一样的数据 + size = object.size; + maxsize = object.maxsize; + array = new int[maxsize]; + memcpy(array, object.array, size * sizeof (int)); + } + + // 成员方法:getSize(得到当前动态数组的元素个数) + // 读取 size 成员变量的值 + __host__ __device__ int + getSize() const + { + // 返回 size 成员变量的值 + return size; + } + + // 成员方法:getMaxsize(得到当前动态数组的最大容量) + // 读取 maxsize 成员变量的值 + __host__ __device__ int + getMaxsize() const + { + // 返回 maxsize 成员变量的值 + return maxsize; + } + + // 成员方法:array(得到当前动态数组的指针) + // 读取 array 成员变量的值 + __host__ __device__ int * + getCrvDatap() const + { + // 返回 array 成员变量的值 + return array; + } + + // 成员方法:operator[] (重载中括号) + // 得到下标为 i 的元素 + __host__ int & + operator[](int i) { + // 得到下标为 i 的元素 + return array[i]; + } + + // 成员方法:addElem(往动态数组增加元素) + // 往动态数组末尾增加一个元素 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR + addElem( + int elem // 增加的元素 elem + ); + + // 成员方法:addTail(往动态数组末尾增加两个元素) + // 往动态数组末尾同时增加两个元素,赋值曲线坐标的 x 轴和 y 轴坐标 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR + addTail( + int x, // 末尾添加的元素 x + int y // 末尾添加的元素 y + ); + + // 成员方法:addHead(往动态数组头部增加两个元素) + // 往动态数组首部同时增加两个元素,赋值曲线坐标的 x 轴和 y 轴坐标 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR + addHead( + int x, // 首部添加的元素 x + int y // 首部添加的元素 y + ); + + // 成员方法:delElem(删除数组中为相邻值 x,y 的点) + // 曲线跟踪辅助函数,删除曲线中值为 x,y 的点,这是一种快速删除方式 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR + delElem( + int x, // 删除值为 x 的数 + int y // 删除值为 y 的数 + ); + + // 成员方法:delElemXY(删除数组中为相邻值 x,y 的点) + // 曲线跟踪辅助函数,删除曲线中值为 x,y 的点,这个删除方式相对前一种缓慢 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR + delElemXY( + int x, // 删除值为 x 的数 + int y // 删除值为 y 的数 + ); + + // 成员方法:delTail(删除末尾最后一个数) + // 实现动态数组实现栈的 pop 方式,并且得到栈顶元素 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + delTail( + int &elem // 得到删除的末尾元素 + ); + + // 成员方法:reverse(动态数组以成对坐标反转) + // 实现动态数组得到的曲线坐标进行点坐标的反转 + __host__ int + reverse(); + + // 成员方法:findElem(查找动态数组里是否有元素 elem) + // 查找动态数组里是否有外界给定的元素 elem + __host__ bool // 返回值:如果找到返回 true,否则返回 false + findElem( + int elem // 要查找的元素 + ); + + // 成员方法:findElem(查找动态数组里是否有要查找的坐标对) + // 查找动态数组里是否有外界给定的坐标对 + __host__ bool // 返回值:如果找到返回 true,否则返回 false + findElemXY( + int x, // 要查找的横坐标 x + int y // 要查找的纵坐标 y + ); + + // 成员方法:addArray(动态数组的连接) + // 连接两个动态数组,曲线跟踪辅助函数,实现两个曲线的连接 + __host__ int + addArray( + DynamicArrays &object // 连接在后边的动态数组 + ); + + // 成员方法:clear(动态数组内容的清空) + // 辅助实现栈函数,用于栈的清空 + __host__ __device__ int // 返回值:函数执行正确,返回 NO_ERROR + clear() + { + // 设置 size 为 0 + size = 0; + // 无错返回 + return NO_ERROR; + } + + // 析构函数:~DynamicArrays + // 释放内存空间 + __host__ __device__ + ~DynamicArrays() + { + // 如果不为空就释放空间 + if (array != NULL) + delete []array; + } +}; + +#endif diff --git a/okano_3_0/EdgeCheck.cu b/okano_3_0/EdgeCheck.cu new file mode 100644 index 0000000..79dfc31 --- /dev/null +++ b/okano_3_0/EdgeCheck.cu @@ -0,0 +1,1230 @@ +// EdgeCheck.h +// 边缘异常点和片段检查 + +#include "EdgeCheck.h" + +#include +#include +using namespace std; + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLOCK_1D +// 定义一维块大小。 +#define DEF_BLOCK_1D 256 + +// 宏:DEF_COL_MAX +// 定义欧式距离最大列数。 +#define DEF_COL_MAX 1024 + +// 宏:DEF_HUMOM_SIZE +// 定义 Hu 矩大小。 +#define DEF_HUMOM_SIZE 7 + +// 宏:ERR_EDEFORM 和 ERR_EHUMOM +// 定义错误码。 +#define ERR_EDEFORM 200 +#define ERR_EHUMOM 150 + +// 宏:DEF_ERR_COUNT +// errmap 中每个点的邻域大小中错误点的个数。 +#define DEF_ERR_COUNT 3 + +// 宏:DEF_INVALID_FLOAT +// 定义 float 类型的无效数据。 +#define DEF_INVALID_FLOAT 100000000.0f + + +// Kernel 函数:_edgeMatchKer(边缘匹配算法) +// 计算测试图像每个参考图像的相关系数。 +static __global__ void // Kernel 函数无返回值 +_edgeMatchKer( + ImageCuda teimg, // 测试图像 + ImageCuda *reimg, // 参考图像数组 + int recount, // 参考图像数量 + float *cormapsum // 相关系数数组 +); + +// Kernel 函数:_getCormapMaxIndexKer(获取 cormapsum 中最大的值的索引) +// 在匹配得到的结果中找到最大的值。 +static __global__ void // Kernel 函数无返回值 +_getCormapMaxIndexKer( + float *cormap, // cormapsum 的数据 + int count, // cormapsum 中数据的数量 + int *maxindx // 最大值索引 +); + +// Kernel 函数:_imgConvertCstKer(实现将图像转化为坐标集算法) +// 当输入参数为坐标集时,此算法将细化后的图像转化为输出坐标集。 +static __global__ void // Kernel 函数无返回值 +_imgConvertCstKer( + ImageCuda outimg, // 输出图像 + CoordiSet outcst, // 输出坐标 + unsigned char highpixel, // 高像素 + int *outcstcount // 坐标集索引 +); + +// Kernel 函数:_localMomentsKer(计算边缘点的 local moments) +// 计算边缘坐标集合上的每个点的 local moments。 +static __global__ void // Kernel 函数无返回值 +_localMomentsKer( + CoordiSet cdset, // 边缘的坐标集合 + int width, // 顺逆时针跟踪的宽度 + float *moments // local moments 特征矩阵 +); + +// Kernel 函数:_euclidMatKer(计算边缘点间的欧式距离) +// 计算参考边缘和测试边缘的所有点的欧式距离,输出到矩阵中。 +static __global__ void // Kernel 函数无返回值 +_euclidMatKer( + CoordiSet recdset, // 参考边缘的坐标集合 + CoordiSet tecdset, // 测试边缘的坐标集合 + int group, // 参考边缘的分段大小 + float *eudmat, // 欧式距离矩阵 + float *indexmat // 索引下标矩阵 +); + +// Kernel 函数: _findRowMinKer(查找行最小值) +// 根据差值矩阵 diffmatrix,查找每一行的最小值,并将每一行出现最小 +// 值的行列号保存在数组 rowmin 中。 +static __global__ void // Kernel 函数无返回值 +_findRowMinKer( + float *eudmat, // 欧式距离矩阵 + float *indexmat, // 索引下标矩阵 + int matwidth, // 距离列数大小 + int rowlen, // 最大列数 + float maxdis2, // 欧式距离阈值平方 + int *rowmin // 行最小值矩阵 +); + +// Kernel 函数: _relateCoeffKer(计算特征向量间的相关系数) +// 根据测试边缘点和参考边缘点的对应关系,计算 local moments 特征向量间的标准 +// 相关系数。 +static __global__ void // Kernel 函数无返回值 +_relateCoeffKer( + float *temoments, // 测试边缘点的 local moments 特征矩阵 + float *remoments, // 测试边缘点的 local moments 特征矩阵 + float mincor, // 标准相关系数的阈值大小 + int *rowmin // 相关点的对应索引值 +); + +// Kernel 函数: _makeErrmapKer(错误码标记输入到 errmap 图像中) +// 根据 rowmin 中的错误码标记,输入到 errmap 图像中。 +static __global__ void // Kernel 函数无返回值 +_makeErrmapKer( + CoordiSet tecdset, // 边缘的坐标集合 + int *rowmin, // 相关点的对应索引值 + ImageCuda errmap // 错误码图像 +); + +// Kernel 函数: _confirmErrmapKer(确定 errmap 中的错误点为异常) +// 根据 errmap 图像中每个错误点,如果其 3 * 3 邻域内包括 3 个以上的异常点, +// 则确定当前错误点为异常点。 +static __global__ void // Kernel 函数无返回值 +_confirmErrmapKer( + ImageCuda errmap, // 错误码图像 + ImageCuda errpoint // 异常点图像 +); + +// Kernel 函数:_edgeMatchKer(边缘匹配算法) +static __global__ void _edgeMatchKer(ImageCuda teimg, ImageCuda *reimg, + int recount, float *cormapsum) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标的 + // x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 8 个输出像素,这八个像素位于统一列的相邻行上, + // 因此,对于 r 需要进行乘 8 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 8; + int z = blockIdx.z; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + // 同时检查 z 的值,若大于参考图像数量则 z 值无效,直接返回。 + if (c >= teimg.imgMeta.width || r >= teimg.imgMeta.height || z >= recount) + return; + + // 申请一个 float 型共享内存,用于存储每每个块内线程所计算相关系数总和。 + __shared__ float cormap; + + // 读取线程号。 + int threadid = threadIdx.y * blockDim.x + threadIdx.x; + + // 局部变量,存储当前线程内像素点的相关系数和。 + float tcormap = 0.0f; + + // 存储测试图像和参考图像的像素值。 + unsigned char tepix,repix; + + // 用每个块内的0号线程给共享内存赋初值。 + if (threadid == 0 ) + cormap = 0.0f; + + // 块内同步。 + __syncthreads(); + + // 计算测试图像第一个输入坐标点对应的图像数据数组下标。 + int teimgidx = r * teimg.pitchBytes + c; + + // 计算参考图像第一个输入坐标点对应的图像数据数组下标。 + int reimgidx = r * reimg[z].pitchBytes + c; + + // 读取第一个输入坐标点对应的像素值。 + tepix = teimg.imgMeta.imgData[teimgidx]; + repix = reimg[z].imgMeta.imgData[reimgidx]; + + // 计算当前两个点的相关系数。 + tcormap = tepix * repix; + + // 处理后 7 个点。 + for(int j = 1; j < 8; j++) { + // y 分量加 1 。 + r++; + // 获取当前像素点坐标。 + teimgidx += teimg.pitchBytes; + reimgidx += reimg[z].pitchBytes; + + // 若当前像素点越界,则跳过该点,处理下一点;否则计算当前点的相关系数。 + if (r < teimg.imgMeta.height) { + // 读取第 j 个输入坐标点对应的测试图像和参考图像像素值。 + tepix = teimg.imgMeta.imgData[teimgidx]; + repix = reimg[z].imgMeta.imgData[reimgidx]; + + // 计算当前两个点的相关系数并累加。 + tcormap += tepix * repix; + } + } + + // 原子操作将当前线程所计算相关系数和累加到共享内存中。 + tcormap = tcormap / (255 * 255); + atomicAdd(&cormap, tcormap); + + // 块内同步。 + __syncthreads(); + + // 每个块内 0 号线程将该块所计算的相关系数和累加入 + // 每个参考图像总的相关系数和中。 + if (threadid == 0 ) + // 原子操作将该块所计算的相关系数和累加入每个参考图像总的相关系数和中。 + atomicAdd(&cormapsum[z], cormap); +} + +// Kernel 函数:_imgConvertCstKer(实现将图像转化为坐标集算法) +static __global__ void _imgConvertCstKer(ImageCuda outimg, CoordiSet outcst, + unsigned char highpixel, + int *outcstcount) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // dstc 表示 column, dstr 表示 row )。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return ; + + // 定义目标点位置的指针。 + unsigned char *outptr; + + // 获取当前像素点在图像中的相对位置。 + int curpos = dstr * outimg.pitchBytes + dstc; + + // 获取当前像素点在图像中的绝对位置。 + outptr = outimg.imgMeta.imgData + curpos; + + // 若当前像素值等于 highpixel 值。 + if (*outptr == highpixel) { + // 原子操作获得当前坐标点的索引值。 + int idx = atomicAdd(outcstcount, 1); + + // 保存图像的横纵坐标到输出坐标集中。 + outcst.tplData[idx * 2] = dstc; + outcst.tplData[idx * 2 + 1] = dstr; + } +} + +// Kernel 函数:_getCormapMaxIndexKer(获取 cormapsum 中最大的值的索引) +static __global__ void _getCormapMaxIndexKer(float *cormapcpu, + int count,int *maxindex) +{ + // 获取当前线程的线程号。 + int threadtid = threadIdx.x; + + // 声明共享内存,保存当前块内的相关系数矩阵和索引矩阵。 + extern __shared__ float datashare[]; + + if (threadtid < count) { + // 将当前线程对应的相关系数矩阵中的值以及其对应的索引(即列号)保存 + // 在该块的共享内存中。 + datashare[threadtid] = *(cormapcpu + threadtid); + datashare[threadtid + count] = threadtid; + } else { + datashare[threadtid] = DEF_INVALID_FLOAT; + datashare[threadtid + count] = DEF_INVALID_FLOAT; + } + + // 块内同步,为了保证一个块内的所有线程都已经完成了上述操作,即存 + // 储该行的欧式距离和索引到共享内存中。 + __syncthreads(); + + // 使用双调排序的思想,找到该行的最小值。 + for (int k = 1; k < count; k <<= 1) { + // 对待排序的元素进行分组,每次都将较大的元素交换到数组中 + // 较前的位置,然后改变分组大小,进而在比较上一次得到的较大值 + // 并做相应的交换,以此类推,最终数组中第 0 号元素存放的是该行 + // 的最大值。 + if (((threadtid % (k << 1)) == 0) && + datashare[threadtid] < datashare[threadtid + k] ) { + // 两个值进行交换。 + float temp1 = datashare[threadtid]; + datashare[threadtid] = datashare[threadtid + k]; + datashare[threadtid + k] = temp1; + + // 交换相对应的索引 index 值。 + float temp2 = datashare[threadtid + count]; + datashare[threadtid + count] = datashare[threadtid + k + count]; + datashare[threadtid + k + count] = temp2; + } + // 块内同步。 + __syncthreads(); + } + + // 将最大值的索引保存在 maxindex 中。 + *maxindex = (int)datashare[count]; +} + +// Kernel 函数:_localMomentsKer(计算边缘点的 local moments) +static __global__ void _localMomentsKer(CoordiSet cdset, int width, + float *moments) +{ + // 读取线程号。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 获得坐标集大小。 + int count = cdset.count; + + // 多余的线程直接退出。 + if (idx >= count) + return; + + // 声明空间矩。 + float m00 = 0.0f, m01 = 0.0f, m10 = 0.0f, m11 = 0.0f, m02 = 0.0f, + m20 = 0.0f, m12 = 0.0f, m21 = 0.0f, m03 = 0.0f, m30 = 0.0f; + + // 计算当前边缘点的空间矩。 + for (int i = -width; i <= width; i++) { + // 判断邻域是否越界,注意坐标集合的首尾显示上是相连的。 + int temp = idx + i; + if (temp < 0) + temp = count + temp; + if (temp > count - 1) + temp = temp - count; + + // 获得当前边缘点的横纵坐标。 + int xdata = cdset.tplData[2 * temp]; + int ydata = cdset.tplData[2 * temp + 1]; + + // 计算当前边缘点的空间矩。 + m00 += 1; + m01 += ydata; + m10 += xdata; + m11 += xdata * ydata; + m02 += ydata * ydata; + m20 += xdata * xdata; + m12 += xdata * ydata * ydata; + m21 += xdata * xdata * ydata; + m03 += ydata * ydata * ydata; + m30 += xdata * xdata * xdata; + } + + // 声明矩中心。 + float centerx = 0.0f, centery = 0.0f; + centerx = m10 / m00; + centery = m01 / m00; + + // 声明中心矩。 + float u00, /*u01, u10,*/ u11, u02, u20, u12, u21, u03, u30; + float centerx2 = centerx * centerx; + float centery2 = centery * centery; + + // 计算当前边缘点的中心矩。 + u00 = m00; + // u01 = 0.0f; + // u10 = 0.0f; + u11 = m11 - centerx * m01; + u20 = m20 - centerx * m10; + u02 = m02 - centery * m01; + u21 = m21 - 2 * centerx * m11 - centery * m20 + 2 * centerx2 * m01; + u12 = m12 - 2 * centery * m11 - centerx * m02 + 2 * centery2 * m10; + u30 = m30 - 3 * centerx * m20 + 2 * centerx2 * m10; + u03 = m03 - 3 * centery * m02 + 2 * centery2 * m01; + + // 声明正规矩。 + float /*n00, n01, n10,*/ n11, n02, n20, n12, n21, n03, n30; + float temp1= pow(u00, 2.0f), temp2 = pow(u00, 2.5f); + + // 计算当前边缘点的中心矩。 + n11 = u11 / temp1; + n20 = u20 / temp1; + n02 = u02 / temp1; + n21 = u21 / temp2; + n12 = u12 / temp2; + n30 = u30 / temp2; + n03 = u03 / temp2; + + // 计算当前边缘点在特征矩阵中对应的行数。 + float * humoments = moments + idx * DEF_HUMOM_SIZE; + + // 声明临时变量,减少重复计算。 + float t0 = n30 + n12, t1 = n21 + n03; + float q0 = t0 * t0, q1 = t1 * t1; + float n4 = 4 * n11; + float s = n20 + n02, d = n20 - n02; + + // 计算 Hu 矩值 0, 1, 3, 5。 + humoments[0] = s; + humoments[1] = d * d + n4 * n11; + humoments[3] = q0 + q1; + humoments[5] = d * (q0 - q1) + n4 * t0 * t1; + + // 改变临时变量。 + t0 *= q0 - 3 * q1; + t1 *= 3 * q0 - q1; + q0 = n30 - 3 * n12; + q1 = 3 * n21 - n03; + + // 计算 Hu 矩值 2, 4, 6。 + humoments[2] = q0 * q0 + q1 * q1; + humoments[4] = q0 * t0 + q1 * t1; + humoments[6] = q1 * t0 - q0 * t1; +} + +// Kernel 函数:_euclidMatKer(计算边缘点间的欧式距离) +static __global__ void _euclidMatKer(CoordiSet recdset, CoordiSet tecdset, + int group, float *eudmat, float *indexmat) +{ + // 获取当前线程的块号。 + int blocktid = blockIdx.x; + // 获取当前线程的线程号。 + int threadtid = threadIdx.x; + // 计算矩阵中对应的输出点的位置。 + int inidx = blockIdx.x * blockDim.x + threadIdx.x; + + // 获得当前线程对应的测试边缘点的坐标。 + int tecurx = tecdset.tplData[2 * blocktid]; + int tecury = tecdset.tplData[2 * blocktid + 1]; + + // 保存分段内 group 个距离的最小值。 + float mindis = DEF_INVALID_FLOAT; + // 记录最小值下标。 + int minindex = 0; + + // 测试边缘的每个坐标点与分段内 group 个参考边缘的坐标点计算欧式距离。这样 + // 可以减少欧式矩阵的大小,便于后续算法操作。 + for (int i = 0; i < group; i++) { + // 获得参考边缘点的坐标。 + int tempidx = threadtid * group + i; + int recurx = recdset.tplData[2 * tempidx]; + int recury = recdset.tplData[2 * tempidx + 1]; + // 计算欧式距离。 + float distemp = (float)((tecurx - recurx) * (tecurx - recurx) + + (tecury - recury) * (tecury - recury)); + + // 记录最小值和下标。 + if (distemp < mindis) { + mindis = distemp; + minindex = tempidx; + } + } + + // 将最小值保存到矩阵的当前元素中。 + *(eudmat + inidx) = mindis; + *(indexmat + inidx) = (float)minindex; +} + +// Kernel 函数: _findRowMinKer(查找行最小值) +static __global__ void _findRowMinKer(float *eudmat, float *indexmat, + int matwidth, int rowlen, + float maxdis2, int *rowmin) +{ + // 获取当前线程的块号。 + int blocktid = blockIdx.x; + // 获取当前线程的线程号。 + int threadtid = threadIdx.x; + // 计算当前线程在矩阵中的偏移。 + int tid = blockIdx.x * blockDim.x + threadIdx.x; + + // 声明共享内存,保存当前块内的欧式距离矩阵和索引矩阵。 + extern __shared__ float datashare[]; + + if (threadtid < matwidth) { + // 将当前线程对应的差值矩阵中的值以及其对应的索引(即列号)保存 + // 在该块的共享内存中。 + datashare[threadtid] = *(eudmat + tid); + datashare[threadtid + rowlen] = *(indexmat + tid); + } else { + datashare[threadtid] = DEF_INVALID_FLOAT; + datashare[threadtid + rowlen] = DEF_INVALID_FLOAT; + } + + // 块内同步,为了保证一个块内的所有线程都已经完成了上述操作,即存 + // 储该行的欧式距离和索引到共享内存中。 + __syncthreads(); + + // 使用双调排序的思想,找到该行的最小值。 + for (int k = 1; k < rowlen; k <<= 1) { + // 对待排序的元素进行分组,每次都将距离较小的元素交换到数组中 + // 较前的位置,然后改变分组大小,进而在比较上一次得到的较小值 + // 并做相应的交换,以此类推,最终数组中第 0 号元素存放的是该行 + // 的最小值。 + if (((threadtid % (k << 1)) == 0) && + datashare[threadtid] > datashare[threadtid + k] ) { + // 两个欧式距离进行交换。 + float temp1 = datashare[threadtid]; + datashare[threadtid] = datashare[threadtid + k]; + datashare[threadtid + k] = temp1; + + // 交换相对应的索引 index 值。 + float temp2 = datashare[threadtid + rowlen]; + datashare[threadtid + rowlen] = datashare[threadtid + k + rowlen]; + datashare[threadtid + k + rowlen] = temp2; + } + // 块内同步。 + __syncthreads(); + } + + // 将当前行最小值出现的列号保存在数组 rowmin 中。如果最小距离大于指定阈值, + // 则设置错误码 ERR_EDEFORM。 + if (datashare[0] < maxdis2) + rowmin[blocktid] = (int)datashare[rowlen]; + else + rowmin[blocktid] = ERR_EDEFORM; +} + +// Kernel 函数: _relateCoeffKer(计算特征向量间的相关系数) +static __global__ void _relateCoeffKer( + float *temoments, float *remoments, float mincor, int *rowmin) +{ + // 读取线程号。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 获得当前测试边缘点对应的参考边缘点坐标。 + int corr = rowmin[idx]; + + // 如果没有对应的参考边缘点,则不处理当前的测试边缘点。 + if (corr == ERR_EDEFORM) + return; + + // 获得当前测试边缘点的 local moments。 + float *tepoint = temoments + idx * DEF_HUMOM_SIZE; + + // 获得对应的参考边缘点的 local moments。 + float *repoint = remoments + idx * DEF_HUMOM_SIZE; + + // 计算标准相关系数。 + float sum = 0.0f; + for (int i = 0; i < 7; i++) { + sum += tepoint[i] * repoint[i]; + } + sum /= 7; + + if (sum < mincor) + rowmin[idx] = ERR_EHUMOM; +} + +// Kernel 函数: _makeErrmapKer(错误码标记输入到 errmap 图像中) +static __global__ void _makeErrmapKer(CoordiSet tecdset, int *rowmin, + ImageCuda errmap) +{ + // 读取线程号。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 判断线程是否超出界限。 + if (idx >= tecdset.count) + return; + + // 获得当前点在 errmap 图像中的位置。 + int curerr = tecdset.tplData[2 * idx + 1] * errmap.pitchBytes + + tecdset.tplData[2 * idx]; + + // 如果当前标记是错误码的话,则是异常点。 + if (rowmin[idx] == ERR_EDEFORM || rowmin[idx] == ERR_EHUMOM) { + // 将错误码输出到 errmap 图像中,表示该点是异常点。 + errmap.imgMeta.imgData[curerr] = (unsigned char)rowmin[idx]; + } else { + // 否则设置值为 0,表示非异常点。 + errmap.imgMeta.imgData[curerr] = 0; + } +} + +// Kernel 函数: _confirmErrmapKer(确定 errmap 中的错误点为异常) +static __global__ void _confirmErrmapKer(ImageCuda errmap, ImageCuda errpoint) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃 + if (dstc >= errmap.imgMeta.width || dstr >= errmap.imgMeta.height) + return; + + // 记录输入图像对应位置。 + unsigned char *curinptr; + curinptr = errmap.imgMeta.imgData + dstc + dstr * errmap.pitchBytes; + + // 如果当前点不是错误点则直接退出。 + if (*curinptr != ERR_EDEFORM && *curinptr != ERR_EHUMOM) + return; + + // 记录输出图像对应位置。 + unsigned char *curoutptr; + curoutptr = errpoint.imgMeta.imgData + dstc + dstr * errpoint.pitchBytes; + + // 因为是存放邻域内错误点的个数,所以先初始化为最小值 0x00。 + unsigned char count = 0; + + // 保存邻域的像素值。 + unsigned char neighpixel; + + for (int j = dstr - 1; j <= dstr + 1; j++) { + for (int i = dstc - 1; i <= dstc + 1; i++) { + // 判断当前像素是否越界。 + if (j >= 0 && j < errmap.imgMeta.height && + i >= 0 && i < errmap.imgMeta.width) { + // 循环计算每个邻域内错误点的个数。 + neighpixel = *(errmap.imgMeta.imgData + i + + j * errmap.pitchBytes); + if (neighpixel == ERR_EDEFORM || neighpixel == ERR_EHUMOM) + count++; + + // 如果计数个数大于 DEF_ERR_COUNT 个,则确定当前点为异常点, + // 并结束循环。 + if (count >= DEF_ERR_COUNT) { + *curoutptr = *curinptr; + return; + } + } + } + } +} + +// 宏:FAIL_EDGECHECK_SPACE_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_EDGECHECK_SPACE_FREE do { \ + if (reimgCud != NULL) \ + delete []reimgCud; \ + if (tecdset != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(tecdset); \ + if (recdset != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(recdset); \ + if (reimgcudDev != NULL) \ + cudaFree(reimgcudDev); \ + if (alldevpointer != NULL) \ + cudaFree(alldevpointer); \ + if (alldevpointermat != NULL) \ + cudaFree(alldevpointermat); \ + if (errmap != NULL) \ + ImageBasicOp::deleteImage(errmap); \ + } while (0) + +// Host 成员方法:edgeCheckPoint(边缘的异常点检查) +__host__ int EdgeCheck::edgeCheckPoint(Image *teimg, Image *errpoint) +{ + // 检查测试图像, errpoint 和参考图像是否为空,若为空则直接返回。 + if (teimg == NULL || errpoint == NULL || reImages == NULL) + return NULL_POINTER; + + // 检查每幅参考图像是否为空,若为空则直接返回。 + for(int i = 0; i < reCount; i++) { + if(reImages[i] == NULL) + return NULL_POINTER; + } + + // 局部变量,错误码。 + int errcode; + + // 声明所有中间变量并初始化为空。 + ImageCuda *reimgCud = NULL; + CoordiSet *tecdset = NULL; + CoordiSet *recdset = NULL; + ImageCuda *reimgcudDev = NULL; + float *alldevpointer = NULL; + float *alldevpointermat = NULL; + Image *errmap = NULL; + + // 将测试图像数据拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(teimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取测试图像的 ROI 子图。 + ImageCuda teimgCud; + errcode = ImageBasicOp::roiSubImage(teimg, &teimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将所有参考图像拷贝入 Device 内存。 + for (int i = 0; i < reCount; i++) { + errcode = ImageBasicOp::copyToCurrentDevice(reImages[i]); + if (errcode != NO_ERROR) + return errcode; + } + + // 提取所有参考图像的 ROI 子图。 + reimgCud = new ImageCuda[reCount]; + + for (int i = 0; i < reCount; i++) { + errcode = ImageBasicOp::roiSubImage(reImages[i], &reimgCud[i]); + if (errcode != NO_ERROR) + return errcode; + } + + // 为 reimgcudDev 分配内存空间。 + errcode = cudaMalloc((void **)&reimgcudDev, + reCount * sizeof (ImageCuda)); + if (errcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 将 Host 上的 reimgCud 拷贝到 Device 上。 + errcode = cudaMemcpy(reimgcudDev, reimgCud, + reCount * sizeof (ImageCuda), cudaMemcpyHostToDevice); + // 判断是否拷贝成功,若失败,释放之前的空间,防止内存泄漏,然后返回错误。 + if (errcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 计算坐标集初始大小。 + int count = teimg->height * teimg->width; + + // 一次申请 Device 端所有空间。 + cudaError_t cudaerrcode; + + // 申明所有指针变量。 + float *cormapsumDev; + int *maxindexDev, *tecountdev, *recountdev; + float *deveudmat, *devindexmat; + int *devrowmin; + + // 为 alldevpointer 分配空间。 + cudaerrcode = cudaMalloc((void **)&alldevpointer, + (reCount + 3) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return cudaerrcode; + } + + // 初始化所有 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevpointer, 0, + (reCount + 3) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return cudaerrcode; + } + // 获得 cormapsumDev 位置指针。 + cormapsumDev = alldevpointer; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (teimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (teimgCud.imgMeta.height + blocksize.y * 8 - 1) / + (blocksize.y * 8); + gridsize.z = reCount; + + // 调用匹配函数对每个参考图像进行匹配。 + _edgeMatchKer<<>>(teimgCud,reimgcudDev, + reCount,cormapsumDev); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 获得 maxindexDev 位置指针。 + maxindexDev = (int *)(cormapsumDev + reCount); + + size_t blocksize1D, gridsize1D; + blocksize1D = 1; + // 调用 _getCormapMaxIndexKer 函数,找到相关系数最大的参考图像, + // 即为匹配图像。 + _getCormapMaxIndexKer<<>>(cormapsumDev, + reCount, + maxindexDev); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 将 Device 端的 maxindexDev 拷贝到 Host 端。 + int maxindex; + errcode = cudaMemcpy(&maxindex, maxindexDev, + sizeof (int), cudaMemcpyDeviceToHost); + // 判断是否拷贝成功,若失败,释放之前的空间,防止内存泄漏,然后返回错误。 + if (errcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 获得 tecountdev 和 recountdev 位置指针。 + tecountdev = maxindexDev + 1; + recountdev = tecountdev + 1; + + // 创建测试边缘的坐标集合和匹配得到的参考边缘的坐标集合。 + CoordiSetBasicOp::newCoordiSet(&tecdset); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + CoordiSetBasicOp::newCoordiSet(&recdset); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 在 Device 端创建测试边缘的坐标集合。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(tecdset,count); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 在 Device 端创建匹配参考边缘的坐标集合。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(recdset,count); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (teimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (teimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + gridsize.z = 1; + + // 将测试图像转化为坐标集。 + _imgConvertCstKer<<>>(teimgCud, *tecdset, + this->highPixel, + tecountdev); + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 将匹配参考图像转化为坐标集。 + _imgConvertCstKer<<>>(reimgCud[maxindex], *recdset, + this->highPixel, + recountdev); + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 将 Device 上的 recountdev 拷贝到 Host 上。 + int recount; + errcode = cudaMemcpy(&recount,recountdev, + sizeof (int), cudaMemcpyDeviceToHost); + // 判断是否拷贝成功,若失败,释放之前的空间,防止内存泄漏,然后返回错误。 + if (errcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 将 Device 上的 tecountdev 拷贝到 Host 上。 + int tecount; + errcode = cudaMemcpy(&tecount,tecountdev, + sizeof (int), cudaMemcpyDeviceToHost); + // 判断是否拷贝成功,若失败,释放之前的空间,防止内存泄漏,然后返回错误。 + if (errcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 测试边缘的每个点与参考边缘的 group 个点计算欧式距离。 + int group = (recount + DEF_COL_MAX - 1) / DEF_COL_MAX; + + // 计算欧式距离矩阵的宽度和高度。 + int matwidth = (recount + group - 1) / group; + int matheight = tecount; + + // 为 alldevpointermat 分配空间。 + cudaerrcode = cudaMalloc((void **)&alldevpointermat, + (recount * tecount + + 2 * matheight * matwidth) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return cudaerrcode; + } + + // 初始化所有 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevpointermat, 0, + (recount * tecount + 2 * matheight * matwidth) * + sizeof (float)); + if (cudaerrcode != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return cudaerrcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = matwidth; + gridsize1D = matheight; + + // 获得欧式距离矩阵和索引矩阵位置指针。 + deveudmat = alldevpointermat; + devindexmat = deveudmat + matheight * matwidth; + + // 调用核函数,计算参考边缘和测试边缘的所有点的欧式距离的矩阵。 + _euclidMatKer<<>>(*recdset, *tecdset, + group, deveudmat, devindexmat); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 获得行最小值数组指针。 + devrowmin = (int *)(devindexmat + matheight * matwidth); + + // 获得最小 2 的幂次数,使得排序的长度满足 2 的幂次方。 + int exponent = (int)ceil(log((float)matwidth) / log(2.0f)); + int length = (1 << exponent); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = length; + gridsize1D = matheight; + + // 调用核函数,计算行最小值,即找到点对应关系。 + _findRowMinKer<<>>( + deveudmat, devindexmat, matwidth, length, + maxDisPoint * maxDisPoint, devrowmin); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 申请中间错误码图像。 + errcode = ImageBasicOp::newImage(&errmap); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + // 大小和输入的 errpoint 一致。 + errcode = ImageBasicOp::makeAtCurrentDevice(errmap, errpoint->width, + errpoint->height); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 提取错误码子图像。 + ImageCuda errmapcud; + errcode = ImageBasicOp::roiSubImage(errmap, &errmapcud); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = DEF_BLOCK_1D; + gridsize1D = (tecount + DEF_BLOCK_1D - 1) / DEF_BLOCK_1D; + + // 调用核函数,将错误码标记输出到 errmap 图像中。 + _makeErrmapKer<<>>(*tecdset, devrowmin, errmapcud); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 将错误码图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(errpoint); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 提取错误码子图像。 + ImageCuda errpointcud; + errcode = ImageBasicOp::roiSubImage(errpoint, &errpointcud); + if (errcode != NO_ERROR) { + FAIL_EDGECHECK_SPACE_FREE; + return errcode; + } + + // 计算核函数调用的分块大小。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (errmapcud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (errmapcud.imgMeta.height + blocksize.y - 1) / blocksize.y; + gridsize.z = 1; + + // 调用核函数,根据错误码图像确定最终的异常点。 + _confirmErrmapKer<<>>(errmapcud, errpointcud); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_EDGECHECK_SPACE_FREE; + return CUDA_ERROR; + } + + // 释放 Device 端内存。 + FAIL_EDGECHECK_SPACE_FREE; + return NO_ERROR; +} + +// 取消前面的宏定义。 +#undef FAIL_EDGECHECK_SPACE_FREE + +// Host 成员方法:edgeCheckFragment(边缘的异常片段检查) +__host__ int EdgeCheck::edgeCheckFragment(CoordiSet *recdset, + CoordiSet *tecdset, + Image *errpoint) +{ + // 检查输入坐标集合和图像是否为 NULL,如果为 NULL 直接报错返回。 + if (recdset == NULL || tecdset == NULL || errpoint == NULL) + return NULL_POINTER; + + // 局部变量,错误码 + int errcode; + + // 将参考边缘的坐标集拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(recdset); + if (errcode != NO_ERROR) + return errcode; + + // 将测试边缘的坐标集拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(tecdset); + if (errcode != NO_ERROR) + return errcode; + + // 获取参考边缘和测试边缘点的个数。 + int recount = recdset->count; + int tecount = tecdset->count; + + // 测试边缘的每个点与参考边缘的 group 个点计算欧式距离。 + int group = (recount + DEF_COL_MAX - 1) / DEF_COL_MAX; + + // 计算欧式距离矩阵的宽度和高度。 + int matwidth = (recount + group - 1) / group; + int matheight = tecount; + + // 一次申请 Device 端所有空间。 + cudaError_t cudaerrcode; + float *alldevicepointer, *devremat, *devtemat, *deveudmat, *devindexmat; + int *devrowmin; + cudaerrcode = cudaMalloc((void **)&alldevicepointer, + (recount * DEF_HUMOM_SIZE + + tecount * DEF_HUMOM_SIZE + 2 * matheight * + matwidth + matheight) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 初始化所有 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevicepointer, 0, + (recount * DEF_HUMOM_SIZE + + tecount * DEF_HUMOM_SIZE + 2 * matheight * + matwidth + matheight) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 获得参考边缘的 local moments 特征矩阵 devremat。 + devremat = alldevicepointer; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + size_t blocksize1D, gridsize1D; + blocksize1D = DEF_BLOCK_1D; + gridsize1D = (recount + blocksize1D - 1) / blocksize1D; + + // 调用核函数,计算参考边缘的 local moments。 + _localMomentsKer<<>>(*recdset, this->followWidth, + devremat); + + // 若调用核函数出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 获得测试边缘的 local moments 特征矩阵 devremat。 + devtemat = devremat + recount * DEF_HUMOM_SIZE; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = DEF_BLOCK_1D; + gridsize1D = (tecount + blocksize1D - 1) / blocksize1D; + + // 调用核函数,计算测试边缘的 local moments。 + _localMomentsKer<<>>(*tecdset, this->followWidth, + devtemat); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = matwidth; + gridsize1D = matheight; + + // 获得欧式距离矩阵和索引矩阵位置指针。 + deveudmat = devtemat + tecount * DEF_HUMOM_SIZE; + devindexmat = deveudmat + matheight * matwidth; + + // 调用核函数,计算参考边缘和测试边缘的所有点的欧式距离的矩阵。 + _euclidMatKer<<>>(*recdset, *tecdset, + group, deveudmat, devindexmat); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 获得行最小值数组指针。 + devrowmin = (int *)(devindexmat + matheight * matwidth); + + // 获得最小 2 的幂次数,使得排序的长度满足 2 的幂次方。 + int exponent = (int)ceil(log((float)matwidth) / log(2.0f)); + int length = (1 << exponent); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = length; + gridsize1D = matheight; + + // 调用核函数,计算行最小值,即找到点对应关系。 + _findRowMinKer<<>>( + deveudmat, devindexmat, matwidth, length, + maxDis * maxDis, devrowmin); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = DEF_BLOCK_1D; + gridsize1D = (tecount + blocksize1D - 1) / blocksize1D; + + // 调用核函数,计算对应点间的特征向量的标准相关系数。 + _relateCoeffKer<<>>(devtemat, devremat, + this->minCor, devrowmin); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 申请中间错误码图像。 + Image *errmap; + errcode = ImageBasicOp::newImage(&errmap); + if (errcode != NO_ERROR) { + cudaFree(alldevicepointer); + return errcode; + } + // 大小和输入的 errpoint 一致。 + errcode = ImageBasicOp::makeAtCurrentDevice(errmap, errpoint->width, + errpoint->height); + if (errcode != NO_ERROR) { + cudaFree(alldevicepointer); + ImageBasicOp::deleteImage(errmap); + return errcode; + } + + // 提取错误码子图像。 + ImageCuda errmapcud; + errcode = ImageBasicOp::roiSubImage(errmap, &errmapcud); + if (errcode != NO_ERROR) { + cudaFree(alldevicepointer); + ImageBasicOp::deleteImage(errmap); + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize1D = DEF_BLOCK_1D; + gridsize1D = (tecount + blocksize1D - 1) / blocksize1D; + + // 调用核函数,将错误码标记输出到 errmap 图像中。 + _makeErrmapKer<<>>(*tecdset, devrowmin, errmapcud); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + ImageBasicOp::deleteImage(errmap); + return CUDA_ERROR; + } + + // 将输出的异常点图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(errpoint); + if (errcode != NO_ERROR) { + cudaFree(alldevicepointer); + ImageBasicOp::deleteImage(errmap); + return errcode; + } + + // 提取错误码子图像。 + ImageCuda errpointcud; + errcode = ImageBasicOp::roiSubImage(errpoint, &errpointcud); + if (errcode != NO_ERROR) { + cudaFree(alldevicepointer); + ImageBasicOp::deleteImage(errmap); + return errcode; + } + + // 计算核函数调用的分块大小。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (errmapcud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (errmapcud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用核函数,根据错误码图像确定最终的异常点。 + _confirmErrmapKer<<>>(errmapcud, errpointcud); + + // 若调用核函数出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + ImageBasicOp::deleteImage(errmap); + return CUDA_ERROR; + } + + // 释放中间图像。 + errcode = ImageBasicOp::deleteImage(errmap); + if (errcode != NO_ERROR) { + cudaFree(alldevicepointer); + return errcode; + } + + // 释放 Device 端内存。 + cudaFree(alldevicepointer); + + return NO_ERROR; +} + diff --git a/okano_3_0/EdgeCheck.h b/okano_3_0/EdgeCheck.h new file mode 100644 index 0000000..b46dc1d --- /dev/null +++ b/okano_3_0/EdgeCheck.h @@ -0,0 +1,292 @@ +// EdgeCheck.h +// 创建人:刘宇 +// +// 边缘异常点和片段检查(EdgeCheck) +// 功能说明:与标准边缘模板相比较,检查出测试边缘的异常点和片段。 +// +// 修订历史: +// 2012年12月10日(刘宇) +// 初始版本 +// 2013年1月6日(王雪菲) +// 添加测试边缘异常点检查 +// 2013年1月8日(刘宇、王雪菲) +// 改正一些逻辑错误,修改代码规范 +// 2013年02月25日(刘宇、王雪菲) +// 添加错误码图像的领域判断 + +#ifndef __EDGECHECK_H__ +#define __EDGECHECK_H__ + +#include "Image.h" +#include "CoordiSet.h" +#include "ErrorCode.h" + +// 类:EdgeCheck +// 继承自:无 +// 与标准边缘模板相比较,检查出测试边缘的异常点和片段。 +class EdgeCheck { + +protected: + + // 成员变量:followWidth(顺逆时针跟踪的宽度) + // 曲线进行顺逆时针双向跟踪的宽度。 + int followWidth; + + // 成员变量:maxDis(异常片段检查的欧式距离阈值大小) + // 边缘点间的欧式距离的阈值大小。 + float maxDis; + + // 成员变量:maxDisPoint(异常点检查的欧式距离阈值大小) + // 边缘点间的欧式距离的阈值大小。 + float maxDisPoint; + + // 成员变量:minCor(标准相关系数的阈值大小) + // 对应边缘点间的标准相关系数的阈值大小。 + float minCor; + + // 成员变量:reImages(参考图像数组) + // 存储指向参考图像的指针。 + Image **reImages; + + // 成员变量:reCount(参考图像数量) + // 记录参考图像的数量。 + int reCount; + + // 成员变量:highPixel(高像素) + // 将图像转化成坐标集时,图像的高像素值。 + unsigned char highPixel; + +public: + // 构造函数:EdgeCheck + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + EdgeCheck() + { + // 使用默认值为类的各个成员变量赋值。 + this->followWidth = 3; // 顺逆时针跟踪的宽度设为 3。 + this->maxDis = 10.0f; // 异常片段检查欧式距离阈值大小设为 10.0f。 + this->maxDisPoint = 10.0f; // 异常点检查欧式距离阈值大小设为 10.0f。 + this->minCor = 10000.0f; // 标准相关系数的阈值设为 10000.0f。 + this->reImages = NULL; // 参考图像设置为空。 + this->reCount = 0; // 参考图像数量设置为 0。 + this->highPixel = 255; // 高像素设为 255。 + } + + // 构造函数:EdgeCheck + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + EdgeCheck( + int followwidth, + int maxdis, + int maxdispoint, + int mincor, + Image **reimages, + int recount, + unsigned char highpixel + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->followWidth = 3; // 顺逆时针跟踪的宽度设为 3。 + this->maxDis = 10.0f; // 异常片段检查欧式距离阈值大小设为 10.0f。 + this->maxDisPoint = 10.0f; // 异常点检查欧式距离阈值大小设为 10.0f。 + this->minCor = 10000.0f; // 标准相关系数的阈值设为 10000.0f。 + this->reImages = NULL; // 参考图像设置为空。 + this->reCount = 0; // 参考图像数量设置为 0。 + this->highPixel = 255; // 高像素设为 255。 + + setFollowWidth(followwidth); + setMaxDis(maxdis); + setMaxDisPoint(maxdispoint); + setMinCor(mincor); + setReImages(reimages,recount); + setHighPixel(highpixel); + } + + // 成员方法:getFollowWidth(获取顺逆时针跟踪的宽度) + // 获取成员变量 followWidth 的值。 + __host__ __device__ int // 返回值:成员变量 followWidth 的值 + getFollowWidth() const + { + // 返回 followWidth 成员变量的值。 + return this->followWidth; + } + + // 成员方法:setFollowWidth(设置顺逆时针跟踪的宽度) + // 设置成员变量 followWidth 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setFollowWidth( + int followwidth // 设定新的顺逆时针跟踪的宽度 + ) { + // 将 followWidth 成员变量赋成新值。 + this->followWidth = followwidth; + + return NO_ERROR; + } + + // 成员方法:getMaxDis(获取异常片段检查欧式距离阈值大小) + // 获取成员变量 maxDis 的值。 + __host__ __device__ float // 返回值:成员变量 maxDis 的值 + getMaxDis() const + { + // 返回 maxDis 成员变量的值。 + return this->maxDis; + } + + // 成员方法:setMaxDis(设置异常片段检查欧式距离阈值大小) + // 设置成员变量 maxDis 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setMaxDis( + float maxdis // 设定新的欧式距离阈值大小 + ) { + // 将 maxDis 成员变量赋成新值。 + this->maxDis = maxdis; + + return NO_ERROR; + } + + // 成员方法:getMaxDisPoint(获取异常点检查欧式距离阈值大小) + // 获取成员变量 maxDisPoint 的值。 + __host__ __device__ float // 返回值:成员变量 maxDisPoint 的值 + getMaxDisPoint() const + { + // 返回 maxDisPoint 成员变量的值。 + return this->maxDisPoint; + } + + // 成员方法:setMaxDisPoint(设置异常点检查欧式距离阈值大小) + // 设置成员变量 maxDisPoint 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setMaxDisPoint( + float maxdispoint // 设定新的欧式距离阈值大小 + ) { + // 将 maxDisPoint 成员变量赋成新值。 + this->maxDisPoint = maxdispoint; + + return NO_ERROR; + } + + // 成员方法:getMinCor(获取标准相关系数的阈值大小) + // 获取成员变量 minCor 的值。 + __host__ __device__ float // 返回值:成员变量 minCor 的值 + getMinCor() const + { + // 返回 minCor 成员变量的值。 + return this->minCor; + } + + // 成员方法:setMinCor(设置标准相关系数的阈值大小) + // 设置成员变量 minCor 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setMinCor( + float mincor // 设定新的标准相关系数的阈值大小 + ) { + // 将 minCor 成员变量赋成新值。 + this->minCor = mincor; + + return NO_ERROR; + } + + // 成员方法:getReImages(获取 reImages) + // 获取成员变量 reImages 的值。 + __host__ __device__ Image ** // 返回值:成员变量 reImages 的值 + getReImages() const + { + // 返回 reImages 成员变量的值。 + return this->reImages; + } + + // 成员方法:getReCount(获取参考图像数目) + // 获取成员变量 reCount 的值。 + __host__ __device__ int // 返回值:成员变量 reCount 的值 + getReCount() const + { + // 返回 reCount 成员变量的值。 + return this->reCount; + } + + // 成员方法:setReImages(设置 reImages 的数据) + // 设置所有的 reImages 的数据。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setReImages( + Image **reimages, // 要设置的一组参考图像 + int count // 参考图像的数量 + ) { + // 判断 reimages 是否为空,若为空,直接返回错误。 + if (reimages == NULL) + return NULL_POINTER; + + // 判断 count 是否合法,若不合法,直接返回错误。 + if (count <= 0) + return INVALID_DATA; + + // 扫描 reimages 的每一个成员,检查每个成员是否为空。 + for (int i = 0; i < count; i++) { + // 若某个成员为空,则直接返回错误。 + if (reimages[i] == NULL) + return NULL_POINTER; + } + + // 更新参考图像数据。 + this->reImages = reimages; + // 更新 reCount 数据。 + this->reCount = count; + + // 处理完毕,返回 NO_ERROR。 + return NO_ERROR; + } + + // 成员方法:getHighPixel(获取高像素) + // 获取成员变量 highPixel 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 highPixel 的值 + getHighPixel() const + { + // 返回 highPixel 成员变量的值。 + return this->highPixel; + } + + // 成员方法:setHighPixel(设置高像素) + // 设置成员变量 highPixel 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setHighPixel( + int highpixel // 设定新的高像素 + ) { + // 将 highPixel 成员变量赋成新值。 + this->highPixel = highpixel; + + // 处理完毕,返回 NO_ERROR。 + return NO_ERROR; + } + + // Host 成员方法:edgeCheckPoint(边缘的异常点检查) + // 首先计算测试图像与各参考图像的相关系数并找到最匹配的参考图像, + // 然后将测试图像和匹配图像转化成坐标集并计算参考图像和测试图像 + // 所有点的欧式距离,若欧式距离值不满足规定阈值,则认为是异常点。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + edgeCheckPoint( + Image *teimg, // 测试图像 + Image *errpoint // 异常点图像 + ); + + // Host 成员方法:edgeCheckFragment(边缘的异常片段检查) + // 首先分别计算参考边缘和测试边缘的 local moment 特征,然后根据欧式距离找到 + // 对应点关系,计算对应点关系的标准相关系数,如果系数值不满足规定阈值,则认 + // 为是异常点。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + edgeCheckFragment( + CoordiSet *recdset, // 参考边缘坐标集 + CoordiSet *tecdset, // 测试边缘坐标集 + Image *errpoint // 异常点图像 + ); +}; + +#endif + diff --git a/okano_3_0/ErrorCode.h b/okano_3_0/ErrorCode.h new file mode 100644 index 0000000..c4b6002 --- /dev/null +++ b/okano_3_0/ErrorCode.h @@ -0,0 +1,62 @@ +// ErrorCode.h +// 创建者:于玉龙 +// +// 错误码(Error Codes) +// 功能说明:定义了系统中通用的错误码和状态码。按照委托方河边老师的要求,错误码 +// 使用负数;正确执行使用 0;其余的非错误状态使用正数。用户和各算法可 +// 以根据自己的需要定义额外的错误码和状态码。 +// +// 修订历史: +// 2012年07月15日(于玉龙) +// 初始版本。 + +#ifndef __ERRORCODE_H__ +#define __ERRORCODE_H__ + +// 宏:NO_ERROR(无错误) +// 它表示函数正确执行,没有错误发生。 +#define NO_ERROR 0 + +// 宏:INVALID_DATA(无效数据) +// 它表示参数中包含了无效的数据。 +#define INVALID_DATA -1 + +// 宏:NULL_POINTER(空指针) +// 它表示不可为 NULL 的变量或参数,意外的出现了 NULL 值。 +#define NULL_POINTER -2 + +// 宏:OVERFLOW(计算溢出) +// 它表示函数中某些计算产生了溢出,其中包括了除零错误。 +#define OP_OVERFLOW -3 + +// 宏:NO_FILE(文件未找到) +// 它表示函数未找到指定的文件。 +#define NO_FILE -4 + +// 宏:WRONG_FILE(文件错误) +// 它表示给定的文件的格式是错误的。 +#define WRONG_FILE -5 + +// 宏:OUT_OF_MEM(内存耗尽) +// 它表示当前已没有额外的内存支持所要进行的操作了。 +#define OUT_OF_MEM -6 + +// 宏:CUDA_ERROR(CUDA 错误) +// 它表示由于 CUDA 调用报错,无法继续完成相应的操作。 +#define CUDA_ERROR -7 + +// 宏:UNMATCH_IMG(图像尺寸不匹配) +// 它表示给定的图像尺寸和操作所要求的图像尺寸不匹配,无法进一步完成操作。 +#define UNMATCH_IMG -8 + +// 宏:UMIMPLEMENT(未实现) +// 它表示所调用的操作尚未实现,该错误不会出现在提交给委托方的代码中。 +#define UNIMPLEMENT -998 + +// 宏:UNKNOW_ERROR(未知错误) +// 它表示系统可断定是一个错误,但并不清楚错误的原因。 +#define UNKNOW_ERROR -999 + + +#endif + diff --git a/okano_3_0/FanczosIpl.cu b/okano_3_0/FanczosIpl.cu new file mode 100644 index 0000000..93ef96f --- /dev/null +++ b/okano_3_0/FanczosIpl.cu @@ -0,0 +1,8 @@ +// FanczosIpl.cu +// Fanczos 软件插值算法实现 + +#include "FanczosIpl.h" + +// 由于所有的函数都是 static 类型,已在 FanczosIpl.h 中定义,故本文件无任何代码 +// 内容。 + diff --git a/okano_3_0/FanczosIpl.h b/okano_3_0/FanczosIpl.h new file mode 100644 index 0000000..c1e4dcc --- /dev/null +++ b/okano_3_0/FanczosIpl.h @@ -0,0 +1,511 @@ +// FanczosIpl.h +// 创建者:于玉龙 +// +// Fanczos Interpolation(Fanczos 软件插值) +// 功能说明: +// +// 修订历史: +// 2012年12月12日(于玉龙) +// 初始版本,从 AffineTrans.cu 文件中分离出来。 +// 修改了输入参数 inimg 的传递形式为按引用传递。 + +#ifndef __FANCZOSIPL_H__ +#define __FANCZOSIPL_H__ + +#include "Image.h" + +// Device 子程序:_fanczosInterpoDev(Fanczos 插值) +// Fanczos 软件插值算法,根据给定的图像和指定的坐标,返回该坐标的插值值。注意, +// 为了加快程序的执行速度,这个函数并不是十分健壮,函数中,没有对 inimg 的数据 +// 合法性进行检查,所以调用该子程序一定要保证数据的合法性,否则会导致不可预知的 +// 结果。 +static __device__ unsigned char // 返回值:插值值 +_fanczosInterpoDev( + const ImageCuda &inimg, // 输入图像,希望 CUDA 足够聪明,这种按值传递 + // 参数的方法由于其 inline 的调用模式,不会带 + // 来性能的损失。 + float x, float y // 读取数据的坐标 +); + +// Device 全局常量:_fanczosCoeffDev(Fanczos 插值系数) +// 用于进行 Fanczos 软件插值的系数表。 +static __device__ const float _fanczosCoeffDev[10][10][16] = { + { // [0] + // [0][0] + { 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 1.000000, + 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 0.000000 }, + // [0][1] + { -0.245915, -0.007606, -0.265837, 0.036235, -0.130966, 1.869223, + 0.175148, -0.040077, -0.245915, -0.007606, -0.265837, 0.036235, + 0.046293, 0.000848, 0.041959, 0.003820 }, + // [0][2] + { -0.220745, -0.030128, -0.259005, 0.018282, -0.220755, 1.801697, + 0.396702, -0.091830, -0.220745, -0.030128, -0.259005, 0.018282, + 0.046299, 0.003405, 0.038003, 0.009671 }, + // [0][3] + { -0.183163, -0.065196, -0.235574, -0.010222, -0.266985, 1.653084, + 0.651196, -0.149528, -0.183163, -0.065196, -0.235574, -0.010222, + 0.043693, 0.007535, 0.032411, 0.016902 }, + // [0][4] + { -0.138411, -0.108582, -0.199271, -0.047797, -0.273987, 1.442786, + 0.922267, -0.205346, -0.138411, -0.108582, -0.199271, -0.047797, + 0.038828, 0.012944, 0.025923, 0.024709 }, + // [0][5] + { -0.091757, -0.155112, -0.155112, -0.091757, -0.250136, 1.192366, + 1.192366, -0.250136, -0.091757, -0.155112, -0.155112, -0.091757, + 0.032272, 0.019236, 0.019236, 0.032272 }, + // [0][6] + { -0.047797, -0.199271, -0.108582, -0.138411, -0.205346, 0.922267, + 1.442786, -0.273987, -0.047797, -0.199271, -0.108582, -0.138411, + 0.024709, 0.025923, 0.012944, 0.038828 }, + // [0][7] + { -0.010222, -0.235574, -0.065196, -0.183163, -0.149528, 0.651196, + 1.653085, -0.266985, -0.010222, -0.235574, -0.065196, -0.183163, + 0.016902, 0.032411, 0.007535, 0.043693 }, + // [0][8] + { 0.018282, -0.259005, -0.030128, -0.220745, -0.091830, 0.396702, + 1.801697, -0.220755, 0.018282, -0.259005, -0.030128, -0.220745, + 0.009671, 0.038003, 0.003405, 0.046299 }, + // [0][9] + { 0.036235, -0.265837, -0.007606, -0.245915, -0.040077, 0.175148, + 1.869223, -0.130966, 0.036235, -0.265837, -0.007606, -0.245915, + 0.003820, 0.041959, 0.000848, 0.046293 } + }, { // [1] + // [1][0] + { -0.245915, -0.130966, -0.245915, 0.046293, -0.007606, 1.869223, + -0.007606, 0.000848, -0.265837, 0.175148, -0.265837, 0.041959, + 0.036235, -0.040077, 0.036235, 0.003820 }, + // [1][1] + { -0.226157, -0.140049, -0.270076, 0.043489, -0.140049, 1.891722, + 0.169479, -0.040090, -0.270076, 0.169479, -0.259318, 0.029049, + 0.043489, -0.040090, 0.029049, 0.010148 }, + // [1][2] + { -0.193387, -0.157168, -0.279742, 0.031101, -0.229975, 1.819735, + 0.393373, -0.092974, -0.253452, 0.139746, -0.229975, 0.004358, + 0.047837, -0.037101, 0.019491, 0.018133 }, + // [1][3] + { -0.151962, -0.180783, -0.275321, 0.008558, -0.275321, 1.666033, + 0.650286, -0.151962, -0.219852, 0.089594, -0.180783, -0.031369, + 0.048915, -0.031369, 0.008558, 0.026777 }, + // [1][4] + { -0.106894, -0.208111, -0.259376, -0.023361, -0.280902, 1.451089, + 0.923877, -0.209184, -0.175061, 0.025207, -0.117273, -0.075754, + 0.046823, -0.023361, -0.002760, 0.035041 }, + // [1][5] + { -0.062781, -0.235621, -0.235621, -0.062781, -0.255485, 1.196923, + 1.196923, -0.255485, -0.125189, -0.046205, -0.046205, -0.125189, + 0.041987, -0.013627, -0.013627, 0.041987 }, + // [1][6] + { -0.023361, -0.259376, -0.208111, -0.106894, -0.209184, 0.923877, + 1.451089, -0.280902, -0.075754, -0.117272, 0.025207, -0.175061, + 0.035041, -0.002760, -0.023361, 0.046823}, + // [1][7] + { 0.008558, -0.275321, -0.180783, -0.151962, -0.151962, 0.650285, + 1.666033, -0.275321, -0.031369, -0.180783, 0.089595, -0.219852, + 0.026777, 0.008558, -0.031369, 0.048915 }, + // [1][8] + { 0.031101, -0.279742, -0.157168, -0.193387, -0.092974, 0.393373, + 1.819735, -0.229975, 0.004358, -0.229975, 0.139746, -0.253452, + 0.018133, 0.019491, -0.037101, 0.047837 }, + // [1][9] + { 0.043489, -0.270076, -0.140049, -0.226157, -0.040090, 0.169479, + 1.891722, -0.140049, 0.029049, -0.259318, 0.169479, -0.270076, + 0.010148, 0.029049, -0.040090, 0.043489 } + }, { // [2] + // [2][0] + { -0.220745, -0.220755, -0.220745, 0.046299, -0.030128, 1.801697, + -0.030128, 0.003405, -0.259005, 0.396702, -0.259005, 0.038003, + 0.018282, -0.091830, 0.018282, 0.009671 }, + // [2][1] + { -0.193387, -0.229975, -0.253452, 0.047837, -0.157168, 1.819735, + 0.139746, -0.037101, -0.279742, 0.393373, -0.229975, 0.019491, + 0.031101, -0.092974, 0.004358, 0.018133 }, + // [2][2] + { -0.156579, -0.241512, -0.275172, 0.041121, -0.241512, 1.745519, + 0.355495, -0.089783, -0.275172, 0.355495, -0.173507, -0.011604, + 0.041121, -0.089783, -0.011604, 0.027480 }, + // [2][3] + { -0.114726, -0.254647, -0.285522, 0.025124, -0.281773, 1.592521, + 0.603626, -0.148955, -0.248865, 0.288363, -0.094397, -0.053683, + 0.047468, -0.082679, -0.028258, 0.036405 }, + // [2][4] + { -0.072318, -0.267914, -0.286025, -0.000000, -0.283278, 1.381309, + 0.868400, -0.206984, -0.206984, 0.200462, 0.000000, -0.103448, + 0.049850, -0.072318, -0.044463, 0.043712 }, + // [2][5] + { -0.033132, -0.279226, -0.279226, -0.033132, -0.255000, 1.133429, + 1.133429, -0.255000, -0.156383, 0.101247, 0.101247, -0.156383, + 0.048436, -0.059371, -0.059371, 0.048436 }, + // [2][6] + { -0.000000, -0.286025, -0.267914, -0.072318, -0.206984, 0.868400, + 1.381309, -0.283278, -0.103448, 0.000000, 0.200462, -0.206984, + 0.043712, -0.044463, -0.072318, 0.049850 }, + // [2][7] + { 0.025124, -0.285522, -0.254647, -0.114726, -0.148955, 0.603625, + 1.592521, -0.281773, -0.053683, -0.094397, 0.288363, -0.248865, + 0.036405, -0.028258, -0.082679, 0.047468 }, + // [2][8] + { 0.041121, -0.275172, -0.241512, -0.156579, -0.089783, 0.355494, + 1.745518, -0.241512, -0.011604, -0.173507, 0.355495, -0.275172, + 0.027480, -0.011604, -0.089783, 0.041121 }, + // [2][9] + { 0.047837, -0.253452, -0.229975, -0.193386, -0.037101, 0.139745, + 1.819734, -0.157168, 0.019491, -0.229975, 0.393373, -0.279742, + 0.018133, 0.004358, -0.092974, 0.031101 } + }, { // [3] + // [3][0] + { -0.183163, -0.266985, -0.183163, 0.043693, -0.065196, 1.653084, + -0.065196, 0.007535, -0.235574, 0.651196, -0.235574, 0.032411, + -0.010222, -0.149528, -0.010222, 0.016902 }, + // [3][1] + { -0.151962, -0.275321, -0.219852, 0.048915, -0.180783, 1.666033, + 0.089594, -0.031369, -0.275321, 0.650286, -0.180783, 0.008558, + 0.008558, -0.151962, -0.031369, 0.026777 }, + // [3][2] + { -0.114726, -0.281773, -0.248865, 0.047468, -0.254647, 1.592521, + 0.288363, -0.082679, -0.285522, 0.603626, -0.094397, -0.028258, + 0.025124, -0.148955, -0.053683, 0.036405 }, + // [3][3] + { -0.075566, -0.286304, -0.269214, 0.037985, -0.286304, 1.445920, + 0.518423, -0.141091, -0.269214, 0.518423, 0.016932, -0.075566, + 0.037985, -0.141091, -0.075566, 0.044250 }, + // [3][4] + { -0.038261, -0.288490, -0.281539, 0.020100, -0.281539, 1.245955, + 0.765031, -0.199434, -0.232773, 0.405674, 0.144116, -0.129173, + 0.046327, -0.129173, -0.095881, 0.049061 }, + // [3][5] + { -0.005713, -0.287386, -0.287386, -0.005713, -0.249337, 1.012927, + 1.012927, -0.249337, -0.183693, 0.277180, 0.277180, -0.183693, + 0.049942, -0.113919, -0.113919, 0.049942 }, + // [3][6] + { 0.020100, -0.281539, -0.288490, -0.038261, -0.199434, 0.765030, + 1.245955, -0.281539, -0.129173, 0.144116, 0.405674, -0.232773, + 0.049061, -0.095881, -0.129173, 0.046327 }, + // [3][7] + { 0.037985, -0.269214, -0.286304, -0.075566, -0.141091, 0.518423, + 1.445920, -0.286304, -0.075566, 0.016932, 0.518423, -0.269214, + 0.044250, -0.075566, -0.141091, 0.037985 }, + // [3][8] + { 0.047468, -0.248865, -0.281773, -0.114726, -0.082679, 0.288363, + 1.592520, -0.254647, -0.028257, -0.094397, 0.603625, -0.285522, + 0.036405, -0.053683, -0.148955, 0.025124 }, + // [3][9] + { 0.048915, -0.219852, -0.275321, -0.151962, -0.031369, 0.089594, + 1.666032, -0.180782, 0.008558, -0.180783, 0.650286, -0.275321, + 0.026777, -0.031369, -0.151962, 0.008558 } + }, { // [4] + // [4][0] + { -0.138411, -0.273987, -0.138411, 0.038828, -0.108582, 1.442786, + -0.108582, 0.012944, -0.199271, 0.922267, -0.199271, 0.025923, + -0.047797, -0.205346, -0.047797, 0.024709 }, + // [4][1] + { -0.106894, -0.280902, -0.175061, 0.046823, -0.208111, 1.451089, + 0.025207, -0.023361, -0.259376, 0.923877, -0.117273, -0.002760, + -0.023361, -0.209184, -0.075754, 0.035041 }, + // [4][2] + { -0.072318, -0.283278, -0.206984, 0.049850, -0.267914, 1.381309, + 0.200462, -0.072318, -0.286025, 0.868400, 0.000000, -0.044463, + 0.000000, -0.206984, -0.103448, 0.043712 }, + // [4][3] + { -0.038261, -0.281539, -0.232773, 0.046327, -0.288490, 1.245955, + 0.405674, -0.129173, -0.281539, 0.765031, 0.144116, -0.095881, + 0.020100, -0.199434, -0.129173, 0.049061 }, + // [4][4] + { -0.007702, -0.275978, -0.252355, 0.035491, -0.275978, 1.063146, + 0.627325, -0.187372, -0.252355, 0.627325, 0.304388, -0.151969, + 0.035491, -0.187372, -0.151969, 0.049886 }, + // [4][5] + { 0.017293, -0.266457, -0.266457, 0.017293, -0.239189, 0.851408, + 0.851408, -0.239189, -0.206515, 0.469434, 0.469434, -0.206515, + 0.045464, -0.171437, -0.171437, 0.045464 }, + // [4][6] + { 0.035491, -0.252355, -0.275978, -0.007702, -0.187372, 0.627325, + 1.063146, -0.275978, -0.151969, 0.304388, 0.627325, -0.252355, + 0.049886, -0.151969, -0.187372, 0.035491 }, + // [4][7] + { 0.046327, -0.232772, -0.281539, -0.038261, -0.129173, 0.405674, + 1.245955, -0.288490, -0.095881, 0.144116, 0.765031, -0.281539, + 0.049061, -0.129173, -0.199434, 0.020100 }, + // [4][8] + { 0.049850, -0.206984, -0.283278, -0.072318, -0.072318, 0.200462, + 1.381310, -0.267914, -0.044463, 0.000000, 0.868400, -0.286025, + 0.043712, -0.103448, -0.206984, 0.000000 }, + // [4][9] + { 0.046823, -0.175061, -0.280902, -0.106894, -0.023361, 0.025207, + 1.451089, -0.208111, -0.002760, -0.117273, 0.923877, -0.259376, + 0.035041, -0.075754, -0.209184, -0.023361 } + }, { // [5] + // [5][0] + { -0.091757, -0.250136, -0.091757, 0.032272, -0.155112, 1.192366, + -0.155112, 0.019236, -0.155112, 1.192366, -0.155112, 0.019236, + -0.091757, -0.250136, -0.091757, 0.032272 }, + // [5][1] + { -0.062781, -0.255485, -0.125189, 0.041987, -0.235621, 1.196923, + -0.046205, -0.013627, -0.235621, 1.196923, -0.046205, -0.013627, + -0.062781, -0.255485, -0.125189, 0.041987 }, + // [5][2] + { -0.033132, -0.255000, -0.156383, 0.048436, -0.279226, 1.133429, + 0.101247, -0.059371, -0.279226, 1.133429, 0.101247, -0.059371, + -0.033132, -0.255000, -0.156383, 0.048436 }, + // [5][3] + { -0.005713, -0.249337, -0.183693, 0.049942, -0.287386, 1.012927, + 0.277180, -0.113919, -0.287386, 1.012927, 0.277180, -0.113919, + -0.005713, -0.249337, -0.183693, 0.049942 }, + // [5][4] + { 0.017293, -0.239189, -0.206515, 0.045464, -0.266457, 0.851408, + 0.469434, -0.171437, -0.266457, 0.851408, 0.469434, -0.171437, + 0.017293, -0.239189, -0.206515, 0.045464 }, + // [5][5] + { 0.034558, -0.224927, -0.224927, 0.034558, -0.224927, 0.665297, + 0.665297, -0.224927, -0.224927, 0.665297, 0.665297, -0.224927, + 0.034558, -0.224927, -0.224927, 0.034558 }, + // [5][6] + { 0.045464, -0.206515, -0.239189, 0.017293, -0.171437, 0.469434, + 0.851407, -0.266457, -0.171437, 0.469434, 0.851407, -0.266457, + 0.045464, -0.206515, -0.239189, 0.017293 }, + // [5][7] + { 0.049942, -0.183693, -0.249337, -0.005713, -0.113919, 0.277180, + 1.012927, -0.287386, -0.113919, 0.277180, 1.012927, -0.287386, + 0.049942, -0.183693, -0.249337, -0.005713 }, + // [5][8] + { 0.048436, -0.156383, -0.255000, -0.033132, -0.059371, 0.101247, + 1.133429, -0.279226, -0.059371, 0.101247, 1.133429, -0.279226, + 0.048436, -0.156383, -0.255000, -0.033132 }, + // [5][9] + { 0.041987, -0.125189, -0.255485, -0.062781, -0.013627, -0.046205, + 1.196923, -0.235621, -0.013627, -0.046205, 1.196923, -0.235621, + 0.041987, -0.125189, -0.255485, -0.062781 } + }, { // [6] + // [6][0] + { -0.047797, -0.205346, -0.047797, 0.024709, -0.199271, 0.922267, + -0.199271, 0.025923, -0.108582, 1.442786, -0.108582, 0.012944, + -0.138411, -0.273987, -0.138411, 0.038828 }, + // [6][1] + { -0.023361, -0.209184, -0.075754, 0.035041, -0.259376, 0.923877, + -0.117272, -0.002760, -0.208111, 1.451089, 0.025207, -0.023361, + -0.106894, -0.280902, -0.175061, 0.046823 }, + // [6][2] + { 0.000000, -0.206984, -0.103448, 0.043712, -0.286025, 0.868400, + 0.000000, -0.044463, -0.267914, 1.381309, 0.200462, -0.072318, + -0.072318, -0.283278, -0.206984, 0.049850 }, + // [6][3] + { 0.020100, -0.199434, -0.129173, 0.049061, -0.281539, 0.765030, + 0.144116, -0.095881, -0.288490, 1.245955, 0.405674, -0.129173, + -0.038261, -0.281539, -0.232773, 0.046327 }, + // [6][4] + { 0.035491, -0.187372, -0.151969, 0.049886, -0.252355, 0.627325, + 0.304388, -0.151969, -0.275978, 1.063146, 0.627325, -0.187372, + -0.007702, -0.275978, -0.252355, 0.035491 }, + // [6][5] + { 0.045464, -0.171437, -0.171437, 0.045464, -0.206515, 0.469434, + 0.469434, -0.206515, -0.239189, 0.851407, 0.851407, -0.239189, + 0.017293, -0.266457, -0.266457, 0.017293 }, + // [6][6] + { 0.049886, -0.151969, -0.187372, 0.035491, -0.151969, 0.304388, + 0.627325, -0.252355, -0.187372, 0.627325, 1.063146, -0.275978, + 0.035491, -0.252355, -0.275978, -0.007702 }, + // [6][7] + { 0.049061, -0.129173, -0.199434, 0.020100, -0.095881, 0.144115, + 0.765030, -0.281539, -0.129173, 0.405674, 1.245955, -0.288490, + 0.046327, -0.232773, -0.281539, -0.038261 }, + // [6][8] + { 0.043712, -0.103448, -0.206984, 0.000000, -0.044463, 0.000000, + 0.868400, -0.286025, -0.072318, 0.200462, 1.381310, -0.267914, + 0.049850, -0.206984, -0.283278, -0.072318 }, + // [6][9] + { 0.035041, -0.075754, -0.209184, -0.023361, -0.002760, -0.117273, + 0.923877, -0.259376, -0.023361, 0.025207, 1.451089, -0.208111, + 0.046823, -0.175061, -0.280902, -0.106894 } + }, { // [7] + // [7][0] + { -0.010222, -0.149528, -0.010222, 0.016902, -0.235574, 0.651196, + -0.235574, 0.032411, -0.065196, 1.653085, -0.065196, 0.007535, + -0.183163, -0.266985, -0.183163, 0.043693 }, + // [7][1] + { 0.008558, -0.151962, -0.031369, 0.026777, -0.275321, 0.650285, + -0.180783, 0.008558, -0.180783, 1.666033, 0.089595, -0.031369, + -0.151962, -0.275321, -0.219852, 0.048915,}, + // [7][2] + { 0.025124, -0.148955, -0.053683, 0.036405, -0.285522, 0.603625, + -0.094397, -0.028258, -0.254647, 1.592521, 0.288363, -0.082679, + -0.114726, -0.281773, -0.248865, 0.047468 }, + // [7][3] + { 0.037985, -0.141091, -0.075566, 0.044250, -0.269214, 0.518423, + 0.016932, -0.075566, -0.286304, 1.445920, 0.518423, -0.141091, + -0.075566, -0.286304, -0.269214, 0.037985 }, + // [7][4] + { 0.046327, -0.129173, -0.095881, 0.049061, -0.232772, 0.405674, + 0.144116, -0.129173, -0.281539, 1.245955, 0.765031, -0.199434, + -0.038261, -0.288490, -0.281539, 0.020100 }, + // [7][5] + { 0.049942, -0.113919, -0.113919, 0.049942, -0.183693, 0.277180, + 0.277180, -0.183693, -0.249337, 1.012927, 1.012927, -0.249337, + -0.005713, -0.287386, -0.287386, -0.005713 }, + // [7][6] + { 0.049061, -0.095881, -0.129173, 0.046327, -0.129173, 0.144115, + 0.405674, -0.232773, -0.199434, 0.765030, 1.245955, -0.281539, + 0.020100, -0.281539, -0.288490, -0.038261 }, + // [7][7] + { 0.044250, -0.075566, -0.141091, 0.037985, -0.075566, 0.016932, + 0.518423, -0.269214, -0.141091, 0.518423, 1.445920, -0.286304, + 0.037985, -0.269214, -0.286304, -0.075566 }, + // [7][8] + { 0.036405, -0.053683, -0.148955, 0.025124, -0.028257, -0.094397, + 0.603625, -0.285522, -0.082679, 0.288363, 1.592520, -0.254647, + 0.047468, -0.248865, -0.281773, -0.114726 }, + // [7][9] + { 0.026777, -0.031369, -0.151962, 0.008558, 0.008558, -0.180783, + 0.650286, -0.275321, -0.031369, 0.089594, 1.666033, -0.180782, + 0.048915, -0.219852, -0.275321, -0.151962 } + }, { // [8] + // [8][0] + { 0.018282, -0.091830, 0.018282, 0.009671, -0.259005, 0.396702, + -0.259005, 0.038003, -0.030128, 1.801697, -0.030128, 0.003405, + -0.220745, -0.220755, -0.220745, 0.046299 }, + // [8][1] + { 0.031101, -0.092974, 0.004358, 0.018133, -0.279742, 0.393373, + -0.229975, 0.019491, -0.157168, 1.819735, 0.139746, -0.037101, + -0.193387, -0.229975, -0.253452, 0.047837 }, + // [8][2] + { 0.041121, -0.089783, -0.011604, 0.027480, -0.275172, 0.355494, + -0.173507, -0.011604, -0.241512, 1.745518, 0.355495, -0.089783, + -0.156579, -0.241512, -0.275172, 0.041121 }, + // [8][3] + { 0.047468, -0.082679, -0.028257, 0.036405, -0.248865, 0.288363, + -0.094397, -0.053683, -0.281773, 1.592520, 0.603625, -0.148955, + -0.114726, -0.254647, -0.285522, 0.025124 }, + // [8][4] + { 0.049850, -0.072318, -0.044463, 0.043712, -0.206984, 0.200462, + 0.000000, -0.103448, -0.283278, 1.381310, 0.868400, -0.206984, + -0.072318, -0.267914, -0.286025, 0.000000 }, + // [8][5] + { 0.048436, -0.059371, -0.059371, 0.048436, -0.156383, 0.101247, + 0.101247, -0.156383, -0.255000, 1.133429, 1.133429, -0.255000, + -0.033132, -0.279226, -0.279226, -0.033132 }, + // [8][6] + { 0.043712, -0.044463, -0.072318, 0.049850, -0.103448, 0.000000, + 0.200462, -0.206984, -0.206984, 0.868400, 1.381310, -0.283278, + -0.000000, -0.286025, -0.267914, -0.072318 }, + // [8][7] + { 0.036405, -0.028257, -0.082679, 0.047468, -0.053683, -0.094397, + 0.288363, -0.248865, -0.148955, 0.603625, 1.592520, -0.281773, + 0.025124, -0.285522, -0.254647, -0.114726 }, + // [8][8] + { 0.027480, -0.011604, -0.089783, 0.041121, -0.011604, -0.173507, + 0.355494, -0.275172, -0.089783, 0.355494, 1.745519, -0.241512, + 0.041121, -0.275172, -0.241512, -0.156579 }, + // [8][9] + { 0.018133, 0.004358, -0.092974, 0.031101, 0.019491, -0.229975, + 0.393373, -0.279742, -0.037101, 0.139745, 1.819735, -0.157168, + 0.047837, -0.253452, -0.229975, -0.193387 } + }, { // [9] + // [9][0] + { 0.036235, -0.040077, 0.036235, 0.003820, -0.265837, 0.175148, + -0.265837, 0.041959, -0.007606, 1.869223, -0.007606, 0.000848, + -0.245915, -0.130966, -0.245915, 0.046293 }, + // [9][1] + { 0.043489, -0.040090, 0.029049, 0.010148, -0.270076, 0.169479, + -0.259318, 0.029049, -0.140049, 1.891722, 0.169479, -0.040090, + -0.226157, -0.140049, -0.270076, 0.043489 }, + // [9][2] + { 0.047837, -0.037101, 0.019491, 0.018133, -0.253452, 0.139745, + -0.229975, 0.004358, -0.229975, 1.819734, 0.393373, -0.092974, + -0.193386, -0.157168, -0.279742, 0.031101 }, + // [9][3] + { 0.048915, -0.031369, 0.008558, 0.026777, -0.219852, 0.089594, + -0.180783, -0.031369, -0.275321, 1.666032, 0.650286, -0.151962, + -0.151962, -0.180782, -0.275321, 0.008558 }, + // [9][4] + { 0.046823, -0.023361, -0.002760, 0.035041, -0.175061, 0.025207, + -0.117273, -0.075754, -0.280902, 1.451089, 0.923877, -0.209184, + -0.106894, -0.208111, -0.259376, -0.023361 }, + // [9][5] + { 0.041987, -0.013627, -0.013627, 0.041987, -0.125189, -0.046205, + -0.046205, -0.125189, -0.255485, 1.196923, 1.196923, -0.255485, + -0.062781, -0.235621, -0.235621, -0.062781 }, + // [9][6] + { 0.035041, -0.002760, -0.023361, 0.046823, -0.075754, -0.117273, + 0.025207, -0.175061, -0.209184, 0.923877, 1.451089, -0.280902, + -0.023361, -0.259376, -0.208111, -0.106894 }, + // [9][7] + { 0.026777, 0.008558, -0.031369, 0.048915, -0.031369, -0.180783, + 0.089594, -0.219852, -0.151962, 0.650286, 1.666033, -0.275321, + 0.008558, -0.275321, -0.180782, -0.151962 }, + // [9][8] + { 0.018133, 0.019491, -0.037101, 0.047837, 0.004358, -0.229975, + 0.139745, -0.253452, -0.092974, 0.393373, 1.819735, -0.229975, + 0.031101, -0.279742, -0.157168, -0.193387 }, + // [9][9] + { 0.010148, 0.029049, -0.040090, 0.043489, 0.029049, -0.259318, + 0.169479, -0.270076, -0.040090, 0.169479, 1.891723, -0.140049, + 0.043489, -0.270076, -0.140049, -0.226157 } + } +}; + +// Device 子程序:_fanczosInterpoDev(Fanczos 插值) +static __device__ unsigned char _fanczosInterpoDev(const ImageCuda &inimg, + float x, float y) +{ + // 为了减少算法的执行时间,这里没有对 inimg 的合法性进行检查。 + + // 如果给定的坐标不再图像的返回内,返回 0 值。 + if (x < 0.0f || y < 0.0f || + x >= inimg.imgMeta.width || y >= inimg.imgMeta.height) + return 0; + + // 分离给定坐标的正数部分和小数部分,整数部分存放入 intx 和 inty,小数部分 + // 存放入 dx 和 dy。这里小数部分只保留了 1 位小数,并用整数的方式保存。 + int intx, inty, dx, dy; + intx = (int)x; + inty = (int)y; + dx = (int)((x - intx) * 10.0f); + dy = (int)((y - inty) * 10.0f); + + // 为了代码的简洁,这里将一些常用的参数的分量使用了别的名称替代,希望 CUDA + // 足够聪明,这些用来替代较长名称的变量不会带来额外的性能负担。 + unsigned char *inptr = inimg.imgMeta.imgData; + size_t pitch = inimg.pitchBytes; + const float *iplparam = _fanczosCoeffDev[dy][dx]; + + // 由于图像边缘的点存在越界访存的情况,因此对于边缘点采取临近点插值的方法。 + // 虽然,临近点插值是各种插值算法中最弱的,但考虑到河边老师提供的参考代码就 + // 是如此,故委托方未作改变。 + if (intx < 3 || inty < 3 || + intx >= inimg.imgMeta.width - 3 || inty >= inimg.imgMeta.height - 3) + return inptr[inty * pitch + intx]; + + // 根据当前点周围的 4×4 临近点计算出该点的插值。这个插值根据 Fanzcos 系数 + // 做加权平均数。同样,这里我们也希望 CUDA 足够聪明,这些 += 不要带来额外的 + // 性能负担。这里,我们考虑到计算的准确性与灵活性,插值暂用浮点型数据表示, + // 下边的步骤中,我们会将它安全的转换成 unsigned char。 + float tmpipl = 0.0f; + tmpipl += iplparam[ 0] * inptr[(inty - 1) * pitch + (intx - 1)]; + tmpipl += iplparam[ 1] * inptr[(inty - 1) * pitch + (intx )]; + tmpipl += iplparam[ 2] * inptr[(inty - 1) * pitch + (intx + 1)]; + tmpipl += iplparam[ 3] * inptr[(inty - 1) * pitch + (intx + 2)]; + tmpipl += iplparam[ 4] * inptr[(inty ) * pitch + (intx - 1)]; + tmpipl += iplparam[ 5] * inptr[(inty ) * pitch + (intx )]; + tmpipl += iplparam[ 6] * inptr[(inty ) * pitch + (intx + 1)]; + tmpipl += iplparam[ 7] * inptr[(inty ) * pitch + (intx + 2)]; + tmpipl += iplparam[ 8] * inptr[(inty + 1) * pitch + (intx - 1)]; + tmpipl += iplparam[ 9] * inptr[(inty + 1) * pitch + (intx )]; + tmpipl += iplparam[10] * inptr[(inty + 1) * pitch + (intx + 1)]; + tmpipl += iplparam[11] * inptr[(inty + 1) * pitch + (intx + 2)]; + tmpipl += iplparam[12] * inptr[(inty + 2) * pitch + (intx - 1)]; + tmpipl += iplparam[13] * inptr[(inty + 2) * pitch + (intx )]; + tmpipl += iplparam[14] * inptr[(inty + 2) * pitch + (intx + 1)]; + tmpipl += iplparam[15] * inptr[(inty + 2) * pitch + (intx + 2)]; + + // 将浮点型的临时的插值值,转换为 unsigned char 型。注意,我们没有直接采用 + // 类型强转,就是因为防止数据越界引起的不确定结果。 + if (tmpipl > 255.0f) // 对于高于 unsigned char 限度 255 的值,将其直接 + return 255; // 削平至 255。 + else if (tmpipl < 0.0f) // 对于低于 unsigned char 限度 0 的值,将其直接修 + return 0; // 正至 0。 + else + return (unsigned char)tmpipl; +} + +#endif + diff --git a/okano_3_0/FeatureVecArray.cu b/okano_3_0/FeatureVecArray.cu new file mode 100644 index 0000000..8245712 --- /dev/null +++ b/okano_3_0/FeatureVecArray.cu @@ -0,0 +1,187 @@ +// FeatureVecArray.cu +// 创建人:邱孝兵 + +#include "FeatureVecArray.h" +#include "ErrorCode.h" + + +// 宏 DELETE_FEATUREVECARRAY_HOST (删除 Host 端特征向量) +// 根据给定的 FeatureVecArray 分别清除它 对应 x, y, CV, SD, NC +// 五个数组所占的内存 +#define DELETE_FEATUREVECARRAY_HOST(featurevecarray) do { \ + delete [] ((FeatureVecArray *)(featurevecarray))->x; \ + delete [] ((FeatureVecArray *)(featurevecarray))->y; \ + delete [] ((FeatureVecArray *)(featurevecarray))->CV; \ + delete [] ((FeatureVecArray *)(featurevecarray))->SD; \ + delete [] ((FeatureVecArray *)(featurevecarray))->NC; \ +} while(0) + + +// Host 静态方法:makeAtCurrentDevice(在当前 Device 内存中构建数据) +__host__ int FeatureVecArrayBasicOp::makeAtCurrentDevice( + FeatureVecArray *fvp, size_t count) +{ + // 检查输入特征向量组是否为NULL + if (fvp == NULL) + return NULL_POINTER; + + // 检查给定的特征向量组长度 + if (count < 1) + return INVALID_DATA; + + // 在当前的 Device 上申请存储指定坐特征向量 X 坐标所需要的内存空间 + cudaError_t cuerrcode; + cuerrcode = cudaMalloc((void **)(&fvp->x), count * sizeof (int)); + if (cuerrcode != cudaSuccess) { + fvp->x = NULL; + return CUDA_ERROR; + } + + // 在当前的 Device 上申请存储指定坐特征向量 Y 坐标所需要的内存空间 + cuerrcode = cudaMalloc((void **)(&fvp->y), count * sizeof (int)); + if (cuerrcode != cudaSuccess) { + fvp->y = NULL; + return CUDA_ERROR; + } + + // 在当前的 Device 上申请存储指定坐特征向量 CV 所需要的内存空间 + cuerrcode = cudaMalloc((void **)(&(fvp->CV)), count * sizeof (float)); + if (cuerrcode != cudaSuccess) { + fvp->CV = NULL; + return CUDA_ERROR; + } + + // 在当前的 Device 上申请存储指定坐特征向量 SD 所需要的内存空间 + cuerrcode = cudaMalloc((void **)(&(fvp->SD)), count * sizeof (float)); + if (cuerrcode != cudaSuccess) { + fvp->SD = NULL; + return CUDA_ERROR; + } + + // 在当前的 Device 上申请存储指定坐特征向量 NC 所需要的内存空间 + cuerrcode = cudaMalloc((void **)(&(fvp->NC)), count * sizeof (float)); + if (cuerrcode != cudaSuccess) { + fvp->NC = NULL; + return CUDA_ERROR; + } + + // 修改模板的元数据 + fvp->count = count; + + // 处理完毕,退出 + return NO_ERROR; +} + +// Host 静态方法:copyToHost(将当前 Device 内存中数据拷贝到 Host 端) +__host__ int FeatureVecArrayBasicOp::copyToHost(FeatureVecArray *srcfvp, + FeatureVecArray *dstfvp) +{ + // 检查输入特征向量是否为 NULL + if (srcfvp == NULL) + return NULL_POINTER; + + // 如果源特征向量组为空,则不进行任何操作,直接报错 + if (srcfvp->count == 0) + return INVALID_DATA; + + // 将目标特征向量组的尺寸修改为源特征向量组的尺寸。 + dstfvp->count = srcfvp->count; + + // 为目标特征向量组申请空间 + dstfvp->x = new int[dstfvp->count]; + dstfvp->y = new int[dstfvp->count]; + dstfvp->CV = new float[dstfvp->count]; + dstfvp->SD = new float[dstfvp->count]; + dstfvp->NC = new float[dstfvp->count]; + + cudaError_t cuerrcode; // CUDA 调用返回的错误码。 + + // 拷贝 x 坐标 + cuerrcode = cudaMemcpy(dstfvp->x, srcfvp->x, + srcfvp->count * sizeof (int), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_FEATUREVECARRAY_HOST(dstfvp); + return CUDA_ERROR; + } + + // 拷贝 y 坐标 + cuerrcode = cudaMemcpy(dstfvp->y, srcfvp->y, + srcfvp->count * sizeof (int), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_FEATUREVECARRAY_HOST(dstfvp); + return CUDA_ERROR; + } + + // 拷贝特征值 CV + cuerrcode = cudaMemcpy(dstfvp->CV, srcfvp->CV, + srcfvp->count * sizeof (float), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_FEATUREVECARRAY_HOST(dstfvp); + return CUDA_ERROR; + } + + // 拷贝特征值 SD + cuerrcode = cudaMemcpy(dstfvp->SD, srcfvp->SD, + srcfvp->count * sizeof (float), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_FEATUREVECARRAY_HOST(dstfvp); + return CUDA_ERROR; + } + + // 拷贝特征值 NC + cuerrcode = cudaMemcpy(dstfvp->NC, srcfvp->NC, + srcfvp->count * sizeof (float), + cudaMemcpyDeviceToHost); + + // 判断是否拷贝成功 + if (cuerrcode != cudaSuccess) { + // 如果拷贝失败,清除申请的空间,并返回报错 + DELETE_FEATUREVECARRAY_HOST(dstfvp); + return CUDA_ERROR; + } + + // 处理完毕,退出 + return NO_ERROR; +} + +// Host 静态方法:deleteFeatureVecArray(删除不再需要的特征向量组) +__host__ int FeatureVecArrayBasicOp::deleteFeatureVecArray(FeatureVecArray *fvp) +{ + // 检查输入特征向量组是否为NULL + if (fvp == NULL) + return NULL_POINTER; + + // 检查是否有数据 + if (fvp->count == 0) + { + // do nothing + } else { + // 释放 Device 内存 + cudaFree(fvp->x); + cudaFree(fvp->y); + cudaFree(fvp->CV); + cudaFree(fvp->SD); + cudaFree(fvp->NC); + } + + return NO_ERROR; +} + + + diff --git a/okano_3_0/FeatureVecArray.h b/okano_3_0/FeatureVecArray.h new file mode 100644 index 0000000..564a8f0 --- /dev/null +++ b/okano_3_0/FeatureVecArray.h @@ -0,0 +1,69 @@ +// FeatureVecArray.h +// 创建人:邱孝兵 +// +// 特征向量组(FeatureVecArray) +// 功能说明:定义了 SmoothVector 操作中特征向量的数据接口, +// 以及和特征向量相关的基本操作。 +// +// 修订历史: +// 2012年10月30日(邱孝兵) +// 初始版本。 +// 2012年11月23日(邱孝兵) +// 将数据结构名称由 FeaturedVectors 修改为 FeatureVecArray +// 2012年12月23日(邱孝兵) +// 增加为 FeatureVecArrayBasicOp 增加 copyToHost 方法 + +#ifndef __FEATUREVECARRAY_H__ +#define __FEATUREVECARRAY_H__ + +// 结构体:FeatureVecArray(特征向量组) +// 该结构体定义了特征向量组的数据结构,组成这个向量组的每个向量都是五维的, +// 这五维分别是 X 坐标、 Y 坐标, CV,SD,NC,此外还有向量组的大小 count +typedef struct FeatureVecArray_st { + size_t count; // 特征向量组的大小 + int *x, *y; // x,y 坐标的数组 + float *CV, *SD, *NC; // 三个特征值的数组 +} FeatureVecArray; + +// 类:FeatureVecArrayBasicOp(特征向量组基本操作) +// 继承自:无 +// 该类包含了对于特征向量组的基本操作,如向量组的创建与销毁,向量组在各地址 +// 空间之间的拷贝等。要求所有的特征向量组实例,都需要通过该类创建,否则将会 +// 导致系统运行的紊乱。 +class FeatureVecArrayBasicOp { + +public: + + // Host 静态方法:makeAtCurrentDevice(在当前 Device 内存中构建数据) + // 针对空向量组,在当前 Device 内存中为其申请一段指定的大小的空间,这段空间 + // 中的数据是未被赋值的混乱数据。 + static __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + makeAtCurrentDevice( + FeatureVecArray *fvp, // 特征向量组,要求为空 + size_t count // 指定的模板中点的数量 + ); + + // Host 静态方法:copytToHost 方法(将当前 Device 内存中数据拷贝到 Host 端) + // 这是一个 Out-Place 形式的拷贝。无论 srcfvp 位于哪一个内存空间中,都会得 + // 到一个和其内容完全一致的 dstfvp,且数据是存储于 Host 上的。如果 dstfvp + // 中原来存在有数据,如果原来的数据同新的数据尺寸相同,且也存放在 Host 上, + // 则覆盖原内容,但不重新分配空间;否则原数据将会被释放,并重新申请空间。 + static __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + copyToHost( + FeatureVecArray *srcfvp, // 源特征向量组,要求其中必须有数据。 + FeatureVecArray *dstfvp // 目标特征向量组。 + ); + + // Host 静态方法:deleteFeatureVecArray(销毁模板) + // 销毁一个不再被使用的特征向量组。 + static __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + deleteFeatureVecArray( + FeatureVecArray *fvp // 输入参数,不再需要使用、需要被释放的 + // 特征向量组。 + ); +}; + +#endif diff --git a/okano_3_0/FeatureVecCalc.cu b/okano_3_0/FeatureVecCalc.cu new file mode 100644 index 0000000..346b851 --- /dev/null +++ b/okano_3_0/FeatureVecCalc.cu @@ -0,0 +1,541 @@ +// FeatureVecCalc.cu +// 实现计算起始特征向量 + +#include "FeatureVecCalc.h" + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 256 +#define DEF_BLOCK_Y 1 + + +// 结构体 FeatureVectorProcessorParam(特征向量处理参数) +// 该结构体中定义了初始特征向量进一步处理的参数,有 +// cv、sd、nc 的上限,下限,均值。 +typedef struct ProcessorParam_st +{ + float mincv, maxcv, avgcv, rangecv; // cv 的上限,下限,均值,极限值的差。 + float minsd, maxsd, avgsd, rangesd; // sd 的上限,下限,均值,极限值的差。 + float minnc, maxnc, avgnc, rangenc; // nc 的上限,下限,均值,极限值的差。 +} FeatureVectorProcessorParam; + + +// 核函数 _calcFeatureVectorKer(生成初始特征向量) +// 该方法用于计算指定坐标集所规定的图像范围内的各 PIXEL 的初始特征向量。 +// 利用需求文档中给出的公式,计算每个像素的三个特征值:灰度中心値 float CV 、 +// 灰度値标准差 float SD 、最大灰度非共起系数 float NC。 +static __global__ void // Kernel 函数无返回值 +_calcFeatureVectorKer( + ImageCuda inimgcud, // 输入图像 + CoordiSet incoordiset, // 输入坐标集 + FeatureVecArray outfeaturevecarray, // 输出特征向量 + FeatureVecCalc featureveccalc, // 平滑向量处理类 + unsigned char *neighbortmp // 暂存空间,暂存每个像素邻域像素 + // 每个像素的邻域存两份,一个为排 + // 序前,一个为排序后 +); + +// 核函数 _processFeatureVectorKer(处理初始特征向量) +// 该方法用于进一步处理在 _calcFeatureVectorKer 中计算出来的初始特征向量, +// 利用的参数是在初始特征向量的上下限、均值等 +static __global__ void // Kernel 函数无返回值 +_processFeatureVectorKer( + FeatureVecArray inoutfeaturevecarray, // 输入输出特征向量 + FeatureVecCalc featureveccalc, // 平滑向量处理类 + FeatureVectorProcessorParam param // 处理参数 +); + +// 核函数:_calcFeatureVectorKer(计算初始特征向量) +static __global__ void _calcFeatureVectorKer( + ImageCuda inimgcud, CoordiSet incoordiset, FeatureVecArray + outfeaturevecarray, FeatureVecCalc featureveccalc, + unsigned char *neighbortmppointer) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前索引超过了坐标集中的点的个数,直接返回 + if(index >= incoordiset.count) + return; + + // 计算该点在原图像中的位置 + int xcrd = incoordiset.tplData[2 * index]; + int ycrd = incoordiset.tplData[2 * index + 1]; + + // 将输出特征向量的 X,Y 坐标写入到特征向量组中 + outfeaturevecarray.x[index] = xcrd; + outfeaturevecarray.y[index] = ycrd; + + // 计算邻域正方形的边长及线性数组的长度。 + int n = featureveccalc.getNeighborWidth(); + int neighborwidth = n * 2 + 1; + int length = neighborwidth * neighborwidth; + + // 计算当前像素使用的邻域暂存空间的偏移 + int offset = index * length * 2; + unsigned char *neighbortmp = neighbortmppointer + offset; + + // 复制邻域像素到邻域暂存中 + for (int i = 0; i < neighborwidth; i++) { + for (int j = 0; j < neighborwidth; j++) { + // 计算对应图像中的坐标 + int xcrdi = xcrd - n + i; + int ycrdj = ycrd - n + j; + + // 如果当前坐标超过图像边缘,设定为边缘值 + if (xcrdi >= inimgcud.imgMeta.width) + xcrdi = inimgcud.imgMeta.width - 1; + if (xcrdi < 0) + xcrdi = 0; + if (ycrdj >= inimgcud.imgMeta.height) + ycrdj = inimgcud.imgMeta.width - 1; + if (ycrdj < 0) + ycrdj = 0; + + // 将图像中像素值拷贝到邻域暂存空间 + neighbortmp[i * neighborwidth + j] = + inimgcud.imgMeta.imgData[ycrdj * inimgcud.pitchBytes + + xcrdi]; + } + } + + // 统计邻域内每个像素值的点的个数 + int pixelcount[PIXELRANGE]; + + // 排序后的数组 + unsigned char *neighbortmpsorted = neighbortmp + length; + + // 对neighbortmp进行排序 + featureveccalc.sortNeighbor(neighbortmp, neighbortmpsorted, pixelcount, + length); + + // 计算平均灰度值 + float cv = featureveccalc.calAvgPixel(neighbortmpsorted, length * 1 / 3, + length * 2 / 3); + // 将平均灰度值写入到输出特征向量组中 + outfeaturevecarray.CV[index] = cv; + + // 计算灰度标准差 + float sd = featureveccalc.calPixelSd(neighbortmpsorted, length, cv); + + // 将灰度标准差写入到输出特征向量组中 + outfeaturevecarray.SD[index] = sd; + + // 计算最大非共起系数,首先求八个方向的灰度平均值 + // 从0-8依次为从方向右逆时针开始到方向右下结束 + float eightdirectionavg[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + for (int j = 1; j <= n; j++) { + // 当前像素右边的点的灰度求和 + eightdirectionavg[0] += neighbortmp[n * neighborwidth + n + j]; + + // 当前像素右上的点的灰度求和 + eightdirectionavg[1] += neighbortmp[(n - j) * neighborwidth + n + j]; + + // 当前像素上边的点的灰度求和 + eightdirectionavg[2] += neighbortmp[(n - j) * neighborwidth + n]; + + // 当前像素左上的点的灰度求和 + eightdirectionavg[3] += neighbortmp[(n - j) * neighborwidth + n - j]; + + // 当前像素左边的点的灰度求和 + eightdirectionavg[4] += neighbortmp[n * neighborwidth + n - j]; + + // 当前像素左下的点的灰度求和 + eightdirectionavg[5] += neighbortmp[(n + j) * neighborwidth + n - j]; + + // 当前像素下边的点的灰度求和 + eightdirectionavg[6] += neighbortmp[(n + j) * neighborwidth + n]; + + // 当前像素右下的点的灰度求和 + eightdirectionavg[7] += neighbortmp[(n + j) * neighborwidth + n + j]; + } + + // 求八个方向上平均灰度值 + for (int i = 0; i < 8; i++) { + eightdirectionavg[i] /= n; + } + + // 根据给定的公式计算八个方向的共起系数并找出最大共起系数 + int t = (featureveccalc.getPitch() + 1) / 2; + int m = (n - 2 * t + 1) * powf((2 * n + 1), 4); + float eightncs[8]= { 0, 0, 0, 0, 0, 0, 0, 0 }; // 八个方向的共起系数 + float nc = 0; + unsigned char pixeltmp1 = 0; // 暂存对角线上的像素 + unsigned char pixeltmp2 = 0; // 暂存对角线上的像素 + + for (int j = t; j <= n - t; j++) { + if (featureveccalc.getPitch() % 2 == 0) { + + // 当前像素右边的点共起系数求和 + pixeltmp1 = neighbortmp[n * neighborwidth + n + j - t]; + pixeltmp2 = neighbortmp[n * neighborwidth + n + j + t - 1]; + eightncs[0] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[0]) * + (pixeltmp2 - eightdirectionavg[0])); + + // 当前像素右上的点共起系数求和 + pixeltmp1 = neighbortmp[(n + j - t) * neighborwidth + n + j - t]; + pixeltmp2 = + neighbortmp[(n + j + t - 1) * neighborwidth + + n + j + t - 1]; + eightncs[1] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[1]) * + (pixeltmp2 - eightdirectionavg[1])); + + // 当前像素上边的点共起系数求和 + pixeltmp1 = neighbortmp[(n + j - t) * neighborwidth + n]; + pixeltmp2 = neighbortmp[(n + j + t - 1) * neighborwidth + n]; + eightncs[2] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[2]) * + (pixeltmp2 - eightdirectionavg[2])); + + // 当前像素左上的点共起系数求和 + pixeltmp1 = neighbortmp[(n + j - t) * neighborwidth + n - (j - t)]; + pixeltmp2 = + neighbortmp[(n + j + t - 1) * neighborwidth + + n - (j + t - 1)]; + eightncs[3] +=pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[3]) * + (pixeltmp2 - eightdirectionavg[3])); + + // 当前像素左边的点共起系数求和 + pixeltmp1 = neighbortmp[n * neighborwidth + n - (j - t)]; + pixeltmp2 = neighbortmp[n * neighborwidth + n - (j + t - 1)]; + eightncs[4] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[4]) * + (pixeltmp2 - eightdirectionavg[4])); + + // 当前像素左下的点共起系数求和 + pixeltmp1 = + neighbortmp[(n - (j - t)) * neighborwidth + n - (j - t)]; + pixeltmp2 = + neighbortmp[(n - (j + t - 1)) * neighborwidth + + n - (j + t - 1)]; + eightncs[5] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[5]) * + (pixeltmp2 - eightdirectionavg[5])); + + // 当前像素下边的点共起系数求和 + pixeltmp1 = neighbortmp[(n - (j - t)) * neighborwidth + n]; + pixeltmp2 = neighbortmp[(n - (j + t - 1)) * neighborwidth + n]; + eightncs[6] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[6]) * + (pixeltmp2 - eightdirectionavg[6])); + + // 当前像素右下的点共起系数求和 + pixeltmp1 = neighbortmp[(n - (j - t)) * neighborwidth + n + j - t]; + pixeltmp2 = + neighbortmp[(n - (j + t - 1)) * neighborwidth + + n + j + t - 1]; + eightncs[7] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[7]) * + (pixeltmp2 - eightdirectionavg[7])); + } else { + // 当前像素右边的点共起系数求和 + pixeltmp1 = neighbortmp[n * neighborwidth + n + j - t]; + pixeltmp2 = neighbortmp[n * neighborwidth + n + j + t]; + eightncs[0] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[0]) * + (pixeltmp2 - eightdirectionavg[0])); + + // 当前像素右上的点共起系数求和 + pixeltmp1 = neighbortmp[(n + j - t) * neighborwidth + n + j - t]; + pixeltmp2 = + neighbortmp[(n + j + t) * neighborwidth + n + j + t]; + eightncs[1] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[1]) * + (pixeltmp2 - eightdirectionavg[1])); + + // 当前像素上边的点共起系数求和 + pixeltmp1 = neighbortmp[(n + j - t) * neighborwidth + n]; + pixeltmp2 = neighbortmp[(n + j + t) * neighborwidth + n]; + eightncs[2] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[2]) * + (pixeltmp2 - eightdirectionavg[2])); + + // 当前像素左上的点共起系数求和 + pixeltmp1 = neighbortmp[(n + j - t) * neighborwidth + n - (j - t)]; + pixeltmp2 = + neighbortmp[(n + j + t) * neighborwidth + n - (j + t)]; + eightncs[3] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[3]) * + (pixeltmp2 - eightdirectionavg[3])); + + // 当前像素左边的点共起系数求和 + pixeltmp1 = neighbortmp[n * neighborwidth + n - (j - t)]; + pixeltmp2 = neighbortmp[n * neighborwidth + n - (j + t)]; + eightncs[4] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[4]) * + (pixeltmp2 - eightdirectionavg[4])); + + // 当前像素左下的点共起系数求和 + pixeltmp1 = + neighbortmp[(n - (j - t)) * neighborwidth + n - (j - t)]; + pixeltmp2 = + neighbortmp[(n - (j + t)) * neighborwidth + n - (j + t)]; + eightncs[5] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[5]) * + (pixeltmp2 - eightdirectionavg[5])); + + // 当前像素下边的点共起系数求和 + pixeltmp1 = neighbortmp[(n - (j - t)) * neighborwidth + n]; + pixeltmp2 = neighbortmp[(n - (j + t)) * neighborwidth + n]; + eightncs[6] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[6]) * + (pixeltmp2 - eightdirectionavg[6])); + + // 当前像素右下的点共起系数求和 + pixeltmp1 = neighbortmp[(n - (j - t)) * neighborwidth + n + j - t]; + pixeltmp2 = + neighbortmp[(n - (j + t)) * neighborwidth + n + j + t]; + eightncs[7] += pixelcount[pixeltmp1] * pixelcount[pixeltmp2] * + abs((pixeltmp1 - eightdirectionavg[7]) * + (pixeltmp2 - eightdirectionavg[7])); + } + } + + // 计算并找出最大非共起系数 + for (int i = 0; i < 8; i++) { + eightncs[i] /= m; + + // 记录最大非共起系数 + if (nc < eightncs[i]) + nc = eightncs[i]; + } + + // 将最大非共起系数写入输出特征向量组中 + outfeaturevecarray.NC[index] = nc; +} + +// 核函数 _processFeatureVectorKer(处理初始特征向量) +static __global__ void +_processFeatureVectorKer(FeatureVecArray inoutfeaturevecarray, + FeatureVecCalc featureveccalc, FeatureVectorProcessorParam param) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= inoutfeaturevecarray.count) + return; + + // 取出上一步计算得到的特征值 + float cv = inoutfeaturevecarray.CV[index]; + float sd = inoutfeaturevecarray.SD[index]; + float nc = inoutfeaturevecarray.NC[index]; + + // 如果特征值低于下界或者高于上界,则置为下界或上界 + if (cv < param.mincv) + cv = param.mincv; + if (cv > param.maxcv) + cv = param.maxcv; + if (sd < param.minsd) + sd = param.minsd; + if (sd > param.maxsd) + sd = param.maxsd; + if (nc < param.minnc) + nc = param.minnc; + if (nc > param.maxnc) + nc = param.maxnc; + + // 计算外部参数 + float a = 1 / (1 + featureveccalc.getAlpha() + featureveccalc.getBeta()); + + // 处理特征值 + if (param.rangecv != 0) + cv = a * (cv - param.avgcv) / param.rangecv; + if (param.rangesd != 0) + sd = a * featureveccalc.getAlpha() * (sd - param.avgsd) / + param.rangesd; + if (param.rangenc != 0) + nc = a * featureveccalc.getBeta() * (nc - param.avgnc) / + param.rangenc; + + // 将特征值赋回去 + inoutfeaturevecarray.CV[index] = cv; + inoutfeaturevecarray.SD[index] = sd; + inoutfeaturevecarray.NC[index] = nc; +} + +// 全局函数:cmp (两个 float 变量的比较函数) +// 使用于针对特征值快速排序中的比较函数指针 +int cmp(const void * a, const void * b) +{ + return((*(float *)a - *(float *)b > 0) ? 1 : -1); +} + + +// 宏 DELETE_THREE_FEATURE_ARRAY_HOST (删除 Host 端三个特征向量数组) +// 在出错或者函数运行结束后,清理三个特征向量数组的值 +#define DELETE_THREE_FEATURE_ARRAY_HOST do { \ + if ((cvshost) != NULL) \ + delete [] (cvshost); \ + if ((sdshost) != NULL) \ + delete [] (sdshost); \ + if ((ncshost) != NULL) \ + delete [] (ncshost); \ + } while(0) + + +// Host 成员方法:calFeatureVector(计算初始特征向量) +__host__ int FeatureVecCalc::calFeatureVector(Image *inimg, + CoordiSet *incoordiset, FeatureVecArray *outfeaturevecarray) +{ + // 检查输入参数是否为 NULL, 如果为 NULL 直接报错返回 + if (inimg == NULL || incoordiset == NULL || outfeaturevecarray == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标集拷贝到 Device 内存中 + errcode = CoordiSetBasicOp::copyToCurrentDevice(incoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 申请邻域存储空间 + unsigned char *neighbortmp = NULL ; + int neighborsize = (neighborWidth * 2 + 1) * (neighborWidth * 2 + 1); + errcode = cudaMalloc( + (void **)(&neighbortmp), + 2 * incoordiset->count * neighborsize * sizeof (unsigned char)); + if (errcode != cudaSuccess) { + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = incoordiset->count / blocksize.x + 1; + gridsize.y = 1; + + // 计算初始特征向量 + _calcFeatureVectorKer<<>>(insubimgCud, *incoordiset, + *outfeaturevecarray, *this, + neighbortmp); + + // 及时释放申请的 neighbortmp + cudaFree(neighbortmp); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // Host 端三个特征向量数组声明 + float *cvshost = NULL; + float *sdshost = NULL; + float *ncshost = NULL; + + // 申请 Host 端 cv 特征值数组空间 + cvshost = new float[incoordiset->count]; + if (cvshost == NULL) { + DELETE_THREE_FEATURE_ARRAY_HOST; + return NULL_POINTER; + } + + // 申请 Host 端 sd 特征值数组空间 + sdshost = new float[incoordiset->count]; + if (sdshost == NULL) { + DELETE_THREE_FEATURE_ARRAY_HOST; + return NULL_POINTER; + } + + // 申请 Host 端 nc 特征值数组空间 + ncshost = new float[incoordiset->count]; + if (ncshost == NULL) { + DELETE_THREE_FEATURE_ARRAY_HOST; + return NULL_POINTER; + } + + // 将 CV 拷贝到 Host 端 + errcode = cudaMemcpy(cvshost, outfeaturevecarray->CV, + incoordiset->count * sizeof (float), + cudaMemcpyDeviceToHost); + + // 如果拷贝失败,则需要释放掉刚刚申请的内存空间,以防止内存泄漏。之 + // 后报错返回。 + if (errcode != cudaSuccess) { + // 清除之前申请的内存 + DELETE_THREE_FEATURE_ARRAY_HOST; + return CUDA_ERROR; + } + + // 将 SD 拷贝到 Host 端 + errcode = cudaMemcpy(sdshost, outfeaturevecarray->SD, + incoordiset->count * sizeof (float), + cudaMemcpyDeviceToHost); + + // 如果拷贝失败,则需要释放掉刚刚申请的内存空间,以防止内存泄漏。之 + // 后报错返回。 + if (errcode != cudaSuccess) { + // 清除之前申请的内存 + DELETE_THREE_FEATURE_ARRAY_HOST; + return CUDA_ERROR; + } + + // 将 NC 拷贝到 Host 端 + errcode = cudaMemcpy(ncshost, outfeaturevecarray->NC, + incoordiset->count * sizeof (float), + cudaMemcpyDeviceToHost); + + // 如果拷贝失败,则需要释放掉刚刚申请的内存空间,以防止内存泄漏。之 + // 后报错返回。 + if (errcode != cudaSuccess) { + // 清除之前申请的内存 + DELETE_THREE_FEATURE_ARRAY_HOST; + return CUDA_ERROR; + } + + // 使用快速排序分别对三组特征值排序 + qsort(cvshost, incoordiset->count, sizeof(float), cmp); + qsort(sdshost, incoordiset->count, sizeof(float), cmp); + qsort(ncshost, incoordiset->count, sizeof(float), cmp); + + // 处理参数定义 + FeatureVectorProcessorParam param; + + // 计算上下限的下标 + int bordermin = (int)(0.05 * incoordiset->count); + int bordermax = (int)(0.95 * incoordiset->count); + + // 给各个处理参数赋值 + param.mincv = cvshost[bordermin]; + param.maxcv = cvshost[bordermax]; + param.avgcv = calAvgFeatureValue(cvshost, bordermin, bordermax); + param.rangecv = param.maxcv - param.mincv; + + param.minsd = sdshost[bordermin]; + param.maxsd = sdshost[bordermax]; + param.avgsd = calAvgFeatureValue(sdshost, bordermin, bordermax); + param.rangesd = param.maxsd - param.minsd; + + param.minnc = ncshost[bordermin]; + param.maxnc = ncshost[bordermax]; + param.avgnc = calAvgFeatureValue(ncshost, bordermin, bordermax); + param.rangenc = param.maxnc - param.minnc; + + // 对初始特征向量进行简单处理 + _processFeatureVectorKer<<>>(*outfeaturevecarray, + *this, param); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 内存清理 + DELETE_THREE_FEATURE_ARRAY_HOST; + + return NO_ERROR; +} + diff --git a/okano_3_0/FeatureVecCalc.h b/okano_3_0/FeatureVecCalc.h new file mode 100644 index 0000000..90dcf2c --- /dev/null +++ b/okano_3_0/FeatureVecCalc.h @@ -0,0 +1,288 @@ +// FeatureVecCalc.h +// 创建人:邱孝兵 +// +// 特征向量的计算(FeatureVecCalc) +// 功能说明:计算指定坐标集所规定的图像范围内的各像素的一个特征向量 +// +// 修订历史: +// 2012年10月25日 (邱孝兵) +// 初始版本。 +// 2012年11月02日 (邱孝兵) +// 增加了 sortNeighbor,calAvgPixel,calPixelSd 三个 device 函数 +// 2012年11月04日 (邱孝兵) +// 增加了 sortFeatureValue,calFeatureVecCalc 两个 host 函数 +// 2012年11月22日 (邱孝兵) +// 修改一些格式问题,修改构造函数赋值方式 +// 2012年11月23日 (邱孝兵) +// 将类名由 FeatureVecCalc 修改为 FeatureVecCalc +// 2012年12月26日 (邱孝兵) +// 修改了三处程序逻辑错误。 +// 2012年12月31日 (邱孝兵) +// 删除原有的 host 端排序方法,并换成快速排序,大幅度提升了性能 + +#include "Image.h" +#include "ErrorCode.h" +#include "FeatureVecArray.h" +#include "CoordiSet.h" + +#ifndef __FEATUREVECTOR_H__ +#define __FEATUREVECTOR_H__ + +// 定义像素范围 +#define PIXELRANGE 256 + + +// 类:FeatureVecCalc +// 继承自:无 +// 根据设定的外部参数计算指定坐标集所规定的图像范围内的各像素的一个特征向量 +class FeatureVecCalc { + +protected: + + // 成员变量:alpha(外部指定系数) + // 计算三个特征值时需要用到的一个系数。 + float alpha; + + // 成员变量:beta(外部指定系数) + // 计算三个特征值时需要用到的一个系数。 + float beta; + + // 成员变量:neighborWidth(邻域宽度) + // 在利用每个像素 n 邻域的点的像素计算该像素特征值的时候,指定的 + // 邻域宽度 n 。 + int neighborWidth; + + // 成员变量:pitch(间距) + // 在计算每个像素的最大非共起系数 NC 的时候需要用到的一个外部参数 + int pitch; + +public: + + // 构造函数:SmoothVector + // 无参数版本的构造函数,所有成员变量均初始化为默认值 + __host__ __device__ + FeatureVecCalc() + { + // 无参数的构造函数,使用默认值初始化各个变量 + this->alpha = 0; + this->beta = 0; + this->neighborWidth = 1; + this->pitch = 1; + } + + // 构造函数:SmoothVector + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + FeatureVecCalc( + float alpha, // 外部参数阿尔法 + float beta, // 外部参数贝塔 + int neighborwidth, // 邻域宽度 + int pitch // 间距 + ) { + // 初始化各个变量 + this->alpha = alpha; + this->beta = beta; + this->neighborWidth = neighborwidth; + this->pitch = pitch; + } + + // 成员方法:getAlpha (获取 alpha 的值) + // 获取成员变量 alpha 的值 + __host__ __device__ float // 返回值:成员变量 alpha 的值 + getAlpha() const + { + // 返回成员变量 alpha 的值 + return this->alpha; + } + + // 成员方法:setAlpha(设置 alpha 的值) + // 设置成员变量 alpha 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setAlpha( + float alpha // 外部参数 alpha + ) { + // 设置成员变量 beta 的值 + this->alpha = alpha; + + return NO_ERROR; + } + + // 成员方法:getBeta (获取 beta 的值) + // 获取成员变量 beta 的值 + __host__ __device__ float // 返回值:成员变量 beta 的值 + getBeta() const + { + // 返回成员变量 beta 的值 + return this->beta; + } + + // 成员方法:setBeta(设置 beta 的值) + // 设置成员变量 beta 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setBeta( + float beta // 外部参数 beta + ) { + // 设置成员变量 beta 的值 + this->beta = beta; + + return NO_ERROR; + } + + // 成员方法:getNeighborWidth (获取 neighborWidth 的值) + // 获取成员变量 neighborWidth 的值 + __host__ __device__ int // 返回值:成员变量 neighborWidth 的值 + getNeighborWidth() const + { + // 返回成员变量 neighborWidth 的值 + return this->neighborWidth; + } + + // 成员方法:setNeighborWidth(设置 neighborWidth 的值) + // 设置成员变量 neighborWidth 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR。 + setNeighborWidth( + int neighborwidth // 邻域大小 + ) { + // 设置成员变量 neighborWidth 的值 + this->neighborWidth = neighborwidth; + + return NO_ERROR; + } + + // 成员方法:getPitch (获取 pitch 的值) + // 获取成员变量 pitch 的值 + __host__ __device__ int // 返回值:成员变量 pitch 的值 + getPitch() const + { + // 返回成员变量 pitch 的值 + return this->pitch; + } + + // 成员方法:setPitch(设置 pitch 的值) + // 设置成员变量 pitch 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setPitch( + int pitch // 间距 + ) { + // 设置成员变量 pitch 的值 + this->pitch = pitch; + + return NO_ERROR; + } + + // 成员方法:sortNeighbor(对邻域像素进行排序) + // 对一个线性数组的像素点,根据像素值进行重新排序 + // 使用统计排序法可以实现线性时间排序 + __host__ __device__ int // 返回值:函数是否正确执行, + // 若函数正确执行,返回 NO_ERROR。 + sortNeighbor( + unsigned char *pixels, // 像素存储指针 + unsigned char *pixelssorted, // 排序后的数组存储位置 + int *pixelcount, // 像素统计 + int length // 数组长度 + ) { + int i, j; + + // 临时存储像素统计 + int tmpcount[PIXELRANGE]; + + // 像素统计置0 + for (i = 0; i < PIXELRANGE; i++) { + pixelcount[i] = 0; + tmpcount[i] = 0; + } + + // 统计像素值等于 i 的个数 + for (j = 0; j < length; j++) { + pixelcount[pixels[j]]++; + tmpcount[pixels[j]]++; + } + + // 统计像素值小于或等于 i 的个数 + for (i = 1; i < PIXELRANGE; i++) + tmpcount[i] = tmpcount[i] + tmpcount[i - 1]; + + // 将各个元素放到正确的位置 + for (j = length - 1; j >= 0; j--) { + pixelssorted[tmpcount[pixels[j]] - 1] = pixels[j]; + tmpcount[pixels[j]]--; + } + + return NO_ERROR; + } + + // 成员方法:calAvgPixel(计算灰度平均值) + // 对一个像素数组,在指定的下标范围内,计算其平均值 + __host__ __device__ float // 计算得到的平均值 + calAvgPixel( + unsigned char *pixels, // 像素存储指针 + int low, // 下标左值 + int high // 下标右值 + ) { + // 求和 + int sum = 0; + for (int i = low; i < high; i++) + sum += pixels[i]; + + // 返回均值 + return sum * 1.0f / (high - low); + } + + // 成员方法:calPixelSd(计算灰度标准差) + // 对一个像素数组,根据其平均值计算标准差 + __host__ __device__ float // 计算得到的标准差 + calPixelSd( + unsigned char *pixels, // 像素存储指针 + int length, // 数组长度 + float cv // 平均值 + ) { + // 求和 + float sum = 0; + for (int i = 0; i < length ; i++) + sum += powf((pixels[i] - cv), 2); + + // 返回标准差 + return sqrtf(sum / length); + } + + // 成员方法:calAvgFeatureValue(计算特征向量平均值) + // 计算某一特征值数组在特定下标范围内的平均值 + __host__ float // 返回值,计算得到的平均值 + calAvgFeatureValue( + float *featurevalue, // 特征值数组 + int minborder, // 下标下限 + int maxborder // 下标上限 + ) { + float avgfeaturevalue = 0; + for (int i = minborder; i < maxborder; i++) + avgfeaturevalue += featurevalue[i]; + + // 返回平均值 + return avgfeaturevalue / (maxborder - minborder); + } + + // 成员方法:calFeatureVector(计算特征向量) + // 该方法用于计算指定坐标集所规定的图像范围内的各 PIXEL 的初始特征向量。 + // 具体可以分为三个步骤: + // 1. 利用需求文档中给出的公式,计算每个像素的三个特征值:灰度中心値 float + // CV 、灰度値标准差 float SD 、最大灰度非共起系数 float NC 。 + // 2. 针对三个特征值的数组进行排序,取极值,求均值等操作。 + // 3. 根据上个步骤中,求得的极值、均值以及外部参数 α 、β 求出我们所需要的 + // 每个像素的初始特征向量。 + __host__ int // 返回值:函数是否正确执行, + // 若函数正确执行返回 + // NO_ERROR。 + calFeatureVector( + Image *inimg, // 输入图像 + CoordiSet *incoordiset, // 输入坐标集 + FeatureVecArray *outfeaturevecarray // 输出特征向量 + ); +}; + +#endif + diff --git a/okano_3_0/FillCoor.cu b/okano_3_0/FillCoor.cu new file mode 100644 index 0000000..fba9fc8 --- /dev/null +++ b/okano_3_0/FillCoor.cu @@ -0,0 +1,1012 @@ +#include "FillCoor.h" +#include "ImageDrawer.h" +#include "ImgConvert.h" +#include +#include +using namespace std; +// 宏:DEBUG +// 定义是否输出调试信息 +//#define DEBUG_IMG +//#define DEBUG_TIME + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 +// 宏:PIXEL(x,y) +// 获取图像中(x,y)像素的位置 +#define PIXEL(x,y) workimg->imgData[(y)*workimg->width+(x)] +// 宏:VALID(x,y) +// 判断(x,y)像素的位置是否合法 +#define VALID(x,y) (x>=0 && xwidth && y>=0 && yheight) + +// 宏:CUDA_PIXEL(x,y) +// 获取内核函数中图像中(x,y)像素的位置 +#define CUDA_PIXEL(x,y) imgcud.imgMeta.imgData[(y)*imgcud.pitchBytes+(x)] +// 宏:CUDA_VALID(x,y) +// 判断内核函数中(x,y)像素的位置是否合法 +#define CUDA_VALID(x,y) (x>=0 && x=0 && y=imgcud.imgMeta.width || y >= imgcud.imgMeta.height ) + return; + int cudastack[CUDA_STACK_SIZE]; + // 填充工作, + // 输入:轮廓线workimg,种子seed; + // 输出:填充过的workimg + + int stackptr=0; + point seed; + seed.x=40+x*10; + seed.y=40+y*10; + + if(seed.x>=imgcud.imgMeta.width || seed.y >= imgcud.imgMeta.height ) + return; + + + int xtemp,xright,xleft; + int spanfill; + // 种子入栈 + cudastack[stackptr++]=seed.x; + cudastack[stackptr++]=seed.y; + // stackptr==0表示栈为空,>0说明栈不空,每个像素点占用2个位置 + while(stackptr>0){ + point cur; + // 统计堆栈最大深度 + if(stackptr>stackmaxsize[0]) + stackmaxsize[0]=stackptr; + // 入栈顺序x、y,出栈顺序应y、x。 + + cur.y=cudastack[--stackptr]; + cur.x=cudastack[--stackptr]; + // 填充当前点 + CUDA_PIXEL(cur.x,cur.y)=BORDER_COLOR; + // 向右填充,填充过程中检测当前点坐标,如果越界,说明种子在图形外 + for(xtemp=cur.x+1;CUDA_PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp++){ + if(CUDA_VALID(xtemp,cur.y)==false) return ; + CUDA_PIXEL(xtemp,cur.y)=BORDER_COLOR;} + //纪录当前线段最右位置 + xright=xtemp-1; + // 向左填充 + for(xtemp=cur.x-1;CUDA_PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp--){ + if(CUDA_VALID(xtemp,cur.y)==false) return ; + CUDA_PIXEL(xtemp,cur.y)=BORDER_COLOR; + } + // 纪录当前线段最左位置 + xleft=xtemp+1; + + //cout<<"hang:"<=imgcud.imgMeta.width || + seed.y >= imgcud.imgMeta.height || + CUDA_PIXEL(seed.x,seed.y) != BK_COLOR) + return; + + // 填充工作 + // 输入:轮廓线workimg,种子seed; + // 输出:填充过的workimg + int cudastack[CUDA_STACK_SIZE]; + int stackptr=0; + int xtemp,xright,xleft; + int spanfill; + // 种子入栈 + cudastack[stackptr++]=seed.x; + cudastack[stackptr++]=seed.y; + // stackptr==0表示栈为空,>0说明栈不空,每个像素点占用2个位置 + while(stackptr>0){ + point cur; + // 统计堆栈最大深度 + //if(stackptr>stackmaxsize[0]) + //stackmaxsize[0]=stackptr; + // 入栈顺序x、y,出栈顺序应y、x。 + + cur.y=cudastack[--stackptr]; + cur.x=cudastack[--stackptr]; + // 填充当前点 + CUDA_PIXEL(cur.x,cur.y)=BORDER_COLOR; + // 向右填充,填充过程中检测当前点坐标 + for(xtemp=cur.x+1;CUDA_VALID(xtemp,cur.y)&&CUDA_PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp++){ + CUDA_PIXEL(xtemp,cur.y)=BORDER_COLOR;} + //纪录当前线段最右位置 + xright=xtemp-1; + // 向左填充 + for(xtemp=cur.x-1;CUDA_VALID(xtemp,cur.y)&&CUDA_PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp--){ + CUDA_PIXEL(xtemp,cur.y)=BORDER_COLOR; + } + // 纪录当前线段最左位置 + xleft=xtemp+1; + + //cout<<"hang:"<=0 && cur.y=0 && cur.y=outborderCud.imgMeta.width || y >= outborderCud.imgMeta.height ) + return; + + + // 内边界填充图填充部分 且 外边界填充图未填充部分 是要求的结果,其余部分 + // 认为是背景 + if(outborderCud.imgMeta.imgData[index] != BORDER_COLOR && + inborderCud.imgMeta.imgData[index] == BORDER_COLOR) + outborderCud.imgMeta.imgData[index]=BORDER_COLOR; + else + outborderCud.imgMeta.imgData[index]=BK_COLOR; + + return; +} +// Kernel 函数:_negateKer(对输入图像求反 BORDER_COLOR<-->BK_COLOR) +static __global__ void _negateKer( + ImageCuda outborderCud // 外轮廓被填充过的图形,反转后是轮廓内部填充 +){ + // 计算线程对应的输出点的位置的 x 和 y 分量 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + int index=y*outborderCud.pitchBytes+x; + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if(x>=outborderCud.imgMeta.width || y >= outborderCud.imgMeta.height ) + return; + + // BORDER_COLOR 变成 BK_COLOR ,或者相反 + if(outborderCud.imgMeta.imgData[index] == BORDER_COLOR) + outborderCud.imgMeta.imgData[index]=BK_COLOR; + else + outborderCud.imgMeta.imgData[index]=BORDER_COLOR; + + return; +} +//--------------------------全局方法声明------------------------------------ +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax); + +//--------------------------全局方法实现------------------------------------ +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax) +{ + // 声明局部变量。 + int errcode; + + // 在 host 端申请一个新的 CoordiSet 变量。 + CoordiSet *tmpcoordiset; + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + errcode = CoordiSetBasicOp::makeAtHost(tmpcoordiset, guidingset->count); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(guidingset, tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 初始化 x 和 y 方向上的最小最大值。 + xmin[0] = xmax[0] = tmpcoordiset->tplData[0]; + ymin[0] = ymax[0] = tmpcoordiset->tplData[1]; + // 循环寻找坐标集最左、最右、最上、最下的坐标。 + for (int i = 1;i < tmpcoordiset->count;i++) { + // 寻找 x 方向上的最小值。 + if (xmin[0] > tmpcoordiset->tplData[2 * i]) + xmin[0] = tmpcoordiset->tplData[2 * i]; + // 寻找 x 方向上的最大值 + if (xmax[0] < tmpcoordiset->tplData[2 * i]) + xmax[0] = tmpcoordiset->tplData[2 * i]; + + // 寻找 y 方向上的最小值。 + if (ymin[0] > tmpcoordiset->tplData[2 * i + 1]) + ymin[0] = tmpcoordiset->tplData[2 * i + 1]; + // 寻找 y 方向上的最大值 + if (ymax[0] < tmpcoordiset->tplData[2 * i + 1]) + ymax[0] = tmpcoordiset->tplData[2 * i + 1]; + } + + // 释放临时坐标集变量。 + CoordiSetBasicOp::deleteCoordiSet(tmpcoordiset); + + return errcode; +} + +//--------------------------成员方法实现------------------------------------ +// 成员方法:fillCoordiSetSeri(串行方法,填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 +FillCoor::seedScanLineSeri( +CoordiSet *incoor, // 输入的coordiset,内容为一条闭合曲线 +CoordiSet *outcoor, // 输出填充过的的coordiset,内容一个填充区域 +int x, // 种子x坐标 +int y // 种子y坐标 +){ + + // 获取坐标集中点的分布范围,即包围盒坐标 + int minx,maxx,miny,maxy; + int errorcode=_findMinMaxCoordinates(incoor,&minx,&miny,&maxx,&maxy); + if(errorcode!=NO_ERROR) + return 0; + // 创建工作图像 + Image *workimg; + ImageBasicOp::newImage(&workimg); + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数 + ImageBasicOp::makeAtHost(workimg,maxx+1 ,maxy+1); + // 把坐标集绘制到图像上 + ImgConvert imgcvt(BORDER_COLOR,BK_COLOR); + imgcvt.cstConvertToImg(incoor,workimg); + // 把填充前的图像保存到文件 + ImageBasicOp::copyToHost(workimg); + ImageBasicOp::writeToFile("biforeFill.bmp",workimg); + +//---------------------------------------------------- + // 填充工作, + // 输入:轮廓线workimg,种子seed; + // 输出:填充过的workimg + int deepestnum=0; + point seed; + seed.x=x; + seed.y=y; + + + int xtemp,xright,xleft; + int spanfill; + stack st; + + st.push(seed); + int loopnum=0; + while(!st.empty()){ + point cur; + loopnum++; + // 统计堆栈最大深度 + + if(st.size()>deepestnum){ + deepestnum=st.size(); + } + cur=st.top(); + st.pop(); + PIXEL(cur.x,cur.y)=BORDER_COLOR; + // 向右填充,填充过程中检测当前点坐标,如果越界,说明种子在图形外 + for(xtemp=cur.x+1;PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp++){ + if(VALID(xtemp,cur.y)==false) return INVALID_DATA; + PIXEL(xtemp,cur.y)=BORDER_COLOR;} + //纪录当前线段最右位置 + xright=xtemp-1; + // 向左填充 + for(xtemp=cur.x-1;PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp--){ + if(VALID(xtemp,cur.y)==false) return INVALID_DATA; + PIXEL(xtemp,cur.y)=BORDER_COLOR; + } + // 纪录当前线段最左位置 + xleft=xtemp+1; + + //cout<<"hang:"<st; + + st.push(seed); + while(!st.empty()){ + point cur; + cur=st.top(); + st.pop(); + PIXEL(cur.x,cur.y)=BORDER_COLOR; + // 向右填充,填充过程中检测当前点坐标,如果越界,说明种子在图形外 + for(xtemp=cur.x+1;PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp++){ + if(VALID(xtemp,cur.y)==false) return false; + PIXEL(xtemp,cur.y)=BORDER_COLOR;} + //纪录当前线段最右位置 + xright=xtemp-1; + // 向左填充 + for(xtemp=cur.x-1;PIXEL(xtemp,cur.y)!=BORDER_COLOR;xtemp--){ + if(VALID(xtemp,cur.y)==false) return false; + PIXEL(xtemp,cur.y)=BORDER_COLOR; + } + //纪录当前线段最左位置 + xleft=xtemp+1; + + //cout<<"hang:"<height>outborderimg->width? + outborderimg->height:outborderimg->width; + dim3 grid,block; + block.x=DEF_BLOCK_X; + block.y=1; + block.z=1; + grid.x=(outmaxsize+DEF_BLOCK_X-1)/DEF_BLOCK_X; + grid.y=4; + grid.z=1; + + _seedScanLineOutConKer<<>>(outborderCud); + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_Filled.bmp",outborderimg); + // 交操作还要在 device 端使用图像 + ImageBasicOp::copyToCurrentDevice(outborderimg); + #endif + }// end of out border + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "out border fill " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------处理内轮廓------------------------------- + if(outbordercoor!=NULL && inbordercoor!=NULL){ + // 注意,内边界图像将来要和外边界图像求交,大小按外边界分配 + // 给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(inborderimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(inbordercoor,inborderimg); + #ifdef DEBUG_IMG + // 把填充前的图像保存到文件 + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborder_notFilled.bmp",inborderimg); + #endif + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(inborderimg, &inborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + // 找长宽的最大值当内核函数的参数 + int inmaxsize=inborderimg->height>inborderimg->width? + inborderimg->height:inborderimg->width; + dim3 grid,block; + block.x=DEF_BLOCK_X; + block.y=1; + block.z=1; + grid.x=(inmaxsize+DEF_BLOCK_X-1)/DEF_BLOCK_X; + grid.y=4; + grid.z=1; + _seedScanLineOutConKer<<>>(inborderCud); + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborderFilled.bmp",inborderimg); + // 交操作还要在 device 端使用图像 + ImageBasicOp::copyToCurrentDevice(inborderimg); + #endif + }// end of in border & process + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "in border fill " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + //--------------如果有内轮廓,则内外轮廓填充图像求交---------------------- + if(outbordercoor!=NULL && inbordercoor!=NULL){ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求交,结果放入outbordercud中,此时outborderCud和 + // inborderCud都在divice中,不用再次copytodevice + _intersectionKer<<>>( + outborderCud, + inborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + //--------------如果没有内轮廓,则仅仅对外轮廓填充结果求反--------- + else{ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求反 + _negateKer<<>>( + outborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "inter or negate " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + + + //------------------------串行图像转化成coor,返回------------------------- + ImageBasicOp::copyToHost(outborderimg); + #ifdef DEBUG_IMG + // 最终图像输出到文件 + ImageBasicOp::writeToFile("intersection.bmp",outborderimg); + #endif + // 此时imgcvt的设置是前景255,背景0,灰色部分会忽略,故自定义串行转化方法 + //imgcvt.imgConvertToCst(outborderimg,fillcoor); + int w,h; + w=outborderimg->width; + h=outborderimg->height; + int imgsize=w*h; + // 每个点(x,y)占用两个整数存放 + int *coorarray=(int *)malloc(2*imgsize*sizeof(int)); + int coorcount=0; + for(int i=0;iimgData[j*w+i]; + if(curpix==BORDER_COLOR ){ + coorarray[coorcount*2]=i; + coorarray[coorcount*2+1]=j; + coorcount++; + } + } + CoordiSetBasicOp::makeAtHost(fillcoor,coorcount); + memcpy(fillcoor->tplData,coorarray,coorcount*2*sizeof(int)); + free(coorarray); + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "seri img to coor " << runTime << " ms" << endl; + #endif + + /* + //------------------------并行图像转化成coor,返回------------------------- + // 经过测试,效率不如串行,故不采用 + imgcvt.imgConvertToCst(outborderimg,fillcoor); + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "con img to coor " << runTime << " ms" << endl; + #endif + */ +//------------------------内存回收------------------------------------- + ImageBasicOp::deleteImage(outborderimg); + ImageBasicOp::deleteImage(inborderimg); + return NO_ERROR; +} + + // 成员方法:seedScanLineCon(并行种子扫描线算法填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCoor::seedScanLineCon( + Image *outborderimg, // 外轮廓闭合曲线图像,同时也是输出结果 + Image *inborderimg // 内轮廓闭合曲线图像,没有内轮廓设为空 + ){ + ImageCuda outborderCud; + ImageCuda inborderCud; + // --------------------------处理外轮廓------------------------------- + if(outborderimg!=NULL){ + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(outborderimg, &outborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + // 找长宽的最大值当内核函数的参数 + int outmaxsize=outborderimg->height>outborderimg->width? + outborderimg->height:outborderimg->width; + dim3 grid,block; + block.x=DEF_BLOCK_X; + block.y=1; + block.z=1; + grid.x=(outmaxsize+DEF_BLOCK_X-1)/DEF_BLOCK_X; + grid.y=4; + grid.z=1; + + _seedScanLineOutConKer<<>>(outborderCud); + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_Filled.bmp",outborderimg); + // 交操作还要在 device 端使用图像 + ImageBasicOp::copyToCurrentDevice(outborderimg); + #endif + }// end of out border + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "out border fill " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------处理内轮廓------------------------------- + if(outborderimg!=NULL && inborderimg!=NULL){ + + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(inborderimg, &inborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + // 找长宽的最大值当内核函数的参数 + int inmaxsize=inborderimg->height>inborderimg->width? + inborderimg->height:inborderimg->width; + dim3 grid,block; + block.x=DEF_BLOCK_X; + block.y=1; + block.z=1; + grid.x=(inmaxsize+DEF_BLOCK_X-1)/DEF_BLOCK_X; + grid.y=4; + grid.z=1; + _seedScanLineOutConKer<<>>(inborderCud); + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborderFilled.bmp",inborderimg); + // 交操作还要在 device 端使用图像 + ImageBasicOp::copyToCurrentDevice(inborderimg); + #endif + }// end of in border & process + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "in border fill " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + //--------------如果有内轮廓,则内外轮廓填充图像求交---------------------- + if(outborderimg!=NULL && inborderimg!=NULL){ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求交,结果放入outbordercud中,此时outborderCud和 + // inborderCud都在divice中,不用再次copytodevice + _intersectionKer<<>>( + outborderCud, + inborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + //--------------如果没有内轮廓,则仅仅对外轮廓填充结果求反--------- + else{ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求反 + _negateKer<<>>( + outborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "inter or negate " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + + + return NO_ERROR; +} diff --git a/okano_3_0/FillCoor.h b/okano_3_0/FillCoor.h new file mode 100644 index 0000000..4e91f09 --- /dev/null +++ b/okano_3_0/FillCoor.h @@ -0,0 +1,75 @@ +// FillCoor.h +// 创建人:曹建立 +// +// 填充 coordiset 集合围起的封闭区域(FillCoor) +// 功能说明:参数给出的 CoordiSet 集合是一条或多条闭合曲线,本类功能为将闭合 +// 曲线围成的封闭区域内部用 FILL_COLOR 像素值填充。 +// 使用并行算法时,输入的外轮廓集合和内轮廓集合要放入两个coordiset中。如果没 +// 有内轮廓,内轮廓坐标集设为NULL。 + +// 修订历史: + +#ifndef __FillCoor_H__ +#define __FillCoor_H__ + +#include "Image.h" +#include "CoordiSet.h" +#include "ErrorCode.h" +#include +using namespace std; + +// 宏:BORDER_COLOR +// 定义边界颜色 +#define BORDER_COLOR 255 +// 宏:BK_COLOR +// 定义背景颜色 +#define BK_COLOR 0 + +class FillCoor +{ + +public: + + + // 成员方法:fillCoordiSetSeri(填充 coordiset 集合围起的区域) + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineSeri( + CoordiSet *incoor, // 输入的coordiset,内容为围起一个封闭区域的一条 + // 或多条封闭曲线,允许区域有孔洞 + CoordiSet *outcoor, // 输出填充过的的coordiset,内容为一个填充区域 + int x, // 种子x坐标 + int y // 种子y坐标 + ); + + // 成员方法:isInCoordiSetSeri(判断当前点是否在 coordiset 集合围起的区域中 + // 的串行算法) + __host__ bool // 返回值:在内部返回真,否则返回假 + isInCoordiSetSeri( + CoordiSet *incoor, // 输入的coordiset,内容为围起封闭区域的一 + // 条或多条封闭曲线,允许区域有孔洞 + int x, // 坐标点x坐标 + int y // 坐标点y坐标 + ); + + // 成员方法:seedScanLineCon(并行种子扫描线算法填充 coordiset 集合围起的区域) + // 使用本并行算法时,内外轮廓要放入不同的coordiset中。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineCon( + CoordiSet *outbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 外轮廓闭合曲线 + CoordiSet *inbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + CoordiSet *fillcoor // 输出填充过的的 coordiset + ); + +// 成员方法:seedScanLineCon(并行种子扫描线算法填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineCon( + Image *outborderimg, // 外轮廓闭合曲线图像,同时也是输出结果 + Image *inborderimg // 内轮廓闭合曲线图像,没有内轮廓设为空 + ); +}; +#endif diff --git a/okano_3_0/FillCurve.cu b/okano_3_0/FillCurve.cu new file mode 100644 index 0000000..cdd8a72 --- /dev/null +++ b/okano_3_0/FillCurve.cu @@ -0,0 +1,1217 @@ +// FillCurve.cu +// 创建人:曹建立 + +#include "FillCurve.h" +#include +using namespace std; +// 宏:DEBUG_IMG +// 定义是否输出中间图像调试信息 +// #define DEBUG_IMG +// 宏:DEBUG_TIME +// 定义是否输出时间调试信息 +// #define DEBUG_TIME + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// 宏:CUDA_PIXEL_GLO(x,y) +// 获取全局内存中内核函数中图像中(x,y)像素的位置 +#define CUDA_PIXEL_GLO(x,y) imgcud.imgMeta.imgData[(y)*imgcud.pitchBytes+(x)] +// 宏:CUDA_VALID_GLO(x,y) +// 判断全局内存中内核函数中(x,y)像素的位置是否合法 +#define CUDA_VALID_GLO(x,y) (x>=0 && x=0 && y=0 && x=0 && y=len) return; + if (x=imgcud.imgMeta.width || + seedy >= imgcud.imgMeta.height || + CUDA_PIXEL_GLO(seedx,seedy) != BK_COLOR) + return; + + // 填充工作 + // 输入:轮廓线workimg,种子seed; + // 输出:填充过的workimg + int cudastack[CUDA_STACK_SIZE]; + int stackptr=0; + int xtemp,xright,xleft; + int spanfill; + // 种子入栈 + cudastack[stackptr++]=seedx; + cudastack[stackptr++]=seedy; + // stackptr==0表示栈为空,>0说明栈不空,每个像素点占用2个位置 + while(stackptr>0){ + int curx,cury; + // 统计堆栈最大深度 + //if(stackptr>stackmaxsize[0]) + //stackmaxsize[0]=stackptr; + // 入栈顺序x、y,出栈顺序应y、x。 + + cury=cudastack[--stackptr]; + curx=cudastack[--stackptr]; + // 填充当前点 + CUDA_PIXEL_GLO(curx,cury)=BORDER_COLOR; + // 向右填充,填充过程中检测当前点坐标 + for(xtemp=curx+1;CUDA_VALID_GLO(xtemp,cury)&&CUDA_PIXEL_GLO(xtemp,cury)!=BORDER_COLOR;xtemp++){ + CUDA_PIXEL_GLO(xtemp,cury)=BORDER_COLOR;} + //纪录当前线段最右位置 + xright=xtemp-1; + // 向左填充 + for(xtemp=curx-1;CUDA_VALID_GLO(xtemp,cury)&&CUDA_PIXEL_GLO(xtemp,cury)!=BORDER_COLOR;xtemp--){ + CUDA_PIXEL_GLO(xtemp,cury)=BORDER_COLOR; + } + // 纪录当前线段最左位置 + xleft=xtemp+1; + + //cout<<"hang:"<=0 && cury=0 && cury=threadsize) return; + extern __shared__ unsigned char shareImg[]; + int w=imgcud.imgMeta.width; + int h=imgcud.imgMeta.height; + // 图像拷贝到共享内存 + int imgarraysize=w*h; + // 计算每个线程需要负责多少个像素点的复制 + register int stride=imgarraysize/threadsize+1; + // 本线程负责的像素点在数组中的开始位置下标 + register int beginIdx=x*stride; + + register int ny,nx; + if (beginIdx=imgarraysize) break; + shareImg[ny*w+nx+i]=imgcud.imgMeta.imgData[ny*imgcud.pitchBytes+nx+i]; + } + } + + __syncthreads(); + + // 线程号转换成顺时针种子点编号,根据编号,计算该点坐标 + if (x=imgcud.imgMeta.width || + seedy >= imgcud.imgMeta.height || + CUDA_PIXEL_SHR(seedx,seedy) != BK_COLOR) + return; + + // 填充工作 + // 输入:轮廓线workimg,种子seed; + // 输出:填充过的workimg + int cudastack[CUDA_STACK_SIZE]; + int stackptr=0; + int xtemp,xright,xleft; + int spanfill; + // 种子入栈 + cudastack[stackptr++]=seedx; + cudastack[stackptr++]=seedy; + // stackptr==0表示栈为空,>0说明栈不空,每个像素点占用2个位置 + while(stackptr>0){ + int curx,cury; + // 统计堆栈最大深度 + //if(stackptr>stackmaxsize[0]) + //stackmaxsize[0]=stackptr; + // 入栈顺序x、y,出栈顺序应y、x。 + + cury=cudastack[--stackptr]; + curx=cudastack[--stackptr]; + // 填充当前点 + CUDA_PIXEL_SHR(curx,cury)=BORDER_COLOR; + // 向右填充,填充过程中检测当前点坐标 + for(xtemp=curx+1;CUDA_VALID_SHR(xtemp,cury)&&CUDA_PIXEL_SHR(xtemp,cury)!=BORDER_COLOR;xtemp++){ + CUDA_PIXEL_SHR(xtemp,cury)=BORDER_COLOR;} + //纪录当前线段最右位置 + xright=xtemp-1; + // 向左填充 + for(xtemp=curx-1;CUDA_VALID_SHR(xtemp,cury)&&CUDA_PIXEL_SHR(xtemp,cury)!=BORDER_COLOR;xtemp--){ + CUDA_PIXEL_SHR(xtemp,cury)=BORDER_COLOR; + } + // 纪录当前线段最左位置 + xleft=xtemp+1; + + //cout<<"hang:"<=0 && cury=0 && cury=imgarraysize) break; + imgcud.imgMeta.imgData[ny*imgcud.pitchBytes+nx+i]=shareImg[ny*w+nx+i]; + } + return ; +} + +// Kernel 函数:_intersectionKer(求两幅图像交,结果放入outbordercud中) +static __global__ void _intersectionKer( + ImageCuda outborderCud, // 外轮廓被填充过后的图像 + ImageCuda inborderCud // 内轮廓被填充过后的图像 +){ + // 此版本中,填充色就是轮廓色,因此,逻辑判定简单 + // 计算线程对应的输出点的位置的 x 和 y 分量 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + int index=y*outborderCud.pitchBytes+x; + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if(x>=outborderCud.imgMeta.width || y >= outborderCud.imgMeta.height ) + return; + + + // 内边界填充图填充部分 且 外边界填充图未填充部分 是要求的结果,其余部分 + // 认为是背景 + if(outborderCud.imgMeta.imgData[index] != BORDER_COLOR && + inborderCud.imgMeta.imgData[index] == BORDER_COLOR) + outborderCud.imgMeta.imgData[index]=BORDER_COLOR; + else + outborderCud.imgMeta.imgData[index]=BK_COLOR; + + return; +} +// Kernel 函数:_negateKer(对输入图像求反 BORDER_COLOR<-->BK_COLOR) +static __global__ void _negateKer( + ImageCuda outborderCud // 外轮廓被填充过的图形,反转后是轮廓内部填充 +){ + // 计算线程对应的输出点的位置的 x 和 y 分量 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + int index=y*outborderCud.pitchBytes+x; + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if(x>=outborderCud.imgMeta.width || y >= outborderCud.imgMeta.height ) + return; + + // BORDER_COLOR 变成 BK_COLOR ,或者相反 + if(outborderCud.imgMeta.imgData[index] == BORDER_COLOR) + outborderCud.imgMeta.imgData[index]=BK_COLOR; + else + outborderCud.imgMeta.imgData[index]=BORDER_COLOR; + + return; +} +//--------------------------全局方法声明------------------------------------ +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax); + +//--------------------------全局方法实现------------------------------------ +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax) +{ + // 声明局部变量。 + int errcode; + + // 在 host 端申请一个新的 CoordiSet 变量。 + CoordiSet *tmpcoordiset; + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + errcode = CoordiSetBasicOp::makeAtHost(tmpcoordiset, guidingset->count); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(guidingset, tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 初始化 x 和 y 方向上的最小最大值。 + xmin[0] = xmax[0] = tmpcoordiset->tplData[0]; + ymin[0] = ymax[0] = tmpcoordiset->tplData[1]; + // 循环寻找坐标集最左、最右、最上、最下的坐标。 + for (int i = 1;i < tmpcoordiset->count;i++) { + // 寻找 x 方向上的最小值。 + if (xmin[0] > tmpcoordiset->tplData[2 * i]) + xmin[0] = tmpcoordiset->tplData[2 * i]; + // 寻找 x 方向上的最大值 + if (xmax[0] < tmpcoordiset->tplData[2 * i]) + xmax[0] = tmpcoordiset->tplData[2 * i]; + + // 寻找 y 方向上的最小值。 + if (ymin[0] > tmpcoordiset->tplData[2 * i + 1]) + ymin[0] = tmpcoordiset->tplData[2 * i + 1]; + // 寻找 y 方向上的最大值 + if (ymax[0] < tmpcoordiset->tplData[2 * i + 1]) + ymax[0] = tmpcoordiset->tplData[2 * i + 1]; + } + + // 释放临时坐标集变量。 + CoordiSetBasicOp::deleteCoordiSet(tmpcoordiset); + + return errcode; +} + +//--------------------------成员方法实现------------------------------------ + + + // 成员方法:seedScanLineCoorGlo(并行种子扫描线算法填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCurve::seedScanLineCoorGlo( + CoordiSet *outbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 外轮廓闭合曲线 + CoordiSet *inbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + CoordiSet *fillcoor // 输出填充过的的 coordiset + ){ + // 获取坐标集中点的分布范围,即包围盒坐标 + int minx,maxx,miny,maxy; + // ----------------------输入coor参数转化成img---------------------------- + Image *outborderimg=NULL; + ImageBasicOp::newImage(&outborderimg); + Image *inborderimg=NULL; + ImageBasicOp::newImage(&inborderimg); + + ImgConvert imgcvt(BORDER_COLOR,BK_COLOR); + + #ifdef DEBUG_TIME + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0); + #endif + // --------------------------轮廓坐标集转换成img------------------------------- + if(outbordercoor!=NULL){ + // 预处理,得到外轮廓大小 + int errorcode=_findMinMaxCoordinates(outbordercoor,&minx,&miny,&maxx,&maxy); + if(errorcode!=NO_ERROR) + return 0; + // 处理外轮廓 + // 创建工作图像 + + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(outborderimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(outbordercoor,outborderimg); + if(inbordercoor!=NULL){ + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(inborderimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(inbordercoor,inborderimg); + } + #ifdef DEBUG_IMG + // 把填充前的图像保存到文件 + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_notFilled.bmp",outborderimg); + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborder_notFilled.bmp",inborderimg); + #endif + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] coor->img time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------调用图像填充算法------------------------------- + seedScanLineImgGlo(outborderimg,inborderimg); + + + }// end of out border + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] fill time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + + + + //------------------------串行图像转化成coor,返回------------------------- + ImageBasicOp::copyToHost(outborderimg); + #ifdef DEBUG_IMG + // 最终图像输出到文件 + ImageBasicOp::writeToFile("[coor]intersection.bmp",outborderimg); + #endif + // 此时imgcvt的设置是前景255,背景0,灰色部分会忽略,故自定义串行转化方法 + //imgcvt.imgConvertToCst(outborderimg,fillcoor); + int w,h; + w=outborderimg->width; + h=outborderimg->height; + int imgsize=w*h; + // 每个点(x,y)占用两个整数存放 + int *coorarray=(int *)malloc(2*imgsize*sizeof(int)); + int coorcount=0; + for(int i=0;iimgData[j*w+i]; + if(curpix==BORDER_COLOR ){ + coorarray[coorcount*2]=i; + coorarray[coorcount*2+1]=j; + coorcount++; + } + } + + // 创建coor,给count、和数据数组赋值 + CoordiSetBasicOp::makeAtHost(fillcoor,coorcount); + memcpy(fillcoor->tplData,coorarray,coorcount*2*sizeof(int)); + free(coorarray); + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] img to coor " << runTime << " ms" << endl; + #endif + + /* + //------------------------并行图像转化成coor,返回------------------------- + // 经过测试,效率不如串行,故不采用 + imgcvt.imgConvertToCst(outborderimg,fillcoor); + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "con img to coor " << runTime << " ms" << endl; + #endif + */ +//------------------------内存回收------------------------------------- + ImageBasicOp::deleteImage(outborderimg); + ImageBasicOp::deleteImage(inborderimg); + return NO_ERROR; +} + + // 成员方法:seedScanLineImgGlo(并行种子扫描线算法填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCurve::seedScanLineImgGlo( + Image *outborderimg, // 外轮廓闭合曲线图像,同时也是输出结果 + Image *inborderimg // 内轮廓闭合曲线图像,没有内轮廓设为空 + ){ + ImageCuda outborderCud; + ImageCuda inborderCud; + + #ifdef DEBUG_TIME + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0); + #endif + // --------------------------处理外轮廓------------------------------- + if(outborderimg!=NULL){ + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(outborderimg, &outborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算边缘点个数,即最大线程个数 + int outmaxthreadsize=(outborderimg->width+outborderimg->height-2)<<1; + dim3 grid,block; + block.x=DEF_BLOCK_X; + block.y=1; + block.z=1; + grid.x=(outmaxthreadsize+DEF_BLOCK_X-1)/DEF_BLOCK_X; + grid.y=1; + grid.z=1; + //--------------------------------- + + _seedScanLineOutConGlobalKer<<>> + (outborderCud,outmaxthreadsize); + //--------------------------------------------- + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_Filled.bmp",outborderimg); + // 交操作还要在 device 端使用图像,将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outborderimg); + // 经过一次主存显存传输,ROI子图像需要重新提取 + errcode = ImageBasicOp::roiSubImage(outborderimg, &outborderCud); + + #endif + }// end of out border + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[img] out border fill time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------处理内轮廓------------------------------- + if(outborderimg!=NULL && inborderimg!=NULL){ + + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(inborderimg, &inborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + // 计算边缘点个数,即最大线程个数 + int inmaxthreadsize=(inborderimg->width+inborderimg->height-2)<<1; + dim3 grid,block; + block.x=DEF_BLOCK_X; + block.y=1; + block.z=1; + grid.x=(inmaxthreadsize+DEF_BLOCK_X-1)/DEF_BLOCK_X; + grid.y=1; + grid.z=1; + _seedScanLineOutConGlobalKer<<>>(inborderCud,inmaxthreadsize); + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborderFilled.bmp",inborderimg); + // 交操作还要在 device 端使用图像,将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inborderimg); + // 经过一次主存显存传输,ROI子图像需要重新提取 + errcode = ImageBasicOp::roiSubImage(inborderimg, &inborderCud); + #endif + }// end of in border & process + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[img] in border fill time " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + //--------------如果有内轮廓,则内外轮廓填充图像求交---------------------- + if(outborderimg!=NULL && inborderimg!=NULL){ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求交,结果放入outbordercud中,此时outborderCud和 + // inborderCud都在divice中,不用再次copytodevice + _intersectionKer<<>>( + outborderCud, + inborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + //--------------如果没有内轮廓,则仅仅对外轮廓填充结果求反--------- + else{ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求反 + _negateKer<<>>( + outborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[img ]inter or negate " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("[img]fill_result.bmp",outborderimg); + #endif + ImageBasicOp::copyToHost(outborderimg); + return NO_ERROR; +} + + + +// 成员方法:seedScanLineCurveGlo(并行种子扫描线算法填充 Curve 集合围起的区域) +// 使用本并行算法时,内外轮廓要放入不同的 Curve 中。 +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCurve::seedScanLineCurveGlo( + Curve *outbordercurve, // 输入的 Curve ,内容为封闭区域 + // 外轮廓闭合曲线 + Curve *inbordercurve, // 输入的 Curve ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + Curve *fillcurve // 输出填充过的的 Curve + ){ + + // ----------------------输入Curve参数转化成img---------------------------- + Image *outborderimg=NULL; + Image *inborderimg=NULL; + + CurveConverter curcvt(BORDER_COLOR,BK_COLOR); + + #ifdef DEBUG_TIME + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0); + #endif + // --------------------------轮廓坐标集转换成img------------------------------- + if(outbordercurve==NULL) + return INVALID_DATA; + + ImageBasicOp::newImage(&outborderimg); + // 创建工作图像 + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(outborderimg,outbordercurve->maxCordiX+2 ,outbordercurve->maxCordiY+2); + // 把坐标集绘制到图像上,前景255,背景0 + curcvt.curve2Img(outbordercurve,outborderimg); + if(inbordercurve!=NULL){ + ImageBasicOp::newImage(&inborderimg); + //给工作图像分配空间,按照外轮廓大小分配 + ImageBasicOp::makeAtHost(inborderimg,outbordercurve->maxCordiX+2 ,outbordercurve->maxCordiY+2); + // 把坐标集绘制到图像上,前景255,背景0 + curcvt.curve2Img(inbordercurve,inborderimg); + } + #ifdef DEBUG_IMG + // 把填充前的图像保存到文件 + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_notFilled.bmp",outborderimg); + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborder_notFilled.bmp",inborderimg); + #endif + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[Curve] coor->img time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------调用图像填充算法------------------------------- + seedScanLineImgGlo(outborderimg,inborderimg); + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[Curve] fill time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + + //------------------------图像转化成curve,返回------------------------- + + + cudaThreadSynchronize(); + curcvt.img2Curve(outborderimg,fillcurve); + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[Curve] img to Curve " << runTime << " ms" << endl; + #endif + +//------------------------内存回收------------------------------------- + ImageBasicOp::deleteImage(outborderimg); + ImageBasicOp::deleteImage(inborderimg); + + return NO_ERROR; +} + + + + // 成员方法:seedScanLineCoorShr(并行种子扫描线算法填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCurve::seedScanLineCoorShr( + CoordiSet *outbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 外轮廓闭合曲线 + CoordiSet *inbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + CoordiSet *fillcoor // 输出填充过的的 coordiset + ){ + // 获取坐标集中点的分布范围,即包围盒坐标 + int minx,maxx,miny,maxy; + // ----------------------输入coor参数转化成img---------------------------- + Image *outborderimg=NULL; + ImageBasicOp::newImage(&outborderimg); + Image *inborderimg=NULL; + ImageBasicOp::newImage(&inborderimg); + + ImgConvert imgcvt(BORDER_COLOR,BK_COLOR); + + #ifdef DEBUG_TIME + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0); + #endif + // --------------------------轮廓坐标集转换成img------------------------------- + if(outbordercoor!=NULL){ + // 预处理,得到外轮廓大小 + int errorcode=_findMinMaxCoordinates(outbordercoor,&minx,&miny,&maxx,&maxy); + if(errorcode!=NO_ERROR) + return 0; + // 处理外轮廓 + // 创建工作图像 + + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(outborderimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(outbordercoor,outborderimg); + if(inbordercoor!=NULL){ + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(inborderimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(inbordercoor,inborderimg); + } + #ifdef DEBUG_IMG + // 把填充前的图像保存到文件 + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_notFilled.bmp",outborderimg); + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborder_notFilled.bmp",inborderimg); + #endif + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] coor->img time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------调用图像填充算法------------------------------- + seedScanLineImgShr(outborderimg,inborderimg); + + + }// end of out border + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] fill time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + + //------------------------串行图像转化成coor,返回------------------------- + ImageBasicOp::copyToHost(outborderimg); + #ifdef DEBUG_IMG + // 最终图像输出到文件 + ImageBasicOp::writeToFile("[coor]intersection.bmp",outborderimg); + #endif + // 此时imgcvt的设置是前景255,背景0,灰色部分会忽略,故自定义串行转化方法 + //imgcvt.imgConvertToCst(outborderimg,fillcoor); + int w,h; + w=outborderimg->width; + h=outborderimg->height; + int imgsize=w*h; + // 每个点(x,y)占用两个整数存放 + int *coorarray=(int *)malloc(2*imgsize*sizeof(int)); + int coorcount=0; + for(int i=0;iimgData[j*w+i]; + if(curpix==BORDER_COLOR ){ + coorarray[coorcount*2]=i; + coorarray[coorcount*2+1]=j; + coorcount++; + } + } + + // 创建coor,给count、和数据数组赋值 + CoordiSetBasicOp::makeAtHost(fillcoor,coorcount); + memcpy(fillcoor->tplData,coorarray,coorcount*2*sizeof(int)); + free(coorarray); + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[coor] img to coor " << runTime << " ms" << endl; + #endif + + /* + //------------------------并行图像转化成coor,返回------------------------- + // 经过测试,效率不如串行,故不采用 + imgcvt.imgConvertToCst(outborderimg,fillcoor); + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "con img to coor " << runTime << " ms" << endl; + #endif + */ +//------------------------内存回收------------------------------------- + ImageBasicOp::deleteImage(outborderimg); + ImageBasicOp::deleteImage(inborderimg); + return NO_ERROR; +} + + // 成员方法:seedScanLineImgShr(并行种子扫描线算法填充 coordiset 集合围起的区域) +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCurve::seedScanLineImgShr( + Image *outborderimg, // 外轮廓闭合曲线图像,同时也是输出结果 + Image *inborderimg // 内轮廓闭合曲线图像,没有内轮廓设为空 + ){ + ImageCuda outborderCud; + ImageCuda inborderCud; + + #ifdef DEBUG_TIME + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0); + #endif + // --------------------------处理外轮廓------------------------------- + if(outborderimg!=NULL){ + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(outborderimg, &outborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算边缘点个数,即最大线程个数 + int outmaxthreadsize=(outborderimg->width+outborderimg->height-2)<<1; + if(outmaxthreadsize>maxThreadsPerBlock) + outmaxthreadsize=maxThreadsPerBlock; + dim3 grid,block; + block.x=outmaxthreadsize; + block.y=1; + block.z=1; + grid.x=1; + grid.y=1; + grid.z=1; + //--------------------------------- + int sharedmemsize=outborderCud.imgMeta.height* + outborderCud.imgMeta.width* + sizeof (unsigned char); + + _seedScanLineOutConShareKer<<>> + (outborderCud, + outmaxthreadsize + ); + //--------------------------------------------- + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_Filled.bmp",outborderimg); + // 交操作还要在 device 端使用图像,将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outborderimg); + // 经过一次主存显存传输,ROI子图像需要重新提取 + errcode = ImageBasicOp::roiSubImage(outborderimg, &outborderCud); + + #endif + }// end of out border + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[img] out border fill time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + // --------------------------处理内轮廓------------------------------- + if(outborderimg!=NULL && inborderimg!=NULL){ + + int errcode; + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inborderimg); + if (errcode != NO_ERROR) { + return errcode; + } + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(inborderimg, &inborderCud); + if (errcode != NO_ERROR) { + return errcode; + } + // 计算边缘点个数,即最大线程个数 + int inmaxthreadsize=(inborderimg->width+inborderimg->height-2)<<1; + if(inmaxthreadsize>maxThreadsPerBlock) + inmaxthreadsize=maxThreadsPerBlock; + dim3 grid,block; + block.x=inmaxthreadsize; + block.y=1; + block.z=1; + grid.x=1; + grid.y=1; + grid.z=1; + + int insharedmemsize=inborderCud.imgMeta.width* + inborderCud.imgMeta.height* + sizeof (unsigned char); + + _seedScanLineOutConShareKer<<>>(inborderCud,inmaxthreadsize); + + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborderFilled.bmp",inborderimg); + // 交操作还要在 device 端使用图像,将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inborderimg); + // 经过一次主存显存传输,ROI子图像需要重新提取 + errcode = ImageBasicOp::roiSubImage(inborderimg, &inborderCud); + #endif + }// end of in border & process + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[img] in border fill time " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + //--------------如果有内轮廓,则内外轮廓填充图像求交---------------------- + if(outborderimg!=NULL && inborderimg!=NULL){ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求交,结果放入outbordercud中,此时outborderCud和 + // inborderCud都在divice中,不用再次copytodevice + _intersectionKer<<>>( + outborderCud, + inborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + //--------------如果没有内轮廓,则仅仅对外轮廓填充结果求反--------- + else{ + dim3 gridsize,blocksize; + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outborderimg->width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outborderimg->height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel 函数求反 + _negateKer<<>>( + outborderCud + ); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + #ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[img ]inter or negate " << runTime << " ms" << endl; + cudaEventRecord(start, 0); + #endif + #ifdef DEBUG_IMG + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("[img]fill_result.bmp",outborderimg); + #endif + ImageBasicOp::copyToHost(outborderimg); + return NO_ERROR; +} + + + +// 成员方法:seedScanLineCurveShr(并行种子扫描线算法填充 Curve 集合围起的区域) +// 使用本并行算法时,内外轮廓要放入不同的 Curve 中。 +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + FillCurve::seedScanLineCurveShr( + Curve *outbordercurve, // 输入的 Curve ,内容为封闭区域 + // 外轮廓闭合曲线 + Curve *inbordercurve, // 输入的 Curve ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + Curve *fillcurve // 输出填充过的的 Curve + ){ + + // ----------------------输入Curve参数转化成img---------------------------- + Image *outborderimg=NULL; + Image *inborderimg=NULL; + + CurveConverter curcvt(BORDER_COLOR,BK_COLOR); + +#ifdef DEBUG_TIME + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0); +#endif + // --------------------------轮廓坐标集转换成img------------------------------- + if(outbordercurve==NULL) + return INVALID_DATA; + + ImageBasicOp::newImage(&outborderimg); + // 创建工作图像 + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(outborderimg,outbordercurve->maxCordiX+2 ,outbordercurve->maxCordiY+2); + // 把坐标集绘制到图像上,前景255,背景0 + curcvt.curve2Img(outbordercurve,outborderimg); + if(inbordercurve!=NULL){ + ImageBasicOp::newImage(&inborderimg); + //给工作图像分配空间,按照外轮廓大小分配 + ImageBasicOp::makeAtHost(inborderimg,outbordercurve->maxCordiX+2 ,outbordercurve->maxCordiY+2); + // 把坐标集绘制到图像上,前景255,背景0 + curcvt.curve2Img(inbordercurve,inborderimg); + } +#ifdef DEBUG_IMG + // 把填充前的图像保存到文件 + ImageBasicOp::copyToHost(outborderimg); + ImageBasicOp::writeToFile("outborder_notFilled.bmp",outborderimg); + ImageBasicOp::copyToHost(inborderimg); + ImageBasicOp::writeToFile("inborder_notFilled.bmp",inborderimg); +#endif +#ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[Curve] coor->img time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); +#endif + // --------------------------调用图像填充算法------------------------------- + seedScanLineImgGlo(outborderimg,inborderimg); + +#ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[Curve] fill time" << runTime << " ms" << endl; + cudaEventRecord(start, 0); +#endif + + //------------------------图像转化成curve,返回------------------------- + + + cudaThreadSynchronize(); + curcvt.img2Curve(outborderimg,fillcurve); + +#ifdef DEBUG_TIME + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + cout << "[Curve] img to Curve " << runTime << " ms" << endl; +#endif + + //------------------------内存回收------------------------------------- + ImageBasicOp::deleteImage(outborderimg); + ImageBasicOp::deleteImage(inborderimg); + + return NO_ERROR; +} + + diff --git a/okano_3_0/FillCurve.h b/okano_3_0/FillCurve.h new file mode 100644 index 0000000..232f603 --- /dev/null +++ b/okano_3_0/FillCurve.h @@ -0,0 +1,148 @@ +// FillCurve.h +// 创建人:曹建立 +// +// 填充 coordiset 集合围起的封闭区域(FillCoor) +// 功能说明:参数给出的 CoordiSet 集合是一条或多条闭合曲线,本类功能为将闭合 +// 曲线围成的封闭区域内部用 FILL_COLOR 像素值填充。 +// 使用并行算法时,输入的外轮廓集合和内轮廓集合要放入两个coordiset中。如果没 +// 有内轮廓,内轮廓坐标集设为NULL。 + +// 修订历史 +// 2013年9月20日(曹建立) +// 初始版本 +// 2013年11月19日(曹建立) +// 设计共享内存版本的填充内核函数,提高填充速度 +// 2014年10月9日 (曹建立) +// 将全局内存、共享内存两个版本放入一个类中整合 +#ifndef __FILLCURVE_H__ +#define __FILLCURVE_H__ + +#include "Image.h" +#include "CoordiSet.h" +#include "ErrorCode.h" +#include "Curve.h" +#include "CurveConverter.h" +#include "ImgConvert.h" + +// 宏:BORDER_COLOR +// 定义边界颜色 +#define BORDER_COLOR 255 +// 宏:BK_COLOR +// 定义背景颜色 +#define BK_COLOR 0 + +class FillCurve +{ + // 成员变量:maxThreadsPerBlock + int maxThreadsPerBlock; +public: + // 构造函数:FillCurveShared + // 有参数版本的构造函数 + __host__ __device__ + FillCurve( + int maxThreads + ) { + maxThreadsPerBlock=1024; + maxThreadsPerBlock=maxThreads; + } + + // 构造函数:FillCurveShared + // 无参数版本的构造函数 + __host__ __device__ + FillCurve() { + maxThreadsPerBlock=1024; + } + // 成员方法:getMaxThreads(获取 block 中最大线程数) + // 获取成员变量 maxThreadsPerBlock 的值。 + __host__ int // 返回值:成员变量 maxThreadsPerBlock 的值 + getMaxThreads(){ + // 返回 detheta 成员变量的值。 + return this->maxThreadsPerBlock; + } + + // 成员方法:setMaxThreads(设置 block 中最大线程数) + // 设置成员变量 maxThreadsPerBlock 的值。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setMaxThreads(int n){ + // 返回 detheta 成员变量的值。 + this->maxThreadsPerBlock=n; + return NO_ERROR; + } + + // 成员方法:seedScanLineImgShr(并行种子扫描线算法填充 coordiset 集合围起的区域) + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineImgShr( + Image *outborderimg, // 外轮廓闭合曲线图像,同时也是输出结果 + Image *inborderimg // 内轮廓闭合曲线图像,没有内轮廓设为空 + ); + + + // 成员方法:seedScanLineCoorShr(并行种子扫描线算法填充 coordiset 集合围起的区域) + // 使用本并行算法时,内外轮廓要放入不同的coordiset中。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineCoorShr( + CoordiSet *outbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 外轮廓闭合曲线 + CoordiSet *inbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + CoordiSet *fillcoor // 输出填充过的的 coordiset + ); + + + // 成员方法:seedScanLineCurveShr(并行种子扫描线算法填充 Curve 集合围起的区域) + // 使用本并行算法时,内外轮廓要放入不同的 Curve 中。该方法得到的填充后Curve + // 类型结构fillcurve中仅curveX、curveY、minX、minY、maxX、maxY域为有效数据, + // 其他域并未赋值。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineCurveShr( + Curve *outbordercurve, // 输入的 Curve ,内容为封闭区域 + // 外轮廓闭合曲线 + Curve *inbordercurve, // 输入的 Curve ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + Curve *fillcurve // 输出填充过的的 Curve + ); + + // 成员方法:seedScanLineImgGlo(并行种子扫描线算法填充 coordiset 集合围起的区域) + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineImgGlo( + Image *outborderimg, // 外轮廓闭合曲线图像,同时也是输出结果 + Image *inborderimg // 内轮廓闭合曲线图像,没有内轮廓设为空 + ); + + + // 成员方法:seedScanLineCoorGlo(并行种子扫描线算法填充 coordiset 集合围起的区域) + // 使用本并行算法时,内外轮廓要放入不同的coordiset中。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineCoorGlo( + CoordiSet *outbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 外轮廓闭合曲线 + CoordiSet *inbordercoor, // 输入的 coordiset ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + CoordiSet *fillcoor // 输出填充过的的 coordiset + ); + + +// 成员方法:seedScanLineCurveGlo(并行种子扫描线算法填充 Curve 集合围起的区域) +// 使用本并行算法时,内外轮廓要放入不同的 Curve 中。该方法得到的填充后Curve +//类型结构fillcurve中仅curveX、curveY、minX、minY、maxX、maxY域为有效数据, +//其他域并未赋值。 +__host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + seedScanLineCurveGlo( + Curve *outbordercurve, // 输入的 Curve ,内容为封闭区域 + // 外轮廓闭合曲线 + Curve *inbordercurve, // 输入的 Curve ,内容为封闭区域 + // 内轮廓闭合曲线。如果没有内轮廓,设为NULL + Curve *fillcurve // 输出填充过的的 Curve + ); + +}; // end of class + + +#endif diff --git a/okano_3_0/FreckleFilter.cu b/okano_3_0/FreckleFilter.cu new file mode 100644 index 0000000..1df38e7 --- /dev/null +++ b/okano_3_0/FreckleFilter.cu @@ -0,0 +1,736 @@ +// FreckleFilter +// 实现广义的中值滤波 + +#include "FreckleFilter.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" +#include "Template.h" +#include "TemplateFactory.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// Device 函数:_getMaxMatchValueDev(得到两个直方图的 length 长度 +// 相似度匹配最大值) +// 对圆周和园内两直方图进行长度为 length 的相似度匹配,返回最大值 +static __device__ void // 返回值:无返回值 +_getMaxMatchValueDev( + unsigned int *histogram1, // 圆周上的直方图 + unsigned int *histogram2, // 圆周上的直方图 + float &maxmatchvalue, // 像素点对应的最大匹配值指针 + int length, // 相似度匹配的长度参数 + int hisnum = 256 // 直方图的数组大小,本方法大小为 256 +); + +// Kernel 函数:_freckleFilterByVarSumCountKer(获得输出图像的每点像素平均值总 +// 和与累加次数算法操作) +// 根据方差阈值大小,得到输出图像的每点像素平均值总和与累加次数算法操作 +static __global__ void // Kernel 函数无返回值 +_freckleFilterByVarSumCountKer( + ImageCuda inimg, // 输入图像 + Template radtpl, // 圆形模板,用于指定圆内领域 + Template archtpl, // 环形模板,用于指定圆周的邻域 + float varTh, // 外部指定的方差阈值 + float *sum, // 像素平均值累加总和 + int *count // 像素平均值累加次数 +); + +// Kernel 函数:_freckleFilterPixelKer(实现给输出图像设定像素值算法操作) +// 根据每点像素累加总和与累加次数,给输出图像设定像素平均值 +static __global__ void // Kernel 函数无返回值 +_freckleFilterSetPixelKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + float *sum, // 像素平均值累加总和 + int *count, // 像素平均值累加次数 + int select // 最后赋值时的选择参数 +); + +// Kernel 函数:_freckleFilterByStrMscKer(获得输出图像的每点像素平均值总 +// 和与累加次数算法操作) +// 通过相似度匹配,根据匹配差阈值,得到输出图像的每点像素平均值总和与 +// 累加次数算法操作 +static __global__ void // Kernel 函数无返回值 +_freckleFilterByStrMscKer( + ImageCuda inimg, // 输入图像 + Template radtpl, // 圆形模板,用于指定圆内领域 + Template archtpl, // 环形模板,用于指定圆周的邻域 + float matchErrTh, // 外部指定的匹配差阈值 + int length, // 相似度匹配的长度参数 + int radius, // 圆领域的半径 + float *sum, // 像素平均值累加总和 + int *count // 像素平均值累加次数 +); + + +// Kernel 函数:_freckleFilterByVarSumCountKer(实现给输出图像设定像素值算法 +// 操作) +static __global__ void _freckleFilterByVarSumCountKer( + ImageCuda inimg, Template radtpl, Template archtpl, float varTh, + float *sum, int *count) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 + // dstc 表示 column, dstr 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于统一列的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致系统崩溃 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + + // 用来记录当前模版所在位置的指针 + int *curtplptr; + + // 用来记录当前输入图像所在位置的指针 + unsigned char *curinptr; + + // 计数器,用来记录某点在模版范围内拥有的点的个数 + int statistic[4] = { 0 , 0, 0, 0 }; + + // 迭代求平均值和方差使用的中间值 + float m[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // 计算得到的平均值 + float mean[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // 计算得到的拱圆模板领域方差 + float variance[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + int pix; // 局部变量,临时存储像素值 + + // 指定当前环形模版所在位置 + curtplptr = archtpl.tplData; + + // 扫描环形模版范围内的每个输入图像的像素点 + for (int i = 0; i < archtpl.count; i++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个下标的 + // 数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + float temp; // 局部变量,在进行迭代时的中间变量 + + // 先判断当前像素的 x 分量是否越界,如果越界,则跳过,扫描下一个模版点, + // 如果没有越界,则分别处理当前列的相邻的 4 个像素 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 根据 dx 和 dy 获取第一个像素的指针 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + // 检测此像素点的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 对第一个点进行迭代 + pix = *(curinptr); + statistic[0]++; + temp = pix - mean[0]; + mean[0] += temp / statistic[0]; + m[0] += temp * (pix - mean[0]); + } + + // 获取第二个像素点的指针 + curinptr = curinptr + inimg.pitchBytes; + dy++; + // 检测第二个像素点的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 对第二个点进行迭代 + pix = *(curinptr); + statistic[1]++; + temp = pix - mean[1]; + mean[1] += temp / statistic[1]; + m[1] += temp * (pix - mean[1]); + } + + // 获取第三个像素点的指针 + curinptr = curinptr + inimg.pitchBytes; + dy++; + // 检测第三个像素点的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 对第三个点进行迭代 + pix = *(curinptr); + statistic[2]++; + temp = pix - mean[2]; + mean[2] += temp / statistic[2]; + m[2] += temp * (pix - mean[2]); + } + + // 获取第四个像素点的指针 + curinptr = curinptr + inimg.pitchBytes; + dy++; + // 检测第四个像素点的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 对第四个点进行迭代 + pix = *(curinptr); + statistic[3]++; + temp = pix - mean[3]; + mean[3] += temp / statistic[3]; + m[3] += temp * (pix - mean[3]); + } + } + } + + // 计算输出坐标点对应的图像数据数组下标。 + int index; + + // 对每个像素点求圆周上点的方差大小,根据方差与阈值大小给输出点累加和 + for(int i = 0; i < 4; i++) { + // 如果圆周领域内的的点个数为 0,则判断下一个像素点 + if(statistic[i] == 0) + continue; + // 计算环形模板领域的方差 + variance[i] = m[i] / statistic[i]; + + // 如果方差小于给定阈值,则对圆形模板里的所有点赋平均值 + if (variance[i] < varTh) { + // 指定当前圆形模版所在位置 + curtplptr = radtpl.tplData; + + // 扫描圆形模版范围内的每个输入图像的像素点 + for (int j = 0; j < radtpl.count; j++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个 + // 下标的数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 根据 dx 和 dy 获取像素下标 + dy = dy + i; + index = dx + dy * inimg.imgMeta.width; + + // 如果没有越界,则分别处理当前列的相邻的符合条件的像素 + // 给累加和累加平均值,累加次数相应加 1 + if (dx >= 0 && dx < inimg.imgMeta.width && + dy >= 0 && dy < inimg.imgMeta.height) { + atomicAdd(&sum[index], mean[i]); + atomicAdd(&count[index], 1); + } + } + } + } +} + +// Kernel 函数:_freckleFilterSetPixelKer(实现给输出图像设定像素值算法操作) +static __global__ void _freckleFilterSetPixelKer( + ImageCuda inimg, ImageCuda outimg, float *sum, int *count, int select) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于统一列的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int outidx = dstr * outimg.imgMeta.width + dstc; + int out = dstr * outimg.pitchBytes + dstc; + + int temp; // 临时变量用于 float 型数据转 int 型,需要四舍五入 + + // 计算每一个点的像素平均值,并且四舍五入 float 转 int 型 + if (count[outidx] == 0) { + // 如果该点没有被累加和,如果为 FRECKLE_OPEN 则应该赋值为 + // 原图像对应灰度值,如果为 FRECKLE_CLOSE,则赋值为 0 + if (select == FRECKLE_OPEN) + temp = inimg.imgMeta.imgData[out]; + else if (select == FRECKLE_CLOSE) + temp = 0; + } else { + // 如果被累加和,则按以下方式求像素平均值并按要求处理 + temp = (int)(sum[outidx] / count[outidx] + 0.5f); + } + + // 对图像每点像素值赋上对应值 + outimg.imgMeta.imgData[out] = (unsigned char)temp; + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++dstr >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + outidx += outimg.imgMeta.width; + out += outimg.pitchBytes; + + // 计算每一个点的像素平均值,并且四舍五入 float 转 int 型 + if (count[outidx] == 0) { + // 如果该点没有被累加和,如果为 FRECKLE_OPEN 则应该赋值为 + // 原图像对应灰度值,如果为 FRECKLE_CLOSE,则赋值为 0 + if (select == FRECKLE_OPEN) + temp = inimg.imgMeta.imgData[out]; + else if (select == FRECKLE_CLOSE) + temp = 0; + } else { + // 如果被累加和,则按以下方式求像素平均值并按要求处理 + temp = (int)(sum[outidx] / count[outidx] + 0.5f); + } + + // 对图像每点像素值赋上对应值 + outimg.imgMeta.imgData[out] = (unsigned char)temp; + } +} + +// Device 函数:_getMaxMatchValueDev(得到两个直方图的 length 长度 +// 相似度匹配最大值) +static __device__ void _getMaxMatchValueDev( + unsigned int *histogram1, unsigned int *histogram2, + float &maxmatchvalue, int length, int hisnum) +{ + // 临时变量 matchvalue,存储匹配的结果值 + float matchvalue = 0.0f; + + // 从左端开始匹配 + // 临时变量 location,用于定位匹配最右位置 + int location = hisnum - length; + for (int j = 0; j <= location; j++) { + + // 临时变量,存储计算相关系数的和 + unsigned int sum1 = { 0 }; + unsigned int sum2 = { 0 }; + unsigned int sum3 = { 0 }; + unsigned int sum4 = { 0 }; + unsigned int sum5 = { 0 }; + // 临时变量,存储获得数组对应值 + unsigned int tmp1, tmp2; + // 临时变量,存储计算相关系数算法的分母 + float m1, m2; + + // 计算相似度需要用到的临时变量 + for (int k = 0; k < length; k++) { + // 取得对应直方图值 + tmp1 = *(histogram1 + j + k); + tmp2 = *(histogram2 + j + k); + + // 计算相似度要用到的累加和 + sum1 += tmp1; + sum2 += tmp2; + sum3 += tmp1 * tmp2; + sum4 += tmp1 * tmp1; + sum5 += tmp2 * tmp2; + } + // 计算相似度的分母临时变量 + m1 = sqrtf((float)(length * sum4 - sum1 * sum1)); + m2 = sqrtf((float)(length * sum5 - sum2 * sum2)); + // 计算匹配的相似度 + if (m1 <= 0.000001f || m2 <= 0.000001f) + matchvalue = 0.0f; + else + matchvalue = ((int)(length * sum3 - sum1 * sum2)) / + (m1 * m2); + + // 取相似度最大值 + if (matchvalue > maxmatchvalue) { + maxmatchvalue = matchvalue; + } + } +} + +// Kernel 函数:_freckleFilterByStrMscKer(实现 +// 给输出图像设定像素值算法操作) +static __global__ void _freckleFilterByStrMscKer( + ImageCuda inimg, Template radtpl, Template archtpl, float matchErrTh, + int length, int radius, float *sum, int *count) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 + // dstc 表示 column, dstr 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于统一列的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否符合作为圆心的条件,若不符,则不进行处理 + if (dstc % radius != 0 || dstr % radius != 0 || dstc <= 0 || dstr <= 0 || + dstc >= inimg.imgMeta.width - 1 || dstr >= inimg.imgMeta.height - 1) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + + // 用来记录当前模版所在位置的指针 + int *curtplptr; + + // 用来记录当前输入图像所在位置的指针 + unsigned char *curinptr; + + // 圆周上的图像直方图 histogram1 + unsigned int histogram1[256] = { 0 }; + + // 圆内的图像直方图 histogram2 + unsigned int histogram2[256] = { 0 }; + + // 计数器,用来记录某点在圆周上和园内拥有的点的个数 + int statistic = 0; + + unsigned int pix; // 局部变量,临时存储像素值 + + // 指定当前环形模版所在位置 + curtplptr = archtpl.tplData; + + // 扫描环形模版范围内的每个输入图像的像素点 + for (int i = 0; i < archtpl.count; i++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个下标的 + // 数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 先判断当前像素的 x 分量,y 分量是否越界,如果越界,则跳过,扫描 + // 下一个模版点,如果没有越界,则分别处理当前列的相邻的 4 个像素 + if (dx >= 0 && dx < inimg.imgMeta.width && + dy >= 0 && dy < inimg.imgMeta.height) { + // 根据 dx 和 dy 获取像素的指针 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + pix = *(curinptr); + histogram1[pix]++; + statistic++; + } + } + + // 如果圆周领域内的的点个数为 0 这直接返回 + if(statistic == 0) + return; + + // 指定当前圆形模版所在位置 + curtplptr = radtpl.tplData; + + // 扫描环形模版范围内的每个输入图像的像素点 + for (int i = 0; i < radtpl.count; i++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个下标的 + // 数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 先判断当前像素的 x 分量,y 分量是否越界,如果越界,则跳过,扫描 + // 下一个模版点,如果没有越界,则分别处理当前列的相邻的 4 个像素 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 根据 dx 和 dy 获取第一个像素的指针 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + pix = *(curinptr); + histogram2[pix]++; + } + } + + // 存储以四个像素圆心得到两直方图的匹配最大值 + float maxmatchvalue = 0.0f; + + // 得到四个像素的两直方图的匹配最大值 + _getMaxMatchValueDev(histogram1, histogram2, maxmatchvalue, length, 256); + + // 计算输出坐标点对应的图像数据数组下标。 + int index; + + // 根据匹配差与阈值大小对符合条件像素点对其圆周上点进行排序, + // 取中间 50% 灰度平均,给输出点累加和累加赋值 + + // 如果匹配差大于给定阈值,则对圆形模板里的所有点赋平均值 + if (1 - maxmatchvalue > matchErrTh) { + // 存储圆周上的图像值的中值平均(取排序后中间 50% 平均) + float mean; + + // 去掉排序结果中前端的数量 + int lownum = (int)(statistic * 0.25f + 0.5f); + // 去掉排序结果中末端端的数量 + int highnum = (int)(statistic * 0.25f + 0.5f); + + // 对直方图前后端个数统计 + int lowcount = 0, highcount = 0; + // 在前后端统计时,中间段少加的值 + int lowvalue = 0, highvalue = 0; + // 前后端统计时的开关 + bool lowmask = false, highmask = false; + // 直方图中间段的两端索引 + int lowindex = 0, highindex = 0; + + for (int k = 0; k < 256; k++) { + // 计算直方图前端的个数 + lowcount += histogram1[k]; + if (!lowmask && lowcount >= lownum) { + lowindex = k + 1; + lowvalue = (lowcount - lownum) * k; + lowmask = true; + } + // 直方图后端的循环索引 + int high = 255 - k; + // 计算直方图后端的个数 + highcount += histogram1[high]; + if (!highmask && highcount >= highnum) { + highindex = high - 1; + highvalue = (highcount - highnum) * high; + highmask = true; + } + // 如果前后端开关都打开,表示都找到了对应位置,就退出循环 + if (lowmask && highmask) + break; + } + + // 如果 lowindex 大于 highindex,表示没有要处理的元素,则返回 + if (lowindex > highindex) + return; + + // 计算领域内的像素值总和 + float tmpsum = (float)(lowvalue + highvalue); + for (int k = lowindex; k <= highindex; k++) + tmpsum += k * histogram1[k]; + + // 计算平均值 + mean = tmpsum / (statistic - lownum - highnum); + + // 指定当前圆形模版所在位置 + curtplptr = radtpl.tplData; + + // 扫描圆形模版范围内的每个输入图像的像素点 + for (int j = 0; j < radtpl.count; j++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个 + // 下标的数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 根据 dx 和 dy 获取像素下标 + dy++; + index = dx + dy * inimg.imgMeta.width; + + // 如果没有越界,则分别处理当前列的相邻的符合条件的像素 + // 给累加和累加平均值,累加次数相应加 1 + if (dx >= 0 && dx < inimg.imgMeta.width && + dy >= 0 && dy < inimg.imgMeta.height) { + atomicAdd(&sum[index], mean); + atomicAdd(&count[index], 1); + } + } + } +} + +// Host 成员方法:freckleFilter(广义的中值滤波) +__host__ int FreckleFilter::freckleFilter(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 开关错误检查,如果既不是开选择也不是闭选择,则返回错误 + if (select != FRECKLE_OPEN && select != FRECKLE_CLOSE) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 定义模板 radtpl 用于获取圆形领域模板 + Template *radtpl; + + // 定义圆形模板的尺寸 + dim3 radsize(this->radius * 2 + 1, this->radius * 2 + 1, 1); + + // 通过模板工厂得到圆形领域模板 + errcode = TemplateFactory::getTemplate(&radtpl, TF_SHAPE_CIRCLE, + radsize, NULL); + + // 检查圆形模板是否为 NULL,如果为 NULL 直接报错返回。 + if (errcode != NO_ERROR) + return errcode; + + // 将模板拷贝到 Device 内存中 + errcode = TemplateBasicOp::copyToCurrentDevice(radtpl); + if (errcode != NO_ERROR) { + // 放回 radtpl 模板 + TemplateFactory::putTemplate(radtpl); + return errcode; + } + + // 定义模板 archtpl 用于获取环形领域模板 + Template *archtpl; + + // 定义环形模板的尺寸 + dim3 arcsize(this->radius * 2 + 1, (this->radius + 4) * 2 + 1, 1); + + // 得到环形领域模板 + errcode = TemplateFactory::getTemplate(&archtpl, TF_SHAPE_ARC, + arcsize, NULL); + + // 检查环形模板是否为 NULL,如果为 NULL 报错返回。 + if (errcode != NO_ERROR) { + // 放回 radtpl 模板 + TemplateFactory::putTemplate(radtpl); + return errcode; + } + + // 将模板拷贝到 Device 内存中 + errcode = TemplateBasicOp::copyToCurrentDevice(archtpl); + if (errcode != NO_ERROR) { + // 放回模板 + TemplateFactory::putTemplate(radtpl); + TemplateFactory::putTemplate(archtpl); + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize1, gridsize2; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize1.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize1.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + gridsize2.x = gridsize1.x; + gridsize2.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 得到要处理的像素总个数 + size_t datasize = outsubimgCud.imgMeta.width * outsubimgCud.imgMeta.height; + + cudaError_t cuerrcode; // CUDA 调用返回的错误码。 + + // 定义 sum 全局变量指针,申请一个 outsubimgCud.imgMeta.width * + // outsubimgCud.imgMeta.height 的 float 型数组,用于存储每点像素平均值累加 + // 总和。 + float *sum; + + // 定义 count 全局变量指针,申请一个 outsubimgCud.imgMeta.width * + // outsubimgCud.imgMeta.height 的 int 型数组,用于存储每点像素平均值累加 + // 次数。 + int *count; + + // 定义局部变量,用于多份数据的一份申请 + void *temp_dev; + + // 在设备端申请内存,然后分配给各个变量 + cuerrcode = cudaMalloc( + (void **)&temp_dev, + datasize * sizeof (float) + datasize * sizeof (int)); + if (cuerrcode != cudaSuccess) { + // 放回模板 + TemplateFactory::putTemplate(radtpl); + TemplateFactory::putTemplate(archtpl); + return CUDA_ERROR; + } + + // 为变量分配内存 + sum = (float *)temp_dev; + count = (int *)(sum + datasize); + + // 初始化累加和的所有值为 0 + cuerrcode = cudaMemset(sum, 0, datasize * sizeof (float)); + if (cuerrcode != cudaSuccess) { + // 放回模板 + TemplateFactory::putTemplate(radtpl); + TemplateFactory::putTemplate(archtpl); + // 释放累加和与累加次数的总空间 + cudaFree(temp_dev); + return CUDA_ERROR; + } + + // 初始化累加次数的所有值为 0 + cuerrcode = cudaMemset(count, 0, datasize * sizeof (int)); + if (cuerrcode != cudaSuccess) { + // 放回模板 + TemplateFactory::putTemplate(radtpl); + TemplateFactory::putTemplate(archtpl); + // 释放累加和与累加次数的总空间 + cudaFree(temp_dev); + return CUDA_ERROR; + } + + if (method == FRECKLE_VAR_TH) { + // 若方法为方差阈值法,则调用相应方差阈值法的 Kernel 获得 + // 输出图像的每点像素平均值累加总和与累加次数。 + _freckleFilterByVarSumCountKer<<>>( + insubimgCud, *radtpl, *archtpl, this->varTh, sum, count); + } else if (method == FRECKLE_MATCH_ERRTH) { + + // 若方法为相似度匹配法,则调用相应相似度匹配法的 Kernel 获得 + // 输出图像的每点像素平均值累加总和与累加次数。 + _freckleFilterByStrMscKer<<>>( + insubimgCud, *radtpl, *archtpl, this->matchErrTh, this->length, + this->radius, sum, count); + } else { + // method 错误检查,进入这条分支表示没有外部方法设置有误 + return INVALID_DATA; + } + + // 检查核函数运行是否出错 + if (cudaGetLastError() != cudaSuccess) { + // 放回模板 + TemplateFactory::putTemplate(radtpl); + TemplateFactory::putTemplate(archtpl); + // 释放累加和与累加次数的总空间 + cudaFree(temp_dev); + return CUDA_ERROR; + } + + // 放回模板 + TemplateFactory::putTemplate(radtpl); + TemplateFactory::putTemplate(archtpl); + + // 调用 Kernel 函数实现给输出图像设定像素值。 + _freckleFilterSetPixelKer<<>>( + insubimgCud, outsubimgCud, sum, count, this->select); + + // 检查核函数运行是否出错 + if (cudaGetLastError() != cudaSuccess) { + // 释放累加和与累加次数的总空间 + cudaFree(temp_dev); + return CUDA_ERROR; + } + + // 释放累加和与累加次数的总空间 + cudaFree(temp_dev); + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/FreckleFilter.h b/okano_3_0/FreckleFilter.h new file mode 100644 index 0000000..c834883 --- /dev/null +++ b/okano_3_0/FreckleFilter.h @@ -0,0 +1,307 @@ +// FreckleFilter.h +// 创建者:欧阳翔 +// +// 广义的中值滤波(Freckle Filter) +// 功能说明:具有两种不同的方法实现:(1)根据给定半径 radius 和 +// radius + 4 生成的拱形邻域内的方差大小,比较方差与外部给定的 +// varTh 大小,如果方差小于给定阈值,则进行领域灰度平均 +// 赋值给新图像对应位置;(2)根据圆内和圆外灰度值序列的子段相 +// 似度匹配得到最小匹配差进行中值滤波。如果最小匹配差小于外部给 +// 定的 matchErrTh 的阈值,则实现另一种方式的中值平均。 +// +// 修订历史: +// 2012年10月29日(欧阳翔) +// 初始版本,方差阈值法的实现。 +// 2012年11月01日(欧阳翔,于玉龙) +// 更正了代码的一些注释规范。 +// 2012年11月29日(欧阳翔) +// 更正了方差迭代算法的一处 bug +// 2012年12月03日(欧阳翔) +// 修改了一些注释错误 +// 2012年12月04日(欧阳翔) +// 初步完成了第二种方法,相似度匹配法 +// 2012年12月05日(欧阳翔) +// 修正了相似度计算的一处 Bug +// 2012年12月10日(欧阳翔,于玉龙) +// 借用了 TemplateFactory 中于玉龙的得到圆形模板算法,改进了 +// 本程序中得到圆形模板的算法 +// 2012年12月11日(欧阳翔) +// 通过模板工厂得到圆形和环形模板,不再使用自己写的圆形和环形模板 +// 2012年12月12日(欧阳翔) +// 增加了宏 FRECKLE_VAR_TH 和 FRECKLE_MATCH_ERRTH,用于区分滤波 +// 实现的两种方法,减少了相同代码量,增加了代码可维护性 +// 2012年12月13日(欧阳翔,于玉龙) +// 更正了部分格式错误 +// 2012年12月18日(欧阳翔) +// 修正了相似度计算的部分 Bug +// 2012年12月19日(欧阳翔) +// 改正了通过模板工厂得到环形模板的调用 +// 2012年12月21日(欧阳翔) +// 更正了相似度匹配法的一处严重 Bug,修改了相似度计算过程 +// 2012年12月28日(欧阳翔) +// 增加了开关宏 FRECKEL_OPEN 和 FRECKLE_CLOSE ,用于最后当累加次数为 0 +// 赋值时是赋值原图像对应灰度值还是赋值 0。 +// 2012年12月29日(欧阳翔) +// 修正了类的设计,增加了成员 method 和 select 属性 +// 2013年01月02日(欧阳翔) +// 相似度匹配法中原来的一个线程处理四个点改成处理一个点,修正了重复计算 +// 问题,减少了计算量,避免了两处可能除 0 的情况。 +// 2013年03月24日(张丽洁) +// 修正了一处潜在的 bug。 + +#ifndef __FRECKLEFILTER_H__ +#define __FRECKLEFILTER_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 宏:VAR_TH +// 表示该滤波处理将用方差阈值法 +#define FRECKLE_VAR_TH 0 + +// 宏:MATCH_ERRTH +// 表示该滤波处理将用相似度匹配法 +#define FRECKLE_MATCH_ERRTH 1 + +// 宏:FRECKLE_OPEN +// 打开宏,表示在最后输出赋值时赋原图像的对应灰度值 +#define FRECKLE_OPEN 1 + +// 宏:FRECKLE_CLOSE +// 关闭宏,表示在最后输出赋值时赋值赋灰度值 0 +#define FRECKLE_CLOSE 0 + + +// 类:FreckleFilter(广义的中值滤波) +// 继承自:无 +// 广义的图像中值滤波。具有两种不同的方法实现:(1)根据给定半径 radius +// 和 radius + 4 生成的拱形邻域内的方差大小,比较方差与外部给定的 varTh +// 大小,如果方差小于给定阈值,则进行领域灰度平均赋值给新图像对应位置;(2)根 +// 据圆内和圆外灰度值序列的子段相似度匹配得到最小匹配差进行中值滤波。如果最小配 +// 配差大于外部给定的 matchErrTh 的阈值,则实现另一种方式的中值平均。 +class FreckleFilter { + +protected: + + // 成员变量:radius(圆形邻域参数) + // 外部设定的圆形领域的半径大小。 + int radius; + + // 成员变量:varTh(方差阈值参数) + // 外部设定的用于圆周上灰度值方差大小的比较阈值。 + float varTh; + + // 成员变量:matchErrTh(匹配差阈值参数) + // 外部设定的用于序列的子段间最小匹配差的阈值。 + float matchErrTh; + + // 成员变量:length(匹配长度参数) + // 外部设定的用于序列匹配计算长度。 + int length; + + // 成员变量:method (区分两种方法的调用参数) + // 外部设定的用于选择方差阈值法还是相似度匹配法 + // 若值为 FRECKLE_VAR_TH 则表示方差阈值法的实现, + // 若为 FRECKLE_MATCH_ERRTH 则表示相似度匹配法的滤波实现 + int method; + + // 成员变量:select(最后给输出图像赋值时的区分参数) + // 外部设定,当最后计算结果是 0,给输出图像赋值时,如果 select + // 值为 FRECKLE_OPEN 则应该赋值为原图像对应灰度值,如果为 + // FRECKLE_CLOSE 则赋值为 0 + int select; + +public: + + // 构造函数:FreckleFilter + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + FreckleFilter() + { + // 使用默认值为类的各个成员变量赋值。 + radius = 1; // 默认圆的半径大小为 1 + varTh = 0.0f; // 圆周上灰度值方差大小的阈值默认为 0 + matchErrTh = 0.0f; // 序列的子段间最小匹配差的阈值的默认值为 0.0 + length = 2; // 匹配计算长度的默认值为 2 + method = FRECKLE_VAR_TH; // 默认调用第一种方法方差阈值法 + select = FRECKLE_OPEN; // 最后给输出图像赋值时遇到赋 0 时默认赋 + // 原图像对应灰度值 + } + + // 构造函数:FreckleFilter + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + FreckleFilter ( + int radius, // 圆形邻域参数(具体解释见成员变量) + float varTh, // 方差阈值参数(具体解释见成员变量) + float matchErrTh, // 匹配差阈值参数(具体解释见成员变量) + int length, // 匹配长度参数(具体解释见成员变量) + int method, // 区分两种方法的调用参数(具体解释见成员变量) + int select // 在最后给输出图像赋值时的区分参数(具体解释见 + // 成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->radius = 1; // 默认圆的半径大小为 1 + this->varTh = 0.0f; // 圆周上灰度值方差大小的阈值默认为 0 + this->matchErrTh = 0.0f; // 序列的子段间最小匹配差的阈值的默认 + // 值为 0.0 + this->length = 2; // 匹配计算长度的默认值为 2 + this->method = FRECKLE_VAR_TH; // 默认调用第一种方法方差阈值法 + this->select = FRECKLE_OPEN; // 最后给输出图像赋值时遇到赋 0 时默认赋 + // 原图像对应灰度值 + + // 根据参数列表中的值设定成员变量的初值 + this->setRadius(radius); + this->setVarTh(varTh); + this->setMatchErrTh(matchErrTh); + this->setLength(length); + this->setMethod(method); + this->setSelect(select); + } + + // 成员方法:getRadius(读取圆形邻域参数) + // 读取 radius 成员变量的值。 + __host__ __device__ int // 返回值:当前 radius 成员变量的值。 + getRadius() const + { + // 返回 radius 成员变量的值。 + return this->radius; + } + + // 成员方法:setRadius(设置圆形邻域参数) + // 设置 radius 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setRadius( + int radius // 新的圆形邻域参数 + ) { + // 将 radius 成员变量赋成新值 + this->radius = radius; + return NO_ERROR; + } + + // 成员方法:getvarTh(读取方差阈值参数) + // 读取 varTh 成员变量的值。 + __host__ __device__ float // 返回值:当前 varTh 成员变量的值。 + getVarTh() const + { + // 返回 varTh 成员变量的值。 + return this->varTh; + } + + // 成员方法:setvarTh(设置方差阈值参数) + // 设置 varTh 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setVarTh( + float varTh // 新的方差阈值参数 + ) { + // 将 varTh 成员变量赋成新值 + this->varTh = varTh; + return NO_ERROR; + } + + // 成员方法:getmatchErrTh(读取匹配差阈值参数) + // 读取 matchErrTh 成员变量的值。 + __host__ __device__ float // 返回值:当前 matchErrTh 成员变量的值。 + getMatchErrTh() const + { + // 返回 matchErrTh 成员变量的值。 + return this->matchErrTh; + } + + // 成员方法:setmatchErrTh(设置匹配差阈值参数) + // 设置 matchErrTh 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + setMatchErrTh( + float matchErrTh // 新的匹配差阈值参数 + ) { + // 将 matchErrTh 成员变量赋成新值 + this->matchErrTh = matchErrTh; + return NO_ERROR; + } + + // 成员方法:getLength(读取匹配长度参数) + // 读取 length 成员变量的值。 + __host__ __device__ int // 返回值:当前 length 成员变量的值。 + getLength() const + { + // 返回 length 成员变量的值。 + return this->length; + } + + // 成员方法:setLength(设置匹配长度参数) + // 设置 length 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setLength( + int length // 新的匹配长度参数 + ) { + // 将 length 成员变量赋成新值 + this->length = length; + return NO_ERROR; + } + + // 成员方法:getMethod(读取区分两种方法的调用参数) + // 读取 method 成员变量的值。 + __host__ __device__ int // 返回值:当前 method 成员变量的值。 + // 若为 0(也即 FRECKLE_VAR_TH),表示调用 + // 第一种方法(方差阈值法),若为 1(也即 + // FRECKLE_MATCH_ERRTH),则表示调用第二种方法 + getMethod() const + { + // 返回 method 成员变量的值。 + return this->method; + } + + // 成员方法:setMethod(设置最后给输出图像赋值时的区分参数) + // 设置 method 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMethod( + int method // 新的区分两种方法的调用参数 + ) { + // 将 method 成员变量赋成新值 + this->method = method; + return NO_ERROR; + } + + // 成员方法:getSelect(读取最后给输出图像赋值时的区分参数) + // 读取 method 成员变量的值。 + __host__ __device__ int // 返回值:当前 length 成员变量的值。 + // 若为 0(也即 FRECKLE_CLOSE),表示最后赋 0 值, + // 若为 1(也即 FRECKLE_OPEN),则表示最后赋原图像 + // 对应灰度值 + getSelect() const + { + // 返回 select 成员变量的值。 + return this->select; + } + + // 成员方法:setSelect(设置最后给输出图像赋值时的区分参数) + // 设置 select 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setSelect( + int select // 新的最后给输出图像赋值时的区分参数 + ) { + // 将 select 成员变量赋成新值 + this->select = select; + return NO_ERROR; + } + + // Host 成员方法:freckleFilter(广义的中值滤波) + // 对图像进行广义的中值滤波,可以实现方差阈值的实现方法,也可以实现相似度 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + freckleFilter( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/GaussianSmoothImage.cu b/okano_3_0/GaussianSmoothImage.cu new file mode 100644 index 0000000..8178162 --- /dev/null +++ b/okano_3_0/GaussianSmoothImage.cu @@ -0,0 +1,1050 @@ +// GaussianSmoothImage.cu +// 图像高斯平滑操作,包括普通高斯平滑和带mask的高斯平滑 + +#include "GaussianSmoothImage.h" +#include "ErrorCode.h" + +// 宏定义,定义了五个高斯平滑尺度对应的权重总和 +#define GAUSS_THREE 16 +#define GAUSS_FIVE 256 +#define GAUSS_SEVEN 4096 +#define GAUSS_NINE 65536 +#define GAUSS_ELEVEN 1048576 + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 下列五个核函数为普通高斯平滑核函数 + +// 1.平滑窗口大小为3*3的高斯平滑函数 +static __global__ void +gauss3SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight // 平滑窗口高度 +); + +// 2.平滑窗口大小为5*5的高斯平滑函数 +static __global__ void +gauss5SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight // 平滑窗口高度 +); +// 3.平滑窗口大小为7*7的高斯平滑函数 +static __global__ void +gauss7SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight // 平滑窗口高度 +); + +// 4.平滑窗口大小为9*9的高斯平滑函数 +static __global__ void +gauss9SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight // 平滑窗口高度 +); + +// 5.平滑窗口大小为11*11的高斯平滑函数 +static __global__ void +gauss11SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight // 平滑窗口高度 +); + +// 下列五个核函数为带mask的高斯平滑函数 + +// 1.平滑窗口大小为3*3,带mask的高斯平滑函数 +static __global__ void +gauss3SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight, // 平滑窗口高度 + ImageCuda maskImageGPU, // mask图像 + unsigned char mask // mask值 +); + +// 2.平滑窗口大小为5*5,带mask的高斯平滑函数 +static __global__ void +gauss5SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight, // 平滑窗口高度 + ImageCuda maskImageGPU, // mask图像 + unsigned char mask // mask值 +); + +// 3.平滑窗口大小为7*7,带mask的高斯平滑函数 +static __global__ void +gauss7SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight, // 平滑窗口高度 + ImageCuda maskImageGPU, // mask图像 + unsigned char mask // mask值 +); + +// 4.平滑窗口大小为9*9,带mask的高斯平滑函数 +static __global__ void +gauss9SmoothImage( + ImageCuda const origiImageGPU , // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight, // 平滑窗口高度 + ImageCuda maskImageGPU, // mask图像 + unsigned char mask // mask值 +); + +// 5.平滑窗口大小为11*11,带mask的高斯平滑函数 +static __global__ void +gauss11SmoothImage( + ImageCuda origiImageGPU, // 原始图像 + ImageCuda gaussSmImageGPU, // 平滑后图像 + int smLocatX, // 平滑起始横坐标 + int smLocatY, // 平滑起始纵坐标 + int smWidth, // 平滑窗口宽度 + int smHeight, // 平滑窗口高度 + ImageCuda maskImageGPU, // mask图像 + unsigned char mask // mask值 +); + + + + // 平滑窗口大小为7*7的高斯平滑函数实现 +static __global__ void gauss7SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 高斯平滑系数数组 + int GF[7] = {1, 6, 15, 20, 15, 6, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 7;i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 7;j++) + c += GF[i] * GF[j] * + origiImageGPU.imgMeta.imgData[(y + i - 3) * w + (x + j - 3)]; + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / GAUSS_SEVEN + 0.5f; +} + + // 平滑窗口大小为5*5的高斯平滑函数实现 +static __global__ void gauss5SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 高斯平滑系数数组 + int GF[5] = {1, 4, 6, 4, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 5;i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 5;j++) + c += GF[i] * GF[j] * + origiImageGPU.imgMeta.imgData[(y + i - 2) * w + (x + j - 2)]; + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / GAUSS_FIVE + 0.5f; +} + +// 平滑窗口大小为9*9的高斯平滑函数实现 +static __global__ void gauss9SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 高斯平滑系数数组 + const int GF[9] = {1, 8, 28, 56, 70, 56, 28, 8, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 9;i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 9; j++) + c += GF[i] * GF[j] * + origiImageGPU.imgMeta.imgData[(y + i - 4) * w + (x + j - 4)]; + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / GAUSS_NINE + 0.5f; +} + +// 平滑窗口大小为11*11的高斯平滑函数实现 +static __global__ void gauss11SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 高斯平滑系数数组 + int GF[11] = {1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 11;i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 11;j++) + c += GF[i] * GF[j] * + origiImageGPU.imgMeta.imgData[(y + i - 5) * w + (x + j - 5)]; + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / GAUSS_ELEVEN + 0.5f; +} + + // 平滑窗口大小为3*3的高斯平滑函数实现 +static __global__ void gauss3SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight) +{ + + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 高斯平滑系数数组 + int GF[3] = {1, 2, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 3;i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 3; j++) + c += GF[i] * GF[j] * + origiImageGPU.imgMeta.imgData[(y + i - 1) * w + (x + j - 1)]; + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / GAUSS_THREE + 0.5f ; +} + + +// 平滑窗口大小为7*7的,带mask高斯平滑函数实现 +static __global__ void gauss7SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight, + ImageCuda maskImageGPU, + unsigned char mask) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 如果mask图像像素值不等于mask则不处理 + if (maskImageGPU.imgMeta.imgData[y * w + x] != mask) + return ; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 获取mask图像数据 + unsigned char * maskImg = maskImageGPU.imgMeta.imgData; + + // 高斯平滑系数数组 + int gf[7] = {1, 6, 15, 20, 15, 6, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 参加计算的像素点权重总和wsum,当前权重wgh + int wsum = 0, wgh; + + // 图像像素索引 + int mIdx; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 7; i++){ + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 7; j++) { + // 获取图像像素索引 + mIdx=(y + i - 3) * w + (x + j - 3); + + // 只处理mask图像像素值等于mask值的像素点 + if (maskImg[mIdx] == mask) { + + // 计算当前像素点的权重 + wgh = gf[i] * gf[j]; + + // 当前像素点的权重累加到总权重中 + wsum += wgh ; + + // 计算像素值加权累加和 + c += wgh * origiImageGPU.imgMeta.imgData[mIdx]; + } + } + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / wsum + 0.5f; +} + + // 平滑窗口大小为5*5的,带mask高斯平滑函数实现 +static __global__ void gauss5SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight, + ImageCuda maskImageGPU, + unsigned char mask) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 如果mask图像像素值不等于mask则不处理 + if (maskImageGPU.imgMeta.imgData[y * w + x] != mask) + return ; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 获取mask图像数据 + unsigned char * maskImg = maskImageGPU.imgMeta.imgData; + + // 高斯平滑系数数组 + int gf[5] = {1, 4, 6, 4, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 参加计算的像素点权重总和wsum,当前权重wgh + int wsum = 0, wgh; + + // 图像像素索引 + int mIdx; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 5; i++){ + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 5; j++) { + // 获取图像像素索引 + mIdx=(y + i - 2) * w + (x + j - 2); + + // 只处理mask图像像素值等于mask值的像素点 + if (maskImg[mIdx] == mask) { + + // 计算当前像素点的权重 + wgh = gf[i] * gf[j]; + + // 当前像素点的权重累加到总权重中 + wsum += wgh ; + + // 计算像素值加权累加和 + c += wgh * origiImageGPU.imgMeta.imgData[mIdx]; + } + } + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / wsum + 0.5f; +} + + // 平滑窗口大小为9*9的,带mask高斯平滑函数实现 +static __global__ void gauss9SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight, + ImageCuda maskImageGPU, + unsigned char mask) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 如果mask图像像素值不等于mask则不处理 + if (maskImageGPU.imgMeta.imgData[y * w + x] != mask) + return ; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 获取mask图像数据 + unsigned char * maskImg = maskImageGPU.imgMeta.imgData; + + // 高斯平滑系数数组 + int gf[9] = {1, 8, 28, 56, 70, 56, 28, 8, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 参加计算的像素点权重总和wsum,当前权重wgh + int wsum = 0, wgh; + + // 图像像素索引 + int mIdx; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 9; i++){ + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 9; j++) { + // 获取图像像素索引 + mIdx=(y + i - 4) * w + (x + j - 4); + + // 只处理mask图像像素值等于mask值的像素点 + if (maskImg[mIdx] == mask) { + + // 计算当前像素点的权重 + wgh = gf[i] * gf[j]; + + // 当前像素点的权重累加到总权重中 + wsum += wgh ; + + // 计算像素值加权累加和 + c += wgh * origiImageGPU.imgMeta.imgData[mIdx]; + } + } + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / wsum + 0.5f; +} + + // 平滑窗口大小为11*11的,带mask高斯平滑函数实现 +static __global__ void gauss11SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight, + ImageCuda maskImageGPU, + unsigned char mask) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 如果mask图像像素值不等于mask则不处理 + if (maskImageGPU.imgMeta.imgData[y * w + x] != mask) + return ; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 获取mask图像数据 + unsigned char * maskImg = maskImageGPU.imgMeta.imgData; + + // 高斯平滑系数数组 + int gf[11] = {1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 参加计算的像素点权重总和wsum,当前权重wgh + int wsum = 0, wgh; + + // 图像像素索引 + int mIdx; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 11; i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 11; j++) { + // 获取图像像素索引 + mIdx=(y + i - 5) * w + (x + j - 5); + + // 只处理mask图像像素值等于mask值的像素点 + if (maskImg[mIdx] == mask) { + + // 计算当前像素点的权重 + wgh = gf[i] * gf[j]; + + // 当前像素点的权重累加到总权重中 + wsum += wgh ; + + // 计算像素值加权累加和 + c += wgh * origiImageGPU.imgMeta.imgData[mIdx]; + } + } + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / wsum + 0.5f; +} + + // 平滑窗口大小为3*3的,带mask高斯平滑函数实现 +static __global__ void gauss3SmoothImage(ImageCuda origiImageGPU, + ImageCuda gaussSmImageGPU, + int smLocatX, int smLocatY, + int smWidth, int smHeight, + ImageCuda maskImageGPU, + unsigned char mask) +{ + // 获取pixel在原图像中的位置 + int w = origiImageGPU.pitchBytes; + int x = blockIdx.x * blockDim.x + threadIdx.x + smLocatX; + int y = blockIdx.y * blockDim.y + threadIdx.y + smLocatY; + + // 如果mask图像像素值不等于mask则不处理 + if (maskImageGPU.imgMeta.imgData[y * w + x] != mask) + return ; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if(x >= smLocatX + smWidth || y >= smLocatY + smHeight) + return ; + + // 获取mask图像数据 + unsigned char * maskImg = maskImageGPU.imgMeta.imgData; + + // 高斯平滑系数数组 + int gf[3] = {1, 2, 1}; + + // 高斯卷积累加和 + int c = 0; + + // 参加计算的像素点权重总和wsum,当前权重wgh + int wsum = 0, wgh; + + // 图像像素索引 + int mIdx; + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int i = 0; i < 3; i++) { + + // 编译预处理,在编译阶段将循环展开,节约循环跳转时间 + #pragma unroll + + for(int j = 0; j < 3; j++) { + // 获取图像像素索引 + mIdx=(y + i - 1) * w + (x + j - 1); + + // 只处理mask图像像素值等于mask值的像素点 + if (maskImg[mIdx] == mask) { + + // 计算当前像素点的权重 + wgh = gf[i] * gf[j]; + + // 当前像素点的权重累加到总权重中 + wsum += wgh ; + + // 计算像素值加权累加和 + c += wgh * origiImageGPU.imgMeta.imgData[mIdx]; + } + } + } + + // 计算平滑后像素值,结果四舍五入 + gaussSmImageGPU.imgMeta.imgData[y * w + x] = 1.0 * c / wsum + 0.5f; + +} + +// 普通高斯平滑函数 +__host__ int GaussSmoothImage::gaussSmoothImage(Image* origiImage, int smWidth, + int smHeight, int smLocatX, + int smLocatY, int smWindowSize, + Image* gaussSmImage) +{ + // 局部变量,错误码。 + int errcode; + + // 输入输出图像指针不能为空 + if (origiImage == NULL || gaussSmImage == NULL) + return NULL_POINTER; + + // 获取图像尺寸信息 + int imgWidth = origiImage->width; + int imgHeight = origiImage->height; + + // 图像小于平滑范围 + if (imgWidth < smWidth || imgHeight < smHeight) + return -11; + + // 平滑范围小于最大平滑窗口大小 + if (smWidth < 11 || smHeight < 11) + return -12; + + // 输入的平滑窗口大小不在处理范围之内 + if (smWindowSize < 3 || smWindowSize > 11) + return -13; + + // 平滑计算所涉及data位置或范围不能超出原始图像的物理范围, + // 故应根据smWindowSize作适当调整。 + int marginOff = (smWindowSize + 1) >> 1; + int leftMargin = smLocatX - marginOff; + int rightMargin = imgWidth - smLocatX - smWidth - marginOff; + + int topMargin = smLocatY - marginOff; + int bottomMargin = imgHeight - smLocatY - smHeight - marginOff; + + // 平滑时将发生左侧出界 + if (leftMargin < 0) { + smLocatX -= leftMargin; + smWidth += leftMargin; + } + + // 平滑时将发生右侧出界 + if (rightMargin < 0) { + smWidth += rightMargin; + } + + // 平滑宽度小于1 + if (smWidth < 1) + return -14; + + // 平滑时将发生上方出界 + if (topMargin < 0) { + smLocatY -= topMargin; + smHeight += topMargin; + } + + // 平滑时将发生下方出界 + if (bottomMargin < 0) { + smHeight += bottomMargin; + } + + // 平滑高度小于1 + if (smHeight < 1) + return -15; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(origiImage); + if (errcode != NO_ERROR) { + return errcode; + } + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(gaussSmImage); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像。 + ImageCuda origiImageGPU; + errcode = ImageBasicOp::roiSubImage(origiImage, &origiImageGPU); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输出图像。 + ImageCuda gaussSmImageGPU; + errcode = ImageBasicOp::roiSubImage(gaussSmImage, &gaussSmImageGPU); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridSize,blockSize; + + blockSize.x = DEF_BLOCK_X; + blockSize.y = DEF_BLOCK_Y; + gridSize.x = (smWidth + blockSize.x - 1) / blockSize.x; + gridSize.y = (smHeight + blockSize.y - 1) / blockSize.y; + + // 根据平滑窗口大小选择对应的核函数 + // 按照委托方要求,顺序为7、5、9、11、3 + switch (smWindowSize) { + + case 7: + // 启动平滑窗口大小为7的核函数 + gauss7SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + break; + + case 5: + // 启动平滑窗口大小为5的核函数 + gauss5SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + break; + + case 9: + + // 启动平滑窗口大小为9的核函数 + gauss9SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + break; + + case 11: + // 启动平滑窗口大小为11的核函数 + gauss11SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + break; + + default: + // 启动平滑窗口大小为3的核函数 + gauss3SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + break; + } + + return NO_ERROR; + +} + +// 带mask的高斯平滑函数 +__host__ int GaussSmoothImage::gaussSmoothImage(Image* origiImage, + int smWidth, int smHeight, + int smLocatX, int smLocatY, + int smWindowSize, + Image* gaussSmImage, + Image* maskImage, + unsigned char mask) +{ + // 局部变量,错误码。 + int errcode; + + // 获取图像尺寸信息 + int imgWidth = origiImage->width; + int imgHeight = origiImage->height; + + // 图像小于平滑范围 + if (imgWidth < smWidth || imgHeight < smHeight) + return -11; + + // 平滑范围小于最大平滑窗口 + if (smWidth < 11 || smHeight < 11) + return -12; + + // 输入的平滑窗口大小不在可处理范围之内 + if (smWindowSize < 3 || smWindowSize > 11) + return -13; + + // 平滑计算所涉及data位置或范围不能超出原始图像的物理范围, + // 故应根据smWindowSize作适当调整。 + int marginOff = (smWindowSize + 1) >> 1; + int leftMargin = smLocatX - marginOff; + int rightMargin = imgWidth - smLocatX - smWidth - marginOff; + + int topMargin = smLocatY - marginOff; + int bottomMargin = imgHeight - smLocatY - smHeight - marginOff; + + // 平滑时将发生左侧出界 + if (leftMargin < 0) { + smLocatX -= leftMargin; + smWidth += leftMargin; + } + + // 平滑时将发生右侧出界 + if (rightMargin < 0) { + smWidth += rightMargin; + } + + // 平滑宽度小于1 + if (smWidth < 1) + return -14; + + // 平滑时将发生上方出界 + if (topMargin < 0) { + smLocatY -= topMargin; + smHeight += topMargin; + } + + // 平滑时将发生下方出界 + if (bottomMargin < 0) { + smHeight += bottomMargin; + } + + // 平滑高度小于1 + if (smHeight < 1) + return -15; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(origiImage); + if (errcode != NO_ERROR) { + return errcode; + } + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(gaussSmImage); + if (errcode != NO_ERROR) { + return errcode; + } + + // 将mask图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(maskImage); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像。 + ImageCuda origiImageGPU; + errcode = ImageBasicOp::roiSubImage(origiImage, &origiImageGPU); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输出图像。 + ImageCuda gaussSmImageGPU; + errcode = ImageBasicOp::roiSubImage(gaussSmImage, &gaussSmImageGPU); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取mask图像。 + ImageCuda maskImageGPU; + errcode = ImageBasicOp::roiSubImage(maskImage, &maskImageGPU); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridSize,blockSize; + + blockSize.x = DEF_BLOCK_X; + blockSize.y = DEF_BLOCK_Y; + gridSize.x = (smWidth + blockSize.x - 1) / blockSize.x; + gridSize.y = (smHeight + blockSize.y - 1) / blockSize.y; + + // 根据平滑窗口大小选择对应的核函数 + // 按照委托方要求,顺序为7、5、9、11、3 + switch (smWindowSize) { + + case 7: + // 启动平滑窗口大小为7的核函数 + gauss7SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight, + maskImageGPU, mask); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + break; + + case 5: + // 启动平滑窗口大小为5的核函数 + gauss5SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight, + maskImageGPU, mask); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + break; + + case 9: + + // 每个窗口纵向线程块数目 + + gauss9SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight, + maskImageGPU, mask); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + break; + + case 11: + // 启动平滑窗口大小为11的核函数 + gauss11SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight, + maskImageGPU, mask); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + break; + + default: + // 启动平滑窗口大小为3的核函数 + gauss3SmoothImage<<>>(origiImageGPU, + gaussSmImageGPU, + smLocatX, smLocatY, + smWidth, smHeight, + maskImageGPU, mask); + // 核函数出错 + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + break; + } + + return NO_ERROR; + +} + + + diff --git a/okano_3_0/GaussianSmoothImage.h b/okano_3_0/GaussianSmoothImage.h new file mode 100644 index 0000000..3f5c864 --- /dev/null +++ b/okano_3_0/GaussianSmoothImage.h @@ -0,0 +1,68 @@ +// GaussianSmoothImage.h +// 创建人:高新凯 +// +// 高斯平滑 +// 功能说明:定义了高斯平滑。 +// +// 修订历史: +// 2013年10月10日(高新凯) +// 初始版本。 +// +// 2013年12月25日(高新凯) +// 优化并行策略 +// +// 2014年10月11日(高新凯) +// 修改了文件编码格式 +// + +#ifndef __GAUSSIANSMOOTNIMAGE_H__ +#define __GAUSSIANSMOOTNIMAGE_H__ + +#include "ErrorCode.h" +#include "Image.h" + +class GaussSmoothImage{ + +public: + + // 构造函数:GaussSmoothImage + // 无参数版本的构造函数,因为该类没有成员变量,所以该构造函数为空。 + // 没有需要设置默认值的成员变量。 + __host__ __device__ + GaussSmoothImage(){ + } + + // Host 成员方法:gaussSmoothImage(普通高斯平滑函数) + // 根据输入图像和平滑区域、平滑窗口尺寸等信息对图像进行高斯平滑 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + gaussSmoothImage( + Image* origiImage, // 输入图像 + int smWidth, // 需要平滑的特定区域的宽度 + int smHeight, // 需要平滑的特定区域的高度 + int smLocatX, // 需要平滑的特定区域的左上角pixel在 + int smLocatY, // 原始图像中的横、纵坐标. + int smWindowSize, // 平滑窗口尺寸 + Image* gaussSmImage // 平滑结果 + ); + + // Host 成员方法:gaussSmoothImage(带mask的高斯平滑函数) + // 此函数与普通高斯平滑图像同名,使用时应以参数进行区分 + // 根据输入图像和平滑区域、平滑窗口尺寸、mask图像等信息进行高斯平滑 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + gaussSmoothImage( + Image* origiImage, // 输入图像 + int smWidth, // 需要平滑的特定区域的宽度 + int smHeight, // 需要平滑的特定区域的高度 + int smLocatX, // 需要平滑的特定区域的左上角pixel在 + int smLocatY, // 原始图像中的横、纵坐标. + int smWindowSize, // 平滑窗口尺寸 + Image* gaussSmImage, // 平滑结果 + Image* maskImage, // mask图像 + unsigned char mask // mask值 + ); + +}; + +#endif diff --git a/okano_3_0/GaussianSmoothxy.cu b/okano_3_0/GaussianSmoothxy.cu new file mode 100644 index 0000000..3643629 --- /dev/null +++ b/okano_3_0/GaussianSmoothxy.cu @@ -0,0 +1,537 @@ +// GaussianSmoothxy.cu +// 实现对curve的高斯平滑 + +#include "GaussianSmoothxy.h" +#include "ErrorCode.h" +#include "Curve.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +//#define DEF_BLOCK_Y 8 + +// 宏,定义了五个高斯平滑尺度对应的系数 +// Gaussian3,5,7,9,11时分别为: 4,16,64,256,1024. +#define GAUSS_THREE 4 +#define GAUSS_FIVE 16 +#define GAUSS_SEVEN 64 +#define GAUSS_NINE 256 +#define GAUSS_ELEVEN 1024 + +// 平滑窗口大小为7的核函数 +static __global__ void +gauss7SmoothXY( + int n, // 曲线上点的数量 + int* ringCordiXY, // 辅助参数 + float* gSmCordiXY // 平滑结果 +); + +// 平滑窗口大小为5的核函数 +static __global__ void +gauss5SmoothXY( + int n, // 曲线上点的数量 + int* ringCordiXY, // 辅助参数 + float* gSmCordiXY // 平滑结果 +); + +// 平滑窗口大小为9的核函数 +static __global__ void +gauss9SmoothXY( + int n, // 曲线上点的数量 + int* ringCordiXY, // 辅助参数 + float* gSmCordiXY // 平滑结果 +); + +// 平滑窗口大小为11的核函数, +static __global__ void +gauss11SmoothXY( + int n, // 曲线上点的数量 + int* ringCordiXY, // 辅助参数 + float* gSmCordiXY // 平滑结果 +); + +// 平滑窗口大小为3的核函数 +static __global__ void +gauss3SmoothXY( + int n, // 曲线上点的数量 + int* ringCordiXY, // 辅助参数 + float* gSmCordiXY // 平滑结果 +); + +// 平滑窗口大小为7的核函数的具体实现 +static __global__ void gauss7SmoothXY(int n, int* ringCordiXY, + float* gSmCordiXY ) +{ + // 计算当前线程下标 + int t = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查线程是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (t >= n) + return; + + // 计算当前线程对应的数组下标 + int i = 2 * (n + t); + + + // 计算横纵坐标对应的数值 + int x = ringCordiXY[i - 6] + 6 * ringCordiXY[i - 4] + + 15 * ringCordiXY[i - 2] + 20 * ringCordiXY[i] + + 15 * ringCordiXY[i + 2] + 6 * ringCordiXY[i + 4] + + ringCordiXY[i + 6]; + int y = ringCordiXY[i - 5] + 6 * ringCordiXY[i - 3] + + 15 * ringCordiXY[i - 1] + 20 * ringCordiXY[i + 1] + + 15 * ringCordiXY[i + 3] + 6 * ringCordiXY[i + 5] + + ringCordiXY[i + 7]; + + // 计算平滑后的横纵坐标,写入gSmCordiXY中 + gSmCordiXY[2 * t] = 1.0 * x / GAUSS_SEVEN; + gSmCordiXY[2 * t + 1] = 1.0 * y / GAUSS_SEVEN; + +} + +// 平滑窗口大小为5的核函数的具体实现 +static __global__ void gauss5SmoothXY(int n, int* ringCordiXY, + float* gSmCordiXY) +{ + // 计算当前线程下标 + int t = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查线程是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (t >= n) + return; + + // 计算当前线程对应的数组下标 + int i = 2 * (n + t); + + + // 计算横纵坐标对应的数值 + int x = ringCordiXY[i - 4] + 4 * ringCordiXY[i - 2] + + 6 * ringCordiXY[i] + 4 * ringCordiXY[i + 2] + ringCordiXY[i + 4] ; + int y = ringCordiXY[i - 3] + 4 * ringCordiXY[i - 1] + + 6 * ringCordiXY[i + 1] + 4 * ringCordiXY[i + 3] + + ringCordiXY[i + 5]; + + // 计算平滑后的横纵坐标,写入gSmCordiXY中 + gSmCordiXY[2 * t] = 1.0 * x / GAUSS_FIVE; + gSmCordiXY[2 * t + 1] = 1.0 * y / GAUSS_FIVE; + +} + +// 平滑窗口大小为9的核函数的具体实现 +static __global__ void gauss9SmoothXY(int n, int* ringCordiXY, + float* gSmCordiXY) +{ + // 计算当前线程下标 + int t = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查线程是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (t >= n) + return; + + // 计算当前线程对应的数组下标 + int i = 2 * (n + t); + + + // 计算横纵坐标对应的数值 + int x = ringCordiXY[i - 8] + 8 * ringCordiXY[i - 6] + + 28 * ringCordiXY[i - 4] + 56 * ringCordiXY[i - 2] + + 70 * ringCordiXY[i] + 56 * ringCordiXY[i + 2] + + 28 * ringCordiXY[i + 4] + 8 * ringCordiXY[i + 6] + + ringCordiXY[i + 8]; + int y = ringCordiXY[i - 7] + 8 * ringCordiXY[i - 5] + + 28 * ringCordiXY[i - 3] + 56 * ringCordiXY[i - 1] + + 70 * ringCordiXY[i + 1] + 56 * ringCordiXY[i + 1] + + 28 * ringCordiXY[i + 3] + 8 * ringCordiXY[i + 5] + + ringCordiXY[i + 7]; + + // 计算平滑后的横纵坐标,写入gSmCordiXY中 + gSmCordiXY[2 * t] = 1.0 * x / GAUSS_NINE; + gSmCordiXY[2 * t + 1] = 1.0 * y / GAUSS_NINE; + + +} + +//平滑窗口大小为11的核函数的具体实现 +static __global__ void gauss11SmoothXY(int n, int* ringCordiXY, + float* gSmCordiXY) +{ + // 计算当前线程下标 + int t = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查线程是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (t >= n) + return; + + // 计算当前线程对应的数组下标 + int i = 2 * (n + t); + + // 计算横纵坐标对应的数值 + int x = ringCordiXY[i - 10] + 10 * ringCordiXY[i - 8] + + 45 * ringCordiXY[i - 6] + 120 * ringCordiXY[i - 4] + + 210 * ringCordiXY[i - 2] + 252 * ringCordiXY[i] + + 210 * ringCordiXY[i + 2] + 120 * ringCordiXY[i + 4] + + 45 * ringCordiXY[i + 6] + 10 * ringCordiXY[i + 8] + + ringCordiXY[i + 10]; + int y = ringCordiXY[i - 9] + 10 * ringCordiXY[i - 7] + + 45 * ringCordiXY[i - 5] + 120 * ringCordiXY[i - 3] + + 210 * ringCordiXY[i - 1] + 252 * ringCordiXY[i + 1] + + 210 * ringCordiXY[i + 3] + 120 * ringCordiXY[i + 5] + + 45 * ringCordiXY[i + 7] + 10 * ringCordiXY[i + 9] + + ringCordiXY[i + 11]; + + // 计算平滑后的横纵坐标,写入gSmCordiXY中 + gSmCordiXY[2 * t] = 1.0 * x / GAUSS_ELEVEN; + gSmCordiXY[2 * t + 1] = 1.0 * y / GAUSS_ELEVEN; +} + +// 平滑窗口大小为3的核函数的具体实现 +static __global__ void gauss3SmoothXY(int n, int* ringCordiXY, + float* gSmCordiXY) +{ + // 计算当前线程下标 + int t = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查线程是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (t >= n) + return; + + // 计算当前线程对应的数组下标 + int i = 2 * (n + t); + + // 计算横纵坐标对应的数值 + int x = ringCordiXY[i - 2] + 2 * ringCordiXY[i] + ringCordiXY[i + 2]; + int y = ringCordiXY[i - 1] + 2 * ringCordiXY[i + 1] + ringCordiXY[i + 3]; + + // 计算平滑后的横纵坐标,写入gSmCordiXY中 + gSmCordiXY[2 * t] = 1.0 * x / GAUSS_THREE; + gSmCordiXY[2 * t + 1] = 1.0 * y / GAUSS_THREE; + +} + +// curve高斯平滑的具体实现 +__host__ int GaussSmoothXY::gaussSmoothCurveXY(Curve *curve, int smWindowSize) +{ + // 局部变量,错误码。 + int state; + + // 获取曲线长度 + int cLength = curve->curveLength; + + // 曲线长度不足3 + // 按照委托方的需求返回错误值,为了避免和现有的errorcode冲突 + // 以下返回值均为自定义 + if (cLength < 3 ) + return -11; + + // 平滑窗口大小不在有效范围之内则报错 + // smWindowSize应仅限于3、5、7、9、11五种,防止因误输入其他数据 + // 导致用户在不知情的情况下采用了默认值3,从而出现和期望不一致的平滑结果 + if (smWindowSize != 3 && smWindowSize != 5 && smWindowSize != 7 && + smWindowSize != 9 && smWindowSize != 11) + return -12; + + // 如果曲线长度过短导致不大于平滑区间长度,则对平滑区间做缩小处理 + // 以取得正确的平滑结果,每次缩小长度为2 + if (cLength <= smWindowSize ) { + + // 如果缩小smWindowSize后不在处理范围则返回 + if ((smWindowSize -= 2) < 3) + return -13; + + // 缩小后曲线长度大于平滑区间长度,则处理结束 + if (cLength > smWindowSize) + curve->smWindowSize = smWindowSize; + + // 仍然不符合要求,继续缩小 + else { + // 如果缩小smWindowSize后不在处理范围则返回 + if ((smWindowSize -= 2) < 3) + return -14; + + // 缩小后曲线长度大于平滑区间长度,则处理结束 + if (cLength > smWindowSize) + curve->smWindowSize = smWindowSize; + + else { + // 如果缩小smWindowSize后不在处理范围则返回 + if ((smWindowSize -= 2) < 3) + return -15; + + // 缩小后曲线长度大于平滑区间长度,则处理结束 + if (cLength > smWindowSize) + curve->smWindowSize = smWindowSize; + + else { + // 如果缩小smWindowSize后不在处理范围则返回 + if ((smWindowSize -= 2) < 3) + return -16; + + // 曲线长度大于平滑区间长度,则处理结束 + if (cLength > smWindowSize) + curve->smWindowSize = smWindowSize; + + // 处理窗口取最小值 + else { + curve->smWindowSize = 3; + } + } + } + } + } + else { + // 设置curve的成员变量smWindowSize的值 + curve->smWindowSize = smWindowSize; + } + + // 如果平滑坐标数组为空,则开辟空间 + if (curve->smCurveCordiXY == NULL){ + // 为gSmCordiXY开辟host内存。 + curve->smCurveCordiXY = new float[2 * cLength]; + } + + // 启动平滑函数 + state = gaussSmoothXY(cLength, curve->crvData, curve->closed, + curve->smCurveCordiXY, smWindowSize); + + // 平滑出错,清除平滑相关数据 + if(state != NO_ERROR) + { + curve->smWindowSize = 0; + delete curve->smCurveCordiXY; + curve->smCurveCordiXY = NULL; + } + + return state; + +} + +// curve高斯平滑核心函数 +__host__ int GaussSmoothXY::gaussSmoothXY(int n, int* origiCordiXY, bool closed, + float* gSmCordiXY, int smWindowSize) +{ + // 局部变量,错误码。 + cudaError_t cudaerrcode; + + // 高斯平滑用到的辅助数据 + static int ringCordiLength = 0; + static int reverseCordiLength = 0; + static int* reverseCordiXY = NULL; + int n3 = n * 3; + + // 计算设置ringCordiLength + if (ringCordiLength <= n3) { + if (ringCordiXY != NULL) { + cudaFree(ringCordiXY); + } + + n3 += 3; + + // 在GPGPU GLOBAL memory中取得一个长度为n3 * 2的memory -> ringCordiXY; + cudaerrcode = cudaMalloc((void **)&ringCordiXY, sizeof (int) * n3 * 2); + + // 开辟失败,释放内存空间。 + if (cudaerrcode != cudaSuccess) { + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + ringCordiLength = n3; + } + + // 处理闭合曲线 + if (closed) { + // 由HOST memory 向GPGPU memory copy: + // copy origiCordiXY to ringCordiXY; copy size = 2 * n + cudaerrcode = cudaMemcpy(ringCordiXY, origiCordiXY, + 2 * n * sizeof(int), cudaMemcpyHostToDevice); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + + // copy origiCordiXY to ringCordiXY + 2 * n; copy size = 2 * n + cudaerrcode = cudaMemcpy(ringCordiXY + 2 * n, origiCordiXY , + 2 * n * sizeof(int) , cudaMemcpyHostToDevice); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + + // copy origiCordiXY to ringCordiXY + 2 * n; copy size = 2 * n + cudaerrcode = cudaMemcpy(ringCordiXY + 4 * n, origiCordiXY , + 2 * n * sizeof(int), cudaMemcpyHostToDevice); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + + } + // 处理非闭合曲线 + else { + + // 为reverseCordiXY开辟空间 + if (reverseCordiLength <= n ) { + delete reverseCordiXY; + reverseCordiXY = new int[2 * n + 6]; + reverseCordiLength = n + 3; + } + + // 将x、y坐标分别反转后存入reverseCordiXY中 + #pragma unroll + for (int i = 0; i < 2 * n; i += 2) { + reverseCordiXY[i + 1] = origiCordiXY[2 * n - i - 1]; + } + #pragma unroll + for (int i = 1; i < 2 * n; i += 2) { + reverseCordiXY[i - 1] = origiCordiXY[2 * n - i - 1]; + } + + // 由HOST memory 向GPGPU memory copy: + // copy reverseCordiXY to ringCordiXY; copy size = 2 * n + cudaerrcode = cudaMemcpy(ringCordiXY, reverseCordiXY, + 2 * n * sizeof(int), cudaMemcpyHostToDevice); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + delete reverseCordiXY; + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + + //copy origiCordiXY to ringCordiXY + 2 * n; + // copy size = 2 * n 这个必须是origiCordiXY + cudaerrcode = cudaMemcpy(ringCordiXY + 2 * n, origiCordiXY, + 2 * n * sizeof(int), cudaMemcpyHostToDevice); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + delete reverseCordiXY; + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + + // copy reverseCordiXY to ringCordiXY + 4 * n; copy size = 2 * n + cudaerrcode = cudaMemcpy(ringCordiXY + 4 * n, reverseCordiXY, + 2 * n * sizeof(int), cudaMemcpyHostToDevice); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + delete reverseCordiXY; + cudaFree(ringCordiXY); + return CUDA_ERROR; + } + + } + + // 为成员变量gSmCordiXY在device端开辟空间 + cudaerrcode = cudaMalloc((void **)&this->gSmCordiXY, sizeof(float) * 2 * n); + + // 开辟失败,释放内存空间。 + if (cudaerrcode != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + // 根据平滑窗口大小选择合适的平滑函数 + // 按照委托方要求的顺序,核函数按照7,5,9,11,3排列 + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridSize,blockSize; + + blockSize.x = DEF_BLOCK_X; + blockSize.y = 1; + gridSize.x = (n + blockSize.x - 1) / blockSize.x; + gridSize.y = 1; + + switch (smWindowSize) { + case 7: + // 启动平滑窗口大小为7的核函数 + gauss7SmoothXY<<>>(n, ringCordiXY, + this->gSmCordiXY ); + //核函数出错 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + break; + + case 5: + // 启动平滑窗口大小为5的核函数 + gauss5SmoothXY<<>>(n, ringCordiXY, + this->gSmCordiXY ); + //核函数出错 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + break; + + case 9: + // 启动平滑窗口大小为9的核函数 + gauss9SmoothXY<<>>(n, ringCordiXY, + this->gSmCordiXY); + //核函数出错 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + break; + + case 11: + + // 启动平滑窗口大小为11的核函数 + gauss11SmoothXY<<>>(n, ringCordiXY, + this->gSmCordiXY); + //核函数出错 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + break; + + default: + // 启动平滑窗口大小为3的核函数 + gauss3SmoothXY<<>>(n, ringCordiXY, + this->gSmCordiXY); + //核函数出错 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + break; + } + + // 将计算结果拷贝回gSmCordiXY中 + cudaerrcode = cudaMemcpy(gSmCordiXY, this->gSmCordiXY, + sizeof (float) * 2 * n, cudaMemcpyDeviceToHost); + // 拷贝函数出错 + if (cudaerrcode != cudaSuccess) { + cudaFree(ringCordiXY); + cudaFree(this->gSmCordiXY); + delete reverseCordiXY; + return CUDA_ERROR; + } + + return NO_ERROR; +} + diff --git a/okano_3_0/GaussianSmoothxy.h b/okano_3_0/GaussianSmoothxy.h new file mode 100644 index 0000000..5f89f7e --- /dev/null +++ b/okano_3_0/GaussianSmoothxy.h @@ -0,0 +1,80 @@ +// GaussianSmoothxy.h +// 创建人:高新凯 +// +// 曲线高斯平滑 +// 功能说明:定义了高斯平滑类GaussSmoothXY。 +// 用于曲线的高斯平滑 +// +// 修订历史: +// 2013年11月14日(高新凯) +// 初始版本。 +// 2013年12月25日(高新凯) +// 优化了并行策略 +// +// 2014年10月11日(高新凯) +// 修改了文件编码格式 +// +#ifndef __GAUSSIANSMOOTNXY_H__ +#define __GAUSSIANSMOOTNXY_H__ + +#include "ErrorCode.h" +#include "Curve.h" + +class GaussSmoothXY{ + +protected: + + // 成员变量,存储高斯平滑辅助数据。类型为GPGPU GLOBAL memory + int* ringCordiXY; + float* gSmCordiXY; + +public: + + // 默认构造函数GaussSmoothXY,将成员变量都初始化为NULL + __host__ __device__ + GaussSmoothXY(){ + ringCordiXY = NULL; + gSmCordiXY = NULL; + } + + // 析构函数,防止内存泄漏 + __host__ __device__ + ~GaussSmoothXY(){ + + // 释放ringCordiXY + if (ringCordiXY != NULL) { + cudaFree(ringCordiXY); + } + + // 释放gSmCordiXY + if (gSmCordiXY != NULL) { + cudaFree(gSmCordiXY); + } + + } + + // Host 成员方法:gaussSmoothCurveXY(curve高斯平滑函数) + // 高斯平滑HOST侧函数,输入curve,根据平滑窗口大小进行平滑 + // 平滑窗口大小smWindowSize应在3、5、7、9、11中选取 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + gaussSmoothCurveXY( + Curve *curve, // 待平滑曲线 + int smWindowSize // 平滑窗口大小 + ) ; + + // Host 成员方法:gaussSmoothXY(高斯平滑计算函数) + // 对曲线进行高斯平滑 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + gaussSmoothXY( + int n, // 曲线上点的数量 + int* origiCordiXY, // 原始的坐标 + bool closed, // 曲线是否是闭合的 + float* gSmCordiXY, // 平滑后的坐标 + int smWindowSize // 平滑窗口大小 + ); + +}; + +#endif diff --git a/okano_3_0/GeoPrimaryProperties.cu b/okano_3_0/GeoPrimaryProperties.cu new file mode 100644 index 0000000..a8292e1 --- /dev/null +++ b/okano_3_0/GeoPrimaryProperties.cu @@ -0,0 +1,6 @@ +// GeoPrimaryProperties.cu +// 曲线的最小有向外接矩形的定义以及基本操作。 + +#include "GeoPrimaryProperties.h" + +// 由于需求不明,此结构体并没有实现对该结构体的基本操作。 diff --git a/okano_3_0/GeoPrimaryProperties.h b/okano_3_0/GeoPrimaryProperties.h new file mode 100644 index 0000000..ce44049 --- /dev/null +++ b/okano_3_0/GeoPrimaryProperties.h @@ -0,0 +1,48 @@ +// GeoPrimaryProperties.h +// 创建人:杨伟光 +// +// 曲线的最小有向外接矩形定义(GeoPrimaryProperties) +// 功能说明:定义了曲线的最小有向外接矩形的数据结构。 +// +// 修订历史: +// 2013年10月06日(杨伟光) +// 初始版本。 +// 2014年09月28日(杨伟光) +// 修改了 .cu 文件的规范。 + +#ifndef __GEOPRIMARYPROPERTIES_H__ +#define __GEOPRIMARYPROPERTIES_H__ + +#include "ErrorCode.h" +#include "Moments.h" + + +// 结构体:GeoPrimaryProperties(曲线的最小有向外接矩形) +// 该结构体定义了曲线的最小有向外接矩形的数据结构,其中包含了曲线的最小有向外接 +// 矩形的数据和和曲线的最小有向外接矩形相关的逻辑参数, +typedef struct GeoPrimaryProperties_st { + int mdrCenterX; // MDR 的几何中心的 x 坐标。 + int mdrCenterY; // MDR 的几何中心的 y 坐标。 + + int contourArea; // 轮廓所围面积,轮廓的曲线时为 0。 + int mdrLS; // MDR 的长边长。 + int mdrSS; // MDR 的短边长。 + float mdrAngle; // MDR 的走向角。 + int *mdrVertexesX; // MDR 的 4 个顶点坐标。 + int *mdrVertexesY; // MDR 的 4 个顶点坐标。 + + int vertexesNum; // convex hull 坐标对的个数。 + int *chCordiX; // convex hull 顶点的 x 坐标集。 + int *chCordiY; // convex hull 顶点的 y 坐标集。 + + int chDomainPixelNum; // convex hull 所围领域的 pixel 个数。 + int *chDomainX; // convex hull 所围领域的 x 坐标集。 + int *chDomainY; // convex hull 所围领域的 y 坐标集。 + int chArea; // convex hull 所围面积。 + + Moments moments; // curve /contour moment. +} GeoPrimaryProperties; + +#endif + + \ No newline at end of file diff --git a/okano_3_0/GeometryProperties.cu b/okano_3_0/GeometryProperties.cu new file mode 100644 index 0000000..e69de29 diff --git a/okano_3_0/GeometryProperties.h b/okano_3_0/GeometryProperties.h new file mode 100644 index 0000000..3744891 --- /dev/null +++ b/okano_3_0/GeometryProperties.h @@ -0,0 +1,434 @@ +// GeometryProperties.h +// 创建人:刘宇 +// +// 几何形状方向等的测度(GeometryProperties) +// 功能说明:计算几何形状的一些特征属性。包括 5 类属性,分别是 +// (1)边缘平滑度的计算;(2)直线的线性度计算;(3)圆度的计算; +// (4)凸面度计算;(5)分布中心和方向的计算。 +// +// 修订历史: +// 2012年10月24日(刘宇) +// 初始版本 +// 2012年10月25日(于玉龙、刘宇) +// 修改代码规范 +// 2012年11月13日(刘宇) +// 在核函数执行后添加 cudaGetLastError 判断语句 +// 2012年11月23日(刘宇) +// 添加输入输出参数的空指针判断 +// 2012年12月25日(刘宇) +// 完成直线的线性度计算和分布方向中心的计算 +// 2012年12月30日(刘宇) +// 完成边缘平滑度计算 +// 2013年03月15日(刘宇) +// 完成凸面度计算 +// 2013年03月21日(刘宇) +// 完成圆度计算 + +#ifndef __GEOMETRYPROPERTIES_H__ +#define __GEOMETRYPROPERTIES_H__ + +#include "Image.h" +#include "Template.h" +#include "CoordiSet.h" +#include "ErrorCode.h" +#include "Rectangle.h" +#include "HoughLine.h" +#include "Histogram.h" +#include "RobustEdgeDetection.h" +#include "Thinning.h" +#include "Moments.h" +#include "Morphology.h" +#include "ConvexHull.h" +#include "SmallestDirRect.h" + +// 结构体:RegionRound(区域的圆度测度) +// 该结构体定了区域圆度的数据结构,其中包含了 6 种圆度测量,分别从不同侧面反映 +// 了区域的圆度属性。 +typedef struct RegionRound_st { + int pointcount; // 区域或曲线上的点数 + float anisometry; // 各向异性的 + float bulkiness; // 蓬松度 + float structurefactor; // 结构因子 + float circularity; // 圆环度 + float compactness; // 紧密度 +} RegionRound; + +// 类:GeometryProperties +// 继承自:无 +// 计算几何形状的一些特征属性。包括 5 类属性,分别是 +// (1)边缘平滑度的计算;(2)直线的线性度计算; +// (3)圆度的计算;(4)凸面度计算;(5)分布中心和方向的计算。 +class GeometryProperties { + +protected: + + // 成员变量:highPixel(高像素) + // 二值图像中轮廓区域的像素值。 + unsigned char highPixel; + + // 成员变量:lowPixel(低像素) + // 二值图像中背景部分的像素值。 + unsigned char lowPixel; + + // 成员变量:smoothWidth(边缘平滑宽度) + // 计算边缘平滑度时,指定的平滑邻域的宽度,邻域的点的总个数等于 + // 2 * smooththred + 1。 + int smoothWidth; + + // 成员变量:smooththred(边缘平滑度的阈值大小) + // 当边缘平滑度大于阈值大小时,设置 errmap 图像。 + float smooththred; + + // 成员变量:linethred(直线线性度的阈值大小) + // 当直线线性度小于阈值大小时,设置 errmap 图像。 + float linethred; + + // 静态成员成员:tpl(模版) + // 长度为 12 的模板。因为以每个像素为中心,有 4 个方向的 3 邻域。 + static Template *tpl; + + // Host 静态方法:initTemplate(在算法操作前初始化模版) + // 对模版中的数据进行赋值,并将模版拷贝到 Device 端。 + static __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR + initTemplate(); + + // 成员变量:isconst(乘积项标识) + // 如果 isconst 等于 true,则在计算几何矩时乘积项恒等于 1;否则等于 + // 正常的灰度值。 + bool isconst; + + // 形态学类声明。 + Morphology mo; + + // 边缘检测类声明。 + RobustEdgeDetection edgedete; + + // 图像细化类声明。 + Thinning thin; + + // 直线检测类声明。 + HoughLine hline; + LineParam lineparam; + + // 凸壳类声明。 + ConvexHull cvhull; + + // 最小包围盒类声明。 + SmallestDirRect smallrect; + DirectedRect outrect; + + // 直方图类声明。 + Histogram hist; + + // 几何矩类声明。 + Moments mom; + +public: + + // 构造函数:GeometryProperties + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + GeometryProperties() + { + // 使用默认值为类的各个成员变量赋值。 + this->highPixel = 255; // 高像素值默认为 255。 + this->lowPixel = 0; // 低像素值默认为 0。 + this->smoothWidth = 5; // 边缘平滑宽度设为 5。 + this->smooththred = 1.0f; // 边缘平滑度的阈值设为 1。 + this->linethred = 1.0f; // 直线线性度的阈值设为 1。 + this->isconst = false; // 图像的灰度值标识默认为 false。 + } + + // 构造函数:GeometryProperties + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + GeometryProperties( + unsigned int highpixel, + unsigned int lowpixel, + int smoothwidth, + float smooththred, + float linethred, + bool isconst + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->highPixel = 255; // 高像素值默认为 255。 + this->lowPixel = 0; // 低像素值默认为 0。 + this->smoothWidth = 5; // 边缘平滑宽度设为 5。 + this->smooththred = 1.0f; // 边缘平滑度的阈值设为 1。 + this->linethred = 1.0f; // 直线线性度的阈值设为 1。 + this->isconst = false; // 图像的灰度值标识默认为 false。 + + setHighLowPixel(highpixel, lowpixel); + setSmoothWidth(smoothwidth); + setSmooththred(smooththred); + setLinethred(linethred); + setIsconst(isconst); + } + + // 成员函数:getHighPixel(获取高像素的值) + // 获取成员变量 highPixel 的值。 + __host__ __device__ unsigned char // 返回值:返回 hignPixel 的值。 + getHighPixel() const + { + // 返回 highPixel 成员变量的值。 + return highPixel; + } + + // 成员函数:setHighPixel(设置高像素) + // 设置成员变量 highPixel 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setHighPixel( + unsigned char highpixel // 设定新的高像素的值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highPixel == lowPixel) + return INVALID_DATA; + + // 将 highPixel 成员变量赋成新值 + this->highPixel = highpixel; + + return NO_ERROR; + } + + // 成员函数:getLowPixel(获取低像素的值) + // 获取成员变量 lowPixel 的值。 + __host__ __device__ unsigned char // 返回值:返回 lowPixel 的值。 + getLowPixel() const + { + // 返回 lowPixel 成员变量的值。 + return lowPixel; + } + + // 成员函数:setLowPixel(设置低像素的值) + // 设置成员变量 lowPixel 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setLowPixel( + unsigned char lowpixel // 设定新的低像素的值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highPixel == lowPixel) + return INVALID_DATA; + + // 将 lowPixel 成员变量赋成新值 + this->lowPixel = lowpixel; + + return NO_ERROR; + } + + // 成员函数:setHighLowPixel(设置高低像素) + // 设置成员变量 highPixel 和 lowPixel 的值。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + setHighLowPixel( + unsigned char highpixel, // 设定新的高像素的值 + unsigned char lowpixel // 设定新的低像素的值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highPixel == lowPixel) + return INVALID_DATA; + + // 将 highpixel 成员变量赋成新值 + this->highPixel = highpixel; + + // 将 lowPixel 成员变量赋成新值 + this->lowPixel = lowpixel; + + return NO_ERROR; + } + + // 成员函数:getSmoothWidth(获取边缘平滑宽度) + // 获取成员变量 smoothWidth 的值。 + __host__ __device__ int // 返回值:返回 smoothWidth 的值。 + getSmoothWidth() const + { + // 返回 smoothWidth 成员变量的值。 + return smoothWidth; + } + + // 成员函数:setSmoothWidth(设置边缘平滑宽度) + // 设置成员变量 smoothWidth 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setSmoothWidth( + int smoothwidth // 设定新的边缘平滑宽度 + ) { + // 将 smoothWidth 成员变量赋成新值 + this->smoothWidth = smoothwidth; + + return NO_ERROR; + } + + // 成员方法:getSmooththred(获取边缘平滑度的阈值大小) + // 获取成员变量 smooththred 的值。 + __host__ __device__ float // 返回值:成员变量 smooththred 的值 + getSmooththred() const + { + // 返回 smooththred 成员变量的值。 + return this->smooththred; + } + + // 成员方法:setSmooththred(设置边缘平滑度的阈值大小) + // 设置成员变量 smooththred 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setSmooththred( + float smooththred // 设定新的边缘平滑度的阈值大小。 + ) { + // 将 smooththred 成员变量赋成新值。 + this->smooththred = smooththred; + + return NO_ERROR; + } + + // 成员方法:getLinethred(获取直线线性度的阈值大小) + // 获取成员变量 linethred 的值。 + __host__ __device__ float // 返回值:成员变量 linethred 的值 + getLinethred() const + { + // 返回 linethred 成员变量的值。 + return this->linethred; + } + + // 成员方法:setLinethred(设置直线线性度的阈值大小) + // 设置成员变量 linethred 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setLinethred( + float linethred // 设定新的直线线性度的阈值大小。 + ) { + // 将 linethred 成员变量赋成新值。 + this->linethred = linethred; + + return NO_ERROR; + } + + // 成员方法:getIsconst(获取乘积项标识) + // 获取成员变量 isconst 的值。 + __host__ __device__ bool // 返回值:成员变量 isconst 的值 + getIsconst() const; + + // 成员方法:setIsconst(设置乘积项标识) + // 设置成员变量 isconst 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setIsconst( + bool isconst // 设定新的乘积项标识 + ); + + // Host 成员方法:edgeSmoothness(计算边缘的平滑度) + // 首先对输入坐标集做膨胀和细化处理,去除孤立点。然后根据尺度平滑坐标点, + // 计算平滑前后的平均误差度,如果大于指定阈值,则设置错误码图像。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + edgeSmoothness( + CoordiSet *cdset, // 输入坐标集 + float *smoothness, // 输出形状边缘的平滑度 + Image *errmap // 错误码图像 + ); + + // Host 成员方法:calcLinearity(计算直线的线性度) + // 对给定的坐标集,利用 Hough 变换计算其直线参数,然后计算各点到拟合直线的 + // 垂直距离,最终得到直线的线性度大小。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + calcLinearity( + CoordiSet *cdset, // 输入坐标集 + float *linearity, // 输出直线的线性度 + Image *errmap // 错误码图像 + ); + + // Host 成员方法:calcConvexity(计算形状的凸面度) + // 对给定的坐标集,计算其凸壳包围的面积,然后和形状自身面积进行对比,得到 + // 凸面度大小。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + calcConvexity( + CoordiSet *cdset, // 输入坐标集 + float *convexity // 输出直线的线性度 + ); + + // Host 成员方法:calcAnisometry(计算区域的各向异性) + // 计算区域的各向异性,等于最小有向外接矩形的短径除以长径。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + calcAnisometry( + DirectedRect outrect, // 最小有向外接矩形 + RegionRound *round // 圆度测度 + ); + + // Host 成员方法:calcBulkiness(计算区域的蓬松度) + // 计算区域的蓬松度,等于最小有向外接矩形的面积除以区域的实际面积。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + calcBulkiness( + DirectedRect outrect, // 最小有向外接矩形 + int area, // 区域的实际面积 + RegionRound *round // 圆度测度 + ); + + // Host 成员方法:calcStructurefactor(计算区域的结构因子) + // 计算区域的结构因子,等于最小有向外接矩形的长径平方除以区域的实际面积。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + calcStructurefactor( + DirectedRect outrect, // 最小有向外接矩形 + int area, // 区域的实际面积 + RegionRound *round // 圆度测度 + ); + + // Host 成员方法:calcCircularity(计算区域的圆环度) + // 计算区域的结圆环度。区域的实际面积除以区域边缘到几何中心的平均距离的 + // 平方。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + calcCircularity( + Image *img, // 输入图像 + DirectedRect outrect, // 最小有向外接矩形 + RegionRound *round // 圆度测度 + ); + + // Host 成员方法:contourLength(计算轮廓的长度) + // 计算轮廓的长度,此处的轮廓是由边缘细化得到的输出结果。轮廓长度的计算采用 + // 需求文档中的并行策略。对于图像上的每个非轮廓点,统计其 3 邻域模版中含有 + // 1 个, 2 个, 3 个轮廓点的情况。最后根据给出公式计算出轮廓的长度。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + contourLength( + Image *img, // 输入图像 + float *length // 输出形状的紧密度 + ); + + // Host 成员方法:calcCompactness(计算形状的紧密度) + // 首先计算轮廓的长度,然后根据公式除以形状区域的面积,得到形状紧密度大小。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + calcCompactness( + Image *img, // 输入图像 + int area, // 区域的实际面积 + RegionRound *round // 圆度测度 + ); + + // Host 成员方法:calcRoundness(计算区域的圆度) + // 通过不同属性计算形状区域的圆度属性。包括:区域或曲线上的点数,各向异性的 + // ,蓬松度,结构因子,圆环度,紧密度 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + calcRoundness( + Image *img, // 输入图像 + RegionRound *round // 圆度测度 + ); + + // Host 成员方法:calcCentOrient(计算形状的分布重心和方向) + // 根据中心矩计算形状的分布中心和方向,调用 Moments 中方法。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + calcCentOrient( + Image *img, // 输入图像 + double centroid[2], // 形状分布重心 + double *orientation // 形状分布方向 + ); +}; + +#endif + diff --git a/okano_3_0/GetContourSet.cu b/okano_3_0/GetContourSet.cu new file mode 100644 index 0000000..4b245c8 --- /dev/null +++ b/okano_3_0/GetContourSet.cu @@ -0,0 +1,1388 @@ +// GetContourSet.cu +// 实现有连接性的闭合轮廓的获得算法 + +#include "GetContourSet.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:GET_CONTOUR_SET_INI_IFI +// 定义了一个无穷大 +#define GET_CONTOUR_SET_INI_IFI 0x7fffffff + +// 宏:OUTER_ORIGIN_CONTOUR +// 定义了输入闭曲线外部区域的标记值。 +#define OUTER_ORIGIN_CONTOUR 0 + +// 宏:INNER_ORIGIN_CONTOUR +// 定义了输入闭曲线内部区域的标记值。 +#define INNER_ORIGIN_CONTOUR 10 + +// 宏:DILATE_CIRCLE +// 定义了膨胀后得到环上点的标记值。 +#define DILATE_CIRCLE 50 + +// 宏:OUTER_CONTOUR +// 定义了膨胀后得到环上外轮廓点的标记值。 +#define OUTER_CONTOUR 100 + +// 宏:OUTER_CIRCLE +// 定义了经过二分类后外部环状物上的点的标记值。 +#define OUTER_CIRCLE 150 + +// 宏:INNER_CONTOUR +// 定义了经过二分类后内外环状物交界处的点的标记值。 +#define INNER_CONTOUR 200 + +// Device 全局变量:_eightNeighDev(八邻域点索引下标) +// 存放当前点八邻域范围内对应点的索引下标。 +// 应用此数组可以便于进行八邻域像素点的遍历。 +static __device__ int _eightNeighDev[8][2] = { + { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, + { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } +}; + +// Device 子程序:_findRootDev (查找根节点标记值) +// 查找根节点标记值算法,根据给定的 label 数组和坐标值 +// 返回该坐标对应的根节点坐标值。该函数是为了便于其他 Kernel 函数调用。 +static __device__ int // 返回值:根节点标记值 +_findRootDev( + int label[], // 输入的标记数组 + int idx // 输入点的标记值 +); + +// Device 子程序:_unionDev (合并两个像素点使其位于同一区域) +// 合并两个不同像素点以使它们位于同一连通区域中 +static __device__ void // 该函数无返回值 +_unionDev( + int label[], // 标记值数组 + unsigned char elenum1, // 第一个像素点灰度值 + unsigned char elenum2, // 第二个像素点灰度值 + int elelabel1, // 第一个像素点标记值 + int elelabel2, // 第二个像素点标记值 + int *flag // 变换标记,当这两个输入像素点被合并到一个 + // 区域后,该标记值将被设为 1。 +); + +// Kernel 函数:_imginitKer(初始化输入图像第一步) +// 将输入图像所有点的灰度值置为 0 +static __global__ void // Kernel 函数无返回值 +_imginitKer( + ImageCuda inimg // 输入图像 +); + +// Kernel 函数:_initInimgKer(初始化输入图像第二步) +// 根据输入闭曲线的坐标值,将输入图像对应位置的灰度值修改为 255, +// 从而得到对应的输入图像。 +static __global__ void // Kernel 函数无返回值 +_initInimgKer( + CoordiSet incoordiset, // 输入闭曲线 + int xmin, int ymin, // 输入闭曲线的最上,最左点坐标值 + int radius, // 半径 + ImageCuda inimg // 输入图像 +); + +// Kernel 函数:_initLabelPerBlockKer (初始化每个块内像素点的标记值) +// 初始化每个线程块内点的标记值。该过程主要分为两个部分,首先, +// 每个节点的标记值为其在源图像中的索引值,如对于坐标为 (c, r) 点, +// 其初始标记值为 r * width + c ,其中 width 为图像宽; +// 然后,将各点标记值赋值为该点满足阈值关系的八邻域点中的最小标记值。 +// 该过程在一个线程块中进行。 +static __global__ void // Kernel 函数无返回值 +_initLabelPerBlockKer( + ImageCuda inimg, // 输入图像 + int label[] // 输入标记数组 +); + +// Kernel 函数:_mergeBordersKer (合并不同块内像素点的标记值) +// 不同线程块的合并过程。该过程主要合并每两个线程块边界的点, +// 在这里我们主要采用每次合并 4 × 4 个线程块的策略。 +static __global__ void // Kernel 函数无返回值 +_mergeBordersKer( + ImageCuda inimg, // 输入图像 + int *label, // 输入标记数组 + int blockw, // 应合并线程块的长度 + int blockh, // 应合并线程块的宽度 + int threadz_z, // 合并水平方向线程块时,z 向线程最大值 + int threadz_y // 合并竖直方向线程块时,z 向线程最大值 +); + +// Kernel 函数:_findFinalLabelKer (找到每个点对应的根节点标记值) +// 找出每个点对应的根节点标记值,并将该值修改为当前点标记值。 +static __global__ void // Kernel 函数无返回值 +_findFinalLabelKer( + int *label, // 输入标记值数组 + int width, // 宽度 + int height // 高度 +); + +// Kernel 函数:_initFlagSetKer(初始化标记值数组) +// 初始化标记值数组,将环外点置为 0,环内点置为 10, +// 同时使得生成的圆环上点的标记值置为 50 +static __global__ void // Kernel 函数无返回值 +_initFlagSetKer( + ImageCuda inimg, // 输入图像 + int inflagset[], // 输入标记值数组 + int *outflagset // 输出标记值数组 +); + +// Kernel 函数:_findOuterContourKer(标记外轮廓点) +// 将外环轮廓点标记值置为 100,同时将点的 class 值存入 classNum 变量中。 +static __global__ void // Kernel 函数无返回值 +_findOuterContourKer( + int inflagset[], // 输入标记值数组 + int *outflagset, // 输出标记值数组(存储外环轮廓点标记值) + int devclassarr[], // 输入 class 数组 + int width, int height, // 图像宽和图像高 + int *classnum // 输出外环的 class 值 +); + +// Kernel 函数:_fillOuterCircleKer(将外环上点标记值置为 150) +// 将属于外环的所有点的标记值置为 150。 +static __global__ void // Kernel 函数无返回值 +_fillOuterCircleKer( + int inflagset[], // 输入标记值数组 + int *outflagset, // 中间标记值数组(存储外环所有点标记值) + int devclassarr[], // 输入 class 数组 + int classnum[], // 输入外环 class 值 + int width, int height // 图像宽和高 +); + +// Kernel 函数:_findInnerContourKer (修改内外环交界处点标记值,第一步) +// 将内外环交界处点标记值置为 200,第一步 +static __global__ void // Kernel 函数无返回值 +_findInnerContourKer( + int outflagset[], // 输入标记值数组 + int *devflagset, // 输出标记值数组 + int width, int height // 图像宽和高 +); + +// Kernel 函数:_findInnerContourSecondKer(修改内外环交界处点标记值,第二步) +// 将内外环交界处点标记值置为 200,第二步 +static __global__ void // Kernel 函数无返回值 +_findInnerContourSecondKer( + int outflagset[], // 输入标记值数组 + int *devflagset, // 输出标记值数组 + int width, int height // 图像宽和高 +); + +// Kernel 函数:_contourSetToimgKer (输出轮廓坐标至图像中) +// 将轮廓坐标输出到图像中。 +static __global__ void // Kernel 函数无返回值 +_contourSetToimgKer( + int inflagset[], // 输入标记值数组 + ImageCuda contourimg // 输出轮廓图像 +); + +// Kernel 函数:_innerConSetToimgKer (输出内环内点坐标至图像中) +// 将内环内点坐标输出到图像中。 +static __global__ void // Kernel 函数无返回值 +_innerConSetToimgKer( + int inflagset[], // 输入标记值数组 + ImageCuda innerimg // 输出内环内点图像 +); + +// Kernel 函数:_imginitKer(初始化输入图像第一步) +static __global__ void _imginitKer(ImageCuda inimg) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + int inidx = r * inimg.pitchBytes + c; + // 将输入图像所有点的灰度值初始化为 0 。 + inimg.imgMeta.imgData[inidx] = 0; +} + +// Kernel 函数:_initInimgKer(初始化输入图像第二步) +static __global__ void _initInimgKer(CoordiSet incoordiset, + int xmin, int ymin, + int radius, + ImageCuda inimg) +{ + // 计算该线程在块内的相对位置。 + int inidx = blockIdx.x * blockDim.x + threadIdx.x; + + // 若线程在块内的相对位置大于输入坐标集大小,即点个数, + // 则不执行任何操作,返回。 + if (inidx >= incoordiset.count) + return; + + // 计算坐标集中每个点的横纵坐标 + int x = incoordiset.tplData[2 * inidx]; + int y = incoordiset.tplData[2 * inidx + 1]; + // 计算在新坐标系下的每个点的横纵坐标值 + x = x - xmin + radius + 1; + y = y - ymin + radius + 1; + + // 计算坐标点对应的图像数据数组下标。 + int outidx = y * inimg.pitchBytes + x; + // 将图像对应点的灰度值置为255。 + inimg.imgMeta.imgData[outidx] = 255; +} + +// Device 子程序:_findRootDev (查找根节点标记值) +static __device__ int _findRootDev(int label[], int idx) +{ + // 在 label 数组中查找 idx 下标对应的最小标记值, + // 并将该值作为返回值。 + int nexidx; + do { + nexidx = idx; + idx = label[nexidx]; + } while (idx < nexidx); + + // 处理完毕,返回根节点标记值。 + return idx; +} + +// Kernel 函数:_initLabelPerBlockKer (初始化各线程块内像素点的标记值) +static __global__ void _initLabelPerBlockKer( + ImageCuda inimg, int label[]) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量 (其中, c 表示 column; r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + int i, j, k; + // 计算输入坐标点在label数组中对应的数组下标 + int idx = r * inimg.imgMeta.width + c; + // 计算输入坐标点对应的图像数据数组下标 + int inidx = r * inimg.pitchBytes + c, newidx; + + // 计算应申请的 shared memory 的步长 + int spitch = blockDim.x + 2; + // 计算当前坐标点在 shared memory 中对应的下标 + int localidx = (threadIdx.y + 1) * spitch + threadIdx.x + 1; + + // oldlabel 用来记录当前点未经过八邻域判断前的标记值, + // newlabel 用来记录经过一轮判断后当前点的最新标记值, + // 当一个点的 oldlabel 与 newlabel 一致时,当前点对应的标记值为最终标记 + // 初始时,每个点的标记值设为其在 shared memory 中的对应下标 + int oldlabel, newlabel = localidx; + // curvalue 用来记录当前点的灰度值,newvalue 用来记录其八邻域点的灰度值 + unsigned char curvalue, newvalue; + curvalue = inimg.imgMeta.imgData[inidx]; + + // 共享内存数据区,该部分包含了存储在共享内存中的像素点的标记值。 + // 由于未对 Kernel 的尺寸做出假设,这里使用动态申请的 Shared + // Memory(共享内存)。 + extern __shared__ int slabel[]; + // 共享变量 sflag 数组用来存储是否应停止循环信息。 + // 当 sflag[0] 的值为 0 时,表示块内的迭代已经完成。 + __shared__ int sflag[1]; + + // 由于 shared memory 的大小为 (blockDim.x + 2) * (blockDim.y + 2) + // 在这里将 shared memory 的边界点(即 shared memory 中超出线程块的点) + // 的标记值设为无穷大。 + if (threadIdx.x == 0) + slabel[localidx - 1] = GET_CONTOUR_SET_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx + 1] = GET_CONTOUR_SET_INI_IFI; + if (threadIdx.y == 0) { + slabel[localidx - spitch] = GET_CONTOUR_SET_INI_IFI; + if (threadIdx.x == 0) + slabel[localidx - spitch - 1] = GET_CONTOUR_SET_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx - spitch + 1] = GET_CONTOUR_SET_INI_IFI; + } + if (threadIdx.y == blockDim.y - 1) { + slabel[localidx + spitch] = GET_CONTOUR_SET_INI_IFI; + if (threadIdx.x == 0) + slabel[localidx + spitch - 1] = GET_CONTOUR_SET_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx + spitch + 1] = GET_CONTOUR_SET_INI_IFI; + } + + while (1) { + // 将当前点的标记值设为其在 shared memory 中的数组下标 + slabel[localidx] = newlabel; + // 将 sflag[0] 标记值设为 0 + if ((threadIdx.x | threadIdx.y) == 0) + sflag[0] = 0; + // 初始时,将 newlabel 值赋给 oldlabel + oldlabel = newlabel; + __syncthreads(); + + // 在当前点的八邻域范围内查找与其灰度值之差的绝对值小于阈值的点, + // 并将这些点的最小标记值赋予记录在 newlabel 中 + for (i = r - 1; i <= r + 1; i++) { + for (j = c - 1; j <= c + 1; j++) { + if (j == c && i == r) + continue; + newidx = i * inimg.pitchBytes + j; + newvalue = inimg.imgMeta.imgData[newidx]; + if ((i >= 0 && i < inimg.imgMeta.height + && j >= 0 && j < inimg.imgMeta.width) + && (curvalue == newvalue)) { + k = localidx + (i - r) * spitch + j - c; + newlabel = min(newlabel, slabel[k]); + } + } + } + __syncthreads(); + + // 若当前点的 oldlabel 值大于 newlabel 值, + // 表明当前点的标记值不是最终的标记值 + // 则将 sflag[0] 值设为 1,来继续进行循环判断,并通过原子操作 + // 将 newlabel 与 slabel[oldlabel] 的较小值赋予 slabel[oldlabel] + if (oldlabel > newlabel) { + atomicMin(&slabel[oldlabel], newlabel); + sflag[0] = 1; + } + __syncthreads(); + + // 当线程块内所有像素点对应的标记值不再改变, + // 即 sflag[0] 的值为 0 时,循环结束。 + if (sflag[0] == 0) break; + + // 计算 newlabel 对应的根节点标记值,并将该值赋给 newlabel + newlabel = _findRootDev(slabel, newlabel); + __syncthreads(); + } + + // 将 newlabel 的值转换为其在 label 数组中的数组下标 + j = newlabel / spitch; + i = newlabel % spitch; + i += blockIdx.x * blockDim.x - 1; + j += blockIdx.y * blockDim.y - 1; + newlabel = j * inimg.imgMeta.width + i; + label[idx] = newlabel; +} + +// Device 子程序:_unionDev (合并两个不同像素点以使它们位于同一连通区域中) +static __device__ void _unionDev( + int label[], unsigned char elenum1, unsigned char elenum2, + int label1, int label2, int *flag) +{ + int newlabel1, newlabel2; + + // 比较两个输入像素点的灰度值是否满足给定的阈值范围 + if (elenum1 == elenum2) { + // 若两个点满足指定条件,则分别计算这两个点的根节点标记值 + // 计算第一个点的根节点标记值 + newlabel1 = _findRootDev(label, label1); + // 计算第二个点的根节点标记值 + newlabel2 = _findRootDev(label, label2); + // 将较小的标记值赋值给另一点在标记数组中的值 + // 并将 flag[0] 置为 1 + if (newlabel1 > newlabel2) { + // 使用原子操作以保证操作的唯一性与正确性 + atomicMin(&label[newlabel1], newlabel2); + flag[0] = 1; + } else if (newlabel2 > newlabel1) { + atomicMin(&label[newlabel2], newlabel1); + flag[0] = 1; + } + } +} + +// Kernel 函数:_mergeBordersKer(合并不同块内像素点的标记值) +static __global__ void _mergeBordersKer( + ImageCuda inimg, int *label, int blockw, int blockh, + int threadz_x, int threadz_y) +{ + int idx, iterateTimes, i; + int x, y; + int curidx, newidx; + unsigned char curvalue, newvalue; + + // 在这里以每次合并 4 * 4 = 16 个线程块的方式合并线程块 + // 分别计算待合并线程块在 GRID 中的 x 和 y 向分量 + int threadidx_x = blockDim.x * blockIdx.x + threadIdx.x; + int threadidx_y = blockDim.y * blockIdx.y + threadIdx.y; + + // 共享数组变量,只含有一个元素,每当有两个像素点合并时,该数组 + // 变量值置为 1。 + __shared__ int sflag[1]; + + while (1) { + // 设置 sflag[0] 的值为 0。 + if ((threadIdx.x | threadIdx.y | threadIdx.z) == 0) + sflag[0] = 0; + __syncthreads(); + + // 合并上下相邻线程块的水平方向边界点 + // 由于位于 GRID 中最后一行的线程块向下没有待合并的线程块 + // 因而这里不处理最后一行的线程块 + if ((threadIdx.y < blockDim.y - 1)) { + // 计算为了合并一行线程块的迭代次数 + iterateTimes = blockw / threadz_x; + + // 计算待合并像素点在源图像中的像素点坐标 + x = threadidx_x * blockw + threadIdx.z; + y = threadidx_y * blockh + blockh - 1; + + // 根据迭代次数合并块内线程标记值 + for (i = 0; i < iterateTimes; i++) { + if (threadIdx.z < threadz_x && x < inimg.imgMeta.width && + y < inimg.imgMeta.height) { + idx = y * inimg.imgMeta.width + x; + + // 计算当前像素点灰度值 + curidx = y * inimg.pitchBytes + x; + curvalue = inimg.imgMeta.imgData[curidx]; + // 计算位于当前像素点下方像素点的灰度值, + // 其坐标值为 (x, y + 1)。 + newidx = curidx + inimg.pitchBytes; + newvalue = inimg.imgMeta.imgData[newidx]; + + // 合并这两个像素点 + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width, sflag); + + // 若当前像素点不为最左侧像素点时,即 x != 0 时,合并 + // 位于当前像素点左下方像素点,其坐标值为 (x - 1, y + 1)。 + if (x - 1 >= 0) { + newidx -= 1; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width - 1, + sflag); + } + + // 若当前像素点不为最右侧像素点时,x != inimg.imgMeta.width + // 时,合并位于当前像素点右下方像素点,其坐标值为 + // (x + 1, y + 1)。 + if (x + 1 < inimg.imgMeta.width) { + newidx += 2; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width + 1, + sflag); + } + } + // 计算下次迭代的起始像素点的 x 坐标 + x += threadz_x; + } + } + + // 合并左右相邻线程块的竖直方向边界点 + // 由于位于 GRID 中最后一列的线程块向右没有待合并的线程块 + // 因而这里不处理最后一列的线程块 + if ((threadIdx.x < blockDim.x - 1)) { + // 计算为了合并一列线程块的迭代次数 + iterateTimes = blockh / threadz_y; + + // 计算待合并像素点在源图像中的像素点坐标, + // 由于处理的是每个线程块的最右一列像素点, + // 因此 x 坐标值因在原基础上加上线程块宽度 - 1 + x = threadidx_x * blockw + blockw - 1; + y = threadidx_y * blockh + threadIdx.z; + + // 根据迭代次数合并块内线程标记值 + for (i = 0; i < iterateTimes; i++) { + if (threadIdx.z < threadz_y && x < inimg.imgMeta.width && + y < inimg.imgMeta.height) { + idx = y * inimg.imgMeta.width + x; + + // 计算当前像素点灰度值 + curidx = y * inimg.pitchBytes + x; + curvalue = inimg.imgMeta.imgData[curidx]; + // 计算位于当前像素点右侧像素点的灰度值, + // 其坐标值为 (x + 1, y)。 + newidx = curidx + 1; + newvalue = inimg.imgMeta.imgData[newidx]; + // 合并这两个像素点 + _unionDev(label, curvalue, newvalue, + idx, idx + 1, sflag); + + // 若当前像素点不为最上侧像素点时,即 y != 0 时,合并 + // 位于当前像素点右上方像素点,其坐标值为 (x + 1, y - 1)。 + if (y - 1 >= 0) { + newidx -= inimg.pitchBytes; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, idx, + idx - inimg.imgMeta.width + 1, + sflag); + } + + // 若当前像素点不为最下侧像素点时, + // 即 y != inimg.imgMeta.height时,合并位于当前像素点 + // 右下方像素点,其坐标值为(x + 1, y + 1)。 + if (y + 1 < inimg.imgMeta.height) { + newidx = curidx + inimg.pitchBytes + 1; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width + 1, + sflag); + } + } + // 计算下次迭代的起始像素点的 y 坐标 + y += threadz_y; + } + } + __syncthreads(); + if (sflag[0] == 0) break; + } +} + +// Kernel 函数:_findFinalLabelKer (找到每个点对应的根节点标记值) +static __global__ void _findFinalLabelKer(int *label, int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量 (其中, c 表示 column; r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算输入坐标点在label数组中对应的数组下标 + int inidx = r * width + c; + + // 计算当前像素点的标记值 + int curlabel = label[inidx]; + // 将当前像素点标记值的根节点值赋给原像素点 + int newlabel = _findRootDev(label, curlabel); + label[inidx] = newlabel; +} + +// Kernel 函数:_initFlagSetKer(初始化标记值数组) +static __global__ void _initFlagSetKer( + ImageCuda inimg, int inflagset[], int *outflagset) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算输入坐标点在图像中对应的数组下标 + int inidx = r * inimg.pitchBytes + c; + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * inimg.imgMeta.width + c; + unsigned char intemp; + // 读取坐标点对应的像素值 + intemp = inimg.imgMeta.imgData[inidx]; + if (inflagset[flagidx] != OUTER_ORIGIN_CONTOUR) + inflagset[flagidx] = INNER_ORIGIN_CONTOUR; + + // 若当前点像素点不为 0,则将标记值数组对应位置的值置为 50。 + if (intemp) { + inflagset[flagidx] = DILATE_CIRCLE; + } + outflagset[flagidx] = inflagset[flagidx]; +} + +// Kernel 函数:_findOuterContourKer(修改外轮廓点标记值) +static __global__ void _findOuterContourKer( + int inflagset[], int *outflagset, int devclassarr[], + int width, int height, int *classnum) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * width + c, newidx; + if (inflagset[flagidx] == DILATE_CIRCLE) { + for (int i = 0; i < 8; i++) { + newidx = (_eightNeighDev[i][1] + r) * width + + _eightNeighDev[i][0] + c; + if (!inflagset[newidx]) { + outflagset[flagidx] = OUTER_CONTOUR; + *classnum = devclassarr[flagidx]; + break; + } + } + } +} + +// Kernel 函数:_fillOuterCircleKer(将外环内所有点标记值置为 150) +static __global__ void _fillOuterCircleKer( + int inflagset[], int *outflagset, int devclassarr[], int classnum[], + int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * width + c, newidx; + // num 用来存储当前点八邻域范围内标记值为 50 的点个数 + // classtotalnum 用来存储当前点八邻域范围内 class 值为 *classNum 的点个数 + int num = 0, classtotalnum = 0; + if (inflagset[flagidx] == DILATE_CIRCLE) { + for (int i = 0; i < 8; i++) { + newidx = (_eightNeighDev[i][1] + r) * width + + _eightNeighDev[i][0] + c; + if (inflagset[newidx] == DILATE_CIRCLE) + num++; + if (devclassarr[newidx] == *classnum) + classtotalnum++; + } + } + + // 若当前点八邻域范围内皆为膨胀环内点时,则设置当前点为外环上点。 + if (num == 8 && classtotalnum == 8) { + outflagset[flagidx] = OUTER_CIRCLE; + } +} + +// Kernel 函数:_findInnerContourKer(修改内外环交界处点标记值,第一步) +static __global__ void _findInnerContourKer( + int outflagset[], int *devflagset, int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * width + c, newidx = 0; + if (outflagset[flagidx]== DILATE_CIRCLE) { + for (int i = 0; i < 8; i++) { + newidx = (_eightNeighDev[i][1] + r) * width + + _eightNeighDev[i][0] + c; + if (outflagset[newidx] == OUTER_CIRCLE || + outflagset[newidx] == OUTER_CONTOUR) { + devflagset[flagidx] = INNER_CONTOUR; + break; + } + } + } +} + +// Kernel 函数:_findInnerContourSecondKer(修改内外环交界处点标记值,第二步) +static __global__ void _findInnerContourSecondKer( + int outflagset[], int *devflagset, int width, int height) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= width || r >= height) + return; + + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * width + c; + if (devflagset[flagidx] == INNER_CONTOUR) + outflagset[flagidx] = INNER_CONTOUR; +} + +// Kernel 函数:_contourSetToimgKer(将轮廓坐标输出到图像中) +static __global__ void _contourSetToimgKer( + int inflagset[], ImageCuda contourimg) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= contourimg.imgMeta.width || r >= contourimg.imgMeta.height) + return; + + // 计算输入坐标点在图像中对应的数组下标 + int inidx = r * contourimg.pitchBytes + c; + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * contourimg.imgMeta.width + c; + contourimg.imgMeta.imgData[inidx] = 0; + if (inflagset[flagidx] == INNER_CONTOUR) + contourimg.imgMeta.imgData[inidx] = 255; +} + +// Kernel 函数:_innerConSetToimgKer(将内环内点坐标输出到图像中) +static __global__ void _innerConSetToimgKer(int inflagset[], ImageCuda innerimg) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= innerimg.imgMeta.width || r >= innerimg.imgMeta.height) + return; + + // 计算输入坐标点在图像中对应的数组下标 + int inidx = r * innerimg.pitchBytes + c; + // 计算输入坐标点在输入数组中对应的数组下标 + int flagidx = r * innerimg.imgMeta.width + c; + innerimg.imgMeta.imgData[inidx] = 0; + if (inflagset[flagidx] == INNER_ORIGIN_CONTOUR || + inflagset[flagidx] == DILATE_CIRCLE) + innerimg.imgMeta.imgData[inidx] = 255; +} + +// Host 成员方法:findMinMaxCoordinates (计算最左最右最上最下点坐标值) +// 根据输入闭曲线分别找到曲线最左,最右,最上,最下点的对应坐标值 +__host__ int GetContourSet::findMinMaxCoordinates(CoordiSet *incoordiset, + int *resultcs) +{ + // 声明局部变量。 + int i; + int errcode = NO_ERROR; + + // 将 incoordiSet 坐标集拷贝至 host 端。 + errcode = CoordiSetBasicOp::copyToHost(incoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 初始化 x 和 y 方向上的最小最大值。 + resultcs[0] = resultcs[1] = incoordiset->tplData[0]; + resultcs[2] = resultcs[3] = incoordiset->tplData[1]; + for (i = 1; i < incoordiset->count; i++) { + // resultcs[0] 存储坐标点横坐标最大值 + if (resultcs[0] > incoordiset->tplData[2 * i]) + resultcs[0] = incoordiset->tplData[2 * i]; + // resultcs[1] 存储坐标点横坐标最小值 + if (resultcs[1] < incoordiset->tplData[2 * i]) + resultcs[1] = incoordiset->tplData[2 * i]; + // resultcs[2] 存储坐标点纵坐标最大值 + if (resultcs[2] > incoordiset->tplData[2 * i + 1]) + resultcs[2] = incoordiset->tplData[2 * i + 1]; + // resultcs[3] 存储坐标点纵坐标最小值 + if (resultcs[3] < incoordiset->tplData[2 * i + 1]) + resultcs[3] = incoordiset->tplData[2 * i + 1]; + } + return errcode; +} + +// Host 成员方法:sortContour (按序输出坐标点集) +// 根据输入 inArray 按顺时针方向顺序输出有序的点集,并将结果 +// 输出到一个坐标集 outcoordiset 中。 +__host__ int GetContourSet::sortContour(int inarray[], CoordiSet *outcoordiset, + int width, int height) +{ + int errcode = NO_ERROR; // 局部变量,错误码 + + CoordiSetBasicOp::makeAtHost(outcoordiset, width * height); + // bFindStartPoint 表示是否是否找到起始点及回到起始点 + // bFindPoint 表示是否扫描到一个边界点 + bool bFindStartPoint, bFindPoint; + // startPW 和 startPH 分别表示起始点对应横坐标以及纵坐标 + // curPW 和 curPH 分别表示当前点对应横坐标以及纵坐标 + int startPW, startPH, curPW, curPH; + // 定义扫描的八邻域方向坐标 + static int direction[8][2] = { + { -1, -1 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, + { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 } + }; + int beginDirect; + bFindStartPoint = false; + int index = 0; + int curvalue; + + // 找到最左下方的边界点 + for (int j = 1; j < height - 1 && !bFindStartPoint; j++) { + for (int i = 1; i < width - 1 && !bFindStartPoint; i++) { + curvalue = inarray[j * width + i]; + if (curvalue == INNER_CONTOUR) { + bFindStartPoint = true; + startPW = i; + startPH = j; + } + } + } + + // 由于起始点是在左下方,故起始扫描沿左上方向 + beginDirect = 0; + bFindStartPoint = false; + curPW = startPW; + curPH = startPH; + while (!bFindStartPoint) { + // 从起始点一直找边界,直到再次找到起始点为止 + bFindPoint = false; + while (!bFindPoint) { + // 沿扫描方向,获取左上方向像素点灰度值 + curvalue = inarray[(curPH + direction[beginDirect][1]) * width + + curPW + direction[beginDirect][0]]; + if (curvalue == INNER_CONTOUR) { + bFindPoint = true; + outcoordiset->tplData[2 * index] = curPW; + outcoordiset->tplData[2 * index + 1] = curPH; + index++; + curPW = curPW + direction[beginDirect][0]; + curPH = curPH + direction[beginDirect][1]; + if (curPH == startPH && curPW == startPW) + bFindStartPoint = true; + // 扫描的方向逆时针旋转两格 + beginDirect--; + if (beginDirect == -1) + beginDirect = 7; + beginDirect--; + if (beginDirect == -1) + beginDirect = 7; + } else { + // 扫描的方向顺时针旋转一格 + beginDirect++; + if (beginDirect == 8) + beginDirect = 0; + } + } + } + // 修改输出坐标集的大小为轮廓点个数,即 index + outcoordiset->count = index; + return errcode; +} + +// 宏:FAIL_GET_CONTOUR_SET_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_GET_CONTOUR_SET_FREE do { \ + if (alldatadev != NULL) \ + cudaFree(alldatadev); \ + if (resultcs != NULL) \ + delete [] resultcs; \ + if (tmpimg != NULL) \ + ImageBasicOp::deleteImage(tmpimg); \ + if (outimg != NULL) \ + ImageBasicOp::deleteImage(outimg); \ + if (coorForTorus != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(coorForTorus); \ + if (torusClass != NULL) \ + delete [] torusClass; \ + if (classArr != NULL) \ + delete [] classArr; \ + } while (0) + +// Host 成员方法:getContourSet(执行有连接性的封闭轮廓的获得算法) +// 根据输入输入闭曲线坐标点集,生成内环内所有点坐标点集到 innerCoordiset 中, +// 生成内外环交界处坐标点集并输出到 contourCoordiset 中。 +__host__ int GetContourSet::getContourSet(CoordiSet *incoordiset, + CoordiSet *innercoordiset, + CoordiSet *contourcoordiset) +{ + // 检查输入输出坐标集是否为 NULL,如果为 NULL 直接报错返回。 + if (incoordiset == NULL || innercoordiset == NULL || + contourcoordiset == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + int *resultcs; + // 声明需要的指针变量。 + int *alldatadev = NULL; + // 声明 Device 端需要的变量。 + int *devFlagset = NULL, *outFlagset = NULL; + int *devNum = NULL, *devClassArr = NULL; + + // 声明需要使用的中间图像 tmpimg 和 outimg + Image *tmpimg, *outimg; + + // 声明调用二分类方法需要使用的临时变量, + // 分别为 coorForTorus,torusClass 以及 classArr。 + CoordiSet *coorForTorus; + unsigned char *torusClass = NULL; + int *classArr = NULL; + + // 为 resultcs 分配大小,用来存储输入闭曲线的最左最右最上最下点的坐标值。 + resultcs = new int[4]; + if (resultcs == NULL) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return OUT_OF_MEM; + } + + // 在 host 端分配 tmpimg 以及 outimg + errcode = ImageBasicOp::newImage(&tmpimg); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = ImageBasicOp::newImage(&outimg); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 分别找到输入闭曲线的最左最右最上最下点的坐标值, + // 并将其输出到一个大小为 4 的数组中。 + errcode = GetContourSet::findMinMaxCoordinates(incoordiset, resultcs); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 计算应生成图像的长和宽 + int width = resultcs[1] - resultcs[0] + 2 * radiusForCurve + 3; + int height = resultcs[3] - resultcs[2] + 2 * radiusForCurve + 3; + + // 将图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::makeAtCurrentDevice(tmpimg, width, height); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 将图像拷贝到 Host 内存中。 + errcode = ImageBasicOp::makeAtHost(outimg, width, height); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 一次性申请 Device 端需要的所有空间。 + errcode = cudaMalloc((void **)&alldatadev, + (1 + 3 * width * height) * sizeof (int)); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 初始化 GetContourSet 累加器在 Device 上的内存空间。 + errcode = cudaMemset(alldatadev, 0, + (1 + 3 * width * height) * sizeof (int)); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 通过偏移得到各指针的地址。 + devNum = alldatadev; + devFlagset = alldatadev + 1; + outFlagset = alldatadev + 1 + width * height; + devClassArr = alldatadev + 1 + 2 * width * height; + + // 将 incoordiset 拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(incoordiset); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 提取图像的 ROI 子图像。 + ImageCuda tmpsubimgCud; + errcode = ImageBasicOp::roiSubImage(tmpimg, &tmpsubimgCud); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + tmpsubimgCud.imgMeta.width = width; + tmpsubimgCud.imgMeta.height = height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 grid, block; + block.x = DEF_BLOCK_X; + block.y = DEF_BLOCK_Y; + block.z = 1; + grid.x = (width + block.x - 1) / block.x; + grid.y = (height + block.y - 1) / block.y; + grid.z = 1; + _imginitKer<<>>(tmpsubimgCud); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = 1; + blocksize.z = 1; + gridsize.x = (incoordiset->count + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + gridsize.z = 1; + _initInimgKer<<>>(*incoordiset, + resultcs[0], resultcs[2], + radiusForCurve, tmpsubimgCud); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 将 tmpimg 拷贝至 host 端,便于执行后续的膨胀操作。 + errcode = ImageBasicOp::copyToHost(tmpimg); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 调用已封装好的膨胀操作 + morForCurve.dilate(tmpimg, outimg); + + // 声明 ImgConvert 类变量,进行图像转坐标集调用。 + ImgConvert imgconForCurve; + CoordiSetBasicOp::newCoordiSet(&coorForTorus); + // 根据输入图像生成坐标集 coorForTorus + errcode = imgconForCurve.imgConvertToCst(outimg, coorForTorus); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 为 torusClass 分配大小,用以存储生成的 class 数组。 + torusClass = new unsigned char[coorForTorus->count]; + if (torusClass == NULL) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return OUT_OF_MEM; + } + // 调用 TorusSegmentation 的 torusSegregate 操作,生成内外环 + errcode = tsForCurve.torusSegregate(width, height, + coorForTorus, torusClass); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + errcode = CoordiSetBasicOp::copyToHost(coorForTorus); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 为 classArr 分配大小,大小为 width * height 的整型数组, + // 用以保存 torusClass 对应于图像索引的值 + //(由于 torusClass 的大小为 coorForTorus->count) + classArr = new int[width * height]; + if (classArr == NULL) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return OUT_OF_MEM; + } + + int x, y; + for (int i = 0; i < coorForTorus->count; i++) { + // 计算输入坐标集对应的横坐标与纵坐标 + x = coorForTorus->tplData[2 * i]; + y = coorForTorus->tplData[2 * i + 1]; + // 将 torusClass 中的值赋值给 classArr 数组 + classArr[y * width + x] = (int)torusClass[i]; + } + // 将 classArr 数组的值拷贝至 Device 端,便于进行 Device 端处理。 + errcode = cudaMemcpy(devClassArr, classArr, + width * height * sizeof (int), + cudaMemcpyHostToDevice); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 将膨胀生成的图像 outimg 拷贝至 Device 端,便于进行 Device 端处理。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + // 提取生成图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + outsubimgCud.imgMeta.width = width; + outsubimgCud.imgMeta.height = height; + + // 计算初始化块内内存时,共享内存的大小。 + int smsize = sizeof (int) * (block.x + 2) * (block.y + 2); + + // 调用核函数,初始化每个线程块内标记值 + _initLabelPerBlockKer<<>>( + outsubimgCud, devFlagset); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 合并线程块时每次合并线程块的长、宽和高 + int blockw, blockh, blockz; + // 计算第一次合并时,应合并线程块的长、宽和高 + // 第一次合并时,应合并线程块的长应为初始线程块长,宽为初始线程块宽 + blockw = block.x; + blockh = block.y; + // 由于这里采用的是 3 维线程块,线程块的高设为初始线程块长和宽的较大者。 + blockz = blockw; + if (blockw < blockh) + blockz = blockh; + + // 计算每次合并的线程块个数,在这里我们采用的是每次合并 4 × 4 的线程块, + // 由于采用这种方式合并所需的迭代次数最少。 + int xtiles = 4, ytiles = 4; + // 计算合并线程块前 GRID 的长 + int tilesizex = grid.x; + // 计算合并线程块前 GRID 的宽 + int tilesizey = grid.y; + // 定义为进行线程块合并而采用的线程块与网格。 + dim3 mrgblocksize, mrggridsize; + + // 由于每个线程块的大小限制为 1024,而 tilesizex * tilesizey * blockz 的值 + // 为每次用来进行合并操作的三维线程块的最大大小,因此当该值不大于 1024 时, + // 可将所有线程块放在一个三维线程块中合并,这样,我们就可以以该值是否 + // 不大于 1024 来作为是否终止循环的判断条件。 + while (tilesizex * tilesizey * blockz > 1024) { + // 计算每次合并线程块时 GRID 的长,这里采用向上取整的方式 + tilesizex = (tilesizex - 1) / xtiles + 1; + // 计算每次合并线程块时 GRID 的宽,这里采用向上取整的方式 + tilesizey = (tilesizey - 1) / ytiles + 1; + + // 设置为了合并而采用的三维线程块大小,这里采用的是 4 × 4 的方式, + // 因此线程块的长为 4,宽也为 4,高则为 32。 + mrgblocksize.x = xtiles; mrgblocksize.y = ytiles; + mrgblocksize.z = blockz; + // 设置为了合并而采用的二维网格的大小。 + mrggridsize.x = tilesizex; mrggridsize.y = tilesizey; + mrggridsize.z = 1; + // 调用核函数,每次合并4 × 4 个线程块内的标记值 + _mergeBordersKer<<>>( + outsubimgCud, devFlagset, blockw, blockh, + block.x, block.y); + if (cudaGetLastError() != cudaSuccess) { + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + // 在每次迭代后,修改应合并线程块的长和宽,因为每次合并 4 * 4 个线程块, + // 因此,经过迭代后,应合并线程块的长和宽应分别乘 4。 + blockw *= xtiles; + blockh *= ytiles; + } + + // 进行最后一轮线程块的合并 + // 计算该轮应采用的三维线程块大小 + mrgblocksize.x = tilesizex; mrgblocksize.y = tilesizey; + mrgblocksize.z = blockz; + // 设置该论应采用的网格大小,长宽高分别为1。 + mrggridsize.x = 1; mrggridsize.y = 1;mrggridsize.z = 1; + // 调用核函数,进行最后一轮线程块合并 + _mergeBordersKer<<>>( + outsubimgCud, devFlagset, blockw, blockh, + block.x, block.y); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,即找出每个结点对应的标记值, + // 其中根节点的标记值与其自身在数组中的索引值一致 + _findFinalLabelKer<<>>(devFlagset, width, height); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,初始化标记值数组,将环外点置为 0,环内点置为 10 + _initFlagSetKer<<>>(outsubimgCud, devFlagset, outFlagset); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,将外环轮廓点标记值置为 100, + // 同时将点的 class 值存入 classNum 变量中。 + _findOuterContourKer<<>>(devFlagset, outFlagset, + devClassArr,width, height, devNum); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,将外环上点标记值置为 150 + _fillOuterCircleKer<<>>(devFlagset, outFlagset, devClassArr, + devNum, width, height); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,将内外环交界处点标记值置为 200,第一步 + _findInnerContourKer<<>>(outFlagset, devFlagset, + width, height); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,将内外环交界处点标记值置为 200,第二步 + _findInnerContourSecondKer<<>>(outFlagset, devFlagset, + width, height); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + // 调用核函数,将内环内点坐标输出到图像中。 + _innerConSetToimgKer<<>>(outFlagset, outsubimgCud); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + + errcode = ImageBasicOp::copyToHost(outimg); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + // 调用已封装好的图像转坐标集函数,生成内环内点坐标集。 + errcode = imgconForCurve.clearAllConvertFlags(); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = imgconForCurve.setConvertFlag(255); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = imgconForCurve.imgConvertToCst(outimg, innercoordiset); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = CoordiSetBasicOp::copyToHost(innercoordiset); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + + // 当 inorder 参数表示为有序时,调用 host 端 SortContour 函数输出数组, + // 否则调用 reindex 实现乱序输出。 + if (inorder) { + int *hostFlagset = new int[width * height]; + if (hostFlagset == NULL) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return OUT_OF_MEM; + } + errcode = cudaMemcpy(hostFlagset, outFlagset, + width * height * sizeof (int), + cudaMemcpyDeviceToHost); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + errcode = sortContour(hostFlagset, contourcoordiset, width, height); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + delete [] hostFlagset; + return errcode; + } + if (hostFlagset != NULL) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + delete [] hostFlagset; + return errcode; + } + } else { + ImageBasicOp::copyToCurrentDevice(outimg); + // 调用核函数,将轮廓点坐标输出到图像中。 + _contourSetToimgKer<<>>(outFlagset, outsubimgCud); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return CUDA_ERROR; + } + errcode = ImageBasicOp::copyToHost(outimg); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = imgconForCurve.clearAllConvertFlags(); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = imgconForCurve.setConvertFlag(255); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + // 调用已封装好的图像转坐标集函数,生成内环内点坐标集。 + errcode = imgconForCurve.imgConvertToCst(outimg, contourcoordiset); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + errcode = CoordiSetBasicOp::copyToHost(contourcoordiset); + if (errcode != NO_ERROR) { + // 释放内存空间。 + FAIL_GET_CONTOUR_SET_FREE; + return errcode; + } + } + + // 释放内存 + FAIL_GET_CONTOUR_SET_FREE; + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/GetContourSet.h b/okano_3_0/GetContourSet.h new file mode 100644 index 0000000..1fe5288 --- /dev/null +++ b/okano_3_0/GetContourSet.h @@ -0,0 +1,220 @@ +// GetContourSet.h +// 创建人:王媛媛 +// +// 有连接性的封闭轮廓的获得(GetContourSet) +// 功能说明:根据设定半径,对输入闭曲线做膨胀处理,再调用 SmoothVector 操作,将 +// 环划分成内外环,最终输出内外环交接处点坐标值以及交界处内所有点坐标值。 +// +// 修订历史: +// 2012年10月08日(王媛媛) +// 初始版本 +// 2012年10月30日(王媛媛) +// 完成进行算法核心代码设计,包括生成初始输入图像,进行膨胀处理, +// 标记值数组的初始化。 +// 2012年11月20日(王媛媛) +// 修改代码格式,添加相应的注释。 +// 2012年11月20日(王媛媛) +// 实现了闭曲线的膨胀处理,外环的查找。 +// 2012年12月04日(王媛媛) +// 更正了闭曲线的外环查找与更新标记值数组值操作。 +// 2012年12月15日(王媛媛) +// 使用连通域类似方法实现了内外环点标记值的赋值。 +// 2013年01月07日(王媛媛) +// 添加了对于二分类方法的调用。 +// 2013年01月09日(王媛媛) +// 更正了代码规范。 +// 2013年01月12日(王媛媛) +// 更正了算法主函数,添加参数 inOrder 指示是否按序输出 +// 内外环交界处坐标点集。 +// 2013年01月13日(王媛媛) +// 更正算法找到内外边界点核函数以及部分代码规范。 +// 2013年04月07日(王媛媛) +// 添加二分类方法 TorusSegmentation 作为调用参数。 +// 2013年04月09日(王媛媛) +// 修改对于二分类算法的调用过程, +// 添加生成与图像大小一致的 Class 数组 Host 端程序。 +// 2013年04月12日(王媛媛) +// 修改内存拷贝错误,使算法调用达到预期效果。更正部分代码规范。 +// 2013年04月12日(王媛媛) +// 修改原有的图像转坐标集为已封装完成的接口。 +// 2013年04月15日(王媛媛) +// 修改代码注释规范。 + +#ifndef __GETCONTOURSET_H__ +#define __GETCONTOURSET_H__ + +#include "Image.h" +#include "Template.h" +#include "CoordiSet.h" +#include "TemplateFactory.h" +#include "Morphology.h" +#include "FeatureVecCalc.h" +#include "FeatureVecArray.h" +#include "SmoothVector.h" +#include "Segmentation.h" +#include "TorusSegmentation.h" +#include "ImgConvert.h" +#include "ErrorCode.h" + +// 类:GetContourSet +// 继承自:无 +// 根据设定的半径,对输入闭曲线做膨胀处理生成圆环, +// 再调用 SmoothVector 操作,对圆环进行划分成内外环, +// 最终输出内外环交接处点坐标值以及交界处内所有点坐标值。 +class GetContourSet { + +protected: + + // 成员变量:radiusForCurve (膨胀半径) + // 进行膨胀的半径。 + int radiusForCurve; + + // 成员变量:inorder (按序输出参数) + // 表示是否按序输出坐标点集,true 表示顺序输出,false 表示乱序输出。 + bool inorder; + + // 成员变量:morForCurve (形态学方法——膨胀) + // 进行环状曲线膨胀算法,生成圆环。 + Morphology morForCurve; + + // 成员变量:tsForCurve (二分类方法) + // 进行二分类方法,生成内外圆环。 + TorusSegmentation tsForCurve; + + // Host 成员方法:findMinMaxCoordinates (计算最左最右最上最下点坐标值) + // 根据输入闭曲线分别找到曲线最左,最右,最上,最下点的对应坐标值 + // 输出到一个一维数组 resultcs 中。 + __host__ int // 返回值:函数是否正确执行, + // 若函数正确执行,返回NO_ERROR。 + findMinMaxCoordinates( + CoordiSet *incoordiset, // 输入坐标点集 + int *resultcs // 输出指示最上、最下、最左、最右坐标集数组 + ); + + // Host 成员方法:sortContour (按序输出坐标点集) + // 根据输入 inArray 按顺时针方向顺序输出有序的点集,并将结果 + // 输出到一个坐标集 outcoordiset 中。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回NO_ERROR。 + sortContour( + int inarray[], // 输入 contour 值,其中 200 表示边界。 + CoordiSet *outcoordiset, // 输出保存内外交界处坐标点集 + int width, int height // 图像宽和图像高 + ); + +public: + // 构造函数:GetContourSet + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + GetContourSet() { + // 使用默认值为类的各个成员变量赋值。 + this->radiusForCurve = 2; // 膨胀半径默认为 2。 + this->inorder = false; // 默认乱序输出坐标点集。 + this->tsForCurve.setNeighborSize(1); // 邻域宽度默认为 1。 + + // 使用默认值为膨胀算法的各个成员变量赋值。 + int diameter = 2 * this->radiusForCurve + 1; + Template *tplfordilate; + dim3 size(diameter, diameter); + // 设置膨胀圆形模板 + TemplateFactory::getTemplate(&tplfordilate, TF_SHAPE_CIRCLE, + size , NULL); + this->morForCurve.setTemplate(tplfordilate); + } + + // 构造函数:GetContourSet + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + GetContourSet( + int radiusforcurve, // 膨胀半径 + bool inorder // 按序输出参数 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->radiusForCurve = 2; // 半径值默认为 2。 + this->inorder = false; // 默认乱序输出坐标点集。 + this->tsForCurve.setNeighborSize(1); // 邻域宽度默认为 1。 + // 使用默认值为膨胀算法的各个成员变量赋值。 + int diameter = 2 * this->radiusForCurve + 1; + Template *tplfordilate; + dim3 size(diameter, diameter); + // 设置膨胀圆形模板 + TemplateFactory::getTemplate(&tplfordilate, TF_SHAPE_CIRCLE, + size , NULL); + this->morForCurve.setTemplate(tplfordilate); + + // 根据参数列表中的值设定成员变量的初值 + setRadius(radiusforcurve); + setOrder(inorder); + } + + // 析构函数:~GetContourSet + // 用于释放膨胀模板元素。 + __host__ + ~GetContourSet() { + // 如果模板元素已经申请,则需要释放掉模板元素。 + if (this->morForCurve.getTemplate() != NULL) + TemplateFactory::putTemplate(this->morForCurve.getTemplate()); + } + + // 成员方法:getRadius(获取半径值) + // 获取成员变量 radiusForCurve 的值。 + __host__ __device__ int // 返回值:成员变量 radiusForCurve 的值 + getRadius() const + { + // 返回 radiusForCurve 成员变量的值。 + return this->radiusForCurve; + } + + // 成员方法:setRadius(设置半径值) + // 设置成员变量 radiusForCurve 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setRadius( + int radiusforcurve // 膨胀半径 + ) { + // 若半径超出允许范围不赋值直接退出 + if (radiusforcurve <= 0) + return INVALID_DATA; + // 将 radiusForCurve 成员变量赋成新值 + this->radiusForCurve = radiusforcurve; + return NO_ERROR; + } + + // 成员方法:getOrder(获取是否按序变量的值) + // 获取成员变量 inorder 的值。 + __host__ __device__ bool // 返回值:成员变量 inorder 的值 + getOrder() const + { + // 返回 inorder 成员变量的值。 + return this->inorder; + } + + // 成员方法:setOrder(设置是否按序变量的值) + // 设置成员变量 inOrder 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setOrder( + bool inorder // 按序输出参数 + ) { + // 将 inorder 成员变量赋成新值 + this->inorder = inorder; + + return NO_ERROR; + } + + // Host 成员方法:getContourSet(执行有连接性的封闭轮廓的获得算法) + // 根据输入输入闭曲线坐标点集,生成内环内所有点坐标到 innerCoordiset 中, + // 生成内外环交界处坐标点集并输出到 contourCoordiset 中。 + __host__ int // 返回值:函数是否正确执行, + // 若函数正确执行,返回NO_ERROR。 + getContourSet( + CoordiSet *incoordiset, // 输入闭曲线坐标点集 + CoordiSet *innercoordiset, // 输出内环内所有点坐标点集 + CoordiSet *contourcoordiset // 输出内外环交界处坐标点集 + ); +}; + +#endif + diff --git a/okano_3_0/Histogram.cu b/okano_3_0/Histogram.cu new file mode 100644 index 0000000..cb38b4f --- /dev/null +++ b/okano_3_0/Histogram.cu @@ -0,0 +1,254 @@ +// Hitogram.cu +// 实现计算图像直方图算法 + +#include "Histogram.h" + +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:HISTOGRAM_PACK_LEVEL +// 定义了一个线程中计算的像素点个数,若该值为4,则在一个线程中计算 2 ^ 4 = 16 +// 个像素点。 +#define HISTOGRAM_PACK_LEVEL 4 + +#define HISTOGRAM_PACK_NUM (1 << HISTOGRAM_PACK_LEVEL) +#define HISTOGRAM_PACK_MASK (HISTOGRAM_PACK_NUM - 1) + +#if (HISTOGRAM_PACK_LEVEL < 1 || HISTOGRAM_PACK_LEVEL > 5) +# error Unsupport HISTOGRAM_PACK_LEVEL Value!!! +#endif + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数: _histogramKer(计算图像的直方图) +// 根据输入图像每个像素点的灰度值,累加到直方图数组中的相应的位置,从而得到 +// 输入图像的直方图。 +static __global__ void // Kernel 函数无返回值。 +_histogramKer( + ImageCuda inimg, // 输入图像。 + unsigned int *devhist //图像直方图。 +); + +// Kernel 函数: _histogramKer(计算图像的直方图) +static __global__ void _histogramKer(ImageCuda inimg, unsigned int *devhist) +{ + // 申请大小为灰度图像灰度级 256 的共享内存,其中下标代表图像的灰度值,数 + // 组用来累加等于该灰度值的像素点个数。 + __shared__ unsigned int temp[256]; + + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,默认令一个线程处理 16 个输出像素,这四个像素位于统一列 + // 的相邻 16 行上,因此,对于 r 需要进行右移计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) << HISTOGRAM_PACK_LEVEL; + + // 计算该线程在块内的相对位置。 + int inindex = threadIdx.y * blockDim.x + threadIdx.x; + + // 临时变量,curgray 用于存储当前点的像素值,inptrgray 存储下一个点的像素值。 + // cursum 用于存储局部累加和。 + unsigned int curgray = 0, inptrgray; + unsigned int curnum = 0; + + // 若线程在块内的相对位置小于 256,即灰度级大小,则用来给共享内存赋初值 0。 + if (inindex < 256) + temp[inindex] = 0; + // 进行块内同步,保证执行到此处,共享内存的数组中所有元素的值都为 0。 + __syncthreads(); + + do { + // 线程中处理第一个点。 + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资 + // 源,一方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + break; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + + // 读取第一个输入坐标点对应的像素值。 + curgray = inimg.imgMeta.imgData[inidx]; + curnum = 1; + + // 处理第二个点。 + // 此后的像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= inimg.imgMeta.height) + break; + + // 得到第二个点的像素值。 + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计算。 + inidx += inimg.pitchBytes; + inptrgray = inimg.imgMeta.imgData[inidx]; + + // 若当前第二个点的像素值不等于前一个,把当前临时变量 cursum 中的统计结 + // 果增加到共享内存中的相应区域;若该值等于前一个点的像素值,则临时变量 + // cursum 加 1,继续检查下一个像素点。 + if (curgray != inptrgray) { + // 使用原子操作把临时变量 curnum 的结果加到共享内存中,可以防止多个 + // 线程同时更改数据而发生的写错误。 + atomicAdd(&temp[curgray], curnum); + curgray = inptrgray; + //curnum = 1; + } else { + curnum++; + } + + // 宏:HISTOGRAM_KERNEL_MAIN_PHASE + // 定义计算下一个像素点的程序片段。使用这个宏可以实现获取下一个点的像素 + // 值,并累加到共享内存,并且简化编码量。 +#define HISTOGRAM_KERNEL_MAIN_PHASE \ + if (++r >= inimg.imgMeta.height) \ + break; \ + inidx += inimg.pitchBytes; \ + inptrgray = inimg.imgMeta.imgData[inidx]; \ + if (curgray != inptrgray) { \ + atomicAdd(&temp[curgray], curnum); \ + curgray = inptrgray; \ + curnum = 1; \ + } else { \ + curnum++; \ + } + +#define HISTOGRAM_KERNEL_MAIN_PHASEx2 \ + HISTOGRAM_KERNEL_MAIN_PHASE \ + HISTOGRAM_KERNEL_MAIN_PHASE + +#define HISTOGRAM_KERNEL_MAIN_PHASEx4 \ + HISTOGRAM_KERNEL_MAIN_PHASEx2 \ + HISTOGRAM_KERNEL_MAIN_PHASEx2 + +#define HISTOGRAM_KERNEL_MAIN_PHASEx8 \ + HISTOGRAM_KERNEL_MAIN_PHASEx4 \ + HISTOGRAM_KERNEL_MAIN_PHASEx4 + +#define HISTOGRAM_KERNEL_MAIN_PHASEx16 \ + HISTOGRAM_KERNEL_MAIN_PHASEx8 \ + HISTOGRAM_KERNEL_MAIN_PHASEx8 + +// 对于不同的 HISTOGRAM_PACK_LEVEL ,定义不同的执行次数,从而使一个线程内部 +// 实现对多个点的像素值的统计。 +#if (HISTOGRAM_PACK_LEVEL >= 2) + HISTOGRAM_KERNEL_MAIN_PHASEx2 +# if (HISTOGRAM_PACK_LEVEL >= 3) + HISTOGRAM_KERNEL_MAIN_PHASEx4 +# if (HISTOGRAM_PACK_LEVEL >= 4) + HISTOGRAM_KERNEL_MAIN_PHASEx8 +# if (HISTOGRAM_PACK_LEVEL >= 5) + HISTOGRAM_KERNEL_MAIN_PHASEx16 +# endif +# endif +# endif +#endif + +// 取消前面的宏定义。 +#undef HISTOGRAM_KERNEL_MAIN_PHASEx16 +#undef HISTOGRAM_KERNEL_MAIN_PHASEx8 +#undef HISTOGRAM_KERNEL_MAIN_PHASEx4 +#undef HISTOGRAM_KERNEL_MAIN_PHASEx2 +#undef HISTOGRAM_KERNEL_MAIN_PHASE + + } while (0); + + // 使用原子操作把临时变量 curnum 的结果加到共享内存中,可以防止多个 + // 线程同时更改数据而发生的写错误。 + if (curnum != 0) + atomicAdd(&temp[curgray], curnum); + + // 块内同步。此处保证图像中所有点的像素值都被统计过。 + __syncthreads(); + + // 用每一个块内前 256 个线程,将共享内存 temp 中的结果保存到输出数组中。 + if (inindex < 256) + atomicAdd(&devhist[inindex], temp[inindex]); +} + +// Host 成员方法:histogram(计算图像直方图) +__host__ int Histogram::histogram(Image *inimg, + unsigned int *histogram, bool onhostarray) +{ + // 检查图像是否为 NULL。 + if (inimg == NULL || histogram == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + int height = (insubimgCud.imgMeta.height + + HISTOGRAM_PACK_MASK) / HISTOGRAM_PACK_NUM; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 判断当前 histogram 数组是否存储在 Host 端。若是,则需要在 Device 端为直 + // 方图申请一段空间;若该数组是在 Device端,则直接调用核函数。 + if (onhostarray){ + // 在 Device 上分配存储临时直方图的空间。 + cudaError_t cudaerrcode; + unsigned int *devhisto; + cudaerrcode = cudaMalloc((void**)&devhisto, + 256 * sizeof (unsigned int)); + if (cudaerrcode != cudaSuccess) { + return cudaerrcode; + } + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(devhisto, 0, 256 * sizeof (unsigned int)); + if (cudaerrcode != cudaSuccess) { + cudaFree(devhisto); + return cudaerrcode; + } + + // 调用核函数,计算输入图像的直方图。 + _histogramKer<<>>(insubimgCud, devhisto); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(devhisto); + return CUDA_ERROR; + } + + // 将直方图的结果拷回 Host 端内存中。 + cudaerrcode = cudaMemcpy( + histogram, devhisto, 256 * sizeof (unsigned int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + cudaFree(devhisto); + return cudaerrcode; + } + + // 释放 Device 端的直方图存储空间。 + cudaFree(devhisto); + + // 如果 histogram 在 Device 端,直接调用核函数。 + } else { + _histogramKer<<>>(insubimgCud, histogram); + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + } + + return NO_ERROR; +} + diff --git a/okano_3_0/Histogram.h b/okano_3_0/Histogram.h new file mode 100644 index 0000000..4897449 --- /dev/null +++ b/okano_3_0/Histogram.h @@ -0,0 +1,55 @@ +// Histogram.h +// 创建人:侯怡婷 +// +// 图像直方图(Histogram) +// 功能说明:实现计算图像的直方图。输出的结果保存在int型的数组中,数组的下标代表 +// 灰度图像的灰度值,数组中存储的为等于该灰度值的像素点总数。 +// +// 修订历史: +// 2012年08月15日(侯怡婷) +// 初始版本。 +// 2012年10月25日(侯怡婷) +// 更正了代码的一些注释规范。 +// 2012年11月15日(侯怡婷) +// 在核函数执行后添加 cudaGetLastError 判断语句。 +// 2012年11月23日(侯怡婷) +// 增加对输入指针 histogram 有效性判断。 +// 2013年06月26日(于玉龙) +// 修正了单线程处理多个点的一处 Bug。 + +#ifndef __HISTOGRAM_H__ +#define __HISTOGRAM_H__ + +#include "Image.h" + + +// 类:Histogram(图像直方图) +// 继承自:无 +// 功能说明:实现计算图像的直方图。输出的结果保存在int型的数组中,数组的下标代表 +// 灰度图像的灰度值,数组中存储的为等于该灰度值的像素点总数。 +class Histogram { + +public: + + // 构造函数:Histogram + // 无参数版本的构造函数,因为该类没有成员变量,所以该构造函数为空。 + // 没有需要设置默认值的成员变量。 + __host__ __device__ + Histogram() {} + + // Host 成员方法:histogram(图像直方图) + // 实现计算图像的直方图。输出的结果保存在 int 型的数组中,数组的下标代表 + // 灰度图像的灰度值,数组中存储的为等于该灰度值的像素点总数。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 + // NO_ERROR。 + histogram( + Image *inimg, // 输入图像 + unsigned int *histogram, // 输出参数,直方图。 + bool onhostarray = true // 判断 histogram 是否是 Host 内存的指针, + // 默认为“是”。 + ); +}; + +#endif + diff --git a/okano_3_0/HistogramDifference.cu b/okano_3_0/HistogramDifference.cu new file mode 100644 index 0000000..96a1925 --- /dev/null +++ b/okano_3_0/HistogramDifference.cu @@ -0,0 +1,305 @@ +// HistogramDifference.cu +// 直方图差异 + +#include "HistogramDifference.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:IN_LABEL 和 OUT_LABEL +// 定义了曲线内的点和曲线外的点标记值 +#define IN_LABEL 255 +#define OUT_LABEL 0 + +// Kernel 函数:_labelCloseAreaKer(标记封闭区域) +// 在 HistogramDifference 算法中,需要对曲线封闭的区域统计直方图, +// 因此需要首先确定图像中每个点是否位于曲线封闭的区域当中,该核函数 +// 使用著名的射线法确定点和一个封闭曲线的位置关系,即如果由当前点 +// 引射线,与曲线有奇数个交点则在内部,如果有偶数个交点,则在曲线 +// 外部( 0 属于偶数),为了提高准确度,在实际实现的时候,我们使用了 +// 由当前点向上下左右四个方向引射线来确定位置关系。 +__global__ void // Kernel 函数无返回值 +_labelCloseAreaKer( + CurveCuda incurve, // 输入曲线 + ImageCuda inimg, // 输入图像 + ImageCuda outlabel // 输出标记结果 +); + +// Kernel 函数:_closeAreaHistKer(统计封闭区域的直方图) +// 该核函数,根据输入图像和输入图像的标记图像,统计由封闭曲线划分 +// 闭合区域的直方图。在块内使用共享内存,末尾利用原子加操作将结果累加 +// 到全局内存。 +__global__ void // Kernel 函数无返回值 +_closeAreaHistKer( + ImageCuda inimg, // 输入图像 + ImageCuda labelimg, // 标记图像 + int *histogram // 直方图 +); + + +// Kernel 函数:_labelCloseAreaKer(标记封闭区域) +__global__ void _labelCloseAreaKer(CurveCuda incurve,ImageCuda outlabelimg) +{ + // 计算当前线程的索引 + int xidx = blockIdx.x * blockDim.x + threadIdx.x; + int yidx = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断当前线程是否越过输入图像尺寸 + if (xidx >= outlabelimg.imgMeta.width || yidx >= outlabelimg.imgMeta.height) + return; + + // 定义部分寄存器变量 + int downcount = 0; // 向下引射线和曲线的交点个数 + int length = incurve.crvMeta.curveLength; // 曲线上的点的个数 + int outpitch = outlabelimg.pitchBytes; // 输出标记图像的 pitch + + // 首先将所有点标记为曲线外的点 + outlabelimg.imgMeta.imgData[yidx * outpitch+ xidx] = OUT_LABEL; + + int flag = 0; // 判断是否进入切线区域 + + // 遍历曲线,统计上述各个寄存器变量的值 + for (int i = 0; i < length; i++) { + int x = incurve.crvMeta.crvData[2 * i]; + int y = incurve.crvMeta.crvData[2 * i + 1]; + + // 曲线中的下一个点的位置 + int j = (i + 1) % length; + int x2 = incurve.crvMeta.crvData[2 * j]; + + // 曲线中上一个点的位置 + int k = (i - 1 + length) % length; + int x3 = incurve.crvMeta.crvData[2 * k]; + + // 曲线上的第 i 个点与当前点在同一列上 + if (x == xidx) { + if (y == yidx) { + // 当前点在曲线上,此处把曲线上的点也作为曲线内部的点 + outlabelimg.imgMeta.imgData[yidx * outpitch+ xidx] = IN_LABEL; + return; + } + + // 交点在当前点的下方 + if (y > yidx) { + // 曲线上下一个也在射线上时,避免重复统计,同时设置 flag + // 标记交点行开始。如果下一个点不在射线上,通过 flag 判断到 + // 底是交点行结束还是单点相交,如果是单点相交判断是否为突出点 + // 如果是交点行结束判断是否曲线在交点行同侧,以上都不是统计值 + // 加一. + if (x2 == xidx) { + if (flag == 0) + flag = x3 - x; + } else { + if (flag == 0) { + if ((x3 - x) * (x2 - x) <= 0) + downcount++; + } else { + if (flag * (x2 - x) < 0) + downcount++; + flag = 0; + } + } + } + } + } + + // 交点数均为奇数则判定在曲线内部 + if (downcount % 2 == 1) { + outlabelimg.imgMeta.imgData[yidx * outpitch + xidx] = IN_LABEL; + } +} + +// Kernel 函数:_closeAreaHistKer(统计封闭区域的直方图) +__global__ void _closeAreaHistKer(ImageCuda inimg, ImageCuda labelimg, + int *histogram) +{ + // 计算当前线程的索引 + int xidx = blockIdx.x * blockDim.x + threadIdx.x; + int yidx = blockIdx.y * blockDim.y + threadIdx.y; + + // 定义寄存器变量 + int imgpitch = inimg.pitchBytes; + int labelpitch = labelimg.pitchBytes; + + // 判断当前线程是否越过输入图像尺寸 + if (xidx >= labelimg.imgMeta.width || yidx >= labelimg.imgMeta.height) + return; + + // 如果当前点在闭合区域外,直接返回 + if (labelimg.imgMeta.imgData[yidx * labelpitch + xidx] == OUT_LABEL) + return; + + // 申请大小为灰度图像灰度级 256 的共享内存,其中下标代表图像的灰度值,数 + // 组用来累加等于该灰度值的像素点个数。 + __shared__ unsigned int temp[256]; + + // 计算该线程在块内的相对位置。 + int inindex = threadIdx.y * blockDim.x + threadIdx.x; + + // 若线程在块内的相对位置小于 256,即灰度级大小,则用来给共享内存赋初值 0。 + if (inindex < 256) + temp[inindex] = 0; + + // 进行块内同步,保证执行到此处,共享内存的数组中所有元素的值都为 0。 + __syncthreads(); + + int curgray = inimg.imgMeta.imgData[yidx * imgpitch + xidx]; + atomicAdd(&temp[curgray], 1); + + // 块内同步。此处保证图像中所有点的像素值都被统计过。 + __syncthreads(); + + // 用每一个块内前 256 个线程,将共享内存 temp 中的结果保存到输出数组中。 + if (inindex < 256) + atomicAdd(&histogram[inindex], temp[inindex]); + +} + +// 宏:FREE_LOCAL_MEMORY_HIST_DIFF(清理局部申请的设备端或者主机端内存) +// 该宏用于清理在 histogramDiff 过程中申请的设备端或者主机端内存空间。 +#define FREE_LOCAL_MEMORY_HIST_DIFF do { \ + if ((labelimg) != NULL) \ + ImageBasicOp::deleteImage(labelimg); \ + if ((histogramhost) != NULL) \ + delete []histogramhost; \ + if ((histogramdevice) != NULL) \ + cudaFree(histogramdevice); \ + } while (0) + +// 成员函数:histogramDiff (直方图差异) +__host__ int HistogramDifference::histogramDiff(Curve *incurve, Image *inimg, + float *referhistogram, + float &chisquarehd, + float &intersecthd) +{ + // 检查输入指针是否为 NULL + if (incurve == NULL || inimg == NULL || referhistogram == NULL) + return NULL_POINTER; + + // 检查输入参数是否有数据 + if (incurve->curveLength <= 0 || inimg -> width <= 0 || inimg->height <= 0) + return INVALID_DATA; + + // 检查输入曲线是否为封闭曲线,如果不是封闭曲线返回错误 + if (!incurve->closed) + return INVALID_DATA; + + int *histogramhost = NULL; // host 端直方图指针 + int *histogramdevice; // device 端直方图指针 + int errcode; // 局部变量,错误码 + + // 将曲线拷贝到 Device 内存中 + errcode = CurveBasicOp::copyToCurrentDevice(incurve); + + if (errcode != NO_ERROR) + return errcode; + + // 获取 CurveCuda 指针 + CurveCuda *incurvecud = CURVE_CUDA(incurve); + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgcud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgcud); + if (errcode != NO_ERROR) + return errcode; + + // 获取输入图像的尺寸 + int width = inimg -> width; + int height = inimg -> height; + + // 创建标记图像 + Image *labelimg; + ImageBasicOp::newImage(&labelimg); + ImageBasicOp::makeAtHost(labelimg, width, height); + + // 将标记图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(labelimg); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_HIST_DIFF; + return errcode; + } + + // 提取标记图像的 ROI 子图像。 + ImageCuda labelsubimgcud; + errcode = ImageBasicOp::roiSubImage(labelimg, &labelsubimgcud); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_HIST_DIFF; + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 调用核函数,标记曲线内部和曲线外部的点 + _labelCloseAreaKer<<>>( + *incurvecud, labelsubimgcud); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + FREE_LOCAL_MEMORY_HIST_DIFF; + return CUDA_ERROR; + } + + // 申请 host 端直方图存储空间 + histogramhost = new int [256]; + + errcode = cudaMalloc((void**)&histogramdevice, + 256 * sizeof (unsigned int)); + if (errcode != cudaSuccess) { + FREE_LOCAL_MEMORY_HIST_DIFF; + return CUDA_ERROR; + } + + // 初始化 Device 上的内存空间。 + errcode = cudaMemset(histogramdevice, 0, 256 * sizeof (int)); + if (errcode != cudaSuccess) { + FREE_LOCAL_MEMORY_HIST_DIFF; + return CUDA_ERROR; + } + + // 调用核函数统计闭合区域内直方图 + _closeAreaHistKer<<>>(insubimgcud,labelsubimgcud, + histogramdevice); + + // 拷贝结果到 Host + errcode = cudaMemcpy(histogramhost, histogramdevice, + 256 * sizeof (int), cudaMemcpyDeviceToHost); + + if (errcode != cudaSuccess) { + FREE_LOCAL_MEMORY_HIST_DIFF; + return CUDA_ERROR; + } + + // 定义 C(H1,H2) 和 I(H1,H2) 差异的统计量 + float csum = 0.0f; + float isum = 0.0f; + + // 计算两个差异统计量 + for (int i = 0; i < 256 ; i++) { + csum += (referhistogram[i] - histogramhost[i]) * + (referhistogram[i] - histogramhost[i]) / + referhistogram[i]; + isum += (referhistogram[i] < histogramhost[i]) ? + referhistogram[i] : histogramhost[i]; + } + + // 将结果赋值给引用参数 + chisquarehd = csum; + intersecthd = isum; + + // 释放空间 + FREE_LOCAL_MEMORY_HIST_DIFF; + + return NO_ERROR; +} \ No newline at end of file diff --git a/okano_3_0/HistogramDifference.h b/okano_3_0/HistogramDifference.h new file mode 100644 index 0000000..32b8c7b --- /dev/null +++ b/okano_3_0/HistogramDifference.h @@ -0,0 +1,44 @@ +// HistogramDifference.h +// 创建人:邱孝兵 +// +// 直方图差异(HistogramDifference) +// 功能说明:根据一个闭合的输入曲线,确定图像上在闭合曲线 +// 内部的点,统计这些点的直方图,和输入的标准直方图之间计算差异。 +// +// 修订历史: +// 2013年10月08日(邱孝兵) +// 初始版本 +// 2013年11月08日(邱孝兵) +// 修复切线上的点造成的 bug + +#ifndef __HISTOGRAMDIFFERENCE_H__ +#define __HISTOGRAMDIFFERENCE_H__ + +#include "ErrorCode.h" +#include "Curve.h" +#include "Image.h" + +// 类:HistogramDifference +// 继承自:无 +// 根据一个闭合的输入曲线,确定图像上在闭合曲线 +// 内部的点,统计这些点的直方图,和输入的标准直方图之间计算差异。 +class HistogramDifference { + +public: + + // Host 成员方法:histogramDiff(直方图差异) + // 根据一个闭合的输入曲线,确定图像上在闭合曲线 + // 内部的点,统计这些点的直方图,和输入的标准直方图之间计算差异。 + __host__ int // 返回值:函数是否正确执行,若正确 + // 执行,返回 NO_ERROR + histogramDiff( + Curve *incurve, // 输入闭合曲线 + Image *inimg, // 输入图像 + float *referhistogram, // 参考直方图 + float &chisquarehd, // 卡方直方图差异 + float &intersecthd // 交叉直方图差异 + ); +}; + +#endif + \ No newline at end of file diff --git a/okano_3_0/HistogramSpec.cu b/okano_3_0/HistogramSpec.cu new file mode 100644 index 0000000..79aab3f --- /dev/null +++ b/okano_3_0/HistogramSpec.cu @@ -0,0 +1,975 @@ +// HistogramSpec.cu +// 实现直方图规定化算法 + +#include "HistogramSpec.h" +#include "Histogram.h" + +#include +using namespace std; + +#include "ErrorCode.h" + + +// 宏:HISTOGRAM_LEVEL +// 输入图像直方图的灰度级,默认为 256。 +#ifndef HISTOGRAM_LEVEL +#define HISTOGRAM_LEVEL 256 +#endif + +// 宏:HISTSPEC_LEVEL +// 规定化后输出图像的灰度级,默认为 256。 +#ifndef HISTSPEC_LEVEL +#define HISTSPEC_LEVEL 256 +#endif + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// Kernel 函数: _calDiffMatrixKer(根据原始和目标累积直方图建立差值矩阵) +// 根据输入图像的原始累积直方图,以及目标累积直方图计算差值矩阵 +// diffmatrix,并且初始化映射矩阵 maptable,将其各元素其初始值置为 +// HISTSPEC_LEVEL. +static __global__ void // Kernel 函数无返回值。 +_calDiffMatrixKer( + float *diffmatrix, // 差值矩阵。 + float *cumhist, // 原始累积直方图。 + float *cumhistequ, // 目标累积直方图。 + unsigned int *maptable // 映射矩阵。 +); + +// Kernel 函数: _findColumnMinKer(查找列最小值) +// 根据差值矩阵 diffmatrix,查找每一列的最小值,并将每一列出现最小 +// 值的行号保存在数组 colmin 中。 +static __global__ void // Kernel 函数无返回值。 +_findColumnMinKer( + float *diffmatrix, // 差值矩阵。 + unsigned int *colmin // 列最小值矩阵。 +); + +// Kernel 函数: _findRowMinKer(查找行最小值) +// 根据差值矩阵 diffmatrix,查找每一行的最小值,并将每一行出现最小 +// 值的行列号保存在数组 rowmin 中。 +static __global__ void // Kernel 函数无返回值。 +_findRowMinKer( + float *diffmatrix, // 差值矩阵。 + unsigned int *rowmin // 行最小值矩阵。 +); + +// Kernel 函数: _groupMappingLawKer(计算灰度级之间的映射矩阵) +// 根据组映射规则,通过行、列最小值矩阵,计算原始图像和目标图像灰度级 +// 之间的映射关系。 +static __global__ void // Kernel 函数无返回值。 +_groupMappingLawKer( + unsigned int *rowmin, // 行最小值矩阵。 + unsigned int *colmin, // 列最小值矩阵。 + unsigned int *maptable // 灰度级之间的映射矩阵。 +); + +// Kernel 函数: _maptableJudgeKer(整理灰度级之间的映射矩阵) +// 根据原始累积直方图 devcumhist,以及向后匹配原则,整理灰度级之间的 +// 映射矩阵。 +static __global__ void // Kernel 函数无返回值。 +_maptableJudgeKer( + unsigned int *maptable, // 灰度级之间的映射矩阵。 + float *devcumhist // 原始累积直方图。 +); + +// Kernel 函数: _mapToOutimgKer(计算输出图像) +// 对于每个像素点,查找原始灰度级,并根据灰度级映射矩阵 maptable,得 +// 到变化后灰度级,从而极端得到输出图像。 +static __global__ void // Kernel 函数无返回值。 +_mapToOutimgKer( + ImageCuda inimg, // 输入图像。 + ImageCuda outimg, // 输出图像。 + unsigned int *maptable // 灰度级之间的映射矩阵。 +); + +// Kernel 函数: _calDiffMatrixKer(根据原始和目标累积直方图建立差值矩阵) +static __global__ void _calDiffMatrixKer( + float *diffmatrix, float *cumhist, float *cumhistequ, + unsigned int *maptable) +{ + // 申请大小为目标直方图灰度级 HISTSPEC_LEVEL 的共享内存。 + __shared__ float shared_cumhistequ[HISTSPEC_LEVEL]; + + // 获取当前线程的块号。 + int blocktid = blockIdx.x; + // 获取当前线程的线程号。 + int threadtid = threadIdx.x; + // 计算差值矩阵中对应的输出点的位置。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + // 申请局部变量,用于临时存储当前线程计算得到的差值。 + float temp = 0.0; + + // 初始化灰度级匹配矩阵。 + maptable[blocktid] = HISTSPEC_LEVEL; + + // 将目标累积直方图对应的存储在共享内存中,方便同一块内的线程共享,从而 + // 提高读取速度。 + shared_cumhistequ[threadtid] = cumhistequ[threadtid]; + // 计算差值。 + temp = shared_cumhistequ[threadtid] - cumhist[blocktid]; + // 进行块内同步。 + __syncthreads(); + + // 将计算所得的差值写入差值矩阵相应位置上,若临时变量 temp 大于或者等 + // 于 0,直接赋值;若 temp 小于 0,则取其相反数之后再赋值。 + *(diffmatrix + index) = (temp >= 0.0 ? temp : -temp); +} + +// Kernel 函数: _findColumnMinKer(查找列最小值) +static __global__ void _findColumnMinKer( + float *diffmatrix, unsigned int *colmin) +{ + // 获取当前线程的块号,即对应差值矩阵中当前的列号。 + int blocktid = blockIdx.x; + // 获取当前线程的线程号,即对应差值矩阵中当前的行号。 + int threadtid = threadIdx.x; + // 计算当前线程在差值矩阵中的偏移。 + int tid = threadIdx.x * gridDim.x + blockIdx.x; + int k; + float tempfloat; + unsigned int tempunint; + + // 申请一个大小等于原始直方图灰度级的 float 型共享内存,用于存储每 + // 一列中待比较的差值。 + __shared__ float shared[HISTOGRAM_LEVEL]; + // 申请一个大小等于原始直方图灰度级的 unsigned int 型共享内存,用于 + // 存储待比较差值对应的行号。 + __shared__ unsigned int index[HISTOGRAM_LEVEL]; + + // 将当前线程对应的差值矩阵中的差值以及其对应的索引(即行号)保存 + // 在该块的共享内存中。 + shared[threadtid] = *(diffmatrix + tid); + index[threadtid] = threadtid; + // 块内同步,为了保证一个块内的所有线程都已经完成了上述操作,即存 + // 储该列的差值以及索引到共享内存中。 + __syncthreads(); + + // 使用双调排序的思想,找到该列的最小值。 + for (k = 1; k < HISTOGRAM_LEVEL; k = k << 1) { + // 对待排序的元素进行分组,每次都将差值较小的元素交换到数组中 + // 较前的位置,然后改变分组大小,进而在比较上一次得到的较小值 + // 并做相应的交换,以此类推,最终数组中第 0 号元素存放的是该列 + // 的最小值。 + if (((threadtid % (k << 1)) == 0) && + shared[threadtid] > shared[threadtid + k] ) { + // 两个差值进行交换。 + tempfloat = shared[threadtid]; + shared[threadtid] = shared[threadtid + k]; + shared[threadtid + k] = tempfloat; + + // 交换相对应的索引 index 值。 + tempunint = index[threadtid]; + index[threadtid] = index[threadtid + k]; + index[threadtid + k] = tempunint; + } + // 块内同步 + __syncthreads(); + } + + // 将当前列最小值出现的行号保存在数组 colmin 中。 + colmin[blocktid] = index[0]; +} + +// Kernel 函数: _findRowMinKer(查找行最小值) +static __global__ void _findRowMinKer( + float *diffmatrix, unsigned int *rowmin) +{ + // 获取当前线程的块号。 + int blocktid = blockIdx.x; + // 获取当前线程的线程号。 + int threadtid = threadIdx.x; + // 计算当前线程在差值矩阵中的偏移。 + int tid = blockIdx.x * blockDim.x + threadIdx.x; + int k; + float tempfloat; + unsigned int tempunint; + + // 申请一个大小等于原始直方图灰度级的 float 型共享内存,用于存储每 + // 一行中待比较的差值。 + __shared__ float shared[HISTSPEC_LEVEL]; + // 申请一个大小等于原始直方图灰度级的 unsigned int 型共享内存,用于 + // 存储待比较差值对应的列号。 + __shared__ unsigned int index[HISTSPEC_LEVEL]; + + // 将当前线程对应的差值矩阵中的差值以及其对应的索引(即列号)保存 + // 在该块的共享内存中。 + shared[threadtid] = *(diffmatrix + tid); + index[threadtid] = threadtid; + // 块内同步,为了保证一个块内的所有线程都已经完成了上述操作,即存 + // 储该行的差值以及索引到共享内存中。 + __syncthreads(); + + // 使用双调排序的思想,找到该行的最小值。 + for (k = 1; k < HISTSPEC_LEVEL; k = k << 1) { + // 对待排序的元素进行分组,每次都将差值较小的元素交换到数组中 + // 较前的位置,然后改变分组大小,进而在比较上一次得到的较小值 + // 并做相应的交换,以此类推,最终数组中第 0 号元素存放的是该行 + // 的最小值。 + if (((threadtid % (k << 1)) == 0) && + shared[threadtid] > shared[threadtid + k] ) { + // 两个差值进行交换。 + tempfloat = shared[threadtid]; + shared[threadtid] = shared[threadtid + k]; + shared[threadtid + k] = tempfloat; + + // 交换相对应的索引index值。 + tempunint = index[threadtid]; + index[threadtid] = index[threadtid + k]; + index[threadtid + k] = tempunint; + } + // 块内同步。 + __syncthreads(); + } + + // 将当前行最小值出现的列号保存在数组 rowmin 中。 + rowmin[blocktid] = index[0]; +} + +// Kernel 函数: _groupMappingLawKer(计算灰度级之间的映射矩阵) +static __global__ void _groupMappingLawKer( + unsigned int *rowmin, unsigned int *colmin, + unsigned int *maptable) +{ + // 获取当前的线程号。 + int tid = threadIdx.x; + + // 通过行列最小值的关系,计算 group mapping law(GML)映射关系。 + // 可得到初始的不完整的映射表。 + maptable[colmin[tid]] = rowmin[colmin[tid]]; +} + +// Kernel 函数: _maptableJudgeKer(整理灰度级之间的映射矩阵) +static __global__ void _maptableJudgeKer( + unsigned int *maptable, float *devcumhist) +{ + // 获取当前的线程号。 + int tid = threadIdx.x; + int temp, i = tid; + + // 通过向高灰度匹配的原则,整理灰度级映射关系表。 + while (devcumhist[tid] >= 0) + { + // 暂存映射表中的值。 + temp = maptable[i]; + + // 判断如果当前映射表中的值是无效值,则向后进行匹配,直到 + // 符合灰度级要求。 + if (temp == HISTSPEC_LEVEL) { + i++; + } else { + // 更新灰度级映射表中映射关系。 + maptable[tid] = temp; + break; + } + } +} + +// Kernel 函数: _mapToOutimgKer(计算输出图像) +static __global__ void _mapToOutimgKer( + ImageCuda inimg, ImageCuda outimg, unsigned int *maptable) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素点. + // 通过灰度级匹配矩阵,得到输入图像当前点所对应的变换后的灰度值,并赋值 + // 给输出图像的对应位置。 + // 线程中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = maptable[intemp]; + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 通过灰度级匹配矩阵,得到输入图像当前点所对应的变换后的灰度值,并赋值 + // 给输出图像的对应位置。 + outimg.imgMeta.imgData[outidx] = maptable[intemp]; + } +} + +// Host 成员方法:HistogramEquilibrium(直方图均衡化) +__host__ int HistogramSpec::HistogramEquilibrium(Image *inimg, Image *outimg) +{ + // 检查输入和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 调用类 Histogram 中的方法,计算输入图像的直方图。 + Histogram hist; + unsigned int histogram[HISTOGRAM_LEVEL] = {0}; + hist.histogram(inimg, histogram, true); + + // 计算原始直方图的累积直方图。 + float cumsum = 0; + float cumhist[HISTOGRAM_LEVEL] = {0}; + for (int i = 0; i < HISTOGRAM_LEVEL; i++) { + cumsum += histogram[i]; + cumhist[i] = (float)cumsum / (inimg->height * inimg->width); + } + + // 计算均衡化后的累积直方图,均衡化之后每个灰度值的概率相等。 + float cumhistequ[HISTSPEC_LEVEL]={0}; + for (int j = 0; j < HISTSPEC_LEVEL; j++) { + cumhistequ[j] = (float)(j+1) / HISTSPEC_LEVEL; + } + + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + // 包括原始累积直方图 devcumhist,均衡化后累积直方图 devcumhistequ,行最 + // 小值矩阵 devrowmin,列最小值矩阵 colmin,映射矩阵 devmaptable,差值矩 + // 阵 devdiffmatrix。 + cudaError_t cudaerrcode; + float *alldevicepointer; + float *devcumhist, *devcumhistequ, *devdiffmatrix; + unsigned int *devcolmin, *devrowmin, *devmaptable; + cudaerrcode = cudaMalloc( + (void **)&alldevicepointer, + (3 * HISTOGRAM_LEVEL + 2 * HISTOGRAM_LEVEL + + HISTOGRAM_LEVEL * HISTOGRAM_LEVEL) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + return cudaerrcode; + } + + // 初始化所有 Device 上的内存空间。 + cudaerrcode = cudaMemset( + alldevicepointer, 0, + (3 * HISTOGRAM_LEVEL + 2 * HISTOGRAM_LEVEL + + HISTOGRAM_LEVEL * HISTOGRAM_LEVEL) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取 devcumhist 内存空间。 + devcumhist = alldevicepointer; + // 将 Host 端计算的累积直方图 cumhist 拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(devcumhist, cumhist, + HISTOGRAM_LEVEL * sizeof (float), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取 devcumhistequ 内存空间。 + devcumhistequ = alldevicepointer + HISTOGRAM_LEVEL; + // 将 Host 端计算的累积直方图 cumhistequ 拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(devcumhistequ, cumhistequ, + HISTSPEC_LEVEL * sizeof (float), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取差值矩阵 devdiffmatrix 内存空间。 + devdiffmatrix = alldevicepointer + 3 * HISTOGRAM_LEVEL + + 2 * HISTOGRAM_LEVEL; + + // 通过偏移读取映射矩阵 devmaptable 内存空间,并将转换指针类型。 + devmaptable = (unsigned int *)(alldevicepointer + + 2 * (HISTOGRAM_LEVEL + HISTSPEC_LEVEL)); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + gridsize.x = HISTOGRAM_LEVEL; + gridsize.y = 1; + blocksize.x = HISTSPEC_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算差值矩阵。 + _calDiffMatrixKer<<>>( + devdiffmatrix, devcumhist, devcumhistequ, devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 通过偏移读取映射矩阵 devrowmin 内存空间,并将转换指针类型。 + devrowmin = (unsigned int *)(alldevicepointer + + HISTOGRAM_LEVEL + HISTSPEC_LEVEL); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + gridsize.x = HISTOGRAM_LEVEL; + gridsize.y = 1; + blocksize.x = HISTSPEC_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算行最小值。 + _findRowMinKer<<>>( + devdiffmatrix, (unsigned int *)devrowmin); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 通过偏移读取映射矩阵 devcolmin 内存空间,并将转换指针类型。 + devcolmin = (unsigned int *)(alldevicepointer + 2 * HISTOGRAM_LEVEL + + HISTSPEC_LEVEL); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + gridsize.x = HISTSPEC_LEVEL; + gridsize.y = 1; + blocksize.x = HISTOGRAM_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算列最小值。 + _findColumnMinKer<<>>( + devdiffmatrix, (unsigned int *)devcolmin); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算灰度级之间映射关系。 + _groupMappingLawKer<<<1, HISTSPEC_LEVEL>>>( + (unsigned int *)devrowmin, (unsigned int *)devcolmin, devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 整理映射关系 + _maptableJudgeKer<<<1, HISTOGRAM_LEVEL>>>( + (unsigned int *)devmaptable, devcumhist); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 通过映射矩阵,得到输出图像。 + _mapToOutimgKer<<>>( + insubimgCud, outsubimgCud,(unsigned int *)devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 释放 Device 上的临时空间 alldevicedata。 + cudaFree(alldevicepointer); + return NO_ERROR; +} + +// Host 成员方法:HistogramSpecByImage(根据参考图像进行规定化) +__host__ int HistogramSpec::HistogramSpecByImage(Image *inimg, Image *outimg) +{ + // 检查输入图像,参考图像和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL || this->refimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 调用类 Histogram 中的方法,计算输入图像的直方图。 + Histogram hist; + unsigned int histogram[HISTOGRAM_LEVEL] = {0}; + hist.histogram(inimg, histogram, true); + + // 调用类 Histogram 中的方法,计算参考图像的直方图。 + unsigned int histspec[HISTSPEC_LEVEL] = {0}; + hist.histogram(this->refimg, histspec, true); + + // 计算原始直方图的累积直方图。 + unsigned int cumsum = 0; + float cumhist[HISTOGRAM_LEVEL] = {0.0}; + for (int i = 0; i < HISTOGRAM_LEVEL; i++) { + cumsum += histogram[i]; + cumhist[i] = (float)cumsum / (inimg->height * inimg->width); + } + + // 计算参考图像的累积直方图,均衡化之后每个灰度值的概率相等。 + float cumhistspec[HISTSPEC_LEVEL]={0.0}; + cumsum = 0; + for (int i = 0; i < HISTSPEC_LEVEL; i++) { + cumsum += histspec[i]; + cumhistspec[i] = (float)cumsum / (this->refimg->width + * this->refimg->height); + } + + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + // 包括原始累积直方图 devcumhist,均衡化后累积直方图 devcumhistequ,行最 + // 小值矩阵 devrowmin,列最小值矩阵 colmin,映射矩阵 devmaptable,差值矩 + // 阵 devdiffmatrix。 + cudaError_t cudaerrcode; + float *alldevicepointer; + float *devcumhist, *devcumhistspec, *devdiffmatrix; + unsigned int *devcolmin, *devrowmin, *devmaptable; + cudaerrcode = cudaMalloc( + (void **)&alldevicepointer, + (3 * HISTOGRAM_LEVEL + 2 * HISTOGRAM_LEVEL + + HISTOGRAM_LEVEL * HISTOGRAM_LEVEL) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 初始化所有 Device 上的内存空间。 + cudaerrcode = cudaMemset( + alldevicepointer, 0, + (3 * HISTOGRAM_LEVEL + 2 * HISTOGRAM_LEVEL + + HISTOGRAM_LEVEL * HISTOGRAM_LEVEL) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取 devcumhist 内存空间。 + devcumhist = alldevicepointer; + // 将 Host 端计算的累积直方图 cumhist 拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(devcumhist, cumhist, + HISTOGRAM_LEVEL * sizeof (float), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取 devcumhistequ 内存空间。 + devcumhistspec = alldevicepointer + HISTOGRAM_LEVEL; + // 将 Host 端计算的累积直方图 cumhistspec 拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(devcumhistspec, cumhistspec, + HISTSPEC_LEVEL * sizeof (float), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取差值矩阵 devdiffmatrix 内存空间。 + devdiffmatrix = alldevicepointer + 3 * HISTOGRAM_LEVEL + + 2 * HISTOGRAM_LEVEL; + + // 通过偏移读取映射矩阵 devmaptable 内存空间,并将转换指针类型。 + devmaptable = (unsigned int *)(alldevicepointer + + 2 * (HISTOGRAM_LEVEL + HISTSPEC_LEVEL)); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + gridsize.x = HISTOGRAM_LEVEL; + gridsize.y = 1; + blocksize.x = HISTSPEC_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算差值矩阵。 + _calDiffMatrixKer<<>>( + devdiffmatrix, devcumhist, devcumhistspec, devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 通过偏移读取映射矩阵 devrowmin 内存空间,并将转换指针类型。 + devrowmin = (unsigned int *)(alldevicepointer + + HISTOGRAM_LEVEL + HISTSPEC_LEVEL); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + gridsize.x = HISTOGRAM_LEVEL; + gridsize.y = 1; + blocksize.x = HISTSPEC_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算行最小值。 + _findRowMinKer<<>>( + devdiffmatrix, (unsigned int *)devrowmin); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 通过偏移读取映射矩阵 devcolmin 内存空间,并将转换指针类型。 + devcolmin = (unsigned int *)(alldevicepointer + 2 * HISTOGRAM_LEVEL + + HISTSPEC_LEVEL); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + gridsize.x = HISTSPEC_LEVEL; + gridsize.y = 1; + blocksize.x = HISTOGRAM_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算列最小值。 + _findColumnMinKer<<>>( + devdiffmatrix, (unsigned int *)devcolmin); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算灰度级之间映射关系。 + _groupMappingLawKer<<<1, HISTSPEC_LEVEL>>>( + (unsigned int *)devrowmin, (unsigned int *)devcolmin, devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 整理映射关系 + _maptableJudgeKer<<<1, HISTOGRAM_LEVEL>>>( + (unsigned int *)devmaptable, devcumhist); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 通过映射矩阵,得到输出图像。 + _mapToOutimgKer<<>>( + insubimgCud, outsubimgCud,(unsigned int *)devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 释放 Device 上的临时空间 alldevicedata。 + cudaFree(alldevicepointer); + return NO_ERROR; +} + +// Host 成员方法:HistogramSpecByHisto(根据参考直方图进行规定化) +__host__ int HistogramSpec::HistogramSpecByHisto(Image *inimg, Image *outimg) +{ + // 检查输入和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL || refHisto == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 调用类 Histogram 中的方法,计算输入图像的直方图。 + Histogram hist; + unsigned int histogram[HISTOGRAM_LEVEL] = {0}; + hist.histogram(inimg, histogram, true); + + // 计算原始直方图的累积直方图。 + unsigned int cumsum = 0; + float cumhist[HISTOGRAM_LEVEL] = {0}; + for (int i = 0; i < HISTOGRAM_LEVEL; i++) { + cumsum += histogram[i]; + cumhist[i] = (float)cumsum / (inimg->height * inimg->width); + } + + // 计算参考直方图的累积直方图。 + float cumhistspec[HISTSPEC_LEVEL] = {0}; + cumhistspec[0] = this->refHisto[0]; + for (int i = 1; i < HISTSPEC_LEVEL; i++) { + cumhistspec[i] = cumhistspec[i - 1] + this->refHisto[i]; + } + + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + // 包括原始累积直方图 devcumhist,均衡化后累积直方图 devcumhistequ,行最 + // 小值矩阵 devrowmin,列最小值矩阵 colmin,映射矩阵 devmaptable,差值矩 + // 阵 devdiffmatrix。 + cudaError_t cudaerrcode; + float *alldevicepointer; + float *devcumhist, *devcumhistspec, *devdiffmatrix; + unsigned int *devcolmin, *devrowmin, *devmaptable; + cudaerrcode = cudaMalloc( + (void **)&alldevicepointer, + (3 * HISTOGRAM_LEVEL + 2 * HISTOGRAM_LEVEL + + HISTOGRAM_LEVEL * HISTOGRAM_LEVEL) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 初始化所有 Device 上的内存空间。 + cudaerrcode = cudaMemset( + alldevicepointer, 0, + (3 * HISTOGRAM_LEVEL + 2 * HISTOGRAM_LEVEL + + HISTOGRAM_LEVEL * HISTOGRAM_LEVEL) * sizeof (float)); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取 devcumhist 内存空间。 + devcumhist = alldevicepointer; + // 将 Host 端计算的累积直方图 cumhist 拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(devcumhist, cumhist, + HISTOGRAM_LEVEL * sizeof (float), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取 devcumhistequ 内存空间。 + devcumhistspec = alldevicepointer + HISTOGRAM_LEVEL; + // 将 Host 端计算的参考直方图拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(devcumhistspec, cumhistspec, + HISTSPEC_LEVEL * sizeof (float), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicepointer); + return cudaerrcode; + } + + // 通过偏移读取差值矩阵 devdiffmatrix 内存空间。 + devdiffmatrix = alldevicepointer + 3 * HISTOGRAM_LEVEL + + 2 * HISTOGRAM_LEVEL; + + // 通过偏移读取映射矩阵 devmaptable 内存空间,并将转换指针类型。 + devmaptable = (unsigned int *)(alldevicepointer + + 2 * (HISTOGRAM_LEVEL + HISTSPEC_LEVEL)); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + gridsize.x = HISTOGRAM_LEVEL; + gridsize.y = 1; + blocksize.x = HISTSPEC_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算差值矩阵。 + _calDiffMatrixKer<<>>( + devdiffmatrix, devcumhist, devcumhistspec, devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 通过偏移读取映射矩阵 devrowmin 内存空间,并将转换指针类型。 + devrowmin = (unsigned int *)(alldevicepointer + + HISTOGRAM_LEVEL + HISTSPEC_LEVEL); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + gridsize.x = HISTOGRAM_LEVEL; + gridsize.y = 1; + blocksize.x = HISTSPEC_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算行最小值。 + _findRowMinKer<<>>( + devdiffmatrix, (unsigned int *)devrowmin); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 通过偏移读取映射矩阵 devcolmin 内存空间,并将转换指针类型。 + devcolmin = (unsigned int *)(alldevicepointer + 2 * HISTOGRAM_LEVEL + + HISTSPEC_LEVEL); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + gridsize.x = HISTSPEC_LEVEL; + gridsize.y = 1; + blocksize.x = HISTOGRAM_LEVEL; + blocksize.y = 1; + + // 调用核函数,计算列最小值。 + _findColumnMinKer<<>>( + devdiffmatrix, (unsigned int *)devcolmin); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算灰度级之间映射关系。 + _groupMappingLawKer<<<1, HISTSPEC_LEVEL>>>( + (unsigned int *)devrowmin, (unsigned int *)devcolmin, + devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 整理映射关系 + _maptableJudgeKer<<<1, HISTOGRAM_LEVEL>>>( + (unsigned int *)devmaptable, devcumhist); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 通过映射矩阵,得到输出图像。 + _mapToOutimgKer<<>>( + insubimgCud, outsubimgCud,(unsigned int *)devmaptable); + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicepointer); + return CUDA_ERROR; + } + + // 释放 Device 上的临时空间 alldevicedata。 + cudaFree(alldevicepointer); + return NO_ERROR; +} + diff --git a/okano_3_0/HistogramSpec.h b/okano_3_0/HistogramSpec.h new file mode 100644 index 0000000..bc4937d --- /dev/null +++ b/okano_3_0/HistogramSpec.h @@ -0,0 +1,162 @@ +// HistogramSpec.h +// 创建人:侯怡婷 +// +// 直方图规定化(HistogramSpec) +// 功能说明:实现输入图像的直方图规定化,对于输入图像,规定化之后不改变原图 +// 像尺寸大小。该算法有三种不同的实现:(1)直方图均衡化;(2)根 +// 据参考图像对输入图像进行规定化;(3)根据给定直方图对输入图像 +// 进行规定化。 +// +// 修订历史: +// 2012年09月01日(侯怡婷) +// 初始版本。 +// 2012年09月05日(刘瑶、侯怡婷) +// 更正了代码中对 ROI 处理的一些错误。 +// 2012年10月25日(侯怡婷) +// 更正了代码的一些注释规范。 +// 2012年11月15日(侯怡婷) +// 在核函数执行后添加 cudaGetLastError 判断语句。 +// 2012年11月23日(侯怡婷) +// 增加对类成员变量 RefHisto 有效性判断。 + +#ifndef __HISTOGRAMSPEC_H__ +#define __HSITOGRAMSPEC_H__ + +#include "Image.h" +#include "Histogram.h" +#include "ErrorCode.h" + + +// 类:HistogramSpec(直方图规定化类) +// 继承自:无 +// 实现输入图像的直方图规定化,对于输入图像,规定化之后不改变原图像尺寸大小。 +// 该算法有三种不同的实现:(1)直方图均衡化;(2)根据参考图像对输入图像进行 +// 规定化;(3)根据给定直方图对输入图像进行规定化。 +class HistogramSpec{ + +protected: + + // 成员变量:refimg(参考图像) + // 参考图像,根据参考图像的直方图对输入图像进行规定化。 + Image *refimg; + + // 成员变量:refhistogram(规定直方图) + // 规定直方图,根据该给定的直方图对输入图像进行规定化。 + float *refHisto; + +public: + + // 构造函数:HistogramSpec + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + HistogramSpec() + { + // 使用默认值为类的各个成员变量赋值。 + refimg = NULL; // 参考图像默认为空。 + refHisto = NULL; // 参考直方图数组默认为空。 + } + + // 构造函数:HistogramSpec + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + HistogramSpec( + Image *refimg, // 形状特征值数组 + float *refHisto // 特征数组中键值对的个数 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法的初 + // 始值而使系统进入一个未知的状态。 + this->refimg = NULL; // 参考图像默认为空。 + this->refHisto = NULL; // 参考直方图数组默认为空。 + + // 根据参数列表中的值设定成员变量的初值。 + setRefimg(refimg); + setRefHisto(refHisto); + } + + // 成员方法:getRefimg(读取参考图像) + // 读取 refimg 成员变量的值。 + __host__ __device__ Image * // 返回值:当前 refimg 成员变量的值。 + getRefimg() const + { + // 返回 refimg 成员变量的值。 + return this->refimg; + } + + // 成员方法:setRefimg(设置参考图像) + // 设置 refimg成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setRefimg( + Image *refimg // 指定的特征值数组。 + ) { + // 检查输入参数是否合法 + if (refimg == NULL) + return INVALID_DATA; + + // 将 refimg 成员变量赋成新值 + this->refimg = refimg; + + return NO_ERROR; + } + + // 成员方法:getRefHisto(读取给定的参考直方图) + // 读取 refHisto 成员变量的值。 + __host__ __device__ float * // 返回值:当前 refHisto 成员变量的值。 + getRefHisto() const + { + // 返回 refHisto 成员变量的值。 + return this->refHisto; + } + + // 成员方法:setRefHisto(设置特征值数组中键值对的个数) + // 设置 refHisto 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setRefHisto( + float *refHisto // 给定的参考直方图。 + ) { + // 检查输入参数是否合法 + if (refHisto == NULL) + return INVALID_DATA; + + // 将 refHisto 成员变量赋成新值 + this->refHisto = refHisto; + + return NO_ERROR; + } + + // Host 成员方法:HistogramEquilibrium(直方图均衡化) + // 根据组映射方法,将输入图像的直方图进行均衡化,得到输出图像,使得输出图像 + // 的直方图分布极大可能的接近等概率分布。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + HistogramEquilibrium( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); + + // Host 成员方法:HistogramSpecByImage(根据参考图像进行直方图规定化) + // 根据参考图像refimg的直方图分布情况,利用组映射方法,对输入图像inimg直方图 + // 进行规定化处理,使得最终输出图像outimg的直方图接近参考图像refimg的直方 + // 图。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + HistogramSpecByImage( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); + + // Host 成员方法:HistogramSpecByHisto(根据参考直方图进行规定化) + // 根据参考直方图refHisto,利用组映射方法,对输入图像inimg直方图进行规定化 + // 处理,使得最终输出图像outimg的直方图接近参考直方图refHisto。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + HistogramSpecByHisto( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); +}; + +#endif + diff --git a/okano_3_0/HoughCircle.cu b/okano_3_0/HoughCircle.cu new file mode 100644 index 0000000..1bdfece --- /dev/null +++ b/okano_3_0/HoughCircle.cu @@ -0,0 +1,1413 @@ + // HoughCircle.cu +// 实现 Hough 变换检测圆 + +#include "HoughCircle.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" +#include "CoordiSet.h" + +//#define DEBUG + +// 宏:HOUGH_INF_GREAT +// 定义了一个足够大的正整数,该整数在使用过程中被认为是无穷大。 +#define HOUGH_INF_GREAT ((1 << 30) - 1) + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_YI +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLOCK_1D +// 定义了默认的一维线程块的尺寸。 +#define DEF_BLOCK_1D 512 + +//----------------------------内核函数声明(10个)----------------------------- + + +// Kernel 函数:_houghcirImgKer(根据输入图像 inimg 计算得票数) +// 对输入图像的每一个有效像素点,寻找每一个该点可能在的圆,计算圆心以及半径, +// 统计得票数。 +static __global__ void // Kernel 函数无返回值 +_houghcirImgKer( + ImageCuda inimg, // 输入图像。 + int bufHoughDev[], // 得票数矩阵。 + int radiusMin, // 最小检测的圆半径。 + int bufwidth, // 得票数矩阵 bufHough 的宽度。 + int bufheight // 得票数矩阵 bufHough 的高度。 +); + + + +// Kernel 函数:_findpartmaxKer(计算局部最大值) +// 在得票数矩阵 bufHough 中寻找局部最大值,当该位置的得票数大于其邻域的得票 +// 数,并且大于圆的阈值,被认为是一个局部最大值,即一个可能圆。 +static __global__ void +_findpartmaxKer( + int bufHoughDev[], // 得票数矩阵。 + int bufsortDev[], // 局部最值矩阵。 + int sumdev[], // 存在的圆的个数。 + int threshold, // 圆的阈值。 + int bufwidth, // 得票数矩阵 bufHough 的宽度。 + int bufheights, // 得票数矩阵 bufHough 的高度。 + int numperRDev[] // 不同半径的圆的个数。 +); + + + +// Kernel 函数:_countcirbyRKer(按照不同的半径计算圆的个数) +// 根据局部最大值矩阵,按照不同的半径,统计每个局部最大值(可能圆)是当前半径 +// 的第几个局部最大值,并将索引值保存在 bufsort 数组中。 +static __global__ void +_countcirbyRKer( + int bufsortDev[], // 局部最值矩阵。 + int bufwidth, // 矩阵的宽度。 + int bufheight // 矩阵的高度。 +); + +// Kernel 函数:_getcirinfKer(获得圆的得票数和索引信息) +// 将得到的可能圆的得票数和索引值保存在对应的数组中。 +static __global__ void +_getcirinfKer( + int bufHoughDev[], // 得票数矩阵。 + int bufsortDev[], // 局部最值矩阵。 + int numperRDev[], // 不同半径的圆的个数。 + int cirvoteDev[], // 圆的得票数 + int cirindexDev[], // 圆的索引值。 + int bufwidth, // 矩阵的宽度。 + int bufheight // 矩阵的高度。 +); + +// Kernel 函数: _shearToPosKer(转换数据形式) +// 对排序后的数组进行整理。 +static __global__ void +_shearToPosKer( + int cirvoteDev[], // 圆的得票数。 + int cirindexDev[], // 圆的索引值。 + int lensec, // 矩阵行数。 + int judge // 块内共享内存的大小。 +); + +// Kernel 函数: _shearSortRowDesKer(行降序排序) +// 对待排序矩阵的每一行进行双调排序。 +static __global__ void +_shearSortRowDesKer( + int cirvoteDev[], // 圆的得票数。 + int cirindexDev[], // 圆的索引值。 + int lensec, // 矩阵行数。 + int judge // 块内共享内存的大小。 +); + +// Kernel 函数: _shearSortColDesKer(列降序排序) +// 对待排序矩阵的每一列进行双调排序。 +static __global__ void +_shearSortColDesKer( + int cirvoteDev[], // 圆的得票数。 + int cirindexDev[], // 圆的索引值。 + int length, // 矩阵列数。 + int lensec, // 矩阵行数。 + int judge // 块内共享内存的大小。 +); + +// Kernel 函数:_calcirparamKer(计算圆的返回参数) +// 对按照得票数进行排序之后的圆,重新恢复参数,并保存在圆的返回参数结构体中。 +static __global__ void +_calcirparamKer( + int cirvoteDev[], // 圆的得票数。 + int cirindexDev[], // 圆的索引值。 + CircleParam circleDev[], // 圆的返回参数。 + int bufwidth, // 矩阵的宽度。 + int bufheight, // 矩阵的高度。 + int radiusMin // 最小检测的圆半径。 +); + + + +// Kernel 函数:_houghoutKer(画出已检测到的圆) +// 根据圆的参数返回结构体,对最终已经检测到的圆,输出到 outimg 中。 +static __global__ void +_houghoutKer( + ImageCuda outimg, // 输出图像 + CircleParam cirparamdev[], // 圆的参数结构体 + int circlenum // 圆的个数 +); + +//----------------------------全局函数声明(3个)------------------------------------- +// Device 静态方法:_findcirsumDev(计算小于半径 radius 的圆的总个数) +// 根据上一步计算结果,按照不同半径存在的圆的个数进行累加,统计小于给定半径的 +// 圆的总个数。 +static __device__ int +_findcirsumDev( + int radius, // 圆的半径。 + int numperRDev[] // 不同半径的圆的个数。 +); + +// Host 函数:_recalCirParam(确定最终检测的圆和参数) +// 根据可能圆之间的距离信息,确定最终检测到的圆的个数,并还原圆的参数。 +static __host__ int +_recalCirParam( + CircleParam circle[], // 可能圆的参数结构体 + CircleParam *circleparam, // 圆的参数结构体 + int *circleMax, // 检测圆的最大数量 + int sum, // 可能圆的数量 + float distThres, // 两个不同圆之间的最小距离 + int rThres // 区别两个圆的最小半径差别。 +); + +// Host 函数:_houghcirByImg(根据输入图像进行 Hough 圆检测) +// 根据输入图像,通过 Hough 变换进行圆检测。 +static __host__ int +_houghcirByImg( + Image *inimg, // 输入图像 + int *circleMax, // 检测的圆的最大数量 + CircleParam *circleparam, // 圆的参数结构体 + int radiusMin, // 最小检测的圆半径 + int radiusMax, // 最大检测的圆半径 + int cirThreshold, // 圆的阈值 + float distThres, // 区别两个圆的最小距离。 + int rThres // 区别两个圆的最小半径差别。 +); + + + +//-----------------------------内核函数实现------------------------------------------- + +// 宏:VOTE(x,y,z) +// 在三维投票空间中投票。 +#define VOTE(x,y,z) \ + if(x>=0 && x=0 && yROX1+x0,inimg->ROY1+y0),以ROI区域的左上角 + // (inimg->ROX1,inimg->ROY1)为原点 + int x0 = blockIdx.x * blockDim.x + threadIdx.x; + int y0 = blockIdx.y * blockDim.y + threadIdx.y; + int z = blockIdx.z; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (x0 >= inimgCud.imgMeta.width || y0 >= inimgCud.imgMeta.height) + return; + + // 定义局部变量。 + unsigned char intemp; + int radius; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = y0 * inimgCud.pitchBytes + x0; + // 读取第一个输入坐标点对应的像素值。 + intemp = inimgCud.imgMeta.imgData[inidx]; + // 根据当前 block 的 z 方向坐标计算需要计算的半径的值。r是z坐标,radius是 + radius = z + radiusMin; + // 若当前像素点(x0, y0)是前景点(即像素值为 255),以他为中心,radisu半径, + // 投票空间画圆 + + // 如果当前像素值为 255,即有效像素值,则在投票空间进行bresenham法画圆投票。 + if (intemp == 255) { + int x, y,d; + x = 0; + y = radius; + d = 3-2*radius; + while(x < y){ + // 注意:x,y是以(0,0)为圆心得到的坐标,需要偏移到(x0,x0)坐标系中,z是 + // 投票空间第三维坐标,不是圆的半径,注意和radius区别。 + VOTE(x0+x,y0+y,z); + VOTE(x0+x,y0-y,z); + VOTE(x0-x,y0+y,z); + VOTE(x0-x,y0-y,z); + VOTE(x0+y,y0+x,z); + VOTE(x0+y,y0-x,z); + VOTE(x0-y,y0+x,z); + VOTE(x0-y,y0-x,z); + if(d < y) + d += 4*x+6; + else{ + d += 4*(x-y)+10; + y--; + } + x++; + }// while + }// if (intemp == 255) +}// end of kernel +#undef VOTE + +/* +// Kernel 函数:_houghcirImgKer(根据输入图像计算得票数) +static __global__ void _houghcirImgKer( + ImageCuda inimg, int bufHoughDev[], int radiusMin, + int bufwidth, int bufheight) +{ + // 计算线程对应的输出点的位置,其中 x 和 y 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,x 表示 column;y 表示 row)。 + // r 代表当前线程处理的圆的半径的大小。 + int x0 = blockIdx.x * blockDim.x + threadIdx.x; + int y0 = blockIdx.y * blockDim.y + threadIdx.y; + int r = blockIdx.z; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (x0 >= inimg.imgMeta.width || y0 >= inimg.imgMeta.height) + return; + + // 定义局部变量。 + unsigned char intemp; + int bufidx; + int y, ymin, ymax; + float tempx; + int x, radius; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = y0 * inimg.pitchBytes + x0; + // 读取第一个输入坐标点对应的像素值。 + intemp = inimg.imgMeta.imgData[inidx]; + + // 根据当前 block 的 z 方向坐标计算需要计算的半径的值。 + radius = r + radiusMin; + // 若当前像素点(x, y)是前景点(即像素值为 1),则经过该点的半径为 r 的圆 + // 心纵坐标范围为(y - r, y + r)。 + // 计算该线程需要处理的圆心纵坐标的范围。 + ymin = max(0, (int)y0 - radius); + ymax = min(y0 + radius, (int)inimg.imgMeta.height); + + // 如果当前像素值为 255,即有效像素值,则对该像素点进行圆检测。 + if (intemp == 255) { + // 圆心纵坐标从 bmin 循环到 bmax,对于每一个可能的纵坐标值, + // 计算其对应的圆心横坐标 a。若 a 在图像范围内,进行投票。 + //i是当前像素点高度坐标,a是当前点宽度坐标 + for (y = ymin; y < ymax + 1; y++){ + // 计算圆心横坐标 a的值。 + tempx = sqrtf((float)(radius * radius - (y0 - y) * (y0 - y))); + + // 左半圆投票 + x = (int)(fabs(x0 - tempx) + 0.5f); + // 若 a 不在范围内,则跳出循环。 + if (x <= 0 || x > inimg.imgMeta.width) + continue; + // 计算当前 (x, y, r) 在得票数矩阵中的索引值。 + bufidx = r * (bufwidth + 2) * (bufheight + 2) + + (y + 1) * (bufwidth + 2) + x + 1; + // 使用原子操作进行投票。 + atomicAdd(&bufHoughDev[bufidx], 1); + + // 右半圆投票 + x = (int)(fabs(x0 + tempx) + 0.5f); + // 若 a 不在范围内,则跳出循环。 + if (x <= 0 || x > inimg.imgMeta.width) + continue; + // 计算当前 (x, y, r) 在得票数矩阵中的索引值。 + bufidx = r * (bufwidth + 2) * (bufheight + 2) + + (y + 1) * (bufwidth + 2) + x + 1; + // 使用原子操作进行投票。 + atomicAdd(&bufHoughDev[bufidx], 1); + } + } +} +*/ + +// Kernel 函数:_findpartmaxKer(计算局部最大值) +static __global__ void _findpartmaxKer( + int bufHoughDev[], int bufsortDev[], int sumdev[], + int threshold, int bufwidth, int bufheight, int numperRDev[]) +{ + // 计算线程对应的输出点的位置,其中 x 和 y 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,x 表示 column;y 表示 row)。 + // r 代表当前线程处理的圆的半径的大小。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int z = blockIdx.z; + // 计算该线程在块内的相对位置。 + int inindex = threadIdx.y * blockDim.x + threadIdx.x; + + // 申请共享内存,存该块内符合条件的局部最大值个数,即存在的圆的个数。 + __shared__ int totalsum[1]; + + // 初始化所有块内的共享内存。 + if (inindex == 0) + totalsum[0] = 0; + // 块内同步。 + __syncthreads(); + + // 计算当前线程在 bufHough 矩阵中的对应索引值。 + int index = z * (bufwidth + 2) * (bufheight + 2) + + (r + 1) * (bufwidth + 2) + c + 1; + int idx = z * bufwidth * bufheight + r * bufwidth + c; + + // 当前线程的得票数大于圆的阈值,并且大于邻域中的值时,认为是局部最大值, + // 即可能是圆。 + if (bufHoughDev[index] > threshold && + bufHoughDev[index] > bufHoughDev[index - 1] && + bufHoughDev[index] >= bufHoughDev[index + 1] && + bufHoughDev[index] > bufHoughDev[index - bufwidth - 2] && + bufHoughDev[index] >= bufHoughDev[index + bufwidth + 2]) { + bufsortDev[idx] = bufHoughDev[index]; + // 使用原子操作对局部最大值进行统计。 + atomicAdd(&numperRDev[z], 1); + atomicAdd(&totalsum[0], 1); + } else { + bufsortDev[idx] = 0; + } + + // 块内同步。 + __syncthreads(); + + // 将统计出的圆的个数统计到 sumdev 中。 + if (inindex == 0 && totalsum[0] != 0) { + atomicAdd(&sumdev[0], totalsum[0]); + } +} + +// Kernel 函数:_countcirbyRKer(按照不同的半径计算圆的个数) +static __global__ void _countcirbyRKer( + int bufsortDev[], int bufwidth, int bufheight) +{ + // 计算线程的索引,即圆的半径。 + int r = blockIdx.x * blockDim.x + threadIdx.x; + + // 初始化圆的个数为 1。 + int count = 1; + // 计算该线程对应的局部最大值矩阵 bufsort 中的索引值。 + int idx = r * bufwidth * bufheight; + int index; + + // 半径为 r,对矩阵 bufsort 进行统计,得到该半径的圆的个数。 + for (int j = 0; j < bufheight; j++) { + index = idx; + for (int i = 0; i < bufwidth; i++) { + // 若矩阵 bufsort 当前位置的值不为 0,则为其赋值 count,表示该 + // 局部最大值是半径为 r 的圆中的第 count 个。 + if (bufsortDev[index] != 0) { + bufsortDev[index] = count; + count++; + } + index += 1; + } + idx += bufwidth; + } +} + +// Kernel 函数:_getcirinfKer(获得圆的得票数和索引信息) +static __global__ void _getcirinfKer( + int bufHoughDev[], int bufsortDev[], int numperRDev[], + int cirvoteDev[], int cirindexDev[], int bufwidth, int bufheight) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + // z 代表当前线程处理的圆的半径的大小。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int z = blockIdx.z; + + // 计算当前线程在 bufsort 矩阵中的对应索引值。 + int index = z * bufwidth * bufheight + r * bufwidth + c; + int ciridx, idx; + + // 如果 bufsort 矩阵当前位置的值不为 0,则说明是局部最大值。 + if (bufsortDev[index] != 0) { + // 计算该局部最大值的输出位置。该位置等于半径小于 z 的所有圆数 + // 加上该局部最大值在当前半径中的位置。 + ciridx = bufsortDev[index] + _findcirsumDev(z, numperRDev) - 1; + + // 将该局部最大值的索引信息赋值到 cirindex 数组中。 + // 该索引是不加边框的bufSortDev中的索引。 + cirindexDev[ciridx] = index; + + // 计算在得票数矩阵 bufHough 中的索引值。 + idx = z * (bufwidth + 2) * (bufheight + 2) + + (r + 1) * (bufwidth + 2) + c + 1; + // 将该局部最大值的得票数赋值到 cirvote 数组中。 + cirvoteDev[ciridx] = bufHoughDev[idx]; + } +} + +// Kernel 函数: _shearToPosKer(转换数据形式) +static __global__ void _shearToPosKer( + int cirvoteDev[], int cirindexDev[], int lensec, int judge) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + extern __shared__ int shared[]; + // 通过偏移,获得存放得票数和索引值的两部分共享内存空间。 + int *vote, *index; + vote = shared; + index = shared + judge; + // 为得票数和索引值赋初始值。 + vote[cid] = cirvoteDev[rid * lensec + cid]; + index[cid] = cirindexDev[rid * lensec + cid]; + // 块内同步。 + __syncthreads(); + + // 偶数行赋值。 + if (rid % 2 == 0) { + cirvoteDev[rid * lensec + cid] = vote[cid]; + cirindexDev[rid * lensec + cid] = index[cid]; + } else { + // 奇数行赋值。 + cirvoteDev[rid * lensec + cid] = vote[lensec - 1 - cid]; + cirindexDev[rid * lensec + cid] = index[lensec - 1 - cid]; + } +} + +// Kernel 函数: _shearSortRowDesKer(行降序排序) +static __global__ void _shearSortRowDesKer( + int cirvoteDev[], int cirindexDev[], int lensec, int judge) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + extern __shared__ int shared[]; + // 通过偏移,获得存放得票数和索引值的两部分共享内存空间。 + int *vote, *index; + vote = shared; + index = shared + judge; + + // 为共享内存赋初始值。 + if (cid < lensec) { + vote[cid] = cirvoteDev[rid * lensec + cid]; + index[cid] = cirindexDev[rid * lensec + cid]; + } + // 块内同步。 + __syncthreads(); + + // 声明临时变量 + int ixj, tempvote, tempindex; + // 偶数行降序排序。 + if (rid % 2 == 0) { + for (int k = 2; k <= lensec; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (vote[cid] < vote[ixj])) { + // 交换得票数。 + tempvote = vote[cid]; + vote[cid] = vote[ixj]; + vote[ixj] = tempvote; + // 交换索引值。 + tempindex = index[cid]; + index[cid] = index[ixj]; + index[ixj] = tempindex; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && vote[cid] > vote[ixj]) { + // 交换得票数。 + tempvote = vote[cid]; + vote[cid] = vote[ixj]; + vote[ixj] = tempvote; + // 交换索引值。 + tempindex = index[cid]; + index[cid] = index[ixj]; + index[ixj] = tempindex; + } + } + __syncthreads(); + } + } + // 奇数行升序排序。 + } else { + for (int k = 2; k <= lensec; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (vote[cid] > vote[ixj])) { + // 交换得票数。 + tempvote = vote[cid]; + vote[cid] = vote[ixj]; + vote[ixj] = tempvote; + // 交换索引值。 + tempindex = index[cid]; + index[cid] = index[ixj]; + index[ixj] = tempindex; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && vote[cid] < vote[ixj]) { + // 交换得票数。 + tempvote = vote[cid]; + vote[cid] = vote[ixj]; + vote[ixj] = tempvote; + // 交换索引值。 + tempindex = index[cid]; + index[cid] = index[ixj]; + index[ixj] = tempindex; + } + } + __syncthreads(); + } + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + if (cid = lensec) + return; + + extern __shared__ int shared[]; + // 通过偏移,获得存放得票数和索引值的两部分共享内存空间。 + int *vote, *index; + vote = shared; + index = shared + judge; + + // 为共享内存赋初始值。 + if (cid < length) { + vote[cid] = cirvoteDev[rid + cid * lensec]; + index[cid] = cirindexDev[rid + cid * lensec]; + } + // 块内同步。 + __syncthreads(); + + // 声明临时变量。 + int ixj, tempvote, tempindex; + // 并行双调排序,降序排序。 + for (int k = 2; k <= length; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (vote[cid] < vote[ixj])) { + // 交换得票数。 + tempvote = vote[cid]; + vote[cid] = vote[ixj]; + vote[ixj] = tempvote; + // 交换索引值。 + tempindex = index[cid]; + index[cid] = index[ixj]; + index[ixj] = tempindex; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && vote[cid] > vote[ixj]) { + // 交换得票数。 + tempvote = vote[cid]; + vote[cid] = vote[ixj]; + vote[ixj] = tempvote; + // 交换索引值。 + tempindex = index[cid]; + index[cid] = index[ixj]; + index[ixj] = tempindex; + } + } + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + if (cid < length) { + cirvoteDev[rid + cid * lensec] = vote[cid]; + cirindexDev[rid + cid * lensec] = index[cid]; + } +} + +// Kernel 函数:_calcirparamKer(计算圆的返回参数) +static __global__ void _calcirparamKer( + int cirvoteDev[], int cirindexDev[], CircleParam circleDev[], + int bufwidth, int bufheight, int radiusMin) +{ + // 获取线程号。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 声明局部变量。 + int radius, x, y, temp; + // 获取当前圆在 cirindex 矩阵中的索引值。 + int idx = cirindexDev[index]; + // 矩阵的大小。 + // 矩阵的大小。 + int size = (bufwidth) * (bufheight); + // 计算当前圆的半径。 + radius = idx / size; + temp = idx - radius * size; + // 计算当前圆的圆心的纵坐标。 + y = temp / (bufwidth); + // 计算当前圆的圆心的横坐标。 + x = temp % (bufwidth); + + // 为当前圆的返回参数进行赋值。 + circleDev[index].a = x; + circleDev[index].b = y; + circleDev[index].radius = radius + radiusMin; + circleDev[index].votes = cirvoteDev[index]; +} + + +// Kernel 函数:_houghoutKer(画出已检测到的圆) +static __global__ void _houghoutKer( + ImageCuda outimg, CircleParam cirparamdev[], int circlenum) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省 + // 计算资源,一方面防止由于段错误导致的程序崩溃。 + if (c >= outimg.imgMeta.width || r >= outimg.imgMeta.height) + return; + + // 计算当前坐标点对应的图像数据数组下标。 + unsigned char *outptr; + outptr = outimg.imgMeta.imgData + c + r * outimg.pitchBytes; + *outptr = 0; + + // 声明局部变量 + int i, temp, radius, a, b; + + // 对所有已经检测出的圆进行循环,找到输入图像中对应的点,并赋值 128。 + for (i = 0; i < circlenum; i++) { + // 得到圆的参数,圆心 (a, b), 圆的半径 radius。 + radius = cirparamdev[i].radius; + a = cirparamdev[i].a; + b = cirparamdev[i].b; + + // 计算当前像素点 (c, r) 到该圆心 (a, b) 的距离。 + temp = (c - a) * (c - a) + (r - b) * (r - b); + + // 若该距离小于 20,则认为是该圆上的点,在输出图像中赋值 128。 + if (abs(temp - radius * radius) < 50) + *outptr = 255; + } +} + +//-----------------------------全局函数实现------------------------------------------- +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax) +{ + // 声明局部变量。 + int errcode; + + // 在 host 端申请一个新的 CoordiSet 变量。 + CoordiSet *tmpcoordiset; + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + errcode = CoordiSetBasicOp::makeAtHost(tmpcoordiset, guidingset->count); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(guidingset, tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 初始化 x 和 y 方向上的最小最大值。 + xmin[0] = xmax[0] = tmpcoordiset->tplData[0]; + ymin[0] = ymax[0] = tmpcoordiset->tplData[1]; + // 循环寻找坐标集最左、最右、最上、最下的坐标。 + for (int i = 1;i < tmpcoordiset->count;i++) { + // 寻找 x 方向上的最小值。 + if (xmin[0] > tmpcoordiset->tplData[2 * i]) + xmin[0] = tmpcoordiset->tplData[2 * i]; + // 寻找 x 方向上的最大值 + if (xmax[0] < tmpcoordiset->tplData[2 * i]) + xmax[0] = tmpcoordiset->tplData[2 * i]; + + // 寻找 y 方向上的最小值。 + if (ymin[0] > tmpcoordiset->tplData[2 * i + 1]) + ymin[0] = tmpcoordiset->tplData[2 * i + 1]; + // 寻找 y 方向上的最大值 + if (ymax[0] < tmpcoordiset->tplData[2 * i + 1]) + ymax[0] = tmpcoordiset->tplData[2 * i + 1]; + } + + // 释放临时坐标集变量。 + CoordiSetBasicOp::deleteCoordiSet(tmpcoordiset); + + return errcode; +} + +// Device 静态方法:_findcirsumDev(计算小于半径 radius 的圆的总个数) +static __device__ int _findcirsumDev(int radius, int numperRDev[]) +{ + int n = radius; + // 将圆的总个数初始化为 0。 + int cirsum = 0; + + // 计算小于所给半径 radius 的圆的总个数,并赋值给 cirsum。 + while (--n >= 0) { + cirsum += numperRDev[n]; + } + // 返回计算所得的圆的总个数。 + return cirsum; +} + + +// Host 静态方法:_cirSortLoop(shear 排序核心函数) +static __host__ int _cirSortLoop(int cirvoteDev[], int cirindexDev[], + int length, int lensec) +{ + // 检查数组是否为 NULL,如果为 NULL 直接报错返回。 + if (cirvoteDev == NULL || cirindexDev == NULL) + return NULL_POINTER; + + // 计算二维数组中长和宽的较大值。 + int judge; + if (length > 0 && lensec > 0) + judge = (length > lensec) ? length : lensec; + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + for (int i = length; i >= 1; i >>= 1) { + // 首先进行列排序。 + _shearSortColDesKer<<>> + (cirvoteDev, cirindexDev, length, lensec, judge); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 然后进行行排序。 + _shearSortRowDesKer<<>> + (cirvoteDev, cirindexDev, lensec, judge); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + // 整理排序后的数组。 + _shearToPosKer<<>> + (cirvoteDev, cirindexDev, lensec, judge); + // 若调用 CUDA 出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// Host 函数:_recalCirParam(确定最终检测的圆和参数) +// 从circle中选出有效的、不重复的,放入circleparam中 +static __host__ int _recalCirParam( + CircleParam *circle, CircleParam *circleparam, + int *circleMax, int sum, float distThres, int rThres) +{ + // 根据两个圆的距离关系,确定最终检测出的圆。 + int a1, a2, b1, b2, diffr; + float distance; + // 统计最终检测的圆的个数。 + int circlenum = 0; + for (int i = 0; i < sum; i++) { + // 若当前圆的参数结构体的得票数为 0,直接进行下次循环。 + if (circle[i].votes == 0 || circle[i].radius == 0) + continue; + for (int j = i + 1; j < sum; j++) { + // 得到两个圆的圆心的坐标。 + a1 = circle[i].a; + b1 = circle[i].b; + a2 = circle[j].a; + b2 = circle[j].b; + // 计算两个圆半径差值 + diffr = abs(circle[i].radius - circle[j].radius); + // 计算两个圆的圆心 (a1, b1), (a2, b2) 的距离。 + distance = (float)(a1 - a2) * (a1 - a2) + (b1 - b2) * (b1 - b2); + // 圆心和半径都相近的,认为是同一个圆 + if (distance < distThres * distThres && diffr < rThres) { + // 合并后的圆参数取平均值,票数合并 ,放入i中 + circle[i].a = (circle[i].a+circle[j].a)/2; + circle[i].b = (circle[i].b+circle[j].b)/2; + circle[i].radius = (circle[i].radius+circle[j].radius)/2; + circle[i].votes = circle[i].votes+circle[j].votes; + // j中另一圆取消 + circle[j].a = 0; + circle[j].b = 0; + circle[j].radius = 0; + circle[j].votes = 0; + } + } + // 检测出的圆的个数加 1。 + circlenum++; + } + + // 根据circlenum以及期望检测出的圆的个数circleMax,确定最终圆的个数。 + circleMax[0] = (circlenum < circleMax[0]) ? circlenum : circleMax[0]; + + // 将最终检测的圆的参数赋值到需要返回的圆的参数结构体中。 + int k = 0; + for (int i = 0; i < sum; i++) { + // 赋值到最后一个圆时,结束循环。 + if (k >= circleMax[0]) + break; + // 若得票数不为 0,说明是检测出的圆,赋值到圆的返回参数结构体中。 + // 票数为零 则说明是被合并到其他圆中,直接跳过。 + if (circle[i].votes != 0) { + circleparam[k].a = circle[i].a; + circleparam[k].b = circle[i].b; + circleparam[k].radius = circle[i].radius; + circleparam[k].votes = circle[i].votes; + // 标记加 1。 + k++; + } + } + return NO_ERROR; +} + + +// 宏:FAIL_CIRCLE_IMG_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_CIRCLE_IMG_FREE do { \ + if (alldataDev != NULL) \ + cudaFree(alldataDev); \ + if (cirdataDev != NULL) \ + cudaFree(cirdataDev); \ + if (circleDev != NULL) \ + cudaFree(circleDev); \ + if (circle != NULL) \ + delete[] circle; \ + } while (0) + +// Host 静态方法:_houghcirByImg(根据输入图像进行 Hough 圆检测) +// 根据输入图像 inimg,通过 Hough 变换进行圆检测。 +static __host__ int _houghcirByImg( + Image *inimg, int *circleMax, CircleParam *circleparam, + int radiusMin, int radiusMax, int cirThreshold, float distThres,int rThres) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + int bufwidth, bufheight; + int rangeR = radiusMax - radiusMin + 1; + + // 输入图像不为空,则根据输入图像的ROI区域得到ROI区域的宽和高。 + bufwidth = inimg->roiX2-inimg->roiX1; + bufheight = inimg->roiY2-inimg->roiY1; + + // 定义设备端的输入输出数组指针,当输入输出指针在 Host 端时,在设备端申请对 + // 应大小的数组。 + int *alldataDev = NULL; + int *cirdataDev = NULL; + CircleParam *circleDev = NULL; + CircleParam *circle = NULL; + + // 声明 Device 端需要的所有空间。 + int *bufHoughDev = NULL, *bufsortDev = NULL; + int *sumdev = NULL, *numperRDev = NULL; + cudaError_t cudaerrcode; + + // 一次性申请 Device 端需要的所有空间。 + cudaerrcode = cudaMalloc((void **)&alldataDev, + (1 + rangeR + bufwidth * bufheight * rangeR + + (bufwidth + 2) * (bufheight + 2) * rangeR) * + sizeof (int)); + if (cudaerrcode != cudaSuccess) + return CUDA_ERROR; + + // 通过偏移得到各指针的地址。 + sumdev = alldataDev; + numperRDev = alldataDev + 1; + bufsortDev = alldataDev + 1 + rangeR; + bufHoughDev = alldataDev + 1 +rangeR+ bufwidth * bufheight * rangeR; + + // 初始化 Hough 变换累加器在 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldataDev, 0, + (1 + rangeR + bufwidth * bufheight * rangeR + + (bufwidth + 2) * (bufheight + 2) * rangeR) * + sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return errcode; + } + + // 调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / + blocksize.y; + gridsize.z = rangeR; + + // 调用核函数,对输入图像计算 Hough 累加矩阵。 + _houghcirImgKer<<>>( + insubimgCud, bufHoughDev, radiusMin, bufwidth, bufheight); + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 重新计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (bufwidth + blocksize.x - 1) / blocksize.x; + gridsize.y = (bufheight + blocksize.y - 1) / blocksize.y; + gridsize.z = rangeR; + + // 调用核函数,对 bufHough 矩阵寻找局部最大值。 + _findpartmaxKer<<>>( + bufHoughDev, bufsortDev, sumdev, cirThreshold, + bufwidth, bufheight, numperRDev); + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 可能存在的圆的数量。 + int sum; + // 将计算得到的可能存在的圆的数量 sum 拷贝到 Host 端。 + cudaerrcode = cudaMemcpy(&sum, sumdev, sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + if(sum<=0){ + *circleMax=0; + return NO_ERROR; + } + // 重新计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = (rangeR > DEF_BLOCK_1D) ? DEF_BLOCK_1D : rangeR; + blocksize.y = 1; + blocksize.z = 1; + gridsize.x = (rangeR + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + gridsize.z = 1; + + // 调用核函数,统计不同半径的圆的个数。 + _countcirbyRKer<<>>(bufsortDev, bufwidth, bufheight); + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 对统计出的可能存在的圆的总数 sum, + // 取大于或者等于它的最小的 2 的幂次方数。 + int index = (int)ceil(log(sum*1.0) / log(2.0f)); + if (index > sizeof (int) * 8 - 1) + return OP_OVERFLOW; + int sortlength = (1 << index); + + // 声明 Device 端需要的所有空间。 + int *cirvoteDev = NULL, *cirindexDev = NULL; + // 一次性申请 Device 端需要的所有空间。 + cudaerrcode = cudaMalloc((void **)&cirdataDev, + (2 * sortlength) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(cirdataDev, 0, (2 * sortlength) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 通过偏移获得数组的地址。 + cirindexDev = cirdataDev; + cirvoteDev = cirdataDev + sortlength; + + // 重新计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (bufwidth + blocksize.x - 1) / blocksize.x; + gridsize.y = (bufheight + blocksize.y - 1) / blocksize.y; + gridsize.z = rangeR; + + // 调用核函数,计算可能圆的索引以及得票数值。 + _getcirinfKer<<>>( + bufHoughDev, bufsortDev, numperRDev, + cirvoteDev, cirindexDev, bufwidth, bufheight); + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 需要使用并行的 Shear Sort 对可能圆按照得票数大小 + // 排序,定义排序时需要的数组宽度和高度。 + int sortwidth = (sortlength > 256) ? 256 : sortlength; + int sortheight = (sortlength + sortwidth - 1) / sortwidth; + + // 调用并行 Shear Sort 算法,对可能圆按照得票数排序。 + errcode = _cirSortLoop(cirvoteDev, cirindexDev, sortwidth, sortheight); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return errcode; + } + + #ifdef DEBUG + int *cirIndex=(int*)malloc(sortlength*sizeof (int)); + int *cirVote=(int*)malloc(sortlength*sizeof (int)); + // 将计算得到的极值点数量 sum 拷贝到 Host 端。 + cudaerrcode = cudaMemcpy(cirIndex, cirindexDev, sortlength*sizeof (int), + cudaMemcpyDeviceToHost); + cudaerrcode = cudaMemcpy(cirVote, cirvoteDev, sortlength*sizeof (int), + cudaMemcpyDeviceToHost); + int size = (bufwidth) * (bufheight); + for(int n=0;n DEF_BLOCK_1D) ? DEF_BLOCK_1D : sum; + blocksize.y = 1; + blocksize.z = 1; + gridsize.x = (sum + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + gridsize.z = 1; + + // 调用核函数,计算圆的返回参数。 + _calcirparamKer<<>>( + cirvoteDev, cirindexDev, circleDev, bufwidth, bufheight, + radiusMin); + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 为圆的参数返回结构体分配空间。 + circle = new CircleParam[sum]; + + // 将核函数计算出的圆的返回参数复制到 Host 端中。 + cudaerrcode = cudaMemcpy(circle, circleDev, + sum * sizeof (CircleParam), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return CUDA_ERROR; + } + + // 调用函数 _recalCirParam 计算最终检测的圆的数量以及参数。 + errcode = _recalCirParam(circle, circleparam, circleMax, sum, distThres,rThres); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + FAIL_CIRCLE_IMG_FREE; + return errcode; + } + + // 释放之前申请的内存。 + cudaFree(alldataDev); + cudaFree(cirdataDev); + cudaFree(circleDev); + delete[] circle; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// 取消前面的宏定义。 +#undef FAIL_CIRCLE_IMG_FREE + +// 全局方法:_drawCircle(把圆参数数组绘制到图像上) +__host__ int _drawCircle(Image *resultimg, + int *circleMax, + CircleParam *circleparam + ){ + + int errcode; // 局部变量,错误码 + cudaError_t cudaerrcode; + + CircleParam *circleDev = NULL; + // 为 device 端圆返回参数数组申请空间。 + cudaerrcode = cudaMalloc((void **)&circleDev, + circleMax[0] * sizeof (CircleParam)); + if (cudaerrcode != cudaSuccess) { + cudaFree(circleDev); + return CUDA_ERROR; + } + + // 将计算得到的参数从 Host 端拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(circleDev, circleparam, + circleMax[0] * sizeof (CircleParam), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + // 释放之前申请的内存。 + cudaFree(circleDev); + return CUDA_ERROR; + } + // 将结果图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(resultimg); + if (errcode != NO_ERROR) + return errcode; + // 提取结果图像的 ROI 子图像。 + ImageCuda resultimgCud; + errcode = ImageBasicOp::roiSubImage(resultimg, &resultimgCud); + if (errcode != NO_ERROR) + return errcode; + + dim3 blocksize, gridsize; + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (resultimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (resultimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + gridsize.z = 1; + + // 调用 kernel函数,得出最终输出图像。 + _houghoutKer<<>>( + resultimgCud, circleDev, circleMax[0]); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + + return NO_ERROR; +} + +//-----------------------------成员函数实现------------------------------------------- + +// Host 成员方法:houghcircle(Hough 变换检测圆) +__host__ int HoughCircle::houghcircle(Image *inimg, CoordiSet *guidingset, + int *circleMax, CircleParam *circleparam, + bool writetofile) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL && guidingset == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + + // 声明输出图像 + Image *resultimg; + ImageBasicOp::newImage(&resultimg); + + if (guidingset != NULL) { + // 若输入坐标集不为空,则将该点集拷贝入 Device 内存。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(guidingset); + if (errcode != NO_ERROR) + return errcode; + // 计算坐标集的最大坐标位置,为创建图像做准备 + int minx,miny,maxx,maxy; + int errorcode=_findMinMaxCoordinates(guidingset,&minx,&miny,&maxx,&maxy); + if(errorcode!=NO_ERROR) + return INVALID_DATA; + + // 根据输入 coordiset 创建输入图像 coorimg + Image *coorimg; + ImageBasicOp::newImage(&coorimg); + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证轮廓外连通 + ImageBasicOp::makeAtHost(coorimg,maxx+2 ,maxy+2); + // coordiset 转成coorimg ,把坐标集绘制到图像上,前景255,背景0 + ImgConvert imgcvt(255,0); + imgcvt.cstConvertToImg(guidingset,coorimg); + #ifdef DEBUG + // 把填充前的图像coorimg保存到文件 + ImageBasicOp::copyToHost(coorimg); + ImageBasicOp::writeToFile("coorimg.bmp",coorimg); + #endif + + // 根据输入图像 coorimg 进行 Hough 变换圆检测。 + errcode = _houghcirByImg(coorimg, circleMax, circleparam, radiusMin, + radiusMax, cirThreshold, distThres,rThres); + if (errcode != NO_ERROR) + return errcode; + // 清除输入图像 coorimg + ImageBasicOp::deleteImage(coorimg); + // 如果需要输出图像,分配空间 + if(writetofile) + ImageBasicOp::makeAtHost(resultimg,maxx+2 ,maxy+2); + } else { + + // 输入图像不为空,则根据输入图像进行 Hough 变换圆检测。 + errcode = _houghcirByImg(inimg, circleMax, circleparam, radiusMin, + radiusMax, cirThreshold, distThres,rThres); + if (errcode != NO_ERROR) + return errcode; + + // 分片局部坐标转化成全局坐标 + for(int i=0; i< *circleMax; i++) { + circleparam[i].a+=inimg->roiX1; + circleparam[i].b+=inimg->roiY1; + } + + // 如果需要输出图像,分配空间 + if(writetofile) + ImageBasicOp::makeAtHost(resultimg, + inimg->width, + inimg->height); + } + + + // 如果需要输出图像,调用 kernel 写入到“result.bmp”中 + if(writetofile) + _drawCircle(resultimg, circleMax, circleparam); + + // 检测结果写入文件 + ImageBasicOp::copyToHost(resultimg); + ImageBasicOp::writeToFile("result.bmp",resultimg); + // 删除输出图像img + ImageBasicOp::deleteImage(resultimg); + // 处理完毕,退出。 + return NO_ERROR; +} + + + +// Host 成员方法:pieceCircle(分片检测inimg中的圆形,放入数组返回) +__host__ int +HoughCircle:: pieceCircle( + Image *inimg, // 输入待检测的图形 + int piecenum, // 每个维度上分块数量 + int *circleMax, // 返回检测到的圆数量 + CircleParam *circleparam, // 返回检测到的圆参数 + bool writetofile // 是否把检测结果写到文件中 +){ + int pointer=0; + + // 计算分片的大小 + int cell_x=inimg->width/piecenum; + int cell_y=inimg->height/piecenum; + #ifdef DEBUG + printf("cell_x=%d cell_y=%d\n",cell_x,cell_y); + #endif + + // 开始分块处理 + for(int y=0;yroiX1=x*cell_x; + inimg->roiX2=x*cell_x+cell_x-1; + inimg->roiY1=y*cell_y; + inimg->roiY2=y*cell_y+cell_y-1; + + #ifdef DEBUG + printf("x1=%d x2=%d y1=%d y2=%d \n", + inimg->roiX1,inimg->roiX2, + inimg->roiY1,inimg->roiY2); + #endif + // 下面函数运行后,piececirmax中放的是检测到的圆的个数。 + // houghcircle()返回后得到的是全局坐标 + houghcircle(inimg, NULL, &piececirmax, piececirparam,false); + + // 分片圆结果放入全局数组 + for(int i=0; i< piececirmax; i++) { + if(pointer>=*circleMax)break; + circleparam[pointer]=piececirparam[i]; + pointer++; + + } + // 循环内声明的局部动态内存,循环内回收 + if(piececirparam!=NULL) + {delete[] piececirparam;piececirparam=NULL;} + + //.........................分块第二阶段........................ + if(xwidth,inimg->height); + + _drawCircle(resultimg, circleMax, circleparam); + + // 检测结果写入文件 + ImageBasicOp::copyToHost(resultimg); + ImageBasicOp::writeToFile("result.bmp",resultimg); + ImageBasicOp::deleteImage(resultimg); + } + return NO_ERROR; + } + + + + diff --git a/okano_3_0/HoughCircle.h b/okano_3_0/HoughCircle.h new file mode 100644 index 0000000..2cc815e --- /dev/null +++ b/okano_3_0/HoughCircle.h @@ -0,0 +1,259 @@ +// HoughCircle.h +// 创建人:侯怡婷 +// +// Hough变换检测圆(HoughCircle) +// 功能说明:实现 Hough 变换检测圆。输入参数为坐标集 guidingset 或者输 +// 入图像 inimg,若 guidingset 不为空,则只处理该坐标集;若为 +// 空,则对图像 inimg 进行圆检测。 +// +// 修订历史: +// 2012年12月12日(侯怡婷) +// 初始版本。 +// 2012年12月28日(侯怡婷) +// 基本完成的 HoughCircle 算法,初始版本。 +// 2012年12月30日(侯怡婷) +// 改善 HoughCircle 算法。 +// 2012年12月31日(侯怡婷) +// 完善注释规范。 +// 2013年01月02日(侯怡婷) +// 改善计算得票数时的方法,缩小了每个点的查找范围。 +// 2013年01月06日(侯怡婷) +// 加入处理坐标点集的算法。 +// 2013年01月14日(侯怡婷) +// 修改确定最终圆的个数的方法。 +// 2013年03月20日(侯怡婷) +// 修改代码中一处 bug。 +// 2013年04月13日(侯怡婷) +// 将寻找输入坐标集横纵坐标最小、最大值的过程改成并行算法, +// 并尽量合并代码,减少冗余。 + +#ifndef __HOUGHCIRCLE_H__ +#define __HOUGHCIRCLE_H__ + +#include "Image.h" +#include "CoordiSet.h" +#include "ErrorCode.h" +#include "ImgConvert.h" + +// 结构体:CircleParam(圆返回参数) +// 将检测到圆的参数:圆心的坐标,圆的半径等定义为结构体,作为函数最终 +// 输出结果。 +typedef struct CircleParam_st { + int a; // 圆心的横坐标。 + int b; // 圆心的纵坐标。 + int radius; // 圆的半径。 + int votes; // 得票数,即 BufHough 矩阵中的数据。 +} CircleParam; + +// 类:HoughLCircle +// 继承自:无 +// 实现 Hough 变换检测圆。输入参数为坐标集 guidingset 或者输入图像 inimg, +// 若 guidingset 不为空,则只处理该坐标集;若为空,则对图像 inimg 进行圆检测。 +class HoughCircle { + +protected: + + // 成员变量: minradius(最小检测的圆半径) + // 描述在检测圆时,最小的圆半径。 + int radiusMin; + + // 成员变量: maxradius(最小检测的圆半径) + // 描述在检测圆时,最大的圆半径。 + int radiusMax; + + // 成员变量:distMin(判定相似圆的圆心之间距离阈值) + // 用于明显区分两个不同圆之间的最小距离。 + float distThres; + + // 成员变量:radius(判定相似圆的半径相差阈值) + // 用于区分不同圆的最小半径阈值 + float rThres; + + // 成员变量:cirThreshold(圆形阈值) + // 若累加器中相应的累计值大于该参数,则认为是一个圆。 + int cirThreshold; + +public: + // 构造函数:HoughCircle + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + HoughCircle() + { + // 使用默认值为类的各个成员变量赋值。 + this->radiusMin = 5; // 最小检测的圆半径为 5。 + this->radiusMax = 50; // 最大小检测的圆半径为 50。 + this->distThres = 50.0f; // 两个圆之间的最小距离为 50.0f。 + this->rThres=20; + this->cirThreshold = 100; // 圆的阈值为100。 + } + + // 构造函数:HoughCircle + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + HoughCircle( + int radiusMin, // 最小检测的圆半径。 + int radiusMax, // 最小检测的圆半径。 + float distMin, // 两个圆之间的最小距离。 + int rThres, + int cirThreshold // 圆的阈值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中 + // 给了非法的初始值而使系统进入一个未知的状态。 + this->radiusMin = 5; // 最小检测的圆半径为 5。 + this->radiusMax = 50; // 最大检测的圆半径为 50。 + this->distThres = 50.0f; // 两个圆之间的最小距离为 50.0f。 + this->rThres=20; + this->cirThreshold = 100; // 圆的阈值为100。 + + // 根据参数列表中的值设定成员变量的初值 + setRadiusMin(radiusMin); + setRadiusMax(radiusMax); + setDistThres(distMin); + setRThres(rThres); + setCirThreshold(cirThreshold); + } + + // 成员方法:getRadiusMin(获取最小检测的圆半径) + // 获取成员变量 radiusMin 的值。 + __host__ __device__ int // 返回值:成员变量 radiusMin 的值 + getRadiusMin() const + { + // 返回 radiusMin 成员变量的值。 + return this->radiusMin; + } + + // 成员方法:setRadiusMin(设置最小检测的圆半径) + // 设置成员变量 radiusMin 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setRadiusMin( + int radiusMin // 设定新的最小检测的圆半径 + ) { + // 检查 radiusMin 的合法性。 + if (radiusMin < 0) + return INVALID_DATA; + // 将 radiusMin 成员变量赋成新值 + this->radiusMin = radiusMin; + + return NO_ERROR; + } + + // 成员方法:getRadiusMax(获取最大检测的圆半径) + // 获取成员变量 radiusMax 的值。 + __host__ __device__ int // 返回值:成员变量 radiusMax 的值 + getRadiusMax() const + { + // 返回 radiusMax 成员变量的值。 + return this->radiusMax; + } + + // 成员方法:setRadiusMax(设置最大检测的圆半径) + // 设置成员变量 radiusMax 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setRadiusMax( + int radiusMax // 设定最大检测的圆半径 + ) { + // 检查 radiusMax 的合法性。 + if (radiusMax < 0) + return INVALID_DATA; + // 将 radiusMax 成员变量赋成新值 + this->radiusMax = radiusMax; + + return NO_ERROR; + } + + // 成员方法:getDistMin(获取两个不同圆之间的最小距离) + // 获取成员变量 distMin 的值。 + __host__ __device__ float // 返回值:成员变量 distMin 的值 + getDistThres() const + { + // 返回 distMin 成员变量的值。 + return this->distThres; + } + + // 成员方法:setDistMin(设置两个不同圆之间的最小距离) + // 设置成员变量 distMin 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setDistThres( + float dist // 设定两个不同圆之间的最小距离 + ) { + // 将 distMin 成员变量赋成新值 + this->distThres=dist; + + return NO_ERROR; + } + // 成员方法:getDistMin(获取两个不同圆之间的最小距离) + // 获取成员变量 distMin 的值。 + __host__ __device__ int // 返回值:成员变量 distMin 的值 + getRThres() const + { + // 返回 distMin 成员变量的值。 + return this->rThres; + } + + // 成员方法:setDistMin(设置评定相似园的半径差别值) + // 设置成员变量 rThresMin 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setRThres( + int r // 设定两个不同圆之间的最小距离 + ) { + this->rThres=r; + return NO_ERROR; + } + // 成员方法:getCirThreshold(获取圆的阈值) + // 获取成员变量 cirThreshold 的值。 + __host__ __device__ int // 返回值:成员变量 cirThreshold 的值 + getCirThreshold() const + { + // 返回 cirThreshold 成员变量的值。 + return this->cirThreshold; + } + + // 成员方法:setCirThreshold(设置圆的阈值) + // 设置成员变量 cirThreshold 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setCirThreshold( + int cirThreshold // 设定新的圆的阈值 + ) { + // 检查 cirThreshold 的合法性。 + if (cirThreshold < 0) + return INVALID_DATA; + // 将 cirThreshold 成员变量赋成新值 + this->cirThreshold = cirThreshold; + + return NO_ERROR; + } + + + // Host 成员方法:houghline(Hough 变换检测圆) + // 若输入的坐标集 guidingset 不为空,则只处理该坐标集;若该坐标集为空,则 + // Hough 变换就处理输入图像的 ROI 区域,并把最终检测圆的结果返回 + // 到定义的参数结构体中,并且返回检测到的圆的数量。输入图像可以为空。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回NO_ERROR。 + houghcircle( + Image *inimg, // 输入图像 + CoordiSet *guidingset, // 输入坐标集 + int *circleMax, // 检测圆的最大数量 + CircleParam *circleparam, // 圆返回参数结构体 + bool writetofile // 是否需要将检测结果写入图像文件result.bmp中 + ); + // Host 成员方法:pieceCircle(分片检测inimg中的圆形,放入数组返回) + __host__ int + pieceCircle( + Image *inimg, // 输入待检测的图形 + int piecenum, // 每个维度上分块数量 + int *circleMax, // 返回检测到的圆数量 + CircleParam *circleparam, // 返回检测到的圆参数 + bool writetofile // 是否把检测结果写到文件中 + ); + +}; + +#endif + diff --git a/okano_3_0/HoughLine.cu b/okano_3_0/HoughLine.cu new file mode 100644 index 0000000..4abe13a --- /dev/null +++ b/okano_3_0/HoughLine.cu @@ -0,0 +1,1466 @@ + // HoughLine.cu +// 实现 Hough 变换检测直线 + +#include "cuda_runtime.h" +#include +#include "device_launch_parameters.h" +#include +#include "HoughLine.h" +#include "Image.h" +#include +#include +#include + +using namespace std; + +#include "ErrorCode.h" +#include "CoordiSet.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:M_PI +// π 值。对于某些操作系统,M_PI 可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif +// ==========================全局函数声明============================== +// 根据输入点集的坐标,找到最上、最下、最左、最右的点,从而确定图像的宽和高。 +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax); +// ==========================Kernel函数声明============================== +static __global__ void // Kernel 函数无返回值 +_houghlineImgKer( + ImageCuda inimg, // 输入图像。 + int *bufhoughdev, // 得票数矩阵。 + int numtheta, // theta 的递增次数。 + int numrho, // rho 的递增次数。 + double detheta, // 每一次的角度增量。 + double derho // 每一次的距离增量。 +); + +// Kernel 函数:_houghlineCorKer(根据输入坐标集计算得票数) +// 根据输入坐标集,通过计算角度,距离等参数,计算最终的得票数。 +static __global__ void +_houghlineCorKer( + CoordiSet guidingset, // 输入坐标集。 + int *bufhoughdev, // 得票数矩阵。 + int numtheta, // theta 的递增次数。 + int numrho, // rho 的递增次数。 + double detheta, // 每一次的角度增量。 + double derho // 每一次的距离增量。 +); + +// Kernel 函数:_findlocalMaxKer(计算局部最大值) +static __global__ void +_findlocalMaxKer( + int *bufhoughdev, // 得票数矩阵。 + int *bufsortdev, // 局部最值矩阵。 + int *sumdev, // 存在的直线数。 + int numtheta, // theta 的递增次数。 + int threshold // 直线的阈值。 +); + +// Kernel 函数:_houghoutKer(画出已检测到的直线) +// 根据计算得到的直线的参数,得到输出图像。所有检测出来的直线, +// 在输出图像中用像素值 128 将其画出。 +static __global__ void +_houghoutKer( + ImageCuda outimg, // 输出图像。 + LineParam *lineparamdev, // 计算得到的直线参数。 + int linenum, // 最大待检测直线数量。 + int derho // 每一次的距离增量。 +); +// Kernel 函数:_realLineKer(检测给出线段的真实性)在inimg中检测参数给出的线段 +// 是真实线段,还是某个线段的延长线, +static __global__ void _realLineKer(ImageCuda inimg, + int x1, int y1, int x2, int y2, + int xmax, int xmin, int ymax, int ymin, + int delta, int *pointnumdev); +// ==========================Kernel函数定义============================== + +// Kernel 函数:_houghlineImgKer(根据输入图像计算得票数) +static __global__ void _houghlineImgKer(ImageCuda inimg, int *bufhoughdev, + int numtheta, int numrho, + double detheta, double derho) +{ + // 处理当前线程对应的图像点(c,r),其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量, + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if(c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 定义局部变量。 + unsigned char intemp; + int k, rho, bufidx; + float theta; + float irho = 1.0f / derho; + float tempr; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + intemp = inimg.imgMeta.imgData[inidx]; + + // 如果当前像素值为 255,即有效像素值,则对该像素点进行直线检测。 + if (intemp == 255) { + for (k = 0; k < numtheta; k++) { + // 计算当前的角度 theta。 + theta = k * detheta; + // 计算该角度 theta 对应直线另一个参数 rho 的值。 + tempr = (int)(c * cos(theta) * irho + r * sin(theta) * irho); + // 根据上一步结果进行四舍五入。 + rho = (int)(tempr + (tempr >= 0 ? 0.5f : -0.5f)); + rho += (numrho - 1) / 2; + + // 计算得到当前直线的两个参数 theta 和 rho 对应的累加器 + // bufferHough 中的索引。使用原子操作,统计得票数。 + bufidx = (rho + 1) * (numtheta + 2) + k + 1; + atomicAdd(&bufhoughdev[bufidx], 1); + } + } +} + +// Kernel 函数:_houghlineCorKer(根据输入坐标集计算得票数) +static __global__ void _houghlineCorKer(CoordiSet guidingset, int *bufhoughdev, + int numtheta, int numrho, + double detheta, double derho) +{ + // 计算计算当前线程的索引。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 处理coordiset中的点(dx,dy) + int dx = guidingset.tplData[2 * idx]; + int dy = guidingset.tplData[2 * idx + 1]; + + // 定义局部变量。 + int k, rho, bufidx; + float theta; + float irho = 1.0f / derho; + float tempr; + + // 计算得票数。 + for (k = 0; k < numtheta; k ++) { + // 计算当前的角度 theta。 + theta = k * detheta; + + // 计算该角度 theta 对应直线另一个参数 rho 的值。 + tempr = (int)(dx * cos(theta) * irho + dy * sin(theta) * irho); + rho = (int)(tempr + (tempr >= 0 ? 0.5f : -0.5f)); + rho += (numrho - 1) / 2; + + // 计算得到当前直线的两个参数 theta 和 rho 对应的累加器 + // bufferHough 中的索引。使用原子操作,统计得票数。 + bufidx = (rho + 1) * (numtheta + 2) + k + 1; + atomicAdd(&bufhoughdev[bufidx], 1); + } +} + +// Kernel 函数:_findlocalMaxKer(计算局部最大值) +static __global__ void _findlocalMaxKer( + int *bufhoughdev, int *bufsortdev, int *sumdev, + int numtheta, int threshold) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 计算该线程在块内的相对位置。 + int inindex = threadIdx.y * blockDim.x + threadIdx.x; + + // 申请共享内存,存该块内符合条件的局部最大值个数, + // 即存在的直线数。 + __shared__ int totalsum[1]; + + // 初始化所有块内的共享内存。 + if (inindex == 0) + totalsum[0] = 0; + // 块内同步。 + __syncthreads(); + + // 计算当前线程在 bufHough 矩阵中的对应索引值。 + int index = (r + 1) * (numtheta + 2) + (c + 1); + + // 当前线程的得票数大于直线阈值,并且大于邻域中的值时, + // 认为他是局部最大值,即可能是直线。 + if (bufhoughdev[index] > threshold && + bufhoughdev[index] > bufhoughdev[index - 1] && + bufhoughdev[index] >= bufhoughdev[index + 1] && + bufhoughdev[index] > bufhoughdev[index - numtheta - 2] && + bufhoughdev[index] >= bufhoughdev[index + numtheta + 2]) + { + bufsortdev[r * numtheta + c] = index; + // 使用原子操作对局部最大值进行统计。 + atomicAdd(&totalsum[0], 1); + } else { + bufsortdev[r * numtheta + c] = 0; + } + // 块内同步。 + __syncthreads(); + + // (0,0)号线程负责将本块(共32*8个线程)统计出的直线的存在数统计到 sumdev 中。 + if (inindex == 0 && totalsum[0] != 0) + atomicAdd(&sumdev[0], totalsum[0]); +} + +// Kernel 函数:_houghoutKer(画出已检测到的直线) +static __global__ void _houghoutKer(ImageCuda outimg, LineParam *lineparamdev, + int linenum, int derho) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省 + // 计算资源,一方面防止由于段错误导致的程序崩溃。 + if (c >= outimg.imgMeta.width || r >= outimg.imgMeta.height) + return; + + // 计算当前坐标点对应的图像数据数组下标。 + unsigned char *outptr; + outptr = outimg.imgMeta.imgData + c + r * outimg.pitchBytes; + + // 声明局部变量 + int i, temp; + float theta; + float irho = 1.0f / derho; + + // 对所有已经检测出的直线进行循环,找到输入图像中该点所在的直线, + // 并赋值 128。 + for (i = 0; i < linenum; i++) { + // 得到直线的参数 rho,theta。 + theta = lineparamdev[i].angle; + temp = (int)(c * cos(theta) * irho + r * sin(theta) * irho); + if (temp == lineparamdev[i].distance) + {*outptr = 255; + break; + } + } +} +// Kernel 函数:_realLineKer(检测给出线段的真实性)在inimg中检测参数给出的线段 +// 是真实线段,还是某个线段的延长线, +static __global__ void _realLineKer(ImageCuda inimg, + int x1, int y1, int x2, int y2, + int xmax, int xmin, int ymax, int ymin, + int delta, int *pointnumdev) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int cx=blockIdx.x * blockDim.x+threadIdx.x; + int ry=blockIdx.y * blockDim.y+threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if(cx >= inimg.imgMeta.width || ry >= inimg.imgMeta.height) + return ; + + int inidx, temp=0; + + // 检查当前点是否在线段带容许误差的包围盒范围内,不在的话,直接返回 + if( cx <= xmax+delta && cx >= xmin-delta && + ry <= ymax+delta && ry >= ymin-delta ) { + // 计算输入坐标点对应的图像数据数组下标。 + inidx=ry * inimg.pitchBytes+cx; + // 读取第一个输入坐标点对应的像素值。 + temp=inimg.imgMeta.imgData[inidx]; + // 如果当前点在线段上,或者误差小于门限值,计数器加一 + if(temp == 255){ + float dis=abs( (x2-x1) * (ry-y1)-(cx-x1) * ( y2-y1) ) / sqrt( (x2-x1) * (x2-x1) * 1.0+(y2-y1) * (y2-y1)); + if(discount); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(guidingset, tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 初始化 x 和 y 方向上的最小最大值。 + xmin[0] = xmax[0] = tmpcoordiset->tplData[0]; + ymin[0] = ymax[0] = tmpcoordiset->tplData[1]; + // 循环寻找坐标集最左、最右、最上、最下的坐标。 + for (i = 1;i < tmpcoordiset->count;i++) { + // 寻找 x 方向上的最小值。 + if (xmin[0] > tmpcoordiset->tplData[2 * i]) + xmin[0] = tmpcoordiset->tplData[2 * i]; + // 寻找 x 方向上的最大值 + if (xmax[0] < tmpcoordiset->tplData[2 * i]) + xmax[0] = tmpcoordiset->tplData[2 * i]; + + // 寻找 y 方向上的最小值。 + if (ymin[0] > tmpcoordiset->tplData[2 * i + 1]) + ymin[0] = tmpcoordiset->tplData[2 * i + 1]; + // 寻找 y 方向上的最大值 + if (ymax[0] < tmpcoordiset->tplData[2 * i + 1]) + ymax[0] = tmpcoordiset->tplData[2 * i + 1]; + } + + // 释放临时坐标集变量。 + CoordiSetBasicOp::deleteCoordiSet(tmpcoordiset); + + return errcode; +} + +// ==========================成员函数定义============================== + +// 宏:FAIL_HOUGH_LINE_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_HOUGH_LINE_FREE do { \ + if (alldatadev != NULL) \ + cudaFree(alldatadev); \ + if (alldata != NULL) \ + delete[] alldata; \ + if (linedata != NULL) \ + delete[] linedata; \ + if (line != NULL) \ + delete[] line; \ + } while (0) + +// Host 成员方法:houghlineCor(Hough 变换检测直线) +__host__ int HoughLine::houghLineCor(CoordiSet *guidingset, + int *linesmax, + LineParam *lineparam) +{ + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 得到输入图像的宽和高。 + int width, height; + int xmin, ymin, xmax, ymax; + + if (guidingset != NULL) { + // 输入图像为空,则根据输入点集得到最左、 + // 最右、最上、最下的坐标值。 + errcode = _findMinMaxCoordinates(guidingset, &xmin, &ymin, + &xmax, &ymax); + if (errcode != NO_ERROR) + return errcode; + + // 计算得票数矩阵的宽和高。 + width = xmax-xmin ; + height = ymax-ymin; + + } + + // 计算rho 和 theta 的递增次数。为减少计算,numrho用了近似值的距离最大值。 + int numrho = (int)((width + height) * 2 + 1) / derho; + int numtheta = (int)(M_PI / detheta); + + // 声明需要的指针变量。 + int *alldatadev = NULL; + int *alldata = NULL; + int *linedata = NULL; + LineParam *line = NULL; + LineParam *lineparamdev = NULL; + + // 一次性申请 Device 端需要的所有空间。 + int *bufhoughdev = NULL, *bufsortdev = NULL, *sumdev = NULL; + cudaError_t cudaerrcode; + cudaerrcode = cudaMalloc((void **)&alldatadev, + (1 + (numtheta + 2) * (numrho + 2) + + numtheta * numrho) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 通过偏移得到各指针的地址。 + sumdev = alldatadev; + bufhoughdev = alldatadev + 1; + bufsortdev = alldatadev + 1 + (numtheta + 2) * (numrho + 2); + + // 初始化 Hough 变换累加器在 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldatadev, 0, + (1 + (numtheta + 2) * (numrho + 2) + + numtheta * numrho) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + + + // 根据输入坐标集是否为空,分两种情况进行: + if (guidingset != NULL) { + // 若输入坐标集不为空,则将该点集拷贝入 Device 内存。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(guidingset); + if (errcode != NO_ERROR) + return errcode; + + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = 16; + blocksize.y = 1; + gridsize.x = (guidingset->count+15)/16; + gridsize.y = 1; + + // 调用核函数,对输入坐标集 guidingset 计算 Hough 累加矩阵。 + _houghlineCorKer<<>>(*guidingset, bufhoughdev, + numtheta, numrho, + detheta, derho); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + } + + // 在 Host 端一次性申请全部所需的空间。 + int *bufHough = NULL, *bufsort = NULL; + int sum; + alldata = new int [(numtheta + 2) * (numrho + 2) + numtheta * numrho]; + if (alldata == NULL) + return OUT_OF_MEM; + + // 通过偏移得到各指针的地址。 + bufHough = alldata; + bufsort = alldata + (numtheta + 2) * (numrho + 2); + + // 将 Kernel 函数中计算的得票数矩阵 bufHoughDev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(bufHough, bufhoughdev, + (numtheta + 2) * (numrho + 2) * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (numtheta + blocksize.x - 1) / blocksize.x; + gridsize.y = (numrho + blocksize.y - 1) / blocksize.y; + + // 调用计算局部最大值的 kernel 函数。 + _findlocalMaxKer<<>>(bufhoughdev, bufsortdev, sumdev, + numtheta, threshold); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + + // 将 Kernel 函数中计算的得票数 sumdev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(&sum, sumdev, sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 将 Kernel 函数中计算的得票数矩阵 bufsortdev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(bufsort, bufsortdev, + numtheta * numrho * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 在 Host 端申请存放直线得票数和索引的数组。 + int *linevote = NULL, *lineindex = NULL; + + // 根据计算出存在的直线数,一次性申请所需空间。 + linedata = new int [sum * 2]; + if (linedata == NULL) + return OUT_OF_MEM; + linevote = linedata; + lineindex = linedata + sum; + // 局部变量。 + int k = 0, temp; + + // 统计可能存在的直线的得票数和索引值。 + for (int j = 0; j < numrho; j++) { + for (int i = 0; i < numtheta; i++){ + temp = j * numtheta + i; + if (bufsort[temp] != 0) { + // 将直线的索引值赋值到 lineindex 数组。 + lineindex[k] = bufsort[temp]; + // 将直线的得票数赋值到 linevote 数组。 + linevote[k] = bufHough[bufsort[temp]]; + k++; + } + } + } + + // 使用希尔排序,以得票数递减的顺序,为存在的直线排序。 + int i, j, tempvote, tempindex; + // 希尔排序的增量。 + int gap = sum >> 1; + + while(gap > 0) { + // 对所有相隔 gap 位置的所有元素采用直接插入排序。 + for (i = gap; i < sum;i++) { + tempvote = linevote[i]; + tempindex = lineindex[i]; + j = i - gap; + // 对相隔 gap 位置的元素进行排序。 + while (j >= 0 && tempvote > linevote[j]) { + linevote[j + gap] = linevote[j]; + lineindex[j + gap] = lineindex[j]; + j = j - gap; + } + linevote[j + gap] = tempvote; + lineindex[j + gap] = tempindex; + j = j - gap; + } + // 减小增量。 + gap = gap >> 1; + } + + // 申请直线返回参数结构体,保存找到的可能直线。 + line = new LineParam[sum]; + if (line == NULL) + return OUT_OF_MEM; + + // 计算检测出的直线的参数:rho 以及 theta 的值,并 + // 保存在参数结构体中。 + float scale; + scale = 1.0 / (numtheta + 2); + for (int i = 0; i < sum; i++) { + int idx = lineindex[i]; + int rho = (int)(idx * scale) - 1; + // 根据原始计算方法反计算出 theta 的值。 + int theta = idx - (rho + 1) * (numtheta + 2) - 1; + line[i].angle = theta * detheta; + // 计算出直线参数 rho 的值。 + rho =(int)(rho - ((numrho - 1) / 2) - ((rho >= 0) ? -0.5f : 0.5f)); + line[i].distance = rho; + // 将得票数保存在直线参数结构体中。 + line[i].votes = linevote[i]; + } + + // 统计最终检测的直线的个数。 + int linenum = 0; + int diffdis,diffdis2; + float diffang,diffang2; + for (int i = 0; i < sum; i++) { + // 若当前直线的参数结构体的得票数为 0,0是认为重复的直线 + // 则直接进行下次循环。 + if (line[i].votes <= 0) + continue; + for (int j = i + 1; j < sum; j++) { + // 计算两条直线距离和角度的差值。 + diffang=abs(line[i].angle-line[j].angle); + diffdis=abs(line[i].distance-line[j].distance); + // 角度为1度和179度也很相似 + diffang2=abs(M_PI-line[i].angle-line[j].angle); + // 角度相差180时,dis值异号,相加才相当于他们之差 + diffdis2=abs(line[i].distance+line[j].distance); + // 若距离和角度的差值均小于设定的阈值, + // 则认为这两条直线实质上是一条直线。 + if ( (diffdisthresang) + || (diffdis2thresang)){ + line[j].angle = 0.0f; + line[j].distance = 0; + line[j].votes = 0; + } + } + // 检测出的直线数加 1。 + linenum++; + } + + // 检测出的最大直线数。 + // 检测出的最大直线数是期望检测的最大直线数 linenum 和 + // 存在的直线数 linesmax[0] 二者中的较小值。 + linesmax[0] = (linenum < linesmax[0]) ? linenum : linesmax[0]; + + // 将最终检测的直线的参数赋值到需要返回的直线参数结构体中。 + int n = 0; + for (int i = 0; i < sum; i++) { + // 若得票数不为 0,说明是检测出的直线, + // 赋值到直线参数返回结构体中。 + if (n == linesmax[0]) + break; + if (line[i].votes > 0) { + lineparam[n].angle = line[i].angle; + lineparam[n].distance = line[i].distance; + lineparam[n].votes = line[i].votes; + // 标记加 1。 + n++; + } + } + + + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + cudaFree(lineparamdev); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:houghline(Hough 变换检测直线) +__host__ int HoughLine::houghLine(Image *inimg, CoordiSet *guidingset, + int *linesmax, + LineParam *lineparam) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL && guidingset == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 得到输入图像的宽和高。 + int width, height; + int xmin, ymin, xmax, ymax; + + if (guidingset != NULL) { + // 输入图像为空,则根据输入点集得到最左、 + // 最右、最上、最下的坐标值。 + errcode = _findMinMaxCoordinates(guidingset, &xmin, &ymin, + &xmax, &ymax); + if (errcode != NO_ERROR) + return errcode; + + // 计算得票数矩阵的宽和高。 + width = xmax-xmin ; + height = ymax-ymin; + + } else { + // 输入图像不为空,则根据输入图像的尺寸得到图像需要处理部分的宽和高。 + width = inimg->roiX2-inimg->roiX1; + height = inimg->roiY2-inimg->roiY1; + } + + // 计算rho 和 theta 的递增次数。为减少计算,numrho用了近似值的距离最大值。 + int numrho = (int)((width + height) * 2 + 1) / derho; + int numtheta = (int)(M_PI / detheta); + + // 声明需要的指针变量。 + int *alldatadev = NULL; + int *alldata = NULL; + int *linedata = NULL; + LineParam *line = NULL; + LineParam *lineparamdev = NULL; + + // 一次性申请 Device 端需要的所有空间。 + int *bufhoughdev = NULL, *bufsortdev = NULL, *sumdev = NULL; + cudaError_t cudaerrcode; + cudaerrcode = cudaMalloc((void **)&alldatadev, + (1 + (numtheta + 2) * (numrho + 2) + + numtheta * numrho) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 通过偏移得到各指针的地址。 + sumdev = alldatadev; + bufhoughdev = alldatadev + 1; + bufsortdev = alldatadev + 1 + (numtheta + 2) * (numrho + 2); + + // 初始化 Hough 变换累加器在 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldatadev, 0, + (1 + (numtheta + 2) * (numrho + 2) + + numtheta * numrho) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + + + // 根据输入坐标集是否为空,分两种情况进行: + if (guidingset != NULL) { + // 若输入坐标集不为空,则将该点集拷贝入 Device 内存。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(guidingset); + if (errcode != NO_ERROR) + return errcode; + + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = 16; + blocksize.y = 1; + gridsize.x = (guidingset->count+15)/16; + gridsize.y = 1; + + // 调用核函数,对输入坐标集 guidingset 计算 Hough 累加矩阵。 + _houghlineCorKer<<>>(*guidingset, bufhoughdev, + numtheta, numrho, + detheta, derho); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + } else { + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + + + // 若输入坐标集guidingset为空 + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / + blocksize.y; + + // 调用核函数,对输入图像计算 Hough 累加矩阵。 + _houghlineImgKer<<>>(insubimgCud, bufhoughdev, + numtheta, numrho, + detheta, derho); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + } + + // 在 Host 端一次性申请全部所需的空间。 + int *bufHough = NULL, *bufsort = NULL; + int sum; + alldata = new int [(numtheta + 2) * (numrho + 2) + numtheta * numrho]; + if (alldata == NULL) + return OUT_OF_MEM; + + // 通过偏移得到各指针的地址。 + bufHough = alldata; + bufsort = alldata + (numtheta + 2) * (numrho + 2); + + // 将 Kernel 函数中计算的得票数矩阵 bufHoughDev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(bufHough, bufhoughdev, + (numtheta + 2) * (numrho + 2) * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (numtheta + blocksize.x - 1) / blocksize.x; + gridsize.y = (numrho + blocksize.y - 1) / blocksize.y; + + // 调用计算局部最大值的 kernel 函数。 + _findlocalMaxKer<<>>(bufhoughdev, bufsortdev, sumdev, + numtheta, threshold); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + + // 将 Kernel 函数中计算的得票数 sumdev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(&sum, sumdev, sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 将 Kernel 函数中计算的得票数矩阵 bufsortdev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(bufsort, bufsortdev, + numtheta * numrho * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 在 Host 端申请存放直线得票数和索引的数组。 + int *linevote = NULL, *lineindex = NULL; + + // 根据计算出存在的直线数,一次性申请所需空间。 + linedata = new int [sum * 2]; + if (linedata == NULL) + return OUT_OF_MEM; + linevote = linedata; + lineindex = linedata + sum; + // 局部变量。 + int k = 0, temp; + + // 统计可能存在的直线的得票数和索引值。 + for (int j = 0; j < numrho; j++) { + for (int i = 0; i < numtheta; i++){ + temp = j * numtheta + i; + if (bufsort[temp] != 0) { + // 将直线的索引值赋值到 lineindex 数组。 + lineindex[k] = bufsort[temp]; + // 将直线的得票数赋值到 linevote 数组。 + linevote[k] = bufHough[bufsort[temp]]; + k++; + } + } + } + + // 使用希尔排序,以得票数递减的顺序,为存在的直线排序。 + int i, j, tempvote, tempindex; + // 希尔排序的增量。 + int gap = sum >> 1; + + while(gap > 0) { + // 对所有相隔 gap 位置的所有元素采用直接插入排序。 + for (i = gap; i < sum;i++) { + tempvote = linevote[i]; + tempindex = lineindex[i]; + j = i - gap; + // 对相隔 gap 位置的元素进行排序。 + while (j >= 0 && tempvote > linevote[j]) { + linevote[j + gap] = linevote[j]; + lineindex[j + gap] = lineindex[j]; + j = j - gap; + } + linevote[j + gap] = tempvote; + lineindex[j + gap] = tempindex; + j = j - gap; + } + // 减小增量。 + gap = gap >> 1; + } + + + + // 申请直线返回参数结构体,保存找到的可能直线。 + line = new LineParam[sum]; + if (line == NULL) + return OUT_OF_MEM; + + // 计算检测出的直线的参数:rho 以及 theta 的值,并 + // 保存在参数结构体中。 + float scale; + scale = 1.0 / (numtheta + 2); + for (int i = 0; i < sum; i++) { + int idx = lineindex[i]; + int rho = (int)(idx * scale) - 1; + // 根据原始计算方法反计算出 theta 的值。 + int theta = idx - (rho + 1) * (numtheta + 2) - 1; + line[i].angle = theta * detheta; + // 计算出直线参数 rho 的值。 + rho =(int)(rho - ((numrho - 1) / 2) - ((rho >= 0) ? -0.5f : 0.5f)); + line[i].distance = rho; + // 将得票数保存在直线参数结构体中。 + line[i].votes = linevote[i]; + } + + // 统计最终检测的直线的个数。 + int linenum = 0; + int diffdis,diffdis2; + float diffang,diffang2; + for (int i = 0; i < sum; i++) { + // 若当前直线的参数结构体的得票数为 0,0是认为重复的直线 + // 则直接进行下次循环。 + if (line[i].votes <= 0) + continue; + for (int j = i + 1; j < sum; j++) { + // 计算两条直线距离和角度的差值。 + diffang=abs(line[i].angle-line[j].angle); + diffdis=abs(line[i].distance-line[j].distance); + // 角度为1度和179度也很相似 + diffang2=abs(M_PI-line[i].angle-line[j].angle); + // 角度相差180时,dis值异号,相加才相当于他们之差 + diffdis2=abs(line[i].distance+line[j].distance); + // 若距离和角度的差值均小于设定的阈值, + // 则认为这两条直线实质上是一条直线。 + if ( (diffdisthresang) + || (diffdis2thresang)){ + line[j].angle = 0.0f; + line[j].distance = 0; + line[j].votes = 0; + } + } + // 检测出的直线数加 1。 + linenum++; + } + + // 检测出的最大直线数。 + // 检测出的最大直线数是期望检测的最大直线数 linenum 和 + // 存在的直线数 linesmax[0] 二者中的较小值。 + linesmax[0] = (linenum < linesmax[0]) ? linenum : linesmax[0]; + + // 将最终检测的直线的参数赋值到需要返回的直线参数结构体中。 + int n = 0; + for (int i = 0; i < sum; i++) { + // 若得票数不为 0,说明是检测出的直线, + // 赋值到直线参数返回结构体中。 + if (n == linesmax[0]) + break; + if (line[i].votes > 0) { + lineparam[n].angle = line[i].angle; + lineparam[n].distance = line[i].distance; + lineparam[n].votes = line[i].votes; + // 标记加 1。 + n++; + } + } + + + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + cudaFree(lineparamdev); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:houghlineimg(Hough 变换检测直线) +__host__ int HoughLine::houghLineImg(Image *inimg, CoordiSet *guidingset, + Image *outimg, int *linesmax, + LineParam *lineparam) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL && guidingset == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 得到输入图像的宽和高。 + int width, height; + int xmin, ymin, xmax, ymax; + + if (guidingset != NULL) { + // 输入图像为空,则根据输入点集得到最左、 + // 最右、最上、最下的坐标值。 + errcode = _findMinMaxCoordinates(guidingset, &xmin, &ymin, + &xmax, &ymax); + if (errcode != NO_ERROR) + return errcode; + + // 计算得票数矩阵的宽和高。 + width = xmax-xmin ; + height = ymax-ymin; + + } else { + // 输入图像不为空,则根据输入图像的尺寸得到图像需要处理部分的宽和高。 + width = inimg->roiX2-inimg->roiX1; + height = inimg->roiY2-inimg->roiY1; + } + + // 计算rho 和 theta 的递增次数。为减少计算,numrho用了近似值的距离最大值。 + int numrho = (int)((width + height) * 2 + 1) / derho; + int numtheta = (int)(M_PI / detheta); + + // 声明需要的指针变量。 + int *alldatadev = NULL; + int *alldata = NULL; + int *linedata = NULL; + LineParam *line = NULL; + LineParam *lineparamdev = NULL; + + // 一次性申请 Device 端需要的所有空间。 + int *bufhoughdev = NULL, *bufsortdev = NULL, *sumdev = NULL; + cudaError_t cudaerrcode; + cudaerrcode = cudaMalloc((void **)&alldatadev, + (1 + (numtheta + 2) * (numrho + 2) + + numtheta * numrho) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 通过偏移得到各指针的地址。 + sumdev = alldatadev; + bufhoughdev = alldatadev + 1; + bufsortdev = alldatadev + 1 + (numtheta + 2) * (numrho + 2); + + // 初始化 Hough 变换累加器在 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldatadev, 0, + (1 + (numtheta + 2) * (numrho + 2) + + numtheta * numrho) * sizeof (int)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + ImageCuda outsubimgCud; + + // 根据输入坐标集是否为空,分两种情况进行: + if (guidingset != NULL) { + // 若输入坐标集不为空,则将该点集拷贝入 Device 内存。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(guidingset); + if (errcode != NO_ERROR) + return errcode; + + outimg->width = xmax; + outimg->height = ymax; + // 将输出图片拷贝至 Device 端。 + ImageBasicOp::copyToCurrentDevice(outimg); + + // 提取输出图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + outsubimgCud.imgMeta.width = xmax; + outsubimgCud.imgMeta.height = ymax; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = 16; + blocksize.y = 1; + gridsize.x = (guidingset->count+15)/16; + gridsize.y = 1; + + // 调用核函数,对输入坐标集 guidingset 计算 Hough 累加矩阵。 + _houghlineCorKer<<>>(*guidingset, bufhoughdev, + numtheta, numrho, + detheta, derho); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + } else { + // 将输入图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 若输入坐标集guidingset为空 + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / + blocksize.y; + + // 调用核函数,对输入图像计算 Hough 累加矩阵。 + _houghlineImgKer<<>>(insubimgCud, bufhoughdev, + numtheta, numrho, + detheta, derho); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + } + + // 在 Host 端一次性申请全部所需的空间。 + int *bufHough = NULL, *bufsort = NULL; + int sum; + alldata = new int [(numtheta + 2) * (numrho + 2) + numtheta * numrho]; + if (alldata == NULL) + return OUT_OF_MEM; + + // 通过偏移得到各指针的地址。 + bufHough = alldata; + bufsort = alldata + (numtheta + 2) * (numrho + 2); + + // 将 Kernel 函数中计算的得票数矩阵 bufHoughDev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(bufHough, bufhoughdev, + (numtheta + 2) * (numrho + 2) * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 计算调用计算局部最大值的 kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (numtheta + blocksize.x - 1) / blocksize.x; + gridsize.y = (numrho + blocksize.y - 1) / blocksize.y; + + // 调用计算局部最大值的 kernel 函数。 + _findlocalMaxKer<<>>(bufhoughdev, bufsortdev, sumdev, + numtheta, threshold); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return CUDA_ERROR; + } + + // 将 Kernel 函数中计算的得票数 sumdev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(&sum, sumdev, sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 将 Kernel 函数中计算的得票数矩阵 bufsortdev 拷贝至 Host 端。 + cudaerrcode = cudaMemcpy(bufsort, bufsortdev, + numtheta * numrho * sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 在 Host 端申请存放直线得票数和索引的数组。 + int *linevote = NULL, *lineindex = NULL; + + // 根据计算出存在的直线数,一次性申请所需空间。 + linedata = new int [sum * 2]; + if (linedata == NULL) + return OUT_OF_MEM; + linevote = linedata; + lineindex = linedata + sum; + // 局部变量。 + int k = 0, temp; + + // 统计可能存在的直线的得票数和索引值。 + for (int j = 0; j < numrho; j++) { + for (int i = 0; i < numtheta; i++){ + temp = j * numtheta + i; + if (bufsort[temp] != 0) { + // 将直线的索引值赋值到 lineindex 数组。 + lineindex[k] = bufsort[temp]; + // 将直线的得票数赋值到 linevote 数组。 + linevote[k] = bufHough[bufsort[temp]]; + k++; + } + } + } + + // 使用希尔排序,以得票数递减的顺序,为存在的直线排序。 + int i, j, tempvote, tempindex; + // 希尔排序的增量。 + int gap = sum >> 1; + + while(gap > 0) { + // 对所有相隔 gap 位置的所有元素采用直接插入排序。 + for (i = gap; i < sum;i++) { + tempvote = linevote[i]; + tempindex = lineindex[i]; + j = i - gap; + // 对相隔 gap 位置的元素进行排序。 + while (j >= 0 && tempvote > linevote[j]) { + linevote[j + gap] = linevote[j]; + lineindex[j + gap] = lineindex[j]; + j = j - gap; + } + linevote[j + gap] = tempvote; + lineindex[j + gap] = tempindex; + j = j - gap; + } + // 减小增量。 + gap = gap >> 1; + } + + // 申请直线返回参数结构体,保存找到的可能直线。 + line = new LineParam[sum]; + if (line == NULL) + return OUT_OF_MEM; + + // 计算检测出的直线的参数:rho 以及 theta 的值,并 + // 保存在参数结构体中。 + float scale; + scale = 1.0 / (numtheta + 2); + for (int i = 0; i < sum; i++) { + int idx = lineindex[i]; + int rho = (int)(idx * scale) - 1; + // 根据原始计算方法反计算出 theta 的值。 + int theta = idx - (rho + 1) * (numtheta + 2) - 1; + line[i].angle = theta * detheta; + // 计算出直线参数 rho 的值。 + rho =(int)(rho - ((numrho - 1) / 2) - ((rho >= 0) ? -0.5f : 0.5f)); + line[i].distance = rho; + // 将得票数保存在直线参数结构体中。 + line[i].votes = linevote[i]; + } + + // 统计最终检测的直线的个数。 + int linenum = 0; + int diffdis,diffdis2; + float diffang,diffang2; + for (int i = 0; i < sum; i++) { + // 若当前直线的参数结构体的得票数为 0,0是认为重复的直线 + // 则直接进行下次循环。 + if (line[i].votes <= 0) + continue; + for (int j = i + 1; j < sum; j++) { + // 计算两条直线距离和角度的差值。 + diffang=abs(line[i].angle-line[j].angle); + diffdis=abs(line[i].distance-line[j].distance); + // 角度为1度和179度也很相似 + diffang2=abs(M_PI-line[i].angle-line[j].angle); + // 角度相差180时,dis值异号,相加才相当于他们之差 + diffdis2=abs(line[i].distance+line[j].distance); + // 若距离和角度的差值均小于设定的阈值, + // 则认为这两条直线实质上是一条直线。 + if ( (diffdisthresang) + || (diffdis2thresang)){ + line[j].angle = 0.0f; + line[j].distance = 0; + line[j].votes = 0; + } + } + // 检测出的直线数加 1。 + linenum++; + } + + // 检测出的最大直线数。 + // 检测出的最大直线数是期望检测的最大直线数 linenum 和 + // 存在的直线数 linesmax[0] 二者中的较小值。 + linesmax[0] = (linenum < linesmax[0]) ? linenum : linesmax[0]; + + // 将最终检测的直线的参数赋值到需要返回的直线参数结构体中。 + int n = 0; + for (int i = 0; i < sum; i++) { + // 若得票数不为 0,说明是检测出的直线, + // 赋值到直线参数返回结构体中。 + if (n == linesmax[0]) + break; + if (line[i].votes > 0) { + lineparam[n].angle = line[i].angle; + lineparam[n].distance = line[i].distance; + lineparam[n].votes = line[i].votes; + + // 标记加 1。 + n++; + } + } + + // 在 Device 端申请内存空间用于存储直线的返回参数。 + cudaerrcode = cudaMalloc((void **)&lineparamdev, + linesmax[0] * sizeof (LineParam)); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + return cudaerrcode; + } + + // 将计算得到的直线返回参数从 Host 端拷贝到 Device 端。 + cudaerrcode = cudaMemcpy(lineparamdev, lineparam, + linesmax[0] * sizeof (LineParam), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + cudaFree(lineparamdev); + return cudaerrcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用 kernel函数,得出最终输出图像。 + _houghoutKer<<>>(outsubimgCud, lineparamdev, + linesmax[0], derho); + if (cudaGetLastError() != cudaSuccess) { + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + cudaFree(lineparamdev); + return CUDA_ERROR; + } + + // 释放内存空间。 + FAIL_HOUGH_LINE_FREE; + cudaFree(lineparamdev); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:getGlobalParam(ROI局部直线参数转换成为全局坐标下的参数) +// 把 hough 变换检测到的直线参数转换成为全局坐标下的参数 +__host__ int // 返回值:函数是否正确执行,若函数 + // 否则返回假 + HoughLine::getGlobalParam( + Image *inimg, // 输入图像 + int *linesmax, // 检测直线的最大数量 + LineParam *lineparam // 直线返回参数结构体 +){ + int rx=inimg->roiX1; + int ry=inimg->roiY1; + if(rx==0 && ry==0) + return NO_ERROR; + + for (int i=0; i<*linesmax; i++) + lineparam[i].distance=lineparam[i].distance+ + rx*cos(lineparam[i].angle)+ + ry*sin(lineparam[i].angle); + return NO_ERROR; +} + +// Host 成员方法:realLine(判断给出线段的真实性) +__host__ bool HoughLine::realLine( + Image *inimg, // 输入图像 + int x1, // 要判断的线段两端点坐标 + int y1, + int x2, + int y2, + float threshold, // 点是否在线段上的误差范围参数,1-3 + float thresperc // 线段真实性判定阈值,线段上有效点和线段理论上应该有的 + // 点的比值超过此阈值,认为线段真实存在 +){ + // 对端点x、y坐标进行排序,方便判断范围 + int xmax, xmin, ymax, ymin; + if(x1>x2){ + xmax=x1; + xmin=x2; + } + else{ + xmax=x2; + xmin=x1; + } + + if(y1>y2){ + ymax=y1; + ymin=y2; + } + else{ + ymax=y2; + ymin=y1; + } + + // 正常线段应该的有效点个数 + int pointnumfull=sqrt(0.0+(x1-x2) * (x1-x2)+(y1-y2) * (y1-y2)); + // 显存空间,用来存储内核函数计算出来的线段有效点个数 + int *pointnumdev=NULL; + int cudaerrcode=cudaMalloc((void **)&pointnumdev, sizeof (int)); + if (cudaerrcode != cudaSuccess) + { + // 释放内存空间。 + cudaFree(pointnumdev); + return cudaerrcode; + } + cudaerrcode=cudaMemset(pointnumdev, 0, sizeof (int)); + if (cudaerrcode != cudaSuccess) + { + // 释放内存空间。 + cudaFree(pointnumdev); + return cudaerrcode; + } + // 将输入图像拷贝入 Device 内存。 + int errcode=ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode=ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x=DEF_BLOCK_X; + blocksize.y=DEF_BLOCK_Y; + gridsize.x=(insubimgCud.imgMeta.width+blocksize.x-1) / blocksize.x; + gridsize.y=(insubimgCud.imgMeta.height+blocksize.y-1) / blocksize.y; + // 调用 kernel函数,计算线段上有效点个数 + _realLineKer <<< gridsize, blocksize>>>(insubimgCud, x1, y1, x2, y2, + xmax, xmin, ymax, ymin, + threshold, pointnumdev); + // 把显存数据复制到内存中 + int pointnum=0; + cudaerrcode=cudaMemcpy(&pointnum, pointnumdev, + sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess){ + // 释放内存空间。 + cudaFree(pointnumdev); + return cudaerrcode; + } + // 判断计算得到的有效点数是否合理,从而判断线段真实性 +#ifdef DEBUG + cout << endl << "pointnum=" << pointNum << ", pointnumfull=" << pointNumfull << endl; +#endif + if(pointnum>pointnumfull *thresperc) + return true; + else + return false; +} + +// 取消前面的宏定义。 +#undef FAIL_HOUGH_LINE_FREE + diff --git a/okano_3_0/HoughLine.h b/okano_3_0/HoughLine.h new file mode 100644 index 0000000..d91354d --- /dev/null +++ b/okano_3_0/HoughLine.h @@ -0,0 +1,332 @@ +// HoughLine.h +// 创建人:侯怡婷 +// +// Hough变换检测直线(HoughLine) +// 功能说明:实现 Hough 变换检测直线。输入参数为坐标集 guidingset 或者输 +// 入图像 inimg,若 guidingset 不为空,则优先处理该坐标集;若为 +// 空,则对图像 inimg 进行直线检测。直线采用角度-距离形式表示,检 +// 测的角度范围为 0 - π。 +// 需要注意,如果的是图像,且该图像roi区域不是整个图像,则得到直线 +// 参数是以roi区域左上角为原点的,如果需要全局坐标下参数,则需要调用 +// getGlobalParam()函数转换一下。如果处理的是coordiset坐标集则因为 +// coordiset不存在ROI区域,得到的直线参数永远是全局坐标。 +// 修订历史: +// 2012年09月15日(侯怡婷) +// 初始版本 +// 2012年10月15日(侯怡婷) +// 完成计算得票数的 kernel 函数。 +// 2012年10月24日(侯怡婷) +// 对头文件隐藏的错误进行修改。 +// 2012年10月28日(侯怡婷) +// 完成直线检测的剩余操作。 +// 2012年10月31日(侯怡婷) +// 更改注释规范。 +// 2012年11月05日(侯怡婷) +// 完善了代码执行错误时的内存释放,合并可以统一申请的内存空间。 +// 2012年11月15日(侯怡婷) +// 加入对坐标集的 Hough 变换处理。 +// 2012年11月19日(侯怡婷) +// 对坐标集的 Hough 变换进行测试并修改注释规范。 +// 2012年12月02日(侯怡婷) +// 对 Hough 变换中计算直线距离参数的公式做修改,以便检测 +// 出更多的直线。 +// 2012年12月04日(侯怡婷) +// 增加没有输出图像的接口,算法只返回检测出的直线的参数。 +// 2012年12月05日(侯怡婷) +// 增加只处理点集的接口,算法只返回检测出的直线的参数。 +// 2012年12月28日(侯怡婷) +// 删除申请的多余的 Device 端空间。 +// 2013年01月06日(侯怡婷) +// 修改部分注释。 +// 2013年01月13日(侯怡婷) +// 修改计算局部最大值时的错误。 +// 2013年01月15日(侯怡婷) +// 增加根据距离判断检测出的两条直线是否是一条。 +// 2013年08月15日(侯怡婷) +// 增加根据距离判断检测出的两条直线是否是一条。 +// 2013.07.02 (曹建立)修复相似直线合并未时未考虑两直线相差180度的bug这也解释 +// 了为什么之前结果图中其他方向直线顺利合并,而垂直直线经常是多条未合并现象 +// 2013年08月19日(曹建立) +// 增加线段真实性判断算法 +// 2013年10月13日(曹建立) +// 增加参数为图像时,ROI区域不是全部图像时,局部坐标参数转化全局坐标参数函数 + +#ifndef __HOUGHLINE_H__ +#define __HOUGHLINE_H__ + +#include "Image.h" +#include "CoordiSet.h" +#include "ErrorCode.h" + +// 宏:M_PI +// π 值。对于某些操作系统,M_PI 可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +// 结构体:lineParam(直线返回参数) +// 将直线的参数:与横轴的夹角,与坐标轴原点的距离等定义为 +// 结构体,作为函数最终的输出结果。 +typedef struct LineParam_st { + float angle; // 与横轴的夹角,角度采用弧度制。 + int distance; // 与坐标原点的距离。 + int votes; // 得票数,即 BufHough 矩阵中的数据。 +} LineParam; + +// 类:HoughLine +// 继承自:无 +// 实现 Hough 变换检测直线。输入参数为坐标集 guidingset 或 +// 者输入图像 inimg,若 guidingset 不为空,则只处理该坐标集; +// 若为空,则对图像 inimg 进行直线检测。直线采用极坐标 +// 形式表示,检测的角度范围为 0 —— π。 +class HoughLine { + +protected: + + // 成员变量: detheta(最小角度单位,弧度制) + // 描述在检测直线时,每一次的角度增量,角度采用弧度制。 + double detheta; + + // 成员变量: derho(最小距离单位) + // 描述在检测直线时,每一次的距离增量。 + double derho; + + // 成员变量:threshold(直线阈值) + // 若累加器中相应的累计值大于该参数,则认为是 + // 一条直线,则函数返回这条线段。 + int threshold; + + // 成员变量:thresang(区别两条直线的最小角度,弧度制) + // 若两条检测出来的直线的参数 angle, + // 即与坐标轴横轴的夹角小于该参数,则认为 + // 这两条直线实质上是一条。 + float thresang; + + // 成员变量:thresdis(区别两条直线的最小距离) + // 若两条检测出来的直线的参数 distance, + // 即与坐标原点的距离小于该参数,则认为 + // 这两条直线实质上是一条。 + int thresdis; + + + +public: + // 构造函数:HoughLine + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + HoughLine() + { + // 使用默认值为类的各个成员变量赋值。 + this->detheta = M_PI / 180; // 最小角度单位设置为 π / 180。 + this->derho = 1.0; // 最小距离单位设置为 1.0。 + this->threshold = 90; // 直线阈值默认为 90。 + this->thresang = M_PI/180*5.0f;// 区别两条直线的最小角度设置为 5.0(弧度制) + this->thresdis = 50; // 区别两条直线的最小距离设置为 50。 + } + + // 构造函数:HoughLine + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + HoughLine( + double detheta, // 最小角度单位 + double derho, // 最小距离单位 + int threshold, // 直线阈值 + float thresang, // 区别两条直线的最小角度(弧度制)。 + int thresdis // 区别两条直线的最小距离。 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中 + // 给了非法的初始值而使系统进入一个未知的状态。 + this->detheta = M_PI / 180; // 最小角度单位设置为 π / 180。 + this->derho = 1.0; // 最小距离单位设置为 1.0。 + this->threshold = 90; // 直线阈值默认为 90。 + this->thresang =M_PI/180*5.0;// 区别两条直线的最小角度设置为 5.0(弧度制)。 + this->thresdis = 50; // 区别两条直线的最小距离设置为 50。 + + // 根据参数列表中的值设定成员变量的初值 + setDeTheta(detheta); + setDeRho(derho); + setThreshold(threshold); + setThresAng(thresang); + setThresDis(thresdis); + } + + // 成员方法:getDeTheta(获取最小角度单位) + // 获取成员变量 detheta 的值。 + __host__ __device__ double // 返回值:成员变量 detheta 的值 + getDeTheta() const + { + // 返回 detheta 成员变量的值。 + return this->detheta; + } + + // 成员方法:setDeTheta(设置最小角度单位) + // 设置成员变量 detheta 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setDeTheta( + double detheta // 设定新的角度增量 + ) { + // 将 detheta 成员变量赋成新值 + this->detheta = detheta; + + return NO_ERROR; + } + + // 成员方法:getDeRho(获取最小距离单位) + // 获取成员变量 derho 的值。 + __host__ __device__ double // 返回值:成员变量 derho 的值 + getDeRho() const + { + // 返回 detheta 成员变量的值。 + return this->derho; + } + + // 成员方法:setDeRho(设置最小距离单位) + // 设置成员变量 derho 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setDeRho( + double derho // 设定新的距离增量 + ) { + // 将 derho 成员变量赋成新值 + this->derho = derho; + + return NO_ERROR; + } + + // 成员方法:getThreshold(获取直线阈值) + // 获取成员变量 threshold 的值。 + __host__ __device__ int // 返回值:成员变量 threshold 的值 + getThreshold() const + { + // 返回 threshold 成员变量的值。 + return this->threshold; + } + + // 成员方法:setThreshold(设置直线阈值) + // 设置成员变量 threshold 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setThreshold( + int threshold // 设定新的直线阈值 + ) { + // 将 threshold 成员变量赋成新值 + this->threshold = threshold; + + return NO_ERROR; + } + + // 成员方法:getThresAng(获取区别两条直线的最小角度) + // 获取成员变量 thresang 的值。 + __host__ __device__ float // 返回值:成员变量 thresang 的值 + getThresAng() const + { + // 返回 thresang 成员变量的值。 + return this->thresang; + } + + // 成员方法:setThresAng(设置区别两条直线的最小角度) + // 设置成员变量 thresang 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setThresAng( + float thresang // 设定新的区别两条直线的最小角度 + ) { + // 将 thresang 成员变量赋成新值 + this->thresang = thresang; + + return NO_ERROR; + } + + // 成员方法:getThresDis(获取区别两条直线的最小距离) + // 获取成员变量 thresdis 的值。 + __host__ __device__ int // 返回值:成员变量 thresdis 的值 + getThresDis() const + { + // 返回 thresdis 成员变量的值。 + return this->thresdis; + } + + // 成员方法:setThresDis(设置区别两条直线的最小距离) + // 设置成员变量 thresdis 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setThresDis( + int thresdis // 设定区别两条直线的最小距离 + ) { + // 将 thresdis 成员变量赋成新值 + this->thresdis = thresdis; + + return NO_ERROR; + } + + // Host 成员方法:houghlineCor(Hough 变换检测直线) + // 对输入点集 guidingset 进行直线检测,返回最终的直线检测结果 + // 以及检测到的直线数量。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回NO_ERROR。 + houghLineCor( + CoordiSet *guidingset, // 输入坐标集 + int *linesmax, // 检测直线的最大数量 + LineParam *lineparam // 直线返回参数结构体 + ); + + // Host 成员方法:houghline(Hough 变换检测直线) + // 若输入的坐标集 guidingset 不为空,则只处理该坐标集;若该坐标集为空,则 + // Hough 变换就处理输入图像的 ROI 区域,并把最终检测直线的结果返回 + // 到定义的参数结构体中,并且返回检测到的直线的数量。输入图像可以为空。 + // 如果图像roi区域不是整个图像,则检测得到直线参数是以roi区域左上角为原点的, + // 如果需要全局坐标下参数,则需要调用getGlobalParam()函数转换一下。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + houghLine( + Image *inimg, // 输入图像 + CoordiSet *guidingset, // 输入坐标集 + int *linesmax, // 检测直线的最大数量 + LineParam *lineparam // 直线返回参数结构体 + ); + + // Host 成员方法:houghlineimg(Hough 变换检测直线) + // 输入图像不能为空,若输入的坐标集 guidingset 不为空,则只处理该坐标集; + // 若该坐标集为空,则 Hough 变换就处理输入图像的 ROI 区域,并把最终检测 + // 直线的结果返回到定义的参数结构体中,返回检测到的直线的数量,以及输出图像。 + // 如果图像roi区域不是整个图像,则检测得到直线参数是以roi区域左上角为原点的, + // 如果需要全局坐标下参数,则需要调用getGlobalParam()函数转换一下。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + houghLineImg( + Image *inimg, // 输入图像 + CoordiSet *guidingset, // 输入坐标集 + Image *outimg, // 输出图像 + int *linesmax, // 检测直线的最大数量 + LineParam *lineparam // 直线返回参数结构体 + ); + // Host 成员方法:realLine(判断给出线段的真实性) + // 统计给出图像中两端点间有效点个数,大于参数给出的比例,认为线段真实返回真。 + __host__ bool // 返回值:若两点间存在真实直线,返回真, + // 否则返回假 + realLine( + Image *inimg, // 输入图像 + int x1, // 要判断的线段两端点坐标 + int y1, // 要判断的线段两端点坐标 + int x2, // 要判断的线段两端点坐标 + int y2, // 要判断的线段两端点坐标 + float threshold, // 距离线段多远的点认为是线段有效点,单位像素 + float thresperc // 线段真实性判定阈值,线段上有效点和线段理论上应该有的 + // 点的比值超过此阈值,认为线段真实存在 + ); + + // Host 成员方法:getGlobalParam(ROI局部直线参数转换成为全局坐标下的参数) + // 把 hough 变换检测到的直线参数转换成为全局坐标下的参数 + __host__ int // 返回值:函数是否正确执行,若函数 + // 否则返回假 + getGlobalParam( + Image *inimg, // 输入图像 + int *linesmax, // 检测直线的最大数量 + LineParam *lineparam // 直线返回参数结构体 + ); + +}; +#endif + diff --git a/okano_3_0/HoughRec.cu b/okano_3_0/HoughRec.cu new file mode 100644 index 0000000..d1dff5e --- /dev/null +++ b/okano_3_0/HoughRec.cu @@ -0,0 +1,1295 @@ +// HoughRec.cu +// 实现 Hough 变换检测矩形 +#include "ImgConvert.h" +#include "HoughRec.h" +#include "ImageDrawer.h" +#include +#include +#include +#include +#include "ErrorCode.h" +#include "CoordiSet.h" +using namespace std; + +// 宏:BORDER_COLOR +// 定义边界颜色 +#define BORDER_COLOR 255 +// 宏:BK_COLOR +// 定义背景颜色 +#define BK_COLOR 0 + +// 宏:DEBUG +// 定义是否输出调试信息 +//#define DEBUG + +// 宏:M_PI +// π 值。对于某些操作系统,M_PI 可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +//--------------------------全局方法声明------------------------------------ +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax); + +//--------------------------全局方法实现------------------------------------ +// 函数:_findMinMaxCoordinates(根据输入点集的坐标,找到最上、最下、最左、最右 +// 的点,从而确定图像的宽和高) +static __host__ int _findMinMaxCoordinates(CoordiSet *guidingset, + int *xmin, int *ymin, + int *xmax, int *ymax) +{ + // 声明局部变量。 + int errcode; + + // 在 host 端申请一个新的 CoordiSet 变量。 + CoordiSet *tmpcoordiset; + errcode = CoordiSetBasicOp::newCoordiSet(&tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + errcode = CoordiSetBasicOp::makeAtHost(tmpcoordiset, guidingset->count); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(guidingset, tmpcoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 初始化 x 和 y 方向上的最小最大值。 + xmin[0] = xmax[0] = tmpcoordiset->tplData[0]; + ymin[0] = ymax[0] = tmpcoordiset->tplData[1]; + // 循环寻找坐标集最左、最右、最上、最下的坐标。 + for (int i = 1;i < tmpcoordiset->count;i++) { + // 寻找 x 方向上的最小值。 + if (xmin[0] > tmpcoordiset->tplData[2 * i]) + xmin[0] = tmpcoordiset->tplData[2 * i]; + // 寻找 x 方向上的最大值 + if (xmax[0] < tmpcoordiset->tplData[2 * i]) + xmax[0] = tmpcoordiset->tplData[2 * i]; + + // 寻找 y 方向上的最小值。 + if (ymin[0] > tmpcoordiset->tplData[2 * i + 1]) + ymin[0] = tmpcoordiset->tplData[2 * i + 1]; + // 寻找 y 方向上的最大值 + if (ymax[0] < tmpcoordiset->tplData[2 * i + 1]) + ymax[0] = tmpcoordiset->tplData[2 * i + 1]; + } + + // 释放临时坐标集变量。 + CoordiSetBasicOp::deleteCoordiSet(tmpcoordiset); + + return errcode; +} + +//--------------------------类声明实现------------------------------------ +// 类:Pair(存放一组平行线组(超过两条)中的一对) +// 继承自:无 +// 从一组平行线中提取所有平行线对时,用该结构当返回值 +class Pair{ +public: + int rho1;// 直线1的距离值 + int rho2;// 直线2的距离值 + int vote1;// 直线1的投票值 + int vote2;// 直线2的投票值 + Pair(int a,int b,int v1,int v2) + { + rho1=a; + rho2=b; + vote1=v1; + vote2=v2; + } +}; +// 类:ThetaCluster(存放一组角度相同直线角度、距离rho、票数,即记录一组平行线) +// 继承自:无 +// 检测平行四边形时,用此类存放可能的对边。 +class ThetaCluster{ +public: + float theta; // 该簇平行线的角度值,弧度制 + vector rhoList; // 存放多条直线的距离值 + vector voteList; // 存放多条直线的投票值 + // 构造方法 + ThetaCluster(float ang,int rho,int vote){ + theta=ang; + rhoList.push_back(rho); + voteList.push_back(vote); + } + // 加入一条直线的距离值 + void addRho(int rho){ + rhoList.push_back(rho); + } + // 加入一条直线的投票值 + void addVote(int vote){ + voteList.push_back(vote); + } + // 提取该组中所有的平行线对,结果用pairList向量返回 + void getPair(vector &pairList) + { + if(rhoList.size()>=2) + for(int i=0;i thetaIndexParam; + // 遍历参数给出的每一条直线 + for(int i=0;i0 ? x[0] :-x[0]); + x[1]=(x[1]>0 ? x[1] :-x[1]); + x[2]=(x[2]>0 ? x[2] :-x[2]); + x[3]=(x[3]>0 ? x[3] :-x[3]); + recxyparam[idx].x1=x[0]; + recxyparam[idx].x2=x[1]; + recxyparam[idx].x3=x[2]; + recxyparam[idx].x4=x[3]; + recxyparam[idx].xc=(x[0]+x[1]+x[2]+x[3]) / 4; + + x[0]=(x[0]>0 ? x[0] :-x[0]); + x[1]=(x[1]>0 ? x[1] :-x[1]); + x[2]=(x[2]>0 ? x[2] :-x[2]); + x[3]=(x[3]>0 ? x[3] :-x[3]); + recxyparam[idx].y1=y[0]; + recxyparam[idx].y2=y[1]; + recxyparam[idx].y3=y[2]; + recxyparam[idx].y4=y[3]; + recxyparam[idx].yc=(y[0]+y[1]+y[2]+y[3]) / 4; + + recxyparam[idx].votes= + 2 * (recpolarparam[idx].votes1+recpolarparam[idx].votes2); + } + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:detectRectangle(检测inimg图像中的矩形,放入数组返回) +__host__ int +HoughRec:: detectRectangle( + Image *inimg, // 输入图像 + int linenum, // 最大直线数量 + int linethres, // 直线票数阈值 + float lineangthres, // 相似直线角度 + int linedisthres, // 相似直线距离 + int *rectnum, // 返回矩形数量 + RecXYParam *rectxypara // 返回矩形xy坐标参数 +){ + HoughLine houghline; + // 直线检测角度和距离的步长 + houghline.setDeTheta(M_PI / 180.0); + houghline.setDeRho(1); + // 票数阈值,根据图像分片大小和图像中直线的粗细设定 + houghline.setThreshold(linethres); + // 合并相似直线采用的参数,倾角相差6度内且dis值相差15以内,可以认为是同一条直线 + houghline.setThresAng(lineangthres); + houghline.setThresDis(linedisthres); + + + int linesMax =linenum; + LineParam *lineparam= new LineParam[linesMax]; + + // 直线检测 + #ifdef DEBUG + Image *outimg; + ImageBasicOp::newImage(&outimg); + ImageBasicOp::makeAtHost(outimg, inimg->width, inimg->height); + + houghline.houghLineImg(inimg, NULL,outimg, &linesMax, lineparam); + + ImageBasicOp::copyToHost(outimg); + ImageBasicOp::writeToFile("line_out.bmp", outimg); + ImageBasicOp::deleteImage(outimg); + + cout << "linesMax = " << linesMax << endl; + printf("序号 angle distance vote \n"); + for (int i = 0; i < linesMax; i++) + printf("%4d %12f(%12f) %5d %5d\n",i,lineparam[i].angle,lineparam[i].angle/M_PI*180, + lineparam[i].distance,lineparam[i].votes); + #else + /* + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + float runTime; + cudaEventRecord(start, 0);*/ + houghline.houghLine(inimg, NULL, &linesMax, lineparam); + + /* + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + cudaEventElapsedTime(&runTime, start, stop); + printf(" %f ",runTime);*/ + #endif + + + + + // 根据直线结果处理矩形 + cudaThreadSynchronize();// 不加同步语句,则下面的代码结果不正确 + + // 申请倾角-距离参数数组 + RecPolarParam *rectpp=new RecPolarParam[*rectnum]; + // 检测矩形,放入倾角-距离参数数组,数量放入*rectnum + detectRectangle(lineparam,linesMax,rectnum,rectpp); + // 倾角-距离参数数组转换成矩形XY坐标参数结构体 + polar2XYparam (rectpp, rectxypara, *rectnum, 1); + // 把矩形xy坐标由分片局部坐标加上分片远点坐标,转换成全局坐标 + for(int i=0;i<*rectnum;i++){ + rectxypara[i].x1 += inimg->roiX1; + rectxypara[i].x2 += inimg->roiX1; + rectxypara[i].x3 += inimg->roiX1; + rectxypara[i].x4 += inimg->roiX1; + rectxypara[i].y1 += inimg->roiY1; + rectxypara[i].y2 += inimg->roiY1; + rectxypara[i].y3 += inimg->roiY1; + rectxypara[i].y4 += inimg->roiY1; + } + // 局部动态内存回收 + if(lineparam!=NULL) + {delete[] lineparam;lineparam=NULL;} + if(rectpp!=NULL) + {delete[] rectpp;rectpp=NULL;} + + return NO_ERROR; + } + +// Host 成员方法:detectRectangle(检测CoordiSet中的矩形,放入数组返回) +__host__ int +HoughRec:: detectRectangle( + CoordiSet *coor, // 输入坐标集 + int linenum, // 最大直线数量 + int linethres, // 直线票数阈值 + float lineangthres, // 相似直线角度 + int linedisthres, // 相似直线距离 + int *rectnum, // 返回矩形数量 + RecXYParam *rectxypara // 返回矩形xy坐标参数 + ){ + if(coor==NULL) + return INVALID_DATA; + // 获取坐标集中点的分布范围,即包围盒坐标 + int minx,maxx,miny,maxy; + Image *inimg; + ImageBasicOp::newImage(&inimg); + ImgConvert imgcvt(BORDER_COLOR,BK_COLOR); + // ----------------------输入coor参数转化成img---------------------------- + // 预处理,得到外轮廓大小 + int errorcode=_findMinMaxCoordinates(coor,&minx,&miny,&maxx,&maxy); + if(errorcode!=NO_ERROR) + return 0; + // 创建工作图像 + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证点在图像内部 + ImageBasicOp::makeAtHost(inimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(coor,inimg); + + // 调用图像接口完成剩下操作。 + detectRectangle( + inimg, // 输入坐标集 + linenum, // 最大直线数量 + linethres, // 直线票数阈值 + lineangthres, // 相似直线角度 + linedisthres, // 相似直线距离 + rectnum, // 返回矩形数量 + rectxypara // 返回矩形xy坐标参数 + ); + + ImageBasicOp::deleteImage(inimg); + return NO_ERROR; + } + +// Host 成员方法:detectRealRectangle(检测矩形数组中真实矩形数量, +// 放入数组返回,参照为坐标集) +__host__ int +HoughRec:: detectRealRectangle( + CoordiSet *coor, // 输入坐标集 + int rectnum, // 可能矩形数量 + RecXYParam *rectxypara, //可能矩形参数数组 + int distance, // 真实直线判定距离 + float percent, // 真实直线判定阈值 + int *realrectnum, //真实矩形数量 + RecXYParam *realrectxypara //真实矩形xy坐标参数 + ){ + if(coor==NULL) + return INVALID_DATA; + // 获取坐标集中点的分布范围,即包围盒坐标 + int minx,maxx,miny,maxy; + Image *inimg; + ImageBasicOp::newImage(&inimg); + ImgConvert imgcvt(BORDER_COLOR,BK_COLOR); + // ----------------------输入coor参数转化成img---------------------------- + // 预处理,得到外轮廓大小 + int errorcode=_findMinMaxCoordinates(coor,&minx,&miny,&maxx,&maxy); + if(errorcode!=NO_ERROR) + return 0; + // 创建工作图像 + //给工作图像分配空间,宽度是最大坐标值+1,因为坐标从0开始计数,再+1,保证点在图像内部 + ImageBasicOp::makeAtHost(inimg,maxx+2 ,maxy+2); + // 把坐标集绘制到图像上,前景255,背景0 + imgcvt.cstConvertToImg(coor,inimg); + + // 调用图像接口完成剩余操作。 + detectRealRectangle( + inimg, // 输入图像 + rectnum, // 可能矩形数量 + rectxypara, //可能矩形参数数组 + distance, // 真实直线判定距离 + percent, // 真实直线判定阈值 + realrectnum, //真实矩形数量 + realrectxypara //真实矩形xy坐标参数 + ); + + ImageBasicOp::deleteImage(inimg); + return NO_ERROR; +} + +// Host 成员方法:detectRealRectangle(检测矩形数组中真实矩形数量,放入数组返回) +__host__ int +HoughRec:: detectRealRectangle( + Image *inimg, // 输入图像 + int rectnum, // 可能矩形数量 + RecXYParam *rectxyparam, //可能矩形参数数组 + int distance, // 真实直线判定距离 + float percent, // 真实直线判定阈值 + int *realrectnum, //真实矩形数量 + RecXYParam *realrectxypara //真实矩形xy坐标参数 +){ + int pointer=0; + HoughLine houghline; + for(int i=0; i< rectnum; i++) { + // 对矩形四个边进行真实性判定 + bool b1,b2,b3,b4; + inimg->roiX1=0; + inimg->roiX2=inimg->width; + inimg->roiY1=0; + inimg->roiY2=inimg->height; + + b1=houghline.realLine(inimg,rectxyparam[i].x1,rectxyparam[i].y1, + rectxyparam[i].x2,rectxyparam[i].y2, distance,percent); + b2=houghline.realLine(inimg,rectxyparam[i].x2,rectxyparam[i].y2, + rectxyparam[i].x3,rectxyparam[i].y3, distance,percent); + b3=houghline.realLine(inimg,rectxyparam[i].x3,rectxyparam[i].y3, + rectxyparam[i].x4,rectxyparam[i].y4, distance,percent); + b4=houghline.realLine(inimg,rectxyparam[i].x4,rectxyparam[i].y4, + rectxyparam[i].x1,rectxyparam[i].y1, distance,percent); + // 判断四个边是否是真实直线,全都是的话,放入结果数组中。 + #ifdef DEBUG + cout<<"b1="<roiX1=x*cell_x; + inimg->roiX2=x*cell_x+cell_x-1; + inimg->roiY1=y*cell_y; + inimg->roiY2=y*cell_y+cell_y-1; + outimg->roiX1= inimg->roiX1; + outimg->roiX2= inimg->roiX2; + outimg->roiY1= inimg->roiY1; + outimg->roiY2= inimg->roiY2; + #ifdef DEBUG + printf("x1=%d x2=%d y1=%d y2=%d \n" + ,inimg->roiX1,inimg->roiX2 + ,inimg->roiY1,inimg->roiY2); + #endif + // 注意,此时得到的直线参数是分片内的局部坐标,非全局坐标,要转换 + hough.houghLineImg(inimg, NULL, outimg, &linesMax, lineparam); + + // 根据直线结果处理矩形 + // 不加同步语句,则下面的代码结果不正确 + cudaThreadSynchronize(); + + RecPolarParam *rectpp=new RecPolarParam[rectnum]; + // 初始化 + for(int i=0;iroiX1; + recxyparam[i].x2 += inimg->roiX1; + recxyparam[i].x3 += inimg->roiX1; + recxyparam[i].x4 += inimg->roiX1; + recxyparam[i].y1 += inimg->roiY1; + recxyparam[i].y2 += inimg->roiY1; + recxyparam[i].y3 += inimg->roiY1; + recxyparam[i].y4 += inimg->roiY1; + } + + // 绘制矩形:输入 recxyparam 、rectMax 绘制到recoutimg图像中 + // 创建只有4个点的一个坐标集 + CoordiSet *cst; + CoordiSetBasicOp::newCoordiSet(&cst); + CoordiSetBasicOp::makeAtHost(cst, 4); + // 每个矩形四个顶点放入坐标集 + for (int i=0; i< rectMax; i++) { + CoordiSetBasicOp::copyToHost(cst); + cst->tplData[0]=recxyparam[i].x1; + cst->tplData[1]=recxyparam[i].y1; + cst->tplData[2]=recxyparam[i].x2; + cst->tplData[3]=recxyparam[i].y2; + cst->tplData[4]=recxyparam[i].x3; + cst->tplData[5]=recxyparam[i].y3; + cst->tplData[6]=recxyparam[i].x4; + cst->tplData[7]=recxyparam[i].y4; + // 对矩形四个边进行真实性判定 + bool b1,b2,b3,b4; + inimg->roiX1=0; + inimg->roiX2=inimg->width; + inimg->roiY1=0; + inimg->roiY2=inimg->height; + b1=hough.realLine(inimg,recxyparam[i].x1,recxyparam[i].y1, + recxyparam[i].x2,recxyparam[i].y2, distance,percent); + b2=hough.realLine(inimg,recxyparam[i].x2,recxyparam[i].y2, + recxyparam[i].x3,recxyparam[i].y3, distance,percent); + b3=hough.realLine(inimg,recxyparam[i].x3,recxyparam[i].y3, + recxyparam[i].x4,recxyparam[i].y4, distance,percent); + b4=hough.realLine(inimg,recxyparam[i].x4,recxyparam[i].y4, + recxyparam[i].x1,recxyparam[i].y1, distance,percent); + // 判断四个边是否是真实直线,全都是的话,就把cst中点顺序连接 + // 成一个闭合图形,绘制到recoutimg中。 + #ifdef DEBUG + cout<<"b1="<roiX1=x*cell_x+cell_x/2; + inimg->roiX2=x*cell_x+cell_x/2+cell_x-1; + inimg->roiY1=y*cell_y+cell_y/2; + inimg->roiY2=y*cell_y+cell_y/2+cell_y-1; + outimg2->roiX1=inimg->roiX1; + outimg2->roiX2=inimg->roiX2; + outimg2->roiY1=inimg->roiY1; + outimg2->roiY2=inimg->roiY2; + #ifdef DEBUG + printf("x1=%d x2=%d y1=%d y2=%d \n", + inimg->roiX1,inimg->roiX2, + inimg->roiY1,inimg->roiY2); + #endif + // 注意,此时得到的直线参数是局部坐标,非全局坐标,要转换 + hough.houghLineImg(inimg, NULL, outimg2, &linesMax, lineparam); + // 根据直线结果处理矩形 + cudaThreadSynchronize();// 不加同步语句,则下面的代码结果不正确 + + RecPolarParam *rectpp; + rectpp=new RecPolarParam[rectnum]; + // 初始化 + for(int i=0;iroiX1; + recxyparam[i].x2 += inimg->roiX1; + recxyparam[i].x3 += inimg->roiX1; + recxyparam[i].x4 += inimg->roiX1; + recxyparam[i].y1 += inimg->roiY1; + recxyparam[i].y2 += inimg->roiY1; + recxyparam[i].y3 += inimg->roiY1; + recxyparam[i].y4 += inimg->roiY1; + } + // 绘制矩形:输入 recxyparam 、rectMax 绘制到recoutimg图像中 + // 创建坐标集 + CoordiSet *cst; + CoordiSetBasicOp::newCoordiSet(&cst); + // 只有4个点的一个坐标集 + CoordiSetBasicOp::makeAtHost(cst,4); + for (int i=0; i< rectMax; i++){ + CoordiSetBasicOp::copyToHost(cst); + cst->tplData[0]=recxyparam[i].x1; + cst->tplData[1]=recxyparam[i].y1; + cst->tplData[2]=recxyparam[i].x2; + cst->tplData[3]=recxyparam[i].y2; + cst->tplData[4]=recxyparam[i].x3; + cst->tplData[5]=recxyparam[i].y3; + cst->tplData[6]=recxyparam[i].x4; + cst->tplData[7]=recxyparam[i].y4; + bool b1,b2,b3,b4; + inimg->roiX1=0; + inimg->roiX2=inimg->width; + inimg->roiY1=0; + inimg->roiY2=inimg->height; + b1=hough.realLine(inimg,recxyparam[i].x1,recxyparam[i].y1, + recxyparam[i].x2,recxyparam[i].y2, distance,percent); + b2=hough.realLine(inimg,recxyparam[i].x2,recxyparam[i].y2, + recxyparam[i].x3,recxyparam[i].y3, distance,percent); + b3=hough.realLine(inimg,recxyparam[i].x3,recxyparam[i].y3, + recxyparam[i].x4,recxyparam[i].y4, distance,percent); + b4=hough.realLine(inimg,recxyparam[i].x4,recxyparam[i].y4, + recxyparam[i].x1,recxyparam[i].y1, distance,percent); + // 判断四个边是否是真实直线,全都是的话,就把cst中点顺序连接 + // 成一个闭合图形,绘制到recoutimg中。 + #ifdef DEBUG + cout<<"b1="<width/piecenum; + int cell_y=inimg->height/piecenum; + #ifdef DEBUG + printf("cell_x=%d cell_y=%d\n",cell_x,cell_y); + #endif + HoughLine hough; + // 直线检测角度和距离的步长 + hough.setDeTheta(M_PI / 180.0); + hough.setDeRho(1); + // 票数阈值,根据图像分片大小和图像中直线的粗细设定 + hough.setThreshold(linethres); + // 合并相似直线采用的参数,倾角相差6度内且dis值相差15以内,可以认为是同一条直线 + hough.setThresAng(lineangthres); + hough.setThresDis(linedisthres); + + // 开始分块处理 + for(int y=0;yroiX1=x*cell_x; + inimg->roiX2=x*cell_x+cell_x-1; + inimg->roiY1=y*cell_y; + inimg->roiY2=y*cell_y+cell_y-1; + + #ifdef DEBUG + printf("x1=%d x2=%d y1=%d y2=%d \n", + inimg->roiX1,inimg->roiX2, + inimg->roiY1,inimg->roiY2); + #endif + + hough.houghLine(inimg, NULL, &linesMax, lineparam); + // 根据直线结果处理矩形 + cudaThreadSynchronize();// 不加同步语句,则下面的代码结果不正确 + + RecPolarParam *rectpp=new RecPolarParam[rectnum]; + // 初始化 + for(int i=0;iroiX1; + recxyparam[i].x2 += inimg->roiX1; + recxyparam[i].x3 += inimg->roiX1; + recxyparam[i].x4 += inimg->roiX1; + recxyparam[i].y1 += inimg->roiY1; + recxyparam[i].y2 += inimg->roiY1; + recxyparam[i].y3 += inimg->roiY1; + recxyparam[i].y4 += inimg->roiY1; + } + for(int i=0; i< rectMax; i++) { + // 对矩形四个边进行真实性判定 + bool b1,b2,b3,b4; + inimg->roiX1=0; + inimg->roiX2=inimg->width; + inimg->roiY1=0; + inimg->roiY2=inimg->height; + b1=hough.realLine(inimg,recxyparam[i].x1,recxyparam[i].y1, + recxyparam[i].x2,recxyparam[i].y2, distance,percent); + b2=hough.realLine(inimg,recxyparam[i].x2,recxyparam[i].y2, + recxyparam[i].x3,recxyparam[i].y3, distance,percent); + b3=hough.realLine(inimg,recxyparam[i].x3,recxyparam[i].y3, + recxyparam[i].x4,recxyparam[i].y4, distance,percent); + b4=hough.realLine(inimg,recxyparam[i].x4,recxyparam[i].y4, + recxyparam[i].x1,recxyparam[i].y1, distance,percent); + // 判断四个边是否是真实直线,全都是的话,放入结果数组中。 + #ifdef DEBUG + cout<<"b1="<roiX1=x*cell_x+cell_x/2; + inimg->roiX2=x*cell_x+cell_x/2+cell_x-1; + inimg->roiY1=y*cell_y+cell_y/2; + inimg->roiY2=y*cell_y+cell_y/2+cell_y-1; + + #ifdef DEBUG + printf("x1=%d x2=%d y1=%d y2=%d \n", + inimg->roiX1,inimg->roiX2, + inimg->roiY1,inimg->roiY2); + #endif + // 注意,此时得到的直线参数是局部坐标,非全局坐标,要转换 + hough.houghLine(inimg, NULL, &linesMax, lineparam); + + // 根据直线结果处理矩形 + // 不加同步语句,则下面的代码结果不正确 + cudaThreadSynchronize(); + + RecPolarParam *rectpp; + rectpp=new RecPolarParam[rectnum]; + // 初始化 + for(int i=0;iroiX1; + recxyparam[i].x2 += inimg->roiX1; + recxyparam[i].x3 += inimg->roiX1; + recxyparam[i].x4 += inimg->roiX1; + recxyparam[i].y1 += inimg->roiY1; + recxyparam[i].y2 += inimg->roiY1; + recxyparam[i].y3 += inimg->roiY1; + recxyparam[i].y4 += inimg->roiY1; + } + + for (int i=0; i< rectMax; i++){ + bool b1,b2,b3,b4; + inimg->roiX1=0; + inimg->roiX2=inimg->width; + inimg->roiY1=0; + inimg->roiY2=inimg->height; + b1=hough.realLine(inimg,recxyparam[i].x1,recxyparam[i].y1, + recxyparam[i].x2,recxyparam[i].y2, distance,percent); + b2=hough.realLine(inimg,recxyparam[i].x2,recxyparam[i].y2, + recxyparam[i].x3,recxyparam[i].y3, distance,percent); + b3=hough.realLine(inimg,recxyparam[i].x3,recxyparam[i].y3, + recxyparam[i].x4,recxyparam[i].y4, distance,percent); + b4=hough.realLine(inimg,recxyparam[i].x4,recxyparam[i].y4, + recxyparam[i].x1,recxyparam[i].y1, distance,percent); + // 判断四个边是否是真实直线,全都是的话,放入结果数组中。 + #ifdef DEBUG + cout<<"b1="< +using namespace std; +// 结构体:recPolarParam(矩形极坐标返回参数) +// 包括两组平行线的参数,两组平行线互相垂直。 +typedef struct RecPolarParam_st { + float theta1; // 第一组平行线与横轴的夹角。(弧度制) + float theta2; // 第二组平行线与横轴的夹角。(弧度制) + int rho1a; // 第一组平行线中直线段 a 与坐标原点的距离。 + int rho1b; // 第一组平行线中直线段 b 与坐标原点的距离。 + int rho2a; // 第二组平行线中直线段 a 与坐标原点的距离。 + int rho2b; // 第二组平行线中直线段 b 与坐标原点的距离。 + int votes1; // 第一组平行线中每条直线的得票数,即 BufHough 矩形中的数据。 + int votes2; // 第二组平行线中每条直线的得票数,即 BufHough 矩形中的数据。 +} RecPolarParam; + +// 结构体:recXYParam(矩形 XY 坐标返回参数) +// 包括四个角点及中心点的坐标。 +typedef struct RecXYParam_st { + int x1; // 第一个角点的列号。 + int y1; // 第一个角点的行号。 + int x2; // 第二个角点的列号。 + int y2; // 第二个角点的行号。 + int x3; // 第三个角点的列号。 + int y3; // 第三个角点的行号。 + int x4; // 第四个角点的列号。 + int y4; // 第四个角点的行号。 + int xc; // 中心点的列号。 + int yc; // 中心点的行号。 + long votes; // 矩形得票数。 +} RecXYParam; + +// 类:HoughRec +// 继承自:无 +// 实现 Hough 变换检测矩形。输入参数为采用极坐标的直线参数坐标集。 +class HoughRec { +private: + // 识别平行线组时的误差容许范围,邻边夹角误差容许范围,弧度制 + float toloranceAngle; + +public: + // 构造函数:HoughRec + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + HoughRec(){ + toloranceAngle=M_PI/180*3; + } + + // 构造函数:HoughRec + // 有参数版本的构造函数,根据需要给定参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + HoughRec(float tolorance){ + toloranceAngle=tolorance; + } + + // 成员方法:getToloranceAngle(获取角度差阈值) + // 获取成员变量 toloranceAngle 的值。 + __host__ __device__ float // 返回值:成员变量 toloranceAngle 的值 + getToloranceAngle() const{ + return this->toloranceAngle; + } + + // 成员方法:setToloranceAngle(设置最小角度单位) + // 设置成员变量 toloranceAngle 的值。 + __host__ __device__ int // 返回值:函数是否正确执行, + // 若函数正确执行,返回 NO_ERROR。 + setToloranceAngle( float Theta ) // 设定新的角度差阈值。 + { + toloranceAngle=Theta; + return NO_ERROR; + } + + // Host 成员方法:detectParallelogram(Hough 变换检测平行四边形) + // 对输入的直线集 lineparam 进行平行四边形检测,返回可能的四边形参数 + // 结果以及检测到的四边形数量。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + detectParallelogram( + LineParam *lineparam, // 输入的直线参数集结构体。 + int linenum, // 输入的直线的数目。 + int *rectmax, // 返回检测四边形的最大数量。 + RecPolarParam *recpolarparm, // 返回四边形返回参数结构体。 + float anglelist[], // 希望检测出的四边形夹角列表 + int anglenum // 夹角列表中元素个数 + ); + + // Host 成员方法:detectRectangle(Hough 变换检测矩形) + // 调用detectParallelogram方法,指定夹角列表中只有一个直角。返回倾角-距离参数坐标。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + detectRectangle( + LineParam *lineparam, // 输入的直线参数集结构体。 + int linemax, // 输入的直线的数目。 + int *recsmax, // 返回检测四边形的最大数量。 + RecPolarParam *recpolarparm // 返回四边形返回参数结构体。 + ); + // Host 成员方法:detectRectangle(检测inimg图像中的矩形,放入数组返回) + __host__ int + detectRectangle( + Image *inimg, // 输入图像 + int linenum, // 最大直线数量 + int linethres, // 直线票数阈值 + float lineangthres, // 相似直线角度 + int linedisthres, // 相似直线距离 + int *rectnum, // 返回矩形数量 + RecXYParam *rectxypara // 返回矩形xy坐标参数 + ); + + // Host 成员方法:detectRectangle(检测CoordiSet中的矩形,放入数组返回) + + __host__ int + detectRectangle( + CoordiSet *coor, // 输入坐标集 + int linenum, // 最大直线数量 + int linethres, // 直线票数阈值 + float lineangthres, // 相似直线角度 + int linedisthres, // 相似直线距离 + int *rectnum, // 返回矩形数量 + RecXYParam *rectxypara // 返回矩形xy坐标参数 + ); + + // Host 成员方法:detectRealRectangle(检测矩形数组中真实矩形数量, + // 放入数组返回,参照为坐标集) + __host__ int + detectRealRectangle( + CoordiSet *coor, // 输入坐标集 + int rectnum, // 可能矩形数量 + RecXYParam *rectxypara, //可能矩形参数数组 + int distance, // 真实直线判定距离 + float percent, // 真实直线判定阈值 + int *realrectnum, //真实矩形数量 + RecXYParam *realrectxypara //真实矩形xy坐标参数 + ); + + // Host 成员方法:detectRealRectangle(检测矩形数组中真实矩形数量, + // 放入数组返回,参照为图像) + __host__ int + detectRealRectangle( + Image *inimg, // 输入图像 + int rectnum, // 可能矩形数量 + RecXYParam *rectxypara, //可能矩形参数数组 + int distance, // 真实直线判定距离 + float percent, // 真实直线判定阈值 + int *realrectnum, //真实矩形数量 + RecXYParam *realrectxypara //真实矩形xy坐标参数 + ); + // Host 成员方法:polar2XYparam(角度距离坐标转换成直角坐标) + __host__ int // 返回值: 函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + polar2XYparam ( + RecPolarParam *recpolarparam, // 输入的矩形极坐标参数。 + RecXYParam *recxyparam, // 返回的矩形 XY 坐标参数。 + int recnum, // 输入的矩形的个数。 + float derho // 输入的直线距离步长参数。 + ); + + + // Host 成员方法:drawRect(把直角坐标四边形绘制到指定图像文件中) + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + drawRect( + string filename, // 要写入的图像文件名 + size_t w, // 图像宽度 + size_t h, // 图像高度 + RecXYParam recxyparam[], // 矩形直角坐标数组 + int rectmax // 矩形个数 + ); + + // Host 成员方法:pieceRealRectImg(分片检测inimg图像中的矩形写入图像文件) + // 对其边的真实性进行判定,中间结果和最终结果都放入指定的图像文件中 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + pieceRealRectImg( + Image *inimg, // 输入图像 + string lineoutfile1, // 直线检测中间结果1 + string lineoutfile2, // 直线检测中间结果2 + string rectoutfile, // 矩形输出文件名 + int piecenum, // 分片个数 + int linenum, // 每个分片中直线最大数量 + int linethres, // 每个分片中直线票数阈值 + float lineangthres, // 每个分片中相似直线角度判定阈值 + int linedisthres, // 每个分片中相似直线距离判定阈值 + int rectnum, // 每个分片中矩形最大数量 + int distance=3, // 真实线段判定参数,距离线段多远的点有效 + float percent=0.7 // 真实线段判定参数,多大比例的真实点作为判定阈值 + ); + // Host 成员方法:重载pieceRealRectImg(分片检测coor坐标集合中的矩形写入图像文件) + // 对其边的真实性进行判定,中间结果和最终结果都放入指定的图像文件中 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + pieceRealRectImg( + CoordiSet* coor, // 输入坐标集 + string lineoutfile1, // 直线检测中间结果1 + string lineoutfile2, // 直线检测中间结果2 + string rectoutfile, // 矩形输出文件名 + int piecenum, // 分片个数 + int linenum, // 每个分片中直线最大数量 + int linethres, // 每个分片中直线票数阈值 + float lineangthres, // 每个分片中相似直线角度判定阈值 + int linedisthres, // 每个分片中相似直线距离判定阈值 + int rectnum, // 每个分片中矩形最大数量 + int distance=3, // 真实线段判定参数,距离线段多远的点有效 + float percent=0.7 // 真实线段判定参数,多大比例的真实点作为判定阈值 + ); + // Host 成员方法:pieceRealRect(分片检测inimg图像中的矩形,放入数组返回) + // 分片检测inFile中的矩形并对其边的真实性进行判定,结果放入矩形直角坐标参数数组中返回 + __host__ int + pieceRealRect( + Image *inimg, // 输入图像 + int piecenum, // 分片个数 + int linenum, // 每个分片中直线最大数量 + int linethres, // 每个分片中直线票数阈值 + float lineangthres, // 每个分片中相似直线角度判定阈值 + int linedisthres, // 每个分片中相似直线距离判定阈值 + int rectnum, // 每个分片中矩形最大数量 + int distance, // 真实线段判定参数,距离线段多远的点有效 + float percent, // 真实线段判定参数,多大比例的真实点作为判定阈值 + int *realrectnum, // 用于返回最终检测出的矩形数量 + RecXYParam *realxyparam // 用于返回最终检测出的矩形参数数组 + ); + + // Host 成员方法:重载pieceRealRect(分片检测coor坐标集中的矩形,放入数组返回) + // 分片检测inFile中的矩形并对其边的真实性进行判定,结果放入矩形直角坐标参数数组中返回 + __host__ int + pieceRealRect( + CoordiSet* coor, // 输入坐标集 + int piecenum, // 分片个数 + int linenum, // 每个分片中直线最大数量 + int linethres, // 每个分片中直线票数阈值 + float lineangthres, // 每个分片中相似直线角度判定阈值 + int linedisthres, // 每个分片中相似直线距离判定阈值 + int rectnum, // 每个分片中矩形最大数量 + int distance, // 真实线段判定参数,距离线段多远的点有效 + float percent, // 真实线段判定参数,多大比例的真实点作为判定阈值 + int *realrectnum, // 用于返回最终检测出的矩形数量 + RecXYParam *realxyparam // 用于返回最终检测出的矩形参数数组 + ); +}; + +#endif diff --git a/okano_3_0/ImageDrawer.cu b/okano_3_0/ImageDrawer.cu new file mode 100644 index 0000000..56ccf06 --- /dev/null +++ b/okano_3_0/ImageDrawer.cu @@ -0,0 +1,756 @@ +// ImageDrawer.cu +// 在图像上绘制相应的几何构建。 + +#include "ImageDrawer.h" + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLOCK_1D +// 定义了默认的 1D Block 尺寸。 +#define DEF_BLOCK_1D 512 + + +// Kernel 函数:_brushAllImageKer(涂满整幅图像) +// 将整幅图像使用同一种颜色涂满。 +static __global__ void // Kernel 函数无返回值 +_brushAllImageKer( + ImageCuda img, // 待涂刷的图像 + unsigned char clr // 颜色值 +); + +// Kernel 函数:_drawLinesKer(绘制直线) +// 根据坐标点集中给定的直线参数,在图像上绘制直线。如果 color 小于 0,则使用坐 +// 标点集中的附属数据做为亮度值(颜色值);如果 color 大于等于 0,则直接使用 +// color 所指定的颜色。 +static __global__ void // Kernel 函数无返回值 +_drawLinesKer( + ImageCuda img, // 待绘制直线的图像 + CoordiSetCuda cst, // 用坐标点集表示的直线参数 + int color // 绘图使用的颜色值 +); + +// Kernel 函数:_drawLinesKer(绘制直线) +// 根据坐标点集中给定的椭圆参数,在图像上绘制椭圆。如果 color 小于 0,则使用坐 +// 标点集中的附属数据做为亮度值(颜色值);如果 color 大于等于 0,则直接使用 +// color 所指定的颜色。 +static __global__ void // Kernel 函数无返回值 +_drawEllipseKer( + ImageCuda img, // 待绘制直线的椭圆 + CoordiSetCuda cst, // 用坐标点集表示的椭圆参数 + int color // 绘图使用的颜色值 +); + + +// Kernel 函数:_brushAllImageKer(涂满整幅图像) +static __global__ void _brushAllImageKer(ImageCuda img, unsigned char clr) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= img.imgMeta.width || r >= img.imgMeta.height) + return; + + // 计算第一个像素点对应的图像数据数组下标。 + int idx = r * img.pitchBytes + c; + + // 为第一个像素点赋值。 + img.imgMeta.imgData[idx] = clr; + + // 处理剩下的三个像素点。 + for (int i = 1; i < 4; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= img.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + idx += img.pitchBytes; + + // 为当前像素点赋值。 + img.imgMeta.imgData[idx] = clr; + } +} + +// Host 成员方法:brushAllImage(涂满整幅图像) +__host__ int ImageDrawer::brushAllImage(Image *img) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (img == NULL) + return NULL_POINTER; + + // 如果算法 CLASS 的背景色为透明色,则不需要进行任何处理,直接返回即可。 + if (this->brushColor == IDRAW_TRANSPARENT) + return NO_ERROR; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(img); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda subimgCud; + errcode = ImageBasicOp::roiSubImage(img, &subimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (subimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (subimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数完成计算。 + _brushAllImageKer<<>>(subimgCud, this->brushColor); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + +static __global__ void _drawLinesKer(ImageCuda img, CoordiSetCuda cst, + int color) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 计算当前线程所要处理的直线的编号。 + int lineidx = blockIdx.z; + + // 判断当前处理的直线是否已经越界。 + if (2 * lineidx + 1 >= cst.tplMeta.count) + return; + + // 共享内存声明。使用共享内存预先读出来一些数据,这样可以在一定程度上加快 + // Kernel 的执行速度。 + __shared__ int lineptShd[5]; // 直线的两个端点。 + int *curcolorShd = &lineptShd[4]; // 绘制当前直线所用的颜色。 + + // 初始化直线的两个端点。使用当前 Block 的前四个 Thread 读取两个坐标,一共 + // 四个整形数据。 + if (threadIdx.x < 4 && threadIdx.y == 0) { + lineptShd[threadIdx.x] = + cst.tplMeta.tplData[4 * lineidx + threadIdx.x]; + } + + // 初始化当前直线的颜色。 + if (threadIdx.x == 0 && threadIdx.y == 0) { + // 如果颜色值小于 0,说明当前使用了动态颜色模式,此时从坐标点集的附属数 + // 据中读取颜色值。否则,直接使用参数的颜色值作为颜色值。 + if (color < 0) + curcolorShd[0] = + ((int)(fabs(cst.attachedData[2 * lineidx]) * 255)) % 256; + else + curcolorShd[0] = color % 256; + } + + // 同步 Block 内的所有 Thread,使得上面的初始化过程对所有的 Thread 可见。 + __syncthreads(); + + // 如果当前处理的坐标不在图像所表示的范围内,则直接退出。 + if (c >= img.imgMeta.width || r >= img.imgMeta.height) + return; + + // 计算当前 Thread 对应的图像下标。 + int idx = r * img.pitchBytes + c; + + // 将 Shared Memory 中的数据读取到寄存器中,以使得接下来的计算速度更快。 + int pt0x = lineptShd[0]; + int pt0y = lineptShd[1]; + int pt1x = lineptShd[2]; + int pt1y = lineptShd[3]; + unsigned char curcolor = curcolorShd[0]; + + // 针对不同的情况,处理直线。主要的思路就是判断当前点是否在直线上,如果在直 + // 线上,则将当前点的像素值赋值为前景色值;否则什么都不做。显然,相同的 + // Block 处理的是相同的直线,因此对于该 if-else 语句会进入相同的分支,所 + // 以,这个分支语句不会导致分歧执行,也不会产生额外的性能下降。 + if (pt0x == pt1x) { + // 当直线平行于 y 轴,则判断当前点是否跟直线端点具有同样的 x 坐标,如果 + // 是,并且 y 坐标在端点的范围内,则绘制点,否则什么都不做。 + + // 对于当前点和端点具有不同 x 坐标的像素点,则直接退出。 + if (c != pt0x) + return; + + // 计算两个端点之间的 y 坐标的范围。 + int miny = min(pt0y, pt1y); + int maxy = max(pt0y, pt1y); + + // 对于 y 坐标范围已经不在图像像素范围内的情况,则直接退出。 + if (maxy < 0 || miny >= img.imgMeta.height) + return; + + // 检查当前处理的第一个点是否在 y 坐标范围内,如果在,则在该点绘制颜色 + // 点。 + if (r >= miny && r <= maxy) + img.imgMeta.imgData[idx] = curcolor; + + // 处理剩余的三个点。 + for (int i = 1; i < 4; i++) { + // 检查这些点是否越界。 + if (++r >= img.imgMeta.height) + return; + + // 调整下标值,根据上下两点之间的位置关系,可以从前一下标值推算出下 + // 一个下标值。 + idx += img.pitchBytes; + + // 检查当前处理的剩余三个点是否在 y 坐标范围内,如果在,则在该点绘 + // 制颜色点。 + if (r >= miny && r <= maxy) + img.imgMeta.imgData[idx] = curcolor; + } + } else if (pt0y == pt1y) { + // 当直线平行于 x 轴,则判断当前点是否跟直线端点具有同样的 y 坐标,如果 + // 是,并且 x 坐标在端点的范围内,则绘制点,否则什么都不做。 + + // 计算两个端点之间的 x 坐标的范围。 + int minx = min(pt0x, pt1x); + int maxx = max(pt0x, pt1x); + + // 对于当前点的 x 坐标不在两个端点的范围内的像素点,则直接退出。 + if (c < minx || c > maxx) + return; + + // 对于当前点的 y 坐标等于端点的 y 坐标,则在该点绘制颜色值。由于在该点 + // 绘制了颜色后,可以断定其后就不会再有点可以绘制了,因此绘制后直接返 + // 回。 + if (r == pt0y) { + img.imgMeta.imgData[idx] = curcolor; + return; + } + + // 处理剩余的三个点。 + for (int i = 1; i < 4; i++) { + // 检查这些点是否越界。 + if (++r >= img.imgMeta.height) + return; + + // 调整下标值,根据上下两点之间的位置关系,可以从前一下标值推算出下 + // 一个下标值。 + idx += img.pitchBytes; + + // 对于当前点的 y 坐标等于端点的 y 坐标,则在该点绘制颜色值。由于在 + // 该点绘制了颜色后,可以断定其后就不会再有点可以绘制了,因此绘制后 + // 直接返回。 + if (r == pt0y) { + img.imgMeta.imgData[idx] = curcolor; + return; + } + } + } else { + // 对于其他情况,可以直接按照直线方程进行判断。 + + // 计算两个端点之间的 x 坐标的范围。 + int minx = min(pt0x, pt1x); + int maxx = max(pt0x, pt1x); + + // 计算两个端点之间的 y 坐标的范围。 + int miny = min(pt0y, pt1y); + int maxy = max(pt0y, pt1y); + + // 对于当前点的 x 坐标不在不在两个端点的范围内的像素点,则直接退出。 + if (c < minx || c > maxx) + return; + + // 计算直线关于 x 轴的斜率,预先计算斜率可以在后面的计算过程中重复利 + // 用,以减少计算。 + float dydx = (float)(pt1y - pt0y) / (pt1x - pt0x); + + // 计算直线方程。这里如果斜率的绝对值大于 1 时(坡度大于 45 度),按照 + // 关于 y 的方程进行计算,这样函数值变化和图像栅格坐标的变化是在同数量 + // 级的。 + float fx, detfx; + if (fabs(dydx) <= 1.0f ) { + fx = dydx * (c - pt0x) + pt0y - r; + detfx = -1.0f; + } else { + dydx = 1.0f / dydx; + fx = dydx * (r - pt0y) + pt0x - c; + detfx = dydx; + } + + // 如果点落在直线上(即方程等于 0)则绘制该点的颜色值。这里判断 0.5 是 + // 考虑到图像的栅格坐标。 + if (fabs(fx) <= 0.5f && r >= miny && r <= maxy) + img.imgMeta.imgData[idx] = curcolor; + + // 处理剩余的三个点。 + for (int i = 1; i < 4; i++) { + // 检查这些点是否越界。 + if (++r >= img.imgMeta.height) + return; + + // 调整下标值和函数值,根据上下两点之间的位置关系,可以从前一下标值 + // 推算出下一个下标值。 + fx += detfx; + idx += img.pitchBytes; + + // 如果点落在直线上(即方程等于 0)则绘制该点的颜色值。这里判断 + // 0.5 是考虑到图像的栅格坐标。 + if (fabs(fx) <= 0.5f && r >= miny && r <= maxy) + img.imgMeta.imgData[idx] = curcolor; + } + } +} + +// Host 成员方法:drawLines(绘制直线) +__host__ int ImageDrawer::drawLines(Image *img, CoordiSet *cst) +{ + // 判断参数中的图像和坐标点集是否为 NULL。 + if (img == NULL || cst == NULL) + return NULL_POINTER; + + // 如果坐标点击中含有少于 2 个点则无法完成之间绘制,因此报错退出。 + if (cst->count < 2) + return INVALID_DATA; + + // 计算绘制索要使用的颜色。 + int curcolor = this->lineColor; + if (this->colorMode == IDRAW_CM_STATIC_COLOR) { + // 如果是静态着色,且颜色值为透明色,则直接退出。 + if (curcolor == IDRAW_TRANSPARENT) + return NO_ERROR; + } else if (this->colorMode == IDRAW_CM_DYNAMIC_COLOR) { + // 对于动态绘图模式,则将颜色值赋值成一个负数,这样 Kernel 函数就能知道 + // 当前使用的是动态着色模式。 + curcolor = -1; + } + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(img); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda subimgCud; + errcode = ImageBasicOp::roiSubImage(img, &subimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标点集拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + // 取出坐标点集对应的 CoordiSetCuda 型数据。 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。这里使用 Block 的 z 维 + // 度表示不同的直线。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (subimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (subimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + gridsize.z = cst->count / 2; + + // 调用 Kernel 完成计算。 + _drawLinesKer<<>>(subimgCud, *cstCud, curcolor); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Kernel 函数:_extendLineCstKer(扩展坐标点集以绘制连续直线) +static __global__ void _extendLineCstKer(CoordiSetCuda incst, + CoordiSetCuda outcst) +{ + // 计算输出坐标集的下标,本函数为每个输出坐标点配备一个 Thread。 + int outidx = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果输出点对应的是越界数据,则直接退出。 + if (outidx >= outcst.tplMeta.count) + return; + + // 计算输入下标。根据输出数组中各点对应于输入数组中的各点的关系,有【0】 + // 【1】【1】【2】【2】【3】【3】等等,据此推演出输出坐标点和输入坐标点之间 + // 的下标关系。 + int inidx = (outidx + 1) / 2; + + // 如果计算出来的输入下标是越界的,则对计算出来的输入下标取模(之所以单独提 + // 出来进行取模操作,是应为,取模操作比较耗时,且多数情况下不会出现越界的情 + // 况)。 + if (inidx >= incst.tplMeta.count) + inidx %= incst.tplMeta.count; + + // 将对应的坐标点坐标和附属数据从输入坐标点集中拷贝到输出坐标点集中。 + outcst.tplMeta.tplData[2 * outidx] = + incst.tplMeta.tplData[2 * inidx]; + outcst.tplMeta.tplData[2 * outidx + 1] = + incst.tplMeta.tplData[2 * inidx + 1]; + outcst.attachedData[outidx] = incst.attachedData[inidx]; +} + +// 宏:FAIL_DRAWTRACE_FREE +// 当下面的函数失败退出时,负责清理内存,防止内存泄漏。 +#define FAIL_DRAWTRACE_FREE do { \ + if (extcst != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(extcst); \ + } while (0) + +// Host 成员方法:drawTrace(绘制连续直线) +__host__ int ImageDrawer::drawTrace(Image *img, CoordiSet *cst, bool circled) +{ + // 检查参数中的指针是否为 NULL。 + if (img == NULL || cst == NULL) + return NULL_POINTER; + + // 如果 cst 的坐标点少于 3 个,则只能绘制 0 条或 1 条线,因此可直接调用绘制 + // 直线算法处理。 + if (cst->count < 3) + return this->drawLines(img, cst); + + // 局部变量,错误码 + int errcode; + + // 申请扩展后的坐标点集,由于该函数最终要调用 drawLines 完成绘图工作,因此 + // 首先需要根据输入的坐标点集生成所需要的扩展型坐标点集。 + CoordiSet *extcst = NULL; + errcode = CoordiSetBasicOp::newCoordiSet(&extcst); + if (errcode != NO_ERROR) { + FAIL_DRAWTRACE_FREE; + return errcode; + } + + // 计算扩展坐标点集中点的数量,并申请相应大小的内存空间。对于环形绘图相对于 + // 蛇形绘图会多一条直线。 + int extcstcnt = (circled ? 2 * cst->count : 2 * (cst->count - 1)); + errcode = CoordiSetBasicOp::makeAtCurrentDevice(extcst, extcstcnt); + if (errcode != NO_ERROR) { + FAIL_DRAWTRACE_FREE; + return errcode; + } + + // 将输入的坐标点集拷贝当当前 Device。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) { + FAIL_DRAWTRACE_FREE; + return errcode; + } + + // 获取两个坐标点集的 CUDA 型数据指针。 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + CoordiSetCuda *extcstCud = COORDISET_CUDA(extcst); + + // 计算启动 Kernel 所需要的 Block 尺寸和数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = (extcstcnt + DEF_BLOCK_1D - 1) / DEF_BLOCK_1D; + + // 启动 Kernel 完成扩展坐标集的计算操作。 + _extendLineCstKer<<>>(*cstCud, *extcstCud); + + // 检查 Kernel 执行是否正确。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_DRAWTRACE_FREE; + return errcode; + } + + // 使用扩展坐标集绘制连续直线。 + errcode = this->drawLines(img, extcst); + if (errcode != NO_ERROR) { + FAIL_DRAWTRACE_FREE; + return errcode; + } + + // 处理完毕,清除临时使用的扩展坐标点集。 + CoordiSetBasicOp::deleteCoordiSet(extcst); + + // 处理完毕,退出。 + return NO_ERROR; +} + +static __global__ void _drawEllipseKer(ImageCuda img, CoordiSetCuda cst, + int color) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 共享内存声明。使用共享内存预先读出来一些数据,这样可以在一定程度上加快 + // Kernel 的执行速度。 + __shared__ int ellipseptShd[5]; // 椭圆的外界矩形的左上顶点和右下 + // 顶点的坐标 + __shared__ float ellipsecenterptShd[2]; // 椭圆的中心坐标 + __shared__ float ellipseradiuShd[2]; // 椭圆的半径 rx,ry + int *curcolorShd = &ellipseptShd[4]; // 绘制当前椭圆所用的颜色。 + + // 初始化椭圆的外界矩形的 2 个顶点。使用前 4 个 Thread 读取 2 个坐标,一共 + // 4 个整型数据。 + if (threadIdx.x < 4 && threadIdx.y == 0) { + ellipseptShd[threadIdx.x] = + cst.tplMeta.tplData[threadIdx.x]; + } + + // 使用第 5 个 Thread 初始化当前椭圆的颜色。 + if (threadIdx.x == 4 && threadIdx.y == 0) { + // 如果颜色值小于 0,说明当前使用了动态颜色模式,此时从坐标点集的附属数 + // 据中读取颜色值。否则,直接使用参数的颜色值作为颜色值。 + if (color < 0) + curcolorShd[0] = + ((int)(fabs(cst.attachedData[0]) * 255)) % 256; + else + curcolorShd[0] = color % 256; + } + + // 同步 Block 内的所有 Thread,使得上面的初始化过程对所有的 Thread 可见。 + __syncthreads(); + + // 使用前 2 个 Thread 初始化椭圆的中心坐标 + if (threadIdx.x < 2 && threadIdx.y == 0) { + // 初始化椭圆的中心坐标 + ellipsecenterptShd[threadIdx.x] = + (float)(ellipseptShd [threadIdx.x] + + ellipseptShd [threadIdx.x + 2]) / 2; + } + + // 使用第 3 个和第 4 个 Thread 初始化椭圆的半径 + if (threadIdx.x >=2 && threadIdx.x <4 && threadIdx.y == 0) { + ellipseradiuShd[threadIdx.x - 2] = + (float)(ellipseptShd [threadIdx.x] - + ellipseptShd [threadIdx.x - 2]) / 2; + } + + // 同步 Block 内的所有 Thread,使得上面的初始化过程对所有的 Thread 可见。 + __syncthreads(); + + // 如果当前处理的坐标不在图像所表示的范围内,则直接退出。 + if (c >= img.imgMeta.width || r >= img.imgMeta.height) + return; + + // 计算当前 Thread 对应的图像下标。 + int idx = r * img.pitchBytes + c; + + // 将 Shared Memory 中的数据读取到寄存器中,以使得接下来的计算速度更快。 + int pt0x = ellipseptShd[0]; + int pt0y = ellipseptShd[1]; + int pt1x = ellipseptShd[2]; + int pt1y = ellipseptShd[3]; + float rx = ellipseradiuShd[0]; + float ry = ellipseradiuShd[1]; + float xc = ellipsecenterptShd[0]; + float yc = ellipsecenterptShd[1]; + unsigned char curcolor = curcolorShd[0]; + + // 计算椭圆的 x 坐标的范围。 + int minx = min(pt0x, pt1x); + int maxx = max(pt0x, pt1x); + + // 计算椭圆的 y 坐标的范围。 + int miny = min(pt0y, pt1y); + int maxy = max(pt0y, pt1y); + + // 针对不同的情况,处理椭圆。主要的思路就是判断当前点是否在椭圆上,如果在 + // 椭圆上,则将当前点的像素值赋值为前景色值;否则什么都不做。 + if (minx == maxx || miny == maxy) { + // 当输入点集在一条直线上时,退出程序。 + return; + } else { + // 对于其他情况,可以直接按照椭圆方程进行判断。 + + // 对于当前点的 x,y 坐标,若不在椭圆的左上四分之一范围内,则直接退出。 + if (c < minx || c > xc || r < miny || r > yc) + return; + + // 计算椭圆方程。判断点(c,r)的斜率是否大于 1 ,若大于 1 则计算 + // 点(c,r + 1)的方程的值,否则计算点(c + 1,r)的方程的值。 + float fx1,fx2; + int r1,c1; + if (ry * ry * (c - xc) >= rx * rx * (r - yc)) { + r1 = r + 1; + c1 = c; + } else { + r1 = r; + c1 = c + 1; + } + fx1 = (float)ry * ry * (c - xc)* (c - xc) + + rx * rx * (r - yc)* (r - yc) - rx * rx * ry * ry; + fx2 = (float)ry * ry * (c1 - xc)* (c1 - xc) + + rx * rx * (r1 - yc)* (r1 - yc) - rx * rx * ry * ry; + + // 如果相邻两点分别落在椭圆内和椭圆外(即方程的值一正一负)则绘制距离 + // 椭圆较近的点(即方程值的绝对值较小的点)及该点关于直线 x = xc, + // y = yc 及关于点(xc,yc)对称的 3 个点的颜色值。 + if(fx1 >= 0 && fx2 < 0) { + if(fabs(fx1) < fabs(fx2)) { + img.imgMeta.imgData[idx] = curcolor; + // 关于直线 x = xc 对称的点 + img.imgMeta.imgData[r * img.pitchBytes + (int)(2 * xc) - c] = + curcolor; + // 关于直线 y = yc 对称的点 + img.imgMeta.imgData[((int)(2 * yc) - r) * img.pitchBytes + c] = + curcolor; + // 关于点(xc,yc)对称的点 + img.imgMeta.imgData[(int)((2 * yc - r) * img.pitchBytes + + 2 * xc - c)] = curcolor; + } else { + img.imgMeta.imgData[r1 * img.pitchBytes + c1] = curcolor; + // 关于直线 x = xc 对称的点 + img.imgMeta.imgData[r1 * img.pitchBytes + + ((int)(2 * xc) - c1)] = curcolor; + // 关于直线 y = yc 对称的点 + img.imgMeta.imgData[(int)((2 * yc) - r1) * + img.pitchBytes + c1] = curcolor; + // 关于点(xc,yc)对称的点 + img.imgMeta.imgData[(int)((2 * yc - r1) *img.pitchBytes + + 2 * xc - c1)] = curcolor; + } + } + + // 处理剩余的三个点。 + for (int i = 1; i < 4; i++) { + // 检查这些点是否越界。 + if (++r > yc) + return; + + // 计算椭圆方程。判断点(c,r)的斜率是否大于 1 ,若大于 1 则计算 + // 点(c,r + 1)的方程的值,否则计算点(c + 1,r)的方程的值。 + if(ry * ry * (c - xc) >= rx * rx * (r - yc)) { + r1 = r + 1; + c1 = c; + } else { + r1 = r; + c1 = c + 1; + } + fx1 = (float)ry * ry * (c - xc)* (c - xc) + + rx * rx * (r - yc)* (r - yc) - rx * rx * ry * ry; + fx2 = (float)ry * ry * (c1 - xc)* (c1 - xc) + + rx * rx * (r1 - yc)* (r1 - yc) - rx * rx * ry * ry; + + idx += img.pitchBytes; + + // 如果相邻两点分别落在椭圆内和椭圆外(即方程的值一正一负)则绘制 + // 距离椭圆较近的点(即方程值的绝对值较小的点)及该点关于直线 + // x = xc,y = yc 及关于点(xc,yc)对称的 3 个点的颜色值。 + if (fx1 >= 0 && fx2 < 0) { + if(fabs(fx1) < fabs(fx2)) { + img.imgMeta.imgData[idx] = curcolor; + // 关于直线 x = xc 对称的点 + img.imgMeta.imgData[r * img.pitchBytes + (int)(2 * xc) - + c] =curcolor; + // 关于直线 y = yc 对称的点 + img.imgMeta.imgData[((int)(2 * yc) - r) * img.pitchBytes + + c] = curcolor; + // 关于点(xc,yc)对称的点 + img.imgMeta.imgData[(int)((2 * yc - r) * img.pitchBytes + + 2 * xc - c)] = curcolor; + } else { + img.imgMeta.imgData[r1 * img.pitchBytes + c1] = curcolor; + // 关于直线 x = xc 对称的点 + img.imgMeta.imgData[r1 * img.pitchBytes + (int)(2 * xc) - + c1] = curcolor; + // 关于直线 y = yc 对称的点 + img.imgMeta.imgData[((int)(2 * yc) - r1) * img.pitchBytes + + c1] = curcolor; + // 关于点(xc,yc)对称的点 + img.imgMeta.imgData[(int)((2 * yc - r1) * img.pitchBytes + + 2 * xc - c1)] = curcolor; + } + } + } + } +} + +// Host 成员方法:drawEllipse(绘制椭圆) +__host__ int ImageDrawer::drawEllipse(Image *img, CoordiSet *cst) +{ + // 判断参数中的图像和坐标点集是否为 NULL。 + if (img == NULL || cst == NULL) + return NULL_POINTER; + + // 如果坐标点击中含有少于 2 个点则无法完成椭圆绘制,因此报错退出。 + if (cst->count < 2) + return INVALID_DATA; + + // 计算绘制所要使用的颜色。 + int curcolor = this->lineColor; + if (this->colorMode == IDRAW_CM_STATIC_COLOR) { + // 如果是静态着色,且颜色值为透明色,则直接退出。 + if (curcolor == IDRAW_TRANSPARENT) + return NO_ERROR; + } else if (this->colorMode == IDRAW_CM_DYNAMIC_COLOR) { + // 对于动态绘图模式,则将颜色值赋值成一个负数,这样 Kernel 函数就能知道 + // 当前使用的是动态着色模式。 + curcolor = -1; + } + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(img); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda subimgCud; + errcode = ImageBasicOp::roiSubImage(img, &subimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将坐标点集拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(cst); + if (errcode != NO_ERROR) + return errcode; + + // 取出坐标点集对应的 CoordiSetCuda 型数据。 + CoordiSetCuda *cstCud = COORDISET_CUDA(cst); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (subimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (subimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用 Kernel 完成计算。 + _drawEllipseKer<<>>(subimgCud, *cstCud, curcolor); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} \ No newline at end of file diff --git a/okano_3_0/ImageDrawer.h b/okano_3_0/ImageDrawer.h new file mode 100644 index 0000000..7067d0d --- /dev/null +++ b/okano_3_0/ImageDrawer.h @@ -0,0 +1,224 @@ +// ImageDrawer.h +// 创建人:于玉龙 +// +// 绘制几何构建(Image Drawer) +// 功能说明:在给定的图像上绘制相应的标记,如直线、圆等。 +// +// 修订历史: +// 2013年03月18日(于玉龙) +// 初始版本。 +// 2013年03月19日(于玉龙) +// 修正了设定着色模式时的一处 Bug。实现了绘制直线的算法。 +// 补全了目前代码所需要的注释。修正了代码中一处潜在的 Bug。 +// 2013年03月20日(于玉龙) +// 完成了连续直线绘制的函数。 +// 2013年03月21日(于玉龙) +// 修正了直线绘制过程中的一个 Bug。 +// 2013年05月26日(张鹤) +// 完成了椭圆绘制的函数。 + + +#ifndef __IMAGEDRAWER_H__ +#define __IMAGEDRAWER_H__ + +#include "ErrorCode.h" +#include "Image.h" +#include "CoordiSet.h" + + +// 宏:IDRAW_CM_STATIC_COLOR +// 这是一种着色模式,用来表示当前绘图时,前景色采用算法 CLASS 成员变量中所指定 +// 的颜色(所谓颜色,就是指亮度值) +#define IDRAW_CM_STATIC_COLOR 0 + +// 宏:IDRAW_CM_DYNAMIC_COLOR +// 这是一种着色模式,用来表示当前绘图时,前景色采用参数中某些特定元素代表的颜 +// 色(所谓颜色,就是指亮度值) +#define IDRAW_CM_DYNAMIC_COLOR 1 + +// 宏:IDRAW_TRANSPARENT +// 透明色 +#define IDRAW_TRANSPARENT -1 + +// 类:ImageDrawer(构建绘图器) +// 继承自:无 +// 在给定的图像上绘制相应的标记,如直线、圆等。 +class ImageDrawer { + +protected: + + // 成员变量:colorMode(着色模式) + // 设定着色模式,在绘制某些构建时需要从不同的颜色源来选择前景色,该成员规定 + // 了绘图时如何选择颜色源。 + int colorMode; + + // 成员变量:lineColor(前景色) + // 规定了使用静态着色模式时的前景色,可以定义为透明色。 + int lineColor; + + // 成员变量:brushColor(背景色) + // 规定了绘图时使用的背景色,可以为透明色。 + int brushColor; + +public: + + // 构造函数:ImageDrawer + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + ImageDrawer() + { + // 使用默认值为类的各个成员变量赋值。 + colorMode = IDRAW_CM_STATIC_COLOR; // 着色类型默认设为静态着色。 + lineColor = 255; // 直线前景色默认设为白色。 + brushColor = IDRAW_TRANSPARENT; // 背景色默认设为透明色。 + } + + // 构造函数:ImageDrawer + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + ImageDrawer( + int clrmode, // 着色模式 + int lineclr, // 直线前景色 + int brushclr // 背景色 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + colorMode = IDRAW_CM_STATIC_COLOR; // 着色类型默认设为静态着色。 + lineColor = 255; + brushColor = IDRAW_TRANSPARENT; // 背景色默认设为透明色。 + + // 根据参数列表中的值设定成员变量的初值 + setColorMode(clrmode); + setLineColor(lineclr); + setBrushColor(brushclr); + } + + // 成员方法:getColorMode(获取着色模式) + // 获取成员变量 colorMode 的值。 + __host__ __device__ int // 返回值:成员变量 colorMode 的值。 + getColorMode() const + { + // 返回成员变量 colorMode 的值。 + return this->colorMode; + } + + // 成员方法:setColorMode(设置着色模式) + // 设置成员变量 colorMode 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setColorMode( + int clrmode // 新的着色模式 + ) { + // 检查参数的合法性。 + if (clrmode != IDRAW_CM_STATIC_COLOR && + clrmode != IDRAW_CM_DYNAMIC_COLOR) + return INVALID_DATA; + + // 设置新的着色模式。 + this->colorMode = clrmode; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getLineColor(获取直线前景色) + // 获取成员变量 lineColor 的值。 + __host__ __device__ int // 返回值:成员变量 lineColor 的值。 + getLineColor() const + { + // 返回成员变量 lineColor 的值。 + return this->lineColor; + } + + // 成员方法:setLineColor(设置直线前景色) + // 设置成员变量 lineColor 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setLineColor( + int lineclr // 新的直线前景色 + ) { + // 检查参数的合法性。 + if (lineclr != IDRAW_TRANSPARENT && (lineclr < 0 || lineclr > 255)) + return INVALID_DATA; + + // 设置新的直线前景色。 + this->lineColor = lineclr; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getBrushColor(获取背景色) + // 获取成员变量 brushColor 的值。 + __host__ __device__ int // 返回值:成员变量 brushColor 的值。 + getBrushColor() const + { + // 返回成员变量 brushColor 的值。 + return this->brushColor; + } + + // 成员方法:setBrushColor(设置背景色) + // 设置成员变量 brushColor 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setBrushColor( + int brushclr // 新的背景色 + ) { + // 检查参数的合法性。 + if (brushclr != IDRAW_TRANSPARENT && (brushclr < 0 || brushclr > 255)) + return INVALID_DATA; + + // 设置新的背景色。 + this->brushColor = brushclr; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // Host 成员方法:brushAllImage(涂满整幅图像) + // 将图像中所有的像素都涂成背景色。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + brushAllImage( + Image *img // 待背涂色的图像。 + ); + + // Host 成员方法:drawLines(绘制直线) + // 在图像上绘制直线。如果当前使用的着色模式是静态模式,则直线使用 CLASS 内 + // 定义的前景色;如果是动态模式,则使用点集中左侧点的附属数据所表示的颜色 + // 值。背景色在该函数中不起作用。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + drawLines( + Image *img, // 待绘制直线的图像 + CoordiSet *cst // 坐标点集,每两个相邻点绘制一条直线。 + ); + + // Host 成员方法:drawTrace(绘制连续直线) + // 在图像上绘制连续直线最后形成一个折线。可以选择是绘制成收尾相接的环形,还 + // 是收尾不相接的蛇形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + drawTrace( + Image *img, // 待绘制直线的图像 + CoordiSet *cst, // 坐标点击,每两点绘制出一条连续的折线 + bool circled = true // 是否将坐标集的最后一个点和第一个点连起来。 + // 若该值为 true,则最后绘制出一个多边形,否则 + // 绘制出一条蛇形折线。 + ); + + // Host 成员方法:drawEllipse(绘制椭圆) + // 在图像上绘制椭圆。如果当前使用的着色模式是静态模式,则直线使用 CLASS 内 + // 定义的前景色;如果是动态模式,则使用点集中左侧点的附属数据所表示的颜色 + // 值。背景色在该函数中不起作用。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + drawEllipse( + Image *img, // 待绘制椭圆的图像 + CoordiSet *cst // 坐标点集(椭圆的外界矩形的左上角和右下角的坐标) + ); +}; + +#endif + diff --git a/okano_3_0/ImageMatch.cu b/okano_3_0/ImageMatch.cu new file mode 100644 index 0000000..ec91c2b --- /dev/null +++ b/okano_3_0/ImageMatch.cu @@ -0,0 +1,1012 @@ +// ImageMacth.cu +// 实现对图像进行匹配的操作 + +#include "ConnectRegion.h" +#include "ErrorCode.h" +#include "ImageMatch.h" +#include "LabelIslandSortArea.h" +#include "Normalization.h" +#include "Rectangle.h" +#include "RoiCopy.h" +#include "SmallestDirRect.h" +#include "Template.h" +#include "TemplateFactory.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:FAST_RUN +// 打开该开关,在 Kernel 中将不会搜索当前点的领域,可以加快运行时间,但匹配的 +// 准确度将没有那么高 +#define FAST_RUN + +#ifdef FAST_RUN +// Device 数据,_tpl1x1Gpu +// 为了加快算法的运行时间,用此数据来替代 _tpl3x3Gpu +static __device__ int _tpl1x1Gpu[] = {0, 0}; +#else +// Device 数据:_tpl3x3Gpu +// 3 * 3 的模版,为了加快算法的速度,暂时不用 +static __device__ int _tpl3x3Gpu[] = { -1, -1, 0, -1, 1, -1, + -1, 0, 0, 0, 1, 0, + -1, 1, 0, 1, 1, 1 }; +#endif + +// Device 函数:_rotateXYDev(计算旋转表坐标对应的在 TEST 图像上的坐标) +// 计算 TEMPLATE 坐标对应的旋转表坐标在 TEST 图像上所对应的坐标 +static __device__ int // 返回值:函数是否正确执行,如果函数 + // 正确执行,返回 NO_ERROR +_rotateXYDev( + int x, int y, // TEMPLATE 对应的旋转表的横坐标和纵 + // 坐标,旋转中心为原点 + int xc, int yc, // 现在匹配的横坐标和纵坐标,以左上角为原点 + RotateTable rotatetable, // 旋转表 + float angle, // 旋转角 + int *rx, // 转换后得到对应 TEST 图像的横坐标 + int *ry // 转换后得到对应 TEST 图像的纵坐标 +); + +// Device 函数:_getSuitValueFromNormalTplDev(在正规化结果中找到合适值) +// 在 TEMPLATE 的指定坐标的一个邻域内找到正规化后与 flag 最接近的一个值 +static __device__ float // 返回值:在正规化结果中与 flag 最接近的值 +_getSuitValueFromNormalTplDev( + int x, int y, // 在 TEMPLATE 的坐标位置 + float *normalizedata, // TEMPLATE 中正规化的结果 + size_t pitch, // normalizedata 的 pitch 值 + int width, int height, // TEMPLATE 的宽和高 + float flag // 需要对比的值 +); + +// Host 函数:_getCormapMaxIndex(获取 cormapsum 中最大的值的索引) +// 在匹配得到的结果中找到最大的值 +static __host__ int // 返回值:cormapsum 中最大的值的索引 +_getCormapMaxIndex( + float *cormapcpu, // cormapsum 的数据 + int count // cormapsum 中数据的数量 +); + +// Kernel 函数:_calCorMapSumKer(计算每个点的邻域内 cormap 的和) +// 计算一每个点为中心的邻域内 cormap 的和 +static __global__ void // 返回值:Kernel 无返回值 +_calCorMapSumKer( + float *cormap, // cormap 的数据 + int dwidth, int dheight, // 摄动范围的宽和高 + int scope, // 邻域的范围,以 scope * scope 的范围计算 + float *cormapsumgpu // 求和得到的结果 +); + +// Kernel 函数:_matchKer(将一组 TEMPLATE 分别和 TEST 图像进行匹配) +// 将一组 TEMPLATE 图像正规化后,用不同的旋转角与 TEST 图像进行匹配,得到每个 +// 点的相关系数 +static __global__ void // 返回值:Kernel 无返回值 +_matchKer( + float **tplnormalization, // 每个 TEMPLATE 正规化的结果 + size_t *tplpitch, // 每个 TEMPLATE 正规化数组的 ptich 值 + int tplcount, // TEMPLATE 的数量 + int tplwidth, int tplheight, // 每个 TEMPLATE 的宽和高 + float *testnormalization, // TEST 正规化的结果 + size_t testpitch, // TEST 正规化结果的 pitch 值 + int testwidth, int testheight, // TEST 图像的宽和高 + RotateTable rotatetable, // 旋转表 + float *cormap, // 用来存储每个点匹配得到的相关系数 + int offsetx, int offsety, // 摄动范围的偏移量 + int dwidth, int dheight, // 摄动范围的宽和高 + int tploffx, int tploffy // TEMPLATE 的偏移量 +); + +// Kernel 函数:_localCheckErrKer(进行局部异常检查) +// 对匹配得到的结果进行局部异常检查 +static __global__ void // 返回值:Kernel 无返回值 +_localCheckErrKer( + float *besttplnor, // 匹配得到的 TEMPLATE 的正规化数据 + size_t besttplpitch, // besttplnor 的 pitch 值 + int tplwidth, int tplheight, // TEMPLATE 的宽和高 + float *testnormalization, // TEST 图像的正规化数据 + size_t testpitch, // testnormalization 的 pitch 值 + int testwidth, int testheight, // TEST 图像的宽和高 + RotateTable rotatetable, // 旋转表 + int *errmap, // 记录异常情况的数组 + size_t errmappitch, // errmap 的 pitch 值 + int errmapwidth, int errmapheight, // errmap 数组的大小 + float errthreshold, // 异常检查的阈值 + int mx, int my, // 匹配得到的匹配中心 + float angle, // 匹配得到的旋转角 + int tploffx, int tploffy // TEMPLATE 的偏移量 +); + +// Kernel 函数:_binarizeKer(对 errmap 进行二值化) +// 对 errMap 进行二值化 +static __global__ void // 返回值:Kernel 无返回值 +_binarizeKer( + int *errmap, // errmap 的数据 + size_t errmappitch, // errmap 的 pitch 值 + int errmapwidth, int errmapheight, // errmap 的宽和高 + ImageCuda out // 二值化的输出图像 +); + +// Kernel 函数:_getMaxWinKer(获取每个 window 高值点的个数) +// 扫描每个 window,获取每个 window 的个数 +static __global__ void // 返回值:函数是否正确执行,若正确执行, + // 返回 NO_ERROR +_getMaxWinKer( + ImageCuda errmapimg, // errmap 的二值化图像 + Template wintpl, // window 的模版 + int *wincountcud // 存放每个 window 的扫描结果 +); + +// Host 函数:_getDirectRectForErrMap(获取 errmap 的最小有向四边形) +// 获取 errmap 中孤岛的最小有向四边形 +static __host__ int // 返回值:函数是否正确执行,若正确执行, + // 返回 NO_ERROR +_getDirectRectForErrMap( + int *errmap, // errmap 数据 + size_t errmappitch, // errmap 的 pitch 值 + int errmapwidth, // errmap 的宽 + int errmapheight, // errmap 的高 + int errwinwidth, // window 的宽 + int errwinheight, // window 的高 + int errwinthreshold, // window 的阈值 + DirectedRect *dirrect // 得到的最小有向四边形 +); + +// Device 函数:_getSuitValueFromNormalTplDev(在正规化结果中找到合适值) +static __device__ float _getSuitValueFromNormalTplDev(int x, int y, + float *normalizedata, + size_t pitch, int width, + int height, float flag) +{ +#ifdef FAST_RUN + int *tpl = _tpl1x1Gpu; // 指向 1 * 1 模版的指针 +#else + int *tpl = _tpl3x3Gpu; // 指向 3 * 3 模版的指针 +#endif + int currx = x, curry = y; // 当前在 TEMPLATE 中的坐标 + float currvalue; // 存放当前最接近 flag 的值 + float currdiff; // 存放 currvalue 与 flag 的差的绝对值 + + // 初始化 currvalue 为 (x, y)对应的正规化值 + currvalue = *((float *)((char *)normalizedata + curry * pitch) + currx); + // 记录与 flag 最接近的值的差,初始化为(x, y)对应的正规化的值与 flag 的 + // 绝对值 + float mindiff = fabsf(currvalue - flag); + // 记录与 flag 最接近的值,初始化为(x, y)对应的正规化的值 + float minvalue = currvalue; + + // 扫描模版的每一个点,找到与 flag 最接近的值 +#ifdef FAST_RUN + for (int i = 0; i < 1; i++) { + for (int j = 0; j < 1; j++) { +#else + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { +#endif + // 计算当前点的坐标 + currx = x + *(tpl++); + curry = y + *(tpl++); + // 判断当前点的坐标是否越界,如果越界则跳过 + if (currx >= 0 && currx < width && curry >= 0 && curry < height) { + // 计算当前坐标的正规化的值 + currvalue = *((float *)((char *)normalizedata + curry * pitch) + + currx); + // 计算 currvalue 与 flag 的绝对值 + currdiff = fabsf(currvalue - flag); + // 如果 currdiff 比 mindiff 小,则用 currvalue 替换 minvalue + if (currdiff < mindiff) + mindiff = currdiff; + minvalue = currvalue; + } + } + } + // 返回与 flag 最接近的值 + return minvalue; +} + +// Device 函数:_rotateXYDev(计算旋转表坐标对应的在 TEST 图像上的坐标) +static __device__ int _rotateXYDev(int x, int y, int xc, int yc, + RotateTable rotatetable, float angle, + int *rx, int *ry) +{ + int errcode; // 局部变量,错误码 + float tx, ty; // 存放通过旋转表得到的旋转后的坐标 + + // 获取旋转表旋转后的坐标 + errcode = rotatetable.getRotatePos(x, y, angle, tx, ty); + if (errcode != NO_ERROR) + return errcode; + + // 计算旋转后的坐标对应在 TEST 图像上的坐标,先对 tx 和 ty 进行四舍五入 + *rx = (int)(tx + 0.5f) + xc; + *ry = (int)(ty + 0.5f) + yc; + + // 执行完毕,返回 NO_ERROR + return NO_ERROR; +} + +// 成员方法:initNormalizeData(对设置的 TEMPLATE 进行初始化) +__host__ int ImageMatch::initNormalizeData() +{ + int cudaerr; // 局部变量,调用 CUDA 系统 API 返回的错误码 + + // 删除之前的 TEMPLATE 的相关数据 + deleteNormalizeData(); + // 将临时记录 TEMPLATE 的数量的 tplTmpCount 赋值给 tplCount + this->tplCount = this->tplTmpCount; + + // 为 tplNormalization 申请空间 + tplNormalization = new float *[this->tplCount]; + if (tplNormalization == NULL) + return OUT_OF_MEM; + // 为 ptich 申请空间 + pitch = new size_t[this->tplCount]; + if (pitch == NULL) + return OUT_OF_MEM; + + // 依次为 tplNormalization 的每一个元素分别申请 Device 空间 + for (int i = 0; i < tplCount; i++) { + // 申请 Device 空间 + cudaerr = cudaMallocPitch((void **)&(tplNormalization[i]), &(pitch[i]), + tplWidth * sizeof (float), tplHeight); + if (cudaerr != cudaSuccess) + return CUDA_ERROR; + } + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// 成员方法:deleteNormalizeData(删除 TEMPLATE 正规化的数据) +__host__ int ImageMatch::deleteNormalizeData() +{ + // 先判断 tplNormalization 是否为空,若不为空,则删除每个 tplNormalization + // 的 Device 空间,然后删除 tplNormalization 指向的空间 + if (tplNormalization != NULL) { + // 扫描每一个 tplNormalization 成员 + for(int i = 0; i < tplCount; i++) + // 释放每一个 tplNormalization 成员指向的 Device 内存空间 + cudaFree(tplNormalization[i]); + + // 释放 tplNormalization 指向的空间 + delete tplNormalization; + // 将 tplNormalization 置空,防止成为野指针 + tplNormalization = NULL; + } + + // 先判断 pitch 是否为空,若为空,则跳过 + if (pitch != NULL) { + // 若不为空,则释放 pitch 指向的内存空间 + delete pitch; + // 将 pitch 置空,防止成为野指针 + pitch = NULL; + } + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// 成员方法:normalizeForTpl(对设定的 TEMPLATE 进行正规化) +__host__ int ImageMatch::normalizeForTpl() +{ + int errcode; //局部变量,错误码 + + // 先判断是否需要对 TEMPLATE 进行正规化,若不需要,则直接返回,这样可以增 + // 加效率 + if (this->needNormalization) { + // 先初始化正规化的数据,为存放正规化的结果申请空间 + errcode = initNormalizeData(); + if (errcode != NO_ERROR) + return errcode; + + // 创建一个用来正规化操作的对象 + Normalization normal(3); + // 扫每一个 TEMPLATE + for (int i = 0; i < tplCount; i++) { + // 分别对每一个 TEMPLATE 进行正规化操作 + errcode = normal.normalize(tplImages[i], tplNormalization[i], + pitch[i], tplWidth, tplHeight, false); + if (errcode != NO_ERROR) { + return errcode; + } + } + + // 将 needNormalization 变量设置为 false,防止下次再进行正规化 + this->needNormalization = false; + } + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// Host 函数:_getCormapMaxIndex(获取 cormap 中最大的值的索引) +static __host__ int _getCormapMaxIndex(float *cormapcpu, int count) +{ + // 记录 cormap 中的最大值,初始化为 cormap 中的第 1 个数 + float max = cormapcpu[0]; + // 记录 cormap 中的最大值的索引,初始化为第 1 个数 + int maxindex = 0; + + // 扫描每个 cormap 中的数据 + for (int i = 1; i < count; i++) { + // 若当前 cormap 比 max 大,则替换 max 的值,并记录当前的索引 + if (max < cormapcpu[i]) { + max = cormapcpu[i]; + maxindex = i; + } + } + + // 处理完毕,返回 cormap 中最大值的索引 + return maxindex; +} + +// 对 errMap 进行二值化 +static __global__ void _binarizeKer(int *errmap, size_t errmappitch, + int errmapwidth, int errmapheight, + ImageCuda out) +{ + // 计算当前像素要处理的点的坐标 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断要处理的点的坐标是否越界,若越界,则直接返回 + if (x < 0 || x >= errmapwidth || y < 0 || y >= errmapheight) + return; + + // 获取当前坐标对应的 errmap 的指针 + int *perr = (int *)((char *)errmap + y * errmappitch) + x; + // 判断当前坐标对应的 errmap 中的值是否大于阈值,若大于,则赋值为 255,否则 + // 赋值为 0 + *(out.imgMeta.imgData + y * out.pitchBytes + x) = (*perr > 0) ? 255 : 0; +} + +// 找 window 内点最大的 window 的中心 +static __global__ void _getMaxWinKer(ImageCuda errmapimg, Template wintpl, + int *wincountcud) +{ + // 计算当前像素要处理的点的坐标 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断要处理的点的坐标是否越界,若越界,则直接返回 + if (dstc < 0 || dstc >= errmapimg.imgMeta.width + || dstr < 0 || dstr >= errmapimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + // 用来记录当前模版所在位置的指针 + int *curtplptr = wintpl.tplData; + // 用来统计 window 的高值点的个数 + int count = 0; + // 用来记录图像某点的位置 + unsigned char pix; + + // 扫描模版范围内的每个像素点 + for (int i = 0; i < wintpl.count; i++) { + // 计算当前模版位置所在像素的 x 和 y 分量,模版使用相邻的两个下标的 + // 数组表示一个点,所以使当前模版位置的指针作加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + // 判断 dx 和 dy 是否越界 + if (dx >= 0 && dx < errmapimg.imgMeta.width + && dy >= 0 && dy < errmapimg.imgMeta.height) { + // 获取(dx,dy)所在图像的位置指针 + pix = *(errmapimg.imgMeta.imgData + dy * errmapimg.pitchBytes + dx); + // 判断当前像素是否大于 0,若大于 0,则 count 加一 + (pix > 0) ? (count++) : 0; + } + } + // 将统计的个数存放到 wincountcud 中 + *(wincountcud + dstr * errmapimg.imgMeta.width + dstc) = count; +} + +// 获取 errMap 中密度较大的各个孤岛的外接最小有向四边形 +static __host__ int _getDirectRectForErrMap(int *errmap, size_t errmappitch, + int errmapwidth, int errmapheight, + int errwinwidth, int errwinheight, + int errwinthreshold, + DirectedRect *dirrect) +{ + // 判断 errmap 是否为空,若为空,则返回 NULL_POINTER + if (errmap == NULL || dirrect == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + cudaError_t cudaerr; // 局部变量,CUDA 调用返回错误码 + dim3 blocksize; + dim3 gridsize; + + // window 的模版 + Template *wintpl; + // 从 TemplateFactory 中获取一个矩形模版 + errcode = TemplateFactory::getTemplate(&wintpl, TF_SHAPE_BOX, + dim3(errwinwidth, errwinheight)); + // 若获取失败,则返回错误 + if (errcode != NO_ERROR) + return errcode; + // 将模版数据拷贝到设备端 + TemplateBasicOp::copyToCurrentDevice(wintpl); + + // 在设备端,用来存放每个 window 的高值点个数 + int *wincountcud; + // 用来存放 wincount 的大小 + int wincountsize = sizeof (int) * errmapwidth * errmapheight; + // 为 wincountcud 在设备端申请一段内存 + cudaerr = cudaMalloc((void **)&wincountcud, wincountsize); + // 若申请失败,则返回错误 + if (cudaerr != cudaSuccess) { + TemplateFactory::putTemplate(wintpl); + return CUDA_ERROR; + } + + // 计算线程块的大小 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (errmapwidth + blocksize.x - 1) / blocksize.x; + gridsize.y = (errmapheight + blocksize.y - 1) / blocksize.y; + + // errmap 通过二值化得到的图像数据 + Image *errmapimg; + errcode = ImageBasicOp::newImage(&errmapimg); + if (errcode != NO_ERROR) + return errcode; + + // 为 errmapimg 在 Device 端申请空间 + errcode = ImageBasicOp::makeAtCurrentDevice(errmapimg, + errmapwidth, errmapheight); + if (errcode != NO_ERROR) { + ImageBasicOp::deleteImage(errmapimg); + return errcode; + } + + // 将 errmapimg 转化为 ImageCuda 格式 + ImageCuda *errmapimgcud; + errmapimgcud = IMAGE_CUDA(errmapimg); + + // 调用二值化函数对 errmap 转化为二值图像 errmapimg + _binarizeKer<<>>(errmap, errmappitch, + errmapwidth, errmapheight, + *errmapimgcud); + // 若二值化出现错误,则释放内存空间,然后返回 CUDA_ERROR + if (cudaGetLastError() != cudaSuccess) { + TemplateFactory::putTemplate(wintpl); + cudaFree(wincountcud); + return CUDA_ERROR; + } + + // 统计每个 window 的高值点的个数 + _getMaxWinKer<<>>(*errmapimgcud, *wintpl, wincountcud); + // 若 kernel 函数错误,则释放内存空间,然后返回 CUDA_ERROR + if (cudaGetLastError() != cudaSuccess) { + TemplateFactory::putTemplate(wintpl); + cudaFree(wincountcud); + return CUDA_ERROR; + } + // 将模版放回到模版工厂里 + TemplateFactory::putTemplate(wintpl); + + // 在 CPU 端申请一段空间,用来存放得到的每个 window 的高值点的个数 + int *wincount; + wincount = (int *)malloc(wincountsize); + if (wincount == NULL) { + cudaFree(wincountcud); + return OUT_OF_MEM; + } + + // 将 wincountcud 中的数据从设备端拷贝到 CPU 端 + cudaerr = cudaMemcpy(wincount, wincountcud, wincountsize, + cudaMemcpyDeviceToHost); + if (cudaerr != cudaSuccess) { + cudaFree(wincountcud); + free(wincount); + return CUDA_ERROR; + } + + // 记录高值点个数最多的 window 的中心坐标 + int maxwinx = 0, maxwiny = 0; + // 记录 window 中高值点最大的数量,初始化为阈值 + int maxwinvalue = errwinthreshold; + for (int i = 0; i < errmapheight; i++) { + for (int j = 0; j < errmapwidth; j++) { + if (maxwinvalue < *(wincount + i * errmapwidth + j)) { + // 记录当前高值点最大的中心点 + maxwinvalue = *(wincount + i * errmapwidth + j); + maxwinx = j; + maxwiny = i; + } + } + } + + // 若最大值等于阈值,则不存在局部异常 + if (maxwinvalue == errwinthreshold) { + // 将 dirrect 的所有成员都置零 + dirrect->angle = 0.0f; + dirrect->centerPoint[0] = 0; + dirrect->centerPoint[1] = 0; + dirrect->length1 = 0; + dirrect->length2 = 0; + } else { + // 定义一个用来获取最小有向四边形的对象 + SmallestDirRect sdr; + // 设置 errmapimg 的 ROI + errmapimg->roiX1 = maxwinx - errwinwidth / 2; + errmapimg->roiY1 = maxwiny - errwinheight / 2; + errmapimg->roiX2 = maxwinx + errwinwidth / 2; + errmapimg->roiY2 = maxwiny + errwinheight / 2; + + RoiCopy roicopy; + Image *timg; + ImageBasicOp::newImage(&timg); + roicopy.roiCopyAtHost(errmapimg, timg); + + // 调用最小有向四边形算法来得到 errmapimg 的最小有向四边形 + errcode = sdr.smallestDirRect(timg, dirrect); + // 若失败,则释放内存空间,然后返回错误 + if (errcode != NO_ERROR) { + cudaFree(wincountcud); + free(wincount); + return errcode; + } + } + + // 释放空间 + cudaFree(wincountcud); + free(wincount); + // 处理完毕,返回 + return NO_ERROR; +} + +// Kernel 函数:_calCorMapSumKer(计算每个点的邻域内 cormap 的和) +static __global__ void _calCorMapSumKer(float *cormap, + int dwidth, int dheight, + int scope, float *cormapsumgpu) +{ + // Kernel 采用三维来处理,其中第一、二维是摄动范围的坐标,第三维用来记录是 + // 所有模版的数量,其中 z % 模版数量,表示当前是第几个模版,z / 模版数量 + // 表示当前是第几个旋转角度 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + int z = blockIdx.z; + + // 判断坐标是否越界,如果越界,则直接返回 + if (x < 0 || x >= dwidth || y < 0 || y >= dheight) + return ; + + // 记录当前处理的坐标 + int currx, curry; + // 记录(x, y)邻域内的 cormap 的和,初始化为 0.0 + float sum = 0.0f; + + // 依次扫描邻域内的每个点,然后求和 + for (int i = 0; i < scope; i++) { + for (int j = 0; j < scope; j++) { + // 计算当前需要处理的点的坐标 + currx = x + j - scope / 2; + curry = y + i - scope / 2; + // 判断当前坐标是否越界 + if (currx >= 0 && currx < dwidth && curry >= 0 && curry < dheight) { + // 将当前坐标对应的 cormap 值加到 sum 中 + sum += *(cormap + z * dwidth * dheight + curry * dwidth + + currx); + } + } + } + + // 将得到的结果存入 cormapsumgpu 中 + *(cormapsumgpu + z * dwidth * dheight + y * dwidth + x) = sum; +} + +// Kernel 函数:_matchKer(将一组 TEMPLATE 分别和 TEST 图像进行匹配) +static __global__ void _matchKer(float **tplnormalization, size_t *tplpitch, + int tplcount, int tplwidth, int tplheight, + float *testnormalization, size_t testpitch, + int testwidth, int testheight, + RotateTable rotatetable, float *cormap, + int offsetx, int offsety, + int dwidth, int dheight, int tploffx, + int tploffy) +{ + // Kernel 采用三维来处理,其中第一、二维是摄动范围的坐标,第三维用来记录是 + // 所有模版的数量,其中 z % 模版数量,表示当前是第几个模版,z / 模版数量 + // 表示当前是第几个旋转角度 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + int z = blockIdx.z; + + // 判断坐标是否越界,如果越界,则直接返回 + if (x < 0 || x >= dwidth || y < 0 || y >= dheight) + return; + + // 记录原始的(x, y)的坐标,在后面寻址的时候需要使用 + int tx = x; + int ty = y; + // 计算实际的(x, y)在 TEST 图像上的坐标,这里只需要加上一个偏移量就能 + // 计算出 + x += offsetx; + y += offsety; + + int errcode; // 局部变量,错误码 + int testx, testy; // 局部变量,记录在 TEST 图像上的坐标 + float *testnorptr; // 局部变量,指向 TEST 的正规化的结果的指针 + float tcormap = 0.0f; // 局部变量,存储(x, y)的相关系数 + + // 获取旋转角度 + float angle = rotatetable.getAngleVal(z / tplcount); + // 计算当前 TEMPLATE 的缩影 + int tplindex = z % tplcount; + + // 依次扫描当前要处理的 TEMPLATE + for (int i = 0; i < tplheight; i++) { + for (int j = 0; j < tplwidth; j++) { + // 计算每个 TEMPLATE 点旋转 angle 后对应在 TEST 图像上的坐标 + errcode = _rotateXYDev(j - tploffx, i - tploffy, + x, y, rotatetable, angle, + &testx, &testy); + // 若返回错误,则跳过,处理下一个点 + if (errcode != NO_ERROR) + continue ; + + // 如果 testx 和 testy 不在 TEST 图像内,则跳过,处理下一个点 + if (testx < 0 || testx >= testwidth || + testy < 0 || testy >= testheight) + continue; + + // 获取旋转后得到的点在 TEST 正规化结果的指针 + testnorptr = (float *)((char *)testnormalization + + testy * testpitch) + testx; + // 得到对应的 TEST 正规化的值 + float testnor = *testnorptr; + // 在对应 TEMPLATE 的正规化结果的邻域中找到与 testnor 最接近的 + float tplnor = _getSuitValueFromNormalTplDev( + j, i, + tplnormalization[tplindex], + tplpitch[tplindex], + tplwidth, tplheight, + testnor); + + // 将当前的 TEMPLATE 坐标的相关系数加到 tcormap 中 + tcormap += testnor * tplnor; + } + } + + // 将计算得到的相关系数写入 cormap 中 + *(cormap + z * dwidth * dheight + ty * dwidth + tx) = tcormap; +} + +// Kernel 函数:_localCheckErrKer(进行局部异常检查) +static __global__ void _localCheckErrKer(float *besttplnor, size_t besttplpitch, + int tplwidth, int tplheight, + float *testnormalization, + size_t testpitch, + int testwidth, int testheight, + RotateTable rotatetable, + int *errmap, size_t errmappitch, + int errmapwidth, int errmapheight, + float errthreshold, int mx, int my, + float angle, int tploffx, int tploffy) +{ + // Kernel 采用二维来处理,第一维为 TEMPLATE 的横坐标,第二维为 TEMPLATE 的 + // 纵坐标 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断坐标是否越界,若越界,直接返回 + if (x < 0 || x >= tplwidth || y < 0 || y >= tplheight) + return; + + int errcode; // 局部变量,错误码 + +int testx, testy; // 局部变量,记录在 TEST 图像上的坐标 + + // 计算坐标(x, y)旋转 angle 后在 TEST 图像上的坐标 + errcode = _rotateXYDev(x - tploffx, y - tploffy, mx, my, rotatetable, + angle, &testx, &testy); + // 若计算错误,则直接返回 + if (errcode != NO_ERROR) + return; + + // 判断 TEMPLATE 旋转后,在 TEST 图像上是否越界,若越界,直接返回 + if (testx < 0 || testx >= testwidth || testy < 0 || testy >= testheight) + return; + + // 计算坐标(testx, testy)在 TEST 图像上的正规化值的指针 + float testnor = + *((float *)((char *)testnormalization + testy * testpitch) + testx); + // 获取坐标(x, y)邻域内在 TEMPLATE 上的与 testnor 最接近的值 + float tplnor = _getSuitValueFromNormalTplDev(x, y, besttplnor, besttplpitch, + tplwidth, tplheight, testnor); + // 计算(x, y)在 TEMPLATE 上与对应在 TEST 图像上的点的差异 + float v = (testnor - tplnor) * (testnor - tplnor); + // 若差距大于阈值,则扩大 200 倍,然后赋值给 errmap,否则置 0 + *((int *)((char *)errmap + testy * errmappitch) + testx) = + (v > errthreshold) ? (int)(200.0f * v) : 0; +} + +// 宏:FAIL_MEM_FREE +// 该宏用于清理临时申请的内存空间 +#define FAIL_MEM_FREE do { \ + if (testnormalization != NULL) { \ + cudaFree(testnormalization); \ + testnormalization = NULL; \ + } \ + if (bigmem != NULL) { \ + cudaFree(bigmem); \ + bigmem = NULL; \ + } \ + if (errmap != NULL) { \ + cudaFree(errmap); \ + } \ + if (cormapcpu != NULL) { \ + delete [] cormapcpu; \ + cormapcpu = NULL; \ + } \ + } while (0) \ + +// 成员方法:imageMatch(用给定图像及不同旋转角对待匹配的图像进行匹配) +__host__ int ImageMatch::imageMatch(Image *matchimage, MatchRes *matchres, + DirectedRect *dirrect) +{ + int errcode; // 局部变量,错误码 + cudaError_t cudaerr; // 局部变量,CUDA 调用返回错误码 + dim3 gridsize; + dim3 blocksize; + + // 检查旋转表是否为空 + if (rotateTable == NULL) + return NULL_POINTER; + + // 检查 TEMPLATE 图像数组,待匹配图像以及存放匹配结果的 matchres 是否为空 + if (matchimage == NULL || matchres == NULL) + return NULL_POINTER; + + // 初始化旋转表,计算给定的坐标点集对应的旋转表 + if (rotateTable->getCurrentState() == NULL_RTT) { + errcode = rotateTable->initRotateTable(); + if (errcode != NO_ERROR) { + return errcode; + } + } + + // 将 TEST 图像数据拷贝到 Device 内存中 + errcode = ImageBasicOp::copyToCurrentDevice(matchimage); + if (errcode != NO_ERROR) + return errcode; + + // 对设置的 TEMPLATE 图像进行正规化 + errcode = normalizeForTpl(); + if (errcode != NO_ERROR) + return errcode; + + // 提取待匹配图像的 ROI 子图 + ImageCuda matchimageCud; + errcode = ImageBasicOp::roiSubImage(matchimage, &matchimageCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算 TEST 图像的宽和高 + int testwidth = matchimageCud.imgMeta.width; + int testheight = matchimageCud.imgMeta.height; + + // 局部变量,存储 TEST 图像正规化的结果 + float *testnormalization = NULL; + // 局部变量,testnormalization 的 ptich 值 + size_t testpitch; + // 申请一块足够大的空间,后面需要使用的空间可以直接在这里获取 + char *bigmem = NULL; + // 在 Host 上申请一段内存空间,用来将 cormapsumgpu 中的数据拷贝到 Host 上 + float *cormapcpu = NULL; + // 申请临时变量,用来存储临时的 errmap 空间和 pitch + int *errmap = NULL; + + // 为 testnormalization 申请 Device 空间 + cudaerr = cudaMallocPitch((void **)&testnormalization, &testpitch, + testwidth * sizeof (float), testheight); + if (cudaerr != cudaSuccess) + return CUDA_ERROR; + + // 创建一个正规化操作的对象 + Normalization normal(3); + // 对 TEST 图像进行正规化 + errcode = normal.normalize(&(matchimageCud.imgMeta), testnormalization, + testpitch, testwidth, testheight, false); + if (errcode != NO_ERROR) { + FAIL_MEM_FREE; + return errcode; + } + + // 计算旋转角的数量 + int anglecount = rotateTable->getAngleCount(); + + int offsetx, offsety; // 记录摄动中心的偏移良 + // 距离摄动中心的偏移量 + offsetx = dx - dWidth / 2; + offsety = dy - dHeight / 2; + + // bigmem 的游标,用来指定剩余内存的起始地址 + char *cursor; + cudaerr = cudaMalloc((void **)&bigmem, + tplCount * sizeof (float *) + + tplCount * sizeof (size_t) + + 2 * dWidth * dHeight * tplCount * anglecount * + sizeof (float)); + // 判断是否申请成功,若失败,释放之前的空间,防止内存泄漏,然后返回错误 + if (cudaerr != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + // 游标初始指向 bigmem + cursor = bigmem; + + // 用来存储一组 TEMPLATE 正规化的结果的指针,指向 Device + float **tplnormalizationCud; + // 存储一组 TEMPLATE 正规化结果的 ptich 值,指向 Device + size_t *tplpitchCud; + + // 从 bigmem 中获取内存空间 + tplnormalizationCud = (float **)cursor; + // 更新游标的值 + cursor += tplCount * sizeof (float *); + + // 将每个 TEMPLATE 的正规化的指针拷贝到 Device 内存中 + cudaerr = cudaMemcpy(tplnormalizationCud, tplNormalization, + sizeof (float *) * tplCount, cudaMemcpyHostToDevice); + // 若拷贝失败,则释放先前申请的空间,防止内存泄漏,然后返回错误 + if (cudaerr != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + + // 从 bigmem 中获取一块内存空间 + tplpitchCud = (size_t *)cursor; + // 更新游标的值 + cursor += tplCount * sizeof (size_t); + + // 将 ptich 拷贝到 Device 内存空间 + cudaerr = cudaMemcpy(tplpitchCud, pitch, sizeof (size_t) * tplCount, + cudaMemcpyHostToDevice); + // 若拷贝失败,则释放先前申请的空间,防止内存泄漏,然后返回错误 + if (cudaerr != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + + // 用来存储摄动范围每个点与每个 TEMPLATE 的不同旋转角匹配得到的相关系数 + // 这里先存储的是摄动范围的每个点对每个 TEMPLATE 的匹配结果,然后是同一个旋 + // 转角的 TEMPLATE,最后是不同的旋转角 + float *cormapgpu = NULL; + // 在 bigmem 中获取内存空间 + cormapgpu = (float *)cursor; + // 更新游标的值 + cursor += dWidth * dHeight * tplCount * anglecount * sizeof (float); + + // 计算 TEMPLATE 的偏移量 + int tploffx = tplWidth / 2; + int tploffy = tplHeight / 2; + + // 计算线程块的尺寸 + // block 使用默认的线程尺寸 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + // 采用基本的分块方案 + gridsize.x = (dWidth + blocksize.x - 1) / blocksize.x; + gridsize.y = (dHeight + blocksize.y - 1) / blocksize.y; + // 第三维表示所有的 TEMPLATE,其中包括所有的旋转角度 + gridsize.z = anglecount * tplCount; + + // 调用匹配函数对每个 TEMPLATE 在摄动范围内进行匹配 + _matchKer<<>>(tplnormalizationCud, tplpitchCud, + tplCount, tplWidth, tplHeight, + testnormalization, testpitch, + testwidth, testheight, + *rotateTable, cormapgpu, + offsetx, offsety, dWidth, dHeight, + tploffx, tploffy); + + // 若 Kernel 函数执行失败,则释放先前申请的内存空间,防止内存泄漏,然后返回 + if (cudaGetLastError() != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + + // 用来存储每个点在邻域内的相关系数的和 + float *cormapsumgpu = NULL; + // 从 bigmem 中获取内存空间 + cormapsumgpu = (float *)cursor; + + // 调用 Kernel 函数对每个点求得的相关系数在 scope 邻域内求和,结果存放在 + // cormapsumgpu 指向的内存中 + _calCorMapSumKer<<>>(cormapgpu, dWidth, dHeight, + scope, cormapsumgpu); + + // 若 Kernel 函数执行失败,则释放先前申请的内存空间,防止内存泄漏,然后返回 + if (cudaGetLastError() != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + + // 在 Host 上申请一段内存空间,用来将 cormapsumgpu 中的数据拷贝到 Host 上 + cormapcpu = new float[dWidth * dHeight * tplCount * anglecount]; + // 如果内存申请失败,则释放之前申请的空间,然后返回错误 + if (cormapcpu == NULL) { + FAIL_MEM_FREE; + return OUT_OF_MEM; + } + + // 将 cormapsumgpu 中的数据拷贝到 cormapcpu 中 + cudaerr = cudaMemcpy(cormapcpu, cormapsumgpu, + dWidth * dHeight * tplCount * anglecount * + sizeof (float), cudaMemcpyDeviceToHost); + // 若拷贝失败,则释放先前申请的空间,防止内存泄漏,然后返回错误 + if (cudaerr != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + + // 获取 cormapcpu 中最大值的索引 + int maxindex = _getCormapMaxIndex(cormapcpu, + dWidth * dHeight * tplCount * anglecount); + + // 计算最佳匹配的旋转 + int angleindex = maxindex / (dWidth * dHeight) / tplCount; + matchres->angle = rotateTable->getAngleVal(angleindex); + // 计算最佳匹配的 TEMPLATE 的索引 + matchres->tplIndex = (maxindex / (dWidth * dHeight)) % tplCount; + // 计算最佳匹配的 TEST 上的横坐标 + matchres->matchX = maxindex % dWidth + offsetx; + // 计算最佳匹配的 TEST 上的纵坐标 + matchres->matchY = (maxindex % (dWidth * dHeight)) / dWidth + offsety; + // 计算最佳匹配时的相关系数 + matchres->coefficient = cormapcpu[maxindex] / + (scope * scope); + + // 如果 errMap 不为空,则进行局部异常检查 + if (dirrect != NULL) { + + size_t errmappitch; + // errmap 的宽和高 + int errMapWidth = testwidth, errMapHeight = testheight; + + // 在 Device 端创建 errMap 空间 + cudaerr = cudaMallocPitch((void **)&errmap, &errmappitch, + sizeof (int) * errMapWidth, errMapHeight); + // 若创建失败,则释放空间,然后返回 + if (cudaerr != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + // 将 errmap 内的数据置 0 + cudaMemset2D(errmap, errmappitch, 0, sizeof (int) * errMapWidth, + errMapHeight); + + // 先计算线程块的大小 + gridsize.x = (tplWidth + blocksize.x - 1) / blocksize.x; + gridsize.y = (tplHeight + blocksize.y - 1) / blocksize.y; + // 进行局部异常检查 + _localCheckErrKer<<>>( + tplNormalization[matchres->tplIndex], + pitch[matchres->tplIndex], tplWidth, tplHeight, + testnormalization, testpitch, testwidth, testheight, + *rotateTable, errmap, errmappitch, errMapWidth, + errMapHeight, errThreshold, matchres->matchX, + matchres->matchY, matchres->angle, tploffx, tploffy); + // 判断 Kernel 是否发生错误,若有错误,则释放空间,然后返回错误 + if (cudaGetLastError() != cudaSuccess) { + FAIL_MEM_FREE; + return CUDA_ERROR; + } + // 获取 errmap 的最小有向四边形 + errcode = _getDirectRectForErrMap(errmap, errmappitch, + errMapWidth, errMapHeight, + errWinWidth, errWinHeight, + errWinThreshold, dirrect); + if (errcode != NO_ERROR) { + FAIL_MEM_FREE; + return errcode; + } + } + // 释放之前申请的空间,防止内存泄露 + FAIL_MEM_FREE; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} +#undef FAIL_MEM_FREE diff --git a/okano_3_0/ImageMatch.h b/okano_3_0/ImageMatch.h new file mode 100644 index 0000000..15060d8 --- /dev/null +++ b/okano_3_0/ImageMatch.h @@ -0,0 +1,521 @@ +// ImageMatch.h +// 创建人:罗劼 +// +// 由坐标映射的图像匹配(Image match by coordinate mapping) +// 功能说明:用坐标映射的图像的相关计算用来对图像进行匹配。提供一组TEMPLATE图像 +// 以及一张需要匹配的TEST图像,分别用不同旋转角的TEMPLATE图像对TEST图 +// 像进行匹配,得到最大匹配度相对应的旋转角以及在TEST图像上匹配的相应 +// 位置。 +// +// 修订历史: +// 2012年11月21日(罗劼) +// 初始版本 +// 2012年11月22日(罗劼,于玉龙) +// 对代码的一些格式不规则的地方进行了修改 +// 2012年11月23日(罗劼,于玉龙) +// 修改了对 Device 函数的命名以及在分配 Device 内存时,采用先申请一块足够大 +// 的内存,然后从这快大内存中获取需要的内存 +// 2012年11月29日(罗劼) +// 修改了计算最佳匹配的旋转角的错误 +// 2012年12月04日(罗劼) +// 在 MatchRes 结构体中增加了一个变量,用来记录匹配得到的最大相关系数 +// 2012年12月05日(罗劼) +// 增加了在初始化旋转表之前先判断旋转表是否为空的判断 +// 2012年12月28日(罗劼) +// 增加了局部异常检查 +// 2013年01月14日(罗劼) +// 修改了设置局部异常检查的错误以及实现了部分获取 errmap 的最小有向四边形 +// 2013年04月17日(罗劼) +// 完成了局部异常检查模块,修改了调用图像匹配的接口 +// 2013年04月17日(罗劼) +// 将释放内存部分改写成宏 +// 2013年05月07日(罗劼) +// 修改了一处计算相关系数的错误 + +#ifndef __IMAGEMATCH_H__ +#define __IMAGEMATCH_H__ + +#include "Image.h" +#include "Rectangle.h" +#include "RotateTable.h" + +// 结构体:MatchRes(匹配后得到的结果) +// 该结构体定义了匹配后得到的结果,包括最佳匹配位置,最佳匹配对应的旋转角以及最 +// 佳匹配对应的 TEMPLATE 的索引 +typedef struct MatchRes_st { + int matchX; // 匹配后得到的最佳匹配位置的横坐标 + int matchY; // 匹配后得到的最佳匹配位置的纵坐标 + float angle; // 最佳匹配对应的旋转角 + int tplIndex; // 最佳匹配对应的 TEMPLATE 的索引 + float coefficient; // 匹配得到的最大的相关系数 +} MatchRes; + +// 类:ImageMatch +// 继承自:无 +// 用坐标映射的图像的相关计算用来对图像进行匹配。提供一组 TEMPLATE 图像以及一张 +// 需要匹配的 TEST 图像,分别用不同旋转角的 TEMPLATE 图像对 TEST 图像进行匹配, +// 得到最大匹配度相对应的旋转角以及在 TEST 图像上匹配的相应位置。 +class ImageMatch { + +protected: + + // 成员变量:rotateTable(旋转表) + // 记录了 TEMPLATE 的各个旋转角度对应的坐标,该指针指向 Device 内存空间 + RotateTable *rotateTable; + + // 成员变量:dWidth 和 dHeight(摄动范围的宽和高) + // 记录摄动范围的宽和高 + int dWidth, dHeight; + + // 成员变量:dx 和 dy(摄动中心) + // 记录摄动范围的中心 + int dx, dy; + + // 成员变量:scope(标记在每个点为中心对相关系数求和的邻域的大小) + // 标记在每个点为中心对相关系数求和的邻域的大小 + int scope; + + // 成员变量:errThreshold(局部异常检查的阈值) + // 在进行局部异常检查时,需要用到此阈值 + float errThreshold; + + // 成员变量:errWinWidth(errmap 窗口的宽) + // 进行局部异常检查时 errmap 窗口的宽 + int errWinWidth; + + // 成员变量:errWinHeight(errmap 窗口的高) + // 进行局部异常检查时 errmap 窗口的高 + int errWinHeight; + + // 成员变量:errWinThreshold(errmap 窗口的阈值) + // 进行局部异常检查是 errmap 窗口高值点的阈值 + int errWinThreshold; + + // 成员变量:needNormalization(标记是否需要对 TEMPLATE 进行正规化) + // 标记是否需要对 TEMPLATE 进行正规化 + bool needNormalization; + + // 成员变量:tplImages(一组 TEMPLATE) + // 存储指向 TEMPLATE 的指针 + Image **tplImages; + + // 成员变量:tplCount(TEMPLATE 数量) + // 记录 TEMPLATE 的数量 + int tplCount; + + // 成员变量:tplTmpCount(TEMPLATE 的临时数量) + // 临时记录 TEMPLATE 的数量,只有在调用 initNormalizeData 函数时,才把 + // tplTmpCount 赋值给 tplCount + int tplTmpCount; + + // 成员变量:tplNormalization(TEMPLATE 正规化后得到的结果) + // 用来存储每个 TEMPLATE 正规化后得到的结果,每个 TEMPLATE 正规化的结果都 + // 存放在 Device 内存中 + float **tplNormalization; + + // 成员变量:pitch(TEMPLATE 正规化结果的 pitch 值) + // 用来记录每个 TEMPLATE 的 pitch 值 + size_t *pitch; + + // 成员变量:tplWidth 和 tplHeight(TEMPLATE 的宽和高) + // 记录每个 TEMPLATE 的宽和高,这里要求每个 TEMPLATE 的宽和高必须相等 + int tplWidth, tplHeight; + + // 成员方法:setDefParameter(设置默认的参数) + // 为所有的成员变量设置默认的参数 + __host__ __device__ void // 无返回值 + setDefParameter() + { + this->rotateTable = NULL; // 旋转表默认为空 + this->errWinWidth = 3; // window 的宽默认为 3 + this->errWinHeight = 3; // window 的高默认为 3 + this->errWinThreshold = 1; // window 的阈值默认为 1 + this->errThreshold = 0; // 局部异常检查的阈值默认为 0 + this->scope = 3; // 设置默认的 scope 值为 3 + this->dWidth = 32; // 设置默认的摄动范围的宽为 32 + this->dHeight = 32; // 设置默认的摄动范围的高为 32 + this->dx = 32; // 设置默认的摄动中心的横坐标为 32 + this->dy = 32; // 设置默认的摄动中心的纵坐标为 32 + this->tplImages = NULL; // 设置默认的 TEMPLATE 为 NULL + this->tplNormalization = NULL; // 设置默认的 TEMPLATE 正规化结果为 NULL + this->pitch = NULL; // 设置默认的 ptich 为 NULL + this->tplCount = 0; // 设置默认的 TEMPLATE 数量为 0 + } + + // 成员方法:normalizeForTpl(对设定的 TEMPLATE 进行正规化) + // 对所有的设置的 TEMPLATE 进行正规化 + __host__ int // 函数是否正确,若函数执行正确,返回 NO_ERROR + normalizeForTpl(); + + // 成员方法:initNormalizeData(对设置的 TEMPLATE 进行初始化) + // 对 TEMPLATE 进行初始化,为 tplNormalization 分配空间 + __host__ int // 函数是否正确,若函数执行正确,返回 NO_ERROR + initNormalizeData(); + + // 成员方法:deleteNormalizeData(删除 TEMPLATE 正规化的数据) + // 删除 TEMPLATE 正规化的数据,即 tplNormalization 分配的空间 + __host__ int // 函数是否正确,若函数执行正确,返回 NO_ERROR + deleteNormalizeData(); + + // 成员方法:copyNormalizeDataToDevice(拷贝正规化结构的指针到 Device) + // 拷贝 tplNormalization 指向的每个 TEMPLATE 正规化的结果的指针到 Device + __host__ int // 函数是否正确,若函数执行正确, + copyNormalizeDataToDevice(); // 返回 NO_ERROR + +public: + + // 构造函数:ImageMatch + // 无参数版本的构造函数,所有的成员变量都初始化为默认值 + __host__ __device__ + ImageMatch() { + + // 为成员变量设置默认的参数 + setDefParameter(); + } + + // 构造函数:ImageMatch + // 有参数版本的构造函数,根据需要给定各个参数,这些参数还可以通过其他成员函 + // 数改变 + __host__ __device__ + ImageMatch( + RotateTable *rotatetable, // 旋转表 + int dwidth, int dheight, // 摄动范围的宽和高 + int dx, int dy, // 摄动中心的坐标 + int scope, // 对相关系数求和的邻域的大小 + int errwinwidth, int errwinheight, // window 的窗口大小 + int errwinthreshold, // window 高值点的阈值 + float errthreshold // 异常检查的阈值 + ) { + + // 为成员变量设置默认参数,防止用户在构造函数的参数中传递非法的初 + // 始值而使系统进入一个未知的状态 + setDefParameter(); + + // 根据参数列表中的值设定成员变量的初值 + setRotateTable(rotatetable); + setDWidth(dwidth); + setDHeight(dheight); + setDX(dx); + setDY(dy); + setScope(scope); + setErrWinWidth(errwinwidth); + setErrWinHeight(errwinheight); + setErrWinThreshold(errwinthreshold); + setErrThreshold(errthreshold); + } + + // 成员方法:getRotateTable(获取旋转表的指针) + // 获取 rotatetable 的值 + __host__ __device__ RotateTable * // 返回值:成员变量 rotateTable 的值 + getRotateTable() const + { + // 返回 rotateTable 的值 + return this->rotateTable; + } + + // 成员方法:setRotateTable(设置旋转表) + // 设置 rotatetable 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR + setRotateTable( + RotateTable *rotatetable // 旋转表的指针 + ) { + // 更新旋转表的值 + this->rotateTable = rotatetable; + + // 处理完毕,返回 + return NO_ERROR; + } + + // 成员方法:getScope(获取对相关系数求和时的邻域的大小) + // 获取 scope 的值 + __host__ __device__ int // 返回值:scope 的值 + getScope() const + { + // 返回 scope 的值 + return this->scope; + } + + // 成员方法:setScope(设置对相关系数求和时的邻域的大小) + // 设置 scope 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行,返回 NO_ERROR + setScope( + int scope // 新的 scope 的值 + ) { + // 判断 scope 的值是否合法,若不合法,直接返回 + if (scope <= 0) + return INVALID_DATA; + + // 更新 scope 的值 + this->scope = scope; + + // 执行完毕,返回 + return NO_ERROR; + } + + // 成员方法:getDWidth(获取摄动范围的宽) + // 获取 dWidth 的值 + __host__ __device__ int // 返回 dWidth 的值 + getDWidth() const + { + // 返回 dWidth 的值 + return this->dWidth; + } + + // 成员方法:setDWidth(设置摄动范围的宽) + // 设置 dWidth 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行,返回 NO_ERROR + setDWidth( + int dwidth // 新的 dWidth 的值 + ) { + // 判断 dwidth 的值是否合法,如果不合法,直接返回错误 + if (dwidth <= 0) + return INVALID_DATA; + + // 更新 dWidth 的值 + this->dWidth = dwidth; + + // 执行完毕,返回 + return NO_ERROR; + } + + // 成员方法:getDHeight(获取摄动范围的高) + // 获取 dHeight 的值 + __host__ __device__ int // 返回 dHeight 的值 + getDHeight() const + { + // 返回 dHeight 的值 + return this->dHeight; + } + + // 成员方法:setDHeight(设置摄动范围的高) + // 设置 dHeight 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行,返回 NO_ERROR + setDHeight( + int dheight // 新的摄动范围的高 + ) { + // 判断 dheight 的值是否合法,如果不合法,直接返回 + if (dheight <= 0) + return INVALID_DATA; + + // 更新 dHeight 的值 + this->dHeight = dheight; + + // 执行完毕,返回 + return NO_ERROR; + } + + // 成员方法:getDX(获取摄动中心的横坐标) + // 获取 dx 的值 + __host__ __device__ int // 返回 dx 的值 + getDX() const + { + // 返回 dx 的值 + return this->dx; + } + + // 成员方法:setDX(设置摄动中心的纵坐标) + // 设置 dx 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行,返回 NO_ERROR + setDX( + int dx // 新的 dx 的值 + ) { + // 判断 dx 是否合法,若不合法,直接返回 + if (dx < 0) + return INVALID_DATA; + + // 更新 dx 的值 + this->dx = dx; + + // 执行完毕,返回 + return NO_ERROR; + } + + // 成员方法:getDY(获取摄动中心的纵坐标) + // 获取 dy 的值 + __host__ __device__ int // 返回 dy 的值 + getDY() const + { + // 返回 dy 的值 + return dy; + } + + // 成员方法:setDY(设置摄动中心的纵坐标) + // 设置 dy 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行,返回 NO_ERROR + setDY( + int dy // 新的 dy 的值 + ) { + // 判断 dy 是否合法,若不合法,直接返回错误 + if (dy < 0) + return INVALID_DATA; + + // 更新 dy 的值 + this->dy = dy; + + // 执行完毕,返回 + return NO_ERROR; + } + + // 成员方法:setErrWinWidth(设置 errmap 窗口的宽) + // 设置 errWinWidth 的值 + __host__ __device__ int // 函数是否正确执行,若函数正确执行, + // 则返回 NO_ERROR + setErrWinWidth( + int errwinwidth // 新的 errwinwidth 的值 + ) { + // 判断 errwinwidth 是否合法,若不合法,直接返回错误 + if (errwinwidth <= 0) + return INVALID_DATA; + + // 更新 errWinWidth 的值 + this->errWinWidth = errwinwidth; + + // 执行完毕,返回 + return NO_ERROR; + } + + // 成员方法:getErrWinWidth(获取 errmap 窗口的宽) + // 获取成员变量 errWinWidth 的值 + __host__ __device__ int // 返回 errmap 窗口的宽 + getErrWinWidth() const + { + // 返回 errWinWidth 的值 + return errWinWidth; + } + + // 成员方法:setErrWinHeight(设置 errmap 窗口的高) + // 设置 errWinHeight 的值 + __host__ __device__ int // 函数是否正确执行,若正确执行,返回 NO_ERROR + setErrWinHeight( + int errwinheight // 新的 errWinHeight 的值 + ) { + // 判断 errwinheight 是否合法,若不合法,直接返回错误 + if (errwinheight <= 0) + return INVALID_DATA; + + // 更新 errWinHeight 的值 + this->errWinHeight = errwinheight; + + // 处理完毕,返回 + return NO_ERROR; + } + + // 成员方法:getErrWinHeight(获取 errmap 窗口的高) + // 获取成员变量 errWinHeight 的值 + __host__ __device__ int // 返回 errmap 窗口的高 + getErrWinHeight() const + { + // 返回 errWinHeight 的值 + return errWinHeight; + } + + // 成员方法:setErrWinThreshold(设置 errmap 窗口的阈值) + // 设置 errWinThreshold 的值 + __host__ __device__ int // 函数是否正确执行,若正确执行,返回 NO_ERROR + setErrWinThreshold( + int errwinthreshold // 新的 errWinThreshold 的值 + ) { + // 判断 errwinthreshold 是否合法,若不合法,直接返回错误 + if (errwinthreshold < 0) + return INVALID_DATA; + + // 更新 errWinThreshold 的值 + this->errWinThreshold = errwinthreshold; + + // 处理完毕,返回 + return NO_ERROR; + } + + // 成员方法:getErrWinThreshold(获取 errmap 窗口的阈值) + // 获取成员变量 errWinThreshold 的值 + __host__ __device__ int // 返回 errmap 窗口的阈值 + getErrWinThreshold() const + { + // 返回 errWinThreshold 的值 + return errWinThreshold; + } + + // 成员方法:getErrThreshold(获取局部异常检查的阈值) + // 获取 errThreshold 的值 + __host__ __device__ int // 返回值:局部异常检查的阈值 + getErrThreshold() const + { + // 获取成员变量 errThreshold 的值 + return errThreshold; + } + + // 成员方法:setErrThreshold(设置局部异常检查的阈值) + // 设置局部异常检查时使用的阈值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + setErrThreshold( + float errthreshold // 新的局部异常检查的阈值 + ) { + + // 如果参数合法,则进行赋值,否则,直接返回 + if (errthreshold > 0) { + this->errThreshold = errthreshold; + } + + // 处理完毕,返回 + return NO_ERROR; + } + + // 成员方法:setTemplateImage(设置 TEMPLATE 的数据) + // 设置所有的 TEMPLATE 的数据 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + setTemplateImage( + Image **tplimages, // 要设置的一组 TEMPLATE 的图像 + int count // TEMPLATE 的数量 + ) { + // 判断 tplimages 是否为空,若为空,直接返回错误 + if (tplimages == NULL) + return NULL_POINTER; + + // 判断 count 是否合法,若不合法,直接返回错误 + if (count <= 0) + return INVALID_DATA; + + // 扫描 tplimages 的每一个成员,检查每个成员是否为空 + for (int i = 0; i < count; i++) { + // 若某个成员为空,则直接返回错误 + if (tplimages[i] == NULL) + return NULL_POINTER; + } + + // 更新 TEMPLATE 图像数据 + this->tplImages = tplimages; + // 更新 tplTmpCount 数据,这里不能将 count 赋值给 tplCount 变量,因 + // 为需要使用 tplCount 变量来删除原来的 tplNormalization 数据 + this->tplTmpCount = count; + // 更新模版的宽 + this->tplWidth = tplimages[0]->width; + // 更新模版的高 + this->tplHeight = tplimages[0]->height; + + // 将 needNormalization 复制为 ture,标记需要对新的 TEMPLATE 进行初始化 + this->needNormalization = true; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; + } + + // 成员函数:imageMatch(用给定图像及不同旋转角度对待匹配的图像进行匹配) + // 函数用给定的一组 TEMPLATE 在一个旋转角范围内对一张给定的图像进行匹配,得 + // 到最佳匹配位置以及对应的旋转角和 TEMPLATE 的索引。其中 tplimages 里的每 + // 一张图片的 ROI 子图的长和宽必须相等 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR + imageMatch( + Image *matchimage, // 待匹配的图像 + MatchRes *matchres, // 匹配后得到的结果,包括最佳匹配位置,最佳 + // 匹配对应的旋转角以及对应的 TEMPLATE 的索引 + DirectedRect *dirrect // 局部异常检查得到的最小有向四边形,若传入 + // 时为空,则不进行局部异常检查 + ); +}; + +#endif + diff --git a/okano_3_0/ImgConvert.cu b/okano_3_0/ImgConvert.cu new file mode 100644 index 0000000..be1c2b7 --- /dev/null +++ b/okano_3_0/ImgConvert.cu @@ -0,0 +1,436 @@ +// ImgConvert.cu +// 实现图像与坐标记得互相转化。 + +#include "ImgConvert.h" +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLOCK_1D +// 定义一维线程块尺寸。 +#define DEF_BLOCK_1D 512 + +// Kernel 函数:_cstConvertImgKer(实现将坐标集转化为图像算法) +// 将坐标集内的坐标映射到输入图像中并将目标点置为 highpixel 从而实现 +// 坐标集与图像的转化。 +static __global__ void // Kernel 函数无返回值。 +_cstConvertImgKer( + CoordiSet incst, // 输入坐标集 + ImageCuda inimg, // 输入图像 + unsigned char highpixel // 高像素 +); + +// Kernel 函数:_markImageFlagKer(将图像转化为标志数组) +// 将图像转化为标志数组,其中若像素需要记录到坐标集时,对应的标志位为 1,否则为 +// 0。 +static __global__ void // Kernel 函数无返回值。 +_markImageFlagKer( + ImageCuda inimg, // 输入图像 + int imgflag[], // 输出的图像标志位数组。 + ImgConvert imgcvt // 转换算法 CLASS,主要使用其中的转换标志位。 +); + +// Kernel 函数:_arrangeCstKer(重组坐标点集) +// 在计算处图像标志位数组和对应的累加数组之后,将图像的信息转换为坐标点集的信息 +// 写入输出坐标点集中。 +static __global__ void // Kernel 函数无返回值。 +_arrangeCstKer( + ImageCuda inimg, // 输入图像 + int imgflag[], // 图像标志位数组 + int imgacc[], // 图像标志位数组的累加数组 + int ptscnt, // 有效坐标点的数量 + CoordiSetCuda outcst // 输出坐标集 +); + +// Kernel 函数:_curConvertImgKer(实现将曲线转化为图像算法) +// 将曲线内的坐标映射到输入图像中并将目标点置为 highpixel 从而实现 +// 坐标集与图像的转化。 +static __global__ void // Kernel 函数无返回值。 +_curConvertImgKer( + Curve incur, // 输入曲线 + ImageCuda inimg, // 输入图像 + unsigned char highpixel // 高像素 +); + +// Kernel 函数:_cstConvertImgKer(实现将坐标集转化为图像算法) +static __global__ void _cstConvertImgKer(CoordiSet incst, ImageCuda inimg, + unsigned char highpixel) +{ + // index 表示线程处理的像素点的坐标。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查坐标点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (index >= incst.count) + return; + + // 获得目标点在图像中的对应位置。 + int curpos = incst.tplData[2 * index + 1] * inimg.pitchBytes + + incst.tplData[2 * index]; + + // 将坐标集中坐标在图像中对应的像素点的像素值置为 higpixel。 + inimg.imgMeta.imgData[curpos] = highpixel; +} + +// 成员方法:cstConvertToImg(坐标集转化为图像算法) +__host__ int ImgConvert::cstConvertToImg(CoordiSet *incst, Image *outimg) +{ + // 局部变量,错误码。 + int errcode; + + // 检查输入坐标集,输出图像是否为空。 + if (incst == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输出图像拷贝到 device 端。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + return errcode; + } + + // 将输入坐标集拷贝到 device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(incst); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 初始化输入图像内所有的像素点的的像素值为 lowpixel,为转化做准备。 + cudaError_t cuerrcode; + cuerrcode = cudaMemset(outsubimgCud.imgMeta.imgData, this->lowPixel, + sizeof(unsigned char) * outsubimgCud.imgMeta.width * + outsubimgCud.imgMeta.height); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。调用一维核函数, + // 在这里设置线程块内的线程数为 256,用 DEF_BLOCK_1D 表示。 + size_t blocksize1, gridsize1; + blocksize1 = DEF_BLOCK_1D; + gridsize1 = (incst->count + blocksize1 - 1) / blocksize1; + + // 将输入坐标集转化为输入图像图像,即将坐标集内点映射在图像上点的 + // 像素值置为 highpixel。 + _cstConvertImgKer<<>>(*incst, outsubimgCud, + this->highPixel); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Kernel 函数:_markImageFlagKer(将图像转化为标志数组) +static __global__ void _markImageFlagKer(ImageCuda inimg, int imgflag[], + ImgConvert imgcvt) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * inimg.imgMeta.width + c; + // 根据输入像素下标读取输入像素值。 + unsigned char curpixel = inimg.imgMeta.imgData[inidx]; + + // 如果当前像素点为有效像素点,则图像标志位置位,否则置零。 + if (imgcvt.getConvertFlag(curpixel)) { + imgflag[outidx] = 1; + } else { + imgflag[outidx] = 0; + } + + // 处理余下的三个点。 + for (int i = 1; i < 4; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= inimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += inimg.imgMeta.width; + curpixel = inimg.imgMeta.imgData[inidx]; + + // 如果当前像素点为有效像素点,则图像标志位置位,否则置零。 + if (imgcvt.getConvertFlag(curpixel)) { + imgflag[outidx] = 1; + } else { + imgflag[outidx] = 0; + } + } +} + +// Kernel 函数:_arrangeCstKer(重组坐标点集) +static __global__ void _arrangeCstKer( + ImageCuda inimg, int imgflag[], int imgacc[], + int ptscnt, CoordiSetCuda outcst) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算当前 Thread 所对应的原图像数据下标和图像标志位数组的下标。 + int inidx = r * inimg.pitchBytes + c; + int flagidx = r * inimg.imgMeta.width + c; + + // 如果当前计算的像素点是一个无效像素点,则直接退出。 + if (imgflag[flagidx] == 0) + return; + + // 获取当前像素点所对应的坐标集中的下标。 + int outidx = imgacc[flagidx]; + + // 计算坐标点集中的附属数据,这里直接使用其像素值所对应的浮点亮度值来作为附 + // 属数据。 + float curval = inimg.imgMeta.imgData[inidx] / 255.0f; + + // 完成输出操作。这里使用 while 的原因是,如果坐标点集可容纳的坐标点数量超 + // 过了实际的坐标点数量,这样需要重复的防止现有的坐标点(这样做的考虑时为了 + // 防止防止了不是有效像素点的坐标点,导致凸壳等算法出错。 + while (outidx < outcst.tplMeta.count) { + // 将有效像素点存放到坐标点集中,顺带输出了附属数据。 + outcst.tplMeta.tplData[2 * outidx] = c; + outcst.tplMeta.tplData[2 * outidx + 1] = r; + outcst.attachedData[outidx] = curval; + + // 更新输出下标,如果输出坐标点集容量过大,则循环输出有效坐标点。 + outidx += ptscnt; + } +} + +// 宏:FAIL_IMGCONVERTTOCST_FREE +// 当下面函数运行出错时,使用该宏清除内存,防止内存泄漏。 +#define FAIL_IMGCONVERTTOCST_FREE do { \ + if (tmpdata != NULL) \ + cudaFree(tmpdata); \ + } while (0) + +// 成员方法:imgConvertToCst(图像转化成坐标集算法) +__host__ int ImgConvert::imgConvertToCst(Image *inimg, CoordiSet *outcst) +{ + // 检查输入图像,输出坐标集是否为空。 + if (inimg == NULL || outcst == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + cudaError_t cuerrcode; + + // 定义加法运算类型 + add_class add; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 计算数据尺寸。 + int flagsize = inimg->width * inimg->height; + int datasize = (2 * flagsize + 1) * sizeof (int); + // 申请存放图像标志位数组何其对应的累加数组的内存空间。 + int *tmpdata = NULL; + cuerrcode = cudaMalloc((void **)&tmpdata, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_IMGCONVERTTOCST_FREE; + return CUDA_ERROR; + } + + // 将申请的临时内存空间分配给各个指针。 + int *imgflagDev = tmpdata; + int *imgaccDev = imgflagDev + flagsize; + + // 提取输入图像对应的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用第一个 Kernel 所需要的线程块尺寸。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用第一个 Kernel 生成图像标志位数组。 + _markImageFlagKer<<>>(insubimgCud, imgflagDev, *this); + if (cudaGetLastError() != cudaSuccess) { + FAIL_IMGCONVERTTOCST_FREE; + return CUDA_ERROR; + } + + // 通过扫描算法计算图像标志位数组对应的累加数组。 + errcode = this->aryScan.scanArrayExclusive( + imgflagDev, imgaccDev, flagsize, add, false, false, false); + if (errcode != NO_ERROR) { + FAIL_IMGCONVERTTOCST_FREE; + return errcode; + } + + // 将累加数组中最后一个元素拷贝到 Host,该数据表示整个图像中有效像素点的数 + // 量。 + int ptscnt; + cuerrcode = cudaMemcpy(&ptscnt, &imgaccDev[flagsize], sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaGetLastError() != cudaSuccess) { + FAIL_IMGCONVERTTOCST_FREE; + return CUDA_ERROR; + } + + // 将输出坐标点集拷贝入 Device 内存。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(outcst); + if (errcode != NO_ERROR) { + // 如果输出坐标点击无数据(故上面的拷贝函数会失败),则会创建一个和有效 + // 像素点等量的坐标点集。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(outcst, ptscnt); + // 如果创建坐标点集也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) { + FAIL_IMGCONVERTTOCST_FREE; + return errcode; + } + } + + // 获取输出坐标点集对应的 CUDA 型数据。 + CoordiSetCuda *outcstCud = COORDISET_CUDA(outcst); + + // 计算调用第二个 Kernel 所需要的线程块尺寸。 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用第二个 Kernel 得到输出坐标点集。 + _arrangeCstKer<<>>( + insubimgCud, imgflagDev, imgaccDev, ptscnt, *outcstCud); + if (cudaGetLastError() != cudaSuccess) { + FAIL_IMGCONVERTTOCST_FREE; + return CUDA_ERROR; + } + + // 释放临时内存空间。 + cudaFree(tmpdata); + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数:_curConvertImgKer(实现将曲线转化为图像算法) +static __global__ void _curConvertImgKer(Curve incur, ImageCuda inimg, + unsigned char highpixel) +{ + // index 表示线程处理的像素点的坐标。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 检查坐标点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (index >= incur.curveLength) + return; + + // 获得目标点在图像中的对应位置。 + int curpos = incur.crvData[2 * index + 1] * inimg.pitchBytes + + incur.crvData[2 * index]; + + // 将曲线中坐标在图像中对应的像素点的像素值置为 higpixel。 + inimg.imgMeta.imgData[curpos] = highpixel; +} + +// Host 成员方法:curConvertToImg(曲线转化为图像算法) +__host__ int ImgConvert::curConvertToImg(Curve *incur, Image *outimg) +{ + // 局部变量,错误码。 + int errcode; + + // 检查输入曲线,输出图像是否为空。 + if (incur == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输出图像拷贝到 device 端。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + return errcode; + } + + // 将输入曲线拷贝到 device 端。 + errcode = CurveBasicOp::copyToCurrentDevice(incur); + if (errcode != NO_ERROR) { + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 初始化输入图像内所有的像素点的的像素值为 lowpixel,为转化做准备。 + cudaError_t cuerrcode; + cuerrcode = cudaMemset(outsubimgCud.imgMeta.imgData, this->lowPixel, + sizeof(unsigned char) * outsubimgCud.imgMeta.width * + outsubimgCud.imgMeta.height); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。调用一维核函数, + // 在这里设置线程块内的线程数为 256,用 DEF_BLOCK_1D 表示。 + size_t blocksize1, gridsize1; + blocksize1 = DEF_BLOCK_1D; + gridsize1 = (incur->curveLength + blocksize1 - 1) / blocksize1; + + // 将输入曲线转化为输入图像图像,即将曲线内点映射在图像上点的 + // 像素值置为 highpixel。 + _curConvertImgKer<<>>(*incur, outsubimgCud, + this->highPixel); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} \ No newline at end of file diff --git a/okano_3_0/ImgConvert.h b/okano_3_0/ImgConvert.h new file mode 100644 index 0000000..453a631 --- /dev/null +++ b/okano_3_0/ImgConvert.h @@ -0,0 +1,309 @@ +// ImgConvert.h +// 创建人:杨伟光 +// +// 图像与坐标集的互相转化,曲线转化成图像(ImgConvert) +// 功能说明:实现了图像与坐标集互相转化和曲线转化成图像的一些接口。 +// +// 修订历史: +// 2013年03月18日(杨伟光) +// 初始版本。 +// 2013年03月20日(杨伟光) +// 增加了识别图像中高像素的四种模式。 +// 2013年03月23日(于玉龙) +// 修改了算法 CLASS 的设计,使其能够适应更加复杂的情况。 +// 2013年03月24日(于玉龙) +// 实现了并行的图像到坐标点集的转化算法。修正了代码实现中的潜在 Bug。 +// 2013年09月21日(于玉龙) +// 修正了调用 SCAN 算法时的一处 BUG。 +// 2013年10月13日(杨伟光) +// 添加了曲线转化成图像功能。 +// 2014年09月20日(杨伟光) +// 改进了算法中初始化图像的逻辑。 + + +#ifndef __IMGCONVERT_H__ +#define __IMGCONVERT_H__ + +#include "ErrorCode.h" +#include "Image.h" +#include "CoordiSet.h" +#include "ScanArray.h" +#include "Curve.h" + + +// 宏:IC_FLAG_CNT +// 转换标志位数组的尺寸,该值为 unsigned char 型数据所能表示的数据数量的总和。 +#define IC_FLAG_CNT (1 << (sizeof (unsigned char) * 8)) + + +// 类:ImgConvert(图像与坐标集的互相转化算法) +// 继承自:无。 +// 实现了图像与坐标集的互相转化算法。 +class ImgConvert { + +protected: + + // 成员变量:highPixel(高像素) + // 图像内高像素的像素值,可由用户定义。 + unsigned char highPixel; + + // 成员变量:lowPixel(低像素) + // 图像内低像素的像素值,可由用户定义。 + unsigned char lowPixel; + + // 成员变量:convertFlags(转换标志位) + // 表示那些像素值可以在图像转换为坐标集时记录到坐标集中。该数组中对应位为 + // true 的像素值在图像转换成坐标集时会被记录到坐标集中。 + bool convertFlags[IC_FLAG_CNT]; + + // 成员变量:aryScan(扫描累加器) + // 完成非分段扫描,主要在迭代计算凸壳点的过程中用于计算各个标记值所对应的累 + // 加值。 + ScanArray aryScan; + +public: + + // 构造函数:ImgConvert + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + ImgConvert() + { + this->highPixel = 255; // 高像素值默认为 255。 + this->lowPixel = 0; // 低像素值默认为 0。 + + // 初始化转换标志位,其中大于等于 50% 灰度的像素值被置为有效像素值。 + for (int i = 0; i < IC_FLAG_CNT / 2; i++) + convertFlags[i] = false; + for (int i = IC_FLAG_CNT / 2; i < IC_FLAG_CNT; i++) + convertFlags[i] = true; + + // 配置扫描累加器。 + this->aryScan.setScanType(NAIVE_SCAN); + } + + // 构造函数:ImgConvert + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + ImgConvert( + unsigned char highpixel, // 高像素 + unsigned char lowpixel // 低像素 + ) { + this->highPixel = 255; // 高像素值默认为 255。 + this->lowPixel = 0; // 低像素值默认为 0。 + + // 初始化转换标志位,其中大于等于 50% 灰度的像素值被置为有效像素值。 + for (int i = 0; i < IC_FLAG_CNT / 2; i++) + convertFlags[i] = false; + for (int i = IC_FLAG_CNT / 2; i < IC_FLAG_CNT; i++) + convertFlags[i] = true; + + // 配置扫描累加器。 + this->aryScan.setScanType(NAIVE_SCAN); + + // 根据参数列表中的值设定成员变量的初值。 + this->setHighPixel(highpixel); + this->setLowPixel(lowpixel); + } + + // 成员方法:getHighPixel(获取高像素的值) + // 获取成员变量 highPixel 的值。 + __host__ __device__ unsigned char // 返回值:返回 hignPixel 的值。 + getHighPixel() const + { + // 返回 highPixel 成员变量的值。 + return this->highPixel; + } + + // 成员方法:setHighPixel(设置高像素) + // 设置成员变量 highPixel 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setHighPixel( + unsigned char highpixel // 高像素的像素值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highpixel == this->lowPixel) + return INVALID_DATA; + + // 将 highPixel 成员变量赋成新值 + this->highPixel = highpixel; + + return NO_ERROR; + } + + // 成员方法:getLowPixel(获取低像素的值) + // 获取成员变量 lowPixel 的值。 + __host__ __device__ unsigned char // 返回值:返回 lowPixel 的值。 + getLowPixel() const + { + // 返回 lowPixel 成员变量的值。 + return this->lowPixel; + } + + // 成员方法:setLowPixel(设置低像素) + // 设置成员变量 lowPixel 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setLowPixel( + unsigned char lowpixel // 低像素的像素值 + ) { + // 如果高像素和低像素相等,则报错。 + if (lowpixel == this->highPixel) + return INVALID_DATA; + + // 将 lowPixel 成员变量赋成新值。 + this->lowPixel = lowpixel; + + return NO_ERROR; + } + + // 成员方法:setHighLowPixel(设置高低像素) + // 设置成员变量 highPixel 和 lowPixel 的值。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + setHighLowPixel( + unsigned char highpixel, // 高像素的像素值 + unsigned char lowpixel // 低像素的像素值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highpixel == lowpixel) + return INVALID_DATA; + + // 将 highPixel 和 lowPixel 成员变量赋成新值。 + this->highPixel = highpixel; + this->lowPixel = lowpixel; + + return NO_ERROR; + } + + // 成员方法:getConvertFlag(获取某像素值是否为有效像素) + // 获取指定像素值是否在转化为坐标集时写入坐标集中。 + __host__ __device__ bool // 返回值:该像素值是否为有效像素。 + getConvertFlag( + unsigned char pixel // 给定的像素值 + ) const + { + // 返回成员变量 convertFlags 中对应的元素。 + return this->convertFlags[pixel]; + } + + // 成员方法:clearAllConvertFlags(清楚所有转换标志位) + // 清除所有转换标志位。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + clearAllConvertFlags() + { + // 将所有的转换标志位都设为 false。 + for (int i = 0; i < IC_FLAG_CNT; i++) + this->convertFlags[i] = false; + + // 处理完毕,退出。 + return NO_ERROR; + } + + // 成员方法:setAllConvertFlags(置位所有转换标志位) + // 设置所有的转换标志位。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + setAllConvertFlags() + { + // 将所有的转换标志位都设为 true。 + for (int i = 0; i < IC_FLAG_CNT; i++) + this->convertFlags[i] = true; + + // 处理完毕,退出。 + return NO_ERROR; + } + + // 成员方法:clearConvertFlag(清除某个转换标志位) + // 清除指定的某一个转换标志位。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + clearConvertFlag( + unsigned char pixel // 给定的像素值 + ) { + // 将指定的转换标志位置为 false。 + this->convertFlags[pixel] = false; + + // 处理完毕退出。 + return NO_ERROR; + } + + // 成员方法:setConvertFlag(置位某个转换标志位) + // 设置指定的某一个转换标志位。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + setConvertFlag( + unsigned char pixel // 给定的像素值 + ) { + // 将指定的转换标志位置为 true。 + this->convertFlags[pixel] = true; + + // 处理完毕退出。 + return NO_ERROR; + } + + // 成员方法:clearConvertFlags(清除某范围内转换标志位) + // 清除指定的某范围内转换标志位。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + clearConvertFlags( + unsigned char low, // 范围下界(含) + unsigned char high // 范围上界(含) + ) { + // 如果给定的下界大于上界,则报错退出 + if (high < low) + return INVALID_DATA; + + // 将指定范围内的转换标志位置为 false。 + for (int i = low; i <= high; i++) + this->convertFlags[i] = false; + + // 处理完毕退出。 + return NO_ERROR; + } + + // 成员方法:setConvertFlags(置位某范围内转换标志位) + // 设置指定的某范围内转换标志位。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + setConvertFlags( + unsigned char low, // 范围下界(含) + unsigned char high // 范围上界(含) + ) { + // 如果给定的下界大于上界,则报错退出 + if (high < low) + return INVALID_DATA; + + // 将指定范围内的转换标志位置为 true。 + for (int i = low; i <= high; i++) + this->convertFlags[i] = true; + + // 处理完毕退出。 + return NO_ERROR; + } + + // Host 成员方法:imgConvertToCst(图像转化成坐标集算法) + // 将图像中的高像素值点存储到坐标集中从而实现图像与坐标集的转化 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + imgConvertToCst( + Image *inimg, // 输入图像 + CoordiSet *outcst // 输出坐标集 + ); + + // Host 成员方法:cstConvertToImg(坐标集转化为图像算法) + // 将坐标集内的坐标映射到输出图像中并将目标点置为 highpixel 从而实现 + // 坐标集与图像的转化。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR。 + cstConvertToImg( + CoordiSet *incst, // 输入坐标集 + Image *outimg // 输出图像 + ); + + // Host 成员方法:curConvertToImg(曲线转化为图像算法) + // 将曲线内的坐标映射到输出图像中并将目标点置为 highpixel 从而实现 + // 曲线与图像的转化。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR。 + curConvertToImg( + Curve *incur, // 输入曲线 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/InscribedCircle.cu b/okano_3_0/InscribedCircle.cu new file mode 100644 index 0000000..7187238 --- /dev/null +++ b/okano_3_0/InscribedCircle.cu @@ -0,0 +1,397 @@ +// InscribedCircle +// 实现的曲线内接圆 + +#include "InscribedCircle.h" + +#include +#include +#include +#include + +using namespace std; + +#include "Template.h" +#include "TemplateFactory.h" +#include "SortArray.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:IN_LABEL 和 OUT_LABEL +// 定义了曲线内的点和曲线外的点标记值 +#define IN_LABEL 255 +#define OUT_LABEL 0 + +// Kernel 函数:_setCloseAreaKer(将封闭曲线包围的内部区域的值变为白色) +// 该核函数使用著名的射线法确定点和一个封闭曲线的位置关系,即如果由当前点引射线, +// 与曲线有奇数个交点则在内部,如果有偶数个交点,则在曲线外部( 0 属于偶数), +// 引用该算法实现将封闭曲线包围的内部区域的值变为白色,并且需要 +// 得到闭合曲线包围的点的个数,用于后续处理 +static __global__ void // Kernel 函数无返回值 +_setCloseAreaKer( + CurveCuda curve, // 输入曲线 + ImageCuda maskimg, // 输出标记结果 + int *count // 闭合曲线包围点的个数 +); + + +// 全局函数:compare (两个 int 变量的比较函数) +// 使用于针对特征值快速排序中的比较函数指针 +int compare(const void * a, const void * b) +{ + return *(int *)b - *(int *)a; + // return *(float *)b > *(float *)a ? 1:-1; +} + +// Kernel 函数:_setCloseAreaKer(将封闭曲线包围的内部区域的值变为白色) +static __global__ void _setCloseAreaKer(CurveCuda curve, ImageCuda maskimg, + int *count) +{ + // 计算当前线程的索引 + int xidx = blockIdx.x * blockDim.x + threadIdx.x; + int yidx = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断当前线程是否越过输入图像尺寸 + if (xidx >= maskimg.imgMeta.width || yidx >= maskimg.imgMeta.height) + return; + + // 定义部分寄存器变量 + int downcount = 0; // 向下引射线和曲线的交点个数 + int length = curve.crvMeta.curveLength; // 曲线上的点的个数 + int outpitch = maskimg.pitchBytes; // 输出标记图像的 pitch + + // 首先将所有点标记为曲线外的点 + maskimg.imgMeta.imgData[yidx * outpitch + xidx] = OUT_LABEL; + + int flag = 0; // 判断是否进入切线区域 + + // 遍历曲线,统计上述各个寄存器变量的值 + for (int i = 0; i < length; i++) { + int x = curve.crvMeta.crvData[2 * i]; + int y = curve.crvMeta.crvData[2 * i + 1]; + + // 曲线中的下一个点的位置 + int j = (i + 1) % length; + int x2 = curve.crvMeta.crvData[2 * j]; + + // 曲线中上一个点的位置 + int k = (i - 1 + length) % length; + int x3 = curve.crvMeta.crvData[2 * k]; + + // 曲线上的第 i 个点与当前点在同一列上 + if (x == xidx) { + if (y == yidx) { + ////当前点在曲线上,此处把曲线上的点也作为曲线内部的点 + // maskimg.imgMeta.imgData[yidx * outpitch+ xidx] = IN_LABEL; + return; + } + + // 交点在当前点的下方 + if (y > yidx) { + // 曲线上下一个也在射线上时,避免重复统计,同时设置 flag + // 标记交点行开始。如果下一个点不在射线上,通过 flag 判断到 + // 底是交点行结束还是单点相交,如果是单点相交判断是否为突出点 + // 如果是交点行结束判断是否曲线在交点行同侧,以上都不是统计值 + // 加一. + if (x2 == xidx) { + if (flag == 0) + flag = x3 - x; + } else { + if (flag == 0) { + if ((x3 - x) * (x2 - x) <= 0) + downcount++; + } else { + if (flag * (x2 - x) < 0) + downcount++; + flag = 0; + } + } + } + } + } + + // 交点数均为奇数则判定在曲线内部 + if (downcount % 2 == 1) { + maskimg.imgMeta.imgData[yidx * outpitch + xidx] = IN_LABEL; + atomicAdd(count, 1); + } +} + +// Kernel 函数:_calInsCirRadiusKer() +static __global__ void _calInsCirRadiusKer(CurveCuda curve, ImageCuda maskimg, + int *dev_inscirDist, int *dev_inscirX, int *dev_inscirY, int *dev_num) +{ + // 计算当前线程的索引 + int xidx = blockIdx.x * blockDim.x + threadIdx.x; + int yidx = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断当前线程是否越过输入图像尺寸 + if (xidx >= maskimg.imgMeta.width || yidx >= maskimg.imgMeta.height) + return; + + // 得到标记图像的 pitch + int pitch = maskimg.pitchBytes; + int width = maskimg.imgMeta.width; + int index = yidx * width + xidx; + + // 曲线上的点的个数 + int length = curve.crvMeta.curveLength; + + if (maskimg.imgMeta.imgData[yidx * pitch+ xidx] == OUT_LABEL) { + dev_inscirDist[index] = 0; + } else { + int min = 65535; + int dist; + for (int i = 0; i < length; i++) { + int x = curve.crvMeta.crvData[2 * i]; + int y = curve.crvMeta.crvData[2 * i + 1]; + dist = (xidx - x) * (xidx - x) + (yidx - y) * (yidx - y); + if (dist < min) + min = dist; + } + dev_inscirDist[index] = (int)sqrtf(min); + } +} + +__host__ void getH_inscirDist(int *tmp_inscirDist, int width, int height, + int *h_inscirDist, int *h_inscirX, int *h_inscirY) +{ + int size = 0; + int tmp; + int i, j; + for (i = 0; i < height; i++) { + for(j = 0; j < width; j++) { + tmp = *(tmp_inscirDist + i * width + j); + if (tmp) { + *(h_inscirDist + size) = tmp; + *(h_inscirX + size) = j; + *(h_inscirY + size) = i; + size++; + } + } + } +} + +__host__ void swap(int *h_inscirDist, int *h_inscirX, int *h_inscirY, + int index, int max_index) +{ + *(h_inscirDist + index) = *(h_inscirDist + index) ^ *(h_inscirDist + max_index); + *(h_inscirDist + max_index) = *(h_inscirDist + index) ^ *(h_inscirDist + max_index); + *(h_inscirDist + index) = *(h_inscirDist + index) ^ *(h_inscirDist + max_index); + + *(h_inscirX + index) = *(h_inscirX + index) ^ *(h_inscirX + max_index); + *(h_inscirX + max_index) = *(h_inscirX + index) ^ *(h_inscirX + max_index); + *(h_inscirX + index) = *(h_inscirX + index) ^ *(h_inscirX + max_index); + + *(h_inscirY + index) = *(h_inscirY + index) ^ *(h_inscirY + max_index); + *(h_inscirY + max_index) = *(h_inscirY + index) ^ *(h_inscirY + max_index); + *(h_inscirY + index) = *(h_inscirY + index) ^ *(h_inscirY + max_index); + +} + +__host__ void setFlag(bool *flag, int *inscirX, int *inscirY, + int cnum, int index, int disTh) +{ + int x = inscirX[index]; + int y = inscirY[index]; + //int max = 0; + int length = disTh * disTh; + //int flagnum = 0; + for(int i = index + 1; i < cnum; i++) { + if (flag[i]) { + int dis = (inscirX[i] - x) * (inscirX[i] - x) + + (inscirY[i] - y) * (inscirY[i] - y); + //if(max max) { + max = tmp; + max_index = j; + in = true; + } + } + + if (!in) { + count = i; + break; + } + + swap(h_inscirDist, h_inscirX, h_inscirY, i, max_index); + *(inscirDist + i) = *(h_inscirDist + i); + *(inscirX + i) = *(h_inscirX + i); + *(inscirY + i) = *(h_inscirY + i); + flag[i] = false; + setFlag(flag, h_inscirX, h_inscirY, cnum, i, disTh); + } + + if(i == num) { + count = num; + } + delete [] flag; +} + +// Host 成员方法:inscribedCircle(曲线最大内接圆) +__host__ int InscribedCircle::inscribedCircle(Curve *curve, int width, + int height, int &count, int *inscirDist, int *inscirX, int *inscirY) +{ + // 判断输入曲线,输入半径数组,输入圆心坐标是否为空 + if (curve == NULL || inscirDist == NULL || + inscirX == NULL || inscirY == NULL) + return NULL_POINTER; + + // 检查输入参数是否有数据 + if (curve->curveLength <= 0 || width <= 0 || height <= 0) + return INVALID_DATA; + + // 检查输入曲线是否为封闭曲线,如果不是封闭曲线返回错误 + //if (!curve->closed) + // return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + cudaError_t cuerrcode; + + // 将曲线拷贝到 Device 内存中 + errcode = CurveBasicOp::copyToCurrentDevice(curve); + if (errcode != NO_ERROR) + return errcode; + + // 获取 CurveCuda 指针 + CurveCuda *curvecud = CURVE_CUDA(curve); + + // 定义设备端局部变量,用于多份数据的一份申请 + void *temp_dev = NULL; + + // 定义临时标记图像指针 + Image *maskimg = NULL; + + // 给临时标记图像在设备申请空间 + ImageBasicOp::newImage(&maskimg); + if (errcode != NO_ERROR) + return errcode; + errcode = ImageBasicOp::makeAtCurrentDevice(maskimg, width, height); + if (errcode != NO_ERROR) { + // + return errcode; + } + + // 获取 ImageCuda 指针 + ImageCuda *maskimgcud = IMAGE_CUDA(maskimg); + size_t datasize = width * height; + + // 给 temp_dev 在设备申请空间 + cuerrcode = cudaMalloc((void**)&temp_dev, (datasize * 3 + 1) * sizeof (int)); + if (cuerrcode != cudaSuccess) { + // + return CUDA_ERROR; + } + + // 定义设备指针 + int *dev_inscirDist = (int *)temp_dev; + int *dev_inscirX = (int *)(dev_inscirDist + datasize); + int *dev_inscirY = (int *)(dev_inscirX + datasize); + int *dev_count = (int *)(dev_inscirY + datasize); + cudaMemset(dev_count, 0, sizeof (int)); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y - 1) / blocksize.y; + + // 调用核函数,将封闭曲线包围的内部区域的值变为白色,并且得到包围点的个数 + _setCloseAreaKer<<>>( + *curvecud, *maskimgcud, dev_count); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + //FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + // 调用核函数,将 + _calInsCirRadiusKer<<>>(*curvecud, *maskimgcud, + dev_inscirDist, dev_inscirX, dev_inscirY, dev_count); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + //FREE_CURVE_TOPOLOGY; + return CUDA_ERROR; + } + + int cnum; + cudaMemcpy(&cnum, dev_count, sizeof (int), cudaMemcpyDeviceToHost); + + int *h_inscirDist = new int [cnum]; + + int *h_inscirX = new int [cnum]; + int *h_inscirY = new int [cnum]; + + int *tmp_inscirDist = new int [datasize]; + + cudaMemcpy(tmp_inscirDist, dev_inscirDist, sizeof (int) * datasize, + cudaMemcpyDeviceToHost); + + getH_inscirDist(tmp_inscirDist, width, height, + h_inscirDist, h_inscirX, h_inscirY); + + getInscirDist(inscirDist, inscirX, inscirY, this->num, this->disTh, count, + h_inscirDist, h_inscirX, h_inscirY, cnum); + + + // for(int i = 0; i < count; i++) { + // cout<<"["<()); + //for(int i=0;inum = 1; // 默认内接圆个数为 1 + this->disTh = 1; // 默认两个圆心之间的距离默认为 1 + + // 根据参数列表中的值设定成员变量的初值 + this->setNum(num); + this->setDisTH(disTh); + } + + // 成员方法:getNum(得到内接圆个数) + // 读取 num 成员变量的值。 + __host__ __device__ int // 返回值:当前 num 成员变量的值。 + getNum() const + { + // 返回 num 成员变量的值。 + return this->num; + } + + // 成员方法:setnum(设置内接圆个数参数) + // 设置 num 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setNum( + int num // 新的内接圆个数参数 + ) { + // 将 num 成员变量赋成新值 + this->num = num; + return NO_ERROR; + } + + // 成员方法:getDisTh(读取圆形邻域参数) + // 读取 disTh 成员变量的值。 + __host__ __device__ int // 返回值:当前 disTh 成员变量的值。 + getDisTh() const + { + // 返回 num 成员变量的值。 + return this->disTh; + } + + // 成员方法:setDisTH(设置圆形邻域参数) + // 设置 disTh 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setDisTH( + int disTh // 新的距离参数阈值参数 + ) { + // 将 num 成员变量赋成新值 + this->disTh = disTh; + return NO_ERROR; + } + + // Host 成员方法:inscribedCircle(曲线最大内接圆) + // 对图像进行曲线跟踪,得到非闭合曲线和闭合曲线的有序序列,根据输入参数设置 + // 大小具有一定的断点连接功能。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + inscribedCircle( + Curve *curve, // 输入的闭合曲线 + int width, // 曲线对应的图像宽度 + int height, // 曲线对应的图像高度 + int &count, // 得到的真正的符合要求的内接圆个数 + int *inscirDist, // 得到的最大的 count 个内接圆半径序列 + int *inscirX, // 得到的最大的 count 个内接圆的 X 坐标 + int *inscirY // 得到的最大的 count 个内接圆的 Y 坐标 + ); +}; + +#endif diff --git a/okano_3_0/IsolatedPoints.cu b/okano_3_0/IsolatedPoints.cu new file mode 100644 index 0000000..01b12d4 --- /dev/null +++ b/okano_3_0/IsolatedPoints.cu @@ -0,0 +1,230 @@ +// IsolatedPoints.cu +// 实现图像的孤立点检测 + +#include "IsolatedPoints.h" + +#include +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_TPL_SIZE_X 和 DEF_TPL_SIZE_Y +// 定义了模板的横向和纵向尺寸 +#define DEF_TPL_SIZE_X 3 +#define DEF_TPL_SIZE_Y 3 + +// 宏:DEF_TPL_OFFSET_X 和 DEF_TPL_OFFSET_Y +// 定义了模板的横向和纵向偏移 +#define DEF_TPL_OFFSET_X (DEF_TPL_SIZE_X / 2) +#define DEF_TPL_OFFSET_Y (DEF_TPL_SIZE_Y / 2) + +// 宏:DEF_TPL_DATA_SIZE +// 定义了模板的大小 +#define DEF_TPL_DATA_SIZE (DEF_TPL_SIZE_X * DEF_TPL_SIZE_Y) + +// 宏:DEF_TPL_POINT_CNT +// 定义了模板邻域的大小 +#define DEF_TPL_POINT_CNT (DEF_TPL_DATA_SIZE - 1) + +// Device 全局常量:_laplasDev(拉普拉斯模板权重) +// 拉普拉斯模板为 3 * 3 模板,具体权重比例为中心为 -8,四周为 1。 +// 应用此模板可以使孤立点更加突出明显。 +static __device__ int _laplasDev[DEF_TPL_SIZE_Y][DEF_TPL_SIZE_X] = { + { 1, 1, 1 }, + { 1, -8, 1 }, + { 1, 1, 1 } +}; + +// Kernel 函数:_isopointDetectKer(孤立点检测) +// 根据给定的模版对图像进行孤立点检测。 +static __global__ void // Kernel 函数无返回值 +_isopointDetectKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char threshold // 灰度差值 +); + +// Kernel 函数: _isopointDetectKer(孤立点检测) +static __global__ void _isopointDetectKer(ImageCuda inimg, ImageCuda outimg, + unsigned char threshold) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据指针位置。 + unsigned char *curinptr = inimg.imgMeta.imgData + r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据指针位置。 + unsigned char *curoutptr = outimg.imgMeta.imgData + + r * outimg.pitchBytes + c; + // 因为一个线程处理四个点,这里定义一个长度为 4 的数组用来保存滤波后各点值 + int total[4] = { 0, 0, 0, 0 }; + // 计算邻域合法点个数变量,初始化为 -1 + int lepoint[4] = { -1, -1, -1, -1 }; + // 根据图像点位置不同,阈值判断有差异,此为实际阈值,定义长度为 4 的数组 + int curthres[4]; + // 记录输入图像处理模板像素点位置 + unsigned char *curtplptr; + // 记录图像邻域点横纵坐标 + int inx, iny; + + // 进行滤波迭代运算 + for (int dy = -DEF_TPL_OFFSET_Y; dy <= DEF_TPL_OFFSET_Y; dy++) { + for (int dx = -DEF_TPL_OFFSET_X; dx <= DEF_TPL_OFFSET_X; dx++) { + // 计算对应模板数组中实际横坐标 + int lapx = dx + DEF_TPL_OFFSET_X; + // 计算对应模板数组中实际纵坐标 + int lapy = dy + DEF_TPL_OFFSET_Y; + // 计算图像邻域点横坐标 + inx = c + dx; + // 计算图像邻域点纵坐标 + iny = r + dy; + // 判断是否越界 + if (inx < 0 && inx >= inimg.imgMeta.width) + continue; + // 计算输入图像第一个模板点真实坐标 + curtplptr = inimg.imgMeta.imgData + inx + + iny * inimg.pitchBytes; + if (iny >= 0 && iny < inimg.imgMeta.height) { + // 合法点数加一 + lepoint[0]++; + // 计算滤波迭代值 + total[0] += _laplasDev[lapy][lapx] * (*curtplptr); + } + + // 一个线程处理四个像素点. + // 处理剩下的三个像素点。 + for (int i = 1; i < 4; i++) { + // 获取当前列的下一行的像素的位置 + curtplptr += inimg.pitchBytes; + // 使 iny 加一,得到当前要处理的像素的 y 分量 + iny++; + // 判断是否越界,若越界,则继续处理下一个模板点 + if (iny >= 0 && iny < inimg.imgMeta.height) { + // 合法点数加一 + lepoint[i]++; + // 计算滤波迭代值 + total[i] += _laplasDev[lapy][lapx] * (*curtplptr); + } + } + } + } + + // total[0] 减去不合法邻域点的权重 + total[0] += (DEF_TPL_POINT_CNT - lepoint[0]) * (*curinptr); + // 根据点位置不同计算实际阈值 + curthres[0] = threshold * lepoint[0]; + // 通过对滤波后的图像进行二值化处理实现孤立点检测 + // 线程中处理的第一个点。 + // 滤波后值大于阈值设为白色,小于阈值设为黑色 + *curoutptr = (abs(total[0]) >= curthres[0]) ? 255 : 0; + + // 一个线程处理四个像素点. + // 处理剩下的三个像素点,为输出图像对应点赋值 + for (int i = 1; i < 4; i++) { + // 先判断 y 分量是否越界,如果越界,则可以确定后面的点也会越界,所以 + // 直接返回 + if (++r >= outimg.imgMeta.height) + return; + + // 获取当前输出列的下一行的位置指针 + curoutptr += outimg.pitchBytes; + // 获取当前输入列的下一行的位置指针 + curinptr += inimg.pitchBytes; + // total[i] 减去不合法邻域点的权重 + total[i] += (DEF_TPL_POINT_CNT - lepoint[i]) * (*curinptr); + // 根据点位置不同计算实际阈值 + curthres[i] = threshold * lepoint[i]; + + // 一个线程处理四个像素点。 + // 通过对滤波后的图像进行二值化处理实现孤立点检测 + // 线程中处理的后三个点。 + // 滤波后值大于阈值设为白色,小于阈值设为黑色 + *curoutptr = (abs(total[i]) >= curthres[i]) ? 255 : 0; + } +} + +// Host 成员方法:isopointDetect(孤立点检测) +__host__ int IsolatedPoints::isopointDetect(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据模版进行孤立点检测。 + _isopointDetectKer<<>>(insubimgCud, outsubimgCud, + threshold); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/IsolatedPoints.h b/okano_3_0/IsolatedPoints.h new file mode 100644 index 0000000..24c2f7a --- /dev/null +++ b/okano_3_0/IsolatedPoints.h @@ -0,0 +1,106 @@ +//IsolatedPoints.h +//创建人:王春峰 +// +// 孤立点检测(IsolatedPoints) +// 功能说明:根据给定模板,利用拉普拉斯二阶导数对邻域像素进行卷积运算, +// 然后进行二值化操作,实现图像的孤立点检测 +// +// 修订历史: +// 2013年03月22日(王春峰) +// 初始版本。 +// 2013年03月28日(王春峰) +// 修正了一些代码注释规范,修改了模板的 set 和 get 函数。 +// 2013年03月30日(王春峰) +// 添加了无参构造函数 +// 2013年04月01日(王春峰) +// 修改了模板的初始化,删除了模板的 get 和 set 函数,修改了构造函数为 host +// 端,修正了一些代码格式 +// 2013年04月02日(王春峰) +// 删除了模板的成员变量,改为 device 端的静态数组。 +// 2013年04月03日(王春峰) +// 修改了部分变量名和部分代码的结构。加入了一些宏定义 +// 2013年04月05日(王春峰) +// 修改了核函数算法 +// 2013年04月11日(王春峰) +// 修正了成员变量的意义,由灰度阈值变为灰度差值(孤立点检测的灰度差值标准) + +#ifndef __ISOLATEDPOINTS_H__ +#define __ISOLATEDPOINTS_H__ + +#include "Image.h" +#include "ErrorCode.h" + + +// 类:IsolatedPoints(孤立点检测算法) +// 继承自:无。 +// 该类用于根据给定模板,利用拉普拉斯二阶导数对邻域像素进行卷积运算, +// 然后进行二值化操作,实现图像的孤立点检测。 +class IsolatedPoints { + +protected: + + // 成员变量:threshold(灰度差值) + // 二值化判断的标准。 + unsigned char threshold; + +public: + + // 构造函数:IsolatedPoints + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ + IsolatedPoints() + { + // 使用默认值为类的成员变量赋值。 + this->threshold = 50; + } + + // 构造函数:IsolatedPoints + // 有参数版本的构造函数,根据需要给定参数,参数值在程序运行过程中 + // 还是可以改变的。 + __host__ + IsolatedPoints( + unsigned char threshold // 灰度差值 + ) { + // 使用默认值为类的成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + // 根据参数列表中的值设定成员变量的初值 + this->threshold = 50; + + // 根据参数列表中的值设定成员变量的初值 + setThreshold(threshold); + } + + // 成员方法:getThreshold(获取灰度差值) + // 获取孤立点检测二值化差值 + __host__ __device__ unsigned char // 返回值:当前灰度差值 + getThreshold() const + { + // 返回 threshold 成员变量的值。 + return this->threshold; + } + + // 成员方法:setThreshold(设置灰度差值) + // 设置灰度差值 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR + setThreshold( + unsigned char impthreshold // 新的灰度差值 + ) { + // 将 threshold 成员变量赋成新值 + this->threshold = impthreshold; + + return NO_ERROR; + } + + // Host 成员方法:isopointDetect(执行孤立点检测) + // 对图像进行孤立点检测操作,根据模板,计算出当前点的像素值,二值化并输出 + __host__ int // 返回值:函数正确执行,返回 NO_ERROR, + // 否则返回相应的错误码 + isopointDetect( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + +}; + +#endif + diff --git a/okano_3_0/LabelIslandSortArea.cu b/okano_3_0/LabelIslandSortArea.cu new file mode 100644 index 0000000..09e9687 --- /dev/null +++ b/okano_3_0/LabelIslandSortArea.cu @@ -0,0 +1,370 @@ +// LabelIslandSortArea.cu +// 实现区域排序算法 + +#include "LabelIslandSortArea.h" +#include "Histogram.h" + +#include +using namespace std; + + +// 宏:MAX_PAIRS_NUM +//(面积值-标记值)键值对的个数。 +#ifndef MAX_PAIRS_NUM +#define MAX_PAIRS_NUM 256 +#endif + +// 宏:SORT_ARRAY_TYPE_ASC +// 排序标识,升序排序。 +#ifndef SORT_ARRAY_TYPE_ASC +#define SORT_ARRAY_TYPE_ASC 2 +#endif + +// 宏:SORT_ARRAY_TYPE_DESC +// 排序标识,降序排序。 +#ifndef SORT_ARRAY_TYPE_DESC +#define SORT_ARRAY_TYPE_DESC 1 +#endif + + +// Kernel 函数: _findAreasByMinMaxKer(筛选面积) +// 筛选出在最大最小面积范围之间的标记区域。 +static __global__ void +_findAreasByMinMaxKer( + unsigned int *histogram, // 直方图面积。 + unsigned int minArea, // 最小面积。 + unsigned int maxArea // 最大面积。 +); + +// Kernel 函数: _bitonicSortPairsByAscendKer(按照升序排序区域面积) +// 实现并行双调排序,按照升序排序区域面积。 +static __global__ void +_bitonicSortPairsByAscendKer( + unsigned int *devarray, // 面积数组。 + unsigned int *devareaRank // 输出的(面积值-标记值)键值对。 +); + +// Kernel 函数: _bitonicSortPairsByDescendKer(按照降序排序区域面积) +// 实现并行双调排序,按照降序排序区域面积。 +static __global__ void +_bitonicSortPairsByDescendKer( + unsigned int *devarray, // 面积数组。 + unsigned int *devareaRank // 输出的(面积值-标记值)键值对。 +); + +// Kernel 函数: _bitonicSortPairsByDescendKer(按照降序排序区域面积) +static __global__ void _bitonicSortPairsByDescendKer( + unsigned int *devarray, unsigned int *devareaRank) +{ + // 读取线程号。 + int tid = threadIdx.x; + int k, ixj, j; + unsigned int tempArea, tempIndex; + + // 声明共享内存,加快数据存取速度。 + __shared__ unsigned int area[MAX_PAIRS_NUM]; + __shared__ unsigned int index[MAX_PAIRS_NUM]; + + // 将面积值拷贝到共享内存中。 + area[tid] = devarray[tid]; + // 将标记值拷贝到共享内存了。 + index[tid] = tid; + __syncthreads(); + + // 并行双调排序,降序排序。 + for (k = 2; k <= MAX_PAIRS_NUM; k = k << 1) { + // 双调合并。 + for (j = k >> 1; j > 0; j = j >> 1) { + // ixj 是与当前位置 i 进行比较交换的位置。 + ixj = tid ^ j; + if (ixj > tid) { + // 如果 (tid & k) == 0,按照降序交换两项。 + if ((tid & k) == 0 && (area[tid] < area[ixj])) { + // 交换面积值。 + tempArea = area[tid]; + area[tid] = area[ixj]; + area[ixj] = tempArea; + // 交换下标值。 + tempIndex = index[tid]; + index[tid] = index[ixj]; + index[ixj] = tempIndex; + // 如果 (tid & k) == 0,按照升序交换两项。 + } else if ((tid & k) != 0 && area[tid] > area[ixj]) { + // 交换面积值。 + tempArea = area[tid]; + area[tid] = area[ixj]; + area[ixj] = tempArea; + // 交换下标值。 + tempIndex = index[tid]; + index[tid] = index[ixj]; + index[ixj] = tempIndex; + } + } + __syncthreads(); + } + } + // 将共享内存中的面积值拷贝到全局内存中。 + devareaRank[2 * tid] = area[tid]; + // 将共享内存中的下标值拷贝到全局内存中。 + devareaRank[2 * tid + 1] = index[tid]; +} + +// Kernel 函数: _bitonicSortPairsByAscendKer(按照升序排序区域面积) +static __global__ void _bitonicSortPairsByAscendKer( + unsigned int *devarray, unsigned int *devareaRank) +{ + // 读取线程号。 + int tid = threadIdx.x; + int k, ixj, j; + unsigned int tempArea, tempIndex; + + // 声明共享内存,加快数据存取速度。 + __shared__ unsigned int area[MAX_PAIRS_NUM]; + __shared__ unsigned int index[MAX_PAIRS_NUM]; + + // 将面积值拷贝到共享内存中。 + area[tid] = devarray[tid]; + // 将标记值拷贝到共享内存了。 + index[tid] = tid; + __syncthreads(); + + // 并行双调排序,升序排序。 + for (k = 2; k <= MAX_PAIRS_NUM; k = k << 1) { + // 双调合并。 + for (j = k >> 1; j > 0; j = j >> 1) { + // ixj 是与当前位置 i 进行比较交换的位置。 + ixj = tid ^ j; + if (ixj > tid) { + // 如果 (tid & k) == 0,按照升序交换两项。 + if ((tid & k) == 0 && (area[tid] > area[ixj])) { + // 交换面积值。 + tempArea = area[tid]; + area[tid] = area[ixj]; + area[ixj] = tempArea; + // 交换下标值。 + tempIndex = index[tid]; + index[tid] = index[ixj]; + index[ixj] = tempIndex; + // 如果 (tid & k) == 0,按照降序交换两项。 + } else if ((tid & k) != 0 && area[tid] < area[ixj]) { + // 交换面积值。 + tempArea = area[tid]; + area[tid] = area[ixj]; + area[ixj] = tempArea; + // 交换下标值。 + tempIndex = index[tid]; + index[tid] = index[ixj]; + index[ixj] = tempIndex; + } + } + __syncthreads(); + } + } + // 将共享内存中的面积值拷贝到全局内存中。 + devareaRank[2 * tid] = area[tid]; + // 将共享内存中的下标值拷贝到全局内存中。 + devareaRank[2 * tid + 1] = index[tid]; +} + +// Host 成员方法:bitonicSortPairs(对区域面积进行排序) +__host__ int LabelIslandSortArea::bitonicSortPairs( + unsigned int *inarray, unsigned int *areaRank) +{ + // 检查 inarray 是否为空 + if (inarray == NULL) + return NULL_POINTER; + + // 检查 areaRank 是否为空 + if (areaRank == NULL) + return NULL_POINTER; + + if (this->sortflag == SORT_ARRAY_TYPE_ASC) + // 升序排序区域面积。 + _bitonicSortPairsByAscendKer<<<1, MAX_PAIRS_NUM>>>(inarray, areaRank); + else if (this->sortflag == SORT_ARRAY_TYPE_DESC) + // 降序排序区域面积。 + _bitonicSortPairsByDescendKer<<<1, MAX_PAIRS_NUM>>>(inarray, areaRank); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// Kernel 函数: _findAreasByMinMaxKer(筛选面积) +static __global__ void _findAreasByMinMaxKer( + unsigned int *histogram, unsigned int minArea, unsigned int maxArea) +{ + // 获取线程号。 + int tid = threadIdx.x; + histogram[0] = 0; + // 如果直方图面积不在最大最小面积范围内,则将其对应面积清0。 + if (histogram[tid] < minArea || histogram[tid] > maxArea) + histogram[tid] = 0; +} + +// Host 成员方法:labelIslandSortArea(对标记后的所有区域按照面积进行排序) +__host__ int LabelIslandSortArea::labelIslandSortArea( + Image *inimg, unsigned int *areaRank) +{ + // 检查图像是否为 NULL。 + if (inimg == NULL) + return NULL_POINTER; + + // 检查 areaRank 是否为空 + if (areaRank == NULL) + return NULL_POINTER; + + // 检查参数是否合法。 + if (minarea < 0 || maxarea < 0 || (sortflag != SORT_ARRAY_TYPE_ASC && + sortflag != SORT_ARRAY_TYPE_DESC)) + return INVALID_DATA; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + unsigned int *alldevicedata; + unsigned int *devhistogram, *devareaRank; + cudaerrcode = cudaMalloc((void**)&alldevicedata, + 3 * MAX_PAIRS_NUM * sizeof (unsigned int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevicedata, 0, + 3 * MAX_PAIRS_NUM * sizeof (unsigned int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 通过偏移读取 devhistogram 内存空间。 + devhistogram = alldevicedata; + + // 通过直方图计算区域面积. + Histogram hist; + errcode = hist.histogram(inimg, devhistogram, 0); + if (errcode != NO_ERROR) + return errcode; + + // 筛选出在最大最小面积范围之间的标记区域。 + _findAreasByMinMaxKer<<<1, MAX_PAIRS_NUM>>>(devhistogram, + minarea, maxarea); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // areaRank 在 Host 端。 + if (this->ishost == 1) { + // 通过偏移读取 devareaRank 内存空间。 + devareaRank = alldevicedata + MAX_PAIRS_NUM; + + // 调用并行双调排序函数,对所选面积进行排序。 + bitonicSortPairs(devhistogram, devareaRank); + + //将 Device上的 devareaRank 拷贝到 Host上。 + cudaerrcode = cudaMemcpy(areaRank, devareaRank, + MAX_PAIRS_NUM * 2 * sizeof(unsigned int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 计算满足条件的不同区域的个数 + int k; + this->length = 0; + for (k = 0; k< MAX_PAIRS_NUM; k++) { + if (areaRank [2 * k] > 0) + this->length++; + } + + // 当 areaRank 按照升序排序时,在数组的前面会有很多无效的 0 项,因为 + // 区域的个数可能小于定义的 MAX_PAIRS_NUM,所以需要重新颠倒数组,使得 + // 有效的非零数据位于数组的前面。下面的代码就是解决此问题的。例如处理 + // 前假设 areaRank = [0, 0, 0, 0, ......, 50000, 8, 60000, 3, 70000, 6] + // ,那么处理后 areaRank = [50000, 8, 60000, 3, 70000, 6, 0, 0, 0, 0, + // ......]。 + int i, j; + if (sortflag == 2) { + if (areaRank[0] == 0) { + j = 0; + for (i = 0; i < MAX_PAIRS_NUM; i++) { + // 如果面积大于0,则迁移。 + if (areaRank[2 * i] > 0) { + areaRank[2 * j] = areaRank[2 * i]; + areaRank[2 * j + 1] = areaRank[2 * i + 1]; + areaRank[2 * i] = 0; + areaRank[2 * i + 1] = 0; + j++; + } + } + } + } + // areaRank 在 Device 端。 + } else if (this->ishost == 0) { + // 声明 Host 端数组,为以后的处理做准备。 + unsigned int hostareaRank[MAX_PAIRS_NUM * 2]; + // 通过偏移读取 devareaRank 内存空间。 + devareaRank = alldevicedata + MAX_PAIRS_NUM; + + // 调用并行双调排序函数,对所选面积进行排序。 + bitonicSortPairs(devhistogram, areaRank); + + //将 Device上的 areaRank 拷贝到 Host上。 + cudaerrcode = cudaMemcpy(hostareaRank, areaRank, + MAX_PAIRS_NUM * 2 * sizeof(unsigned int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 计算满足条件的不同区域的个数 + int k; + this->length = 0; + for (k = 0; k< MAX_PAIRS_NUM; k++) { + if (hostareaRank [2 * k] > 0) + this->length++; + } + + // 当 hostareaRank 按照升序排序时,在数组的前面会有很多无效的 0 项, + // 因为区域的个数可能小于定义的 MAX_PAIRS_NUM,所以需要重新颠倒数组, + // 使得有效的非零数据位于数组的前面。下面的代码就是解决此问题的。 + // 例如处理前假设 hostareaRank = [0, 0, 0, 0, ......, 50000, 8, 60000, + // 3, 70000, 6],那么处理后 hostareaRank = [50000, 8, 60000, 3, 70000, + // 6, 0, 0, 0, 0,......]。 + int i, j; + if (sortflag == 2) { + if (hostareaRank[0] == 0) { + j = 0; + for (i = 0; i < MAX_PAIRS_NUM; i++) { + // 如果面积大于0,则迁移。 + if (hostareaRank[2 * i] > 0) { + hostareaRank[2 * j] = hostareaRank[2 * i]; + hostareaRank[2 * j + 1] = hostareaRank[2 * i + 1]; + hostareaRank[2 * i] = 0; + hostareaRank[2 * i + 1] = 0; + j++; + } + } + } + } + } + + // 释放显存上的临时空间。 + cudaFree(alldevicedata); + + return NO_ERROR; +} diff --git a/okano_3_0/LabelIslandSortArea.h b/okano_3_0/LabelIslandSortArea.h new file mode 100644 index 0000000..1a139da --- /dev/null +++ b/okano_3_0/LabelIslandSortArea.h @@ -0,0 +1,233 @@ +// LabelIslandSortArea.h +// 创建人:刘宇 +// +// 区域排序(LabelIslandSortArea) +// 功能说明:对标记后的所有区域按照面积进行排序。 +// +// 修订历史: +// 2012年8月17日 (刘宇) +// 初始版本。 +// 2012年8月29日 (刘宇) +// 完善注释规范。 +// 2012年9月5日 (刘宇) +// 添加 ishost 成员变量。 +// 2012年10月25日 (刘宇) +// 修正了 __device__ 方法的定义位置,防止了跨文件访问出现未定义的错误。 +// 2012年11月13日(刘宇) +// 在核函数执行后添加 cudaGetLastError 判断语句 +// 2012年11月23日(刘宇) +// 添加输入输出参数的空指针判断 + +#ifndef __LABELISLANDSORTAREA_H__ +#define __LABELISLANDSORTAREA_H__ + +#include "Image.h" +#include "ErrorCode.h" + + +// 类:LabelIslandSortArea(区域排序类) +// 继承自:无 +// 该类包括区域排序的基本操作。输入的图像是经过区域分割后的标记图像; +// 利用直方图方法计算每类标记的数量,即区域的面积。之后,调用并行双调 +// 排序算法对各个区域进行排序,然后按照(面积-标记)的键值对的形式进行 +// 输出。 +class LabelIslandSortArea { + +protected: + + // 成员变量:length(不同标记的数量) + // 该变量作为保留变量,调用函数前不需要指定。 + int length; + + // 成员变量:minarea(最小面积) + int minarea; + + // 成员变量:maxarea(最大面积) + int maxarea; + + // 成员变量:sortflag(排序标记) + // sortflag 等于 1,降序排序; sortflag 等于 2,升序排序。 + int sortflag; + + // 成员变量:ishost(判断输出数组位置) + // 当 ishost 等于 1 时,表示输出数组在 Host 端,否则 ishost 等于 0, + // 表示在 Device 端。 + int ishost; + +public: + + // 构造函数:LabelIslandSortArea + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + LabelIslandSortArea() + { + // 使用默认值为类的各个成员变量赋值。 + this->length = 0; // 区域标记的数量默认为 0。 + this->minarea = 0; // 最小面积默认为 0。 + this->maxarea = 10000; // 最大面积默认为 10000. + this->sortflag = 1; // 排序标识,默认为 1,降序排序。 + this->ishost = 1; // 判断输出数组位置标记值,默认为 1。 + } + + // 构造函数:LabelIslandSortArea + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + LabelIslandSortArea ( + int length, // 标记的数量(具体解释见成员变量) + int minarea, int maxarea, // 最小最大面积(具体解释见成员变量) + int sortflag, // 排序标记(具体解释见成员变量) + int ishost // 输出数组位置(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非 + // 法的初始值而使系统进入一个未知的状态。 + this->length = 0; // 区域标记的数量默认为 0。 + this->minarea = 0; // 最小面积默认为 0。 + this->maxarea = 10000; // 最大面积默认为 10000. + this->sortflag = 1; // 排序标识,默认为 1,降序排序。 + this->ishost = 1; // 判断输出数组位置标记值,默认为 1。 + + // 根据参数列表中的值设定成员变量的初值 + setLength(length); + setMinarea(minarea); + setMaxarea(maxarea); + setSortflag(sortflag); + setIshost(ishost); + } + + // 成员方法:getLength(读取不同标记的个数) + // 读取 lentgh 成员变量。 + __host__ __device__ int // 返回值:当前 length 成员变量的值。 + getLength() const + { + // 返回 length 成员变量的值。 + return this->length; + } + + // 成员方法:setLength(设置不同标记的个数) + // 设置 lentgh 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setLength( + int length // 指定的不同标记的个数。 + ) { + // 将 length 成员变量赋成新值 + this->length = length; + + return NO_ERROR; + } + + // 成员方法:getminarea(读取指定的区域最小面积值) + // 读取 minarea 成员变量。 + __host__ __device__ int // 返回值:当前 minarea 成员变量的值。 + getMinarea() const + { + // 返回 minarea 成员变量的值。 + return this->minarea; + } + + // 成员方法:setminarea(设置指定的区域最小面积值) + // 设置 minarea 成员变量。。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMinarea( + int minarea // 指定的区域最小面积值。 + ) { + // 将 minarea 成员变量赋成新值 + this->minarea = minarea; + + return NO_ERROR; + } + + // 成员方法:getmaxarea(读取指定的区域最大面积值) + // 读取 maxarea 成员变量。 + __host__ __device__ int // 返回值:当前 maxarea 成员变量的值。 + getMaxarea() const + { + // 返回 maxarea 成员变量的值。 + return this->maxarea; + } + + // 成员方法:setmaxarea(设置指定的区域最大面积值) + // 设置 maxarea 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMaxarea( + int maxarea // 指定的区域最大面积值。 + ) { + // 将 minarea 成员变量赋成新值 + this->maxarea = maxarea; + + return NO_ERROR; + } + + // 成员方法:getsortflag(读取区域排序标记) + // 读取 sortflag 成员变量。 + __host__ __device__ int // 返回值:当前 sortflag 成员变量的值。 + getSortflag() const + { + // 返回 sortflag 成员变量的值。 + return this->sortflag; + } + + // 成员方法:setsortflag(设置区域排序标记) + // 设置 sortflag 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setSortflag( + int sortflag // 指定的区域排序标记。 + ) { + // 将 sortflag 成员变量赋成新值 + this->sortflag = sortflag; + + return NO_ERROR; + } + + // 成员方法:getishost(读取判断输出数组位置标记值) + // 读取 ishost 成员变量。 + __host__ __device__ int // 返回值:当前 ishost 成员变量的值。 + getIshost() const + { + // 返回 ishost 成员变量的值。 + return this->ishost; + } + + // 成员方法:setishost(设置判断输出数组位置标记值) + // 设置 ishost 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setIshost( + int ishost // 指定的判断输入和输出数组位置标记值。 + ) { + // 将 ishost 成员变量赋成新值 + this->ishost = ishost; + + return NO_ERROR; + } + + // Host 成员方法:bitonicSortPairs(对区域面积进行排序) + // 输入 inarray 满足最大最小面积要求的区域面积数组,通过实现并行双调排序 + // 对其进行排序,最后按照(面积值-标记值)键值对的形式进行输出。输入输出 + // 参数都位于 Device 端。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回NO_ERROR。 + bitonicSortPairs( + unsigned int *inarray, // 面积数组(位于 Device 端)。 + unsigned int *areaRank // 输出排序后的键值对(位于 Device 端)。 + ); + + // Host 成员方法:labelIslandSortArea(对标记后的所有区域按照面积进行排序) + // 输入图像是经过区域分割后的标记图像,根据直方图计算每个标记的数量,即 + // 不同区域的面积大小。之后实现并行双调排序算法对所有面积进行排序,筛选 + // 出面积在最大最小范围之间的面积区域,按照(面积值-标记值)的键值对的形式 + // 保存到输出数组 areaRank 中。当 ishost 等于 1 时,表示areaRank在 Host 端, + // 否则 ishost 等于 0,表示areaRank在 Device 端。 + __host__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回NO_ERROR。 + labelIslandSortArea( + Image *inimg, // 输入图像。 + unsigned int *areaRank // 输出(面积-标记)键值对数组。 + ); + +}; +#endif diff --git a/okano_3_0/LinearEnhancement.cu b/okano_3_0/LinearEnhancement.cu new file mode 100644 index 0000000..48b58f5 --- /dev/null +++ b/okano_3_0/LinearEnhancement.cu @@ -0,0 +1,231 @@ +// LinearEnhancement.cu +// 实现图像的线性增强 + +#include "LinearEnhancement.h" + +#include +using namespace std; + +#include "ErrorCode.h" + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// 核函数:_inPlaceLinearEnhanceKer (线性增强) +// 根据给定的参数对图像进行增前处理,这是一个 In-place 形式的处理。 +// 对于每一个像素,根据四个参数确定的函数进行变化。 +static __global__ void // Kernel 函数无返回值 +_inPlaceLinearEnhanceKer( + ImageCuda inimgCud, // 待处理图像 + LinearEnhancement le // 图像增强类 +); + +// 核函数:_linearEnhanceKer (线性增强) +// 根据给定的参数对图像进行增前处理,这是一个 Out-place 形式的处理。 +// 对于每一个像素,根据四个参数确定的函数进行变化。 +static __global__ void // Kernel 函数无返回值 +_linearEnhanceKer( + ImageCuda inimgCud, // 输入图像 + ImageCuda outimgCud, // 输出图像 + LinearEnhancement le // 图像增强类 +); + + +// 核函数:_linearEnhanceKer ( 线性增强) +static __global__ void _linearEnhanceKer( + ImageCuda inimgCud, ImageCuda outimgCud, LinearEnhancement le) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= inimgCud.imgMeta.width || dstr >= inimgCud.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = dstr * inimgCud.pitchBytes + dstc; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = dstr * outimgCud.pitchBytes + dstc; + + // 查转置表得到输出图像对应位置的像素 + outimgCud.imgMeta.imgData[outidx] = + le.getPixelTable()[inimgCud.imgMeta.imgData[inidx]]; + + // 处理剩下的三个点 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++dstr >= inimgCud.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimgCud.pitchBytes; + outidx += outimgCud.pitchBytes; + + // 查转置表得到输出图像对应位置的像素 + outimgCud.imgMeta.imgData[outidx] = + le.getPixelTable()[inimgCud.imgMeta.imgData[inidx]]; + } +} + +// 成员函数:linearEnhance (线性增强) +__host__ int LinearEnhancement::linearEnhance(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 如果输出图像为 NULL,直接调用 In—Place 版本的成员方法。 + if (outimg == NULL || inimg == outimg) + return linearEnhance(inimg); + + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + _linearEnhanceKer<<>>( + insubimgCud, outsubimgCud, *this); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// 核函数:_inPlaceLinearEnhanceKer ( 线性增强) +static __global__ void _inPlaceLinearEnhanceKer( + ImageCuda inimgCud, LinearEnhancement le) +{ + + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= inimgCud.imgMeta.width || dstr >= inimgCud.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * inimgCud.pitchBytes + dstc; + + // 查转置表得到输出图像对应位置的像素 + inimgCud.imgMeta.imgData[dstidx] = + le.getPixelTable()[inimgCud.imgMeta.imgData[dstidx]]; + + // 处理剩下的三个点 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++dstr >= inimgCud.imgMeta.height) + return; + + // 计算输入坐标点以及输出坐标点,由于只有 y 分量增加 1,所以下标只需 + // 要加上对应的 pitch 即可,不需要在进行乘法计 + dstidx += inimgCud.pitchBytes; + + // 查转置表得到输出图像对应位置的像素 + inimgCud.imgMeta.imgData[dstidx] = + le.getPixelTable()[inimgCud.imgMeta.imgData[dstidx]]; + } +} + +// 成员函数:linearEnhance( 线性增强) +__host__ int LinearEnhancement::linearEnhance(Image *inimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + _inPlaceLinearEnhanceKer<<>>(insubimgCud, *this); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + diff --git a/okano_3_0/LinearEnhancement.h b/okano_3_0/LinearEnhancement.h new file mode 100644 index 0000000..912d748 --- /dev/null +++ b/okano_3_0/LinearEnhancement.h @@ -0,0 +1,288 @@ +// LinearEnhancement.h +// 创建人:邱孝兵 +// +// 图像增强(Image Enhancement) +// 功能说明:对于输入图像的每个像素根据传入的函数指针进行变化,实现 +// 线性增强,像素的变换是从 unsigned char 到 unsigned char 。 +// +// 修订历史: +// 2012年09月11日(邱孝兵) +// 初始版本。 +// 2012年09月14日(邱孝兵) +// 调整格式(缩进,空格等),增加 outplace 函数,将 +// 函数指针类型转换为类类型,添加图像增强类 +// 2012年09月15日(邱孝兵) +// 经证实设备端不能支持函数指针,函数模板以及虚类继承 +// 所以简化设计放弃 Enhancement,重新定义线性增强类:LinearEnhancement +// 2012年09月21日(邱孝兵) +// 修改格式问题,在增强类中添加像素转置表 +// 2012年10月24日(邱孝兵) +// 将设备端函数修改为内联模式,即声明实现放在一起 + +#ifndef __LINEARENHANCEMENT_H__ +#define __LINEARENHANCEMENT_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 宏:PIXEL_TABLE_SIZE +// 定义了像素表的默认大小 +#define PIXEL_TABLE_SIZE 256 + + +// 类:LinearEnhancement(图像线性增强) +// 继承自:无 +// 根据提供的四个参数,确定一个线性增强函数, +// 提供多种分段线性图像增强的操作。 +class LinearEnhancement { + +protected: + + // 成员变量:left + // 增强函数的左折点的横坐标 + unsigned char left; + + // 成员变量:right + // 增强函数的右折点的横坐标 + unsigned char right; + + // 成员变量:bottom + // 增强函数的左折点的纵坐标 + unsigned char bottom; + + // 成员变量:top + // 增强函数的右折点的纵坐标 + unsigned char top; + + // 成员变量:pixelTable + // 该增强类的像素转置表,通过查这个表可以得到每个像素 + // 在新图像中的新的像素值。该变量不提供 seter。 + unsigned char pixelTable[PIXEL_TABLE_SIZE]; + + // 成员函数:init (初始化各成员变量) + // 将所有成员变量初始化为默认值。 + __host__ __device__ void + init() + { + left = 0; + right = 255; + bottom = 0; + top = 255; + + // 计算像素转置表 + calPixelTable(); + } + + // 成员函数:calPixelTable (计算像素转置表) + // 根据四个成员变量,计算像素转置表 + __host__ __device__ void + calPixelTable() + { + // 计算中间斜线部分的斜率。 + float slope = (top - bottom) * 1.0 / (right - left); + + // 根据当前 i 值来确定转置表中的值, + // 如果 i < left ,则 pixelTable[i] = bottom + // 如果 i > right ,则 pixelTable[i] = top + // 否则,(i, pixelTable[i]) 为根据 (left, bottom) + // 和 (right, top) 确定的直线上的点 + for ( int i = 0; i < PIXEL_TABLE_SIZE; i++){ + if (i < left) + pixelTable[i] = bottom; + else if (i > right) + pixelTable[i] = top; + else + pixelTable[i] = top - (int)(slope * (right - i)); + } + } + +public: + + // 构造函数:EnhancementOpt + // 无参数版本的构造函数,成员变量初始化为默认值。 + __host__ __device__ + LinearEnhancement() + { + init(); + } + + // 构造函数:EnhancementOpt + // 有参数版本的构造函数,根据需要给定各个参数 + __host__ __device__ + LinearEnhancement( + unsigned char left, // 增强函数的左折点的横坐标 + unsigned char right, // 增强函数的右折点的横坐标 + unsigned char bottom, // 增强函数的左折点的纵坐标 + unsigned char top // 增强函数的右折点的纵坐标 + ) { + // 首先初始化为默认值 + init(); + + // 初始化各个参数 + setLeft(left); + setRight(right); + setBottom(bottom); + setTop(top); + } + + // 成员方法:getLeft (获取 left 的值) + // 获取成员变量 left 的值 + __host__ __device__ unsigned char // 返回值:成员变量 left 的值 + getLeft() const + { + // 返回 left 成员变量的值 + return this->left; + } + + // 成员方法:setLeft (设置 left 的值) + // 设置成员变量 left 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setLeft( + unsigned char left // 设定新 left 值 + ) { + if (left < right) + { + // 将 left 成员变量赋成新值 + this->left = left; + + // 重新计算像素转置表 + calPixelTable(); + } + return NO_ERROR; + } + + // 成员方法:getRight (获取 right 的值) + // 获取成员变量 right 的值 + __host__ __device__ unsigned char // 返回值:成员变量 right 的值 + getRight() const + { + // 返回 right 成员变量的值 + return this->right; + } + + // 成员方法:setRight (设置 right 的值) + // 设置成员变量 right 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setRight( + unsigned char right // 设定新的 right 值 + ) { + // 合法性检查 + if (right > left) + { + // 将 right 成员变量赋成新值 + this->right = right; + + // 重新计算像素转置表 + calPixelTable(); + } + + return NO_ERROR; + } + + // 成员方法:setLeftRight (同时设置 left 和 right 值) + // 设置成员变量 left 和 right 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setLeftRight( + unsigned char left, // 新的 left 值 + unsigned char right // 新的 right 值 + ) { + // 合法性检查 + if (right > left) + { + // 将 left 和 right 成员变量赋成新值 + this->left = left; + this->right = right; + + // 重新计算像素转置表 + calPixelTable(); + } + + return NO_ERROR; + } + + // 成员方法:getBottom (获取 bottom 的值) + // 获取成员变量 bottom 的值 + __host__ __device__ unsigned char // 返回值:成员变量 bottom 的值 + getBottom() const + { + // 返回 bottom 成员变量的值 + return this->bottom; + } + + // 成员方法:setBottom (设置 bottom 的值) + // 设置成员变量 bottom 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setBottom( + unsigned char bottom // 设定新的 bottom 值 + ) { + // 将 bottom 成员变量赋成新值 + this->bottom = bottom; + + // 重新计算像素转置表 + calPixelTable(); + + return NO_ERROR; + } + + // 成员方法:getTop (获取 top 的值) + // 获取成员变量 top 的值 + __host__ __device__ unsigned char // 返回值:成员变量 top 的值 + getTop() const + { + // 返回 top 成员变量的值 + return this->top; + } + + // 成员方法:setTop (设置 top 的值) + // 设置成员变量 top 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setTop( + unsigned char top // 设定新的 top 值 + ) { + // 将 top 成员变量赋成新值 + this->top = top; + + // 重新计算像素转置表 + calPixelTable(); + + return NO_ERROR; + } + + // 成员方法:getPixelTable (获取 pixelTable 的值) + // 获取成员变量 pixelTable 的值 + __host__ __device__ unsigned char * // 返回值:成员变量 pixelTable 的值 + getPixelTable() + { + // 返回 pixelTable 成员变量的值 + return this->pixelTable; + } + + // Host成员方法:enhance (图像增强) + // 根据指定的参数对图像进行增强( In-place 版本), + // 在函数中根据四个成员变量的值构造增强函数,对于 + // 输入图像的每个像素进行增强,并保存到输入图像中 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + linearEnhance( + Image *inoutimg // 输入输出图像 + ); + + // Host成员方法:enhance(图像增强) + // 根据指定的参数对图像进行增强(Out-place 版本) + // 在函数中根据四个成员变量的值构造增强函数,对于 + // 输入图像的每个像素进行增强,并保存到输出图像中 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + linearEnhance( + Image *inImg, // 输入图像 + Image *outImg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/LinearFilter.cu b/okano_3_0/LinearFilter.cu new file mode 100644 index 0000000..9f025d3 --- /dev/null +++ b/okano_3_0/LinearFilter.cu @@ -0,0 +1,403 @@ +// LinearFilter.cu +// 实现图像的线性滤波 + +#include +#include "LinearFilter.h" +#include "ErrorCode.h" +#include "ImageDiff.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// static变量:_defTpl +// 当用户未定义有效的模板时,使用此默认模板,默认为3x3,默认模板值为1 +static TemplateCuda *_defTpl = NULL; + +// Host 函数:_initDefTemplate(初始化默认的模板指针) +// 函数初始化默认模板指针 _defTpl,如果原来模板不为空,则直接返回,否则初始化 +// 为3x3的默认模板 +static __host__ TemplateCuda * // 返回值:返回默认模板指针 _defTpl +_initDefTemplate(); + +// Kernel 函数:_linearFilterKer(实现线性滤波操作) + static __global__ void // Kernel 函数无返回值 +_linearFilterKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + TemplateCuda tpl, // 模板 + int imptype // 滤波操作的实现方式 +); + +// Host 函数:_preOp(在算法操作前进行预处理) +// 在滤波操作前,先进行预处理,包括:(1)对输入和输出图像 +// 进行数据准备,包括申请当前Device存储空间;(2)对模板进行处理,包 +// 申请当前Device存储空间 +static __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR +_preOp( + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + Template *tp // 模板 +); + +// Host 函数:_adjustRoiSize(调整 ROI 子图的大小) +// 调整 ROI 子图的大小,使输入和输出的子图大小统一 +static __host__ void // 无返回值 +_adjustRoiSize( + ImageCuda *inimg, // 输入图像 + ImageCuda *outimg // 输出图像 +); + +// Host 函数:_getBlockSize(获取 Block 和 Grid 的尺寸) +// 根据默认的 Block 尺寸,使用最普通的线程划分方法获取 Grid 的尺寸 +static __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR +_getBlockSize( + int width, // 需要处理的宽度 + int height, // 需要处理的高度 + dim3 *gridsize, // 计算获得的 Grid 的尺寸 + dim3 *blocksize // 计算获得的 Block 的尺寸 +); + +// Host 函数:_initDefTemplate(初始化默认的模板指针) +static __host__ TemplateCuda *_initDefTemplate() +{ + // 如果 _defTpl 不为空,说明已经初始化了,则直接返回 + if (_defTpl != NULL) + return _defTpl; + + // 如果 _defTpl 为空,则初始化为大小为3x3,模板值为1的模板 + Template *tmpdef; + TemplateBasicOp::newTemplate(&tmpdef); + TemplateBasicOp::makeAtHost(tmpdef, 9); + _defTpl = TEMPLATE_CUDA(tmpdef); + // 分别处理每一个点 + for (int i = 0; i < 9; i++) { + // 分别计算每一个点的横坐标和纵坐标 + _defTpl->tplMeta.tplData[2 * i] = i % 3 - 1; + _defTpl->tplMeta.tplData[2 * i + 1] = i / 3 - 1; + // 将每个点的模板值设为1 + _defTpl->attachedData[i] = 1; + } + return _defTpl; +} + +// Kernel 函数:_linearFilterKer(实现滤波算法操作) +static __global__ void _linearFilterKer(ImageCuda inimg, ImageCuda outimg, + TemplateCuda tpl, int imptype) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于统一列的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + + // 用来记录当前模板所在的位置的指针 + int *curtplptr = tpl.tplMeta.tplData; + + // 用来记录当前输入图像所在位置的指针 + unsigned char *curinptr; + + // 用来存放模板中像素点的像素值加和 + unsigned int tplsum[4] = { 0, 0, 0, 0 }; + + // 用来记录当前滤波操作的除数 + float tmpcount[4] = { 0, 0, 0, 0 }; + + // 扫描模板范围内的每个输入图像的像素点 + for (int i = 0; i < tpl.tplMeta.count; i++) { + // 计算当前模板位置所在像素的 x 和 y 分量,模板使用相邻的两个下标的 + // 数组表示一个点,所以使当前模板位置的指针作加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 先判断当前像素的 x 分量是否越界,如果越界,则跳过,扫描下一个模板点 + // 如果没有越界,则分别处理当前列的相邻的 4 个像素 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 根据 dx 和 dy 获取第一个像素的位置 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + + // 检测此像素的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 将第一个像素点邻域内点的像素值累加 + tplsum[0] += (*curinptr) * (tpl.attachedData[i]); + + // 针对不同的实现类型,选择不同的路径进行处理 + switch(imptype) + { + // 使用邻域像素总和除以像素点个数的运算方法实现线性滤波 + case LNFT_COUNT_DIV: + // 记录当前像素点邻域内已累加点的个数 + tmpcount[0] += 1; + break; + + // 使用邻域像素总和除以像素点权重之和的运算方法实现线性滤波 + case LNFT_WEIGHT_DIV: + // 记录当前像素点权重之和 + tmpcount[0] += tpl.attachedData[i]; + break; + + // 使用邻域像素直接带权加和的运算方法实现线性滤波 + case LNFT_NO_DIV: + // 设置除数为 1 + tmpcount[0] = 1; + break; + } + } + + // 处理当前列的剩下的 3 个像素 + for (int j = 1; j < 4; j++) { + // 获取当前像素点的位置 + curinptr += inimg.pitchBytes; + + // 使 dy 加一,得到当前要处理的像素的 y 分量 + dy++; + + // 检测 dy 是否越界,如果越界,则跳过,扫描下一个模板点 + // 如果 y 分量未越界,则处理当前像素点 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 将当前像素点邻域内点的像素值累加 + tplsum[j] += (*curinptr) * (tpl.attachedData[i]); + + // 针对不同的实现类型,选择不同的路径进行处理 + switch(imptype) + { + // 使用邻域像素总和除以像素点个数的运算方法实现线性滤波 + case LNFT_COUNT_DIV: + // 记录当前像素点邻域内已累加点的个数 + tmpcount[j] += 1; + break; + + // 使用邻域像素总和除以像素点权重之和的运算方法实现线性滤波 + case LNFT_WEIGHT_DIV: + // 记录当前像素点权重之和 + tmpcount[j] += tpl.attachedData[i]; + break; + + // 使用邻域像素直接带权加和的运算方法实现线性滤波 + case LNFT_NO_DIV: + // 设置除数为 1 + tmpcount[j] = 1; + break; + } + } + } + } + } + + // 将 4 个平均值分别赋值给对应的输出图像 + // 定义输出图像位置的指针 + unsigned char *outptr; + + // 获取对应的第一个输出图像的位置 + outptr = outimg.imgMeta.imgData + dstr * outimg.pitchBytes + dstc; + + // 计算邻域内点的像素平均值并赋值给输出图像 + *outptr = (tplsum[0] / tmpcount[0]); + + // 处理剩下的 3 个点 + for (int i = 1; i < 4; i++) { + // 先判断 y 分量是否越界,如果越界,则可以确定后面的点也会越界,所以 + // 直接返回 + if (++dstr >= outimg.imgMeta.height) + return; + + // 获取当前列的下一行的位置指针 + outptr = outptr + outimg.pitchBytes; + + // 计算邻域内点的像素平均值并赋值给输出图像 + *outptr = (tplsum[i] / tmpcount[i]); + } +} + +// Host 函数:_preOp(在算法操作前进行预处理) +static __host__ int _preOp(Image *inimg, Image *outimg, Template *tp) +{ + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝到 Device 内存中 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 计算 roi 子图的宽和高 + int roiwidth = inimg->roiX2 - inimg->roiX1; + int roiheight = inimg->roiY2 - inimg->roiY1; + // 如果输出图像无数据,则会创建一个和输出图像子图像尺寸相同的图像 + errcode = ImageBasicOp::makeAtCurrentDevice(outimg, roiwidth, + roiheight); + // 如果创建图像依然操作失败,则返回错误 + if (errcode != NO_ERROR) + return errcode; + } + + // 将模板拷贝到 Device 内存中 + errcode = TemplateBasicOp::copyToCurrentDevice(tp); + if (errcode != NO_ERROR) + return errcode; + + return NO_ERROR; +} + +// Host 函数:_adjustRoiSize(调整输入和输出图像的 ROI 的大小) +static __host__ void _adjustRoiSize(ImageCuda *inimg, ImageCuda *outimg) +{ + // 如果输入图像宽度大于输出图像,则将输出图像宽度值赋给输入图像, + // 否则将输入图像宽度值赋给输出图像 + if (inimg->imgMeta.width > outimg->imgMeta.width) + inimg->imgMeta.width = outimg->imgMeta.width; + else + outimg->imgMeta.width = inimg->imgMeta.width; + + // 如果输入图像高度大于输出图像,则将输出图像高度值赋给输入图像, + // 否则将输入图像高度值赋给输出图像 + if (inimg->imgMeta.height > outimg->imgMeta.height) + inimg->imgMeta.height = outimg->imgMeta.height; + else + outimg->imgMeta.height = inimg->imgMeta.height; +} + +// Host 函数:_getBlockSize(获取 Block 和 Grid 的尺寸) +static __host__ int _getBlockSize(int width, int height, dim3 *gridsize, + dim3 *blocksize) +{ + // 检测 girdsize 和 blocksize 是否是空指针 + if (gridsize == NULL || blocksize == NULL) + return NULL_POINTER; + + // blocksize 使用默认的尺寸 + blocksize->x = DEF_BLOCK_X; + blocksize->y = DEF_BLOCK_Y; + + // 使用最普通的方法划分 Grid + gridsize->x = (width + blocksize->x - 1) / blocksize->x; + gridsize->y = (height + blocksize->y * 4 - 1) / (blocksize->y * 4); + + return NO_ERROR; +} + +// 构造函数:LinearFilter +__host__ LinearFilter::LinearFilter(int imptype, Template *tp) +{ + // 设置滤波操作的实现方式 + setImpType(imptype); + + // 设置滤波操作所要使用的模板 + setTemplate(tp); +} + +// 成员方法:getImpType +__host__ int LinearFilter::getImpType() const +{ + // 返回 impType 成员变量的值 + return this->impType; +} + +// 成员方法:setImpType +__host__ int LinearFilter::setImpType(int imptype) +{ + // 检查输入参数是否合法 + if (imptype != LNFT_COUNT_DIV && imptype != LNFT_WEIGHT_DIV && + imptype != LNFT_NO_DIV) + return INVALID_DATA; + + // 将 impType 成员变量赋成新值 + this->impType = imptype; + return NO_ERROR; +} + +// 成员方法:getTemplate +__host__ Template *LinearFilter::getTemplate() const +{ + // 如果模板指针和默认模板指针相同,则返回空 + if (this->tpl == &(_defTpl->tplMeta)) + return NULL; + + // 否则返回设置的模板指针 + return this->tpl; +} + +// 成员方法:setTemplate +__host__ int LinearFilter::setTemplate(Template *tp) +{ + // 如果 tp 为空,则只用默认的模板指针,否则将 tp 赋值给 tpl + if (tp == NULL) { + this->tpl = &(_initDefTemplate()->tplMeta); + } else { + this->tpl = tp; + } + return NO_ERROR; +} + +// 成员方法:linearFilter +__host__ int LinearFilter::linearFilter(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + dim3 gridsize; + dim3 blocksize; + + // 检查输入图像,输出图像,以及模板是否为空 + if (inimg == NULL || outimg == NULL || tpl == NULL) + return NULL_POINTER; + + // 对输入图像,输出图像和模板进行预处理 + errcode = _preOp(inimg, outimg, tpl); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入和输出图像的 ROI 子图,使大小统一 + _adjustRoiSize(&insubimgCud, &outsubimgCud); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + errcode = _getBlockSize(outsubimgCud.imgMeta.width, + outsubimgCud.imgMeta.height, + &gridsize, &blocksize); + if (errcode != NO_ERROR) + return errcode; + + // 检查滤波实现方式是否为合法值 + if (impType != LNFT_COUNT_DIV && impType != LNFT_WEIGHT_DIV && + impType != LNFT_NO_DIV) + return INVALID_DATA; + + // 调用 Kernel 函数进行均值滤波操作 + _linearFilterKer<<>>(insubimgCud, + outsubimgCud, + *TEMPLATE_CUDA(tpl), + impType); + // 调用 cudaGetLastError 判断程序是否出错 + cudaError_t err; + err = cudaGetLastError(); + if (err != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出 + return errcode; +} + diff --git a/okano_3_0/LinearFilter.h b/okano_3_0/LinearFilter.h new file mode 100644 index 0000000..b22568c --- /dev/null +++ b/okano_3_0/LinearFilter.h @@ -0,0 +1,111 @@ +//LinearFilter.h +// 创建人:王雪菲 +// +// 线性滤波滤波(LinearFilter) +// 功能说明:根据给定模板,对邻域像素进行卷积运算,实现图像的线性滤波操作 +// +// 修订历史: +// 2012年12月06日(王雪菲) +// 初始版本。 +// 2012年12月12日(王雪菲) +// 规范了代码,修改了程序中的越界错误 +// 2012年12月13日(王雪菲) +// 规范了代码,增加了对函数运行结果的检查,完成了部分计算简化 +// 2012年12月14日(王雪菲) +// 增加了函数的中文名称,规范了代码 +// 2012年12月17日(王雪菲) +// 增加了滤波操作的两种实现方式 +// 2012年12月18日(王雪菲) +// 改正了一处数据类型错误 + +#ifndef __LinearFilter_H__ +#define __LinearFilter_H__ + +#include "Image.h" +#include "Template.h" + + +// 宏:LNFT_COUNT_DIV +// 用于设置 LinearFilter 类中的 impType 成员变量,告知类的实例选用邻域像素总和 +// 除以像素点个数的运算方法实现线性滤波 +#define LNFT_COUNT_DIV 1 + +// 宏:LNFT_WEIGHT_DIV +// 用于设置 LinearFilter 类中的 impType 成员变量,告知类的实例选用邻域像素总和 +// 除以像素点权重之和的运算方法实现线性滤波 +#define LNFT_WEIGHT_DIV 2 + +// 宏:LNFT_NO_DIV +// 用于设置 LinearFilter 类中的 impType 成员变量,告知类的实例选用邻域像素 +// 直接带权加和的运算方法实现线性滤波 +#define LNFT_NO_DIV 3 + + +// 类:LinearFilter(线性滤波算法) +// 继承自:无 +// 根据给定模板,对邻域像素进行卷积运算,实现图像的线性滤波操作 +class LinearFilter { + +protected: + + // 成员变量:impType(实现类型) + // 设定三种实现类型中的一种,在调用滤波函数的时候,使用对应的实现方式 + int impType; + + // 成员变量:tpl(模板指针) + // 在滤波操作中需要通过它来指定图像中要处理的像素范围和滤波模板值 + Template *tpl; + +public: + + // 构造函数:LinearFilter + // 根据需要给定各个参数,若不传参数, + // 默认滤波实现方式为除以像素点个数,默认模板为空 + __host__ + LinearFilter( + int imptype = LNFT_COUNT_DIV, // 线性滤波实现类型, + // 默认为除以像素点个数 + Template *tp = NULL // 线性滤波操作需要使用 + // 到模板,默认为空 + ); + + // 成员方法:getImpType(读取实现类型) + // 获取实现滤波操作的类型 + + __host__ int // 返回值:当前实现滤波操作的类型 + getImpType() const; + + // 成员方法:setImpType(设置实现类型) + // 设置实现滤波操作的类型 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR + setImpType( + int imptype // 新的滤波操作实现类型 + ); + + // 成员方法:getTemplate(读取模板) + // 获取模板指针,如果模板指针和默认模板指针相同,则返回空 + + __host__ Template * // 返回值:如果模板和默认模板指针相同,则返 + // 回空,否则返回模板指针 + getTemplate() const; + + // 成员方法:setTemplate(设置模板) + // 设置模板指针,如果参数 tp 为空,则使用默认的模板 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR + setTemplate( + Template *tp // 滤波操作需要使用的模板 + ); + + // 成员方法:linearFilter(执行线性滤波) + // 对图像进行线性滤波操作,根据模板,计算出当前点的像素值并输出 + __host__ int // 返回值:函数正确执行,返回 NO_ERROR, + // 否则返回相应的错误码 + linearFilter( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + +}; + +#endif + diff --git a/okano_3_0/LocalCluster.cu b/okano_3_0/LocalCluster.cu new file mode 100644 index 0000000..2fa524a --- /dev/null +++ b/okano_3_0/LocalCluster.cu @@ -0,0 +1,319 @@ +// LocalCluster.cu +// 局部聚类 + +#include "LocalCluster.h" +#include "Image.h" +#include "ErrorCode.h" + +#include +#include +#include "stdio.h" +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y 以及 DEF_BLOCK_Z +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 1 +#define DEF_BLOCK_Z 8 + +// Device 全局常量: +// 用于计算点之间的坐标关系。 +const int static __device__ _idxDev[8][2] = { + // [0][ ], [1][ ] + { 1, 0}, { 1, -1}, + // [2][ ], [3][ ] + { 0, -1}, {-1, -1}, + // [4][ ], [5][ ] + {-1, 0}, {-1, 1}, + // [6][ ], [7][ ] + { 0, 1}, { 1, 1} +}; + +// Host 函数:_adjustRoiSize(调整 ROI 子图的大小) +// 调整 ROI 子图的大小,使输入和输出的子图大小统一。 +static __host__ void // 无返回值 +_adjustRoiSize( + ImageCuda *inimg, // 输入图像 + ImageCuda *outimg // 输出图像 +); + +// Kernel 函数:_localCluKer(对每一个点进行局部聚类处理) +// 给定一张图像略去边缘部分,在每一个点的八个方向上各求出 pntRange +// 个点的像素平均值并存放在共享变量 temp 中(根据河边老师发来的串行 +// 实现代码,pntRange 不能超过 100)。通过各个方向平均值与当前 +// 像素值做差,从差值中选取满足条件的 pntCount 个点求平均值(根据 +// 河边老师发来的串行实现代码,pntCount 不能超过 8),将该平均值 +// 与当前像素值相加得出点的新像素值。 +static __global__ void // Kernel 函数无返回值 +_localCluKer( + ImageCuda inimg, // 待处理图像 + ImageCuda outimg, // 输出图像 + const int pntrange, // 在当前像素点的八个方向上, + // 每个方向上取得点的个数, + // 据河边老师发来的串行实现代码, + // 不超过 100。 + unsigned char gapthred, // 当前像素点和相邻点的灰度差 + // 的阈值。 + unsigned char diffethred, // 当前点两侧各两个点的像素和 + // 的差的阈值。 + unsigned char problack, // 像素值,默认为 0 + unsigned char prowhite, // 像素值,默认为 250 + int pntCount, // 利用循环在 temp 数组中寻找 + // 最接近 0 的值循环次数的上界, + // 据河边老师发来的串行实现代码, + // 不超过 8。 + int sx, // 处理边界,横坐标小于该值的点保留原值 + int ex, // 处理边界,横坐标大于该值的点保留原值 + int sy, // 处理边界,纵坐标小于该值的点保留原值 + int ey // 处理边界,纵坐标大于该值的点保留原值 +); + +// Kernel 函数:_localCluKer(对每一个点进行局部聚类处理) +static __global__ void _localCluKer( + ImageCuda inimg, ImageCuda outimg, const int pntrange, + unsigned char gapthred, unsigned char diffethred, + unsigned char problack, unsigned char prowhite, + int pntcount, int sx, int ex, int sy, int ey) +{ + // 声明一个 extern 共享数组,存放每一个点的八个方向上的平均值。 + extern __shared__ float temp[]; + + // 计算线程对应像素点的坐标位置,坐标的 x 和 y 分量。 + // z 表示计算方向 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + int z = threadIdx.z; + + // 计算当前线程需要用到的共享内存地址 + int idx = (threadIdx.y * blockDim.x + threadIdx.x) * 8 + z; + float *curtemp = temp + idx; + + // 检查第像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + + if (x >= inimg.imgMeta.width || y >= inimg.imgMeta.height) + return; + + // 当前线程对应的像素点一维索引和像素值 + int idxcv; + unsigned char cv; + idxcv = y * inimg.pitchBytes + x; + cv = inimg.imgMeta.imgData[idxcv]; + + // 对非边缘部分的点进行计算 + // 对于黑白比较明显的点保留原值 + // 图像边缘上的点保持原像素值 + if (x < sx || x > ex || y < sy || y > ey || + cv <= problack || cv >= prowhite) { + outimg.imgMeta.imgData[idxcv] = cv; + return; + } + + // 正在处理点像素值和索引 + // 注:正在处理点于当前点不同,处理点是当前计算点某个方向上的点。 + unsigned char cuv; + int idxcuv = idxcv; + + // 处理点两侧各取两个点的像素和 + float side1, side2; + + // sum 用于累计 pntrange 个点的图像值 + // flag 为标记,标记循环次数 + float sum = cv; + int i = 0; + + // 计算中涉及到的点的坐标 + int dx = x, dy = y; + + // 取出第一个点的像素值 + idxcuv = (y + _idxDev[z][1]) * inimg.pitchBytes + + (x + _idxDev[z][0]); + cuv = inimg.imgMeta.imgData[idxcuv]; + + // 在具体的某一个处理方向,处理的第一个点的左侧两个点像素值 + unsigned char pre1, pre2; + pre1 = inimg.imgMeta.imgData[(y - _idxDev[z][1]) * inimg.pitchBytes + + (x - _idxDev[z][0])]; + pre2 = cv; + + // 在具体的某一个处理方向,处理的第一个点的右侧两个点像素值 + unsigned char latt1, latt2; + latt1 = inimg.imgMeta.imgData[_idxDev[z][0] + idxcuv + + _idxDev[z][1] * inimg.pitchBytes]; + + // 每一个线程循环处理某一个方向 pntrange 个点 + for (i = 1; i < pntrange; i++) { + + // 正在处理点和计算点的做差,如果超过 gapthred, + // 则停止在该该方向上的累加,即跳出循环。 + if (abs((float)cv -(float) cuv) > gapthred) + break; + + // 两侧各取两个点: + // 在具体的某一个处理方向,正在处理点的右侧第二个点的坐标, + // 计算该点的索引,并取出该值 + dx += _idxDev[z][0] * 2; + dy += _idxDev[z][1] * 2; + int idxsid; + idxsid = dy * inimg.pitchBytes + dx; + + // 取出该方向上右侧第二个点的像素值并把每侧的两个值相加 + latt2 = inimg.imgMeta.imgData[idxsid]; + side1 = pre1 + pre2; + side2 = latt1 + latt2; + + // side1 与 side2 做差,如果超过 diffethred, + // 则停止在该该方向上的累加,即跳出循环。 + if (abs(side1 - side2) > diffethred) + break; + + // 更新 pre1, pre2, cuv, latt1 为计算下个点做准备 + // 在计算下一个点时,它的当前值变为 latt1, + // 其他三个变量也顺势向右移动一个点 + pre1 = pre2; + pre2 = cuv; + cuv = latt1; + latt1 = latt2; + + // 将满足条件的像素值累加到 sum 中 + sum += (float)cuv; + } + + // 当前计算方向上的像素平均值 + *curtemp = sum / i; + + // 设置线程块里面的线程同步 + __syncthreads(); + + // 对每一个点接下来的处理只需要用一个线程来处理, + // 此时我们选择 z 等于 0 来处理,因此对 z 不等于 + // 0 的线程 return。 + if (z != 0) + return; + + float sumag = 0.0f; + curtemp = &(temp[(threadIdx.y * blockDim.x + threadIdx.x) * 8]); + + // 处理 temp,寻找 pntCount 个较小值(即最小,次小,以此类推), + // 此时,该算法并未采取排序,而是当找到一个最大值时,并将该值重新 + // 设为 cv(作为哑值处理),再接着找次大值,并也设为 0,直到找完 + // 8 - pntCount 个次大值,然后再将数组里面的所有值求平均值。 + int mark = 0; + for (int j = 8 - pntcount; j > 0; j--) { + for (int i = 0; i < 8; i++) { + if (abs(curtemp[i] - cv) >= abs(curtemp[mark] - cv) ) + mark = i; + } + curtemp[mark] = cv; + } + + for (int j = 0; j < 8; j ++) + sumag += curtemp[j]; + + // 因为在处理时设定的哑值为 cv(即多加了 8 - pntcount 个 cv) , + // 故此处需要将改值减去 + sumag -= (8 - pntcount) * cv; + + // 防止累加值越界并将结果写入输出图片 + outimg.imgMeta.imgData[idxcv] = + (sumag / pntcount > 255) ? 255 : + (unsigned char)(sumag / pntcount); +} + +// Host 函数:_adjustRoiSize(调整输入和输出图像的 ROI 的大小) +inline static __host__ void _adjustRoiSize(ImageCuda *inimg, + ImageCuda *outimg) +{ + if (inimg->imgMeta.width > outimg->imgMeta.width) + inimg->imgMeta.width = outimg->imgMeta.width; + else + outimg->imgMeta.width = inimg->imgMeta.width; + + if (inimg->imgMeta.height > outimg->imgMeta.height) + inimg->imgMeta.height = outimg->imgMeta.height; + else + outimg->imgMeta.height = inimg->imgMeta.height; +} + +// Host 成员方法: localCluster(局部聚类) +__host__ int LocalCluster::localCluster(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL + if (inimg == NULL || inimg->imgData == NULL) + return NULL_POINTER; + + // 输入图像的 ROI 区域尺寸 + int imgroix = inimg->roiX2 - inimg->roiX1; + int imgroiy = inimg->roiY2 - inimg->roiY1; + + // 将输入图像复制到 device + int errcode; + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将 outimg 复制到 device + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和 + // 输入图像尺寸相同的图像 + if (errcode != NO_ERROR) { + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, imgroix, imgroiy); + // 如果创建图像也操作失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入和输出图像的 ROI 子图,使大小统一 + _adjustRoiSize(&insubimgCud, &outsubimgCud); + + // 为 kernel 分配线程 + dim3 blockdim; + dim3 griddim; + blockdim.x = DEF_BLOCK_X; + blockdim.y = DEF_BLOCK_Y; + blockdim.z = DEF_BLOCK_Z; + griddim.x = (insubimgCud.imgMeta.width + blockdim.x - 1) / + blockdim.x; + griddim.y = (insubimgCud.imgMeta.height + blockdim.y - 1) / + blockdim.y; + griddim.z = 1; + + // 计算处理边界,处理的点如果不在这四个值的包围范围内部,则保留原值 + int sx, ex, sy, ey; + sx = this->pntRange + 2; + ex = insubimgCud.imgMeta.width - this->pntRange - 2; + sy = this->pntRange + 2; + ey = insubimgCud.imgMeta.height - this->pntRange - 2; + + // 计算共享内存大小 + int size = DEF_BLOCK_X * DEF_BLOCK_Y * + DEF_BLOCK_Z * sizeof (float); + + // 调用 kernel + _localCluKer<<>>( + insubimgCud,outsubimgCud, + this->pntRange, this->gapThred, this->diffeThred, + this->proBlack, this->proWhite, this->pntCount, + sx, ex, sy, ey); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/LocalCluster.h b/okano_3_0/LocalCluster.h new file mode 100644 index 0000000..36e52ef --- /dev/null +++ b/okano_3_0/LocalCluster.h @@ -0,0 +1,297 @@ +// LocalCluster.h +// 创建人:刘婷 +// +// 局部聚类(Local Cluster) +// 功能说明:给定一张图像略去图像的边缘部分,在每点的八个方向上各 +// 求出 pntRange 个点的像素平均值(根据河边老师发来的 +// 串行实现代码,pntRange 不能超过 100),利用这些 +// 平均值与当前点的图像值做差存放在临时变量(temp)中, +// 再在这个数组中选取最接近 0 的 pntCount 个值求出其 +// 平均值(根据河边老师发来的串行实现代码,pntCount +// 不能超过 8),将该平均值与当前像素值相加最终得出点的 +// 新像素值。 +// +// 修订历史: +// 2012年10月15日(刘婷) +// 进行类设计 +// 2012年10月16日(刘婷) +// 函数的初步实现 +// 2012年10月28日 (于玉龙,刘瑶,刘婷) +// 修改了代码中的一些错误 +// 2012年10月29日(刘婷) +// 修改了代码中的一些错误 +// 2012年11月03日(于玉龙,刘婷) +// 修改代码格式 +// 2012年11月04日(于玉龙,刘婷) +// 修改代码格式 +// 2012年11月14日(刘婷) +// 修改了代码并合并了 kernel +// 2012年12月02日(于玉龙,刘婷) +// 调整代码格式 +// 2012年12月04日(刘婷) +// 更新了 kernel,减少了一些重复计算 +// 2012年12月12日(刘婷) +// 更改了对 temp 数组的操作 +// 2012年12月24日(刘婷) +// 更改了处理 temp 的一处 bug +// 2012年12月25日(刘婷) +// 更改了处理 temp 的一处 bug + +#ifndef __LOCALCLUSTER__H +#define __LOCALCLUSTER__H + +#include "Image.h" +#include "ErrorCode.h" + +// 类:LocalCluster +// 继承自:无 +// 给定一张图像略去图像的边缘部分,在每一个点的八个方向上各求出 +// pntRange 个点的像素平均值(根据河边老师发来的串行实现代码,pntRange +// 不能超过 100),利用这些平均值与当前点的图像值做差存放在一个临时变量 +// temp 中,再在这个数组中选取最接近 0 的 pntCount 个值求出其平均值 +// (根据河边老师发来的串行实现代码,pntCount 不能超过 8),将该平均值 +// 与当前像素值相加,最终得出点的新像素值。 +class LocalCluster { + +protected: + + // 成员变量:gapThred + // 当前像素点和相邻点的灰度差的阈值 + unsigned char gapThred; + + // 成员变量:diffeThred + // 正在计算点的两侧,每侧取两个紧邻当前点的像素点相加 + // 得到 side1 和 side2, diffThred 是其差的阈值。 + unsigned char diffeThred; + + // 成员变量:proBlack, proWhite + // 具有显著性的点的像素值划分上界和下界 + // 像素值,其中 proWhite 默认为 250, proBlack 默认为 10。 + unsigned char proWhite, proBlack; + + // 成员变量:pntRange + // 在当前像素点的八个方向上,每个方向上取得点的个数, + // 根据河边老师发来的串行实现代码,不超过 100。 + int pntRange; + + // 成员变量:pntCount + // 利用循环在 temp 中寻找最接近 0 的值时,循环次数的上界, + // 根据河边老师发来的串行实现代码,不超过 8。 + int pntCount; + +public: + + // 构造函数:LocalCluster + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + LocalCluster() + { + // 使用默认值为类的各个成员变量赋值。 + this->diffeThred = 40; // 正在计算点的两侧差的阈值 + this->gapThred = 40; // 当前像素点和相邻点的灰度差的阈值 + this->pntCount = 7; // 循环次数的上界, + // 不超过 8。 + this->pntRange = 16; // 每个方向上取得点的个数, + // 不超过 100。 + this->proBlack = 10; // 具有显著性的点的像素值划分上界 + this->proWhite = 250; // 具有显著性的点的像素值划分下界 + } + + // 构造函数:LocalCluster + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 可以改变的。 + // proBlack 和 proWhite 采用默认为 10 和 250,用户可以不输入 + // 这两个值而调用默认值。 + __host__ __device__ + LocalCluster( + unsigned char gapthred, // 解释见成员变量 + unsigned char diffethred, // 解释见成员变量 + int pntrange, // 解释见成员变量 + int pntcount, // 解释见成员变量 + unsigned char problack = 10, // 像素值,默认为 10 + unsigned char prowhite = 250 // 像素值,默认为 250 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给 + // 了非法的初始值而使系统进入一个未知的状态。 + this->diffeThred = 10; // 正在计算点的两侧差的阈值 + this->gapThred = 10; // 当前像素点和相邻点的灰度差的阈值 + this->pntCount = 5; // 循环次数的上界 + this->pntRange = 8; // 每个方向上取得点的个数 + this->proBlack = 10; // 具有显著性的点的像素值划分上界 + this->proWhite = 250; // 具有显著性的点的像素值划分下界 + + // 根据参数列表中的值设定成员变量的初值 + this->setGapThred(gapthred); + this->setDiffeThred(diffethred); + this->setPntRange(pntrange); + this->setPntCount(pntcount); + this->setProBlack(problack); + this->setProWhite(prowhite); + } + + // 成员方法:getGapThred(读取当前像素点和相邻点的灰度差的阈值) + // 读取 gapThred 成员变量的值 + __host__ __device__ unsigned char // 返回值: + // gapThred 成员变量的值。 + getGapThred() const + { + // 返回 gapThred 成员变量的值。 + return this->gapThred; + } + + // 成员方法:setGapThred(设置当前像素点和相邻点的灰度差的阈值) + // 设置 gapThred 成员变量的值 + __host__ __device__ int // 返回值:函数是否正确执行,若 + // 函数正确执行,返回 NO_ERROR。 + setGapThred( + unsigned char gapthred // 新 gapThred 值 + ) { + // 将 gapThred 成员变量赋成新值 + this->gapThred = gapthred; + + return NO_ERROR; + } + + // 成员方法:getDiffeThred(读取正在计算点两侧差的阈值) + // 读取 gapThred 成员变量的值 + __host__ __device__ unsigned char // 返回值:当前 diffeThred + // 成员变量的值。 + getDiffeThred() const + { + // 返回 diffeTred 成员变量的值。 + return this->diffeThred; + } + + // 成员方法:setDiffeThred(设置正在计算点两侧差的阈值) + // 设置 gapThred 成员变量的值 + __host__ __device__ int // 返回值:函数是否正确执行, + // 若函数正确执行,返回 NO_ERROR。 + setDiffeThred( + unsigned char diffethred // 新 diffeThred 值 + ) { + // 将 diffeThred 成员变量赋成新值 + this->diffeThred = diffethred; + + return NO_ERROR; + } + + // 成员方法:getProBlack(读取具有显著性的点的像素值划分上界) + // 读取 proBlack 成员变量的值 + __host__ __device__ unsigned char // 返回值:当前 proBlack + // 成员变量的值。 + getProBlack() const + { + // 返回 proBlack 成员变量的值。 + return this->proBlack; + } + + // 成员方法:setProBlack(设置具有显著性的点的像素值划分上界) + // 设置 proBlack 成员变量的值 + __host__ __device__ int // 返回值:函数是否正确执行,若 + // 函数正确执行,返回 NO_ERROR。 + setProBlack( + unsigned char problack // 新 proBlack 值 + ) { + // 将 proBlack 成员变量赋成新值 + this->proBlack = problack; + + return NO_ERROR; + } + + // 成员方法:getProWhite(读取具有显著性的点的像素值划分下界) + // 读取 proWhite 成员变量的值 + __host__ __device__ unsigned char // 返回值:当前 proWhite + // 成员变量的值。 + getProWhite() const + { + // 返回 proWhite 成员变量的值。 + return this->proWhite; + } + + // 成员方法:setProWhite(设置具有显著性的点的像素值划分界下) + // 设置 proWhite 成员变量的值 + __host__ __device__ int // 返回值:函数是否正确执行,若 + // 函数正确执行,返回 NO_ERROR。 + setProWhite( + unsigned char prowhite // 新 proWhite 值 + ) { + + // 将 proWhite 成员变量赋成新值 + this->proWhite = prowhite; + + return NO_ERROR; + } + + // 成员方法:getPntRange(读取每个方向上取得点的个数) + // 读取 pntRange 成员变量的值 + __host__ __device__ unsigned char // 返回值:当前 pntRange + // 成员变量的值。 + getPntRange() const + { + // 返回 pntRange 成员变量的值。 + return this->pntRange; + } + + // 成员方法:setPntRange(设置每个方向上取得点的个数) + // 设置 pntRange 成员变量的值 + __host__ __device__ int // 返回值:函数是否正确执行,若 + // 函数正确执行,返回 NO_ERROR + setPntRange( + unsigned char pntrange // 新 pntRange 值 + // 根据河边老师发来的串行实现代码, + // pntRange 不超过 100。 + ) { + // 判断参数是否合法 + if (pntrange > 100) + return INVALID_DATA; + + // 将 pntRange 成员变量赋成新值 + this->pntRange = pntrange; + + return NO_ERROR; + } + + // 成员方法:getPntCount(读取循环次数的上界) + // 读取 pntCount 成员变量的值 + __host__ __device__ unsigned char // 返回值:当前 pntCount + // 成员变量的值。 + getPntCount() const + { + // 返回 pntCount 成员变量的值。 + return this->pntCount; + } + + // 成员方法:setPntCount(设置循环次数的上界) + // 设置 pntCount 成员变量的值 + __host__ __device__ int // 返回值:函数是否正确执行,若 + // 函数正确执行,返回 NO_ERROR + setPntCount( + unsigned char pntcount // 新 pntCount 值 + // 根据河边老师发来的串行实现代码, + // pntCount 不超过 100。 + ) { + // 判断参数是否合法 + if (pntcount > 8) + return INVALID_DATA; + + // 将 pntCount 成员变量赋成新值 + this->pntCount = pntcount; + + return NO_ERROR; + } + + // 成员方法:localCluster(局部聚类) + // 算法的主函数 + // 略去图像的边缘部分,对输入图像的每一个点并行处理,最终得到输出图像 + // 在每一个点的八个方向上各求出 pntRange 个点的像素平均值,利用这些 + // 平均值与当前点的图像值做运算,最终得出点的新像素值. + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + localCluster( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/LocalHistogramEqualization.cu b/okano_3_0/LocalHistogramEqualization.cu new file mode 100644 index 0000000..0a89860 --- /dev/null +++ b/okano_3_0/LocalHistogramEqualization.cu @@ -0,0 +1,484 @@ +// LocalHistogramEqualization.cu +// 对指定图像进行直方图均衡化处理。 + +#include +#include +#include "LocalHistogramEqualization.h" +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 定义图像灰度值个数 +#define HISTO_NUM 256 + + +// Kernel 函数:_hisEqualHistoKer(并行实现所有窗口的直方图统计) +// 根据分割数将图像分割成窗口后,对每个窗口进行直方图统计工作。 +static __global__ void +_hisEqualHistoKer( + ImageCuda inimg, // 输入图像 + int *his, // 各个窗口的直方图 + int ww, // 窗口宽度 + int wh, // 窗口长度 + int blockperwinw, // 窗每个窗口横向对应的线程块数目 + int blockperwinh // 窗每个窗口纵向对应的线程块数目 +); + +// Kernel 函数:_hisEqualKer(并行实现归一化直方图的计算) +// 各个窗口的直方图统计完成后,对每个直方图进行归一化工作。 +static __global__ void +_hisEqualKer( + int *his, // 各个窗口的直方图 + float *norhis, // 各个窗口归一化直方图 + int *max, // 各个窗口最大灰度 + int *min, // 各个窗口最小灰度 + int total // 窗口总像素点数 +); + +// Kernel 函数:_hisEqualLastKer(实现直方图均衡化操作) +// 根据归一化直方图和原始图像,计算cumucounter和cumugray。 +static __global__ void +_hisEqualLastKer( + ImageCuda inimg, // 输入图像 + float *norhis, // 各个窗口归一化直方图 + int *max, // 各个窗口最大灰度 + int *min, // 各个窗口最小灰度 + int ww, // 窗口宽度 + int wh, // 窗口长度 + int blockperwinw, // 窗每个窗口横向对应的线程块数目 + int blockperwinh, // 窗每个窗口纵向对应的线程块数目 + int *cumucounter, // 窗口重叠数 + int *cumugray // 均衡化结果 +); + + +// Kernel 函数:_hisEqualSecKer(实现将各窗口重新整合成输出图像) +// 各个窗口直方图均衡化完成后,此函数将根据所有窗口的均衡化结果重新整合成输出 +// 图像。 +static __global__ void +_hisEqualSecKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + int *cumucounter, // 窗口重叠数量 + int *cumugray, // 均衡化结果 + unsigned char t0, // 外部参数 + float c1, // 外部参数 + float c2, // 外部参数 + float weight // 外部参数 +); + +// Kernel 函数:_hisEqualHistoKer(并行实现所有窗口的直方图统计) +static __global__ void _hisEqualHistoKer(ImageCuda inimg, int *his, + int ww, int wh, + int blockperwinw, int blockperwinh) +{ + // 申请大小为灰度图像灰度级 256 的共享内存,其中下标代表图像的灰度值,数 + // 组用来累加等于该灰度值的像素点个数。 + __shared__ unsigned int temp[HISTO_NUM]; + + // 标记当前线程块对应的窗口的横纵索引以及总索引数 + __shared__ int winnumx, winnumy, winidx; + + // dstc 和 dstr 分别表示线程坐标的 x 和 y 分量(其中,c 表示 + // column,r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 计算该线程在块内的相对位置。 + int inindex = threadIdx.y * blockDim.x + threadIdx.x; + + // 每块的第一个线程负责计算当前线程块对应的窗口的横纵索引以及总索引数。 + if(inindex == 0) + { + // 当前线程块对应的窗口的横纵索引。 + winnumx = blockIdx.x / blockperwinw ; + winnumy = blockIdx.y / blockperwinh ; + // 窗口索引 + winidx = (gridDim.x / blockperwinw) * winnumy + winnumx; + + } + + // 若线程在块内的相对位置小于 256,即灰度级大小,则用来给共享内存赋初值 0。 + if (inindex < HISTO_NUM) + temp[inindex] = 0; + + // 进行块内同步,保证执行到此处,共享内存的数组中所有元素的值都为 0。 + __syncthreads(); + + // 计算窗口内相对坐标。 + int inidxc = dstc - winnumx * blockDim.x * blockperwinw; + int inidxr = dstr - winnumy * blockDim.y * blockperwinh; + + // 计算线程对应于图像数据的坐标。 + int inidx = (winnumx * ww / 2 + inidxc) + inimg.pitchBytes * + (winnumy * wh / 2 + inidxr); + + // 输入坐标点对应的像素值。 + int curgray ; + + // 检查像素点是否越界,如果不越界,则进行统计。 + if(inidxc < ww && inidxr < wh){ + curgray = inimg.imgMeta.imgData[inidx]; + // 使用原子操作实现+1操作,可以防止多个线程同时更改数据而发生的写错误。 + // 灰度值统计数组对应数目+1 + atomicAdd(&temp[curgray], 1); + } + + // 块内同步。此处保证图像中所有点的像素值都被统计过。 + __syncthreads(); + + // 用每一个块内前 256 个线程,将共享内存 temp 中的结果保存到输出数组中。 + // 每个窗口直方图对应his的一段 + if (inindex < HISTO_NUM && temp[inindex] != 0) + atomicAdd(&his[inindex + winidx * HISTO_NUM], temp[inindex]); + +} + +// Kernel 函数:_hisEqualKer(并行实现归一化直方图的计算) +static __global__ void _hisEqualKer(int *his, float *norhis, + int *max, int *min, int total) +{ + // 计算当前线程块的索引,即对应的窗口索引。 + int winindx = blockIdx.y * gridDim.x + blockIdx.x; + + // 计算当前线程在块内的索引。 + int inindex = threadIdx.y * blockDim.x + threadIdx.x; + + // 计算当前线程块负责处理的直方图的起始下标 + int inxstart = winindx * HISTO_NUM; + + // 统计各个灰度值出现的概率 + // 起始下标加上块内索引即为对应直方图数组的下标。 + norhis[inindex + inxstart] = 1.0 * his [inindex + inxstart] / total; + + // 线程同步,保证概率统计完毕 + __syncthreads(); + + // 块内第一个线程计算累计归一化直方图, + if(inindex == 0){ + // 数组各个位置存在计算先后顺序关系,故串行实现。 + // 起始下标加上索引i即为对应直方图数组的下标。 + for (int i = 1;i < HISTO_NUM;i++){ + norhis[i + inxstart] += norhis[i - 1 + inxstart] ; + } + } + + // 块内第2个线程查找最大最小灰度值max ,min + // 由于对于有序短数组并行查找最值效率不高,故对于每个直方图分别直接串行计算 + // 起始下标加上索引i即为对应直方图数组的下标。 + if(inindex == 1){ + // 第一个不为零的位置即为最小灰度值 + for (int i = 0; i < HISTO_NUM; i++) { + if(his[i + inxstart] != 0) { + min[winindx] = i; + break; + } + } + + // 最后一个不为零的位置即为最大灰度值 + for (int i = HISTO_NUM - 1; i >= 0; i--) { + if (his[i + inxstart] != 0) { + max[winindx] = i; + break; + } + } + + } + +} + +// Kernel 函数:_hisEqualLastKer(实现直方图均衡化操作) + static __global__ void _hisEqualLastKer(ImageCuda inimg, float *norhis, + int *max, int *min, int ww, int wh, + int blockperwinw, int blockperwinh, + int *cumucounter, int *cumugray ) +{ + // 标记当前线程块对应的窗口的横纵索引以及总索引数 + int winnumx, winnumy, winidx; + // 计算当前线程块对应的窗口的横纵索引以及总索引数。 + winnumx = blockIdx.x / blockperwinw ; + winnumy = blockIdx.y / blockperwinh ; + // 窗口索引 + winidx = (gridDim.x / blockperwinw) * winnumy + winnumx; + + // dstc 和 dstr 分别表示线程坐标的 x 和 y 分量 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 计算窗口内相对坐标。 + int inidxc = dstc - winnumx * blockDim.x * blockperwinw; + int inidxr = dstr - winnumy * blockDim.y * blockperwinh; + + // 检查像素点是否越界,如果不越界,则进行统计。 + if(inidxc < ww && inidxr < wh){ + + // 计算线程对应于图像数据的坐标。 + int inidx = (winnumx * ww / 2 + inidxc) + inimg.pitchBytes * + (winnumy * wh / 2 + inidxr); + + // 计算与图像同维度的数组( cumugray 和 cumucounter )的对应下标 。 + int inidx2 = (winnumx * ww / 2 + inidxc) + inimg.imgMeta.width * + (winnumy * wh / 2 + inidxr); + + // 线程对应的直方图的起始下标,即对应的窗口索引乘以256 + int inxstart = winidx * HISTO_NUM; + + // 计算最大最小灰度值之差 + int sub = max[winidx]- min[winidx]; + + // 将均衡化结果累加于 cumugray 数组,同时 cumucounter++ + // 通过原子操作完成累加,避免内存写竞争引起的错误 + atomicAdd(&cumugray[inidx2], norhis[inimg.imgMeta.imgData[inidx] + + inxstart] * sub + min[winidx]); + atomicAdd(&cumucounter[inidx2], 1); + } + +} + +// Kernel 函数:_hisEqualSecKer(实现将各窗口计算结果重新整合成输出图像) +static __global__ void _hisEqualSecKer(ImageCuda inimg, ImageCuda outimg, + int *cumucounter, int *cumugray, + unsigned char t0, + float c1, float c2, float weight) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量(其中,c 表示 + // column,r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height ) + return; + + // 获取当前像素点在图像中的相对位置。 + int curpos = dstr * inimg.pitchBytes + dstc; + + // 获取重叠计数数组和灰度数组下标。 + int curpos2 = dstr * inimg.imgMeta.width+ dstc; + + // 获得当前像素点的像素值。 + unsigned char g = inimg.imgMeta.imgData[curpos]; + + // 对原图像的像素值进行处理。 + float gray; + if (g <= t0) { + gray = 0.0f; + } else if(g >= 250) { + gray = 255.0f; + } else { + gray = c1 * (logf(g) - c2); + } + + // 根据原图像和均衡化的结果计算输出图像。 + outimg.imgMeta.imgData[curpos] = (unsigned char)((cumugray[curpos2] / + cumucounter[curpos2] - gray) * weight + + gray); +} + +// 成员方法:localHistEqual(图像直方图均衡化) +__host__ int LocalHistEqual::localHistEqual(Image *inimg, Image* outimg) +{ + // 局部变量,错误码。 + int errcode; + cudaError_t cudaerrcode; + + // 检查输入图像,输出图像是否为空。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输入图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) { + return errcode; + } + + // 提取输出图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + return errcode; + } + // 计算窗口的宽度和长度。 + int ww = inimg->width / divNum; + int wh = inimg->height / divNum; + + // 获得图像内像素点的总数量。 + int totalnum = inimg->width * inimg->height; + + + // 为核函数所需的 global 内存在 device 端开辟空间。 + // 为均衡化结果和窗口重叠数量一次性申请所有空间,然后通过偏移索引 + // 各个数组。 + int *devdata; + cudaerrcode = cudaMalloc((void **)&devdata, + sizeof (int) * totalnum * 2); + // 开辟失败,释放内存空间。 + if (cudaerrcode != cudaSuccess) { + cudaFree(devdata); + return CUDA_ERROR; + } + int *devcumucounter = devdata; + int *devcumugray = &devdata[totalnum]; + + + // 初始化为 0 + cudaerrcode = cudaMemset(devdata, 0, sizeof (int) * totalnum * 2); + if (cudaerrcode != cudaSuccess) { + cudaFree(devdata); + return CUDA_ERROR; + } + + // 图像窗口的总数 + int winnum = (divNum * 2 - 1) * (divNum * 2 - 1); + + // 每个窗口的直方图和最大值最小值三个数组一次性申请所有空间, + // 然后通过偏移索引各个数组。 + int *his, *max, *min; + cudaerrcode = cudaMalloc((void **)&his, sizeof (int) * winnum * + (HISTO_NUM + 2)); + + // 开辟失败,释放内存空间。 + if (cudaerrcode != cudaSuccess) { + cudaFree(devdata); + cudaFree(his); + return CUDA_ERROR; + } + max = &his[winnum * HISTO_NUM]; + min = &max[winnum]; + + // 初始化为0 + cudaerrcode = cudaMemset(his, 0, sizeof (int) * winnum * (HISTO_NUM + 2)); + if (cudaerrcode != cudaSuccess) { + cudaFree(devdata); + cudaFree(his); + return CUDA_ERROR; + } + + // 归一化直方图 + float *norhis; + cudaerrcode = cudaMalloc((void **)& norhis, + sizeof(float) * winnum * HISTO_NUM ); + + // 开辟失败,释放内存空间。 + if (cudaerrcode != cudaSuccess) { + cudaFree(devdata); + cudaFree(his); + cudaFree(norhis); + return CUDA_ERROR; + } + // 初始化为0 + cudaerrcode = cudaMemset(norhis, 0.0f, sizeof (float) * winnum * HISTO_NUM); + // 开辟失败,释放内存空间。 + if (cudaerrcode != cudaSuccess) { + cudaFree(devdata); + cudaFree(his); + cudaFree(norhis); + return CUDA_ERROR; + } + + // 根据外部参数 t0 计算 c1 和 c2 两个参数的值。 + float c1 = 255 / log(1.0f * 250 / (t0 + 1)); + float c2 = log((t0 + 1) * 1.0f); + + // 计算调用均衡化函数的线程块的尺寸和线程块的数量。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X ; + blocksize.y = DEF_BLOCK_Y ; + + // 每个窗口横向线程块数目 + int blockperwinw = (ww + blocksize.x - 1) / blocksize.x, + // 每个窗口纵向线程块数目 + blockperwinh = (wh + blocksize.y - 1) / blocksize.y; + + // 线程总规模 + gridsize.x = blockperwinw * (divNum * 2 - 1) ; + gridsize.y = blockperwinh * (divNum * 2 - 1) ; + + // 统计直方图 + _hisEqualHistoKer<<>>(insubimgCud, his, ww, wh, + blockperwinw, blockperwinh ); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错。 + cudaFree(his); + cudaFree(norhis); + cudaFree(devdata); + return CUDA_ERROR; + } + + // 窗口内总像素点数 + int total = ww * wh; + + // 线程块数目即为窗口数目 + dim3 gridsize2; + gridsize2.x = divNum * 2 - 1; + gridsize2.y = divNum * 2 - 1; + + // 计算归一化直方图和最大最小值 + _hisEqualKer<<>>(his, norhis, max, min, total); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错。 + cudaFree(his); + cudaFree(norhis); + cudaFree(devdata); + return CUDA_ERROR; + } + + + // 计算各个窗口均衡化最终结果,gridsize与统计直方图时相同 + // 由于窗口长、宽、每个窗口横纵向线程块数目等信息已经计算过, + // 在核函数中直接使用会减少一定的计算量,所以作为参数传入 + _hisEqualLastKer<<>>(insubimgCud, norhis, + max, min, ww, wh, + blockperwinw, blockperwinh, + devcumucounter, devcumugray ); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错。 + cudaFree(his); + cudaFree(norhis); + cudaFree(devdata); + return CUDA_ERROR; + } + + // 计算调用整合 Kernel 函数的线程块的数量。 + dim3 gridsize3; + gridsize3.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize3.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用核函数,将各窗口重新整合成输出图像。 + _hisEqualSecKer<<>>(insubimgCud, outsubimgCud, + devcumucounter, devcumugray, + t0, c1, c2, weight); + + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错。 + cudaFree(his); + cudaFree(norhis); + cudaFree(devdata); + return CUDA_ERROR; + } + + // 释放申请空间,避免内存泄露 + cudaFree(devdata); + cudaFree(his); + cudaFree(norhis); + + return NO_ERROR; +} + diff --git a/okano_3_0/LocalHistogramEqualization.h b/okano_3_0/LocalHistogramEqualization.h new file mode 100644 index 0000000..dfa0f0d --- /dev/null +++ b/okano_3_0/LocalHistogramEqualization.h @@ -0,0 +1,142 @@ +// LocalHistogramEqualization.h +// 创建人:杨伟光 +// +// 自适应直方图均衡化算法(LocalHistEqual) +// 功能说明:对指定图像进行直方图均衡化处理。 +// +// 修订历史: +// 2013年06月28日(杨伟光) +// 初始版本。 +// 2013年09月17日(高新凯) +// 实现算法基本功能,并行处理窗口。 +// 2013年10月9日(高新凯) +// 实现窗口均衡化的完全并行化。 + +#ifndef __LOCALHISTOGRAMEQUALIZATION_H__ +#define __LOCALHISTOGRAMEQUALIZATION_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:LocalHistEqual(自适应直方图均衡化) +// 继承自:无 +// 功能说明:对指定图像进行直方图均衡化处理。 +class LocalHistEqual { + +protected: + + // 成员变量:divNum(分割数) + // 一般为1, 2, 3, 4, 5 + int divNum; + + // 成员变量:t0(外部参数) + // 范围为5,10,15,20, 25, 30,35,40,45,50 + unsigned char t0; + + // 成员变量:weight(外部参数) + // 0 <= weight < 1 + float weight; + +public: + + // 构造函数:LocalHistEqual + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + LocalHistEqual() { + // 分割数默认为 1。 + this->divNum = 1; + // 默认值为最小值。 + this->t0 = 5; + // 默认值为最小值。 + this->weight = 0; + } + + // 成员函数:getDivNum(获取分割率的值) + // 获取成员变量 divNum 的值。 + __host__ __device__ int + getDivNum() const + { + // 返回 divNum 成员变量的值。 + return this->divNum; + } + + // 成员函数:setDivNum(设置分割率) + // 设置成员变量 divNum 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setDivNum( + int divnum // 分割率 + ) { + // 如果分割率小于等于0,则报错。 + if(divNum <= 0) + return INVALID_DATA; + + // 将 divNum 成员变量赋成新值。 + this->divNum = divnum; + + return NO_ERROR; + } + + // 成员函数:getT0(获取 T0 的值) + // 获取成员变量 t0 的值。 + __host__ __device__ int + getT0() const + { + // 返回 t0 成员变量的值。 + return this->t0; + } + + // 成员函数:setT0(设置 t0) + // 设置成员变量 t0 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setT0( + int t0 // t0 + ) { + // 如果 t0 的值超出取值范围,则报错。 + if(t0 < 5 || t0 > 50) + return INVALID_DATA; + + // 将 t0 成员变量赋成新值。 + this->t0 = t0; + + return NO_ERROR; + } + + // 成员函数:getWeight(获取 weight 的值) + // 获取成员变量 weight 的值。 + __host__ __device__ float + getWeight() const + { + // 返回 weight 成员变量的值。 + return this->weight; + } + + // 成员函数:setWeight(设置 weight ) + // 设置成员变量 weight 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setWeight( + float weight // 权值 + ) { + // 如果 weight 的值超出取值范围,则报错。 + if(weight < 0 || weight > 1) + return INVALID_DATA; + + // 将 weight 成员变量赋成新值。 + this->weight = weight; + + return NO_ERROR; + } + + // 成员方法:localHistEqual(图像直方图均衡化) + // 功能说明:对指定图像inImg分块进行直方图均衡化处理,图像分割率为divNum, + // 处理结果保存在outimg中。 + + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR + localHistEqual( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/Makefile b/okano_3_0/Makefile new file mode 100644 index 0000000..ed84b04 --- /dev/null +++ b/okano_3_0/Makefile @@ -0,0 +1,48 @@ +ALGNAMES := Image Matrix Template CoordiSet TemplateFactory AffineTrans \ + RotateTable SelectShape SortArray Histogram HistogramSpec \ + Threshold BoundingRect Flip Binarize Morphology ConnectRegion \ + LabelIslandSortArea LinearEnhancement FillUp ImageDiff \ + ImageHide Moments GeometryProperties Normalization \ + FreckleFilter LocalCluster DownSampleImage HoughLine \ + BilateralFilter MultiThreshold BilinearInterpolation \ + FeatureVecCalc FeatureVecArray ImageMatch ScanArray \ + SalientRegionDetect RoiCopy RobustEdgeDetection \ + PerspectiveTrans FanczosIpl OtsuBinarize LinearFilter \ + WorkAndObjectMatch Thinning ImageScaling EdgeCheck \ + SmoothVector SegmentedScan HoughCircle ConvexHull \ + Segmentation SimpleRegionDetect Rectangle SmallestDirRect \ + HoughRec GetContourSet ImageDrawer ImgConvert ImageOverlay \ + TorusSegmentation IsolatedPoints InnerDigger ImageToText \ + Tattoo ImageStretch Mosaic Complex Mandelbrot Binarization \ + Compact FlutterBinarize Curve Zoom Julia Reduce \ + ConnectRegionNew Multidecrease DiffPattern CurveFluctuPropers \ + CurveFluctuation SuperSmooth ThresholdSegmentation \ + DynamicArrays Graph LearningFilter FillCoor RegionGrow \ + CurveTracing GaussianElimination PriFeatureCheckC \ + HistogramDifference CurveTopology ICcircleRadii \ + GaussianSmoothImage GaussianSmoothxy DouglasPeucker SalientImg \ + LocalHistogramEqualization OtsuForThree ClusterLocalGray \ + ContourMatch CovarianceMatrix InscribedCircle EdgeDetection \ + GeoPrimaryProperties OperationFunctor CombineImage \ + ConsolidateAndIdentifyContours ImageFilter CurveConverter \ + FillCurve SimpleBrightnessGradient MultiConnectRegion + +INCFILES := ErrorCode.h $(addsuffix .h, $(ALGNAMES)) +OBJFILES := main.o $(addsuffix .o, $(ALGNAMES)) + +EXEFILE := okanoexec + +NVCCCMD := nvcc +NVCCFLAG := -arch=sm_20 +NVLDFLAG := -lnppi + +world: $(EXEFILE) + +$(EXEFILE): $(OBJFILES) + $(NVCCCMD) $(OBJFILES) -o $(EXEFILE) $(NVLDFLAG) + +$(OBJFILES): %.o:%.cu $(INCFILES) + $(NVCCCMD) -c $(filter %.cu, $<) -o $@ $(NVCCFLAG) + +clean: + rm -rf $(OBJFILES) $(EXEFILE) diff --git a/okano_3_0/Moments.cu b/okano_3_0/Moments.cu new file mode 100644 index 0000000..8ae4b63 --- /dev/null +++ b/okano_3_0/Moments.cu @@ -0,0 +1,825 @@ +// Moments.cu +// 几何矩的计算 + +#include "Moments.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + + +// 宏:M_PI +// π值。对于某些操作系统,M_PI可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 256 +#define DEF_BLOCK_Y 1 + +// Kernel 函数:_accumulateImageKer(原图像的累进求和) +// 利用差分矩因子算法计算几何矩。对原图像的每一行做 5 次累进求和,保留结果 +// g1(j, 1), g2(j, 2), g3(j, 1) + g3(j, 2), g4(j, 1) + g4(j, 2) * 4 + g4(j, 3), +// g5(j, 1) + g5(j, 2) * 11 + g5(j, 3) * 11 + g5(j, 4),j 等于图像的高度 height, +// 所以输出数组 accimg 大小为 5 * height。每个线程处理一行的元素。 +static __global__ void // Kernel 函数无返回值 +_accumulateImageKer( + ImageCuda img, // 输入图像 + double *accimg // 原图像的 5 次累进求和 +); + +// Kernel 函数:_accumulateConstantOneKer(原图像的累进求和,乘积项设置恒为 1) +// 利用差分矩因子算法计算几何矩。对原图像的每一行做 5 次累进求和,保留结果 +// g1(j, 1), g2(j, 2), g3(j, 1) + g3(j, 2), g4(j, 1) + g4(j, 2) * 4 + g4(j, 3), +// g5(j, 1) + g5(j, 2) * 11 + g5(j, 3) * 11 + g5(j, 4),j 等于图像的高度 height, +// 所以输出数组 accimg 大小为 5 * height。每个线程处理一行的元素。注意,计算矩 +// 过程中的乘积项设置为 1。 +static __global__ void // Kernel 函数无返回值 +_accumulateConstantOneKer( + ImageCuda img, // 输入图像 + double *accimg // 原图像的 5 次累进求和 +); + +// Host 方法:complexMultiply(复数乘法运算) +// 输入两个复数的实部和虚部,输出结果的实虚部。 +__host__ int complexMultiply(double real1, double imag1, double real2, + double imag2, double *realout, double *imagout); + +// Kernel 函数:_accumulateImageKer(原图像的累进求和) +static __global__ void _accumulateImageKer(ImageCuda img, double *accimg) +{ + // 计算线程对应的位置。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + if (idx > img.imgMeta.height) + return; + + // 原图像的每一行做 5 次累进求和的结果。 + double g11, g21, g31, g41, g51; + + // 因为是从右向左累进,所以初始化为每一行最右边的元素。 + int index = img.pitchBytes * idx + (img.imgMeta.width - 1); + g11 = g21 = g31 = g41 = g51 = img.imgMeta.imgData[index]; + + // 循环累加每一行元素。该循环暂且计算到 img.imgMeta.width - 3 个元素, + // 因为需要保存 g54 的值。 + int tempidx = img.pitchBytes * idx; + //int tempidx = img.imgMeta.width * idx; + for (int i = img.imgMeta.width - 2; i >= 3; i--) { + // 对原图像进行 5 次 累进求和。 + g11 += img.imgMeta.imgData[tempidx + i]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + } + // 计算 g54 的值。 + double g54 = g51; + + // 继续累进一个像素,即总体计算到 img.imgMeta.width - 2 个元素。 + g11 += img.imgMeta.imgData[tempidx + 2]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + + // 计算 g43, g53 的值。 + double g43 = g41; + double g53 = g51; + + + // 继续累进一个像素,即总体计算到 img.imgMeta.width - 1 个元素。 + g11 += img.imgMeta.imgData[tempidx + 1]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + + // 计算 g32, g42, g52 的值。 + double g32 = g31; + double g42 = g41; + double g52 = g51; + + // 继续累进一个像素,即总体计算到 img.imgMeta.width 个元素,累进求和结束。 + g11 += img.imgMeta.imgData[tempidx]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + + // 将累进结果保存到输出数组中。 + accimg[idx] = g11; + accimg[idx += img.imgMeta.height] = g21; + accimg[idx += img.imgMeta.height] = g31 + g32; + accimg[idx += img.imgMeta.height] = g41 + g42 * 4.0f + g43; + accimg[idx += img.imgMeta.height] = g51 + g52 * 11.0f + g53 * 11.0f + g54; +} + +// Kernel 函数:_accumulateConstantOneKer(原图像的累进求和,乘积项设置恒为 1) +static __global__ void _accumulateConstantOneKer(ImageCuda img, double *accimg) +{ + // 计算线程对应的位置。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + if (idx > img.imgMeta.height) + return; + + // 原图像的每一行做 5 次累进求和的结果。 + double g11, g21, g31, g41, g51; + + // 因为是从右向左累进,所以初始化为每一行最右边的元素。 + g11 = g21 = g31 = g41 = g51 = 1.0f; + + // 循环累加每一行元素。该循环暂且计算到 img.imgMeta.width - 3 个元素, + // 因为需要保存 g54 的值。 + int tempidx = img.pitchBytes * idx; + //int tempidx = img.imgMeta.width * idx; + for (int i = img.imgMeta.width - 2; i >= 3; i--) { + // 对原图像进行 5 次 累进求和。 + g11 += 1.0f; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + } + // 计算 g54 的值。 + double g54 = g51; + + // 继续累进一个像素,即总体计算到 img.imgMeta.width - 2 个元素。 + g11 += img.imgMeta.imgData[tempidx + 2]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + + // 计算 g43, g53 的值。 + double g43 = g41; + double g53 = g51; + + + // 继续累进一个像素,即总体计算到 img.imgMeta.width - 1 个元素。 + g11 += img.imgMeta.imgData[tempidx + 1]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + + // 计算 g32, g42, g52 的值。 + double g32 = g31; + double g42 = g41; + double g52 = g51; + + // 继续累进一个像素,即总体计算到 img.imgMeta.width 个元素,累进求和结束。 + g11 += img.imgMeta.imgData[tempidx]; + g21 += g11; + g31 += g21; + g41 += g31; + g51 += g41; + + // 将累进结果保存到输出数组中。 + accimg[idx] = g11; + accimg[idx += img.imgMeta.height] = g21; + accimg[idx += img.imgMeta.height] = g31 + g32; + accimg[idx += img.imgMeta.height] = g41 + g42 * 4.0f + g43; + accimg[idx += img.imgMeta.height] = g51 + g52 * 11.0f + g53 * 11.0f + g54; +} + +// Host 成员方法:spatialMoments(计算空间矩) +__host__ int Moments::spatialMoments(Image *img, MomentSet *momset) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (img == NULL) + return NULL_POINTER; + + // 检查 momset 是否为空。 + if (momset == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(img); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda subimgCud; + errcode = ImageBasicOp::roiSubImage(img, &subimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 在 Device 端申请空间,并将其数据初始化为 0。 + cudaError_t cudaerrcode; + double *accimgdev; + int datasize = 5 * subimgCud.imgMeta.height * sizeof (double); + cudaerrcode = cudaMalloc((void**)&accimgdev, datasize); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(accimgdev, 0, datasize); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (subimgCud.imgMeta.height + DEF_BLOCK_X - 1) / DEF_BLOCK_X; + gridsize.y = DEF_BLOCK_Y; + + // 计算矩的过程中正常乘以原图像的灰度值。 + if (!this->isconst) { + // 调用核函数,对原图像每一行计算 5 次累进求和,每个线程计算一行数据。 + _accumulateImageKer<<>>(subimgCud, accimgdev); + // 计算矩的过程中图像的灰度值部分恒等于 1。 + } else { + // 调用核函数,对原图像每一行计算 5 次累进求和,每个线程计算一行数据。 + _accumulateConstantOneKer<<>>(subimgCud, + accimgdev); + } + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(accimgdev); + return CUDA_ERROR; + } + + // 将 Device 端数据拷贝到 Host 端。 + double *accimg = new double[5 * subimgCud.imgMeta.height]; + cudaerrcode = cudaMemcpy(accimg, accimgdev, datasize, + cudaMemcpyDeviceToHost); + + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 对 g1(i, j) 的第一列做 5 次迭代累进求和。 + int tempidx; + double r11, r21, r31, r41, r51, r32 = 0.0f, r42 = 0.0f, + r43 = 0.0f, r52 = 0.0f, r53 = 0.0f, r54 = 0.0f; + tempidx = subimgCud.imgMeta.height - 1; + r11 = r21 = r31 = r41 = r51 = accimg[tempidx]; + + // 对 g2(i, j) 的第一列做 4 次迭代累进求和。 + double s11, s21, s31, s41, s32 = 0.0f, s42 = 0.0f, s43 = 0.0f; + tempidx += subimgCud.imgMeta.height; + s11 = s21 = s31 = s41 = accimg[tempidx]; + + // 对 t0(j) = g3(j, 1) + g3(j, 2)做 3 次累进求和。 + double t11, t21, t31, t32 = 0.0f; + tempidx += subimgCud.imgMeta.height; + t11 = t21 = t31 = accimg[tempidx]; + + // 对 u0(j) = g4(j, 1) + g4(j, 2) * 4 + g4(j, 3) 做 2 次累进求和。 + double u11, u21; + tempidx += subimgCud.imgMeta.height; + u11 = u21 = accimg[tempidx]; + + // 对 p0(j) = g5(j, 1) + g5(j, 2) * 11 + g5(j, 3) * 11 + g5(j, 4) 做 + // 1 次累进求和。 + tempidx += subimgCud.imgMeta.height; + double p11 = accimg[tempidx]; + + // 循环累加每一行元素。中间过程中需要计算一些额外值, + // 包括 r54, r53, r52, r43, r42, s43, s42, s32, t32。 + for (int i = subimgCud.imgMeta.height - 2; i >= 0; i--) { + // 对 g1(j) 进行 5 次累进求和。 + r11 += accimg[i]; + r21 += r11; + r31 += r21; + r41 += r31; + r51 += r41; + + // 对 g2(j) 进行 4 次累进求和。 + tempidx = i + subimgCud.imgMeta.height; + s11 += accimg[tempidx]; + s21 += s11; + s31 += s21; + s41 += s31; + + // 对 t0(j) 进行 3 次累进求和。 + tempidx += subimgCud.imgMeta.height; + t11 += accimg[tempidx]; + t21 += t11; + t31 += t21; + + // 对 u0(j) 进行 2 次累进求和。 + tempidx += subimgCud.imgMeta.height; + u11 += accimg[tempidx]; + u21 += u11; + + // 对 p0(j) 进行 1 次累进求和。 + tempidx += subimgCud.imgMeta.height; + p11 += accimg[tempidx]; + + // 计算 r54。 + if (i == 3) { + r54 = r51; + } + + // 计算 r53, r43, s43。 + if (i == 2) { + r43 = r41; + r53 = r51; + s43 = s41; + } + + // 计算 r32, r42, r52, s42, s32, t32。 + if (i == 1) { + r32 = r31; + r42 = r41; + r52 = r51; + s32 = s31; + s42 = s41; + t32 = t31; + } + } + + // 根据上面计算的变量,对 MomentSet 中的 15 个空间矩进行赋值。 + momset->m00 = r11; + momset->m10 = s11; + momset->m01 = r21; + momset->m20 = t11; + momset->m11 = s21; + momset->m02 = r31 + r32; + momset->m30 = u11; + momset->m21 = t21; + momset->m12 = s31 + s32; + momset->m03 = r41 + r42 * 4.0f + r43; + momset->m22 = t31 + t32; + momset->m31 = u21; + momset->m13 = s41 + s42 * 4.0f + s43; + momset->m40 = p11; + momset->m04 = r51 + r52 * 11.0f + r53 * 11.0f +r54; + + // 释放 Device 端空间。 + cudaFree(accimgdev); + // 释放 Host 端空间。 + delete [] accimg; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:centralMoments(计算中心矩) +__host__ int Moments::centralMoments(Image *img, MomentSet *momset) +{ + // 局部变量,错误码 + int errcode; + + // 首先计算空间矩。 + errcode = spatialMoments(img, momset); + if (errcode != NO_ERROR) + return errcode; + + // 对 MomentSet 中的中心距 mu00, mu10, mu01 进行赋值。 + momset->mu00 = momset->m00; + momset->mu10 = 0.0f; + momset->mu01 = 0.0f; + + // 如果 mu00 不为 0 的话,继续计算其他中心矩的数值。 + if (!(fabs(momset->m00) < 0.000001)) { + // meanX 和 meanY 是形状的分布重心。 + double meanX = momset->m10 / momset->m00; + double meanY = momset->m01 / momset->m00; + + // 判断是否需要调整中心坐标。 + if (this->adjustcenter == true) { + momset->mu20 = momset->m20 - meanX * momset->m10; + momset->mu02 = momset->m02 - meanY * momset->m01; + + // 计算偏差。 + double xs = sqrt(momset->mu20 / momset->mu00); + double ys = sqrt(momset->mu02 / momset->mu00); + + // 重新定义中心。 + meanX = meanX - xs; + meanY = meanY - ys; + } + + // 定义中间变量。 + double meanX2 = meanX * meanX; + double meanY2 = meanY * meanY; + + // 计算其余的中心矩的数值。 + momset->mu20 = momset->m20 - meanX * momset->m10; + momset->mu11 = momset->m11 - meanY * momset->m10; + momset->mu02 = momset->m02 - meanY * momset->m01; + momset->mu30 = momset->m30 - 3.0f * meanX * momset->m20 + + 2.0f * meanX2 * momset->m10; + momset->mu21 = momset->m21 - 2.0f * meanX * momset->m11 + + 2.0f * meanX2 * momset->m01- meanY * momset->m20; + momset->mu12 = momset->m12 - 2.0f * meanY * momset->m11 + + 2.0f * meanY2 * momset->m10 - meanX * momset->m02; + momset->mu03 = momset->m03 - 3.0f * meanY * momset->m02 + + 2.0f * meanY2 * momset->m01; + momset->mu22 = momset->m22 - 2.0f * meanY * momset->m21 + meanY2 * + momset->m20 - 2.0f * meanX * momset->m12 + 4.0f * + meanX * meanY * momset->m11 - 2.0f * meanX * meanY * + momset->m10 + meanX2 * momset->m02 - 3.0f * meanX2 * + meanY * momset->m01; + momset->mu31 = momset->m31 - meanY * momset->m30 - 3.0f * meanX * + momset->m21 + 3.0f * meanX * meanY * momset->m20 + + 3.0f * meanX2 * momset->m11 - 3.0f * meanX2 * meanY * + momset->m10; + momset->mu13 = momset->m13 - 3.0f * meanY * momset->m12 + 3.0f * + meanY2 * momset->m11 - meanX * momset->m03 + 3.0f * + meanX * meanY * momset->m02 - 3.0f * meanX * meanY2 * + momset->m01; + momset->mu40 = momset->m40 - 4.0f * meanX * momset->m30 + + 6.0f * meanX2 * momset->m20 - 3.0f * meanX2 * + meanX * momset->m10; + momset->mu04 = momset->m04 - 4.0f * meanY * momset->m03 + + 6.0f * meanY2 * momset->m02 - 3.0f * meanY2 * + meanY * momset->m01; + + // 否则为了避免产生除 0 的错误,直接退出。 + } else { + return OP_OVERFLOW; + } + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:centralMoments(计算形状的分布重心和方向) +__host__ int Moments::centralMoments(Image *img, double centers[2], + double *angle) +{ + // 检查输入参数是否为空。 + if (centers == NULL || angle == NULL) + return NULL_POINTER; + + // 局部变量,错误码 + int errcode; + + // 声明 MomentSet 结构体变量。 + MomentSet momset; + + // 计算中心矩的各项值。 + errcode = centralMoments(img, &momset); + if (errcode != NO_ERROR) + return errcode; + + // 计算几何分布重心。 + centers[0] = momset.m10 / momset.m00; + centers[1] = momset.m01 / momset.m00; + + // 计算几何分布方向。 + double u_20 = momset.mu20 / momset.mu00; + double u_02 = momset.mu02 / momset.mu00; + double u_11 = momset.mu11 / momset.mu00; + + // 如果 u_11 不等于 0,并且 u_20 不等于 u_02的话,计算方向角度。 + if (!(fabs(u_11) < 0.000001) && !(fabs(u_20 - u_02) < 0.000001)) { + // 计算角度大小。 + *angle = (atan(2.0f * u_11 / (u_20 - u_02)) / 2.0f); + + // 否则为了避免产生除 0 的错误,直接退出。 + } else { + // 特殊标识。 + *angle = -2 * M_PI; + } + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:huMoments(计算 Hu 矩) +__host__ int Moments::huMoments(Image *img, MomentSet *momset) +{ + // 局部变量,错误码 + int errcode; + + // 首先计算中心矩。 + errcode = centralMoments(img, momset); + if (errcode != NO_ERROR) + return errcode; + + // 标准化中心距。 + double p1 = pow(momset->mu00, 2.0f); + double p2 = pow(momset->mu00, 2.5f); + double n11 = momset->mu11 / p1; + double n02 = momset->mu02 / p1; + double n20 = momset->mu20 / p1; + double n12 = momset->mu12 / p2; + double n21 = momset->mu21 / p2; + double n03 = momset->mu03 / p2; + double n30 = momset->mu30 / p2; + + // 声明中间变量,方便简化后面的语句。 + double temp1 = n20 - n02; + double temp2 = n30 - 3.0f * n12; + double temp3 = 3.0f * n21 - n03; + double temp4 = n30 + n12; + double temp5 = n21 + n03; + + // 计算 Hu moments 的 8 个值。 + momset->hu1 = n20 + n02; + momset->hu2 = temp1 * temp1 + 4.0f * n11 * n11; + momset->hu3 = temp2 * temp2 + temp3 * temp3; + momset->hu4 = temp4 * temp4 + temp5 * temp5; + momset->hu5 = temp2 * temp4 * (temp4 * temp4 - 3.0f * temp5 * temp5) + + temp3 * temp5 * (3.0f * temp4 * temp4 - temp5 * temp5); + momset->hu6 = temp1 * (temp4 * temp4 - temp5 * temp5) + + 4.0f * n11 * temp4 * temp5; + momset->hu7 = temp3 * temp4 * (temp4 * temp4 - 3.0f * temp5 * temp5) - + temp2 * temp5 * (3.0f * temp4 * temp4 - temp5 * temp5); + + // Hu 矩的扩展,增加了一个不变量。 + momset->hu8 = n11 * (temp4 * temp4 - temp5 * temp5) - + temp1 * temp4 * temp5; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:affineMoments(计算 affine 矩) +__host__ int Moments::affineMoments(Image *img, MomentSet *momset) +{ + // 局部变量,错误码 + int errcode; + + // 首先计算中心矩。 + errcode = centralMoments(img, momset); + if (errcode != NO_ERROR) + return errcode; + + // 获得中心矩。 + double u11 = momset->mu11; + double u20 = momset->mu20; + double u02 = momset->mu02; + double u12 = momset->mu12; + double u21 = momset->mu21; + double u30 = momset->mu30; + double u03 = momset->mu03; + double u13 = momset->mu13; + double u31 = momset->mu31; + double u22 = momset->mu22; + double u40 = momset->mu40; + double u04 = momset->mu04; + +/* + // 计算 9 个 affine moment invariants + double s = momset->mu00; + momset->ami1 = (u20 * u02 − u11 * u11) / pow(s, 4); + momset->ami2 = (-u30 * u30 * u03 * u03 + 6 * u30 * u21 * u12 * u03 − + 4 * u30 * u12 * u12 * u12 − 4 * u21 * u21 * u21 * u03 + + 3 * u21 * u21 * u12 * u12) / pow(s, 10); + momset->ami3 = (u20 * u21 * u03 − u20 * u12 * u12− u11 * u30 * u03 + u11 * + u21 * u12 + u02 * u30 * u12− u02 * u21 * u21) / pow(s, 7); + momset->ami4 = (−u20 * u20 * u20 * u03 * u03 + 6 * u20 * u20 * u11 * u12 * + u03 – 3 * u20 * u20 * u02 * u12 * u12 − 6 * u20 * u11 * + u11 * u21 * u03 – 6 * u20 * u11 * u11 * u12 * u12 + 12 * + u20 * u11 * u02 * u21 * u12 – 3 * u20 * u02 * u02 * u21 * + u21 + 2 * u11 * u11 * u11 * u30 * u03 + 6 * u11 * u11 * + u11 * u21 * u12 – 6 * u11 * u11 * u02 * u30 * u12 – + 6 * u11 * u11 * u02 * u21 * u21 + 6 * u11 * u02 * u02 * + u30 * u21 − u02 * u02 * u02 * u30 * u30) / pow(s, 11); + momset->ami6 = (u40 * u04 – 4 * u31 * u13 + 3 * u22 * u22 ) / pow(s, 6); + momset->ami7 = (u40 * u22 * u04 − u40 * u13 * u13 − u31 * u31 * u04 + + 2 * u31 * u22 * u13 − u22 * u22 * u22) / pow(s, 9); + momset->ami8 = (u20 * u20 * u04 – 4 * u20 * u11 * u13 + 2 * u20 * u02 * + u22 + 4 * u11 * u11 * u22 − 4 * u11 * u02 * u31 + u02 * + u02 * u40) / pow(s, 7); + momset->ami9 = (u20 * u20 * u22 * u04 − u20 * u20 * u13 * u13− 2 * u20 * + u11 * u31 * u04 + 2 * u20 * u11 * u22 * u13 + u20 * u02 * + u40 * u04 – 2 * u20 * u02 * u31 * u13 + u20 * u02 * u22 * + u22 + 4 * u11 * u11 * u31 * u13 – 4 * u11 * u11 * u22 * + u22 − 2 * u11 * u02 * u40 * u13 + 2 * u11 * u02 * u31 * + u22 + u02 * u02 * u40 * u22 − u02 * u02 * u31 * u31) / + pow(s, 10); + momset->ami19 = (u20 * u30 * u12 * u04 − u20 * u30 * u03 * u13 − u20 * + u21 * u21 * u04 + u20 * u21 * u12 * u13 + u20 * u21 * + u03 * u22 − u20 * u12 * u12 * u22 – 2 * u11 * u30 * u12 * + u13 + 2 * u11 * u30 * u03 * u22 + 2 * u11 * u21 * u21 * + u13 – 2 * u11 * u21 * u12 * u22 – 2 * u11 * u21 * u03 * + u31 + 2 * u11 * u12 * u12 * u31 + u02 * u30 * u12 * u22 − + u02 * u30 * u03 * u31 – u02 * u21 * u21 * u22 + u02 * + u21 * u12 * u31 + u02 * u21 * u03 * u40 − u02 * u12 * + u12 * u40) / pow(s, 10); +*/ + + // 定义一些中间变量。 + double temp1 = u20 * u02; + double temp2 = u11 * u11; + double temp3 = u30 * u30; + double temp4 = u03 * u03; + double temp5 = u03 * u30; + double temp6 = u21 * u12; + double temp7 = u12 * u12 * u12; + double temp8 = u21 * u21 * u21; + double temp9 = u21 * u21; + double temp10 = u12 * u12; + double temp11 = u20 * u20 * u20; + double temp12 = u20 * u20; + double temp13 = u11 * u11 * u11; + double temp14 = u02 * u02; + double temp15 = u02 * u02 * u02; + double temp16 = u40 * u04; + double temp17 = u31 * u13; + double temp18 = u22 * u22; + double temp19 = u13 * u13; + double temp20 = u31 * u31; + + // 计算 9 个 affine moment invariants + double s4 = pow(momset->mu00, 4); + double s6 = pow(momset->mu00, 6); + double s7 = s6 * momset->mu00; + double s9 = pow(momset->mu00, 9); + double s10 = s9 * momset->mu00; + double s11 = s10 * momset->mu00; + + momset->ami1 = (temp1 - temp2) / s4; + + momset->ami2 = (-temp3 * temp4 + 6 * temp5 * temp6 - 4 * u30 * temp7 - + 4 * temp8 * u03 + 3 * temp9 * temp10) / s10; + + momset->ami3 = (u20 * u21 * u03 - u20 * temp10 - u11 * temp5 + u11 * + temp6 + u02 * u30 * u12 - u02 * temp9) / s7; + + momset->ami4 = (-temp11 * temp4 + 6 * temp12 * u11 * u12 * u03 - + 3 * temp12 * u02 * temp10 - 6 * u20 * temp2 * u21 * + u03 - 6 * u20 * temp2 * temp10 + 12 * temp1 * u11 * temp6 - + 3 * temp1 * u02 * temp9 + 2 * temp13 * temp5 + 6 * temp13 * + temp6 - 6 * temp2 * u02 * u30 * u12 - 6 * temp2 * u02 * + temp9 + 6 * u11 * temp14 * u30 * u21 - temp15 * temp3) / + s11; + + momset->ami6 = (temp16 - 4 * temp17 + 3 * temp18) / s6; + + + momset->ami7 = (temp16 * u22 - u40 * temp19 - temp20 * u04 + 2 * u22 * + temp17 - temp18 * u22) / s9; + + + momset->ami8 = (temp12 * u04 - 4 * u20 * u11 * u13 + 2 * temp1 * u22 + + 4 * temp2 * u22 - 4 * u11 * u02 * u31 + temp14 * u40) / + s7; + + momset->ami9 = (temp12 * u22 * u04 - temp12 * temp19 - 2 * u20 * u11 * + u31 * u04 + 2 * u20 * u11 * u22 * u13 + temp1 * temp16 - + 2 * temp1 * temp17 + temp1 * temp18 + 4 * temp2 * temp17 - + 4 * temp2 * temp18 - 2 * u11 * u02 * u40 * u13 + 2 * u11 * + u02 * u31 * u22 + temp14 * u40 * u22 - temp14 * temp20) / + s10; + + momset->ami19 = (u20 * u30 * u12 * u04 - u20 * temp5 * u13 - u20 * temp9 * + u04 + u20 * temp6 * u04 + u20 * temp6 * u13 + u20 * u21 * + u03 * u22 - u20 * temp10 * u22 - 2 * u11 * u30 * u12 * + u13 + 2 * u11 * temp5 * u22 + 2 * u11 * temp9 * u13 - + 2 * u11 * temp6 * u22 - 2 * u11 * u21 * u03 * u31 + + 2 * u11 * temp10 * u31 + u02 * u30 * u12 * u22 - u20 * + temp5 * u31 - u02 * temp9 * u22 + u02 * temp6 * u31 + + u02 * u21 * u03 * u40 - u02 * temp10 * u40) / s10; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 方法:complexMultiply(复数乘法运算) +__host__ int complexMultiply(double real1, double imag1, double real2, + double imag2, double *realout, double *imagout) +{ + // 两复数的乘法运算。 + *realout = real1 * real2 - imag1 * imag2; + *imagout = imag1 * real2 + real1 * imag2; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:flusserMoments(计算 flusser 矩) +__host__ int Moments::flusserMoments(Image *img, MomentSet *momset) +{ + // 局部变量,错误码。 + int errcode; + + // 使用调整的中心距。 + errcode = setAdjustcenter(true); + if (errcode != NO_ERROR) + return errcode; + + // 首先计算中心矩。 + errcode = centralMoments(img, momset); + if (errcode != NO_ERROR) + return errcode; + + // 获得中心矩。 + double u11 = momset->mu11; + double u20 = momset->mu20; + double u02 = momset->mu02; + double u12 = momset->mu12; + double u21 = momset->mu21; + double u30 = momset->mu30; + double u03 = momset->mu03; + double u13 = momset->mu13; + double u31 = momset->mu31; + double u22 = momset->mu22; + double u40 = momset->mu40; + double u04 = momset->mu04; + + // 归一化中心距。 + double temp1 = pow(momset->mu00, 2); + double temp2 = pow(momset->mu00, 2.5f); + double temp3 = pow(momset->mu00, 3); + double n11 = u11 / temp1; + double n20 = u20 / temp1; + double n02 = u02 / temp1; + double n12 = u12 / temp2; + double n21 = u21 / temp2; + double n30 = u30 / temp2; + double n03 = u03 / temp2; + double n13 = u13 / temp3; + double n31 = u31 / temp3; + double n22 = u22 / temp3; + double n40 = u40 / temp3; + double n04 = u04 / temp3; + + // 计算 11 个 flusser moments。 + // 计算 flu1。 + momset->flu1 = n20 + n02; + + // 计算 flu2。 + double c21[2]; + c21[0] = n12 + n30; + c21[1] = n03 + n21; + double c12[2]; + c12[0] = n12 + n30; + c12[1] = -n03 - n21; + // c21 乘以 c12。 + double c21c12[2]; + complexMultiply(c21[0], c21[1], c21[0], c21[1], + &c21c12[0], &c21c12[1]); + momset->flu2 = c21c12[0]; + + // 计算 flu3, flu4。 + double c20[2]; + c20[0] = n20 - n02; + c20[1] = 2 * n11; + // c12 的平方。 + double c12p2[2]; + complexMultiply(c12[0], c12[1], c12[0], c12[1], + &c12p2[0], &c12p2[1]); + // c20 乘以 c12 的平方。 + double c20c12p2[2]; + complexMultiply(c20[0], c20[1], c12p2[0], c12p2[1], + &c20c12p2[0], &c20c12p2[1]); + momset->flu3 = c20c12p2[0]; + momset->flu4 = c20c12p2[1]; + + // 计算 flu5, flu6。 + double c30[2]; + c30[0] = n30 - 3 * n12; + c30[1] = 3 * n21 - n03; + // c12 的 3 次方。 + double c12p3[2]; + complexMultiply(c12[0], c12[1], c12p2[0], c12p2[1], + &c12p3[0], &c12p3[1]); + // c30 乘以 c12 的 3 次方。 + double c30c12p3[2]; + complexMultiply(c30[0], c30[1], c12p3[0], c12p3[1], + &c30c12p3[0], &c30c12p3[1]); + momset->flu5 = c30c12p3[0]; + momset->flu6 = c30c12p3[1]; + + // 计算 flu7。 + momset->flu7 = n04 + 2 * n22 + n40; + + // 计算 flu8, flu9。 + double c31[2]; + c31[0] = n40 - n04; + c31[1] = 2 * n13 + 2 * n31; + // c31 乘以 c12 的平方。 + double c31c12p2[2]; + complexMultiply(c31[0], c31[1], c12p2[0], c12p2[1], + &c31c12p2[0], &c31c12p2[1]); + momset->flu8 = c31c12p2[0]; + momset->flu9 = c31c12p2[1]; + + // 计算 flu10, flu11。 + double c40[2]; + c40[0] = n04 + n40 - 6 * n22; + c40[1] = 4 * n13 + 4 * n31; + // c12 的 4 次方。 + double c12p4[2]; + complexMultiply(c12[0], c12[1], c12p3[0], c12p3[1], + &c12p4[0], &c12p4[1]); + // c40 乘以 c12 的 4 次方。 + double c40c12p4[2]; + complexMultiply(c40[0], c40[1], c12p4[0], c12p4[1], + &c40c12p4[0], &c40c12p4[1]); + momset->flu10 = c40c12p4[0]; + momset->flu11 = c40c12p4[1]; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/Moments.h b/okano_3_0/Moments.h new file mode 100644 index 0000000..10ddf58 --- /dev/null +++ b/okano_3_0/Moments.h @@ -0,0 +1,199 @@ +// Moments.h +// 创建人:刘宇 +// +// 几何矩(Moments) +// 功能说明:计算空间矩(spatial moments),中心矩(central moments), +// 以及 Hu 矩(Hu moments),affine moment invariants 等。 +// +// 修订历史: +// 2012年10月19日(刘宇) +// 初始版本 +// 2012年11月13日(刘宇) +// 在核函数执行后添加 cudaGetLastError 判断语句 +// 2012年11月23日(刘宇) +// 添加输入输出参数的空指针判断 +// 2013年7月14日(刘宇) +// 修正 Hu 矩一处符号错误 +// 2013年9月12日(刘宇) +// 完成 affine moment invriants +// 2013年10月6日(刘宇) +// 完成 flusser moments + +#ifndef __MOMENTS_H__ +#define __MOMENTS_H__ + +#include "Image.h" + + +// 结构体:MomentSet(几何矩数据集合) +// 该结构体包含了空间矩,中心矩和 Hu 矩的所有数据。 +typedef struct MomentSet_st { + double m00, m10, m01, m20, m11, m02, m30, m21, // 空间矩 + m12, m03, m22, m31, m13, m40, m04; + double mu00, mu10, mu01, mu20, mu11, mu02, mu30, mu21, + mu12, mu03, mu22, mu31, mu13, mu40, mu04; // 中心矩 + double hu1, hu2, hu3, hu4, hu5, hu6, hu7, hu8; // Hu 矩 + double ami1, ami2, ami3, ami4, ami6, ami7, // Affine Moments + ami8, ami9, ami19; + double flu1, flu2, flu3, flu4, flu5, flu6, flu7, // Fluuser Moments + flu8, flu9, flu10, flu11; +} MomentSet; + + +// 类:Moments +// 继承自:无 +// 利用差分矩因子算法计算几何矩。差分求和定理:两个离散函数数组的乘积等于 +// 将其中一个差分、另一个累进求和后的乘积。基于该定理对图像计算矩,将矩做 +// 为一个离散函数数组对其差分,将图像函数作为另一个离散数组对其累进求和。 +// 由于矩因子数组差分一次或多次后,除边界附近点外,其余元素值皆为 0,这样 +// 本需对所有数组元素作相乘运算,变为只需对数组边界附近不为 0 的数组元素 +// 作相乘运算,从而减少了大量的运算。根据算法需求,计算矩的分为两种情况, +// 一种是乘以正常的灰度值,一种是设置乘积项恒等于 1。 +// 参考文献《快速不变矩算法基于 CUDA 的并行实现》,计算机应用,2010年07月。 +class Moments { + +protected: + + // 成员变量:isconst(乘积项标识) + // 如果 isconst 等于 true,则在计算几何矩时乘积项恒等于 1;否则等于 + // 正常的灰度值。 + bool isconst; + + // 成员变量:adjustcenter(调整中心标识) + // 如果 adjustcenter 等于 true,则调整中心坐标;否则使用 + // 原始的中心坐标。 + bool adjustcenter; + +public: + + // 构造函数:Moments + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + Moments() + { + // 使用默认值为类的各个成员变量赋值。 + this->isconst = false; // 图像的灰度值标识默认为 false。 + this->adjustcenter = false; // 调整中心标识默认为 false。 + } + + // 构造函数:Moments + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + Moments( + bool isconst, // 乘积项标识 + bool adjustcenter // 调整中心标识 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->isconst = false; // 图像的灰度值标识默认为 false。 + this->adjustcenter = false; // 调整中心标识默认为 false。 + + // 根据参数列表中的值设定成员变量的初值 + setIsconst(isconst); + setAdjustcenter(adjustcenter); + } + + // 成员方法:getIsconst(获取乘积项标识) + // 获取成员变量 isconst 的值。 + __host__ __device__ bool // 返回值:成员变量 isconst 的值 + getIsconst() const + { + // 返回 isconst 成员变量的值。 + return this->isconst; + } + + // 成员方法:setIsconst(设置乘积项标识) + // 设置成员变量 isconst 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setIsconst( + bool isconst // 设定新的乘积项标识 + ) { + // 将 isconst 成员变量赋成新值 + this->isconst = isconst; + + return NO_ERROR; + } + + // 成员方法:getAdjustcenter(获取调整中心标识) + // 获取成员变量 isconst 的值。 + __host__ __device__ bool // 返回值:成员变量 adjustcenter 的值 + getAdjustcenter() const + { + // 返回 adjustcenter 成员变量的值。 + return this->adjustcenter; + } + + // 成员方法:setAdjustcenter(设置调整中心标识) + // 设置成员变量 adjustcenter 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setAdjustcenter( + bool adjustcenter // 设定新的调整中心标识 + ) { + // 将 adjustcenter 成员变量赋成新值 + this->adjustcenter = adjustcenter; + + return NO_ERROR; + } + + // Host 成员方法:spatialMoments(计算空间矩) + // 首先基于差分矩因子算法计算累进求和,然后根据求和结果推导出空间矩的 + // 各项值。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + spatialMoments( + Image *img, // 输入图像 + MomentSet *momset // 输出空间矩数据集合 + ); + + // Host 成员方法:centralMoments(计算中心矩) + // 首先需要计算空间矩,然后根据公式推导出中心距。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + centralMoments( + Image *img, // 输入图像 + MomentSet *momset // 输出中心矩数据集合 + ); + + // Host 成员方法:centralMoments(计算形状的分布重心和方向) + // 通过中心矩推导出形状的分布中心和方向。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR。 + centralMoments( + Image *img, // 输入图像 + double centers[2], // 分布重心 + double *angle // 分布方向 + ); + + // Host 成员方法:huMoments(计算 Hu 矩) + // 首先需要计算空间矩和中心矩,然后根据公式推导出 Hu 距。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + huMoments( + Image *img, // 输入图像 + MomentSet *momset // 输出 hu 矩数据集合 + ); + + // Host 成员方法:affineMoments(计算 affine 矩) + // 首先需要计算空间矩和中心矩,然后根据公式推导出 affine 距。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + affineMoments( + Image *img, // 输入图像 + MomentSet *momset // 输出 affine 矩数据集合 + ); + + // Host 成员方法:flusserMoments(计算 flusser 矩) + // 首先需要计算空间矩和中心矩,然后根据公式推导出 flusser 距。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + flusserMoments( + Image *img, // 输入图像 + MomentSet *momset // 输出 flusser 矩数据集合 + ); +}; + +#endif + diff --git a/okano_3_0/Morphology.cu b/okano_3_0/Morphology.cu new file mode 100644 index 0000000..a0282ba --- /dev/null +++ b/okano_3_0/Morphology.cu @@ -0,0 +1,683 @@ +// Morphology.cu +// 实现形态学图像算法,包括腐蚀,膨胀,开操作,闭操作 + +#include "Morphology.h" + +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:MOR_USE_INTERMEDIA +// 开关宏,如果使能该宏,则 CLASS 内部提供开运算和闭运算的中间变量,免除返回申 +// 请释放的开销,但这样做会由于中间图像尺寸较大而是的 Cache 作用被削弱,因此, +// 未必会得到好性能。关闭该宏,则每次调用开闭运算,都会临时申请中间变量。 +#define MOR_USE_INTERMEDIA + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:MOR_IMI_WIDTH 和 MOR_IMI_HEIGHT +// 定义了中间图像的尺寸。 +#define MOR_IMI_WIDTH 4000 +#define MOR_IMI_HEIGHT 4000 + +// static变量:_defTpl +// 当用户未定义有效的模板时,使用此默认模板,默认为 3 x 3 +static Template *_defTpl = NULL; + +// Host 函数:_initDefTemplate(初始化默认的模板指针) +// 函数初始化默认模板指针 _defTpl,如果原来模板不为空,则直接返回,否则初始化 +// 为 3 x 3 的默认模板 +static __host__ Template* // 返回值:返回默认模板指针 _defTpl +_initDefTemplate(); + +// Kernel 函数:_erosion(实现腐蚀算法操作) +static __global__ void // Kernel 函数无返回值 +_erosion( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + Template tpl // 模板 +); + +// Kernel 函数:_dilation(实现膨胀算法操作) +static __global__ void // Kernel 函数无返回值 +_dilation( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + Template tpl // 模板 +); + +// Host 函数:_preOp(在算法操作前进行预处理) +// 在进行腐蚀,膨胀操作前,先进行预处理,包括:(1)对输入和输出图像 +// 进行数据准备,包括申请当前Device存储空间;(2)对模板进行处理,包 +// 申请当前Device存储空间 +static __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR +_preOp( + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + Template *tp // 模板 +); + +// Host 函数:_adjustRoiSize(调整 ROI 子图的大小) +// 调整 ROI 子图的大小,使输入和输出的子图大小统一 +static __host__ void // 无返回值 +_adjustRoiSize( + ImageCuda *inimg, // 输入图像 + ImageCuda *outimg // 输出图像 +); + +// Host 函数:_getBlockSize(获取 Block 和 Grid 的尺寸) +// 根据默认的 Block 尺寸,使用最普通的线程划分方法获取 Grid 的尺寸 +static __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR +_getBlockSize( + int width, // 需要处理的宽度 + int height, // 需要处理的高度 + dim3 *gridsize, // 计算获得的 Grid 的尺寸 + dim3 *blocksize // 计算获得的 Block 的尺寸 +); + +// Host 函数:_initDefTemplate(初始化默认的模板指针) +static __host__ Template* _initDefTemplate() +{ + // 如果 _defTpl 不为空,说明已经初始化了,则直接返回 + if (_defTpl != NULL) + return _defTpl; + + // 如果 _defTpl 为空,则初始化为 3 x 3 的模板 + TemplateBasicOp::newTemplate(&_defTpl); + TemplateBasicOp::makeAtHost(_defTpl, 9); + // 分别处理每一个点 + for (int i = 0; i < 9; i++) { + // 分别计算每一个点的横坐标和纵坐标 + _defTpl->tplData[2 * i] = i % 3 - 1; + _defTpl->tplData[2 * i + 1] = i / 3 - 1; + } + return _defTpl; +} + +// Kernel 函数:_erosion(实现腐蚀算法操作) +static __global__ void _erosion(ImageCuda inimg, ImageCuda outimg, Template tpl) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于同一行的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = (blockIdx.x * blockDim.x + threadIdx.x) * 4; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y)/* * 1*/; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + + // 用来记录当前模板所在的位置的指针 + int *curtplptr = tpl.tplData; + + // 用来记录当前输入图像所在位置的指针 + unsigned char *curinptr; + + // 用来保存输出像素在模板范围内的最小像素的值,由于采用并行度缩减策略,一 + // 个像素处理 4 个输出像素,所以这里定义一个大小为 4 的数组,因为是存放 + // 最小值,所以先初始化为最大值 0xff + union { + unsigned char pixel[4]; + unsigned int data; + } _min; + _min.pixel[0] = _min.pixel[1] = _min.pixel[2] = _min.pixel[3] = 0xff; + + // 存放临时像素点的像素值 + unsigned char pixel; + + // 扫描模板范围内的每个输入图像的像素点 + for (int i = 0; i < tpl.count; i++) { + // 计算当前模板位置所在像素的 x 和 y 分量,模板使用相邻的两个下标的 + // 数组表示一个点,所以使当前模板位置的指针作加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 先判断当前像素的 y 分量是否越界,如果越界,则跳过,扫描下一个模板点 + // 如果没有越界,则分别处理当前行的相邻的 4 个像素 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 根据 dx 和 dy 获取第一个像素的位置 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + // 检测此像素的 x 分量是否越界 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 和 min[0] 比较,如果比 min[0] 小,则将当前像素赋值给 min[0] + pixel = *curinptr; + pixel < _min.pixel[0] ? (_min.pixel[0] = pixel) : '\0'; + } + + // 处理当前行的剩下的 3 个像素 + for (int j = 1; j < 4; j++) { + // 获取当前列的下一列的像素的位置 + curinptr++; + // 使 dx 加一,得到当前要处理的像素的 x 分量 + dx++; + // 检测 dy 是否越界 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 和 min[j] 比较,如果比 min[j] 小,则将当前像素赋值给 + // min[j] + pixel = *curinptr; + pixel < _min.pixel[j] ? (_min.pixel[j] = pixel) : '\0'; + } + } + } + } + + // 定义输出图像位置的指针 + unsigned int *outptr; + + // 获取对应的第一个输出图像的位置 + outptr = (unsigned int *)(outimg.imgMeta.imgData + + dstr * outimg.pitchBytes + dstc); + // 将计算得到的 min[0] 赋值给输出图像 + *outptr = _min.data; +} + +// 成员方法:erode +__host__ int Morphology::erode(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + dim3 gridsize; + dim3 blocksize; + + // 检查输入图像,输出图像,以及模板是否为空 + if (inimg == NULL || outimg == NULL || tpl == NULL) + return NULL_POINTER; + + // 对输入图像,输出图像和模板进行预处理 + errcode = _preOp(inimg, outimg, tpl); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入和输出图像的 ROI 子图,使大小统一 + _adjustRoiSize(&insubimgCud, &outsubimgCud); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + errcode = _getBlockSize(outsubimgCud.imgMeta.width, + outsubimgCud.imgMeta.height, + &gridsize, &blocksize); + if (errcode != NO_ERROR) + return errcode; + + // 调用 Kernel 函数进行腐蚀操作 + _erosion<<>>(insubimgCud, outsubimgCud, *tpl); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// Kernel 函数:_dilation(实现膨胀算法操作) +static __global__ void _dilation(ImageCuda inimg, ImageCuda outimg, + Template tpl) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于同一行的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = (blockIdx.x * blockDim.x + threadIdx.x) * 4; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y)/* * 1*/; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + + // 用来记录当前模板所在的位置的指针 + int *curtplptr = tpl.tplData; + + // 用来记录当前输入图像所在位置的指针 + unsigned char *curinptr; + + // 用来保存输出像素在模板范围内的最大像素的值,由于采用并行度缩减策略,一 + // 个像素处理 4 个输出像素,所以这里定义一个大小为 4 的数组,因为是存放 + // 最大值,所以先初始化为最小值 0x00 + union { + unsigned char pixel[4]; + unsigned int data; + } _max; + _max.pixel[0] = _max.pixel[1] = _max.pixel[2] = _max.pixel[3] = 0x00; + + // 存放临时像素点的像素值 + unsigned char pixel; + + // 扫描模板范围内的每个输入图像的像素点 + for (int i = 0; i < tpl.count; i++) { + // 计算当前模板位置所在像素的 x 和 y 分量,模板使用相邻的两个下标的 + // 数组表示一个点,所以使当前模板位置的指针作加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + // 先判断当前像素的 y 分量是否越界,如果越界,则跳过,扫描下一个模板点 + // 如果没有越界,则分别处理当前行的相邻的 4 个像素 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 根据 dx 和 dy 获取第一个像素的位置 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + // 检测此像素的 y 分量是否越界 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 和 max[0] 比较,如果比 max[0] 大,则将当前像素赋值给 max[0] + pixel = *curinptr; + pixel > _max.pixel[0] ? (_max.pixel[0] = pixel) : '\0'; + } + + // 处理当前列的剩下的 3 个像素 + for (int j = 1; j < 4; j++) { + // 获取当前列的下一列的像素的位置 + curinptr++; + // 使 dx 加一,得到当前要处理的像素的 x 分量 + dx++; + // 检测 dy 是否越界 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 和 max[j] 比较,如果比 max[j] 大,则将当前像素值赋值给 + // max[j] + pixel = *curinptr; + pixel > _max.pixel[j] ? (_max.pixel[j] = pixel) : '\0'; + } + } + } + } + + // 定义输出图像位置的指针 + unsigned int *outptr; + + // 获取对应的第一个输出图像的位置 + outptr = (unsigned int *)(outimg.imgMeta.imgData + + dstr * outimg.pitchBytes + dstc); + // 将计算得到的 max[0] 赋值给输出图像 + *outptr = _max.data; +} + +// 成员方法:dilate +__host__ int Morphology::dilate(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + dim3 gridsize; + dim3 blocksize; + + // 检查输入图像,输出图像,以及模板是否为空 + if (inimg == NULL || outimg == NULL || tpl == NULL) + return NULL_POINTER; + + // 对输入图像,输出图像和模板进行预处理 + errcode = _preOp(inimg, outimg, tpl); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入和输出图像的 ROI 子图,使大小统一 + _adjustRoiSize(&insubimgCud, &outsubimgCud); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + errcode = _getBlockSize(outsubimgCud.imgMeta.width, + outsubimgCud.imgMeta.height, + &gridsize, &blocksize); + if (errcode != NO_ERROR) + return errcode; + + // 调用 Kernel 函数进行膨胀操作 + _dilation<<>>(insubimgCud, outsubimgCud, *tpl); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// Host 函数:_preOp(在算法操作前进行预处理) +static __host__ int _preOp(Image *inimg, Image *outimg, Template *tp) +{ + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝到 Device 内存中 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 计算 roi 子图的宽和高 + int roiwidth = inimg->roiX2 - inimg->roiX1; + int roiheight = inimg->roiY2 - inimg->roiY1; + // 如果输出图像无数据,则会创建一个和输出图像子图像尺寸相同的图像 + errcode = ImageBasicOp::makeAtCurrentDevice(outimg, roiwidth, + roiheight); + // 如果创建图像依然操作失败,则返回错误 + if (errcode != NO_ERROR) + return errcode; + } + + // 将模板拷贝到 Device 内存中 + errcode = TemplateBasicOp::copyToCurrentDevice(tp); + if (errcode != NO_ERROR) + return errcode; + + return NO_ERROR; +} + +// Host 函数:_adjustRoiSize(调整输入和输出图像的 ROI 的大小) +inline static __host__ void _adjustRoiSize(ImageCuda *inimg, ImageCuda *outimg) +{ + if (inimg->imgMeta.width > outimg->imgMeta.width) + inimg->imgMeta.width = outimg->imgMeta.width; + else + outimg->imgMeta.width = inimg->imgMeta.width; + + if (inimg->imgMeta.height > outimg->imgMeta.height) + inimg->imgMeta.height = outimg->imgMeta.height; + else + outimg->imgMeta.height = inimg->imgMeta.height; +} + +// Host 函数:_getBlockSize(获取 Block 和 Grid 的尺寸) +inline static __host__ int _getBlockSize(int width, int height, dim3 *gridsize, + dim3 *blocksize) +{ + // 检测 girdsize 和 blocksize 是否是空指针 + if (gridsize == NULL || blocksize == NULL) + return NULL_POINTER; + + // blocksize 使用默认的尺寸 + blocksize->x = DEF_BLOCK_X; + blocksize->y = DEF_BLOCK_Y; + + // 使用最普通的方法划分 Grid + gridsize->x = (width + blocksize->x * 4 - 1) / (blocksize->x * 4); + gridsize->y = (height + blocksize->y * 1 - 1) / (blocksize->y * 1); + + return NO_ERROR; +} + +// 构造函数:Morphology +__host__ Morphology::Morphology(Template *tp) +{ + // 设置类成员中的模板参数。 + setTemplate(tp); + +#ifdef MOR_USE_INTERMEDIA + // 初始化中间图像。如果中间图像没有申请成功,则置为 NULL。 + int errcode; + errcode = ImageBasicOp::newImage(&intermedImg); + if (errcode != NO_ERROR) { + intermedImg = NULL; + return; + } + + // 为中间图像申请内存空间。如果中间图像没有申请成功,则置为 NULL。 + errcode = ImageBasicOp::makeAtCurrentDevice( + intermedImg, MOR_IMI_WIDTH, MOR_IMI_HEIGHT); + if (errcode != NO_ERROR) { + ImageBasicOp::deleteImage(intermedImg); + intermedImg = NULL; + return; + } +#else + intermedImg = NULL; +#endif +} + +// 析构函数:~Morphology +__host__ Morphology::~Morphology() +{ + // 如果中间图像已经申请,则需要释放掉中间图像。 + if (intermedImg != NULL) + ImageBasicOp::deleteImage(intermedImg); +} + +// 成员方法:getTemplate +__host__ Template* Morphology::getTemplate() const +{ + // 如果模板指针和默认模板指针相同,则返回空 + if (this->tpl == _defTpl) + return NULL; + + // 否则返回设置的模板指针 + return this->tpl; +} + +// 成员方法:setTemplate +__host__ int Morphology::setTemplate(Template *tp) +{ + // 如果 tp 为空,则只用默认的模板指针 + if (tp == NULL) { + this->tpl = _initDefTemplate(); + } + // 否则将 tp 赋值给 tpl + else { + this->tpl = tp; + } + return NO_ERROR; +} + +// 成员方法:open(开运算) +__host__ int Morphology::open(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + Image *imiimg; // 局部变量,用来存储腐蚀操作的返回结果,再对此图像 + // 进行膨胀操作 + ImageCuda *imiimgCud; // 只在使用 CLASS 提供的中间图像时使用 + size_t pitchold; // 只在使用 CLASS 提供的中间图像时使用,用于记录原始 + // 原始参数。 + + // 检查输入图像,输出图像,以及模板是否为空 + if (inimg == NULL || outimg == NULL || tpl == NULL) + return NULL_POINTER; + + // 若无法使用 CLASS 自身提供的中间图像,则需要自行申请中间图像,这样的图像 + // 在处理完毕后需要释放掉,因此通过一个 bool 变量标识目前使用的是否是一个临 + // 时申请的中间图像。 + bool useprivimiimg = false; + + if (intermedImg == NULL) { + // 如果 CLASS 的中间图像为 NULL,则说明其在 CLASS 构造时没有成功申请空 + // 间,因此只能使用临时申请的中间图像。 + useprivimiimg = true; + errcode = ImageBasicOp::newImage(&imiimg); + if (errcode != NO_ERROR) + return errcode; + } else { + // 计算当前计算所需要的中间图像尺寸,即两个图像 ROI 区域取较小者。 + int roiw1 = inimg->roiX2 - inimg->roiX1; + int roih1 = inimg->roiY2 - inimg->roiY1; + int roiw2 = outimg->roiX2 - outimg->roiX1; + int roih2 = outimg->roiY2 - outimg->roiY1; + if (roiw2 == 0) roiw2 = roiw1; + if (roih2 == 0) roih2 = roih1; + int roiw = (roiw1 <= roiw2) ? roiw1 : roiw2; + int roih = (roih1 <= roih2) ? roih1 : roih2; + + // 根据当前计算所需要的中间图像尺寸,来决定是否使用 CLASS 提供的中间图 + // 像。 + if (roiw <= intermedImg->width && roih <= intermedImg->height) { + // 如果 CLASS 提供的图像图像可以满足尺寸要求,则直接使用 CLASS 提供 + // 的中间图像。 + useprivimiimg = false; + + // 这里需要调整一下 CLASS 提供图像的 ROI 尺寸,以防止上次计算对 ROI + // 尺寸的影响,而得到不正确的图像结果。考虑到 cache 问题,由于较大的 + // 中间图像会导致 cache 局部性的问题,这里我们强行更改了 pitch 以保 + // 证具有更好的局部性。 + // 首先,将 CLASS 提供的中间图像的元数据复制出来一份。 + imiimg = intermedImg; + imiimgCud = IMAGE_CUDA(imiimg); + pitchold = imiimgCud->pitchBytes; + + // 然后强行修改 CLASS 的尺寸参数(由于判断了尺寸的合适性,因此,修 + // 改参数的操作是安全的。 + imiimg->width = roiw; + imiimg->height = roih; + imiimgCud->pitchBytes = roiw; + imiimg->roiX1 = imiimg->roiY1 = 0; + imiimg->roiX2 = roiw; + imiimg->roiY2 = roih; + } else { + // 如果尺寸不满足要求,只能使用临时申请的中间图像。 + useprivimiimg = true; + errcode = ImageBasicOp::newImage(&imiimg); + if (errcode != NO_ERROR) + return errcode; + } + } + + do { + // 先对输入图像进行腐蚀操作,结果临时存在中间图像中 + errcode = erode(inimg, imiimg); + if (errcode != NO_ERROR) + break; + + // 再将腐蚀操作得到的中间图像进行膨胀操作,结果放在 outimg 中 + errcode = dilate(imiimg, outimg); + if (errcode != NO_ERROR) + break; + } while (0); + + // 释放中间图像的资源 + if (useprivimiimg) { + ImageBasicOp::deleteImage(imiimg); + } else { + // 还原 CLASS 中间图像回原来的参数。 + imiimg->width = MOR_IMI_WIDTH; + imiimg->height = MOR_IMI_HEIGHT; + imiimgCud->pitchBytes = pitchold; + imiimg->roiX1 = imiimg->roiY1 = 0; + imiimg->roiX2 = MOR_IMI_WIDTH; + imiimg->roiY2 = MOR_IMI_HEIGHT; + } + + return errcode; +} + +// 成员方法:close +__host__ int Morphology::close(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + Image *imiimg; // 局部变量,用来存储腐蚀操作的返回结果,再对此图像 + // 进行膨胀操作 + ImageCuda *imiimgCud; // 只在使用 CLASS 提供的中间图像时使用 + size_t pitchold; // 只在使用 CLASS 提供的中间图像时使用,用于记录原始 + // 原始参数。 + + // 检查输入图像,输出图像,以及模板是否为空 + if (inimg == NULL || outimg == NULL || tpl == NULL) + return NULL_POINTER; + + // 若无法使用 CLASS 自身提供的中间图像,则需要自行申请中间图像,这样的图像 + // 在处理完毕后需要释放掉,因此通过一个 bool 变量标识目前使用的是否是一个临 + // 时申请的中间图像。 + bool useprivimiimg = false; + + if (intermedImg == NULL) { + // 如果 CLASS 的中间图像为 NULL,则说明其在 CLASS 构造时没有成功申请空 + // 间,因此只能使用临时申请的中间图像。 + useprivimiimg = true; + errcode = ImageBasicOp::newImage(&imiimg); + if (errcode != NO_ERROR) + return errcode; + } else { + // 计算当前计算所需要的中间图像尺寸,即两个图像 ROI 区域取较小者。 + int roiw1 = inimg->roiX2 - inimg->roiX1; + int roih1 = inimg->roiY2 - inimg->roiY1; + int roiw2 = outimg->roiX2 - outimg->roiX1; + int roih2 = outimg->roiY2 - outimg->roiY1; + if (roiw2 == 0) roiw2 = roiw1; + if (roih2 == 0) roih2 = roih1; + int roiw = (roiw1 <= roiw2) ? roiw1 : roiw2; + int roih = (roih1 <= roih2) ? roih1 : roih2; + + // 根据当前计算所需要的中间图像尺寸,来决定是否使用 CLASS 提供的中间图 + // 像。 + if (roiw <= intermedImg->width && roih <= intermedImg->height) { + // 如果 CLASS 提供的图像图像可以满足尺寸要求,则直接使用 CLASS 提供 + // 的中间图像。 + useprivimiimg = false; + + // 这里需要调整一下 CLASS 提供图像的 ROI 尺寸,以防止上次计算对 ROI + // 尺寸的影响,而得到不正确的图像结果。考虑到 cache 问题,由于较大的 + // 中间图像会导致 cache 局部性的问题,这里我们强行更改了 pitch 以保 + // 证具有更好的局部性。 + // 首先,将 CLASS 提供的中间图像的元数据复制出来一份。 + imiimg = intermedImg; + imiimgCud = IMAGE_CUDA(imiimg); + pitchold = imiimgCud->pitchBytes; + + // 然后强行修改 CLASS 的尺寸参数(由于判断了尺寸的合适性,因此,修 + // 改参数的操作是安全的。 + imiimg->width = roiw; + imiimg->height = roih; + imiimgCud->pitchBytes = roiw; + imiimg->roiX1 = imiimg->roiY1 = 0; + imiimg->roiX2 = roiw; + imiimg->roiY2 = roih; + } else { + // 如果尺寸不满足要求,只能使用临时申请的中间图像。 + useprivimiimg = true; + errcode = ImageBasicOp::newImage(&imiimg); + if (errcode != NO_ERROR) + return errcode; + } + } + + do { + // 先对输入图像进行膨胀操作,结果临时存在中间图像中 + errcode = dilate(inimg, imiimg); + if (errcode != NO_ERROR) + break; + + // 再将腐蚀操作得到的中间图像进行膨胀操作,结果放在 outimg 中 + errcode = erode(imiimg, outimg); + if (errcode != NO_ERROR) + break; + } while (0); + + // 释放中间图像的资源 + if (useprivimiimg) { + ImageBasicOp::deleteImage(imiimg); + } else { + // 还原 CLASS 中间图像回原来的参数。 + imiimg->width = MOR_IMI_WIDTH; + imiimg->height = MOR_IMI_HEIGHT; + imiimgCud->pitchBytes = pitchold; + imiimg->roiX1 = imiimg->roiY1 = 0; + imiimg->roiX2 = MOR_IMI_WIDTH; + imiimg->roiY2 = MOR_IMI_HEIGHT; + } + + return errcode; +} + diff --git a/okano_3_0/Morphology.h b/okano_3_0/Morphology.h new file mode 100644 index 0000000..aff91f9 --- /dev/null +++ b/okano_3_0/Morphology.h @@ -0,0 +1,115 @@ +// Morphology.h +// 创建人:罗劼 +// +// 形态学图像算法(morphology algorithm) +// 功能说明:包含四种形态学图像算法,分别为:腐蚀,膨胀,开操作,闭操作 +// +// 修订历史: +// 2012年09月03日(罗劼) +// 初始版本。 +// 2012年09月05日(罗劼,于玉龙) +// 修改了一些注释和代码中的错误规范 +// 2012年09月06日(罗劼,于玉龙) +// 增加了 ROI 子图的处理和代码中的错误规范 +// 2012年09月23日(于玉龙,罗劼) +// 修改了计算默认模版的一处错误 +// 2012年09月27日(罗劼,于玉龙) +// 修改了代码中的一处错误规范 +// 2012年10月26日(罗劼) +// gridsize 中的一处不合理的位置 +// 2012年10月29日(于玉龙) +// 为开运算和闭运算增加了中间图像缓冲区。 +// 修正了代码中部分潜在的 Bug。 +// 2013年05月28日(于玉龙) +// 优化了算法实现技巧,提高了性能近。修正了代码中几处不合理的设计。 + +#ifndef __MORPHOLOGY_H__ +#define __MORPHOLOGY_H__ + +#include "Image.h" +#include "Template.h" + + +// 类:Morphology(形态学图像算法) +// 继承自:无 +// 包含四种形态学图像算法,分别为:腐蚀,膨胀,开操作,闭操作 +class Morphology { + +protected: + + // 成员变量:tpl(模板指针) + // 在腐蚀和膨胀操作中需要通过它来指定图像中要处理的像素范围 + Template *tpl; + + // 成员变量:intermedImg(中间图像) + // 该图像作为缓冲区,用于开闭运算,可以避免每次调用开闭运算都需要重新申请和 + // 释放内存空间,提高运算性能。该图像通过调整 ROI 尺寸来适应不同尺寸的图 + // 像。 + Image *intermedImg; + +public: + + // 构造函数:Morphology + // 传递模板指针,如果不传,则默认为空 + __host__ + Morphology( + Template *tp = NULL //腐蚀和膨胀操作需要使用到模板,默认为空 + ); + + // 析构函数:~Morphology + // 用于释放中间图像。 + __host__ + ~Morphology(); + + // 成员方法:getTemplate + // 获取模板指针,如果模板指针和默认模板指针相同,则返回空 + __host__ Template* // 返回值:如果模板和默认模板指针相同,则返 + // 回空,否则返回模板指针 + getTemplate() const; + + // 成员方法:setTemplate + // 设置模板指针,如果参数 tp 为空,这使用默认的模板 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR + setTemplate( + Template *tp // 腐蚀和膨胀操作需要使用的模板 + ); + + // 成员方法:erode + // 对图像进行腐蚀操作,根据模板,找到附近最小的像素,作为当前像素的输出 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR,否则返回 + // 相应的错误码 + erode( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // 成员方法:dilate + // 对图像进行膨胀操作,根据模板,找到附近最大的像素,当作当前像素的输出 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR,否则返回 + // 相应的错误码 + dilate( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // 成员方法:open + // 对图像进行开操作,先对图像进行腐蚀,然后再进行膨胀 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR,否则返回 + // 相应的错误码 + open( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // 成员方法:close + // 对图像进行关操作,先对图像进行膨胀,然货再进行腐蚀 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR,否则返回 + // 相应的错误码 + close( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/MultiConnectRegion.cu b/okano_3_0/MultiConnectRegion.cu new file mode 100644 index 0000000..cffb190 --- /dev/null +++ b/okano_3_0/MultiConnectRegion.cu @@ -0,0 +1,63 @@ +#include "MultiConnectRegion.h" +#include "ImageFilter.h" +#include "ConnectRegion.h" +#include +using namespace std; + +// 成员方法:multiConnectRegion (多图连通区域) +int MultiConnectRegion::multiConnectRegion(Image *inimg, Image ***outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if(inimg == NULL) + return NULL_POINTER; + + // 局部变量,错误码 + int errcode; + + // 局部变量,多图中间结果 + Image **midoutimg; + + // 定义 ImageFilter 对象,用于图像过滤 + ImageFilter IF(thresholdArr, thresholdNum); + + // 定义ConnectRegion 对象,用于求连通区域 + ConnectRegion CR(threshold, maxArea, minArea); + + // 对输入图像进行阈值过滤 + errcode = IF.calImageFilterOpt(inimg, &midoutimg); + if (errcode != NO_ERROR) { + return errcode; + } + + // 为输出图像开辟空间 + *outimg = new Image *[thresholdNum]; + for(int i = 0; i < thresholdNum; i++) { + errcode = ImageBasicOp::newImage(&((*outimg)[i])); + if (errcode != NO_ERROR) { + return errcode; + } + + errcode = ImageBasicOp::makeAtCurrentDevice((*outimg)[i], inimg->width, + inimg->height); + if (errcode != NO_ERROR) { + return errcode; + } + } + + // 对每一幅图像做连通区域 + for(int i = 0; i < thresholdNum; i++) { + errcode = CR.connectRegion(midoutimg[i], (*outimg)[i]); + if (errcode != NO_ERROR) { + return errcode; + } + } + + // 释放中间图像 + for(int i = 0;i < thresholdNum; i++) + { + ImageBasicOp::deleteImage(midoutimg[i]); + } + + return NO_ERROR; +} + \ No newline at end of file diff --git a/okano_3_0/MultiConnectRegion.h b/okano_3_0/MultiConnectRegion.h new file mode 100644 index 0000000..dd40b95 --- /dev/null +++ b/okano_3_0/MultiConnectRegion.h @@ -0,0 +1,231 @@ +// MultiConnectRegion.h +// 创建人:丁燎原 +// +// 多图连通区域 (MultiConnectRegion) +// 功能说明:给定一幅图像,首先根据用户输入的阈值数组,取出每个阈值区间 +// 对应的图像(详见 ImageFilter ),然后调用 ConnectRegion 对 +// 每一幅图像做连通区域操作。 +// +// 修订历史: +// 2014年10月10日(丁燎原) +// 初始版本 + +#ifndef __MULTICONNECTREGION_H__ +#define __MULTICONNECTREGION_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:MultiConnectRegion +// 继承自:无 +// 给定一幅图像,首先根据用户输入的阈值数组,取出每个阈值区间对应的图像 +// (详见 ImageFilter ), +// 然后调用 ConnectRegion 对每一幅图像做连通区域操作。 +class MultiConnectRegion { +protected: + // 成员变量:thresholdArr + // 阈值数组 + int *thresholdArr; + + // 成员变量:thresholdNum + // 阈值数组大小 + int thresholdNum; + + // 成员变量:threshold(给定阈值) + // 进行区域连通的给定值,当两个点满足八邻域关系, + // 且灰度值之差的绝对值小于该值时,这两点属于同一区域。 + int threshold; + + // 成员变量:maxArea 和 minArea(区域面积的最小和最大值) + // 进行区域面积判断时的面积值最大最小的范围。 + int maxArea, minArea; + +public: + // 构造函数:ConnectRegion + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值 + __host__ + MultiConnectRegion() { + // 阈值数组大小初始化 + thresholdNum = 5; + + // 阈值数组开辟空间 + thresholdArr = new int[5]; + + // 初始化阈值数组 + for(int i = 0; i < thresholdNum; i++) { + thresholdArr[i] = 50 * i; + } + this->threshold = 0; // 给定阈值默认为0 + this->maxArea = 100000; // 区域最大面积默认为100000 + this->minArea = 0; // 区域最小面积默认为10 + } + + // 构造函数:ConnectRegion + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + MultiConnectRegion( + int *thresholdArr, // 阈值数组 + int thresholdnum, // 阈值数组大小 + int threshold, // 给定的标记阈值 + int maxArea, // 区域面积的最大值 + int minArea // 区域面积的最小值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给 + // 了非法的初始值而使系统进入一个未知的状态。 + this->thresholdNum = 5; + this->thresholdArr = new int[5]; + for(int i = 0; i < thresholdNum; i++) { + this->thresholdArr[i] = 50 * i; + } + this->threshold = 0; + this->maxArea = 100000; + this->minArea = 0; + + // 根据参数列表中的值设定成员变量的初值 + setThresholdArrAndNum(thresholdArr, thresholdnum); + setThreshold(threshold); + setMaxArea(maxArea); + setMinArea(minArea); + } + + // 成员方法: setThresholdArrAndNum + // 设置成员变量 thresholdArr 和 thresholdNum + __host__ int // 返回值:函数是否正确执行,若 + // 函数正确执行,返回 NO_ERROR + setThresholdArrAndNum( + int *thresholdArr, // 阈值数组 + int thresholdnum // 阈值数组大小 + ) { + // 判断用户输入是否合法 + if(thresholdArr == NULL || thresholdnum <= 0) { + return INVALID_DATA; + } + else { + + // 释放原阈值数组空间 + delete []this->thresholdArr; + + // 设置 thresholdNum + this->thresholdNum = thresholdnum; + + // 为 thresholdArr 开辟空间 + this->thresholdArr = new int[thresholdNum]; + + // 初始化 thresholdArr 数组 + for(int i = 0; i < thresholdNum; i++) { + this->thresholdArr[i] = thresholdArr[i]; + } + + return NO_ERROR; + } + + } + + // 成员方法:setThreshold(设置阈值) + // 设置 threshold 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setThreshold( + int threshold // 指定的阈值大小。 + ) { + // 将 threshold 成员变量赋值成新值 + this->threshold = threshold; + + return NO_ERROR; + } + + // 成员方法:setMinArea(设置进行区域面积判断时的最小面积值) + // 设置 minArea 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMinArea( + int minArea // 指定的进行区域面积判断时的最小面积值。 + ) { + // 将 minArea 成员变量赋成新值 + this->minArea = minArea; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:setMaxArea(设置进行区域面积判断时的最大面积值) + // 设置 maxArea 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMaxArea( + int maxArea // 指定的进行区域面积判断时的最大面积值。 + ) { + // 将 maxArea 成员变量赋成新值 + this->maxArea = maxArea; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getThresholdArr + // 获取 thresholdArr 成员变量的首地址 + __host__ int * // 返回值:当前 thresholdArr 成员变量的首地址 + getThresholdArr() const // + { + // 返回 thresholdArr 成员变量的首地址 + return thresholdArr; + } + + // 成员方法:getThresholdNum + // 获取 thresholdNum 成员变量的值 + __host__ int // 返回值: 当前 thresholdNum 成员变量的值 + getThresholdNum() const // + { + // 返回 thresholdNum 成员变量的值 + return thresholdNum; + } + + // 成员方法:getThreshold(读取阈值) + // 读取 threshold 成员变量的值。 + __host__ __device__ int // 返回值:当前 threshold 成员变量的值。 + getThreshold() const + { + // 返回 threshold 成员变量的值。 + return this->threshold; + } + + // 成员方法:getMinArea(读取进行区域面积判断时的最小面积值) + // 读取 minArea 成员变量的值。 + __host__ __device__ int // 返回值:当前 minArea 成员变量的值。 + getMinArea() const + { + // 返回 minArea 成员变量的值。 + return this->minArea; + } + + // 成员方法:getMaxArea(读取进行区域面积判断时的最小面积值) + // 读取 maxArea 成员变量的值。 + __host__ __device__ int // 返回值:当前 maxArea 成员变量的值。 + getMaxArea() const + { + // 返回 maxArea 成员变量的值。 + return this->maxArea; + } + + // 成员方法:multiConnectRegion (多图连通区域) + // 给定一幅图像,首先根据用户输入的阈值数组,取出每个阈值区间对应的图像 + // (详见 ImageFilter ), + // 然后调用 ConnectRegion 对每一幅图像做连通区域操作。注:outimg 不需要用户 + // 开辟空间,但需用户释放空间。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + multiConnectRegion( + Image *inimg, // 输入图像 + Image ***outimg // 输出图像数组 + ); + + // 析构函数:~ImageFilter + // 释放 threshold 成员变量 + __host__ + ~MultiConnectRegion() { + // 释放 thresholdArr 成员变量 + delete []thresholdArr; + } +}; + +#endif diff --git a/okano_3_0/MultiThreshold.cu b/okano_3_0/MultiThreshold.cu new file mode 100644 index 0000000..0a01470 --- /dev/null +++ b/okano_3_0/MultiThreshold.cu @@ -0,0 +1,183 @@ +// MultiThreshold.cu +// 实现图像的多阈值二值化图像生成操作 + +#include "MultiThreshold.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏: DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数:_multithresholdKer(多阈值二值化图像生成) +// 对输入图像进行多阈值二值化处理。以 1 - 254 内的所有的灰度为阈值,同时生成 +// 254个2值化结果(0-1)图像。 +static __global__ void // Kernel 函数无返回值 +_multithresholdKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg[] // 输出图像 +); + +// Kernel 函数: _multithresholdKer(多阈值二值化图像生成) +static __global__ void _multithresholdKer(ImageCuda inimg, + ImageCuda *outimg) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标的 + // x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻行上, + // 因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + int z = blockIdx.z; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height || z >= 254) + return; + + // 计算对应阈值,由 1 - 254 + int threshold = z + 1; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg[z].pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + outimg[z].imgMeta.imgData[outidx] = (intemp >= threshold ? 255 : 0); + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg[z].imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg[z].pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 如果输入图像的该位置的像素值大于等于 threshold,则将输出图像中对应 + // 位置的像素值置为 255;否则将输出图像中对应位置的像素值置为 0。线程 + // 中处理的第一个点。 + outimg[z].imgMeta.imgData[outidx] = (intemp >= threshold ? 255 : 0); + } +} + +// Host 成员方法:multithreshold(多阈值二值化处理) +__host__ int MultiThreshold::multithreshold(Image *inimg, Image *outimg[254]) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 检查输出图像是否为 NULL,如果为 NULL 直接报错返回。 + for(int i = 0; i < 254; i++) { + if (outimg[i] == NULL) + return NULL_POINTER; + } + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + for (int i = 0; i < 254; i++) { + errcode = ImageBasicOp::copyToCurrentDevice(outimg[i]); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输 + // 入图像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice(outimg[i], + inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud[254], *outsubimgCudDev; + + // 对 254 幅输出图像分别进行提取。 + for (int i = 0; i < 254; i++) { + errcode = ImageBasicOp::roiSubImage(outimg[i], &outsubimgCud[i]); + if (errcode != NO_ERROR) + return errcode; + } + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一。 + for (int i = 0; i < 254; i++) { + if (insubimgCud.imgMeta.width > outsubimgCud[i].imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud[i].imgMeta.width; + else + outsubimgCud[i].imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud[i].imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud[i].imgMeta.height; + else + outsubimgCud[i].imgMeta.height = insubimgCud.imgMeta.height; + } + + // 为 outsubimgCudDev 分配内存空间。 + errcode = cudaMalloc((void **)&outsubimgCudDev, + 254 * sizeof (ImageCuda)); + if (errcode != NO_ERROR) + return errcode; + + // 将 Host 上的 outsubimgCudDev 拷贝到 Device 上。 + errcode = cudaMemcpy(outsubimgCudDev, outsubimgCud, + 254 * sizeof (ImageCuda), cudaMemcpyHostToDevice); + if (errcode != NO_ERROR) { + cudaFree(outsubimgCudDev); + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + blocksize.z = 1; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 设定 gridsize.z 的大小为 254,存储 1 - 254 的阈值。 + gridsize.z = 254; + + // 调用 Kernel 函数,实现 254 幅图像生成 + _multithresholdKer<<>>(insubimgCud, outsubimgCudDev); + + // 释放已分配的数组内存,避免内存泄露 + cudaFree(outsubimgCudDev); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/MultiThreshold.h b/okano_3_0/MultiThreshold.h new file mode 100644 index 0000000..26f3c55 --- /dev/null +++ b/okano_3_0/MultiThreshold.h @@ -0,0 +1,48 @@ +// MultiThreshold.h +// 创建人:仲思惠 +// +// 多阈值二值化图像生成(MultiThreshold) +// 功能说明:根据设定的阈值,对灰度图像进行二值化处理,得到二值图像。 +// +// 修订历史: +// 2012年10月19日(仲思惠) +// 初始版本 +// 2012年10月20日(王媛媛、仲思惠) +// 去除了多余的成员变量 +// 2012年10月25日(于玉龙、王媛媛) +// 解决了同时生成 254 幅图像时图像无法输出的问题 +// 2012年10月27日(仲思惠) +// 修正了代码的格式,添加了一些注释 + +#ifndef __MULTITHRESHOLD_H__ +#define __MULTITHRESHOLD_H__ + +#include "Image.h" + +// 类:MultiThreshold +// 继承自:无 +// 对灰度图像进行多阈值二值化处理,以 1 - 254 内的所有的灰度为阈值,同时生成 +// 254 个 2 值化结果(0 - 1)图像。 +class MultiThreshold { + +public: + + // 构造函数:Multithreshold + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。该函数没有任何内 + // 容。 + __host__ __device__ + MultiThreshold(){} + + // Host 成员方法:multithreshold(多阈值二值化处理) + // 对输入图像进行多阈值二值化处理。以 1 - 254 内的所有的灰度为阈值,同时 + // 生成 254 个 2 值化结果(0 - 1)图像。 + __host__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 + multithreshold( + Image *inimg, // 输入图像 + Image *outimg[254] // 输出图像集合 + ); +}; + +#endif + diff --git a/okano_3_0/Multidecrease.cu b/okano_3_0/Multidecrease.cu new file mode 100644 index 0000000..e5dd193 --- /dev/null +++ b/okano_3_0/Multidecrease.cu @@ -0,0 +1,485 @@ +// Multidecrease.cu +// 实现图像的多阈值N值化图像生成操作 + +#include "Multidecrease.h" +#include "Histogram.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X ºÍ DEF_BLOCK_Y +// 定义了默认的线程块尺寸 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数: _multidecrease_frontKer(前向 N 值化) +// 根据给定的阈值集合对图像进行 N 值化处理。判断当前像素点灰度值所在的阈值区间, +// 并将处于某个阈值区间的像素点设定为该阈值区间的前向阈值(即阈值区间的左端点)。 +static __global__ void // Kernel 函数无返回值 +_multidecrease_frontKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char *thresholds, // 阈值集合 + int thresnum // 阈值个数 +); + +// Kernel 函数: _multidecrease_backKer(后向 N 值化) +// 根据给定的阈值集合对图像进行 N 值化处理。判断当前像素点灰度值所在的阈值区间, +// 并将处于某个阈值区间的像素点设定为该阈值区间的后向阈值(即阈值区间的右端点)。 +static __global__ void // Kernel 函数无返回值 +_multidecrease_backKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char *thresholds, // 阈值集合 + int thresnum // 阈值个数 +); + +// Kernel 函数: _multidecrease_frontKer(前向 N 值化) +static __global__ void _multidecrease_frontKer(ImageCuda inimg, ImageCuda outimg, + unsigned char *thresholds, int thresnum) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素。 + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 前向阈值。线程中处理的第一个点。 + for (int i = 1; i < thresnum; i++) { + if (intemp == 255) { + outimg.imgMeta.imgData[outidx] = thresholds[thresnum - 2]; + break; + } + if (intemp >= thresholds[i-1] && intemp < thresholds[i]) { + outimg.imgMeta.imgData[outidx] = thresholds[i-1]; + break; + } + } + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 前向阈值。 + for (int j = 1; j < thresnum; j++) { + if (intemp == 255) { + outimg.imgMeta.imgData[outidx] = thresholds[thresnum - 2]; + break; + } + if (intemp >= thresholds[j-1] && intemp < thresholds[j]) { + outimg.imgMeta.imgData[outidx] = thresholds[j-1]; + break; + } + } + } +} + + +// Kernel 函数: _multidecrease_backKer(后向 N 值化) +static __global__ void _multidecrease_backKer(ImageCuda inimg, ImageCuda outimg, + unsigned char *thresholds, int thresnum) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素。 + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 后向阈值。线程中处理的第一个点。线程中处理的第一个点。 + for (int i = 1; i < thresnum; i++) { + if (intemp == 0) { + outimg.imgMeta.imgData[outidx] = thresholds[1]; + break; + } + if (intemp > thresholds[i-1] && intemp <= thresholds[i]) { + outimg.imgMeta.imgData[outidx] = thresholds[i]; + break; + } + } + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 后向阈值。 + for (int j = 1; j < thresnum; j++) { + if (intemp == 0) { + outimg.imgMeta.imgData[outidx] = thresholds[1]; + break; + } + if (intemp > thresholds[j-1] && intemp <= thresholds[j]) { + outimg.imgMeta.imgData[outidx] = thresholds[j]; + break; + } + } + } +} + +// 存储阈值的结构 ThresNode +class ThresNode { + +public: + int thres; // 当前阈值节点的阈值 + int rangeA; // 当前阈值节点的前向范围 + int rangeB; // 当前阈值节点的后向范围 + ThresNode *leftchild; // 当前阈值节点的左子节点 + ThresNode *rightchild; // 当前阈值节点的右子节点 + ThresNode(){}; +}; + +// 给定直方图信息及搜索范围,在此范围内搜索双峰之间的谷值 +__host__ int gettrough(int rangea, int rangeb, unsigned int *his, + unsigned int widthrange, unsigned int pixelrange) { + // 判断区间的值是否正确,当范围出错时返回 -1 + if(rangea < 0 || rangeb < 0 || rangea > 255 || rangeb > 255 + || rangea >= rangeb ||abs(rangea - rangeb) < widthrange) { + return -1; + } + int minnum = 0; // 范围内像素点数目最少的灰度值 + for(int i = rangea; i <= rangeb; i++){ + if (his[i] < his[minnum]) + minnum = i; + } + int firstpeak = minnum; // 范围内的第一峰值 + int secondpeak = minnum; // 范围内的第二峰值 + int secondfront = 1; // 判断坐标轴上的位置,第二峰值是否在第一峰值的前方 + + // 搜索第一峰值 + for (int i = rangea; i <= rangeb; i++) { + if (his[i] > his[firstpeak]) { + firstpeak = i; + } + } + int trough = firstpeak; // 双峰之间的谷值 + + // 分别将区间的左右顶点值与第一峰值做差,与 widthrange 做比较,判断能否求得 + // 第二峰值。若大于 widthrange,则可以求得;若小于,则不可求得。 + if ((firstpeak - rangea) >= widthrange) { + for (int i = rangea; i < (firstpeak - widthrange); i++) { + if (his[i] > his[secondpeak] && his[i] < his[firstpeak]) + secondpeak = i; + } + } + else { + // 不在范围内,则第二峰值不可能在第一峰值的前方。 + secondfront = 0; + } + if (rangeb - firstpeak >= widthrange) { + for (int i = (firstpeak + widthrange); i < rangeb; i++) { + if (his[i] > his[secondpeak] && his[i] < his[firstpeak]) { + secondpeak = i; + // 此时代表第二峰值在第一峰值的后方 + secondfront = 0; + } + } + } + else if (secondfront == 0) { + // 第一峰值的前后均不可求得第二峰值,故无法求得谷值,故退出计算。 + return -1; + } + // 第一峰值在前,第二峰值在后 + if (secondfront == 0) { + for (int i = firstpeak + 1; i < secondpeak; i++){ + if (his[i] < his[trough]) + trough = i; + } + } + // 第一峰值在后,第二峰值在前 + else { + for (int i = secondpeak + 1; i < firstpeak; i++){ + if (his[i] < his[trough]) + trough = i; + } + } + + // 若第二峰值与谷值的像素值的差比设定的范围小,则说明该谷值无效,返回 -1 + if ((his[secondpeak] - his[trough]) < pixelrange ) { + return -1; + } + else + return trough; +}; + +// 根据直方图信息,创建一棵存储阈值信息的二叉树 +void createtree(ThresNode *tree, unsigned int * his, + unsigned int widthrange, unsigned int pixelrange) { + // 获取当前节点的阈值 + + int thres = gettrough(tree->rangeA, tree->rangeB, his, widthrange, pixelrange); + // 判断阈值节点是否合法 + if (thres == -1) { + tree->thres = -1; + } + else{ + tree->thres = thres; + // 为阈值节点创建其左子节点,并设定左子节点的搜索范围 + ThresNode * leftc = new ThresNode(); + tree->leftchild = leftc; + leftc->rangeA = tree->rangeA; + leftc->rangeB = tree->thres; + createtree(tree->leftchild, his, widthrange, pixelrange); + + // 为阈值节点创建其右子节点,并设定其右子节点的搜索范围 + ThresNode *rightc = new ThresNode(); + tree->rightchild = rightc; + rightc->rangeA = tree->thres + 1; + rightc->rangeB = tree->rangeB; + createtree(tree->rightchild, his, widthrange, pixelrange); + } +}; + +// 获取所有阈值 +void searchtree(ThresNode *tree, unsigned char *thresholds, int &thresnum) { + if (tree -> thres != -1) { + // 通过对二叉树进行中序遍历,按照从小到大的顺序存储所有阈值 + searchtree(tree->leftchild, thresholds, thresnum); + thresholds[thresnum++] = (unsigned char)tree->thres; + searchtree(tree->rightchild, thresholds, thresnum); + } +}; + +// Host 成员方法:multidecrease(多阈值N值化处理) +__host__ int Multidecrease::multidecrease(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 获取当前图像的直方图 + Histogram h; + unsigned int his[256]; + h.histogram(inimg, his, true); + + // 存储当前图像的所有粗分割阈值 + unsigned char *devthreshlods; + unsigned char *hostthresholds = new unsigned char [20]; + // 存储当前图像的所有粗分割阈值个数。 + // 为了计算方便将第一个阈值设为 0 。 + int thresnum = 1; + hostthresholds[0] = 0; + + // 创建阈值的根节点信息,并设定其搜索范围 + ThresNode * tn = new ThresNode(); + tn->rangeA = 0; + tn->rangeB = 255; + // 根据当前图像直方图信息建立阈值二叉树 + createtree(tn, his, this->getwidthrange(), this->getpixelrange()); + // 对二叉树进行搜索和存储 + searchtree(tn, hostthresholds, thresnum); + + // 图像总像素数 + int sumpixel = 0; + for (int i = 0; i < 256; i++) { + sumpixel += his[i]; + } + + // 数组 pix_w[i] 记录图像中值为i的像素占总像素数的比例 + float pix_w[256]; + // 计算 pix_w[i] 的值 + for (int i = 0; i < 256; i++) { + pix_w[i] = ((float)his[i])/sumpixel; + } + + // 使用 OTSU 方法对各个粗分割阈值进行搜索,找到使类内方差最小的最佳阈值。 + for (int countthres = 0; countthres < thresnum - 1; countthres++){ + float min = 100000.0; // 类内方差的最小值 + int threshold = 0; // 最佳阈值 + float Wk = 0.0; // 第 k 个类的概率的累加和 + float Ukp = 0.0; // 第 k 个类的各个像素值与概率乘积的累加和 + float Uk = 0.0; // 第 k 个类的均值 + float Qk = 0.0; // 第 k 个类的方差 + + // 在每个粗分割阈值的松弛余量范围内,进行 OTSU 法搜索,直到找到最 + // 小类内方差,此时对应的即是最佳阈值。 + for (int j = -5; j < 6; j++) { + for (int i = hostthresholds[countthres] + 1; + i <= (hostthresholds[countthres + 1] + j); i++){ + + // 计算类的概率的累加和 + Wk+=pix_w[i]; + + // 计算像素值与其概率乘积的累加和 + Ukp+=i * pix_w[i]; + } + + // 计算类的均值 + Uk = Ukp/Wk; + + for (int i = hostthresholds[countthres] + 1; + i <= (hostthresholds[countthres + 1] + j); i++){ + // 再次搜索,计算类的方差 + Qk = (i - Uk)*(i - Uk) * pix_w[i]; + } + + // 判断当前方差是否小于 min,若是则覆盖最小值,并存储当前阈值。 + if (min > Qk) { + min = Qk; + threshold = hostthresholds[countthres + 1] + j; + } + } + // 更新阈值集合的值为求得的最佳阈值。 + hostthresholds[countthres + 1] = threshold; + } + + // 返回最佳阈值集合至 threshold,并跳过第一个阈值(0)。 + threshold = new unsigned char[thresnum -1]; + for (int i = 1; i < thresnum; i++) { + this->threshold[i -1] = hostthresholds[i]; + } + + // 将阈值集合的最后一个阈值设定为255。 + hostthresholds[thresnum] = 255; + + // 为标记数组分配大小。 + errcode = cudaMalloc((void **)&devthreshlods, (thresnum + 1) * sizeof (unsigned char)); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 为标记数组设定初值。 + errcode = cudaMemset(devthreshlods, 0, (thresnum + 1) * sizeof (unsigned char)); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 将数组复制至 device 端。 + errcode = cudaMemcpy(devthreshlods, hostthresholds, (thresnum + 1) * sizeof (unsigned char), + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 判断当前的 stateflag 是前向标记还是后向标记,并根据其值调用对应的 Kernel 函数。 + if (this->stateflag == MD_FRONT) { + _multidecrease_frontKer<<>>( + insubimgCud, outsubimgCud, devthreshlods, (thresnum + 1)); + } + else { + _multidecrease_backKer<<>>( + insubimgCud, outsubimgCud, devthreshlods, (thresnum + 1)); + } + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/Multidecrease.h b/okano_3_0/Multidecrease.h new file mode 100644 index 0000000..f81cf46 --- /dev/null +++ b/okano_3_0/Multidecrease.h @@ -0,0 +1,110 @@ +// Multidecrease.h +// 创建者:仲思惠 +// N值化(Multidecrease) + +// 功能说明:将图像的 256 个灰度级,降低为 N 个 +// 修订历史: +// 2013年07月03日 (仲思惠) +// 初始版本 +// 2013年07月19日 (仲思惠) +// 实现了获取图像粗分割阈值功能。 +// 2013年07月21日(仲思惠) +// 实现了根据粗分割阈值,将图像灰度级降低功能。 +// 2013年07月28日(仲思惠) +// 实现了使用 OTSU 法,在粗分割阈值的基础上搜索最佳阈值。 +// 2013年09月12日(仲思惠) +// 修改了前向N值化和后向N值化核函数中的bug。 +// 2013年10月25日(仲思惠) +// 增加了对区间顶点值的边界情况检测,取消了两个宏定义,提供了参数 +// 设定的新接口。 + +#ifndef __MULTIDECREASE_H__ +#define __MULTIDECREASE_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "Histogram.h" + +#define MD_FRONT 0 +#define MD_BACK 1 + +// 类:Multidecrease +// 继承自:无 +// 将图像的 256 个灰度级,降低为 N 个。 +// 使用双峰法求出图像的粗分割阈值,再在一定的松弛余量范围内搜索 +// 图像的最佳分割阈值。最够根据求得的阈值集合,将图像灰度级降低。 +class Multidecrease { + +protected: + + // 成员变量:threshholds(灰度阈值集合) + unsigned char *threshold; + + // 成员变量:stateflag(状态标记) + unsigned int stateflag; + + // 成员变量:widthrange(宽度范围阈值) + unsigned int widthrange; + + // 成员变量:pixelrange(像素数范围阈值) + unsigned int pixelrange; + +public: + // 构造函数:Multidecrease + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + Multidecrease() + { + threshold = 0; + stateflag = MD_FRONT; + widthrange = 30; + pixelrange = 1000; + } + + // 获取图像的分割阈值集合 + __host__ unsigned char* getthreshold () { + return this->threshold; + } + + // 设置图像的状态标记 + __host__ int setsateflag (unsigned int flag) { + if(flag != MD_FRONT &&flag != MD_BACK) + return 0; + this->stateflag = flag; + return 1; + } + + // 获取图像的宽度范围阈值 + __host__ unsigned int getwidthrange () { + return this->widthrange; + } + + // 设置图像的宽度范围阈值 + __host__ int setwidthrange (unsigned int range) { + if(range <= 0 || range >= 255) + return 0; + this->widthrange = range; + return 1; + } + + // 获取图像的像素数范围阈值 + __host__ unsigned int getpixelrange () { + return this->pixelrange; + } + + // 设置图像的像素数范围阈值 + __host__ int setpixelrange (unsigned int range) { + if(range <= 0 || range >= 255) + return 0; + this->pixelrange = range; + return 1; + } + // Host 成员方法:multidecrease(N值化处理) + __host__ int multidecrease( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/Normalization.cu b/okano_3_0/Normalization.cu new file mode 100644 index 0000000..fc8aa46 --- /dev/null +++ b/okano_3_0/Normalization.cu @@ -0,0 +1,363 @@ +// Normalization.cu +// 对图像进行正规化 + +#include "Normalization.h" +#include "Template.h" + +// 宏:用来定义使用 online 算法求平均值和方差 +//#define USE_ONLINE + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +#ifdef USE_ONLINE +// Kernel 函数:_nomalizeKer(实现对输入图像的每个点进行正规化) +// 对输入图像的每一个点,以该点为中心的邻域,求出该邻域内的平均值和总体方差, +// 然后将该点的像素与平均值作差,再除以总体方差,得到的值作为输出 +static __global__ void // Kernel 无返回值 +_nomalizeKer( + ImageCuda inimg, // 输入图像 + Template tpl, // 模版,用来指定邻域范围 + float *res, // 输出的计算的结果 + size_t pitch // res 的 pitch 值 +); +#endif + +// Host 函数:_creatTemplate(创建模版) +// 创建指定大小的方形模版,模版必须为空模版 +static __host__ int // 返回值:函数是否正确指向,若函数正确指向,返回 + // NO_ERROR +_creatTemplate( + int k, // 指定要创建的方形模版的边长 + Template *tpl // 模版指针,模版必须为空模版 +); + +// Host 函数:_creatTemplate(创建模版) +static __host__ int _creatTemplate(int k, Template *tpl) +{ + int errcode; // 局部变量,错误码 + // 判断 tpl 是否为空 + if (tpl == NULL) + return NULL_POINTER; + + // 判断 k 是否合法 + if (k <= 0) + return INVALID_DATA; + + // 计算模版中点的数量 + int count = k * k; + + // 计算中心点 + int center = k / 2; + + // 为模版构建数据 + errcode = TemplateBasicOp::makeAtHost(tpl, count); + if (errcode != NO_ERROR) + return errcode; + + // 构造方形模版中的点 + for (int i = 0; i < count; i++) { + tpl->tplData[2 * i] = i % k - center; + tpl->tplData[2 * i + 1] = i / k - center; + } + + // 计算完毕,返回 + return NO_ERROR; +} + +#ifdef USE_ONLINE +// Kernel 函数:_nomalizeKer(实现对输入图像的每个点进行正规化) +static __global__ void _nomalizeKer(ImageCuda inimg, Template tpl, + float *res, size_t pitch) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 + // c 表示 column, r 表示 row)。由于采用并行度缩减策略 ,令一个线程 + // 处理 4 个输出像素,这四个像素位于统一列的相邻 4 行上,因此,对于 + // dstr 需要进行乘 4 的计算 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致系统崩溃 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量 + int dx, dy; + + // 用来记录当前模版所在位置的指针 + int *curtplptr = tpl.tplData; + + // 用来记录当前输入图像所在位置的指针 + unsigned char *curinptr; + + // 计数器,用来记录某点在模版范围内拥有的点的个数 + int count[4] = { 0 , 0, 0, 0 }; + + // 迭代求平均值和总体方差使用的中间值 + float m[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // 计算得到的平均值 + float mean[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + // 计算得到的总体方差 + float variance[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + int pix; // 局部变量,临时存储像素值 + // 扫描模版范围内的每个输入图像的像素点 + for (int i = 0; i < tpl.count; i++) { + // 计算当模版位置所在像素的 x 和 y 分量,模版使用相邻的两个下标的 + // 数组表示一个点,所以使用当前模版位置的指针加一操作 + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + + float temp; // 局部变量,在进行迭代时的中间变量 + + // 先判断当前像素的 x 分量是否越界,如果越界,则跳过,扫描下一个模版点, + // 如果没有越界,则分别处理当前列的相邻的 4 个像素 + if (dx >= 0 && dx < inimg.imgMeta.width) { + // 根据 dx 和 dy 获取第一个像素的指针 + curinptr = inimg.imgMeta.imgData + dx + dy * inimg.pitchBytes; + // 检测此像素点的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 对第一个点利用 on-line 算法进行迭代 + pix = *(curinptr); + count[0]++; + temp = pix - mean[0]; + mean[0] += temp / count[0]; + m[0] += temp * (pix - mean[0]); + } + + // 分别处理剩下三个像素点 + for (int j = 1; j < 4; j++) { + // 获取下一个像素点的指针 + curinptr = curinptr + inimg.pitchBytes; + dy++; + // 检测第二个像素点的 y 分量是否越界 + if (dy >= 0 && dy < inimg.imgMeta.height) { + // 对第二个点利用 on-line 算法进行迭代 + pix = *(curinptr); + count[j]++; + temp = pix - mean[j]; + mean[j] += temp / count[j]; + m[j] += temp * (pix - mean[j]); + } + } + } + } + + // 计算 4 个像素点中每个的正规化值 + + // 计算第一个像素点的正规化 + // 定义并计算指向第一个像素在输出数组中的指针 + float *outptr =(float *)((char *)res + dstr * pitch) + dstc; + // 第一个点的像素值 + curinptr = inimg.imgMeta.imgData + dstc + dstr * inimg.pitchBytes; + pix = *(curinptr); + // 判断 m 值是否为 0,如果为 0,则将对应的正规化值设置为 0 + if (m[0] <= 0.000001f && m[0] >= -0.000001f) { + *outptr = 0.0f; + } else { + // 计算第一个像素点的总体方差 + variance[0] = sqrtf(m[0] / count[0]); + // 计算第一个像素点的正规化画像值 + *outptr = (pix - mean[0]) / variance[0]; + } + + // 分别计算剩下三个点的像素值 + for (int i = 1; i < 4; i++) { + // 判断该点的 y 分量是否越界,如果越界,则可以确定后面的点也越界,直接 + // 返回 + if (++dstr >= inimg.imgMeta.height) + return; + // 计算该点在输出数组中的指针 + outptr = (float *)((char *)outptr + pitch); + // 该点的像素值 + curinptr = curinptr + inimg.pitchBytes; + pix = *(curinptr); + // 判断 m 值是否为 0,如果为 0,则将对应的正规化值设置为 0 + if (m[i] <= 0.000001f && m[i] >= -0.000001f) { + *outptr = 0.0f; + } else { + // 计算该像素点的标准方差 + variance[i] = sqrtf(m[i] / count[i]); + // 计算该像素点的正规化画像值 + *outptr = (pix - mean[i]) / variance[i]; + } + } +} +#endif + +// Kernel 函数:_nomalizeKer(使用常规方法求平均值和方差) +static __global__ void _nomalizenorKer(ImageCuda inimg, Template tpl, + float *res, size_t pitch) +{ + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + int *curtplptr = tpl.tplData; + int dx, dy; + int count = 0; + int sum = 0; + float mean; + float variance = 0.0f; + + for (int i = 0; i < tpl.count; i++) { + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + if (dx < 0 || dx >= inimg.imgMeta.width || + dy < 0 || dy >= inimg.imgMeta.height) { + continue; + } + count++; + sum += *(inimg.imgMeta.imgData + dy * inimg.pitchBytes + dx); + } + mean = (float)sum / count; + curtplptr = tpl.tplData; + for (int i = 0; i < tpl.count; i++) { + dx = dstc + *(curtplptr++); + dy = dstr + *(curtplptr++); + if (dx < 0 || dx >= inimg.imgMeta.width || + dy < 0 || dy >= inimg.imgMeta.height) { + continue; + } + int pix = *(inimg.imgMeta.imgData + dy * inimg.pitchBytes + dx); + variance += (mean - pix) * (mean - pix); + } + + float *outptr = (float *)((char *)res + dstr * pitch) + dstc; + + if (variance < 0.00001f) + *outptr = 0.0f; + else { + int pix = *(inimg.imgMeta.imgData + dstr * inimg.pitchBytes + dstc); + *outptr = (mean - pix) / sqrtf(variance); + } +} + +// Host 成员方法:normalize(对输入图像进行正规化) +__host__ int Normalization::normalize(Image *inimg, float *out, size_t pitch, + int width, int height, bool ishost) +{ + int errcode; // 局部变量,错误码 + cudaError_t cudaerr; // 局部变量,CUDA 调用返回的错误码 + dim3 gridsize; + dim3 blocksize; + + // 判断 inimg 和 out 是否是为空 + if (inimg == NULL || out == NULL) + return NULL_POINTER; + + // 判断 width 和 height 参数的合法性 + if (width <= 0 || height <= 0) + return INVALID_DATA; + + // 判断 pitch 的合法性 + if (pitch < width * sizeof (float)) + return INVALID_DATA; + + // 对输入图像申请 Device 存储空间 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图 + ImageCuda inimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &inimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入图像和输出数组的长和宽 + if (inimgCud.imgMeta.width > width) + inimgCud.imgMeta.width = width; + if (inimgCud.imgMeta.height > height) + inimgCud.imgMeta.height = height; + + // 计算线程块的数量 + // blocksize 使用默认线程块 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + // 使用最普通的方法划分 Grid + gridsize.x = (inimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (inimgCud.imgMeta.height + blocksize.y * 4 - 1) / + blocksize.y * 4; + + // 创建模版 + Template *tpl; + errcode = TemplateBasicOp::newTemplate(&tpl); + if (errcode != NO_ERROR) + return errcode; + + // 设置模版形状 + errcode = _creatTemplate(k, tpl); + if (errcode != NO_ERROR) + return errcode; + + errcode = TemplateBasicOp::copyToCurrentDevice(tpl); + if (errcode != NO_ERROR) + return errcode; + + float *resCud; // 指向 Device 内存,用来存储正规化得到的结果 + size_t respitch; // resCud 的 pitch + // 判断 out 是否指向 host,如果是,需要在创建 Device 中创建空间 + if (ishost) { + // 为 resCud 申请内存空间 + cudaerr = cudaMallocPitch((void **)&resCud, &respitch, + width * sizeof (float), height); + if (cudaerr != cudaSuccess) + return CUDA_ERROR; + } else { + resCud = out; + respitch = pitch; + } + + dim3 blocksize1; + dim3 gridsize1; + // blocksize 使用默认线程块 + blocksize1.x = DEF_BLOCK_X; + blocksize1.y = DEF_BLOCK_Y; + // 使用最普通的方法划分 Grid + gridsize1.x = (inimgCud.imgMeta.width + blocksize1.x - 1) / blocksize1.x; + gridsize1.y = (inimgCud.imgMeta.height + blocksize1.y - 1) / + blocksize1.y; + + #ifdef USE_ONLINE + _nomalizeKer<<>>(inimgCud, *tpl, resCud, respitch); + #else + _nomalizenorKer<<>>(inimgCud, *tpl, resCud, respitch); + #endif + + + + // 调用 Kernel 函数进行正规化操作 + // _nomalizeKer<<>>(inimgCud, *tpl, resCud, respitch); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 如果 out 是指向 host 内存的,则需要将 resCud 的内容拷贝到 out 中, + // 并且释放 resCud 指向的内存空间 + if (ishost) { + // 将正规化得到的结果从 Device 内存中拷贝到 Host 内存 + cudaerr = cudaMemcpy2D(out, width * sizeof (float), resCud, respitch, + width * sizeof (float), height, + cudaMemcpyDeviceToHost); + if (cudaerr != cudaSuccess) + errcode = CUDA_ERROR; + else + errcode = NO_ERROR; + // 释放 resCud 指向的内存空间 + cudaFree(resCud); + } + + // 释放模版空间 + TemplateBasicOp::deleteTemplate(tpl); + + // 处理完毕,返回 + return errcode; +} + diff --git a/okano_3_0/Normalization.h b/okano_3_0/Normalization.h new file mode 100644 index 0000000..13f2a09 --- /dev/null +++ b/okano_3_0/Normalization.h @@ -0,0 +1,120 @@ +// Normalization.h +// 创建人:罗劼 +// +// 图像正规化(Image Normalization) +// 功能说明:对一张图像进行正规化 +// +// 修订历史: +// 2012年10月31日(罗劼) +// 初始版本 +// 2012年10月31日(罗劼,于玉龙) +// 修改了多处代码不规范的地方以及 normalize 函数的设计 +// 2012年11月19日(罗劼) +// 修改了将正规化结果从 Device 拷贝到 Host 上的一处错误 +// 2012年11月29日(罗劼) +// 修改了一处计算方差的错误 +// 2013年05月07日(罗劼) +// 修改了一处计算块数量的错误 + +#ifndef __NORMALIZATION_H__ +#define __NORMALIZATION_H__ + +#include "ErrorCode.h" +#include "Image.h" + +// 类:Normalization +// 继承自:无 +// 对一张图像进行正规化操作,对每一个像素值,以该像素为中心,求出该邻域内的平均 +// 值和标准差,将该像素值减平均值,得到的差再除以标准差,得到的结果就是该点的正 +// 规化值 +class Normalization { + +protected: + + // 成员变量:k(邻域的大小) + // 用来指定每个点邻域的大小 + int k; + +public: + + // 构造函数:Normalization + // 无参版本的构造函数,所有成员初始化为默认值 + __host__ __device__ + Normalization() + { + k = 3; // 邻域大小默认为 3 + } + + // 构造函数:Normalization + // 有参版本的构造函数,根据需要,参数可以在程序的运行过程中改变 + __host__ __device__ + Normalization( + int k // 邻域的大小 + ) { + // 使用默认值初始化成员 + this->k = 3; + + // 根据参数列表中的值设定成员变量的初值 + this->setK(k); + } + + // 成员方法:getK(获取邻域的大小) + // 获取 k 的值 + __host__ __device__ int // 返回值:成员变量 k 的值 + getK() const + { + // 返回设置的领域的大小 + return this->k; + } + + // 成员方法:setK(设置邻域的大小) + // 设置 k 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR + setK( + int k // 邻域的大小 + ) { + // 判断 k 是否是负数,是则返回错误 + if (k < 0) + return INVALID_DATA; + + // 设置邻域大小的值 + this->k = k; + + return NO_ERROR; + } + + // 成员方法:normalize(对输入图像进行正规化) + // 对输入图像进行正规化,取 inimg 和 out 中范围更小的,结果存放在 Host + // 内存中 + inline __host__ int // 返回值:函数是否正确执行,如果正确执行,返 + // 回 NO_ERROR + normalize( + Image *inimg, // 输入图像 + float *out, // 输出每个点正规化的画像值 + int width, // out 的宽 + int height, // out 的高 + bool ishost // 标记 out 是否指向 host 内存区域 + ) { + // 将 pitch 设置为 width * sizeof (flaot),此时 out 是不带 pitch 的 + return this->normalize(inimg, out, width * sizeof (float), + width,height,ishost); + } + + // 成员方法:normalize(对输入图像进行正规化) + // 对输入图像进行正规化,取 inimg 和 out 中范围更小的,结果存放在 + // Device 内存中 + __host__ int // 返回值:函数是否正确执行,如果正确执行,返回 + // NO_ERROR + normalize( + Image *inimg, // 输入图像 + float *out, // 输出每个点正规化像素值 + size_t pitch, // out 的 pitch 值 + int width, // out 的宽 + int height, // out 的高 + bool ishost // 标记 out 是否指向 host 内存区域 + ); +}; + +#endif + diff --git a/okano_3_0/OperationFunctor.cu b/okano_3_0/OperationFunctor.cu new file mode 100644 index 0000000..bca8b1b --- /dev/null +++ b/okano_3_0/OperationFunctor.cu @@ -0,0 +1,7 @@ +// OperationFunctor.cu +// OperationFunctor 模板运算的实现 + +#include "OperationFunctor.h" + +// 由于所有的函数都是重载运算符 () 或者返回对应运算的单位元, +// 已在 OperationFunctor.h 中定义,故本文件无任何代码内容。 \ No newline at end of file diff --git a/okano_3_0/OperationFunctor.h b/okano_3_0/OperationFunctor.h new file mode 100644 index 0000000..8f474ea --- /dev/null +++ b/okano_3_0/OperationFunctor.h @@ -0,0 +1,137 @@ +// OperationFunctor.h +// 创建人:王春峰 +// +// 仿函数模板运算(OperationFunctor) +// 功能说明:实现各运算类型的模板运算。 +// +// 修订历史: +// 2013年05月13日(王春峰) +// 初始版本 +// 2013年05月13日(王春峰) +// 在每个类中增加了单位元 +// 2013年05月16日(王春峰) +// 增加了 max 运算和 min 运算,同时宏定义了它们的单位元,无穷小与无穷大。 + +#ifndef __OPERATIONFUNCTOR_H__ +#define __OPERATIONFUNCTOR_H__ + +#include +using namespace std; + +// 宏:OPR_LARGE_ENOUGH +// 定义了一个足够大的正整数,该整数在使用过程中被认为是无穷大。 +#define OPR_LARGE_ENOUGH ((1 << 30) - 1) + +// 宏:OPR_SMALL_ENOUGH +// 定义了一个足够小的整数,该整数在使用过程中被认为是无穷小。 +#define OPR_SMALL_ENOUGH (-(1 << 30)) + +// 类:add_class +// 继承自:无 +// 加法模板类,实现对运算符 () 的重载,以及获取加法运算的单位元。 +template< class T > +class add_class { + +public: + + // 成员方法:operator()(对 () 的重载) + __host__ __device__ T + operator()( + T A, // 操作数 A + T B // 操作数 B + ) const { + // 返回 A、B 加和 + return A + B; + } + + // 成员方法:identity(获取运算类型单位元) + __host__ __device__ T + identity() + { + // 返回加法单位元 0。 + return (T)0; + } +}; + +// 类:multi_class +// 继承自:无 +// 乘法模板类,实现对运算符 () 的重载。 +template< class T > +class multi_class { + +public: + + // 成员方法:operator()(对 () 的重载) + __host__ __device__ T + operator()( + T A, // 操作数 A + T B // 操作数 B + ) const { + // 返回 A、B 乘积 + return A * B; + } + + // 成员方法:identity(获取运算类型单位元) + __host__ __device__ T + identity() + { + // 返回乘法单位元 1。 + return (T)1; + } +}; + +// 类:max_class +// 继承自:无 +// 最大值运算模板类,实现对运算符 () 的重载。 +template< class T > +class max_class { + +public: + + // 成员方法:operator()(对 () 的重载) + __host__ __device__ T + operator()( + T A, // 操作数 A + T B // 操作数 B + ) const { + // 返回 A、B 最大值 + return (A > B ? A : B); + } + + // 成员方法:identity(获取运算类型单位元) + __host__ __device__ T + identity() + { + // 返回最大值运算单位元。 + return (T)OPR_SMALL_ENOUGH; + } +}; + +// 类:min_class +// 继承自:无 +// 最小值运算模板类,实现对运算符 () 的重载。 +template< class T > +class min_class { + +public: + + // 成员方法:operator()(对 () 的重载) + __host__ __device__ T + operator()( + T A, // 操作数 A + T B // 操作数 B + ) const { + // 返回 A 和 B 中较小的值 + return (A < B ? A : B); + } + + // 成员方法:identity(获取运算类型单位元) + __host__ __device__ T + identity() + { + // 返回最小值运算单位元。 + return (T)OPR_LARGE_ENOUGH; + } +}; + +#endif \ No newline at end of file diff --git a/okano_3_0/OtsuBinarize.cu b/okano_3_0/OtsuBinarize.cu new file mode 100644 index 0000000..df226b7 --- /dev/null +++ b/okano_3_0/OtsuBinarize.cu @@ -0,0 +1,210 @@ +// OtsuBinarize.cu +// 根据两个领域之间的分散程度,自动找到最佳二值化结果 + +#include "OtsuBinarize.h" +#include "Histogram.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏: DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数:_OtsuBinarizeKer(最佳二值化自动生成) +// 用 Histogram searching 法自动找到最佳阈值(最佳二值化结果) +// 判断的根据:两个领域之间的分散最大(内分散最小) +static __global__ void // Kernel 函数无返回值 +_OtsuBinarizeKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char threshold // 阈值 +); + +// Kernel 函数: _OtsuBinarizeKer(最佳二值化自动生成) +static __global__ void _OtsuBinarizeKer(ImageCuda inimg, + ImageCuda outimg, + unsigned char threshold) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线 程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像 素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理, 一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素点. + // 如果输入图像的该位置的像素值大于等于 threshold,则 将输出图像中对应位 + // 置的像素值置为 255;否则将输出图像中对应位置的像素 值置为 0。 + // 线程中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = (intemp >= threshold ? 255 : 0); + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图 像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可 ,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 如果输入图像的该位置的像素值大于等于 threshold ,则将输出图像中对应 + // 位置的像素值置为 255;否则将输出图像中对应位置 的像素值置为 0。线程 + // 中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = (intemp >= threshold ? 255 : 0); + } +} + +// Host 成员方法:otsuBinarize(最佳二值化自动生成) +__host__ int OtsuBinarize::otsuBinarize(Image *inimg, Image *outimg) +{ + // 检查输入图像和输出图像是否为 NULL,如果为 NULL 直接报错返回 。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice (outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败), 则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长, 宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 调用直方图,获取图像的像素信息 + Histogram h; + + // 图像的像素信息 + unsigned int his[256]; + h.histogram(inimg, his, true); + + // 图像总像素数 + int sumpixel = 0; + for (int i = 0; i < 256; i++) { + sumpixel += his[i]; + } + + // 获取图像的最小和最大有效像素值 + int imin = 0, imax = 255; + for (int i = 0; his[i] == 0 && i <= 255; i++) { + imin++; + } + for (int i = 255; his[i] == 0 && i >= 0; i--) { + imax--; + } + + // 根据图像信息计算最大类间方差,使用如下公式: + // max =(μ_T ω_1 (k)-μ_1 (k))^2/(ω_1 (k)(1-ω_1(k))) + float pix_w[256]; // 数组 pix_w[i] 记录图像中值为i的像素占总像素数的比例 + float weights[256]; // 数组 weights[i] 记录图像以i为阈值时,前景的像素数占 + // 总像素数的比例 + float means[256]; // 数组 means[i] 记录图像以i为阈值时,前景的平均灰度值 + + // 计算 pix_w[i] 的值 + for (int i = 0; i < 256; i++) { + pix_w[i] = ((float)his[i]) / sumpixel; + } + + // 计算 weights[i] 和 means[i] 的值 + weights[0] = pix_w[0]; + means[0] = 0.0; + for (int i = 1; i < 256; i++) { + weights[i] = weights[i - 1] + pix_w[i]; + means[i] = means[i - 1] + (i * pix_w[i]); + } + + // 计算类间方差,并找出最大类间方差,同时记录最佳阈值 + float mean = means[255]; + float max = 0.0; + int threshold = 0; + for (int i = imin; i <= imax; i++) { + float bcv = mean * weights[i] - means[i]; + bcv *= bcv / (weights[i] * (1.0 - weights[i])); + if (max < bcv) { + max = bcv; + threshold = i; + } + } + + // 将阈值进行类型转换。 + unsigned char thresholds = (unsigned char)threshold; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,使用最佳阈值对图像进行二值化 + _OtsuBinarizeKer<<>>(insubimgCud, outsubimgCud, + thresholds); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/OtsuBinarize.h b/okano_3_0/OtsuBinarize.h new file mode 100644 index 0000000..f07bc4c --- /dev/null +++ b/okano_3_0/OtsuBinarize.h @@ -0,0 +1,50 @@ +// OtsuBinarize.h +// 创建人:仲思惠 +// +// 最佳二值化自动生成(OtsuBinarize.cu) +// 根据两个领域之间的分散程度,自动找到最佳二值化结果 +// +// 修订历史: +// 2012年11月25日(仲思惠) +// 初始版本。 +// 2012年11月28日(王媛媛、仲思惠) +// 实现了 OTSU 算法,自动找到图像的最佳阈值,并实现图像的最佳二值化。 +// 2012年12月03日(仲思惠) +// 添加了另一种实现方法,并对比 OpenCV 的运行结果对两种方法进行了整合。 +// 2012年12月06日(仲思惠) +// 修正了代码格式,并添加适当的注释。 +// 2012年12月13日(于玉龙、仲思惠) +// 修改了部分变量的类型,并修正了代码中的格式错误。 // 2012年12月19日(仲思惠) // 修改了计算最佳阈值的方法。 // 2012年12月20日(于玉龙、仲思惠) // 删除了一处测试代码,修正了代码格式。 + +#ifndef __OTSUBINARIZE_H__ +#define __OTSUBINARIZE_H__ + +#include "Image.h" + +// 类:OtsuBinarize +// 继承自:无 +// 根据两个领域之间的分散程度,自动找到最佳二值化结果 +class OtsuBinarize { + +public: + + // 构造函数:OtsuBinarize + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + // 该函数没有任何内容。 + __host__ __device__ + OtsuBinarize(){ + // 该函数不进行任何操作 + } + + // Host 成员方法:otsuBinarize(最佳二值化自动生成) + // 根据两个领域之间的分散程度,自动找到最佳二值化结果。 + __host__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 + otsuBinarize( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/OtsuForThree.cu b/okano_3_0/OtsuForThree.cu new file mode 100644 index 0000000..1c3aa5a --- /dev/null +++ b/okano_3_0/OtsuForThree.cu @@ -0,0 +1,408 @@ +// OtsuForThree.cu +// 根据图像像素的分散程度,自动找到两个最佳分割阈值,得到 +// 图像的三值化结果。 + +#include "OtsuForThree.h" +#include "Histogram.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏: DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 存储在常量内存中的概率集和均值集 + __constant__ float dev_W[256]; + __constant__ float dev_U[256]; + +// Kernel 函数:_OtsuForThree_ForwardKer(前向三值化) +static __global__ void // Kernel 函数无返回值 +_OtsuForThree_ForwardKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char thresholda, // 阈值1 + unsigned char thresholdb // 阈值2 +); + +// Kernel 函数:_OtsuForThree_ForwardKer(前向三值化) +static __global__ void _OtsuForThree_ForwardKer(ImageCuda inimg, + ImageCuda outimg, + unsigned char thresholda, + unsigned char thresholdb) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线 程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像 素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理, 一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素。 + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 前向阈值。线程中处理的第一个点。 + if (intemp <= thresholda) { + outimg.imgMeta.imgData[outidx] = thresholda; + } + else if (intemp > thresholda && intemp <= thresholdb) { + outimg.imgMeta.imgData[outidx] = thresholdb; + } + else if (intemp > thresholdb && intemp <= 255) { + outimg.imgMeta.imgData[outidx] = 255; + } + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 前向阈值。 + if (intemp <= thresholda) { + outimg.imgMeta.imgData[outidx] = thresholda; + } + else if (intemp > thresholda && intemp <= thresholdb) { + outimg.imgMeta.imgData[outidx] = thresholdb; + } + else if (intemp > thresholdb && intemp <= 255) { + outimg.imgMeta.imgData[outidx] = 255; + } + } +} + +// Kernel 函数:_OtsuForThree_BackwardKer(后向三值化) +static __global__ void // Kernel 函数无返回值 +_OtsuForThree_BackwardKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char thresholda, // 阈值1 + unsigned char thresholdb // 阈值2 +); + +// Kernel 函数:_OtsuForThree_BackwardKer(后向三值化) +static __global__ void _OtsuForThree_BackwardKer(ImageCuda inimg, + ImageCuda outimg, + unsigned char thresholda, + unsigned char thresholdb) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线 程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像 素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理, 一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素。 + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 前向阈值。线程中处理的第一个点。 + if (intemp < thresholda) { + outimg.imgMeta.imgData[outidx] = 0; + } + else if (intemp >= thresholda && intemp < thresholdb) { + outimg.imgMeta.imgData[outidx] = thresholda; + } + else if (intemp >= thresholdb && intemp <= 255) { + outimg.imgMeta.imgData[outidx] = thresholdb; + } + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 判断当前像素点的灰度值处于哪个阈值区间,并将该点的像素值设为阈值区间的 + // 前向阈值。 + if (intemp < thresholda) { + outimg.imgMeta.imgData[outidx] = 0; + } + else if (intemp >= thresholda && intemp < thresholdb) { + outimg.imgMeta.imgData[outidx] = thresholda; + } + else if (intemp >= thresholdb && intemp <= 255) { + outimg.imgMeta.imgData[outidx] = thresholdb; + } + } +} + +// Kernel 函数:_CalcuVarianceKer (计算最小类内方差) +static __global__ void // Kernel 函数无返回值 +_CalcuVarianceKer( + float * thres +); +// Kernel 函数:_CalcuVarianceKer (计算最小类内方差) +static __global__ void _CalcuVarianceKer(float * thres) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线 程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + // 检查第一个像素点是否越界,如果越界,则不进行处理, 一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= 128 || r >= 128) + return; + int index = c * 128 + r; + int counti = c; + int countj = r + 128; + float vara, varb, varc; + + // 每个线程计算一种分割情况下的类内方差总和,并通过对应关系,存储在相应下标的 + // 数组元素中。计算时,分别计算(0-t1)、(t1-t2)、(t2-255)三个类内方差。 + + // 计算(0-t1)的类内方差 vara + float Wk, Uk; + Wk = dev_W[counti] - dev_W[0]; + if (Wk == 0.0) + Uk = 0.0; + else + Uk = (dev_U[counti] - dev_U[0]) / Wk; + vara = 0.0; + for (int count = 1; count <= counti; count++) { + vara += abs(count - Uk) * abs(count - Uk) * + (dev_W[count] - dev_W[count - 1]); + } + // 计算(t1-t2)的类内方差 varb + Wk = dev_W[countj] - dev_W[counti]; + if (Wk == 0.0) + Uk = 0.0; + else + Uk = (dev_U[countj] - dev_U[counti]) / Wk; + varb = 0.0; + for (int count = counti; count <= countj; count++) { + if (count < 1) + continue; + varb += abs(count - Uk) * abs(count - Uk) * + (dev_W[count] - dev_W[count - 1]); + } + // 计算(t2-255)的类内方差varc + Wk = dev_W[255] - dev_W[countj]; + if (Wk == 0.0) + Uk = 0.0; + else + Uk = (dev_U[255] - dev_U[countj]) / Wk; + varc = 0.0; + for (int count = countj; count <= 255; count++) { + varc += abs(count - Uk) * abs(count - Uk) * + (dev_W[count] - dev_W[count - 1]); + } + // 将计算得到的方差和存储在数组中。 + thres[index] = vara + varb + varc; +} + +// Host 成员方法:OtsuForThree(最佳二值化自动生成) +__host__ int OtsuForThree::otsuForThree(Image *inimg, Image *outimg) +{ + // 检查输入图像和输出图像是否为 NULL,如果为 NULL 直接报错返回 。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice (outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败), 则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长, 宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 调用直方图,获取图像的像素信息 + Histogram h; + + // 图像的像素信息 + unsigned int his[256]; + h.histogram(inimg, his, true); + + // 图像总像素数 + int sumpixel = 0; + for (int i = 0; i < 256; i++) { + sumpixel += his[i]; + } + + // 计算图像的概率信息、有聚合度的概率集和有聚合度的均值集合。 + float P[256]; + float W[256]; + float U[256]; + + P[0] = (float)his[0] / (float)sumpixel; + W[0] = P[0]; + U[0] = 0.0; + for(int i = 1; i < 256; i++) { + P[i] = (float)his[i] / (float)sumpixel; + W[i] = P[i] + W[i-1]; + U[i] = i * P[i] + U[i-1]; + } + + // 将概率集和均值集复制到常量内存中 + cudaMemcpyToSymbol(dev_W, W, sizeof(float) * 256); + cudaMemcpyToSymbol(dev_U, U, sizeof(float) * 256); + + // 存储128×128个类内方差总和的数组 + float *hostthresholds = new float[16384]; + float *devthreshlods; + + // 为标记数组分配大小。 + errcode = cudaMalloc((void **)&devthreshlods, 16384 * sizeof (float)); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 为标记数组设定初值。 + errcode = cudaMemset(devthreshlods, 0, 16384 * sizeof (float)); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 将数组复制至 device 端。 + errcode = cudaMemcpy(devthreshlods, hostthresholds, 16384 * sizeof (float), + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (128 + blocksize.x - 1) / blocksize.x; + gridsize.y = (128 + blocksize.y - 1) / blocksize.y; + + // 调用核函数,计算128×128种分割方式下的方差集合 + _CalcuVarianceKer<<>>(devthreshlods); + + // 将数组复制至 host 端。 + errcode = cudaMemcpy(hostthresholds, devthreshlods, 16384 * sizeof (float), + cudaMemcpyDeviceToHost); + if (errcode != cudaSuccess) { + cudaFree(devthreshlods); + return errcode; + } + + // 串行计算,找出128×128个方差元素中的最小值 + float min = 10000.0; + int thresa = 0; + int thresb = 0; + + // 计算数组的最小值 + for (int i = 0; i < 16384; i++) { + if (min > hostthresholds[i]) { + min = hostthresholds[i]; + // 通过对应成二维数组,得到两个对应的阈值 + thresa = i / 128; + thresb = i % 128 + 128; + } + } + + // 将阈值进行类型转换。 + unsigned char thresholda = (unsigned char)thresa; + unsigned char thresholdb = (unsigned char)thresb; + + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,使用最佳阈值对图像进行二值化 + if (this-> isForward) { + _OtsuForThree_ForwardKer<<>>(insubimgCud, + outsubimgCud,thresholda, thresholdb); + } + else { + _OtsuForThree_BackwardKer<<>>(insubimgCud, + outsubimgCud,thresholda, thresholdb); + } + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/OtsuForThree.h b/okano_3_0/OtsuForThree.h new file mode 100644 index 0000000..4c26efe --- /dev/null +++ b/okano_3_0/OtsuForThree.h @@ -0,0 +1,64 @@ +// OtsuForThree.h +// 创建人:仲思惠 +// +// 图像三值化(OtsuForThree.h) +// 根据图像像素的分散程度,自动找到两个最佳分割阈值,得到 +// 图像的三值化结果。 +// +// 修订历史: +// 2014年09月09日(仲思惠) +// 初始版本。 +// 2014年09月19日(仲思惠) +// 修改了一处计算错误。 +// 2014年09月25日(仲思惠) +// 并行实现128×128个方差元素的计算。 + +#ifndef __OTSUFORTHREE_H__ +#define __OTSUFORTHREE_H__ + +#include "Image.h" + +// 类:OtsuBinarize +// 继承自:无 +// 根据图像像素的分散程度,自动找到两个最佳分割阈值,得到 +// 图像的三值化结果。 +class OtsuForThree { + +protected: + + // 成员变量:isForward(三值化类型) + bool isForward; + +public: + + // 构造函数:OtsuForThree + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + OtsuForThree(){ + isForward = true; + } + + // 获取图像的三值化类型 + __host__ bool getisForward () { + return this->isForward; + } + + // 设置图像的三值化类型 + __host__ int set_isForward (bool type) { + this->isForward = type; + return 1; + } + + // Host 成员方法:otsuForThree(图像三值化) + // 根据图像像素的分散程度,自动找到两个最佳分割阈值,得到 + // 图像的三值化结果。 + __host__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 + otsuForThree( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/PerspectiveTrans.cu b/okano_3_0/PerspectiveTrans.cu new file mode 100644 index 0000000..371fc35 --- /dev/null +++ b/okano_3_0/PerspectiveTrans.cu @@ -0,0 +1,519 @@ + +#include "PerspectiveTrans.h" + +#include +using namespace std; + +#include "FanczosIpl.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 全局变量:_hardIplInimgTex(作为输入图像的纹理内存引用) +// 纹理内存只能用于全局变量,因此将硬件插值的旋转变换的 Kernel 函数的输入图像列 +// 于此处。 +static texture _hardIplInimgTex; + +// Kernel 函数:_hardPerspectiveKer(利用硬件插值实现的射影变换) +// 利用纹理内存提供的硬件插值功能,实现的并行射影变换。没有输入图像的参数,是因 +// 为输入图像通过纹理内存来读取数据,纹理内存只能声明为全局变量。 +static __global__ void // Kernel 函数无返回值。 +_hardPerspectiveKer( + ImageCuda outimg, // 输出图像 + PerspectiveMatrix pm // 旋转变换的参数 +); + +// Kernel 函数:_softPerspectiveKer(利用软件插值实现的射影变换) +// 利用 Fanczos 软件插值功能,实现的并行射影变换。 +static __global__ void // Kernel 函数无返回值。 +_softPerspectiveKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + PerspectiveMatrix pm // 旋转变换的参数 +); + +// Host 函数:_pointsToPsptMatrix(单侧变换标准矩阵) +// 该函数计算从单位矩形到给定四个点的射影变换所对应的矩阵。由于一个由给定四个点 +// 到新的四个点的变换所对应的射影变换可以看成是现将给定四个点变换为一个单位矩 +// 形,再由单位矩形变换到新的四个点的两个射影变换的组合,因此这个函数称之为单侧 +// 变换的标准矩阵。求的一个完整的变换矩阵的过程也是将两个单侧变换的矩阵通过矩阵 +// 乘法进行组合的过程(其中的一个矩阵是逆矩阵)。 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR。 +_pointsToPsptMatrix( + const float pts[4][2], // 给定的四个坐标点 + PerspectiveMatrix *pm // 单侧变换的标准矩阵 +); + +// Host 函数:_detPsptMatrix(射影变换矩阵的行列式) +// 计算射影变换矩阵的行列式。通过这个函数可以判断矩阵是否为满秩的。 +static __host__ float // 返回值:行列式计算结果(如果计算发生错 + // 误,则返回 0.0f)。 +_detPsptMatrix( + const PerspectiveMatrix &pm // 输入矩阵 +); + +// Host 函数:_invPsptMatrix(射影变换矩阵求逆) +// 为了得到一个变换所对应的逆变换的矩阵,实现了该函数用来求一个矩阵对应的逆矩 +// 阵。 +static __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 +_invPsptMatrix( + const PerspectiveMatrix &inpm, // 输入矩阵 + PerspectiveMatrix *outpm // 输出的逆矩阵 +); + +// Host 函数:_mulPsptMatrix(射影变换矩阵乘法计算) +// 计算两个矩阵的乘积。这一函数用于将两个连续的变化拼接成一个单次的变化。 +static __host__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 +_mulPsptMatrix( + const PerspectiveMatrix &inpm1, // 输入矩阵 1 + const PerspectiveMatrix &inpm2, // 输入矩阵 2 + PerspectiveMatrix *outpm // 输出矩阵 +); + +// Host 函数:_pointsToPsptMatrix(单侧变换标准矩阵) +static __host__ int _pointsToPsptMatrix(const float pts[4][2], + PerspectiveMatrix *pm) +{ + // 检查输入参数是否为 NULL + if (pts == NULL || pm == NULL) + return NULL_POINTER; + + // 局部变量,比例系数 + float d = (pts[1][0] - pts[3][0]) * (pts[2][1] - pts[3][1]) - + (pts[2][0] - pts[3][0]) * (pts[1][1] - pts[3][1]); + if (fabs(d) < 1.0e-8f) + return INVALID_DATA; + + // 按照射影变换的公式(这些公式在任何一本介绍图像处理或图形学的书中都会有介 + // 绍,这里就不再赘述)求出矩阵的各个元素。 + pm->elem[2][0] = ((pts[0][0] - pts[1][0] + pts[3][0] - pts[2][0]) * + (pts[2][1] - pts[3][1]) - + (pts[0][1] - pts[1][1] + pts[3][1] - pts[2][1]) * + (pts[2][0] - pts[3][0])) / d; + pm->elem[2][1] = ((pts[0][1] - pts[1][1] + pts[3][1] - pts[2][1]) * + (pts[1][0] - pts[3][0]) - + (pts[0][0] - pts[1][0] + pts[3][0] - pts[2][0]) * + (pts[1][1] - pts[3][1])) / d; + pm->elem[2][2] = 1.0f; + pm->elem[0][0] = pts[1][0] - pts[0][0] + pm->elem[2][0] * pts[1][0]; + pm->elem[0][1] = pts[2][0] - pts[0][0] + pm->elem[2][1] * pts[2][0]; + pm->elem[0][2] = pts[0][0]; + pm->elem[1][0] = pts[1][1] - pts[0][1] + pm->elem[2][0] * pts[1][1]; + pm->elem[1][1] = pts[2][1] - pts[0][1] + pm->elem[2][1] * pts[2][1]; + pm->elem[1][2] = pts[0][1]; + + // 计算结束,退出 + return NO_ERROR; +} + +// Host 函数:_detPsptMatrix(射影变换矩阵的行列式) +static __host__ float _detPsptMatrix(const PerspectiveMatrix &pm) +{ + // 按照行列式的计算公式,返回行列式的值。计算行列式的方法在所有的关于线性代 + // 数的书中都有详细的介绍,这里不在赘述。 + return pm.elem[0][0] * pm.elem[1][1] * pm.elem[2][2] + + pm.elem[0][1] * pm.elem[1][2] * pm.elem[2][0] + + pm.elem[0][2] * pm.elem[1][0] * pm.elem[2][1] - + pm.elem[0][0] * pm.elem[1][2] * pm.elem[2][1] - + pm.elem[0][1] * pm.elem[1][0] * pm.elem[2][2] - + pm.elem[0][2] * pm.elem[1][1] * pm.elem[2][0]; +} + +// Host 函数:_invPsptMatrix(射影变换矩阵求逆) +static __host__ int _invPsptMatrix( + const PerspectiveMatrix &inpm, PerspectiveMatrix *outpm) +{ + // 检查输入参数是否为 NULL + if (outpm == NULL) + return NULL_POINTER; + + // 求出矩阵的行列式,这个行列式可以辅助求逆计算,同时可以检查该矩阵是否为奇 + // 异阵。 + float det = _detPsptMatrix(inpm); + if (fabs(det) < 1.0e-8f) + return INVALID_DATA; + + // 根据 3×3 矩阵求逆的公式,计算给定矩阵的逆矩阵,这个公式可以在任何一本介 + // 绍线性代数的书中找到,此处不再赘述。 + outpm->elem[0][0] = (inpm.elem[1][1] * inpm.elem[2][2] - + inpm.elem[1][2] * inpm.elem[2][1]) / det; + outpm->elem[0][1] = (inpm.elem[0][2] * inpm.elem[2][1] - + inpm.elem[0][1] * inpm.elem[2][2]) / det; + outpm->elem[0][2] = (inpm.elem[0][1] * inpm.elem[1][2] - + inpm.elem[0][2] * inpm.elem[1][1]) / det; + outpm->elem[1][0] = (inpm.elem[1][2] * inpm.elem[2][0] - + inpm.elem[1][0] * inpm.elem[2][2]) / det; + outpm->elem[1][1] = (inpm.elem[0][0] * inpm.elem[2][2] - + inpm.elem[0][2] * inpm.elem[2][0]) / det; + outpm->elem[1][2] = (inpm.elem[0][2] * inpm.elem[1][0] - + inpm.elem[0][0] * inpm.elem[1][2]) / det; + outpm->elem[2][0] = (inpm.elem[1][0] * inpm.elem[2][1] - + inpm.elem[1][1] * inpm.elem[2][0]) / det; + outpm->elem[2][1] = (inpm.elem[0][1] * inpm.elem[2][0] - + inpm.elem[0][0] * inpm.elem[2][1]) / det; + outpm->elem[2][2] = (inpm.elem[0][0] * inpm.elem[1][1] - + inpm.elem[0][1] * inpm.elem[1][0]) / det; + + // 计算完毕返回 + return NO_ERROR; +} + +// Host 函数:_mulPsptMatrix(射影变换矩阵乘法计算) +static __host__ int _mulPsptMatrix(const PerspectiveMatrix &inpm1, + const PerspectiveMatrix &inpm2, + PerspectiveMatrix *outpm) +{ + // 检查输出指针是否为 NULL。 + if (outpm == NULL) + return NULL_POINTER; + + // 按照矩阵乘法的公式进行计算。由于矩阵乘法的计算公式在任何一本介绍线性代数 + // 的书中均有介绍,此处不再赘述。 + outpm->elem[0][0] = inpm1.elem[0][0] * inpm2.elem[0][0] + + inpm1.elem[0][1] * inpm2.elem[1][0] + + inpm1.elem[0][2] * inpm2.elem[2][0]; + outpm->elem[0][1] = inpm1.elem[0][0] * inpm2.elem[0][1] + + inpm1.elem[0][1] * inpm2.elem[1][1] + + inpm1.elem[0][2] * inpm2.elem[2][1]; + outpm->elem[0][2] = inpm1.elem[0][0] * inpm2.elem[0][2] + + inpm1.elem[0][1] * inpm2.elem[1][2] + + inpm1.elem[0][2] * inpm2.elem[2][2]; + outpm->elem[1][0] = inpm1.elem[1][0] * inpm2.elem[0][0] + + inpm1.elem[1][1] * inpm2.elem[1][0] + + inpm1.elem[1][2] * inpm2.elem[2][0]; + outpm->elem[1][1] = inpm1.elem[1][0] * inpm2.elem[0][1] + + inpm1.elem[1][1] * inpm2.elem[1][1] + + inpm1.elem[1][2] * inpm2.elem[2][1]; + outpm->elem[1][2] = inpm1.elem[1][0] * inpm2.elem[0][2] + + inpm1.elem[1][1] * inpm2.elem[1][2] + + inpm1.elem[1][2] * inpm2.elem[2][2]; + outpm->elem[2][0] = inpm1.elem[2][0] * inpm2.elem[0][0] + + inpm1.elem[2][1] * inpm2.elem[1][0] + + inpm1.elem[2][2] * inpm2.elem[2][0]; + outpm->elem[2][1] = inpm1.elem[2][0] * inpm2.elem[0][1] + + inpm1.elem[2][1] * inpm2.elem[1][1] + + inpm1.elem[2][2] * inpm2.elem[2][1]; + outpm->elem[2][2] = inpm1.elem[2][0] * inpm2.elem[0][2] + + inpm1.elem[2][1] * inpm2.elem[1][2] + + inpm1.elem[2][2] * inpm2.elem[2][2]; + + // 运算完毕,返回 + return NO_ERROR; +} + +// Host 成员方法:setPerspectiveMatrix(设置放射透视变换矩阵) +__host__ int PerspectiveTrans::setPerspectiveMatrix( + const PerspectiveMatrix &newpm) +{ + // 如果给定的矩阵是一个奇异阵(通过判断行列式是否为 0,可以得到一个矩阵是否 + // 为奇异阵),则直接报错返回,因为,一个奇异阵无法进行映射变换的计算。 + if (fabs(_detPsptMatrix(newpm)) < 1.0e-8f) + return INVALID_DATA; + + // 将 impType 成员变量赋成新值 + this->psptMatrix = newpm; + return NO_ERROR; +} + +// Host 成员方法:setPerspectivePoints(设置射影透视变换四点参数) +__host__ int PerspectiveTrans::setPerspectivePoints( + const PerspectivePoints &newpp) +{ + // 局部变量声明 + int errcode; + PerspectiveMatrix a1, a2, inva2; // 由于计算四点参数到矩阵,需要使用单位矩 + // 形作为中间过度,因此这里需要计算出两个 + // 矩阵,最后将这两个矩阵拼合起来。 + + // 首先,计算源坐标点到单位矩形的单侧变换矩阵。 + errcode = _pointsToPsptMatrix(newpp.srcPts, &a1); + if (errcode != NO_ERROR) + return errcode; + + // 然后计算目标坐标点到单位矩阵的单侧变换矩阵。 + errcode = _pointsToPsptMatrix(newpp.dstPts, &a2); + if (errcode != NO_ERROR) + return errcode; + + // 由于需要拼合的两个变换,是首先从源四点参数变换到单位矩形,然后再由单位矩 + // 形变换到目标四点参数,这样,需要对第二个单侧矩阵求逆,这样后续步骤才有实 + // 际的物理含义。 + errcode = _invPsptMatrix(a2, &inva2); + if (errcode != NO_ERROR) + return errcode; + + // 通过矩阵乘法将两两步变换进行整合,形成一个综合的矩阵。 + errcode = _mulPsptMatrix(a1, inva2, &this->psptMatrix); + if (errcode != NO_ERROR) + return errcode; + + // 处理完毕,返回退出。 + return NO_ERROR; +} + +// Host 成员方法:setPerspectivePoints(设置射影透视变换四点参数) +__host__ int PerspectiveTrans::setPerspectiveUnitRect( + const PerspectiveUnitRect &newur) +{ + // 将给定的单位矩形类型的数据转化成内部函数可识别的数据类型。这里将 + // PerspectiveUnitRect 转换为 float[4][2]。 + float tmppts[4][2]; + tmppts[0][0] = newur.pt00[0]; + tmppts[0][1] = newur.pt00[1]; + tmppts[1][0] = newur.pt10[0]; + tmppts[1][1] = newur.pt10[1]; + tmppts[2][0] = newur.pt01[0]; + tmppts[2][1] = newur.pt01[1]; + tmppts[3][0] = newur.pt11[0]; + tmppts[3][1] = newur.pt11[1]; + + // 调用内部的转换函数完成转换。 + return _pointsToPsptMatrix(tmppts, &this->psptMatrix); +} + +// Kernel 函数:_hardPerspectiveKer(利用硬件插值实现的射影变换) +static __global__ void _hardPerspectiveKer(ImageCuda outimg, + PerspectiveMatrix pm) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * outimg.pitchBytes + dstc; + + // 声明目标图像输出像素对应的源图像中的坐标点,由于计算会得到小数结果,因此 + // 使用浮点型存储该做标。 + float srcc, srcr; + + // 在计算过程中会使用到一些中间变量。 + float dstc1 = dstc + outimg.imgMeta.roiX1; // 由于输入 Kernel 的都是 ROI + float dstr1 = dstr + outimg.imgMeta.roiY1; // 子图像,所以,需要校正出 ROI + // 图像中像素在原图像中的像素坐 + // 标。 + float tmpc, tmpr; // 没有除以比例系数的临时坐标。 + float hh; // 比例系数,这个系数随着坐标位置变化而变化。 + + // 计算第一个输出坐标点对应的源图像中的坐标点。计算过程实际上是目标点的坐标 + // 组成的二维向量扩展成三维向量后乘以映射变换矩阵,之后再将得到的向量的 z + // 分量归一化到 1 后得到新的坐标(具体步骤可参看任何一本图像处理的书籍)。 + // 反映到代码上分为三个步骤,首先计算得到比例系数,然后计算得到没有初期比例 + // 系数的临时坐标,最后在将这个临时坐标除以比例系统,得到最终的源图像坐标。 + hh = pm.elem[2][0] * dstc1 + pm.elem[2][1] * dstr1 + pm.elem[2][2]; + tmpc = pm.elem[0][0] * dstc1 + pm.elem[0][1] * dstr1 + pm.elem[0][2]; + tmpr = pm.elem[1][0] * dstc1 + pm.elem[1][1] * dstr1 + pm.elem[1][2]; + srcc = tmpc / hh; + srcr = tmpr / hh; + + // 通过上面的步骤,求出了第一个输出坐标对应的源图像坐标。这里利用纹理内存的 + // 硬件插值功能,直接使用浮点型的坐标读取相应的源图像“像素”值,并赋值给目 + // 标图像。这里没有进行对源图像读取的越界检查,这是因为纹理内存硬件插值功能 + // 可以处理越界访问的情况,越界访问会按照事先的设置得到一个相对合理的像素颜 + // 色值,不会引起错误。 + outimg.imgMeta.imgData[dstidx] = tex2D(_hardIplInimgTex, srcc, srcr); + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++dstr >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + dstidx += outimg.pitchBytes; + + // 根据上一个源坐标位置计算当前的源坐标位置。由于只有 y 分量增加 1,因 + // 此,对应的源坐标只有在涉及到 dstr 的项上有变化,从而消除了一系列乘法 + // 计算,而通过两个源坐标的差值进行简单的加减法而得。 + hh += pm.elem[2][1]; + tmpc += pm.elem[0][1]; + tmpr += pm.elem[1][1]; + srcc = tmpc / hh; + srcr = tmpr / hh; + + // 将对应的源坐标位置出的插值像素写入到目标图像的当前像素点中。 + outimg.imgMeta.imgData[dstidx] = tex2D(_hardIplInimgTex, srcc, srcr); + } +} + +// Kernel 函数:_softPerspectiveKer(利用软件插值实现的射影变换) +static __global__ void _softPerspectiveKer( + ImageCuda inimg, ImageCuda outimg, PerspectiveMatrix pm) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= outimg.imgMeta.width || dstr >= outimg.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * outimg.pitchBytes + dstc; + + // 声明目标图像输出像素对应的源图像中的坐标点,由于计算会得到小数结果,因此 + // 使用浮点型存储该做标。 + float srcc, srcr; + + // 在计算过程中会使用到一些中间变量。 + float dstc1 = dstc + outimg.imgMeta.roiX1; // 由于输入 Kernel 的都是 ROI + float dstr1 = dstr + outimg.imgMeta.roiY1; // 子图像,所以,需要校正出 ROI + // 图像中像素在原图像中的像素坐 + // 标。 + float tmpc, tmpr; // 没有除以比例系数的临时坐标。 + float hh; // 比例系数,这个系数随着坐标位置变化而变化。 + + // 计算第一个输出坐标点对应的源图像中的坐标点。计算过程实际上是目标点的坐标 + // 组成的二维向量扩展成三维向量后乘以映射变换矩阵,之后再将得到的向量的 z + // 分量归一化到 1 后得到新的坐标(具体步骤可参看任何一本图像处理的书籍)。 + // 反映到代码上分为三个步骤,首先计算得到比例系数,然后计算得到没有初期比例 + // 系数的临时坐标,最后在将这个临时坐标除以比例系统,得到最终的源图像坐标。 + hh = pm.elem[2][0] * dstc1 + pm.elem[2][1] * dstr1 + pm.elem[2][2]; + tmpc = pm.elem[0][0] * dstc1 + pm.elem[0][1] * dstr1 + pm.elem[0][2]; + tmpr = pm.elem[1][0] * dstc1 + pm.elem[1][1] * dstr1 + pm.elem[1][2]; + srcc = tmpc / hh; + srcr = tmpr / hh; + + // 通过上面的步骤,求出了第一个输出坐标对应的源图像坐标。这里利用纹理内存的 + // 硬件插值功能,直接使用浮点型的坐标读取相应的源图像“像素”值,并赋值给目 + // 标图像。这里没有进行对源图像读取的越界检查,这是因为纹理内存硬件插值功能 + // 可以处理越界访问的情况,越界访问会按照事先的设置得到一个相对合理的像素颜 + // 色值,不会引起错误。 + outimg.imgMeta.imgData[dstidx] = _fanczosInterpoDev(inimg, srcc, srcr); + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++dstr >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + dstidx += outimg.pitchBytes; + + // 根据上一个源坐标位置计算当前的源坐标位置。由于只有 y 分量增加 1,因 + // 此,对应的源坐标只有在涉及到 dstr 的项上有变化,从而消除了一系列乘法 + // 计算,而通过两个源坐标的差值进行简单的加减法而得。 + hh += pm.elem[2][1]; + tmpc += pm.elem[0][1]; + tmpr += pm.elem[1][1]; + srcc = tmpc / hh; + srcr = tmpr / hh; + + // 将对应的源坐标位置出的插值像素写入到目标图像的当前像素点中。 + outimg.imgMeta.imgData[dstidx] = _fanczosInterpoDev(inimg, + srcc, srcr); + } +} + +// Host 成员方法:perspectiveTrans(射影透视变换) +__host__ int PerspectiveTrans::perspectiveTrans(Image *inimg, Image *outimg) +{ + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图像 + // 尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->width, inimg->height); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 获得输入图像对应的 ImageCuda 型的结构体。没有取 ROI 子图像,是因为输入图 + // 像的 ROI 区域相对于输出图像来说是没有意义的。与其在输出图像中把 ROI 区域 + // 以外的区域视为越界区域,还不如把它们纳入计算范畴更为方便。处理起来也不容 + // 易收到硬件对齐因素的限制。 + ImageCuda *inimgCud = IMAGE_CUDA(inimg); + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 针对不同的实现类型,选择不同的路径进行处理。 + cudaError_t cuerrcode; + switch (this->impType) { + // 使用硬件插值实现射影变换: + case PERSPECT_HARD_IPL: + // 设置数据通道描述符,因为只有一个颜色通道(灰度图),因此描述符中只有 + // 第一个分量含有数据。概述据通道描述符用于纹理内存的绑定操作。 + struct cudaChannelFormatDesc chndesc; + chndesc = cudaCreateChannelDesc(sizeof (unsigned char) * 8, 0, 0, 0, + cudaChannelFormatKindUnsigned); + + // 将输入图像的 ROI 子图像绑定到纹理内存。 + cuerrcode = cudaBindTexture2D( + NULL, &_hardIplInimgTex, inimg->imgData, &chndesc, + inimg->width, inimg->height, inimgCud->pitchBytes); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + // 调用 Kernel 函数,完成实际的图像射影变换。 + _hardPerspectiveKer<<>>( + outsubimgCud, this->psptMatrix); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + break; + + // 使用软件插值实现射影变换: + case PERSPECT_SOFT_IPL: + // 调用 Kernel 函数,完成实际的图像射影变换。 + _softPerspectiveKer<<>>( + *inimgCud, outsubimgCud, this->psptMatrix); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + break; + + // 其他方式情况下,直接返回非法数据错误。由于 NPP 实现已在前面跳转入了相应 + // 的其他函数,该 switch-case 语句中未包含对 NPP 实现的处理。 + default: + return INVALID_DATA; + } + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/PerspectiveTrans.h b/okano_3_0/PerspectiveTrans.h new file mode 100644 index 0000000..97bd838 --- /dev/null +++ b/okano_3_0/PerspectiveTrans.h @@ -0,0 +1,201 @@ +// PerspectiveTrans.h +// 创建人:于玉龙 +// +// 射影透视变换(Perspective Transform) +// 功能说明:实现图像的四点射影透视变换,给定图像的四个点以及这四个点经过变换后 +// 的坐标位置,求出整个图像随之变换后的样子。这种变换通常是绘画中使用 +// 的透视画法的技术是相一致的。这里,该算法主要被需求方用来完成待匹配 +// 图像的形变问题,使匹配算法更加准确。这里我们实现了两种插值算法,第 +// 一种使用 CUDA 默认的硬件插值方法,第二种则使用 Fanczos 软件插值。 +// +// 修订历史: +// 2012年12月09日(于玉龙) +// 初始版本。 +// 2012年12月11日(于玉龙) +// 调整了算法中对于图像 ROI 的处理方式,使其更加符合常理。 +// 2012年12月12日(于玉龙) +// 增加了通过单位矩形的形式输入射影变换的方法。 +// 增加了基于软件插值的射影透视变换。 + +#ifndef __PERSPECTIVETRANS_H__ +#define __PERSPECTIVETRANS_H__ + +#include "ErrorCode.h" +#include "Image.h" + +// 宏:PERSPECT_SOFT_IPL +// 用于设置 PerspectiveTrans 类中的 impType 成员变量,告知类的实例选用软件实现 +// 的 Fanczos 插值实现旋转变换。 +#define PERSPECT_SOFT_IPL 1 + +// 宏:PERSPECT_HARD_IPL +// 用于设置 PerspectiveTrans 类中的 impType 成员变量,告知类的实例选用基于纹理 +// 内存的硬件插值功能实现的旋转变换。 +#define PERSPECT_HARD_IPL 2 + +// 结构体:PerspectiveMatrix(射影矩阵) +// 以矩阵的形式表达射影透视变换的参数。该结构体也是算法内部实际使用的变换表达方 +// 式,这种表达方式虽然不够直观,但是却能够很好的用于计算。所有通过坐标点形式输 +// 入的参数,最终也会转化位这种形式。 +typedef struct PerspectiveMatrix_st { + float elem[3][3]; // 矩阵元素,是一个 3×3 的矩阵 +} PerspectiveMatrix; + +// 结构体:PerspectiveUnitRect(单位矩形变换参数) +// 射影变换使用的一种参数形式,给定单位矩形在进行影变换后的四个点坐标。其中, +// ptXY 单位矩形上的四个角点 (X, Y) 的新坐标,下标 [0] 表示 x 坐标,下标 [1] 表 +// 示 y 坐标。 +typedef struct PerspectiveUnitRect_st { + float pt00[2]; // 点 (0, 0) 对应的新坐标。 + float pt10[2]; // 点 (1, 0) 对应的新坐标。 + float pt01[2]; // 点 (0, 1) 对应的新坐标。 + float pt11[2]; // 点 (1, 1) 对应的新坐标。 +} PerspectiveUnitRect; + +// 结构体:PerspectivePoints(射影变换坐标点形式参数) +// 射影变换使用的一种最形象化的参数。通过给定四个原始坐标点,和对应的四个新坐 +// 标,来刻画射影变换。其中,下标 [0] 表示 x 坐标,下标 [1] 表示 y 坐标。 +typedef struct PerspectivePoints_st { + float srcPts[4][2]; // 四个原始坐标点 + float dstPts[4][2]; // 变换后四个点对应的新坐标 +} PerspectivePoints; + + +// 类:PerspectiveTrans +// 继承自:无 +// 实现图像的四点射影透视变换,给定图像的四个点以及这四个点经过变换后的坐标位 +// 置,求出整个图像随之变换后的样子。这种变换通常是绘画中使用的透视画法的技术是 +// 相一致的。这里,该算法主要被需求方用来完成待匹配图像的形变问题,使匹配算法更 +// 加准确。这里我们实现了两种插值算法,第一种使用 CUDA 默认的硬件插值方法,第二 +// 种则使用 Fanczos 软件插值。 +class PerspectiveTrans { + +protected: + + // 成员变量:impType(实现类型) + // 设定三种实现类型中的一种,在调用仿射变换函数的时候,使用对应的实现方式。 + int impType; + + // 成员变量:psptMatrix(射影透视变换矩阵) + // 描述射影透视变换的矩阵参数形式。由于这种矩阵可以直接用于计算,因此所有通 + // 过其他形式传递进来的参数,都会实现转换成这种表示形式。由于实际计算过程中 + // 是通过目标点反算回源图像坐标点,因此,该矩阵实际上是逆运算的矩阵。 + PerspectiveMatrix psptMatrix; + +public: + + // 构造函数:PerspectiveTrans + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + PerspectiveTrans() + { + // 使用默认值为类的各个成员变量赋值。 + impType = PERSPECT_HARD_IPL; // 实现类型的默认值为“硬件插值” + psptMatrix.elem[0][0] = 1.0f; // 变换矩阵初始化为一个单位矩阵。选择单 + psptMatrix.elem[0][1] = 0.0f; // 位矩阵的原因是由于单位阵不进行任何变 + psptMatrix.elem[0][2] = 0.0f; // 化,输入图像和输出图像是完全一致的。 + psptMatrix.elem[1][0] = 0.0f; + psptMatrix.elem[1][1] = 1.0f; + psptMatrix.elem[1][2] = 0.0f; + psptMatrix.elem[2][0] = 0.0f; + psptMatrix.elem[2][1] = 0.0f; + psptMatrix.elem[2][2] = 1.0f; + } + + // 构造函数:PerspectiveTrans + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + PerspectiveTrans ( + int imptype // 实现类型(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值。 + impType = PERSPECT_HARD_IPL; // 实现类型的默认值为“硬件插值” + psptMatrix.elem[0][0] = 1.0f; // 变换矩阵初始化为一个单位矩阵。选择单 + psptMatrix.elem[0][1] = 0.0f; // 位矩阵的原因是由于单位阵不进行任何变 + psptMatrix.elem[0][2] = 0.0f; // 化,输入图像和输出图像是完全一致的。 + psptMatrix.elem[1][0] = 0.0f; + psptMatrix.elem[1][1] = 1.0f; + psptMatrix.elem[1][2] = 0.0f; + psptMatrix.elem[2][0] = 0.0f; + psptMatrix.elem[2][1] = 0.0f; + psptMatrix.elem[2][2] = 1.0f; + + // 根据参数列表中的值设定成员变量的初值 + this->setImpType(imptype); + } + + // 成员方法:getImpType(读取实现类型) + // 读取 impType 成员变量的值。 + __host__ __device__ int // 返回值:当前 impType 成员变量的值。 + getImpType() const + { + // 返回 impType 成员变量的值。 + return this->impType; + } + + // 成员方法:setImpType(设置实现类型) + // 设置 impType 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setImpType( + int imptype // 新的实现类型 + ) { + // 检查输入参数是否合法 + if (imptype != PERSPECT_HARD_IPL && imptype != PERSPECT_SOFT_IPL) + return INVALID_DATA; + + // 将 impType 成员变量赋成新值 + this->impType = imptype; + return NO_ERROR; + } + + // 成员方法:getPerspectiveMatrix(读取射影透视变换矩阵) + // 读取 psptMatrix 成员变量的值。 + __host__ __device__ PerspectiveMatrix // 返回值:当前 psptMatrix 成员变量 + // 的值。 + getPerspectiveMatrix() const + { + // 返回 psptMatrix 成员变量的值。 + return this->psptMatrix; + } + + // Host 成员方法:setPerspectiveMatrix(设置射影透视变换矩阵) + // 设置 psptMatrix 成员变量的值。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + setPerspectiveMatrix( + const PerspectiveMatrix &newpm // 新的射影变换矩阵 + ); + + // Host 成员方法:setPerspectivePoints(设置射影透视变换四点参数) + // 设置射影变换的参数,虽然给定的形式是四点变换,但是该函数会将这个四点参数 + // 转换为矩阵形式。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + setPerspectivePoints( + const PerspectivePoints &newpp // 新的摄影变换四点参数 + ); + + // Host 成员方法:setPerspectiveUnitRect(以单位矩形的形式设定变换参数) + // 通过使用单位矩形的形式设定射影变换的参数,这个方式给定单位矩形上四个角点 + // 在变换后的坐标来表述射影变换的参数。 + __host__ int // 返回值:函数是否正确执行,若函 + // 数正确执行,返回 NO_ERROR。 + setPerspectiveUnitRect( + const PerspectiveUnitRect &newur // 新的单位矩形形式的参数 + ); + + // Host 成员方法:perspectiveTrans(射影透视变换) + // 射影透视变换的主函数。其中输入图像与输出图像的原点坐标对齐。如果输出图像 + // 为一个空图像,则输出图像的尺寸会和输入图像等大小。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + perspectiveTrans( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/PriFeatureCheckC.cu b/okano_3_0/PriFeatureCheckC.cu new file mode 100644 index 0000000..083f52b --- /dev/null +++ b/okano_3_0/PriFeatureCheckC.cu @@ -0,0 +1,261 @@ +// PriFeatureCheckC.cu +// 任意形状曲线的基础特征检查 + +#include "PriFeatureCheckC.h" + +// 宏:DEF_BLOCK_X +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 + + +// Kernel 函数:_ComputeSquareKer(计算闭合曲线面积) +// 计算闭合曲线的面积,方便后续计算。 +static __global__ void // Kernel 函数无返回值 +_computeSquareKer( + int *coordset, // 输入曲线的坐标。 + int *area, // 闭合曲线的面积。 + int curlen // 曲线点的个数 +); + +// Host 函数:comCurSquare(计算闭合曲线的面积) +static __host__ int // 函数若正确执行返回 NO_ERROR +comCurSquare( + int *coordset, // 输入曲线的坐标集 + int *area, // 闭合曲线的面积 + int curlen // 闭合曲线点数目 +); +/* +// Kernel 函数:_ComputeCornerCntKer(计算角点个数) +// 计算曲线角点的个数,原理是道格拉斯算法。 +static __global__ void // Kernel 函数无返回值 +_computeCornerCntKer( + CurveCuda curve, // 输入曲线 + int *count // 角点的个数 +); + +// Host 函数:ComputeCornerCnt(计算角点个数) +// 计算曲线角点的个数,原理是道格拉斯算法。 +static __host__ int // 函数若正确执行返回 NO_ERROR +computeCornerCnt( + Curve *curve, // 输入曲线 + int *count // 角点的个数 +); +*/ +// Kernel 函数:_computeSquareKer(计算闭合曲线面积) +static __global__ void _computeSquareKer( + int *coordset, int *area, int curlen) +{ + // 判断参数是否合法。 + if (coordset == NULL || area == NULL) + return; + + // 线程索引。 + int idx = threadIdx.x; + + // 判断是否越界。 + if (idx < 0 || idx >= curlen) + return; + + // 当前处理的点及其相邻的下一个点的横坐标的索引。 + int curxidx = 2 * idx; + int nextxidx = curxidx + 2; + + // 记录当前处理点和相邻点的坐标。 + int curx = coordset[curxidx]; + int nextx = coordset[nextxidx]; + int cury = coordset[curxidx + 1]; + int nexty = coordset[nextxidx + 1]; + // 一个中间变量,它是对曲线上相邻两个点的坐标进行运算。 + int coordtemp = abs(curx * nexty - nextx * cury); + + // 使用原子加求面积和中心坐标的中间值。 + atomicAdd(area, coordtemp); + + // 块内同步。 + __syncthreads(); + + // 计算闭合曲线最终面积和中心坐标。 + if (threadIdx.x != 0) + return; + *area = *area >> 1; +} + +// Host 函数:comCurSquare(计算闭合曲线的面积) +static __host__ int comCurSquare( + int *coordset, int *area, int curlen) +{ + // 判断参数合法性。 + if (coordset == NULL || area == NULL) + return INVALID_DATA; + // 在 Device 端申请 area 的空间 + int *areaCud; + int cudaerrcode = cudaMalloc((void**)&areaCud, sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + cudaerrcode = cudaMemcpy(areaCud, area, sizeof (int), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 计算线程块的大小。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X; + gridsize.x = (curlen + blocksize.x - 1) / blocksize.x; + _computeSquareKer<<>>(coordset, areaCud, curlen); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 将 area 拷贝至 Host 端 + cudaerrcode = cudaMemcpy(area, areaCud, sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 释放 Device 端空间。 + cudaFree(areaCud); + + // 处理完毕,退出。 + return NO_ERROR; +} +/* +// Kernel 函数:_ComputeCornerCntKer(计算角点个数) +static __global__ void _computeCornerCntKer(CurveCuda curve, int *count) +{ + // UN_IMPLEMENT + return; +} + +// Host 函数:ComputeCornerCnt(计算角点个数) +static __host__ int computeCornerCnt(Curve *curve, int *count) +{ + // UN_IMPLEMENT + return NO_ERROR; +} +*/ +// Host 函数:priFeatureCheckC(任意曲线的基本特征检查) +__host__ int PriFeatureCheckC::priFeatureCheckC( + Curve *curve, float *result, bool *errorJudge) +{ + // 判断参数是否合理 + if (curve == NULL || curve->crvData == NULL || + result == NULL || errorJudge == NULL) + return INVALID_DATA; + // 取出曲线的点数 + int pntcnt = curve->curveLength; + + // 标记该曲线是否是闭合曲线,如果不是闭合曲线则其面积默认为 0。 + // 若闭合标记为 1。 + int flag = 0; + if (curve->closed == true) + flag = 1; + + // 申请一张临时图片,此图片由 curve 转换来,利用该图像调用 Moments 和 MDR + // 算法。 + Image *inimg; + int errcode = ImageBasicOp::newImage(&inimg); + if (errcode != cudaSuccess) + return errcode; + + errcode = ImageBasicOp::makeAtHost(inimg, curve->maxCordiX + 1, + curve->maxCordiY + 1); + if (errcode != cudaSuccess) { + ImageBasicOp::deleteImage(inimg); + return errcode; + } + + errcode = imgconvert.curConvertToImg(curve, inimg); + if (errcode != cudaSuccess) { + ImageBasicOp::deleteImage(inimg); + return errcode; + } + + // 将结果图像拷贝到 Host 端 + errcode = ImageBasicOp::copyToHost(inimg); + if (errcode != cudaSuccess) { + ImageBasicOp::deleteImage(inimg); + return errcode; + } + + // (1)计算曲线点数和 MDR 周长的比值 + DirectedRect *rect = new DirectedRect[1]; + errcode = sdr.smallestDirRect(inimg, rect); // 调用最小有向外接矩形 + if (errcode != cudaSuccess) { + ImageBasicOp::deleteImage(inimg); + delete rect; + return errcode; + } + + // 计算最小外接矩形的长和宽。 + int length = rect->length1; + int width = rect->length2; + + // 最小外接矩形的周长和面积 + float mdrperimeter = 2 * (length + width); + float square = length * width; + + // 计算 LengthRatio + result[0] = pntcnt / mdrperimeter; + + // (1)判断 LengthRatio 是否在指标范围内。 + if (result[0] >= minLengthRatio && result[0] <= maxLengthRatio) + errorJudge[0] = false; + else + errorJudge[0] = true; + + // (2)计算 MDRSideRatio 并判断是否在指标范围内。 + result[1] = width * 1.0f / length; + if (result[1] >= minMDRSideRatio && result[1] <= maxMDRSideRatio) + errorJudge[1] = false; + else + errorJudge[1] = true; + + + // (3)检查 AMIS + MomentSet *momentset = new MomentSet[1]; + // 调用算法,求解 AMIS + errcode = this->ami.affineMoments(inimg, momentset); + if (errcode != cudaSuccess) { + ImageBasicOp::deleteImage(inimg); + delete rect; + delete momentset; + return errcode; + } + // 存储 9 个 AMIS 值 + double amis[9] = {momentset->ami1, momentset->ami2, momentset->ami3, + momentset->ami4, momentset->ami6, momentset->ami7, + momentset->ami8, momentset->ami9, momentset->ami19}; + // 若 amis 中某一项不满足条件则在 errorjudge 中赋为 false + for (int i = 0; i < 9; i++) { + if (abs(amis[i] - this->avgAMIs[i]) > maxAMIsError[i]) + errorJudge[2] = true; + else + errorJudge[2] = false; + } + + // (4)闭合曲线的面积与 MDR 的面积比 + // 首先判断是否闭合曲线,若不是闭合曲线则跳过此步骤 + if (flag == 0) { + int area = 0; + errcode = comCurSquare(curve->crvData, &area, pntcnt); + if (errcode != cudaSuccess) { + ImageBasicOp::deleteImage(inimg); + delete rect; + delete momentset; + return errcode; + } + + // 计算闭合曲线的面积与 MDR 的面积比 + result[3] = area * 1.0f / square; + if (result[3] >= minContourAreaRatio || result[3] <= maxContourAreaRatio) + errorJudge[3] = false; + else + errorJudge[3] = true; + } + + // (5)曲线个数与角点比值还未实现 + return NO_ERROR; + +} + + diff --git a/okano_3_0/PriFeatureCheckC.h b/okano_3_0/PriFeatureCheckC.h new file mode 100644 index 0000000..ad037aa --- /dev/null +++ b/okano_3_0/PriFeatureCheckC.h @@ -0,0 +1,435 @@ +// PriFeatureCheckC.h +// 创建人:刘婷 + +// 任意形状曲线基本特征检测(Primary Feature Check for arbitrarily shaped-Curve) +// 功能说明:对于任意形状的曲线,计算该曲线的几种特征值:(1)曲线点数与曲线最小 +// 有向外接矩形(Smallest Direction Rectangle,简称 MDR)周长比; +// (2)MDR 短边与长边比;(3)AMI(AFFINE MOMENT INVARIANTS)平均值; +// (4)曲线角点个数与曲线的点数比;(5)曲线多边形面积和曲线 MDR 面积 +// 比。并判断这些特征值是否在给定指标范围内,从而给出错误判断。 +// 修订历史: +// 2013年08月03日(刘婷) +// 编写头文件。 +// 2013年09月16日(刘婷) +// 完成求闭合曲线的面积和中心坐标函数。 +// 2013年10月16日(刘婷) +// 实现除角点检测的其他功能。 +// 2013年10月17日(刘婷) +// 修改 BUG,更改代码规范。 + +#ifndef __PRIFEATURECHECKC_H__ +#define __PRIEFATURECHECKC_H__ + +#include "ErrorCode.h" +#include "Image.h" +#include "Curve.h" +#include "ImgConvert.h" +#include "Moments.h" +#include "SmallestDirRect.h" + +// 宏变量,默认设置 AMI 的长度为 9 +#define AMISIZE 9 + +// 类:PriFeatureCheckC(任意形状曲线基本特征检测) +// 继承自:无 +// 实现任意形状曲线的基本特征检测。对于任意形状的曲线,计算该曲线的几种特征值: +//(1)曲线点数与曲线最小有向外接矩形(Smallest Direction Rectangle,简称 MDR) +// 周长比;(2)MDR 短边与长边比;(3)AMI 平均值;(4)曲线角点个数与曲线的点数 +// 比;(5)曲线多边形面积和曲线 MDR 面积比。并判断这些特征值是否在给定指标范围 +// 内,从而给出错误判断。 +class PriFeatureCheckC { + +protected: + + // 成员变量:minLengthRatio,maxLengthRatio(给定指标范围) + // 曲线的点数与曲线的最小有向外接矩形(MDR)周长比值的下限、上限。 + float minLengthRatio, maxLengthRatio; + + // 成员变量:minMDRSideRatio,maxMDRSideRatio(给定指标范围) + // MDR的短边与长边的比值的下限、上限。 + float minMDRSideRatio, maxMDRSideRatio; + + // 成员变量:minPolygonAreaRatio,maxPloygonArearRatio(给定指标范围) + // 曲线的多边形面积与曲线的 MDR 面积比值的下限、上限。 + float minContourAreaRatio, maxContourAreaRatio; + + // 成员变量:minVertexNumRatio,maxVertexNumRatio(给定指标范围) + // 曲线角点个数与曲线点数比值的下限、上限。 + float minVertexNumRatio, maxVertexNumRatio; + + // 成员变量:avgAMIs(平均 AMI 值) + // 按照需求,它是一个长度为 9 的数组。 + double *avgAMIs; + + // 成员变量:maxAMIsError(AMI 容差向量) + // 按照需求,它是一个长度为 9 的数组。 + double *maxAMIsError; + + // 成员变量:imgconvert(ImgConvert 对象) + // 在本算法中需要调用 ImgConvert 算法将曲线转为图像,因此此处将 ImgConvert + // 对象作为该类的成员变量,方便使用。 + ImgConvert imgconvert; + + // 调用最小外接矩形算法得到最小外接矩形的长短边。 + SmallestDirRect sdr; + + // 成员变量:ami(Moments 对象) + // 在本算法中需要调用 Moments 算法,计算图像的 AMIS。 + Moments ami; + + +public: + + // 构造函数:PriFeatureCheckC + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + PriFeatureCheckC() :imgconvert(), ami(), sdr() + { + // 使用默认值为成员变量赋值。 + minLengthRatio = 0.0f; + maxLengthRatio = 1.0f; + minMDRSideRatio = 0.0f; + maxMDRSideRatio = 1.0f; + minVertexNumRatio = 0.0f; + maxVertexNumRatio = 1.0f; + minContourAreaRatio = 0.0f; + maxContourAreaRatio = 1.0f; + avgAMIs = NULL; + maxAMIsError = NULL; + } + + // 构造函数:PriFeatureCheckC + // 有参数版本的构造函数,这些值在运行过程中可以改变。 + __host__ __device__ + PriFeatureCheckC( + // 各参数含义见上。 + float minlengthratio, float maxlengthratio, + float minmdrsideratio, float maxmdrsideratio, + float minvertexnumratio, float maxvertexnumratio, + float mincontourarearatio, float maxcontourarearatio, + double *avgamis, double *maxamiserror + ):imgconvert(), ami(), sdr() + { + // 使用默认值为成员变量赋值。 + minLengthRatio = 0.0f; // 各参数含义见上 + maxLengthRatio = 1.0f; // 各参数含义见上 + minMDRSideRatio = 0.0f; // 各参数含义见上 + maxMDRSideRatio = 1.0f; // 各参数含义见上 + minVertexNumRatio = 0.0f; // 各参数含义见上 + maxVertexNumRatio = 1.0f; // 各参数含义见上 + minContourAreaRatio = 0.0f; // 各参数含义见上 + maxContourAreaRatio = 1.0f; // 各参数含义见上 + avgAMIs = NULL; // 各参数含义见上 + maxAMIsError = NULL; // 各参数含义见上 + + // 根据参数列表中的值设定成员变量的初值 + setMinLengthRatio(minlengthratio); + setMaxLengthRatio(maxlengthratio); + setMinMDRSideRatio(minmdrsideratio); + setMaxMDRSideRatio(maxmdrsideratio); + setMinContourAreaRatio(mincontourarearatio); + setMaxContourAreaRatio(maxcontourarearatio); + setMinVertexNumRatio(minvertexnumratio); + setMaxVertexNumRatio(maxvertexnumratio); + setAvgAMIs(avgamis); + setMaxAMIsError(maxamiserror); + } + + // 成员方法:getMinLengthRatio(获取指标 minLengthRatio) + // 获取曲线的点数与曲线的最小有向外接矩形(MDR)周长比值的下限。 + __host__ __device__ float // 返回值:获取指标 minLengthRatio。 + getMinLengthRatio() const + { + // 返回指标 minLengthRatio。 + return this->minLengthRatio; + } + + // 成员方法:setMinLengthRatio(设置指标 minLengthRatio 的值) + // 设置曲线的点数与曲线的最小有向外接矩形(MDR)周长比值的下限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMinLengthRatio( + float minlengthratio // 新的 minLengthRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (minlengthratio <= 0 || minlengthratio >= this->maxLengthRatio) + return INVALID_DATA; + + // 为成员变量 minLengthRatio 赋新值。 + this->minLengthRatio = minlengthratio; + + return NO_ERROR; + } + + // 成员方法:getMaxLengthRatio(获取指标 maxLengthRatio) + // 获取曲线的点数与曲线的最小有向外接矩形(MDR)周长比值的上限。 + __host__ __device__ float // 返回值:获取指标 maxLengthRatio。 + getMaxLengthRatio() const + { + // 返回指标 maxLengthRatio。 + return this->maxLengthRatio; + } + + // 成员方法:setMaxLengthRatio(设置指标 maxLengthRatio 的值) + // 设置曲线的点数与曲线的最小有向外接矩形(MDR)周长比值的上限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMaxLengthRatio( + float maxlengthratio // 新的 maxLengthRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (maxlengthratio <= 0 || maxlengthratio <= this->minLengthRatio) + return INVALID_DATA; + + // 为成员变量 maxLengthRatio 赋新值。 + this->maxLengthRatio = maxlengthratio; + + return NO_ERROR; + } + + // 成员方法:getMinMDRSideRatio(获取指标 minMDRSideRatio) + // 获取MDR的短边与长边的比值的下限。 + __host__ __device__ float // 返回值:获取指标 minMDRSideRatio。 + getMinMDRSideRatio() const + { + // 返回指标 minMDRSideRatio。 + return this->minMDRSideRatio; + } + + // 成员方法:setMinMDRSideRatio(设置指标 minMDRSideRatio 的值) + // 设置MDR的短边与长边的比值的下限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMinMDRSideRatio( + float minmdrsideratio // 新的 minMDRSideRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (minmdrsideratio <= 0 || minmdrsideratio >= this->minMDRSideRatio) + return INVALID_DATA; + + // 为成员变量 minMDRSideRatio 赋新值。 + this->minMDRSideRatio = minmdrsideratio; + + return NO_ERROR; + } + + // 成员方法:getMaxMDRSideRatio(获取指标 maxMDRSideRatio) + // 获取MDR的短边与长边的比值的上限。 + __host__ __device__ float // 返回值:获取指标 maxMDRSideRatio。 + getMaxMDRSideRatio() const + { + // 返回指标 maxMDRSideRatio。 + return this->maxMDRSideRatio; + } + + // 成员方法:setMaxMDRSideRatio(设置指标 maxMDRSideRatio 的值) + // 设置MDR的短边与长边的比值的上限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMaxMDRSideRatio( + float maxmdrsideratio // 新的 maxMDRSideRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (maxmdrsideratio <= 0 || maxmdrsideratio <= this->minMDRSideRatio) + return INVALID_DATA; + + // 为成员变量 maxMDRSideRatio 赋新值。 + this->maxMDRSideRatio = maxmdrsideratio; + + return NO_ERROR; + } + + // 成员方法:getMinContourAreaRatio(获取指标 minContourAreaRatio) + // 曲线的多边形面积与曲线的 MDR 面积比值的下限。 + __host__ __device__ float // 返回值:获取指标 minContourAreaRatio。 + getMinContourAreaRatio() const + { + // 返回指标 minContourAreaRatio。 + return this->minContourAreaRatio; + } + + // 成员方法:setMinContourAreaRatio(设置指标 minContourAreaRatio的值) + // 设置曲线的多边形面积与曲线的 MDR 面积比值的下限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMinContourAreaRatio( + float mincontourarearatio // 新的 minContourAreaRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (mincontourarearatio <= 0 || + mincontourarearatio >= this->maxContourAreaRatio) + return INVALID_DATA; + + // 为成员变量 minContourAreaRatio 赋新值。 + this->minContourAreaRatio = mincontourarearatio; + + return NO_ERROR; + } + + // 成员方法:getMaxContourAreaRatio(获取指标 maxContourAreaRatio) + // 获取曲线的多边形面积与曲线的 MDR 面积比值的上限。 + __host__ __device__ float // 返回值:获取指标 maxContourAreaRatio。 + getMaxContourAreaRatio() const + { + // 返回指标 maxContourAreaRatio。 + return this->maxContourAreaRatio; + } + + // 成员方法:setMaxContourAreaRatio(设置指标 maxContourAreaRatio 的值) + // 设置曲线的多边形面积与曲线的 MDR 面积比值的上限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMaxContourAreaRatio( + float maxcontourarearatio // 新的 maxContourAreaRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (maxcontourarearatio <= 0 || + maxcontourarearatio <= this->minContourAreaRatio) + return INVALID_DATA; + + // 为成员变量 maxContourAreaRatio 赋新值。 + this->maxContourAreaRatio = maxcontourarearatio; + + return NO_ERROR; + } + + // 成员方法:getMinVertexNumRatioo(获取指标 minVertexNumRatio) + // 获取曲线的角点个数与曲线的点数比值的下限。 + __host__ __device__ float // 返回值:获取指标 minVertexNumRatio。 + getMinVertexNumRatio() const + { + // 返回指标 minVertexNumRatio。 + return this->minVertexNumRatio; + } + + // 成员方法:setMinVertexNumRatio(设置指标 minVertexNumRatio 的值) + // 设置曲线的角点个数与曲线的点数比值的下限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMinVertexNumRatio( + float minvertexnumratio // 新的 minVertexNumRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (minvertexnumratio <= 0 || + minvertexnumratio >= this->maxVertexNumRatio) + return INVALID_DATA; + + // 为成员变量 minVertexNumRatio 赋新值。 + this->minVertexNumRatio = minvertexnumratio; + + return NO_ERROR; + } + + // 成员方法:getMaxVertexNumRatioo(获取指标 maxVertexNumRatio) + // 获取曲线的角点个数与曲线的点数比值的上限。 + __host__ __device__ float // 返回值:获取指标 maxVertexNumRatio。 + getMaxVertexNumRatio() const + { + // 返回指标 maxVertexNumRatio。 + return this->maxVertexNumRatio; + } + + // 成员方法:setMaxVertexNumRatio(设置指标 maxVertexNumRatio 的值) + // 设置曲线的角点个数与曲线的点数比值的上限。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMaxVertexNumRatio( + float maxvertexnumratio // 新的 maxVertexNumRatio 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (maxvertexnumratio <= 0 || + maxvertexnumratio <= this->minVertexNumRatio) + return INVALID_DATA; + + // 为成员变量 maxVertexNumRatio 赋新值。 + this->maxVertexNumRatio = maxvertexnumratio; + + return NO_ERROR; + } + + // 成员方法:getAvgAMIs(获取平均 AMI) + // 获取平均 AMI 值。 + __host__ __device__ double* // 返回值:获取平均 AMI 值。 + getAvgAMIs() const + { + // 返回 avgAMIs。 + return this->avgAMIs; + } + + // 成员方法:setAvgAMIs(设置平均 AMI 值) + // 设置平均 AMI 值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setAvgAMIs( + double *avgamis // 新的 AvgAMIs 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (avgamis == NULL) + return INVALID_DATA; + + for (int i = 0; i < AMISIZE; i++) { + // 若新的 AvgAMIs 包含负值,则报错。 + if (avgamis[i] < 0) + return INVALID_DATA; + } + + // 为成员变量 avgAMIs 赋新值。 + this->avgAMIs = avgamis; + + return NO_ERROR; + } + + // 成员方法:getMaxAMIsError(获取 AMI 最大差值向量) + // 获取 AMI 最大差值向量,该向量的每一个值都是一个指标。 + __host__ __device__ double* // 返回值:获取平均 AMI 最大差值向量。 + getMaxAMIsError() const + { + // 返回 maxAMIsError。 + return this->maxAMIsError; + } + + // 成员方法:setMaxAMIsError(设置 AMI 最大差值向量) + // 设置 AMI 最大差值向量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setMaxAMIsError( + double *maxamiserror // 新的 maxAMIsError 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (maxamiserror == NULL) + return INVALID_DATA; + + for (int i = 0; i < AMISIZE; i++) { + // 若新的 maxamiserror 包含负值,则报错。 + if (maxamiserror[i] < 0) + return INVALID_DATA; + } + + // 为成员变量 MaxAMIsError 赋新值。 + this->maxAMIsError = maxamiserror; + + return NO_ERROR; + } + + // 成员方法:priFeatureCheckC(任意曲线的基本特征值检查) + // 根据给定 Curve 信息,提取出来曲线的特征值进而检查这些特征值是否在给定指 + // 标范围内。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + priFeatureCheckC( + Curve *curve, // 输入 Curve 类对象 + float *result, // 结果数组,按照需求,它是一个长度为 5 的数组, + // 存储的是本算法计算出来的特征值(即 length + // Ratio,MDR Side Ratio,AIM Errors,Vertex Num + // Ratio,Contour Area Ratio)。 + + bool *errorJudge // 存放 bool 值的结果数组,按照需求,长度为 5 的 + // 数组,初始化均为 false,存放的是特征值与给定指 + // 标之间的关系,若在指标范围内则为 true,反之为 + // false。 + ); + +}; + +#endif + diff --git a/okano_3_0/Rectangle.cu b/okano_3_0/Rectangle.cu new file mode 100644 index 0000000..b8d2376 --- /dev/null +++ b/okano_3_0/Rectangle.cu @@ -0,0 +1,6 @@ +// Rectangle.cu +// 矩形公共结构体及公共函数。 + +#include "Rectangle.h" + +// 由于所有的公共结构体和宏已在 FanczosIpl.h 中定义,故本文件无任何代码内容。 \ No newline at end of file diff --git a/okano_3_0/Rectangle.h b/okano_3_0/Rectangle.h new file mode 100644 index 0000000..89ac0f2 --- /dev/null +++ b/okano_3_0/Rectangle.h @@ -0,0 +1,88 @@ +// Rectangle.h +// 创建人:刘瑶 +// +// 矩形(Rectangle) +// 功能说明:定义 BoundingRect 和 SmallestDirRect 里用到的公共结构体。 +// +// 修订历史: +// 2013年01月09日(刘瑶) +// 初始版本 +// 2013年03月21日(刘瑶) +// 将公共结构体的命名由 BR_XXX 统一为 RECT_XXX + +#ifndef __RECTANGLE_H__ +#define __RECTANGLE_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 结构体:Quadrangle(有向矩形) +// 该结构体定了有向矩形的数据结构,其中包含了矩形的方向角度,4个角的坐标。 +typedef struct Quadrangle_st { + float angle; // 有向矩形的角度,即矩形的长边与水平方向的顺时针夹角 + int points[4][2]; // 有向矩形的四个角坐标,为左上,右上,右下,左下四个点 +} Quadrangle; + +// 结构体:DirectedRect(有向矩形) +// 该结构体定了有向矩形的另一种数据结构,其中包含了矩形的方向角度,中心点坐标, +// 长边长度,短边长度。 +typedef struct DirectedRect_st { + float angle; // 有向矩形的角度,即矩形的长边与水平方向的顺时针夹 + // 角 + int centerPoint[2]; // 有向矩形的中心点坐标 + int length1, length2; // 有向矩形的长边,短边长度,length1 ≧ length2 +} DirectedRect; + +// 结构体:RotationInfo(旋转矩阵信息) +// 该结构体定义了旋转矩阵的信息,包含了旋转角度,余弦值,正弦值。 +typedef struct RotationInfo_st +{ + float radian; // 旋转角度对应的弧度。 + float cos; // 旋转角度的余弦值。 + float sin; // 旋转角度的正弦值。 +} RotationInfo; + +// 结构体:BoundBox(包围矩形的边界坐标) +// 该结构体定义了包围矩形的边界坐标信息。坐标的数据类型为 float。 +typedef struct BoundBox_st +{ + float left; // 左边界坐标。 + float right; // 右边界坐标。 + float top; // 上边界坐标。 + float bottom; // 下边界坐标。 +} BoundBox; + +// 结构体:BoundBoxInt(包围矩形的边界坐标) +// 该结构体定义了包围矩形的边界坐标信息。坐标的数据类型为 int。 +typedef struct BoundBoxInt_st +{ + int left; // 左边界坐标。 + int right; // 右边界坐标。 + int top; // 上边界坐标。 + int bottom; // 下边界坐标。 +} BoundBoxInt; + +// 宏:M_PI +// π值。对于某些操作系统,M_PI可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +// 宏:RECT_ROTATE_POINT +// 根据给定的旋转信息,计算旋转后的点坐标。 +#define RECT_ROTATE_POINT(ptsor, pttar, rtinfo) do { \ + (pttar)[0] = (ptsor)[0] * (rtinfo).cos - \ + (ptsor)[1] * (rtinfo).sin; \ + (pttar)[1] = (ptsor)[0] * (rtinfo).sin + \ + (ptsor)[1] * (rtinfo).cos; \ + } while (0) + +#endif + +// 宏:RECT_RAD_TO_DEG +// 从弧度转换为角度。 +#define RECT_RAD_TO_DEG(rad) ((rad) * 180.0f / M_PI) + +// 宏:RECT_DEG_TO_RAD +// 从角度转换为弧度。 +#define RECT_DEG_TO_RAD(deg) ((deg) * M_PI / 180.0f) \ No newline at end of file diff --git a/okano_3_0/Reduce.cu b/okano_3_0/Reduce.cu new file mode 100644 index 0000000..0886717 --- /dev/null +++ b/okano_3_0/Reduce.cu @@ -0,0 +1,129 @@ +#include "ErrorCode.h" +#include "Reduce.h" +#include +using namespace std; + +// 每个线程格包含的线程数 +#define THREADSPERBLOCK 256 + +// Device 子程序:_reduceAddDev +// 将数组内部的值归约求和,将和存储到该数组的下标 0 处 +static __device__ void +_reduceAddDev( + float *input, // 输入数组,应当归约运算的数组 + int inputL, // 数组长度 + int cacheIndex // 当前的 cache 索引 +); + +// Kernel 核函数:_addKer(归约加法) +// 对输入的数组,进行归约求和 +static __global__ void +_addKer( + float *input, // 输入数组 + int inputL, // 数组长度 + float *output // 输出 +); + +// Device 子程序:_reduceAddDev +static __device__ void _reduceAddDev(float *input, int inputL, int cacheIndex) +{ + int i = inputL / 2; + while (i != 0) { + // 当共享内存索引小于当前的归约计数 i 的值时,可以进行归约,表明当前共享 + // 内存中有 2 * i 个有效数据等待归约 + if (cacheIndex < i) + input[cacheIndex] += input[cacheIndex + i]; + __syncthreads(); + i /= 2; + } +} + +// Kernel 核函数:_addKer(归约加法) +static __global__ void _addKer(float *input, int inputL, float *output) +{ + + // 共享内存 cache 用于存储归约运算的数组 + __shared__ float cache[THREADSPERBLOCK]; + + // 计算线程索引,线程 threadIdx.x 处理一个数据的下标,同时处理的数据为 + // blockDim.x 个数据,每两个数据间相隔 blockDim.x * guidDim.x 个数据 + int tid = blockIdx.x * blockDim.x + threadIdx.x; + + // 将共享内存中的每一个元素置 0。 + cache[threadIdx.x] = 0.0f; + // 共享内存中的索引 + int cacheIndex = threadIdx.x; + + // 设置 cache 中相应位置上的值 + while (tid < inputL) { + cache[cacheIndex] += input[tid]; + tid += blockDim.x * gridDim.x; + } + + // 调用 Device 子程序对 cache 内数据进行归约求和 + _reduceAddDev(cache, blockDim.x, cacheIndex); + + // 对线程块中的线程进行同步 + __syncthreads(); + + // 归约运算求和,将所有共享内存中的数据同时进行规约求和。 + // 初始化归约计数 i 为共享内存长度的一半 + if (cacheIndex != 0) + return; + // 利用原子运算求和, 用 0 号线程规约 + if (cacheIndex == 0) + atomicAdd(output, cache[0]); +} + +// 成员方法:ReduceAdd(进行加法归约运算) +__host__ int Reduce::reduceAdd(float *input, int inputL, float *output) +{ + // 检查输入数组和输出指针是否为空 + if (input == NULL || output == NULL) + return NULL_POINTER; + + // 检查数组长度是否有效 + if (inputL < 1) + return INVALID_DATA; + + // 获取线程块数 + dim3 gridsize, blocksize; + blocksize.x = THREADSPERBLOCK; + gridsize.x = (inputL + THREADSPERBLOCK - 1) / blocksize.x; + + // 设备端数组,存储输入值 + float *dev_input = NULL; + float *dev_output = NULL; + + // 在 GPU 上分配内存,在 GPU 上申请一块连续的内存 temp,通过指针将内存分配给 + // dev_input 和 dev_output,其中 dev_input 指向长为 inputL 的地址的首地址, + // dev_output 指向一个内存,这样避免了反复申请内存的耗时 + float *temp = NULL; + cudaMalloc((void**)&temp, (inputL + 1) * sizeof(float)); + dev_input = temp; + dev_output = temp + inputL; + + // 将输入数组 input 复制到 GPU + cudaMemcpy(dev_input, input, inputL * sizeof(float), + cudaMemcpyHostToDevice); + cudaMemcpy(dev_output, output, sizeof(float), cudaMemcpyHostToDevice); + + // 运行 Kernel 核函数 + _addKer<<>>(dev_input, inputL, dev_output); + + // 将结果拷贝到 CPU 端 + cudaMemcpy(output, dev_output, sizeof(float), cudaMemcpyDeviceToHost); + + // 获取错误信息 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(temp); + return CUDA_ERROR; + } + + // 释放 GPU 上的内存 + cudaFree(temp); + + // 处理完毕,退出 + return NO_ERROR; +} + diff --git a/okano_3_0/Reduce.h b/okano_3_0/Reduce.h new file mode 100644 index 0000000..c96176b --- /dev/null +++ b/okano_3_0/Reduce.h @@ -0,0 +1,67 @@ +// Reduce.h +// 创建人:雷健龙,刘瑶,刘婷 +// +// 归约算法(Reduce) +// 功能说明:根据输入的类来实现对该类的归约运算操作 +// +// 修订历史: +// 2013年05月20日(雷健龙) +// 初始版本 +// 2013年05月22日(雷健龙) +// 改为方法类 +// 2013年05月24日(雷健龙) +// 修改了类结构 +// 2013年05月25日(雷健龙) +// 修改核函数,使用原子操作在核函数中求和 +// 2013年05月29日(雷健龙) +// 修改格式,修改核函数参数名称 +// 2013年05月30日(雷健龙) +// 修改代码错误,修改参数为指针调用 +// 2013年06月02日(雷健龙) +// 修改代码错误 +// 2013年06月04日(雷健龙) +// 调试程序,修改代码错误 +// 2013面06月07日(雷健龙) +// 调试程序,修改代码错误,修改格式规范 +// 2013年06月08日(雷健龙) +// 取消对长度不符合归约要求的数组判断,在 GPU 端用 0 补足为 2 的整数倍 +// 2013年06月09日(雷健龙) +// 修改注释规范 +// 2013年06月10日(雷健龙) +// 修改输入数据向共享内存拷贝为累加拷贝,增加 blocksize,增强可读性,增加错 +// 误判断 +// 2013年06年22日(雷健龙) +// 增加 Device 端函数,形成调用关系 + +#ifndef __REDUCE_H__ +#define __REDUCE_H__ + +#include "ErrorCode.h" + +// 类:Reduce +// 继承自:无 +// 根据输入的类实现归约操作 +class Reduce { + +public: + + // 构造函数:Reduce + // 无参数构造函数 + __host__ __device__ + Reduce() + { + // #do nothing + } + + // 成员方法:reduceAdd(将输入数组规约求和输出) + // 将输入数据归约求和输出,目前只接受对 float 类型的数组的加法运算。 + __host__ int // 返回错误代码 + reduceAdd( + float input[], // 输入 + int inputL, // 输入数组长度 + float *output // 输出 + ); +}; + +#endif + diff --git a/okano_3_0/RegionGrow.cu b/okano_3_0/RegionGrow.cu new file mode 100644 index 0000000..c303f89 --- /dev/null +++ b/okano_3_0/RegionGrow.cu @@ -0,0 +1,724 @@ +// RegionGrow.cu +// 实现图像的区域生长操作,串行算法 regionGrow_serial,并行 regionGrow_parallel + +#include "RegionGrow.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:REGIONGROW_INI_IFI +// 定义了一个无穷大 +#define REGIONGROW_INI_IFI 0x7fffffff + +// Device 子程序: _findRootDev +// 查找根节点标记值算法,根据给定的 label 数组和坐标值 +// 返回该坐标对应的根节点坐标值。该函数是为了便于其他 Kernel 函数调用。 +static __device__ int // 返回值:根节点标记值 +_findRootDev( + int *label, // 输入的标记数组 + int idx // 输入点的标记值 +); + +// Device 子程序: _unionDev +// 合并两个不同像素点以使它们位于同一连通区域中 +static __device__ void // Device 程序无返回值 +_unionDev( + int *label, // 标记值数组 + unsigned char elenum1, // 第一个像素点灰度值 + unsigned char elenum2, // 第二个像素点灰度值 + int elelabel1, // 第一个像素点标记值 + int elelabel2, // 第二个像素点标记值 + unsigned char threshold, // 输入生长规则 + int *flag // 变换标记,当这两个输入像素点被合并到一个 + // 区域后,该标记值将被设为 1。 +); + +// Kernel 函数: _initLabelPerBlockKer (初始化每个块内像素点的标记值) +// 初始化每个线程块内点的标记值。该过程主要分为两个部分,首先,若当前节点的灰度 +// 值为种子点,则标记值设为 -1,否则节点的标记值为其在源图像中的索引值, +// 如对于坐标为 (c, r) 点,其初始标记值为 r * width + c,其中 width 为图像宽; +// 然后,将各点标记值赋值为该点满足阈值关系的八邻域点中的最小标记值。 +// 该过程在一个线程块中进行。 +static __global__ void // Kernel 函数无返回值 +_initLabelPerBlockKer( + ImageCuda inimg, // 输入图像 + int *label, // 输入标记数组 + unsigned char seed, // 输入种子点标记值 + unsigned char threshold // 输入生长规则 +); + +// Kernel 函数: _mergeBordersKer (合并不同块内像素点的标记值) +// 不同线程块的合并过程。该过程主要合并每两个线程块边界的点, +// 在这里我们主要采用每次合并 4 × 4 个线程块的策略。 +static __global__ void // Kernel 函数无返回值 +_mergeBordersKer( + ImageCuda inimg, // 输入图像 + int *label, // 输入标记数组 + int blockw, // 应合并线程块的长度 + int blockh, // 应合并线程块的宽度 + int threadz_z, // 合并水平方向线程块时,z 向线程最大值 + int threadz_y, // 合并竖直方向线程块时,z 向线程最大值 + unsigned char threshold // 输入生长规则 +); + +// Device 子程序:_findRootDev (查找根节点标记值) +static __device__ int _findRootDev(int *label, int idx) +{ + // 在 label 数组中查找 idx 下标对应的最小标记值, + // 并将该值作为返回值。 + int nexidx; + do { + nexidx = idx; + if (nexidx == -1) + return -1; + idx = label[nexidx]; + } while (idx < nexidx); + + // 处理完毕,返回根节点标记值。 + return idx; +} + +// Kernel 函数:_initLabelPerBlockKer (初始化各线程块内像素点的标记值) +static __global__ void _initLabelPerBlockKer(ImageCuda inimg, int *label, + unsigned char seed, + unsigned char threshold) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量 (其中, c 表示 column; r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + int i, j, k; + // 计算输入坐标点在label数组中对应的数组下标 + int idx = r * inimg.imgMeta.width + c; + // 计算输入坐标点对应的图像数据数组下标 + int inidx = r * inimg.pitchBytes + c, newidx; + + // 计算应申请的 shared memory 的步长 + int spitch = blockDim.x + 2; + // 计算当前坐标点在 shared memory 中对应的下标 + int localidx = (threadIdx.y + 1) * spitch + threadIdx.x + 1; + + // oldlabel 用来记录当前点未经过八邻域判断前的标记值, + // newlabel 用来记录经过一轮判断后当前点的最新标记值, + // 当一个点的 oldlabel 与 newlabel 一致时,当前点对应的标记值为最终标记 + // 初始时,每个点的标记值设为其在 shared memory 中的对应下标 + int oldlabel, newlabel = localidx; + // curvalue 用来记录当前点的灰度值,newvalue 用来记录其八邻域点的灰度值 + unsigned char curvalue, newvalue; + curvalue = inimg.imgMeta.imgData[inidx]; + + // 若当前点的灰度值与种子点灰度值 seed 相同,则标记值设为 -1。 + if (curvalue == seed) + newlabel = -1; + + // 共享内存数据区,该部分包含了存储在共享内存中的像素点的标记值。 + // 由于未对 Kernel 的尺寸做出假设,这里使用动态申请的 Shared + // Memory(共享内存)。 + extern __shared__ int slabel[]; + // 共享变量 sflag 数组用来存储是否应停止循环信息。 + // 当 sflag[0] 的值为 0 时,表示块内的迭代已经完成。 + __shared__ int sflag[1]; + + // 由于 shared memory 的大小为 (blockDim.x + 2) * (blockDim.y + 2) + // 在这里将 shared memory 的边界点(即 shared memory 中超出线程块的点) + // 的标记值设为无穷大。 + if (threadIdx.x == 0) + slabel[localidx - 1] = REGIONGROW_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx + 1] = REGIONGROW_INI_IFI; + if (threadIdx.y == 0) { + slabel[localidx - spitch] = REGIONGROW_INI_IFI; + if (threadIdx.x == 0) + slabel[localidx - spitch - 1] = REGIONGROW_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx - spitch + 1] = REGIONGROW_INI_IFI; + } + if (threadIdx.y == blockDim.y - 1) { + slabel[localidx + spitch] = REGIONGROW_INI_IFI; + if (threadIdx.x == 0) + slabel[localidx + spitch - 1] = REGIONGROW_INI_IFI; + if (threadIdx.x == blockDim.x - 1) + slabel[localidx + spitch + 1] = REGIONGROW_INI_IFI; + } + + while (1) { + // 将当前点的标记值设为其在 shared memory 中的数组下标 + slabel[localidx] = newlabel; + // 将 sflag[0] 标记值设为 0 + if ((threadIdx.x | threadIdx.y) == 0) + sflag[0] = 0; + // 初始时,将 newlabel 值赋给 oldlabel + oldlabel = newlabel; + __syncthreads(); + + // 若当前点灰度值满足生长规则,则在八邻域范围内查找也满足生长规则的点, + // 并将这些点的最小标记值赋予记录在 newlabel 中 + if (curvalue > threshold && curvalue != seed) { + for (i = r - 1;i <= r + 1;i++) { + for (j = c - 1;j <= c + 1;j++) { + if (j == c && i == r) + continue; + newidx = i * inimg.pitchBytes + j; + newvalue = inimg.imgMeta.imgData[newidx]; + if ((i >= 0 && i < inimg.imgMeta.height + && j >= 0 && j < inimg.imgMeta.width) + && newvalue > threshold) { + k = localidx + (i - r) * spitch + j - c; + newlabel = min(newlabel, slabel[k]); + } + } + } + } + __syncthreads(); + + // 若当前点的 oldlabel 值大于 newlabel 值, + // 表明当前点的标记值不是最终的标记值 + // 则将 sflag[0] 值设为 1,来继续进行循环判断,并通过原子操作 + // 将 newlabel 与 slabel[oldlabel] 的较小值赋予 slabel[oldlabel] + if (oldlabel > newlabel) { + atomicMin(&slabel[oldlabel], newlabel); + sflag[0] = 1; + } + __syncthreads(); + + // 当线程块内所有像素点对应的标记值不再改变, + // 即 sflag[0] 的值为 0 时,循环结束。 + if (sflag[0] == 0) break; + + // 计算 newlabel 对应的根节点标记值,并将该值赋给 newlabel + newlabel = _findRootDev(slabel, newlabel); + __syncthreads(); + } + + // 将 newlabel 的值转换为其在 label 数组中的数组下标 + if (newlabel != -1) { + j = newlabel / spitch; + i = newlabel % spitch; + i += blockIdx.x * blockDim.x - 1; + j += blockIdx.y * blockDim.y - 1; + newlabel = j * inimg.imgMeta.width + i; + } + label[idx] = newlabel; +} + +// Device 子程序:_unionDev (合并两个不同像素点以使它们位于同一连通区域中) +static __device__ void _unionDev( + int *label, unsigned char elenum1, unsigned char elenum2, + int label1, int label2, unsigned char threshold, int *flag) +{ + int newlabel1, newlabel2; + + // 比较两个输入像素点的灰度值是否均大于等于 threshold + if ((elenum1 > threshold && elenum2 > threshold)) { + // 若两个点满足指定条件,则分别计算这两个点的根节点标记值 + // 计算第一个点的根节点标记值 + newlabel1 = _findRootDev(label, label1); + // 计算第二个点的根节点标记值 + newlabel2 = _findRootDev(label, label2); + // 将较小的标记值赋值给另一点在标记数组中的值 + // 并将 flag[0] 置为 1 + if (newlabel1 > newlabel2) { + // 使用原子操作以保证操作的唯一性与正确性 + atomicMin(&label[newlabel1], newlabel2); + flag[0] = 1; + } else if (newlabel2 > newlabel1) { + atomicMin(&label[newlabel2], newlabel1); + flag[0] = 1; + } + } +} + +static __global__ void _mergeBordersKer( + ImageCuda inimg, int *label, + int blockw, int blockh, + int threadz_x, int threadz_y, unsigned char threshold) +{ + int idx, iterateTimes, i; + int x, y; + int curidx, newidx; + unsigned char curvalue, newvalue; + + // 在这里以每次合并 4 * 4 = 16 个线程块的方式合并线程块 + // 分别计算待合并线程块在 GRID 中的 x 和 y 向分量 + int threadidx_x = blockDim.x * blockIdx.x + threadIdx.x; + int threadidx_y = blockDim.y * blockIdx.y + threadIdx.y; + + // 共享数组变量,只含有一个元素,每当有两个像素点合并时,该数组 + // 变量值置为 1。 + __shared__ int sflag[1]; + + while (1) { + // 设置 sflag[0] 的值为 0。 + if ((threadIdx.x | threadIdx.y | threadIdx.z) == 0) + sflag[0] = 0; + __syncthreads(); + + // 合并上下相邻线程块的水平方向边界点 + // 由于位于 GRID 中最后一行的线程块向下没有待合并的线程块 + // 因而这里不处理最后一行的线程块 + if ((threadIdx.y < blockDim.y - 1)) { + // 计算为了合并一行线程块的迭代次数 + iterateTimes = blockw / threadz_x; + + // 计算待合并像素点在源图像中的像素点坐标 + x = threadidx_x * blockw + threadIdx.z; + y = threadidx_y * blockh + blockh - 1; + + for (i = 0; i < iterateTimes; i++) { + if (threadIdx.z < threadz_x && x < inimg.imgMeta.width && + y < inimg.imgMeta.height) { + idx = y * inimg.imgMeta.width + x; + + // 计算当前像素点灰度值 + curidx = y * inimg.pitchBytes + x; + curvalue = inimg.imgMeta.imgData[curidx]; + // 计算位于当前像素点下方像素点的灰度值, + // 其坐标值为 (x, y + 1)。 + newidx = curidx + inimg.pitchBytes; + newvalue = inimg.imgMeta.imgData[newidx]; + + // 合并这两个像素点 + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width, threshold, sflag); + + // 若当前像素点不为最左侧像素点时,即 x != 0 时,合并 + // 位于当前像素点左下方像素点,其坐标值为 (x - 1, y + 1)。 + if (x - 1 >= 0) { + newidx -= 1; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width - 1, + threshold, sflag); + } + + // 若当前像素点不为最右侧像素点时,x != inimg.imgMeta.width + // 时,合并位于当前像素点右下方像素点,其坐标值为 + // (x + 1, y + 1)。 + if (x + 1 < inimg.imgMeta.width) { + newidx += 2; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width + 1, + threshold, sflag); + } + } + // 计算下次迭代的起始像素点的 x 坐标 + x += threadz_x; + } + } + + // 合并左右相邻线程块的竖直方向边界点 + // 由于位于 GRID 中最后一列的线程块向右没有待合并的线程块 + // 因而这里不处理最后一列的线程块 + if ((threadIdx.x < blockDim.x - 1)) { + // 计算为了合并一列线程块的迭代次数 + iterateTimes = blockh / threadz_y; + + // 计算待合并像素点在源图像中的像素点坐标, + // 由于处理的是每个线程块的最右一列像素点, + // 因此 x 坐标值因在原基础上加上线程块宽度 - 1 + x = threadidx_x * blockw + blockw - 1; + y = threadidx_y * blockh + threadIdx.z; + + for (i = 0;i < iterateTimes;i++) { + if (threadIdx.z < threadz_y && x < inimg.imgMeta.width && + y < inimg.imgMeta.height) { + idx = y * inimg.imgMeta.width + x; + + // 计算当前像素点灰度值 + curidx = y * inimg.pitchBytes + x; + curvalue = inimg.imgMeta.imgData[curidx]; + // 计算位于当前像素点右侧像素点的灰度值, + // 其坐标值为 (x + 1, y)。 + newidx = curidx + 1; + newvalue = inimg.imgMeta.imgData[newidx]; + // 合并这两个像素点 + _unionDev(label, curvalue, newvalue, + idx, idx + 1, threshold, sflag); + + // 若当前像素点不为最上侧像素点时,即 y != 0 时,合并 + // 位于当前像素点右上方像素点,其坐标值为 (x + 1, y - 1)。 + if (y - 1 >= 0) { + newidx -= inimg.pitchBytes; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, idx, + idx - inimg.imgMeta.width + 1, + threshold, sflag); + } + + // 若当前像素点不为最下侧像素点时,y != inimg.imgMeta.height + // 时,合并位于当前像素点右下方像素点,其坐标值为 + // (x + 1, y + 1)。 + if (y + 1 < inimg.imgMeta.height) { + newidx = curidx + inimg.pitchBytes + 1; + newvalue = inimg.imgMeta.imgData[newidx]; + _unionDev(label, curvalue, newvalue, + idx, idx + inimg.imgMeta.width + 1, + threshold, sflag); + } + } + // 计算下次迭代的起始像素点的 y 坐标 + y += threadz_y; + } + } + __syncthreads(); + if (sflag[0] == 0) break; + } +} + +static __global__ void _markFinalLabelKer( + ImageCuda outimg, int *label) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量 (其中, c 表示 column; r 表示 row)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= outimg.imgMeta.width || r >= outimg.imgMeta.height) + return; + + // 计算输入坐标点在label数组中对应的数组下标 + int inidx = r * outimg.imgMeta.width + c; + // 计算输入坐标点在图像数组中对应的数组下标 + int outidx = r * outimg.pitchBytes + c; + label[inidx] = _findRootDev(label, label[inidx]); + outimg.imgMeta.imgData[outidx] = 0; + + if (label[inidx] == -1) + outimg.imgMeta.imgData[outidx] = 255; +} + +// Host 成员方法:regionGrow_parallel +__host__ int RegionGrow::regionGrow_parallel(Image *inimg, Image *outimg) +{ + // 检查输入输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 计算初始化块内内存时,共享内存的大小。 + int smsize = sizeof (int) * (blocksize.x + 2) * (blocksize.y + 2); + // 计算各标记数组的存储数据大小 + int data_size = insubimgCud.imgMeta.width * insubimgCud.imgMeta.height * + sizeof (int); + + // 存储最终标记值的数组,其大小与输入图像大小一致 + int *devLabel; + + cudaError_t cudaerrcode; + + // 为标记数组分配大小。 + cudaerrcode = cudaMalloc((void **)&devLabel, data_size); + if (cudaerrcode != cudaSuccess) { + cudaFree(devLabel); + return cudaerrcode; + } + + // 调用核函数,初始化每个线程块内标记值 + _initLabelPerBlockKer<<>>( + insubimgCud, devLabel, seed, threshold); + + // 合并线程块时每次合并线程块的长、宽和高 + int blockw, blockh, blockz; + // 计算第一次合并时,应合并线程块的长、宽和高 + // 第一次合并时,应合并线程块的长应为初始线程块长,宽为初始线程块宽 + blockw = blocksize.x; + blockh = blocksize.y; + // 由于这里采用的是 3 维线程块,线程块的高设为初始线程块长和宽的较大者。 + blockz = blockw; + if (blockw < blockh) + blockz = blockh; + + // 计算每次合并的线程块个数,在这里我们采用的是每次合并 4 × 4 的线程块, + // 由于采用这种方式合并所需的迭代次数最少。 + int xtiles = 4, ytiles = 4; + // 计算合并线程块前 GRID 的长 + int tilesizex = gridsize.x; + // 计算合并线程块前 GRID 的宽 + int tilesizey = gridsize.y; + + // 定义为进行线程块合并而采用的线程块与网格。 + dim3 blockformerge, gridformerge; + + // 由于每个线程块的大小限制为 1024,而 tilesizex * tilesizey * blockz 的值 + // 为每次用来进行合并操作的三维线程块的最大大小,因此当该值不大于 1024 时, + // 可将所有线程块放在一个三维线程块中合并,这样,我们就可以以该值是否 + // 不大于 1024 来作为是否终止循环的判断条件。 + while (tilesizex * tilesizey * blockz > 1024) { + // 计算每次合并线程块时 GRID 的长,这里采用向上取整的方式 + tilesizex = (tilesizex - 1) / xtiles + 1; + // 计算每次合并线程块时 GRID 的宽,这里采用向上取整的方式 + tilesizey = (tilesizey - 1) / ytiles + 1; + + // 设置为了合并而采用的三维线程块大小,这里采用的是 4 × 4 的方式, + // 因此线程块的长为 4,宽也为 4,高则为 32。 + blockformerge.x = xtiles; blockformerge.y = ytiles; + blockformerge.z = blockz; + // 设置为了合并而采用的二维网格的大小。 + gridformerge.x = tilesizex; gridformerge.y = tilesizey; + gridformerge.z = 1; + // 调用核函数,每次合并4 × 4 个线程块内的标记值 + _mergeBordersKer<<>>( + insubimgCud, devLabel, blockw, blockh, + blocksize.x, blocksize.y, threshold); + // 在每次迭代后,修改应合并线程块的长和宽,因为每次合并 4 * 4 个线程块, + // 因此,经过迭代后,应合并线程块的长和宽应分别乘 4。 + blockw *= xtiles; + blockh *= ytiles; + } + + // 进行最后一轮线程块的合并 + // 计算该轮应采用的三维线程块大小 + blockformerge.x = tilesizex; blockformerge.y = tilesizey; + blockformerge.z = blockz; + // 设置该论应采用的网格大小,长宽高分别为1。 + gridformerge.x = 1; gridformerge.y = 1;gridformerge.z = 1; + // 调用核函数,进行最后一轮线程块合并 + _mergeBordersKer<<>>( + insubimgCud, devLabel, blockw, blockh, + blocksize.x, blocksize.y, threshold); + + // 调用核函数,将最终标记值传到输出图像中 + _markFinalLabelKer<<>>( + outsubimgCud, devLabel); + + // 释放已分配的数组内存,避免内存泄露 + cudaFree(devLabel); + + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + +/*__host__ int RegionGrow::regionGrow_serial(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + static int nDx[] = {-1, 0, 1, -1, 1, -1, 0, 1}; + static int nDy[] = {-1, -1, -1, 0, 0, 1, 1, 1}; + // 定义堆栈,存储坐标 + int * pnGrowQueX ; + int * pnGrowQueY ; + int * pUnRegion; + pUnRegion = new int [inimg->width * inimg->height]; + + // 分配空间 + pnGrowQueX = new int [inimg->width * inimg->height]; + pnGrowQueY = new int [inimg->width * inimg->height]; + + // 定义堆栈的起点和终点 + // 当nStart=nEnd, 表示堆栈中只有一个点 + int nStart = 0; + int nEnd = 0; + int index; + int nCurrX, nCurrY, xx, yy; + int originFlag = 1; + memset(pUnRegion,0,sizeof(int)*inimg->width*inimg->height); + int i, j; + + for (i = 0; i < inimg->width; i++) + for (j = 0; j < inimg->height; j++) { + index = j * inimg->width + i; + outimg->imgData[index] = 0; + } + + for (int i = 0; i < inimg->width; i++) + for (int j = 0; j < inimg->height; j++) + { + index = j * inimg->width + i; + if (inimg->imgData[index] == seed) { + // 把种子点的坐标压入栈 + outimg->imgData[index] = seed; + nStart = nEnd = 0; + pnGrowQueX[nEnd] = i; + pnGrowQueY[nEnd] = j; + originFlag++; + + while (nStart <= nEnd) { + nCurrX = pnGrowQueX[nStart]; + nCurrY = pnGrowQueY[nStart]; + for (int k = 0;k < 8;k++) { + // 4邻域象素的坐标 + xx = nCurrX + nDx[k]; + yy = nCurrY + nDy[k]; + // 判断象素(xx,yy) 是否在图像内部 + // 判断象素(xx,yy) 是否已经处理过 + if (xx >= 0 && xx < inimg->width + && yy >= 0 && yy < inimg->height + && pUnRegion[yy * inimg->width + xx] != originFlag + && inimg->imgData[yy * inimg->width + xx] > threshold) { + // 堆栈的尾部指针后移一位 + nEnd++; + // 象素(xx,yy) 压入栈 + pnGrowQueX[nEnd] = xx; + pnGrowQueY[nEnd] = yy; + // 把象素(xx,yy)设置成逻辑() + // 同时也表明该象素处理过 + pUnRegion[yy * inimg->width + xx] = originFlag; + outimg->imgData[yy * outimg->width + xx] = seed; + } + } + nStart++; + } + } + } + // 释放内存 + delete []pnGrowQueX; + delete []pnGrowQueY; + delete []pUnRegion; + pnGrowQueX = NULL ; + pnGrowQueY = NULL ; + return NO_ERROR; +}*/ + +__host__ int RegionGrow::regionGrow_serial(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + static int nDx[] = {-1, 0, 1, -1, 1, -1, 0, 1}; + static int nDy[] = {-1, -1, -1, 0, 0, 1, 1, 1}; + // 定义堆栈,存储坐标 + int * pnGrowQueX ; + int * pnGrowQueY ; + int * pUnRegion; + pUnRegion = new int [inimg->width * inimg->height]; + + // 分配空间 + pnGrowQueX = new int [inimg->width * inimg->height]; + pnGrowQueY = new int [inimg->width * inimg->height]; + + // 定义堆栈的起点和终点 + // 当nStart=nEnd, 表示堆栈中只有一个点 + int nStart = 0; + int nEnd = 0; + int index; + int nCurrX, nCurrY, xx, yy; + memset(pUnRegion,0,sizeof(int)*inimg->width*inimg->height); + int i, j; + + for (i = 0; i < inimg->width; i++) + for (j = 0; j < inimg->height; j++) { + index = j * inimg->width + i; + outimg->imgData[index] = 0; + } + for (int i = 0; i < inimg->width; i++) + for (int j = 0; j < inimg->height; j++) + { + index = j * inimg->width + i; + if (inimg->imgData[index] == seed && pUnRegion[index] == 0) { + // 把种子点的坐标压入栈 + outimg->imgData[index] = seed; + nStart = nEnd = 0; + pnGrowQueX[nEnd] = i; + pnGrowQueY[nEnd] = j; + + while (nStart <= nEnd) { + nCurrX = pnGrowQueX[nStart]; + nCurrY = pnGrowQueY[nStart]; + for (int k = 0;k < 8;k++) { + // 4邻域象素的坐标 + xx = nCurrX + nDx[k]; + yy = nCurrY + nDy[k]; + // 判断象素(xx,yy) 是否在图像内部 + // 判断象素(xx,yy) 是否已经处理过 + if (xx >= 0 && xx < inimg->width + && yy >= 0 && yy < inimg->height + && pUnRegion[yy * inimg->width + xx] == 0 + && inimg->imgData[yy * inimg->width + xx] > threshold) { + // 堆栈的尾部指针后移一位 + nEnd++; + // 象素(xx,yy) 压入栈 + pnGrowQueX[nEnd] = xx; + pnGrowQueY[nEnd] = yy; + // 把象素(xx,yy)设置成逻辑() + // 同时也表明该象素处理过 + pUnRegion[yy * inimg->width + xx] = 1; + outimg->imgData[yy * outimg->width + xx] = seed; + } + } + nStart++; + } + } + } + // 释放内存 + delete []pnGrowQueX; + delete []pnGrowQueY; + delete []pUnRegion; + pnGrowQueX = NULL ; + pnGrowQueY = NULL ; + return NO_ERROR; +} diff --git a/okano_3_0/RegionGrow.h b/okano_3_0/RegionGrow.h new file mode 100644 index 0000000..9c0d1bf --- /dev/null +++ b/okano_3_0/RegionGrow.h @@ -0,0 +1,112 @@ +// RegionGrow.h +// 创建人:王媛媛 +// +// +// 修订历史: +// 2012年09月13日(王媛媛) +// 初始版本 + +#ifndef __REGIONGROW_H__ +#define __REGIONGROW_H__ + +#include "Image.h" +#include "ErrorCode.h" + +class RegionGrow { + +protected: + + unsigned char seed; + unsigned char threshold; + +public: + // 构造函数:RegionGrow + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + RegionGrow() + { + // 使用默认值为类的各个成员变量赋值。 + this->seed = 255; // 灰度阈值默认为 128。 + this->threshold = 225; + } + + // 构造函数:RegionGrow + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + RegionGrow( + unsigned char seed, + unsigned char threshold + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->seed = 255; // 灰度阈值默认为 128。 + this->threshold = 225; + + // 根据参数列表中的值设定成员变量的初值 + setT(seed); + setD(threshold); + } + + // 成员方法:getT(获取灰度阈值) + // 获取成员变量 threshold 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 threshold 的值 + getT() const + { + // 返回 threshold 成员变量的值。 + return this->seed; + } + + // 成员方法:setT(设置灰度阈值) + // 设置成员变量 threshold 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setT( + unsigned char seed // 设定新的灰度阈值 + ) { + // 将 threshold 成员变量赋成新值 + this->seed = seed; + + return NO_ERROR; + } + + // 成员方法:getD(获取灰度阈值) + // 获取成员变量 threshold 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 threshold 的值 + getD() const + { + // 返回 threshold 成员变量的值。 + return this->threshold; + } + + // 成员方法:setD(设置灰度阈值) + // 设置成员变量 threshold 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setD( + unsigned char threshold // 设定新的灰度阈值 + ) { + // 将 threshold 成员变量赋成新值 + this->threshold = threshold; + + return NO_ERROR; + } + + + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + regionGrow_parallel( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + regionGrow_serial( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/RobustEdgeDetection.cu b/okano_3_0/RobustEdgeDetection.cu new file mode 100644 index 0000000..b9d5816 --- /dev/null +++ b/okano_3_0/RobustEdgeDetection.cu @@ -0,0 +1,1401 @@ +// RobustEdgeDetection.cu +// 实现图像的边缘检测算法。 + +#include "RobustEdgeDetection.h" +#include + +// 宏:SA_DEF_BLOCK_X 和 SA_DEF_BLOCK_Y 和 SA_DEF_BLOCK_Z +// 定义了默认单纯平均法线程快的尺寸。 +#define SA_DEF_BLOCK_X 32 +#define SA_DEF_BLOCK_Y 2 +#define SA_DEF_BLOCK_Z 4 + +// 宏:FV_DEF_BLOCK_X 和 FV_DEF_BLOCK_Y 和 FV_DEF_BLOCK_Z +// 定义了默认三维特征向量法线程快的尺寸。 +#define FV_DEF_BLOCK_X 32 +#define FV_DEF_BLOCK_Y 2 +#define FV_DEF_BLOCK_Z 4 + +// 宏:RED_CODE_CHOICE +// 定义一个选择宏,来决定注释掉原来算法的代码。 +// #define RED_CODE_CHOICE + +// 宏:RED_DIV_PERCENTAGE +// 定义特征向量法中求中值平均时去掉前后一定百分比像素点的辅助宏。 +#define RED_DIV_PERCENTAGE 20 + +// 宏:RED_ARRAY_MAXLEN +// 特征向量法计算临时数组的最大尺寸的辅助宏即对向邻域的最大尺寸, +// 此处默认为 11。 +#define RED_ARRAY_MAXLEN 11 + +// Device 全局常量:_argSupreDev[4][4](最大增强法操作的参数) +// 最大增强法的参数。 +const int static __device__ _argSupreDev[4][4] = { + // [0][ ], [1][ ] + { -1, 0, 1, 0 }, { 0, -1, 0, 1 }, + // [2][ ], [3][ ] + { -1, -1, 1, 1 }, { 1, -1, -1, 1 } +}; + +// Host 函数:_initNeighbor(初始化对向邻域坐标) +// 设置 8 个对向邻域的坐标,为了方便传入 Device 端,此处把 8 个邻域的数据赋值 +// 在一个数组内,通过偏移来寻找每一个线程应该用到的坐标。 +static __host__ int // 函数若正确执行返回 NO_ERROR。 +_initNeighbor( + int neighbor[], // 欲初始化的坐标数组。 + int diffsize, // 对向邻域的大小。 + int *sizedp01, // dp 号为 0 和 1 的对向邻域的尺寸。 + int *sizedp23 // dp 号为 2 和 3 的对向邻域的尺寸。 +); + +// Device 函数:_maxEnhancementDev(最大增强法) +// 进行最大增强操作。差分正规化计算得到的临时图片中每一个位置(ndif)都对应一个 +// THREAD,各个 THREAD 在其对应的 dP 号所指示的方向上检查像素点的像素值,考察在 +// ndif[][] 上蓝箭头所经过的 pixel 范围内 60% (0.6) 以上的位置上的值小于 rate * +// ndif(x,y)。如果是,则 pixel(x,y) 及其 8 邻域对应位置上置 200。每个对向临域对 +// 应两种检测方向,分别为上下,左右,左上右下和右上左下,根据这四种方向以及检测 +// 范围参数 searchscope 进行遍历 (3 x 3 的邻域,箭头下共有 4 个 pixel;5 x 5的邻 +// 域,箭头下共有 6 个 pixel;7 x 7 的邻域,箭头下共有 6 个 pixel;9 x 9 和 11 x +// 11 邻域,箭头下则都是共有8个pixel),最后将最终得到的像素值赋给输出图像。 +static __device__ void // 无返回值。 +_maxEnhancementDev( + ImageCuda inimg, // 输入图像。 + ImageCuda outimg, // 输出图像。 + ImageCuda tempimg, // 需要用到的临时图像。 + int searchscope, // 控制最大增强方法的搜索范围参数。 + int ndif, // 进行最大增强法的正规化后的点的像素值。 + int dp, // 对应搜索方向的 dp 号。 + int c, // 进行最大增强的点的横坐标。 + int r // 进行最大增强的点的纵坐标。 +); + +// Device 函数:_computeMavSgmDev(计算中值平均即 MAV,方差值 SGM) +// 对某一邻域内部的像素值排序,分别计算两个邻域块内点的像素平均值和方差值,辅助 +// 求解正规化差分值。在本算法中,传入一个已经统计好的数组。 +static __device__ int // 函数若正确执行,返回 NO_ERROR。 +_computeMavSgmDev( + unsigned char pixeltmpDev[], // 传入的一个临时数组,记载了邻域内部不同 + // 的像素值。 + int pixelareacnt, // 对向邻域内不同像素点的个数。 + float *mav, // 计算中值平均结果。 + float *sgm // 计算某一个邻域的方差值 SGM +); + +// Device 函数:_computeMavMaxDev(计算各个对向邻域的3个统计量) +// 计算特征向量值。分别计算已排好序的对向邻域的像素值高端处 10% 个图像值的平均值 +// 作为高灰度值 hg ;计算排序结果的低端处 10% 个图像值的平均值作为低灰度值 lg;计 +// 算排序结果中部 20% 个图像值的平均值作为中央均值 ag;各对向域内的 pixel的灰度的 +// 整体平均值,并以此求对向域内的灰度标准偏差 sd。并同时计算出对向邻域内的像素值 +// 最大值 max。 +static __device__ int // 函数若正确执行,返回 NO_ERROR。 +_computeMavMaxDev( + unsigned char pixeltmpDev[], // 传入的一个临时数组,记载了邻域内部不同 + // 的像素值。 + int pixelareacnt, // 对向邻域内不同像素点的个数。 + float *hg, // 高端处 10% 个图像值的平均值,为高灰度值 + float *lg, // 低端处 10% 个图像值的平均值,为低灰度值 + float *ag, // 中部 20% 个图像值的平均值,为中央均值 + int *max // 对向邻域内的像素最大值 max。 +); + +// Kernel 函数:_detectEdgeSAKer(单纯平均法) +// 直接进行对向差分运算。利用四种对向临域的模版,进行差分正规化,得到差分计算的 +// 最大值,然后进行最大增强操作。即由最新的 Maximum Enhancement 方法代替原来的 +// Non-maximum Suppression 方法,对正规化后的差分值结果进行处理。最后再进行 +// Thinning 和 FrekleFilter 处理,得到单像素宽度边缘图像。但是由于非最大抑制操作 +// 的消除自身的特点,检测出的边缘有很大的可能是非连续的。 +static __global__ void // Kernel 函数无返回值 +_detectEdgeSAKer( + int searchscope, // 控制非极大值抑制方法的搜索范围参数 + int diffsize, // 对向临域像素点个数的一半 + int neighbor[], // 传入的模板,可以通过计算偏移量得到 8 个对向邻域的 + // 模板坐标值。 + ImageCuda inimg, // 输入图像 + ImageCuda tempimg, // 在 host 函数里申请的临时图片,然后传入 + // Kernel 函数中 + // 用来存储差分计算后得到的各个像素点像素值 + ImageCuda outimg, // 输出图像 + int sizedp01, // dp 号为 0 和 1 的对向邻域的尺寸。 + int sizedp23 // dp 号为 2 和 3 的对向邻域的尺寸。 +); + +// Kernel 函数:_detectEdgeFVKer(特征向量法) +// 通过公式计算,进行边缘检测。首先,运用并行计算,一个线程计算一个像素点,通过 +// 四种对向临域方向计算(分别为上下,左右,左上右下,右上左下四种方向),计算出 +// 每一个邻域的 MAV,MMD,SGM,以及整幅图片的 EMAV,EMMD,ESGM,再利用河边老师提 +// 供的公式进行计算,可以得到四个 disp 值,从中选择出最大的值,并记下其 dp 号, +// 在 dp 号对应方向上进行非最大抑制。最后,将最终结果赋值到输出图像 outimg 上, +// 再进行 Thinning 和 FreckleFilter 处理,得到最终结果。 +static __global__ void // Kernel 函数无返回值 +_detectEdgeFVKer( + ImageCuda inimg, // 输入图像 + ImageCuda tempimg, // 用于中间存储的临时图像。 + ImageCuda outimg, // 输出图像 + int diffsize, // 对向临域像素点个数 + int searchscope, // 在 dp 方向上进行搜索的范围。 + int neighbor[], // 传入的模板,可以通过计算偏移量得到 8 个对向邻域的 + // 模板坐标值。 + int sizedp01, // dp 号为 0 和 1 的对向邻域的尺寸。 + int sizedp23 // dp 号为 2 和 3 的对向邻域的尺寸。 +); + +// Host 函数:_initNeighbor(初始化对向邻域坐标) +static __host__ int _initNeighbor( + int neighbor[], int diffsize, int *sizedp01, int *sizedp23) +{ + // 判断指针参数是否合法 + if (neighbor == NULL || sizedp01 == NULL || sizedp23 == NULL) + return NULL_POINTER; + + // dp 为 0 和 1 时的邻域大小为 diffsize * diffsize。 + // 计算 dp 为 2 和 3 时重叠区域大小。 + int overlap; + if ((diffsize + 1) & 2 != 0) { + overlap = (diffsize + 1) / 2; + } else { + overlap = (diffsize - 1) / 2; + } + + // 临时变量,用于计算点的坐标。 + int pntlocationtmp = diffsize >> 1; + int pntlocationofftmp = overlap >> 1; + // 分别处理每一个点的索引。 + int idx = 0; + // 为了减少 for 循环的次数,在计算 dp 为 0 时可以根据数学分析一并将 dp 为 2 + // 的点计算,dp 为 3 和 4 时同理。 + // dp 为 0 和 dp 为 1 时的尺寸。 + int offdp01 = diffsize * diffsize * 2; + // dp 为 0 和 dp 为 1 时存放邻域内点所需要的内存大小。 + *sizedp01 = offdp01; + // dp 为 2 和 dp 为 3 时存放邻域内点所需要的内存大小。 + int offdp23 = offdp01 - overlap * (overlap + 1); + *sizedp23 = offdp23; + // 存放 dp 为 2 及其之后的对向邻域的点坐标,相对于数组起始位置的偏移量。 + int offdp12 = offdp01 << 2; + // 为模板赋值。 + for (int i = -diffsize; i < 0; i++) { + for (int j = -pntlocationtmp; j <= pntlocationtmp; j++, idx++) { + // dp 为 0 时记录点的横纵坐标。 + neighbor[idx << 1] = i; + neighbor[(idx << 1) + 1] = j; + // 据分析 dp 为 0 时左块和右块点的坐标存在着某种关系: + // 关于 y 轴对称。 + neighbor[offdp01 + (idx << 1)] = -i; + neighbor[offdp01 + (idx << 1) + 1] = j; + + // 据分析dp 为 0 和 dp 为 1 的点的坐标存在着某种关系: + // x 和 y 值交换。 + // 故可以推出 dp 为 1 时的坐标,如下: + neighbor[(offdp01 << 1) + (idx << 1)] = j; + neighbor[(offdp01 << 1) + (idx << 1) + 1] = i; + + // 据分析 dp 为 1 上块和下块的点的坐标存在着某种关系: + // 关于 x 轴对称。 + neighbor[offdp01 * 3 + (idx << 1)] = j; + neighbor[offdp01 * 3 + (idx << 1) + 1] = -i; + } + } + // 为数组赋值 dp 为 2 和 dp 为 3 时的情形。 + // 计算左边第一个点的横纵坐标。 + int firstpntx = -(diffsize - (overlap + 1) / 2); + int firstpnty = -(diffsize - (overlap + 1) / 2); + // 计算模板值。 + idx = 0; + for (int i = firstpntx; i <= pntlocationofftmp; i++) { + for (int j = firstpnty; j <= pntlocationofftmp; j++) { + // 保证点的有效性,舍去不符合要求的点。 + // 而此时也已经找到了邻域中所有的点,所以可以返回 NO_ERROR + if (i + j >= 0) + continue; + // dp 为 2 时记录点的横纵坐标。 + neighbor[offdp12 + (idx << 1)] = i; + neighbor[offdp12 + (idx << 1) + 1] = j; + + // 根据分析发现,dp 为 2 时左上块和右下块点的坐标存在着某种 + // 关系:关于 y = -x 对称。 + neighbor[offdp12 + offdp23 + (idx << 1)] = -j; + neighbor[offdp12 + offdp23 + (idx << 1) + 1] = -i; + + // 根据分析发现,dp 为 2 左上块和 dp 为 3 右上块的点的坐标存 + // 在着某种关系:x 值互为相反数。 + // 故可以推出 dp 为 3 时的模板,如下: + neighbor[offdp12 + (offdp23 << 1) + (idx << 1)] = -i; + neighbor[offdp12 + (offdp23 << 1) + (idx << 1) + 1] = j; + + // 根据分析发现,dp 为 3 左上块和右下块的点的坐标存在着某种 + // 关系:关于 y = x 对称。 + neighbor[offdp12 + offdp23 * 3 + (idx << 1)] = j; + neighbor[offdp12 + offdp23 * 3 + (idx << 1) + 1] = -i; + + idx++; + } + } + + // 调试代码,输出所有生成的坐标点集 + //for (int i = 0; i < offdp12 + offdp23 * 4; i += 2) { + // cout << "(" << neighbor[i] << ", " << neighbor[i + 1] << "), "; + // if (i % 30 == 0) cout << endl; + //} + //cout << endl; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Device 函数:_maxEnhancementDev(最大增强法) +static __device__ void _maxEnhancementDev( + ImageCuda inimg, ImageCuda outimg, ImageCuda tempimg, + int searchscope, int ndif, int dp, int c, int r) +{ + // dp 号为 0,左右方向检测 + // dp 号为 1,上下方向检测 + // dp 号为 2,左上右下方向检测 + // dp 号为 3,右上左下方向检测 + int curc = c, curr = r; // 临时变量表示点的坐标。 + int arrsub = 0; // 定义一个临时变量,用来定位 Device 数组的下标。 + int icounter = 0; // 辅助计算的计数临时变量。 + float rate = 0.4f; // 外部指定参数,要求 0 < rate <= 1,这里设为0.5。 + for (int i = 1; i <= searchscope; i++) { + // 在左(上、左上、右上)方向上搜索,如果有符合要求的值,则计数变量自加 + arrsub = 0; + curc = c + _argSupreDev[dp][arrsub++] * i; + curr = r + _argSupreDev[dp][arrsub++] * i; + if (tempimg.imgMeta.imgData[curr * inimg.pitchBytes + curc] < + (unsigned char)(rate * ndif) && curc >= 0 && + curc < inimg.imgMeta.width && curr >= 0 + && curr < inimg.imgMeta.height) { + icounter++; + } + + // 在右(下、右下、左下)方向上搜索 ,如果有符合要求的值,则计数变量自加 + curc = c + _argSupreDev[dp][arrsub++] * i; + curr = r + _argSupreDev[dp][arrsub++] * i; + if (tempimg.imgMeta.imgData[curr * inimg.pitchBytes + curc] < + (unsigned char)(rate * ndif) && curc >= 0 && + curc < inimg.imgMeta.width && curr >= 0 + && curr < inimg.imgMeta.height) { + icounter++; + } + } + + // 在所判断方向上经过的 pixel 范围内 60% 以上的位置,如果都符合要求,则 + // pixel(x,y) 及其 8邻域对应位置上置 200 + if((icounter * 1.0f / searchscope) > 0.3) { + outimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 200; + outimg.imgMeta.imgData[r * inimg.pitchBytes + c + 1] = 200; + outimg.imgMeta.imgData[r * inimg.pitchBytes + c - 1] = 200; + outimg.imgMeta.imgData[(r + 1) * inimg.pitchBytes + c] = 200; + outimg.imgMeta.imgData[(r - 1) * inimg.pitchBytes + c] = 200; + outimg.imgMeta.imgData[(r + 1) * inimg.pitchBytes + c + 1] = 200; + outimg.imgMeta.imgData[(r + 1) * inimg.pitchBytes + c + 1] = 200; + outimg.imgMeta.imgData[(r - 1) * inimg.pitchBytes + c - 1] = 200; + outimg.imgMeta.imgData[(r - 1) * inimg.pitchBytes + c - 1] = 200; + } + // 否则置 0。 + else + outimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; +} + +// Kernel 函数:_detectEdgeSAKer(单纯平均法) +static __global__ void _detectEdgeSAKer( + int searchscope, int diffsize, int neighbor[], ImageCuda inimg, + ImageCuda tempimg, ImageCuda outimg, int sizedp01, int sizedp23) +{ + // 计算线程对应的输出点的位置,线程处理的像素点的坐标的 c 和 r 分量,z 表示 + // 对应的邻域方向,其中 0 到 3 分别表示左右、上下、左上右下、右上左下。 + // 采用的是二维的 grid,三维的 block。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int z = threadIdx.z; + + // 计算当前线程在线程块内的索引。 + int tidinblk = z * blockDim.x * blockDim.y + + threadIdx.y * blockDim.x + threadIdx.x; + + // 申请动态共享内存。 + extern __shared__ int shdedgesa[]; + + // 为了只使用一个线程来做最大增强法,此处默认选择 z 为 0 的线程来做最大增强 + // 法,但是这个线程可能在边界处理时被 return 掉,因此需要一个标记值,当 z + // 为 0 的线程 return 之后由其他线程来做最大增强。 + int *shdflag = &shdedgesa[0]; + // 每一个点的 0 号线程在线程块内的索引。 + int index = threadIdx.y * blockDim.x + threadIdx.x; + if (z == 0) + shdflag[index] = 0; + // 在共享内存中申请出一段空间用来存放 4 个对向邻域差分正规化值的结果。 + int *shddiffvalue = &shdflag[blockDim.x * blockDim.y]; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算当前线程要用到的模板地址。 + int templateidx = (z < 2 ? z * sizedp01 : + (sizedp01 << 1) + (z - 2) * sizedp23); + int *curtemplate = &neighbor[templateidx]; + + // 计算重叠区域大小。 + int overlap; + if ((diffsize + 1) & 2 != 0) { + overlap = (diffsize + 1) >> 1; + } else { + overlap = (diffsize - 1) >> 1; + } + // 一个临时变量用来判断 dp 为 3 和 4 时的边界。 + int offcoord = diffsize - (overlap + 1) / 2; + + // 判断边缘点,将其像素值置零。 + // 并不再进行下面的处理。 + // 分别对应 dp 为 0,1,2 和 3 时的边界情况。 + // 分别对应 dp 为 0,1,2 和 3 时的边界点放在数组中。 + unsigned char edgec[4] = {diffsize, diffsize >> 1, offcoord, offcoord}; + unsigned char edger[4] = {diffsize >> 1, diffsize, offcoord, offcoord}; + + // 判断是否是边界点,如果是则置零并退出。 + if (c < edgec[z] || c >= inimg.imgMeta.width - edgec[z] || + r < edger[z] || r >= inimg.imgMeta.height - edger[z]) { + tempimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; + // 为了防止某些点的 4 个线程都出界,故先将输出图像的对应点也置为 0; + outimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; + // 将值写入到共享内存。 + shddiffvalue[tidinblk] = 0; + // 如果 z 为 0 的线程由于边界判断被 return,则将重新设置标记值。 + // 此处的 255 仅仅表示非 0 的概念,没有实际意义。 + if (z == 0) + shdflag[index] = 255; + return; + } + + // 当标记值非 0 时,即 z 为 0 的线程已经不复存在了,此时需要更换新的标记值。 + // 这时可能用的有 z 为 1、2、3 线程,为了减少 bank conflict,同时又因为 z + // 为 2 和 3 必然同时存在或者同时被 return,故判断 z 是否为奇数,可以将 3 + // 路冲突改为 2 路冲突。 + if (shdflag[index] != 0 && z & 1 != 0) + shdflag[index] = z; + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 在计算中用来记录点的像素值的临时变量。 + int curgray = 0; + + // 申请两个中间数组,分别用来存放对向邻域两个块内点的值。 + unsigned char pixeltmpDev1[RED_ARRAY_MAXLEN * RED_ARRAY_MAXLEN] = { 0 }; + unsigned char pixeltmpDev2[RED_ARRAY_MAXLEN * RED_ARRAY_MAXLEN] = { 0 }; + + // 用 for 循环,分别算出每个对向临域的各个点的索引。 + int curc = c, curr = r; + // 点的坐标个数。 + int pntcnt = (z < 2) ? sizedp01: sizedp23; + + // 邻域内部点的数目. + int pixelareacnt = 0; + for (int i = 0; i < pntcnt; i = i + 2, pixelareacnt++) { + // 统计对向邻域的第一模板内的点的坐标。 + curc = c + curtemplate[i]; + curr = r + curtemplate[i + 1]; + // 取出第一个邻域内的点的像素值并统计到对应的数组中。 + curgray = inimg.imgMeta.imgData[curr * inimg.pitchBytes + curc]; + // 利用像素个数进行判断,将两个对向邻域块内的值分别记录到两个数组中。 + if (pixelareacnt < (pntcnt >> 2)) + pixeltmpDev1[pixelareacnt] = curgray; + else + pixeltmpDev2[pixelareacnt - (pntcnt >> 2)] = curgray; + } + + // 块内同步,保证块内的差分值都写入到了共享内存中。 + __syncthreads(); + + // 设置临时变量 sgm1,sgm2,来分别存储两个对向邻域块的方差值,sgm记录 sgm1 + // 和 sgm2 中较大的值,来进行最后的正规化计算。 + float sgm1, sgm2, sgm; + + // 设置临时变量 sgm1,sgm2,来分别存储两个对向邻域块的平均值。 + float mav1, mav2; + + // 调用 device 端的函数求解对向邻域的像素平均值和方差。 + _computeMavSgmDev(pixeltmpDev1, pixelareacnt / 2, &mav1, &sgm1); + + // 调用 device 端的函数求解对向邻域的像素平均值和方差。 + _computeMavSgmDev(pixeltmpDev2, pixelareacnt / 2, &mav2, &sgm2); + + // 比较出 sgm1 和 sgm2 两者中较大的赋值给 sgm。 + sgm = (sgm1 > sgm2) ? sgm1 : sgm2; + + // 设 ndif 为两个对向域之间的正规化差分值,数组 t 和 k 为计算正规化差分值 + // ndif 的参数数组,大小都为 10。 + int ndif = 0, dp = 0; + double t[10] = { 9, 25, 49, 81, 121, 169, 225, 289, 361, 441 } ; + double k[10] = { 0.001, 0.005, 0.025, 0.125, 0.625, 3.125, + 15.624, 78.125, 390.625, 1953.125 } ; + + shddiffvalue[tidinblk] = (mav1 - mav2) * (mav1 - mav2) / + (t[4] + k[5] * sgm); + + // 块内同步,保证块内的正规化差分值都写入到了共享内存中。 + __syncthreads(); + + // 只需要标记的线程来做以下处理。 + if (z != shdflag[index]) + return; + // 用设定的变量 ndif 来存储四种方向正规化差分计算的最大值,dp 记录四种方向 + // 中差分值最大的方向。 + // 局部变量,方便一下计算。 + int offinblk = blockDim.x * blockDim.y; + ndif = shddiffvalue[index]; + for (int i = index + offinblk, j = 1; + i < index + 4 * offinblk; i += offinblk, j++) { + if (ndif < shddiffvalue[i]) { + ndif = shddiffvalue[i]; + dp = j; + } + } + + // 块内同步,保证块内的正规化差分值都写入到了共享内存中。 + __syncthreads(); + + // 根据是否有宏 RED_CODE_CHOICE,来决定调用那部分检测边缘的代码。 +#ifndef RED_CODE_CHOICE + // 判断是否是边界点。如果 ndfi 大于等于 1,则证明是边界点,在输出图像的对应 + // 位置像素点置 200,否则置 0。 + if(ndif >= 1) + outimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 200; + else + outimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; + + _maxEnhancementDev(inimg, tempimg, inimg, searchscope, ndif, dp, c, r); +#else + // 将正规化后的差分值赋给当前像素点,存储到新的临时图片上。 + tempimg.imgMeta.imgData[r * inimg.pitchBytes + c] = ndif; + __syncthreads(); + + // 设置数组 assist 辅助计算最大增强法的搜索范围参数 searchscope,其中第一个 + // 数据 1 为辅助数据,没有实际意义。 + int assist[6] = {1, 2, 3, 3, 4, 4}; + // 计算 searchscope 的值。 + searchscope = assist[(diffsize - 1) / 2]; + + // 进行最大增强操作。 + _maxEnhancementDev(inimg, outimg, tempimg, searchscope, ndif, dp, c, r); +#endif +} + +// 宏:FAIL_RED_SA_FREE +// 该宏用于清理在申请的设备端或者主机端内存空间。 +#define FAIL_RED_SA_FREE do { \ + if (tempimg != NULL) \ + ImageBasicOp::deleteImage(tempimg); \ + if (tempimg1 != NULL) \ + ImageBasicOp::deleteImage(tempimg1); \ + if (neighborDev != NULL) \ + cudaFree(neighborDev); \ + if (neighbor != NULL) \ + delete [] (neighbor); \ + } while (0) + +// Host 成员方法:detectEdgeSA(单纯平均法) +__host__ int RobustEdgeDetection::detectEdgeSA( + Image *inimg, Image *outimg, CoordiSet *guidingset) +{ + // 检查输入图像和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查图像是否为空 + if (inimg->imgData == NULL) + return UNMATCH_IMG; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // guidingset 为边缘检测的指导区域,如果 guidingset 不为空,暂未实现。 + if (guidingset != NULL) { + return UNIMPLEMENT; + } + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图像 + // 的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 临时图像和存放邻域坐标的数组已经在 Device 端的邻域坐标数组。 + Image *tempimg = NULL, * tempimg1 = NULL; + int *neighbor = NULL, *neighborDev = NULL; + // 创建临时图像。 + errcode = ImageBasicOp::newImage(&tempimg); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 将 temp 图像在 Device 内存中建立数据。 + errcode = ImageBasicOp::makeAtCurrentDevice( + tempimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像操作失败,则释放内存报错退出。 + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 创建第二幅临时图像,供调用 Thinning 函数和 FreckleFilter 函数使用 + errcode = ImageBasicOp::newImage(&tempimg1); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 提取临时图像 tempimg 的 ROI 子图像。 + ImageCuda subimgCud; + errcode = ImageBasicOp::roiSubImage(tempimg, &subimgCud); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 记录不同 dp 情况下邻域中点的横纵坐标个数总和。 + int sizedp01 = 0, sizedp23 = 0; + // 申请 Host 端记录邻域坐标的空间。 + neighbor = new int[16 * diffsize * diffsize]; + // 判断空间是否申请成功。 + if (neighbor == NULL) { + // 释放空间。 + FAIL_RED_SA_FREE; + return OUT_OF_MEM; + } + // 调用 _initNeighbor + errcode = _initNeighbor(neighbor, diffsize, &sizedp01, &sizedp23); + // 如果调用失败,则删除临时图像和临时申请的空间。 + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_SA_FREE; + return errcode; + } + + // 将邻域坐标 neighbor 传入 Device 端。 + // 为 neighborDev 在 Device 端申请空间。 + int cudaerrcode = cudaMalloc((void **)&neighborDev, + sizeof (int) * 16 * diffsize * diffsize); + // 若申请不成功则释放空间。 + if (cudaerrcode != cudaSuccess) { + // 释放空间。 + FAIL_RED_SA_FREE; + return CUDA_ERROR; + } + // 将 neighbor 拷贝到 Device 端的 neighborDev 中。 + cudaerrcode = cudaMemcpy(neighborDev, neighbor, + sizeof (int) * 16 * diffsize * diffsize, + cudaMemcpyHostToDevice); + // 如果拷贝不成功,则释放空间。 + if (cudaerrcode != cudaSuccess) { + // 释放空间。 + FAIL_RED_SA_FREE; + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = SA_DEF_BLOCK_X; + blocksize.y = SA_DEF_BLOCK_Y; + blocksize.z = SA_DEF_BLOCK_Z; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / + blocksize.y; + gridsize.z = 1; + + // 计算用到的共享内存空间大小。 + int memsize = SA_DEF_BLOCK_X * SA_DEF_BLOCK_Y * (SA_DEF_BLOCK_Z + 1) * + sizeof (int); + + // 调用核函数 + _detectEdgeSAKer<<>>( + searchScope, diffsize, neighborDev, insubimgCud, + subimgCud, outsubimgCud, 2 * sizedp01, 2 * sizedp23); + + // 判断 CUDA 调用是否出错。 + if (cudaGetLastError() != cudaSuccess) { + FAIL_RED_SA_FREE; + return CUDA_ERROR; + } + + // 调用 Thinning + errcode = this->thinning.thinMatlabLike(outimg, tempimg1); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 调用 FreckleFilter + errcode = this->frecklefilter.freckleFilter(tempimg1, outimg); + if (errcode != NO_ERROR) { + FAIL_RED_SA_FREE; + return errcode; + } + + // 退出前删除临时图像。 + ImageBasicOp::deleteImage(tempimg); + ImageBasicOp::deleteImage(tempimg1); + delete [] neighbor; + cudaFree(neighborDev); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Device 函数:_computeMavSgmDev(计算中值平均即 MAV,方差值 SGM) +static __device__ int _computeMavSgmDev( + unsigned char pixeltmpDev[], int pixelareacnt, float *mav, float *sgm) +{ + // 判断输入参数的有效性。 + if (pixeltmpDev == NULL || mav == NULL || sgm == NULL) + return INVALID_DATA; + + // 若邻域内部的点的值都一样,则不用再进行下面的计算,直接返回。 + if (pixeltmpDev[0] == pixeltmpDev[pixelareacnt - 1]) { + *mav = pixeltmpDev[0]; + *sgm = 0.0f; + return NO_ERROR; + } + + // 定义一个临时变量 sumpixel 来计算数组里下标从 up 到 down 的像素值和。 + double sumpixel = 0.0f; + + // 累加像素值和。 + for (int i = 0; i < pixelareacnt; i++) + sumpixel += pixeltmpDev[i]; + + // 计算 MAV。 + *mav = sumpixel / pixelareacnt; + + // 计算 SGM。 + double sum = 0.0; + // 先累加每一个像素值和平均值的差的平方和。 + for (int i = 0; i < pixelareacnt; i++) + sum += (pixeltmpDev[i] - *mav) * (pixeltmpDev[i] - *mav); + // 计算方差值。 + *sgm = sum / pixelareacnt; + + // 正常执行,返回 NO_ERROR。 + return NO_ERROR; +} + +// Device 函数:_computeMavMaxDev(计算各个对向邻域的3个统计量) +static __device__ int _computeMavMaxDev( + unsigned char pixeltmpDev[], int pixelareacnt, float *hg, float *lg, + float *ag, int *max) +{ + // 判断输入参数的有效性。 + if (pixeltmpDev == NULL || max == NULL || hg == NULL || lg == NULL || + ag == NULL) + return INVALID_DATA; + + // 若邻域内部的点的值都一样,则不用再进行下面的计算,直接返回。 + if (pixeltmpDev[0] == pixeltmpDev[pixelareacnt - 1]) { + *max = pixeltmpDev[0]; + *hg = pixeltmpDev[0]; + *lg = pixeltmpDev[0]; + *ag = pixeltmpDev[0]; + return NO_ERROR; + } + + // 排序后数组成降序,可直接获取最大值。 + *max = pixeltmpDev[pixelareacnt - 1]; + + // 计数器 icounter,jcounter,初始化均为 0。 + int icounter = 0, jcounter = 0; + // 统计变量 sum,初始化为 0。 + float sum = 0.0f; + + // 计算低灰度值 lg + jcounter = pixelareacnt / 10; + for(icounter = 0; icounter < jcounter; icounter++) + sum += pixeltmpDev[icounter]; + + *lg = sum / jcounter; + + // 计算高灰度值 hg + sum = 0.0f; + jcounter = pixelareacnt - (pixelareacnt / 10); + for(icounter = jcounter; icounter < pixelareacnt; icounter++) + sum += pixeltmpDev[icounter]; + + jcounter = pixelareacnt / 10; + *hg = sum / jcounter; + + // 计算中央灰度均值 ag + sum = 0.0f; + jcounter = pixelareacnt / 10; + for(icounter = (4 * jcounter); icounter < (6 * jcounter); icounter++) + sum += pixeltmpDev[icounter]; + + *ag = sum / jcounter; + + // 正常执行,返回 NO_ERROR。 + return NO_ERROR; +} + +// Kernel 函数:_detectEdgeFVKer(特征向量法) +// 通过公式计算,进行边缘检测。 +static __global__ void _detectEdgeFVKer( + ImageCuda inimg, ImageCuda tempimg, ImageCuda outimg, + int diffsize, int searchscope, int neighbor[], int sizedp01, + int sizedp23) +{ + // 计算线程对应的输出点的位置,线程处理的像素点的坐标的 c 和 r 分量,z 表示 + // 对应的邻域方向,其中 0 到 3 分别表示左右、上下、左上右下、右上左下。 + // 采用的是二维的 grid,三维的 block。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int z = threadIdx.z; + + // 计算当前线程在线程块内的索引。 + int tidinblk = z * blockDim.x * blockDim.y + + threadIdx.y * blockDim.x + threadIdx.x; + + // 申请动态共享内存。 + extern __shared__ int shdedgesa[]; + + // 为了只使用一个线程来做非极大值抑制,此处默认选择 z 为 0 的线程来做非极大 + // 值抑制,但是这个线程可能在边界处理时被 return 掉,因此需要一个标记值, + // 当 z 为 0 的线程 return 之后由其他线程来做非极大值抑制。 + int *shdflag = &shdedgesa[0]; + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 每一个点的 0 号线程在线程块内的索引。 + int index = threadIdx.y * blockDim.x + threadIdx.x; + if (z == 0) + shdflag[index] = 0; + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 在共享内存中申请出一段空间用来存放 4 个对向邻域差分正规化值的结果。 + int *shddiffvalue = &shdflag[blockDim.x * blockDim.y]; + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算当前线程要用到的模板地址。 + int templateidx = (z < 2 ? z * sizedp01 : + (sizedp01 << 1) + (z - 2) * sizedp23); + int *curtemplate = &neighbor[templateidx]; + + // 计算重叠区域大小。 + int overlap; + if ((diffsize + 1) & 2 != 0) { + overlap = (diffsize + 1) >> 1; + } else { + overlap = (diffsize - 1) >> 1; + } + // 一个临时变量用来判断 dp 为 3 和 4 时的边界。 + int offcoord = diffsize - (overlap + 1) / 2; + + // 判断边缘点,将其像素值置零。 + // 并不再进行下面的处理。 + // 分别对应 dp 为 0,1,2 和 3 时的边界情况。 + // 分别对应 dp 为 0,1,2 和 3 时的边界点放在数组中。 + unsigned char edgec[4] = {diffsize, diffsize >> 1, offcoord, offcoord}; + unsigned char edger[4] = {diffsize >> 1, diffsize, offcoord, offcoord}; + + // 判断是否是边界点,如果是则置零并退出。 + if (c < edgec[z] || c >= inimg.imgMeta.width - edgec[z] || + r < edger[z] || r >= inimg.imgMeta.height - edger[z]) { + tempimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; + // 为了防止某些点的 4 个线程都出界,故先将输出图像的对应点也置为 0; + outimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; + // 将值写入到共享内存。 + shddiffvalue[tidinblk] = 0; + // 如果 z 为 0 的线程由于边界判断被 return,则将重新设置标记值。 + // 此处的 255 仅仅表示非 0 的概念,没有实际意义。 + if (z == 0) + shdflag[index] = 255; + return; + } + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 当标记值非 0 时,即 z 为 0 的线程已经不复存在了,此时需要更换新的标记值。 + // 这时可能用的有 z 为 1、2、3 线程,为了减少 bank conflict,同时又因为 z + // 为 2 和 3 必然同时存在或者同时被 return,故判断 z 是否为奇数,可以将 3 + // 路冲突改为 2 路冲突。 + if (shdflag[index] != 0 && z & 1 != 0) + shdflag[index] = z; + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 在计算中用来记录点的像素值的临时变量。 + int curgray = 0; + + // 申请两个中间数组,分别用来存放对向邻域两个块内点的值。 + unsigned char pixeltmpDev1[RED_ARRAY_MAXLEN * RED_ARRAY_MAXLEN] = { 0 }; + unsigned char pixeltmpDev2[RED_ARRAY_MAXLEN * RED_ARRAY_MAXLEN] = { 0 }; + + // 用 for 循环,分别算出每个对向临域的各个点的索引 + int curc = c, curr = r; + // 点的坐标个数。 + int pntcnt = (z < 2) ? sizedp01: sizedp23; + int middle = pntcnt >> 2; + // 邻域内部点的数目. + int pixelareacnt = 0; + for (int i = 0; i < pntcnt; i = i + 2, pixelareacnt++) { + // 统计对向邻域的第一模板内的点的坐标。 + curc = c + curtemplate[i]; + curr = r + curtemplate[i + 1]; + // 取出第一个邻域内的点的像素值并统计到对应的数组中。 + curgray = inimg.imgMeta.imgData[curr * inimg.pitchBytes + curc]; + // 利用像素个数进行判断,将两个对向邻域块内的值分别记录到两个数组中。 + if (pixelareacnt < middle) + pixeltmpDev1[pixelareacnt] = curgray; + else{ + pixeltmpDev2[pixelareacnt - middle] = curgray;} + } + + // 块内同步,保证块内的差分值都写入到了共享内存中。 + __syncthreads(); + + // 为两个 pixeltmpDev 排序,经过上面的处理,现在数组内部只有 pixelareacnt + // 个值,此时可以使用插入排序。 + for (int i = 1; i < (pixelareacnt / 2); i++) { + int sorttmp = pixeltmpDev1[i]; + int j = i - 1; + while (j >= 0 && sorttmp < pixeltmpDev1[j]) { + pixeltmpDev1[j + 1] = pixeltmpDev1[j]; + j = j - 1; + } + pixeltmpDev1[j + 1] = sorttmp; + } + + for (int i = 1; i < (pixelareacnt / 2); i++) { + int sorttmp = pixeltmpDev2[i]; + int j = i - 1; + while (j >= 0 && sorttmp < pixeltmpDev2[j]) { + pixeltmpDev2[j + 1] = pixeltmpDev2[j]; + j = j - 1; + } + pixeltmpDev2[j + 1] = sorttmp; + } + + // 计算邻域平均高灰度值 hg,平均低灰度值 lg 和中央均值ag 以 + // 及对向邻域内像素值的最大值 max。 + // 计算邻域整体的平均值 MAV,方差 SGM。 + // 调用 device 函数。 + float mav1 = 0.0f, mav2 = 0.0f; + float sgm1 = 0.0f, sgm2 = 0.0f; + float hg1 = 0.0f, hg2 = 0.0f; + float lg1 = 0.0f, lg2 = 0.0f; + float ag1 = 0.0f, ag2 = 0.0f; + int max1 = 0, max2 = 0; + + // 调用 device 端的函数求解一个对向邻域块的 MAV 和 SGM。 + _computeMavSgmDev(pixeltmpDev1, pixelareacnt / 2, &mav1, &sgm1); + // 调用 device 端的函数求解一个对向邻域块儿的平均高灰度值 hg,平均低灰度值 + // lg 和中央均值ag 以及对向邻域内像素值的最大值 max。 + _computeMavMaxDev(pixeltmpDev1, pixelareacnt / 2, &hg1, &lg1, &ag1, &max1); + + // 调用 device 端的函数求解另一个对向邻域块的 MAV 和 SGM。 + _computeMavSgmDev(pixeltmpDev2, pixelareacnt / 2, &mav2, &sgm2); + // 调用 device 端的函数求解另一个对向邻域块儿的平均高灰度值 hg,平均低灰度值 + // lg 和中央均值ag 以及对向邻域内像素值的最大值 max。 + _computeMavMaxDev(pixeltmpDev2, pixelareacnt / 2, &hg2, &lg2, &ag2, &max2); + + // 求解对向邻域的三个特征向量。 + // 设置临时变量。 + int aa = 1, bb = 1, cc = 1; // 外部参数 + + float abc = aa + bb + cc; + float A = aa / abc; + float B = bb / abc; + float C = cc / abc; + + float dg1 = hg1 - lg1, dg2 = hg2 - lg2; + float s1 = 255 / (1 + max1 - mav1), s2 = 255 / (1 + max2 - mav2); + float sd1 = s1 * sqrt(sgm1), sd2 = s2 * sqrt(sgm2); + + // 特征向量对向域间的相关系数 indexc。 + float indexc; + + // 设置几个辅助变量,辅助计算特征向量对向域间的相关系数 indexc, 无实际含义。 + float m0 = A * dg1 * dg2 + B * ag1 * ag2 + C * sd1 * sd2; + float m1 = A * dg1 * dg1 + B * ag1 * ag1 + C * sd1 * sd1; + float m2 = A * dg2 * dg2 + B * ag2 * ag2 + C * sd2 * sd2; + + // 用块内的偶数号线程来根据公式计算,可以得到 4 个值, + // 并存放在共享内存中。 + // 计算特征向量对向域间的相关系数 indexc。 + indexc = (m1 * m2 == 0) ? 1.0f : m0 / (3 * sqrt(m1 * m2)); + + // 将结果存入共享内存。 + shddiffvalue[tidinblk] = indexc; + + // 块内同步。 + __syncthreads(); + + // 以下处理主要是找出 indexc 的最小值,并在其对应的 dp 方向上做最大增强,如 + // 果只用 1 个线程来处理该步骤可能会出现该线程被提前 return 掉的情形,所以此 + // 步骤采用偶数线程来处理,并将选出的 minc 的最小值处理,赋值赋值给临时图片 + // tempimg。 + // 只需要标记的线程来做以下处理。 + if (z != shdflag[index]) + return; + // 记录最小 minc 值的方向。 + // 设定变量 minc 来存储四种方向差分计算的最小值,dp 记录四种方向中差分值最 + // 大的方向。 + int offinblk = blockDim.x * blockDim.y; // 局部变量,方便一下计算。 + float minc = shddiffvalue[index]; + int dp = 0; + for (int i = index + offinblk, j = 1; + i < index + 4 * offinblk; i += offinblk, j++) { + if (minc > shddiffvalue[i]) { + minc = shddiffvalue[i]; + dp = j; + } + } + + // 块内同步,保证新的标记值已经写入到共享内存中。 + __syncthreads(); + + // 外部指定参数 + float distRate = 0.5f; + float maxMahaDist = 3.0f; + float correTh = 0.5f; + float centerFV[3] = { 1.0f, 1.0f, 1.0f }; + // 此乃给定的对称半正定矩阵 + float variMatrix[9] = + { 1.0f, 2.0f, 1.0f, 2.0f, 3.0f, 2.0f, 1.0f, 2.0f, 1.0f } ; + + // 将 minc 与外部传入参数做比较,进行判断赋值 + int ipixel = 0; + if(minc < correTh) { + // 将最大值赋给当前像素点,存储到临时图片上。 + ipixel = 255 * (1 - minc); + tempimg.imgMeta.imgData[r * inimg.pitchBytes + c] = ipixel; + // edge- likelihood- score的印加方法 + if(centerFV != NULL && variMatrix != NULL){ + // edge- likelihood- score的印加 + // 计算两个对向域特征 (dgi, agi, sdi) (i =1,2) 和 centerFV 之间的马 + // 氏距离。 + float v1[3], v2[3]; + float md1, md2, minmd; + v1[0] = abs(dg1 - centerFV[0]) * A; + v1[1] = abs(ag1 - centerFV[1]) * B; + v1[2] = abs(sd1 - centerFV[2]) * C; + + v2[0] = abs(dg2 - centerFV[0]) * A; + v2[1] = abs(ag2 - centerFV[1]) * B; + v2[2] = abs(sd2 - centerFV[2]) * C; + + md1 = sqrt((v1[0] * (v1[0] * variMatrix[0] + v1[1] * variMatrix[1] + + v1[2] * variMatrix[2])) + + (v1[1] * (v1[0] * variMatrix[0] + v1[1] * variMatrix[1] + + v1[2] * variMatrix[2])) + + (v1[2] * (v1[0] * variMatrix[0] + v1[1] * variMatrix[1] + + v1[2] * variMatrix[2]))); + + md2 = sqrt((v2[0] * (v2[0] * variMatrix[0] + v2[1] * variMatrix[1] + + v2[2] * variMatrix[2])) + + (v2[1] * (v2[0] * variMatrix[0] + v2[1] * variMatrix[1] + + v2[2] * variMatrix[2])) + + (v2[2] * (v2[0] * variMatrix[0] + v2[1] * variMatrix[1] + + v2[2] * variMatrix[2]))); + + minmd = (md1 < md2) ? md1 : md2; + + if(minmd > (distRate * maxMahaDist)) { + ipixel = 255 * (1 - minmd / maxMahaDist); + tempimg.imgMeta.imgData[r * inimg.pitchBytes + c] = ipixel; + } + } + } + + else + // 最终为图片赋值。 + tempimg.imgMeta.imgData[r * inimg.pitchBytes + c] = 0; + + // 设置数组 assist 辅助计算最大增强法的搜索范围参数 searchscope,其中第一个 + // 数据 1 为辅助数据,没有实际意义。 + int assist[6] = {1, 2, 3, 3, 4, 4}; + // 计算 searchscope 的值。 + searchscope = assist[(diffsize - 1) / 2]; + + // 进行最大增强操作。 + _maxEnhancementDev(inimg, outimg, tempimg, searchscope, ipixel, dp, c, r); +} + +// 宏:FAIL_RED_FV_FREE +// 该宏用于清理在申请的设备端或者主机端内存空间。 +#define FAIL_RED_FV_FREE do { \ + if (tempimg != NULL) \ + ImageBasicOp::deleteImage(tempimg); \ + if (tempimg1 != NULL) \ + ImageBasicOp::deleteImage(tempimg1); \ + if (neighborDev != NULL) \ + cudaFree(neighborDev); \ + if (neighbor != NULL) \ + delete [] (neighbor); \ + } while (0) + +// Host 成员方法:detectEdgeFV(特征向量法) +__host__ int RobustEdgeDetection::detectEdgeFV( + Image *inimg, Image *outimg, CoordiSet *guidingset) +{ + // 检查输入图像和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查图像是否为空 + if (inimg->imgData == NULL) + return UNMATCH_IMG; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + // 局部变量,错误码 + int errcode; + cudaError_t cudaerrcode; + + // guidingset 为边缘检测的指导区域,如果 guidingset 不为空暂时未实现。 + if (guidingset != NULL) + return UNIMPLEMENT; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 创建临时图像 + Image *tempimg = NULL, *tempimg1 = NULL; + // 创建记录邻域坐标的指针和对应在 Device 端的指针。 + int *neighbor = NULL, *neighborDev = NULL; + errcode = ImageBasicOp::newImage(&tempimg); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + // 将 tempimg 图像在 Device 内存中建立数据。 + errcode = ImageBasicOp::makeAtCurrentDevice( + tempimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + + // 如果创建图像操作失败,则释放内存报错退出。 + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 创建第二幅临时图像,供调用 Thinning 函数和 FreckleFilter 函数使用 + errcode = ImageBasicOp::newImage(&tempimg1); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 提取临时图像的 ROI 子图像。 + ImageCuda subimgCud; + errcode = ImageBasicOp::roiSubImage(tempimg, &subimgCud); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 如果调用失败,则删除临时图像。 + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 调用 _initNeighbor + // 记录不同 dp 情况下邻域中点的横纵坐标个数总和。 + int sizedp01 = 0, sizedp23 = 0; + neighbor = new int[16 * diffsize * diffsize]; + // 判断空间是否申请成功。 + if (neighbor == NULL) { + // 释放空间。 + FAIL_RED_FV_FREE; + return NULL_POINTER; + } + errcode = _initNeighbor(neighbor, diffsize, &sizedp01, &sizedp23); + // 如果调用失败,则删除临时图像和临时申请的空间。 + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 将邻域坐标 neighbor 传入 Device 端。 + // 为 neighborDev 在 Device 端申请空间。 + cudaerrcode = cudaMalloc((void **)&neighborDev, + sizeof (int) * 16 * diffsize * diffsize); + // 若申请不成功则释放空间。 + if (cudaerrcode != cudaSuccess) { + // 释放空间。 + FAIL_RED_FV_FREE; + return CUDA_ERROR; + } + // 将 neighbor 拷贝到 Device 端的 neighborDev 中。 + cudaerrcode = cudaMemcpy(neighborDev, neighbor, + sizeof (int) * 16 * diffsize * diffsize, + cudaMemcpyHostToDevice); + // 如果拷贝不成功,则释放空间。 + if (cudaerrcode != cudaSuccess) { + // 释放空间。 + FAIL_RED_FV_FREE; + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = FV_DEF_BLOCK_X; + blocksize.y = FV_DEF_BLOCK_Y; + blocksize.z = FV_DEF_BLOCK_Z; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / + blocksize.y; + gridsize.z = 1; + + // 计算共享内存的空间。 + int memsize = FV_DEF_BLOCK_Y * FV_DEF_BLOCK_X * FV_DEF_BLOCK_Z * + (3 * sizeof (float) + sizeof (int)); + // 调用核函数 + _detectEdgeFVKer<<>>( + insubimgCud, subimgCud, outsubimgCud, diffsize, searchScope, + neighborDev, sizedp01 * 2, sizedp23 * 2); + + // 检查 kernel 是否调用出错,若出错则释放空间。 + if (cudaGetLastError() != cudaSuccess) { + // 释放空间。 + FAIL_RED_FV_FREE; + return CUDA_ERROR; + } + + // 调用 Thinning,如果调用出错则释放空间。 + errcode = this->thinning.thinMatlabLike(outimg, tempimg1); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 调用 FreckleFilter,如果调用出错则释放空间。 + errcode = this->frecklefilter.freckleFilter(tempimg1, outimg); + if (errcode != NO_ERROR) { + // 释放空间。 + FAIL_RED_FV_FREE; + return errcode; + } + + // 退出前删除临时图像。 + ImageBasicOp::deleteImage(tempimg); + ImageBasicOp::deleteImage(tempimg1); + delete [] neighbor; + cudaFree(neighborDev); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:CPU 端的 sobel 算子边缘检测。 +__host__ int RobustEdgeDetection::sobelHost(Image *src, Image *out) +{ + int x, y, s, s1, s2; + for (x = 1; x < src->width - 1; x++) + { + for (y = 1; y < src->height - 1; y++) + { + //横向梯度[-1 0 1; -2 0 2; -1 0 1] + s1 = src->imgData[x + 1 + (y - 1) * src->width] + + 2 * src->imgData[x + 1 + y * src->width] + + src->imgData[x + 1 + (y + 1) * src->width]; + s1 = s1 - src->imgData[x - 1 + (y - 1) * src->width] - + 2 * src->imgData[x - 1 + y * src->width] - + src->imgData[x - 1 + (y + 1) * src->width]; + + //纵向梯度[-1 -2 -1; 0 0 0; 1 2 1] + s2 = src->imgData[x + 1 + (y + 1) * src->width] + + 2 * src->imgData[x + (y + 1) * src->width] + + src->imgData[x - 1 + (y + 1) * src->width]; + s2 = s2 - src->imgData[x + 1 + (y - 1) * src->width] - + 2 * src->imgData[x + (y - 1) * src->width] - + src->imgData[x - 1 + (y - 1) * src->width]; + + //给图像赋值 + s = s1 * s1 + s2 * s2; + s = sqrt(s); + out->imgData[y * src->width + x] = s; + } + } + return NO_ERROR; +} + +// 核函数:GPU 端的 sobel 算子边缘检测。 +static __global__ void // Kernel 函数无返回值 +_sobelKer(ImageCuda inimg, ImageCuda outimg) +{ + // 并行策略:一个线程处理一个像素点 + int x = blockIdx.x * blockDim.x + threadIdx.x; + int y = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (x >= inimg.imgMeta.width || y >= inimg.imgMeta.height) + return; + + int s, s1, s2; + + //横向梯度[-1 0 1; -2 0 2; -1 0 1] + s1 = inimg.imgMeta.imgData[x + 1 + (y - 1) * inimg.pitchBytes] + + 2 * inimg.imgMeta.imgData[x + 1 + y * inimg.pitchBytes] + + inimg.imgMeta.imgData[x + 1 + (y + 1) * inimg.pitchBytes]; + s1 = s1 - inimg.imgMeta.imgData[x - 1 + (y - 1) * inimg.pitchBytes] - + 2 * inimg.imgMeta.imgData[x - 1 + y * inimg.pitchBytes] - + inimg.imgMeta.imgData[x - 1 + (y + 1) * inimg.pitchBytes]; + //纵向梯度[-1 -2 -1; 0 0 0; 1 2 1] + s2 = inimg.imgMeta.imgData[x + 1 + (y + 1) * inimg.pitchBytes] + + 2 * inimg.imgMeta.imgData[x + (y + 1) * inimg.pitchBytes] + + inimg.imgMeta.imgData[x - 1 + (y + 1) * inimg.pitchBytes]; + s2 = s2 - inimg.imgMeta.imgData[x + 1 + (y - 1) * inimg.pitchBytes] - + 2 * inimg.imgMeta.imgData[x + (y - 1) * inimg.pitchBytes] - + inimg.imgMeta.imgData[x - 1 + (y - 1) * inimg.pitchBytes]; + + //给图像赋值 + s = s1 * s1 + s2 * s2; + s = sqrtf(s); + outimg.imgMeta.imgData[y * inimg.pitchBytes + x] = s; +} +// Host 成员方法:GPU 端的 sobel 算子边缘检测。 +__host__ int RobustEdgeDetection::sobel(Image *inimg, Image *outimg) +{ + // 检查输入图像和输出图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查图像是否为空 + if (inimg->imgData == NULL) + return UNMATCH_IMG; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + // 局部变量,错误码 + int errcode; + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = SA_DEF_BLOCK_X; + blocksize.y = SA_DEF_BLOCK_Y; + blocksize.z = SA_DEF_BLOCK_Z; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / + blocksize.y; + gridsize.z = 1; + + // 调用核函数 + _sobelKer<<>>(insubimgCud, outsubimgCud); + + return NO_ERROR; +} diff --git a/okano_3_0/RobustEdgeDetection.h b/okano_3_0/RobustEdgeDetection.h new file mode 100644 index 0000000..db43c9a --- /dev/null +++ b/okano_3_0/RobustEdgeDetection.h @@ -0,0 +1,415 @@ +// RobustEdgeDetection.h +// 创建者:张丽洁 +// +// 健壮的边缘检测图像算法(RobustEdgeDetection) +// 功能说明:根据图像中每个像素的两个对象领域间的差分对图像进行 Robust 边缘 +// 检测。共有两种算法实现,第一种为单纯平均法,第二种为特征向量法。 +// 修订历史: + +// 2012年10月13日(张丽洁) +// 初始版本。 +// 2012年10月17日(张丽洁) +// 修改了成员方法声明范围有误而产生的 bug。 +// 2012年10月22日(张丽洁) +// 根据 ver2.01 版项目书以及 v3.41 版编码规范对代码进行适当修改。 +// 2012年10月30日(张丽洁) +// 根据 ver2.03 版项目书及问题反馈文档对代码进行修改,删除 percent 参数。 +// 2012年11月10日(张丽洁) +// 运用模板完成对象临域的定义的实现。 +// 2012年11月20日(张丽洁) +// 改进对象临域的实现方法,用函数将所要参加计算的点的索引一一算出,然后 +// 进行差分计算。 +// 2012年11月27日(张丽洁) +// 将非极大值抑制方法初步实现。 +// 2012年12月04日(张丽洁) +// 添加差分计算时的边界溢出检测,修改了差分计算时没进行绝对值处理的的 bug。 +// 2012年12月09日(张丽洁) +// 修改了对向临域索引值计算的一处误将真实坐标当做相对坐标计算索引逻辑错误。 +// 2012年12月10日(张丽洁) +// 代码规范化。 +// 2012年12月28日(张丽洁) +// 修改了非极大值抑制处的一个 DP 号匹配的 bug,同时在代码中添加了控制是否将 +// 差分结果进行求平均的开关宏 RED_DIV_AVERAGE。 +// 2013年3月13日(刘婷,张丽洁) +// 修改了单纯平均法中对向邻域求索引在计算有重叠的对向邻域出重叠部分大小时的 +// 一处 bug。 +// 2013年03月16日(刘婷) +// 修改了对向邻域坐标计算错误。 +// 修改了非最大值抑制判断条件的错误。 +// getMinInterval 方法返回值类型错误。 +// 2013年03月18日(刘婷) +// 添加了 host 函数 callHistogram, +// 调用 Histogram 算法计算EMAV, EMMD, ESGM, +// 将 EMAV, EMMD, ESGM 的计算区域改为了整幅图片, +// 而非每一个对向邻域对应求出四个 EMAV (EMMD, ESGM)。 +// 添加了 host 函数 findmaxid 找出出现次数最多的像素点值。 +// 2013年03月19日(刘婷) +// 更改了 minInterval 的默认值,根据对河边老师需求文档的分 +// 析,该值在计算 MMD 时代表的是两个峰值之间的最小距离,所 +// 以该值得默认值设为 255 是不合适,更改为 5。 +// 更改了图像的边缘条件判断。 +// 2013年03月20日(刘婷,张丽洁) +// 代码规范化。 +// 修改一系列 set 函数返回值类型错误。 +// 2013年03月21日(张丽洁) +// 由于需求分析文档有所更改,所以将现在已用不上的原来的成员变量 +// complexWeight 删除,增加 diffsize(对向邻域的大小)为成员变量。 +// 2013年03月21日(张丽洁,刘婷) +// 增加了对输入参数的有效性的判断。 +// 2013年03月22日(刘婷) +// 将非极大值部分单纯整理为一个 device 方法,减少了代码的冗余。 +// 对 host 函数里面的 guidingset 设默认参数为NULL。 +// 将计算 8 个邻域内像素点值的累加和计算每个邻域的直方图 +// 数据整理成单独的函数。 +// 2013年03月28日(张丽洁) +// 修改了个别变量的名称,增加 Histogram 型成员变量 histogram,增加对输出图像 +// outimg 是否为空的检查,以及代码规范化。 +// 2013年03月28日(刘婷,张丽洁) +// 增加对坐标点集 guidingset 是否为空的检查,并调用 ImgConvert 函数,将边缘 +// 检测所要操作的指导区域的点集转换为图像。 +// 2013年03月29日(刘婷) +// 添加对除数是否为 0 的判断。 +// 将源代码改成三维,并且将 8 个对向邻域做成一个模板,将模板传入 kernel 中, +// 便于并行处理,使用一个线程处理一个邻域的方式。 +// 2013年03月30日(刘婷) +// 将 MAV 等三个量的求解分解成两个 device 函数。 +// 2013年04月01日(刘婷) +// 通过使用 device 端的数组的方式,去掉了非极大值抑制处理中的 if 语句, +// 减少线程的分支。 +// 代码规范化。 +// 2013年04月02日(刘婷) +// 更改一处计算重叠区域的一处逻辑错误。 +// 在 kernel 中增加了一个临时数组,大大减少了求解三个特征量的时间,在保证正 +// 确性的基础上大大提高了效率。 +// 2013年04月03日(刘婷) +// 代码规范化。 +// 2013年04月06日(刘婷) +// 删除了 newipixel[256] 这个数组,减少了寄存器的使用,新设置了一个 unsigned +// char 型数组,用来记录邻域内部的值,该数组的长度目前通过宏来设置,初始值设 +// 为 11。因为通过调试发现 kernel 中寄存器溢出导致性能瓶颈,因此通过减少寄存 +// 的使用使得性能出现了显著的改善。 +// 2013年04月07日(刘婷) +// 将单纯平均法改为三维,将原来的一个点处理四个对象邻域改为一个点处理一个对 +// 象邻域,增加了共享内存的使用。 +// 增加了对宏来回收空间。 +// 2013年04月08日(刘婷) +// 修改边界处理的一处错误。 +// 2013年04月09日(刘婷) +// 整理代码规范。 +// 2013年04月11日(刘婷) +// 整理代码规范。 +// 2013年05月02日(刘婷) +// 更改求解 mmd 时的一处逻辑错误。 +// 整理代码规范。 +// 2013年05月20日(张丽洁) +// 改写原来的 Device 函数:_computeMavSgmDev 函数,来计算差分正规化需要的中 +// 间变量中值平均 mav 和 方差 sgm。 +// 2013年06月05日(张丽洁) +// 修改了核函数,用差分正规化替代原来的对向差分计算。 +// 2013年06月24日(张丽洁) +// 修改原来的非极大抑制方法为最大增强法。 +// 2013年07月02日(张丽洁) +// 第二期健壮的边缘检测算法(RobustEdgeDetection)初始版。程序能正常运行,但 +// 是图像的结果还不正确。 +// 2013年09月17日(张丽洁) +// 在成员方法中分别增加了 sobel 算子边缘检测方法的串行实现和并行实现,分别为 +// sobelHost 函数和 sobel 函数。 +// 2013年09月25日(张丽洁) +// 修正特征向量法中编译时会产生 warning 的两处 bug。 +// 2013年10月09日(张丽洁) +// 改写原来的 Device 函数:_computeMavSgmDev 函数,简化其接口。 +// 2013年10月17日(张丽洁) +// 第二期健壮的边缘检测算法(RobustEdgeDetection)单纯平均法的初始完成版。将 +// 原来的核函数中的申请存放像素点的数组由一个变为两个,修改了最大增强法中存 +// 在的一处计算 bug。 +// 2013年10月20日(张丽洁) +// 添加开关宏 RED_CODE_CHOICE,来决定注释掉原来算法的代码。 +// 整理代码规范。 +// 2013年11月02日(张丽洁) +// 添加 Device 函数:_computeMavMaxDev,计算各个对向邻域的3个统计量。 +// 2013年11月11日(张丽洁) +// 修改特征向量法的核函数 _detectEdgeFVKer。 +// 2013年11月15日(张丽洁) +// 第二期健壮的边缘检测算法(RobustEdgeDetection)特征向量法的初始完成版。 +// 2013年11月18日(张丽洁) +// 完整注释,整理代码规范。 +// 2013年11月28日(张丽洁) +// 完整注释,整理代码规范。 +// 2014年09月28日(于玉龙) +// 修正了检测模版生成算法中的一处下标计算错误。 + + +#ifndef __ROBUSTEDGEDETECTION_H__ +#define __ROBUSTEDGEDETECTION_H__ + +#include "cmath" +#include "CoordiSet.h" +#include "ErrorCode.h" +#include "FreckleFilter.h" +#include "Image.h" +#include "Thinning.h" +#include "Histogram.h" + +// 类:RobustEdgeDetection(健壮的边缘检测图像算法) +// 继承自:无 +// 应用两种不同的算法,将输入图像的边缘检测出来。第一种为单纯平均法,第二种为特 +// 征向量法。第一种方法是直接通过差分计算,将边缘像素点计算出来,第二种是通过公 +// 求出平均值和标准差等一些中间量,最后得到边缘,然后经过精化处理得到准确的图像 +// 的边缘点。 +class RobustEdgeDetection { + +protected: + + // 成员变量:diffsize(对向邻域的大小) + // 通过此成员变量可从外部设定算法中计算涉及到的对向邻域的大小。 + int diffsize; + + // 成员变量:mmdWeight 和 sgmWeight(计算量的权重) + // mmdWeight 和 sgmWeight 分别表示计算两个对向域的三个特征量间几何距离时, + // 两个个数最多的图像值之差 MMD 的权重和各个领域图像值的标准差 SGM 的权重。 + float mmdWeight, sgmWeight; + + // 成员变量:minInterval(控制参数) + // 在计算两个个数最多的图像值之差 MMD 时,要先对各个辉度的出现次数 + // 进行统计,但这两个峰值之间的最小距离是由参数 minInterval 来控制的。 + int minInterval; + + // 成员变量:searchScope(搜索范围) + // 对差分图像,在 DP 方向上进行搜索的范围。可由外部输入。 + int searchScope; + + // 成员变量:thinning(细化对象) + // 图像进行菲最大抑制之后进行细化处理,得到单像素边缘。 + Thinning thinning; + + // 成员变量:frecklefilter(frecklefilter 对象) + // 利用 FreckleFilter 去除像素孤岛。 + FreckleFilter frecklefilter; + + // 成员变量:histogram(直方图对象) + // 调用 Histogram 函数,求出图像的直方图数据。 + Histogram histogram; + +public: + + // 构造函数:RobustEdgeDetection + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + RobustEdgeDetection() : + thinning(), histogram(), + frecklefilter(1, 100.5f, 0.2f, 2, 0, 1) + { + diffsize = 3; // 权重参数 complexWeight 的默认值为 3 + mmdWeight = 500.0f; // 权重参数 mmdWeight 的默认值范围为 + // 0 ~ 1000,这里默认为 500 + sgmWeight = 500.0f; // 权重参数 sgmWeight 的默认值范围为 + // 0 ~ 1000,这里默认为 500 + minInterval = 5; // 像素值两个峰值之间的最小距离参数, + // 这里默认值设为 5 + searchScope = 10; // dp 方向上进行搜索范围的参数, + // 默认值为 10 + } + + // 构造函数:RobustEdgeDetection + // 有参数版的构造函数,根据需要给定各个参数,这些参数值在程序运行 + // 过程中是可以改变的。 + __host__ __device__ + RobustEdgeDetection ( + int diffsize, // 控制参数(具体解释见成员变量) + float mmdWeight, // 权重参数(具体解释见成员变量) + float sgmWeight, // 权重参数(具体解释见成员变量) + int minInterval, // 控制参数(具体解释见成员变量) + int searchScope // 搜索参数(具体解释见成员变量) + ) : + thinning(), histogram(), + frecklefilter(1, 100.5f, 0.2f, 10, 0, 1) + { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数 + // 的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->diffsize = 3; // 控制参数 diffsize 的默认值为 3 + this->mmdWeight = 500.0f; // 权重参数 mmdWeight 的默认值范围 + // 为 0 ~ 1000,这里默认为 500 + this->sgmWeight = 500.0f; // 权重参数 sgmWeight 的默认值范围 + // 为 0 ~ 1000,这里默认为 500 + this->minInterval = 5; // 像素值两个峰值间的最小距离参数, + // 这里默认值设为 5 + this->searchScope = 10; // dp 方向上进行搜索范围的参数,默 + // 认值为 10 + + // 根据参数列表中的值设定成员变量的初值 + setDiffsize(diffsize); + setMmdWeight(mmdWeight); + setSgmWeight(sgmWeight); + setMinInterval(minInterval); + setSearchScope(searchScope); + } + + // 成员方法:getDiffsize(获取控制参数) + // 获取成员变量 diffsize 的值。 + __host__ __device__ int // 返回值:控制对向邻域的大小。 + getDiffsize() const + { + // 返回 diffsize 成员变量值。 + return this->diffsize; + } + + // 成员方法:setDiffsize(设定控制参数) + // 设置成员变量 Diffsize 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,函数正确执行,返回 + // NO_ERROR。 + setDiffsize( + int diffsize // 新的对向邻域的大小 + ) { + // 判断参数是否合法 + if (diffsize < 3 || (diffsize % 2) == 0) + return INVALID_DATA; + + // 将 diffsize 成员变量赋成新值。 + this->diffsize = diffsize; + return NO_ERROR; + } + + // 成员方法:getMmdWeight(获取权重参数) + // 获取成员变量 mmdWeight 的值。 + __host__ __device__ float // 返回值:图像中的权重参数。 + getMmdWeight() const + { + // 返回 mmdWeight 成员变量值。 + return this->mmdWeight; + } + + // 成员方法:setMmdWeight(获取权重参数) + // 获取成员变量 mmdWeight 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,如果函数正确执行, + // 返回 NO_ERROR。 + setMmdWeight( + float mmdWeight // 新的两个个数最多的图像值之差 MMD 的权重 + ) { + // 判断输入参数的有效性 + if (mmdWeight < 0 || mmdWeight > 1000) + return INVALID_DATA; + + // 将 mmdWeight 成员变量赋成新值。 + this->mmdWeight = mmdWeight; + return NO_ERROR; + } + + // 成员方法:getSgmWeight(获取权重参数) + // 获取成员变量 sgmWeight 的值。 + __host__ __device__ float // 返回值:图像中的权重参数。 + getSgmWeight() const + { + // 返回 sgmWeight 成员变量值。 + return this->sgmWeight; + } + + // 成员方法:setSgmWeight(获取权重参数) + // 获取成员变量 sgmWeight 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,如果函数正确执行, + // 返回 NO_ERROR。 + setSgmWeight( + float sgmWeight // 新的各个领域的图像值的标准差 SGM 的权重 + ) { + // 判断输入参数的有效性 + if (mmdWeight < 0 || mmdWeight > 1000) + return INVALID_DATA; + + // 将 sgmWeight 成员变量赋成新值。 + this->sgmWeight = sgmWeight; + return NO_ERROR; + } + + // 成员方法:getMinInterval(获取控制参数) + // 获取成员变量 minInterval 的值。 + __host__ __device__ int // 返回值:图像中的控制参数。 + getMinInterval() const + { + // 返回 minInterval 成员变量值。 + return this->minInterval; + } + + // 成员方法:setMinInterval(获取控制参数) + // 获取成员变量 minInterval 的值。 + __host__ __device__ int // 返回值:函数是否正确执行 + // 如果函数正确执行返回 NO_ERROR。 + setMinInterval( + float minInterval // 新的控制参数 + ) { + // 判断参数是否合法 + if (minInterval < 0.0f || minInterval > 255.0f) + return INVALID_DATA; + + // 将 minInterval 成员变量赋成新值。 + this->minInterval = minInterval; + return NO_ERROR; + } + + // 成员方法:getSearchScope(获取搜索参数) + // 获取成员变量 searchScope 的值。 + __host__ __device__ int // 返回值:图像中的搜索参数。 + getSearchScope() const + { + // 返回 searchScope 成员变量值。 + return this->searchScope; + } + + // 成员方法:setSearchScope(获取搜索参数) + // 获取成员变量 searchScope 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,如果函数正确执行, + // 返回 NO_ERROR。 + setSearchScope( + int searchScope // 新的搜索参数 + ) { + // 判断参数是否合法 + if (searchScope <= 0) + return INVALID_DATA; + + // 将 searchScope 成员变量赋成新值。 + this->searchScope = searchScope; + return NO_ERROR; + } + + // Host 成员方法:detectEdgeSA(单纯平均法) + // 利用单纯平均法,直接进行对向差分计算。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + detectEdgeSA( + Image *image, // 输入图像 + Image *outimg, // 输出图像 + CoordiSet *guidingset // 边缘检测所要操作的指导区域 + ); + + // Host 成员方法:detectEdgeFV(特征向量法) + // 利用特征向量法,通过计算一些权重值以及对图像进行 + // 非最大抑制处理进行检测。 + __host__ int // 返回值:函数是否正确执行,如果函数能够 + // 正确执行,返回 NO_ERROR。 + detectEdgeFV( + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + CoordiSet *guidingset // 边缘检测所要操作的指导区域 + ); + + // Host 成员方法:sobelHost(串行版 sobel 边缘检测) + // CPU 端的 sobel 算子边缘检测。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + sobelHost( + Image *src, // 输入图像 + Image *out // 输出图像 + ); + + // Host 成员方法:sobel(并行版 sobel 边缘检测) + // GPU 端的 sobel 算子边缘检测。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + sobel( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + +}; + +#endif + diff --git a/okano_3_0/RoiCopy.cu b/okano_3_0/RoiCopy.cu new file mode 100644 index 0000000..8cffbd6 --- /dev/null +++ b/okano_3_0/RoiCopy.cu @@ -0,0 +1,128 @@ +// RoiCopy.cu +// 拷贝图片的 ROI 子图 + +#include "ErrorCode.h" +#include "RoiCopy.h" + +// 成员函数:roiCopyAtHost(拷贝图像的 ROI 子图 Host 版本) +__host__ int RoiCopy::roiCopyAtHost(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + cudaError_t cudaerr; // CUDA 错误码 + + // 判断 inimg 和 outimg 是否为空,若为空,返回错误 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输入图像拷贝到 Host 端 + errcode = ImageBasicOp::copyToHost(inimg); + if (errcode != NO_ERROR) + return errcode; + // 将输出图像拷贝到 Host 端 + errcode = ImageBasicOp::copyToHost(outimg); + // 若输出图像无数据,则根据输入图像子图的大小创建输出图像的数据 + if (errcode != NO_ERROR) { + // 计算输入图像子图的宽和高 + int roiwidth = inimg->roiX2 - inimg->roiX1; + int roiheight = inimg->roiY2 - inimg->roiY1; + // 为输出图像申请在 Host 端申请空间 + errcode = ImageBasicOp::makeAtHost(outimg, roiwidth, roiheight); + if (errcode != NO_ERROR) + return errcode; + } + + ImageCuda outcuda, incuda; + // 提取输入图像的子图 + errcode = ImageBasicOp::roiSubImage(inimg, &incuda); + if (errcode != NO_ERROR) + return errcode; + // 提取输出图像的子图 + errcode = ImageBasicOp::roiSubImage(outimg, &outcuda); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入图像和输出图像的大小,是两个图像的大小相同 + if (incuda.imgMeta.width > outcuda.imgMeta.width) + incuda.imgMeta.width = outcuda.imgMeta.width; + else + outcuda.imgMeta.width = incuda.imgMeta.width; + + if (incuda.imgMeta.height > outcuda.imgMeta.height) + incuda.imgMeta.height = outcuda.imgMeta.height; + else + outcuda.imgMeta.height = incuda.imgMeta.height; + + // 将输入图像的子图拷贝到输出图像的子图中 + cudaerr = cudaMemcpy2D((void *)outcuda.imgMeta.imgData, outcuda.pitchBytes, + (void *)incuda.imgMeta.imgData, incuda.pitchBytes, + incuda.imgMeta.width, incuda.imgMeta.height, + cudaMemcpyHostToHost); + // 若拷贝失败,则返回错误 + if (cudaerr != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// 成员函数:roiCopyAtDevice(拷贝图像的 ROI 子图 Device 版本) +__host__ int RoiCopy::roiCopyAtDevice(Image *inimg, Image *outimg) +{ + int errcode; // 局部变量,错误码 + cudaError_t cudaerr; // CUDA 错误码 + + // 判断 inimg 和 outimg 是否为空,若为空,返回错误 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 将输入图像拷贝到当前 Device 端 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + // 将输出图像拷贝到当前 Device 端 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + // 若输出图像无数据,则根据输入图像子图的大小创建输出图像的数据 + if (errcode != NO_ERROR) { + // 计算输入图像子图的宽和高 + int roiwidth = inimg->roiX2 - inimg->roiX1; + int roiheight = inimg->roiY2 - inimg->roiY1; + // 为输出图像申请在当前 Device 端申请空间 + errcode = ImageBasicOp::makeAtCurrentDevice(outimg, + roiwidth, roiheight); + if (errcode != NO_ERROR) + return errcode; + } + + ImageCuda outcuda, incuda; + // 提取输入图像的子图 + errcode = ImageBasicOp::roiSubImage(inimg, &incuda); + if (errcode != NO_ERROR) + return errcode; + // 提取输出图像的子图 + errcode = ImageBasicOp::roiSubImage(outimg, &outcuda); + if (errcode != NO_ERROR) + return errcode; + + // 调整输入图像和输出图像的大小,是两个图像的大小相同 + if (incuda.imgMeta.width > outcuda.imgMeta.width) + incuda.imgMeta.width = outcuda.imgMeta.width; + else + outcuda.imgMeta.width = incuda.imgMeta.width; + + if (incuda.imgMeta.height > outcuda.imgMeta.height) + incuda.imgMeta.height = outcuda.imgMeta.height; + else + outcuda.imgMeta.height = incuda.imgMeta.height; + + // 将输入图像的子图拷贝到输出图像的子图中 + cudaerr = cudaMemcpy2D((void *)outcuda.imgMeta.imgData, outcuda.pitchBytes, + (void *)incuda.imgMeta.imgData, incuda.pitchBytes, + incuda.imgMeta.width, incuda.imgMeta.height, + cudaMemcpyDeviceToDevice); + // 若拷贝失败,则返回错误 + if (cudaerr != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} diff --git a/okano_3_0/RoiCopy.h b/okano_3_0/RoiCopy.h new file mode 100644 index 0000000..08adbd9 --- /dev/null +++ b/okano_3_0/RoiCopy.h @@ -0,0 +1,53 @@ +// RoiCopy.h +// 创建人:罗劼 +// +// 拷贝子图(copy roi) +// 功能说明:将图像的 ROI 子图拷贝出来 +// +// 修订历史: +// 2012年12月05日(罗劼) +// 初始版本 + +#ifndef __ROICOPY_H__ +#define __ROICOPY_H__ + +#include "Image.h" + +// 类:RoiCopy(拷贝图片的 ROI 子图) +// 继承自:无 +// 拷贝图片的 ROI 子图到一张新的图中 +class RoiCopy { + +public: + + // 构造函数:RoiCopy + // 无参构造函数,此函数不做任何操作 + __host__ __device__ + RoiCopy() + { + // 该函数无任何操作。 + } + + // 成员方法:roiCopyAtHost(拷贝图片的 ROI 子图 Host 版本) + // 根据输入图片设置的 ROI 子图,将其子图拷贝到输出图像中,将拷贝得到的图片 + // 放到 Host 端 + __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR + roiCopyAtHost( + Image *inimg, // 输入图片 + Image *outimg // 输出图片 + ); + + // 成员方法:roiCopyAtDevice(拷贝图片的 ROI 子图 Device 版本) + // 根据输入图片设置的 ROI 子图,将其子图拷贝到输出图像中,将拷贝得到的图片 + // 放到当前 Device 端 + __host__ int // 返回值:函数是否正确执行,若正确执行,返回 + // NO_ERROR + roiCopyAtDevice( + Image *inimg, // 输入图片 + Image *outimg // 输出图片 + ); +}; + +#endif + diff --git a/okano_3_0/RotateTable.cu b/okano_3_0/RotateTable.cu new file mode 100644 index 0000000..da01a0e --- /dev/null +++ b/okano_3_0/RotateTable.cu @@ -0,0 +1,174 @@ +// RotateTable.cu +// 生成指定点集在某一旋转范围内各角度下的旋转后坐标集。 + +#include "RotateTable.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:M_PI +// π值。对于某些操作系统,M_PI可能没有定义,这里补充定义 M_PI。 +#ifndef M_PI +#define M_PI 3.14159265359 +#endif + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。这里之所以定义成 512×1 的 Block 尺寸,是希望能够 +// 减少重复的角度计算,并能够充分的利用全局内存的带宽。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 +#define DEF_BLOCK_Z 1 + +// Kernel 函数:_rotateTableKer(计算旋转表) +// 计算出原始点集对应各个旋转角度下的旋转表。在这个 Kernel 函数中,Grid 的 x 分 +// 量用于计算对应的点;y 分量用于计算对应的角度。 +static __global__ void // Kernel 函数无返回值 +_rotateTableKer( + RotateTable rt // 旋转表类实例,用于提供旋转参数 +); + + +// Kernel 函数:rotateTableKer(计算旋转表) +static __global__ void _rotateTableKer(RotateTable rt) +{ + // 计算当前线程在 Grid 中的位置。其中,c 表示 x 分量,各列处理各自的坐标 + // 点;r 表示 y 分量,各行处理各自的角度。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int aidx = blockIdx.z * blockDim.z + threadIdx.z; + + // 共享内存数据区,该部分包含了当前 Block 的各角度的正弦和余弦值,和总的角 + // 度数量。由于未对 Kernel 的尺寸做出假设,这里使用动态申请的 Shared + // Memory(共享内存)。 + extern __shared__ float shddata[]; + + // 从共享内存中拆出当前线程所需要的那些数据。这些数据包括,当前线程处理的角 + // 度的正弦和余弦值,总的角度的数量(用于计算输出结果在旋转表中的下标)。 + float *sinangle = &(shddata[threadIdx.z * 2]); + float *cosangle = &(shddata[threadIdx.z * 2 + 1]); + int *angcnt = (int *)(&(shddata[blockDim.z * 2])); + + // 计算共享内存中的数据。由于这些数据需要在 Block 内的各线程共享,因此只需 + // 要几个线程计算出结果,其他线程就可以坐享其成。这里选择线程号为 0 的线程 + // 完成这些计算。 + if (threadIdx.x == 0 || threadIdx.y == 0) { + // 根据当前线程在 Grid 中的行号,计算出当前行所要处理的角度。 + float angle = rt.getAngleVal(aidx); + + // 计算角度的正弦余弦值。由于 sin 和 cos 内建函数要求输入弧度,因此需要 + // 先将角度转化为弧度。 + angle = angle * (float)M_PI / 180.0f; + sinangle[0] = sin(angle); + cosangle[0] = cos(angle); + + // 计算总的角度数量。由于对于所有的线程,该值都一样,因此只需要在每个 + // Block 的第一个线程处理这个计算即可,其他线程全部可以坐享其成。 + if (threadIdx.z == 0) + angcnt[0] = rt.getAngleCount(); + } + // 对共享内存的写入操作到此结束,因此需要同步一下 Block 内的个线程,使得其 + // 写入的结果在其他个线程中也是可见的。 + __syncthreads(); + + // 如果当前线程是一个越界的线程,则直接退出。 + if (c >= rt.getSizeX() || r >= rt.getSizeY() || aidx >= rt.getAngleCount()) + return; + + // 获得当前线程处理的坐标点。 + float srcx = c + rt.getOffsetX(); + float srcy = r + rt.getOffsetY(); + + // 进行旋转,并将旋转后的结果平移回原处。 + float dstx = srcx * cosangle[0] - srcy * sinangle[0]; + float dsty = srcx * sinangle[0] + srcy * cosangle[0]; + + // 将得到的旋转结果写入旋转表中。 + int outidx = (aidx * rt.getSizeY() + r) * rt.getSizeX() + c; + rt.getRotateTableX()[outidx] = dstx; + rt.getRotateTableY()[outidx] = dsty; +} + +// Host 成员方法:calcRotateTable(计算旋转表) +__host__ int RotateTable::initRotateTable() +{ + // 如果 CLASS 实例已处于 READY_RTT 状态,则直接返回,不需要进行任何操作。 + if (this->curState == READY_RTT) + return OP_OVERFLOW; + + // 为旋转表申请内存空间。 + cudaError_t cuerrcode; + // 首先计算初各种数据的尺寸,包括角度的数量和旋转表的尺寸。旋转表的尺寸应该 + // 能够包括所有的范围内的坐标点在所有角度范围内旋转后的坐标。 + size_t anglecnt = this->getAngleCount(); + size_t datasize = this->sizex * this->sizey * anglecnt * sizeof (float); + + // 按照计算得到的数据尺寸为旋转表申请空间 + // 受限申请 x 分量旋转表的内存空间。 + cuerrcode = cudaMalloc((void **)&this->rttx, datasize); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + + // 之后申请 y 分量旋转表的内存空间。 + cuerrcode = cudaMalloc((void **)&this->rtty, datasize); + if (cuerrcode != cudaSuccess) { + // 如果 y 分量旋转表的内存空间申请失败需要释放掉之前申请的 x 分量旋转 + // 表,以防止内存泄漏。 + cudaFree(this->rttx); + this->rttx = NULL; + return CUDA_ERROR; + } + + // 计算 Kernel 函数调用的 Grid 尺寸,根据默认的 Block 尺寸,使用最普通的线 + // 程块划分方法。 + dim3 blocksize(DEF_BLOCK_X, DEF_BLOCK_Y, DEF_BLOCK_Z); + dim3 gridsize; + gridsize.x = (this->sizex + blocksize.x - 1) / blocksize.x; + gridsize.y = (this->sizey + blocksize.y - 1) / blocksize.y; + gridsize.z = (anglecnt + blocksize.z - 1) / blocksize.z; + + // 计算所需要的共享内存尺寸,共享内存存储的内容包括前面存储角度的正弦余弦 + // 值(偶数下标用于正弦,奇数下标用于余弦)和总的角度数量。 + int shdsize = 2 * blocksize.z * sizeof (float) + sizeof (int); + + // 调用 Kernel 函数,完成并行旋转表的计算。 + _rotateTableKer<<>>(*this); + if (cudaGetLastError() != cudaSuccess) { + // 注意,为了防止操作失败导致内存泄露,此处需要释放先前申请的内存空间。 + cudaFree(this->rttx); + cudaFree(this->rtty); + return CUDA_ERROR; + } + + // 将 CLASS 状态转为 READY_RTT + this->curState = READY_RTT; + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Host 成员方法:calcRotateTable(销毁旋转表) +__host__ int RotateTable::disposeRotateTable() +{ + // 如果 CLASS 实例已处于 NULL_RTT 状态,则直接返回,不需要进行任何操作。 + if (this->curState == NULL_RTT) + return NO_ERROR; + + // 将当前 CLASS 的状态转回 NULL_RTT,之所以要先转状态,是考虑了可能的多线程 + // 操作,防止释放数据空间后的访问导致意外的错误。当然如果是多线程操作这段代 + // 码需要加锁。 + this->curState = NULL_RTT; + + // 释放旋转表所占用的内容空间。 + cudaFree(this->rttx); + cudaFree(this->rtty); + this->rttx = NULL; + this->rtty = NULL; + + // 处理完毕返回。 + return NO_ERROR; +} + diff --git a/okano_3_0/RotateTable.h b/okano_3_0/RotateTable.h new file mode 100644 index 0000000..db04725 --- /dev/null +++ b/okano_3_0/RotateTable.h @@ -0,0 +1,485 @@ +// RotateTable.h +// 创建人:于玉龙 +// +// 旋转表生成(Rotation Table) +// 功能说明:根据设定的旋转变换范围和变换步长,求出给定坐标点集在所有旋转范围内 +// 各旋转角度的下得到的新坐标。 +// +// 修订历史: +// 2012年08月20日(于玉龙) +// 初始版本。 +// 2012年08月21日(于玉龙) +// 实现了并行的旋转表计算。 +// 2012年09月09日(于玉龙) +// 增加了旋转表的输出形式,增加了分别由两个数组表示 x 和 y 分量的重载函数。 +// 修正了旋转表计算 GPU 代码中一个 Bug。 +// 2012年09月10日(于玉龙) +// 增加了旋转表输入参数的形式,增加了分别由两个数组表示 x 和 y 分量的重载函 +// 数。 +// 2012年10月19日(于玉龙) +// 按照新的需求,修改了代码,将旋转表存入 CLASS 实例内部,并提供在 Device +// 上访问的旋转表内容的函数。 +// 2012年10月20日(于玉龙) +// 增加了 dispose 函数,简化析构逻辑。 +// 2012年10月25日(于玉龙) +// 修改了 Kernel 函数中计算角度时的 Bug。 +// 2012年11月30日(于玉龙) +// 修改了角度和角度索引值与总角度数量之间的计算错误,调整了不合理的角度分 +// 配策略。 +// 2012年12月05日(于玉龙) +// 优化了 detAngle 设定时的限定条件,使其更加合理。 + +#ifndef __ROTATETABLE_H__ +#define __ROTATETABLE_H__ + +#include "ErrorCode.h" + +// 宏:NULL_RTT(旋转表为空) +// 旋转表的状态,当旋转表 CLASS 实例刚刚初始化完毕后,旋转表处于该状态。在该状 +// 态下旋转表不可用,但允许修改旋转表中的各种参数。通过调用方法 initRotateTable +// 跳出该状态。 +#define NULL_RTT 0 + +// 宏:READY_RTT(旋转表就绪) +// 旋转标的状态,当旋转表 CLASS 实例中旋转表处于可用状态时处于此状态。在该状态 +// 中,旋转表可用,但是不允许修改旋转表中的各项参数。 +#define READY_RTT 1 + +// 类:RotateTable +// 继承自:无 +// 根据设定的旋转变换范围和变换步长,求出给定坐标点集在所有旋转范围内各旋转角度 +// 的下得到的新坐标。旋转表得到一个矩阵,该矩阵的各行表示不同的旋转角度,各旋转 +// 角度从小到大依次存放在各行中;矩阵各列对应于输入点集的各点,在不同的旋转角度 +// 下的新坐标。 +class RotateTable { + +protected: + + // 成员变量:minAngle(旋转范围下限) + // 生成旋转表中的最小的角度,单位是“度”(°)。 + float minAngle; + + // 成员变量:maxAngle(旋转范围上限) + // 生成的旋转表中的角度上限,如果 maxAngle - minAngle 可以被 detAngle 整除 + // (通常情况下都会是这样的),则旋转表中的最后一列为 maxAngle 角度对应的旋 + // 转结果。单位是“度”(°)。 + float maxAngle; + + // 成员变量:detAngle(旋转步长) + // 旋转表中各行之间的角度差距。单位是“度”(°)。 + float detAngle; + + // 成员变量:sizex 和 sizey(旋转表的尺寸) + // 规定了旋转表中包含的坐标范围。 + int sizex, sizey; + + // 成员变量:offsetx, offsety(坐标偏移量) + // 这个值根据旋转表的尺寸计算得到,外界无法访问到这个值,它随着设定旋转表的 + // 尺寸而设定,用来方便计算,并保证未来的可扩展性。该变量的实际含义是旋转表 + // 中行或者列的起始处所对应的坐标。 + int offsetx, offsety; + + // 成员变量:rttx 和 rtty(旋转表) + // 旋转表。这是一个逻辑上三维的表,各个维度表示的含义是:列、行、角度。这里 + // 为了更快的访存,采用了 x 与 y 坐标分立的形式。 + float *rttx, *rtty; + + // 成员变量:curState(当前状态) + // 用来表示实例当前的状态。目前可选的值为 NULL_RTT 和 READY_RTT。该值对于外 + // 界是只读的。 + int curState; + +public: + + // 构造函数:RotateTable + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + RotateTable() + { + // 使用默认值初始化各个成员变量。 + this->minAngle = -30.0f; // 旋转范围下线的初始值为 -30。 + this->maxAngle = 30.0f; // 旋转范围上限的初始值为 30。 + this->detAngle = 0.2f; // 旋转步长的初始值为 0.2。 + this->sizex = 11; // 旋转表尺寸 x 分量初始值。 + this->sizey = 11; // 旋转标尺寸 y 分量初始值。 + this->offsetx = -5; // 坐标偏移量 x 分量初始值。 + this->offsety = -5; // 坐标偏移量 y 分量初始值。 + this->rttx = NULL; // 旋转表 x 分量表初始化为 NULL。 + this->rtty = NULL; // 旋转表 y 分量表初始化为 NULL。 + this->curState = NULL_RTT; // 状态的初始值为 NULL_RTT,即旋转表尚未被 + // 计算。 + } + + // 构造函数:RotateTable + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + RotateTable( + float minangle, // 旋转范围角度下限 + float maxangle, // 旋转范围角度上限 + float detangle, // 旋转步长 + int sizex, int sizey // 旋转表的尺寸 + ) { + // 使用默认值初始化各个成员变量。 + this->minAngle = -30.0f; // 旋转范围下线的初始值为 -30。 + this->maxAngle = 30.0f; // 旋转范围上限的初始值为 30。 + this->detAngle = 0.2f; // 旋转步长的初始值为 0.2。 + this->sizex = 11; // 旋转表尺寸 x 分量初始值。 + this->sizey = 11; // 旋转标尺寸 y 分量初始值。 + this->offsetx = -5; // 坐标偏移量 x 分量初始值。 + this->offsety = -5; // 坐标偏移量 y 分量初始值。 + this->rttx = NULL; // 旋转表 x 分量表初始化为 NULL。 + this->rtty = NULL; // 旋转表 y 分量表初始化为 NULL。 + this->curState = NULL_RTT; // 状态的初始值为 NULL_RTT,即旋转表尚未被 + // 计算。 + + // 根据参数列表中的值设定成员变量的初值 + this->setMinAngle(minangle); + this->setMaxAngle(maxangle); + this->setDetAngle(detangle); + this->setSizeX(sizex); + this->setSizeY(sizey); + } + + // 成员方法: getMinAngle(获取旋转范围下限) + // 获取成员变量 minAngle 的值。 + __host__ __device__ float // 返回值:成员变量 minAngle 的值 + getMinAngle() const + { + // 返回成员变量 minAngle 的值。 + return this->minAngle; + } + + // 成员方法:setMinAngle(设置旋转范围下限) + // 设置成员变量 minAngle 的值。如果新的 minAngle 的值大于或等于 maxAngle 的 + // 值,则会适当的调整 maxAngle 的值。采用这中容忍错误的做法,是因为程序如果 + // 在随后也设置了 maxAngle,不会导致程序执行中存在陷阱。如,当前的旋转范围 + // 为 [-10, 10],而新的范围是 [20, 40],如果不使用容忍方式,则程序设置会, + // 会变为 [-10, 40](如果出错则不处理,直接返回)或 [9.8, 40](如果出错,则 + // 调整最小值到最合适后返回)。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMinAngle( + float minangle // 新的旋转范围角度下限。 + ) { + // 如果实例当前不是处于旋转表还未计算的状态,则直接报错,因为这样会导致 + // 系统状态的混乱。 + if (this->curState != NULL_RTT) + return INVALID_DATA; + // 将新的旋转范围下限赋值给 minAngle。 + this->minAngle = minangle; + + // 如果新的旋转范围下限大于当前旋转角度上限,则调整角度上限为新的下限 + if (minangle > this->maxAngle) + this->maxAngle = minangle; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法: getMaxAngle(获取旋转范围上限) + // 获取成员变量 maxAngle 的值。 + __host__ __device__ float // 返回值:成员变量 maxAngle 的值 + getMaxAngle() const + { + // 返回成员变量 maxAngle 的值。 + return this->maxAngle; + } + + // 成员方法:setMaxAngle(设置旋转范围上限) + // 设置成员变量 maxAngle 的值。如果新的 maxAngle 的值小于或等于 minAngle 的 + // 值,则会适当的调整 minAngle 的值。采用这中容忍错误的做法,理由同 + // setMinAngle 相同。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMaxAngle( + float maxangle // 新的旋转范围角度上限。 + ) { + // 如果实例当前不是处于旋转表还未计算的状态,则直接报错,因为这样会导致 + // 系统状态的混乱。 + if (this->curState != NULL_RTT) + return INVALID_DATA; + // 将新的旋转范围上限赋值给 maxAngle。 + this->maxAngle = maxangle; + + // 如果新的旋转范围上限小于当前旋转角度下限,则调整角度下限为新的上限值 + if (maxangle < this->minAngle) + this->minAngle = maxangle; + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getDetAngle(获取旋转步长) + // 获取成员变量 detAngle 的值。 + __host__ __device__ float // 返回值:成员变量 detAngle的值 + getDetAngle() const + { + // 返回成员变量 detAngle 的值。 + return this->detAngle; + } + + // 成员方法:setDetAngle(设置旋转步长) + // 设置成员变量 detAngle 的值。该值要求必须足够大,以保证能够不发生数值误 + // 差。如果指定的值太小,设置函数将会报错,并不会设置新值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setDetAngle( + float detangle // 新的旋转步长。 + ) { + // 如果实例当前不是处于旋转表还未计算的状态,则直接报错,因为这样会导致 + // 系统状态的混乱。 + if (this->curState != NULL_RTT) + return INVALID_DATA; + // 需要保证 detangle 是一个正数,因此如果是负数,则翻转之。 + if (detangle < 0.0f) + detangle = -detangle; + + // 如果 detangle 相对于旋转范围来说太小,则会引起数值错误,此外,也会使 + // 求得出的旋转表太大,因此如果 detangle 太小,则直接报错退出,不进行任 + // 何操作。 + if (detangle < 1.0e-6 * (this->maxAngle - this->minAngle) || + detangle < 1.0e-8) + return INVALID_DATA; + + // 将新的旋转步长赋值给 detAngle。 + this->detAngle = detangle; + return NO_ERROR; + } + + // 成员方法:getSizeX(获取旋转表尺寸的 x 分量) + // 获取成员变量 sizex 的值。 + __host__ __device__ int // 返回值:成员变量 sizex 的值。 + getSizeX() const + { + // 返回成员变量 sizex 的值。 + return this->sizex; + } + + // 成员方法:setSizeX(设置旋转表尺寸的 x 分量) + // 设置成员变量 sizex 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setSizeX( + int sizex // 新的旋转表尺寸的 x 分量。 + ) { + // 如果实例当前不是处于旋转表还未计算的状态,则直接报错,因为这样会导致 + // 系统状态的混乱。 + if (this->curState != NULL_RTT) + return INVALID_DATA; + // 如果新的 sizex 小于 1,则报错,因为尺寸是不可能小于 1 的。 + if (sizex < 1) + return INVALID_DATA; + + // 将新的 sizex 设置到 sizex 成员变量中。 + this->sizex = sizex; + + // 计算新的 offsetx,整理要求原点在整个旋转表的中心,因此对于一个奇数 + // 2 * k + 1,则 offsetx 为 -k,这样整个旋转表的范围为 [-k, k];对于一 + // 个偶数 2k,则 offsetx 为 -(k - 1),这样整个旋转表的范围为 [-(k -1), + // k]。 + this->offsetx = -((sizex - 1) / 2); + + // 处理完毕,返回。 + return NO_ERROR; + } + + // 成员方法:getSizeY(获取旋转表尺寸的 y 分量) + // 获取成员变量 sizey 的值。 + __host__ __device__ int // 返回值:成员变量 sizey 的值。 + getSizeY() const + { + // 返回成员变量 sizey 的值。 + return this->sizey; + } + + // 成员方法:setSizeY(设置旋转表尺寸的 y 分量) + // 设置成员变量 sizey 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setSizeY( + int sizey // 新的旋转表尺寸的 y 分量。 + ) { + // 如果实例当前不是处于旋转表还未计算的状态,则直接报错,因为这样会导致 + // 系统状态的混乱。 + if (this->curState != NULL_RTT) + return INVALID_DATA; + // 如果参数中新的尺寸小于 1,则直接报错。因为不可能存在尺寸小于 1 的情 + // 况。 + if (sizey < 1) + return INVALID_DATA; + + // 设置新的旋转表尺寸,然后返回。 + this->sizey = sizey; + + // 计算新的 offsety,整理要求原点在整个旋转表的中心,因此对于一个奇数 + // 2 * k + 1,则 offsety 为 -k,这样整个旋转表的范围为 [-k, k];对于一 + // 个偶数 2k,则 offsety 为 -(k - 1),这样整个旋转表的范围为 [-(k -1), + // k]。 + this->offsety = -((sizey - 1) / 2); + + return NO_ERROR; + } + + // 成员方法:getOffsetX(获取坐标偏移量 x 分量) + // 获取成员变量 offsetx 的值。 + __host__ __device__ int // 返回值:成员变量 offsetx 的值。 + getOffsetX() const + { + // 直接返回成员变量 offset 的值。 + return this->offsetx; + } + + // 成员方法:getOffsetY(获取坐标偏移量 y 分量) + // 获取成员变量 offsety 的值。 + __host__ __device__ int // 返回值:成员变量 offsety 的值。 + getOffsetY() const + { + // 直接返回成员变量 offsety 的值。 + return this->offsety; + } + + // 成员方法:getCurrentState(获取当前实例所处状态) + // 获取成员变量 curState 的值。 + __host__ __device__ int // 返回值:成员变量 curState 的值。 + getCurrentState() const + { + // 直接返回成员变量 curState 的值。 + return this->curState; + } + + // Device 成员方法:getRotateTableX(获取 x 分量旋转表) + // 获取成员变量 rttx 的值 + __device__ float * // 返回值:成员变量 rttx 的值。 + getRotateTableX() const + { + // 直接返回成员变量 rttx 的值。 + return this->rttx; + } + + // Device 成员方法:getRotateTableY(获取 y 分量旋转表) + // 获取成员变量 rtty 的值 + __device__ float * // 返回值:成员变量 rtty 的值。 + getRotateTableY() const + { + // 直接返回成员变量 rtty 的值。 + return this->rtty; + } + + // 成员方法:getAngleCount(获取旋转表中不同角度的数量) + // 获取旋转表中不同角度的数量。 + __host__ __device__ int // 返回值:旋转角度的数量。 + getAngleCount() const + { + // 利用区间长度和角度步长的商求出旋转表的总行数(即不同角度的数量)。该 + // 表达式最后的 1.9f 用于校正由于不能整除导致的错误,其中,1.0f 用于校 + // 正 maxAngle - minAngle 所带来的个数缺失,而 0.9f 用于弥补由于不能整 + // 除所带来的额外的一张旋转表。 + return (int)((maxAngle - minAngle) / detAngle + 1.9f); + } + + // 成员方法:getAngleIdx(获取角度对应的旋转表行数) + // 给定一个角度,返回该角度对应的旋转表的行数。对于不是正好在表中的角度,则 + // 按照四舍五入的原则返回最近的一个行号。如果给定的角度不在角度范围之内,则 + // 返回错误码所代表的哑值。 + __host__ __device__ int // 返回值:输入角度对应的行数 + getAngleIdx( + float angle // 输入参数角度 + ) const { + // 如果给定的角度不在角度范围内,则报错退出。 + if (angle < minAngle - detAngle / 2.0f || + angle > maxAngle + detAngle / 2.0f) + return INVALID_DATA; + + // 根据当前相对角度于角度范围下限之差,再除以角度步长,得到角度对应的旋 + // 转表行数。该表达式最后的 0.5 用于四舍五入。 + return (int)((angle - minAngle) / detAngle + 0.5f); + } + + // 成员方法:getAngleVal(获取旋转表指定行的角度值) + // 给定一个旋转表的行号,求出该行号对应的角度值。如果给定的行号小于 0 或者 + // 大于总行数,则返回一个角度范围外的角度值。 + __host__ __device__ float // 返回值:旋转表指定行对应的角度值 + getAngleVal( + int idx // 旋转表中的行号 + ) const { + // 如果给定的行数小于 0,则返回一个比 minAngle 更小的角度。 + if (idx < 0) + return minAngle - detAngle; + // 如果给定的行数大于或等于总行数,则返回一个比 maxAngle 更大的角度。 + if (idx >= getAngleCount()) + return maxAngle + detAngle; + + // 根据行数求出角度。注意,这个角度还不能直接返回,因为对于最后一个角度 + // 值很可能是超过 maxAngle 的值,因此,需要把它削减到 maxAngle。 + float resangle = minAngle + detAngle * idx; + if (resangle > maxAngle) + resangle = maxAngle; + + // 返回求出的角度值。 + return resangle; + } + + // Host 成员方法:initRotateTable(计算旋转表) + // 计算给定的坐标点集对应的旋转表。计算后的旋转表被存储在 CLASS 实例中的 + // rttx 和 rtty 中,这两个数组的空间申请在该函数中完成。该函数运行完毕后, + // CLASS 实例进入 READY_RTT 状态,这个状态下可以使用 getRotatePos 方法获取 + // 旋转的信息,但是其他的各种 set 函数都不能够在被使用了。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR。 + initRotateTable(); + + // Host 成员方法:disposeRotateTable(销毁旋转表) + // 释放旋转表所占用的内存空间,使 CLASS 实例从 READY_RTT 状态返回 NULL_RTT + // 状态。正式析构 CLASS 实例前,必须调用这个函数以保证不会产生内存泄漏。这 + // 里没有将这个功能设计到析构函数中,是为了简化内存管理的代码。用户使用的时 + // 候通常要保证 initRotateTable 和 disposeRotateTable 的成对出现,这样就能 + // 够保证避免内存泄漏。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR。 + disposeRotateTable(); + + // Device 成员方法:getRotatePos(访问旋转表获得坐标点旋转后的坐标) + // 当 CLASS 实例处于 READY_RTT 状态的时候可以调用这个函数,该函数根据输入的 + // 坐标,返回经过给定角度旋转后,这一点所处的坐标。如果给定的角度,不在旋转 + // 表所记录的角度上,则会按照给定的插值规则插值得到对应的坐标值,目前采用的 + // 插值方法是临近插值,即选择最近的旋转表中记录的角度返回相应的值。如果给定 + // 的坐标值或者角度超出了旋转表所支持的范围,则输出参数 rx 和 ry 不会被赋 + // 值,整个方法会报错退出。 + __device__ int + getRotatePos( + int x, int y, + float angle, // 旋转角度 + float &rx, float &ry // 旋转后的坐标,这是输出参数,因此必须给顶一 + // 个左值 + ) const { + // 检查旋转表所处的状态,如果不处于可用状态,则直接报错退出。该检查可能 + // 会导致性能下降,因此如果用户可以保证系统的正确性,建议注释掉该语句。 + if (this->curState != READY_RTT) + return NULL_POINTER; + + // 如果输入的参数不在旋转表所支持的范围内,则会报错退出。当然该语句也可 + // 能会导致性能下降,因此如果用户可以保证系统的正确性,建议注释掉该语 + // 句。 + if (x < this->offsetx || x >= this->sizex + this->offsetx || + y < this->offsety || y >= this->sizey + this->offsety || + angle < this->minAngle || angle > this->maxAngle) + return INVALID_DATA; + + // 获取用户给定的角度对应的的旋转表的序号。 + int rttidx = this->getAngleIdx(angle); + + // 计算用户所要计算的点经过给定角度旋转后的坐标值在旋转表中的下标。 + int arridx = (rttidx * this->sizey + + y - this->offsety) * this->sizex + + x - this->offsetx; + + // 从旋转表中读取坐标旋转后的值。 + rx = this->rttx[arridx]; + ry = this->rtty[arridx]; + + // 处理完毕,退出。 + return NO_ERROR; + } +}; + +#endif + diff --git a/okano_3_0/SalientImg.cu b/okano_3_0/SalientImg.cu new file mode 100644 index 0000000..1bda878 --- /dev/null +++ b/okano_3_0/SalientImg.cu @@ -0,0 +1,247 @@ +// SalientImg.cu +// 实现图像显著图算法 + +#include "SalientImg.h" + +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 定义窗口的大小 +#define HSIZE 3 +#define WSIZE HSIZE * 2 + 1 + +// Kernel 函数:_advantSalientKer(计算 advanSalientImg) +static __global__ void // Kernel 函数无返回值。 +_advanSalientKer( + ImageCuda originalimg, // 输入的原始图像。 + ImageCuda advansalientimg, // 输出的 advansalient 图像。 + unsigned char gapth // 阈值 +){ + // 处理当前线程对应的图像点(c,r)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + if (c >= originalimg.imgMeta.width || r >= originalimg.imgMeta.height) + return; + + int inidx = r * originalimg.pitchBytes + c; + + unsigned char sortedwindow[WSIZE * WSIZE]; + + if (c >= HSIZE + 1 && r >= HSIZE + 1 && + c <= originalimg.imgMeta.width - HSIZE && + r <= originalimg.imgMeta.height - HSIZE) + { + int i, j; + int ind = 0; + for (i = -HSIZE; i < HSIZE; i++) { + for (j = -HSIZE; j < HSIZE; j++) { + sortedwindow[ind] = + originalimg.imgMeta.imgData[inidx + + j * originalimg.imgMeta.width + i]; + ind ++; + } + } + + // 插入排序 + unsigned char temp; + for (i = 1; i < WSIZE * WSIZE; i++) + { + temp = sortedwindow[i]; //temp为要插入的元素 + j = i - 1; + + while (j >= 0 && temp < sortedwindow[j]) + { //从a[i-1]开始找比a[i]小的数,同时把数组元素向后移 + sortedwindow[j + 1] = sortedwindow[j]; + j --; + } + sortedwindow[j + 1] = temp; //插入 + } + + + // 计算 high vale mean 和 low vlaue meam。 + unsigned char hvm = 0, lvm = 0; + int num = WSIZE * WSIZE * 0.1; + for(i = 0; i < num; i++) + { + hvm += sortedwindow[WSIZE * WSIZE - 1 - i]; + lvm += sortedwindow[i]; + } + hvm /= num; + lvm /= num; + + unsigned char v0 = originalimg.imgMeta.imgData[inidx]; + unsigned char dv = hvm - lvm; + unsigned char dv2 = (unsigned char)(dv/2.0f + 0.5f); + if ( dv < gapth ) { + advansalientimg.imgMeta.imgData[inidx] = dv2; + } + + else { + if (abs (v0- hvm) < abs (v0- lvm)) { + advansalientimg.imgMeta.imgData[inidx] = (abs (v0 - hvm) < + abs (v0 - dv2) ? hvm : dv2); + } + else { + advansalientimg.imgMeta.imgData[inidx] = (abs (v0 - lvm) < + abs (v0 - dv2) ? lvm : dv2); + } + } + + } else { + advansalientimg.imgMeta.imgData[inidx] = + originalimg.imgMeta.imgData[inidx]; + } + + +} + + +// Host 成员方法:advanSalient(根据输入图像originalImg计算advanSalientImg) +__host__ int SalientImg::advanSalient(Image* originalimg, + Image* advansalientimg) +{ + if (originalimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(originalimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(originalimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(advansalientimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(advansalientimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用核函数。 + _advanSalientKer<<>>(insubimgCud, outsubimgCud, gapth); + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + return NO_ERROR; +} + +// Kernel 函数:_makeSalientImgKer(计算 SalientImg) +static __global__ void // Kernel 函数无返回值。 +_makeSalientImgKer( + ImageCuda advansalientimg, // 输入的预显著图像。 + ImageCuda gausssmoothimg, // 输入的高斯平滑图像。 + ImageCuda salientimg // 输出的显著图想。 +){ + // 处理当前线程对应的图像点(c,r)。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int inidx = r * advansalientimg.pitchBytes + c; + + int v = (advansalientimg.imgMeta.imgData[inidx] - + gausssmoothimg.imgMeta.imgData[inidx]) * 0.55 + 255 / 2.0f; + + if (v < 0) { + salientimg.imgMeta.imgData[inidx] = 0; + } else if (v > 250) { + salientimg.imgMeta.imgData[inidx] = 250; + } else { + salientimg.imgMeta.imgData[inidx] = v; + } + +} + +// Host 成员函数:makeSalientImg(计算 SalientImg) +__host__ int SalientImg::makeSalientImg( + Image* advansalientimg, // 输入的预显著图像。 + Image* gausssmoothimg, // 输入的高斯平滑图像。 + Image* salientimg // 输出的显著图想。 +) { + if (advansalientimg == NULL) + return NULL_POINTER; + + if (gausssmoothimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像1拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(advansalientimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像1的 ROI 子图像。 + ImageCuda insubimgCud1; + errcode = ImageBasicOp::roiSubImage(advansalientimg, &insubimgCud1); + if (errcode != NO_ERROR) + return errcode; + + // 将输入图像2拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(gausssmoothimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像2的 ROI 子图像。 + ImageCuda insubimgCud2; + errcode = ImageBasicOp::roiSubImage(gausssmoothimg, &insubimgCud2); + if (errcode != NO_ERROR) + return errcode; + + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(salientimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(salientimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud1.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (insubimgCud1.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 调用核函数。 + _makeSalientImgKer<<>>(insubimgCud1, insubimgCud2, + outsubimgCud); + if (cudaGetLastError() != cudaSuccess) { + return CUDA_ERROR; + } + + return NO_ERROR; +} + diff --git a/okano_3_0/SalientImg.h b/okano_3_0/SalientImg.h new file mode 100644 index 0000000..2d14193 --- /dev/null +++ b/okano_3_0/SalientImg.h @@ -0,0 +1,86 @@ +// SalientImg.h +// 创建人:冯振 +// +// 计算显著图(SalientImg) +// 功能说明:实现计算图像的显著图。输出的结果保存在Image型的指针。 +// +// 修订历史: +// 2013年10月15日(冯振) +// 初始版本。 +// 2013年12月5日(冯振) +// 规范了编码格式 + +#ifndef __SALIENTIMG_H__ +#define __SALIENTIMG_H__ + +#include "Image.h" + + +// 类:SalientImg(显著图) +// 继承自:无 +// 功能说明:实现计算图像的显著图。 +class SalientImg { + +protected: + // int wsizea; // 计算advanSalientImg时使用的窗口邻域大小。 + // int wsizeg; // 计算gaussSmoothImg时使用的窗口邻域大小。 + unsigned char gapth; // 计算advanSalientImg时使用的阈值。 + +public: + + // 构造函数:SalientImg + // 无参数版本的构造函数,设置默认参数。 + __host__ __device__ + SalientImg() + { + this->gapth = 55; + } + + // 构造函数:SalientImg + // 有参数版本。 + __host__ __device__ + SalientImg(unsigned char gapth) + { + this->gapth = gapth; + } + + // 成员方法:getGapTh(获取计算advanSalientImg时使用的阈值) + // 获取成员变量 gapth 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 gapth 的值 + getGapTh() const + { + // 返回 gapth 成员变量的值。 + return this->gapth; + } + + // 成员方法:setGapTh(设置计算advanSalientImg时使用的阈值) + // 设置成员变量 wsizeg 的值。 + __host__ __device__ unsigned char // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setGapTh( + unsigned char gapth // 设定计算advanSalientImg时使用的阈值。 + ) { + // 将 gapth 成员变量赋成新值 + this->gapth = gapth; + + return NO_ERROR; + } + + // Host 成员方法:advanSalient(根据输入图像originalImg计算advanSalientImg) + __host__ int // 返回值:函数正确执行,则返回 NO_ERROR。 + advanSalient( + Image* originalimg, // 输入的原始图像。 + Image* advansalientimg // 输出的advanSalientImg图像。 + ); + + // Host 成员函数:makeSalientImg(计算 SalientImg) + __host__ int // 返回值:函数正确执行,则返回 NO_ERROR。 + makeSalientImg( + Image* advansalientimg, // 输入的预显著图像。 + Image* gausssmoothimg, // 输入的高斯平滑图像。 + Image* salientimg // 输出的显著图想。 + ); +}; + +#endif + diff --git a/okano_3_0/SalientRegionDetect.cu b/okano_3_0/SalientRegionDetect.cu new file mode 100644 index 0000000..a89a0d2 --- /dev/null +++ b/okano_3_0/SalientRegionDetect.cu @@ -0,0 +1,962 @@ +// SalientRegionDetect.cu +// 实现图像显著性区域检测 + +#include "SalientRegionDetect.h" +#include "Template.h" +#include "ConnectRegion.h" +#include "Histogram.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:MAX_TEMPLATE +// 定义领域模板的最大值。 +#ifndef MAX_TEMPLATE +#define MAX_TEMPLATE 32 +#endif + +// 宏:GRAY_LEVEL +// 定义灰度级范围。 +#ifndef GRAY_LEVEL +#define GRAY_LEVEL 256 +#endif + + +// Device 全局常量:_gaussCoeffDev(高斯模板权重) +static __device__ float _gaussCoeffDev[6][170] = { + // 3 * 3 模板 + { 1.0f / 16.0f, 2.0f / 16.0f, 1.0f / 16.0f, + 2.0f / 16.0f, 4.0f / 16.0f, 2.0f / 16.0f, + 1.0f / 16.0f, 2.0f / 16.0f, 1.0f / 16.0f }, + // 5 * 5 模板 + { 1.0f / 352.0f, 5.0f / 352.0f, 8.0f / 352.0f, 5.0f / 352.0f, + 1.0f / 352.0f, 5.0f / 352.0f, 21.0f / 352.0f, 34.0f / 352.0f, + 21.0f / 352.0f, 5.0f / 352.0f, 8.0f / 352.0f, 34.0f / 352.0f, + 56.0f / 352.0f, 34.0f / 352.0f, 8.0f / 352.0f, 5.0f / 352.0f, + 21.0f / 352.0f, 34.0f / 352.0f, 21.0f / 352.0f, 5.0f / 352.0f, + 1.0f / 352.0f, 5.0f / 352.0f, 8.0f / 352.0f, 5.0f / 352.0f, + 1.0f / 352.0f }, + // 7 * 7 模板 + { 1.0f / 50888.0f, 12.0f / 50888.0f, 55.0f / 50888.0f, + 90.0f / 50888.0f, 55.0f / 50888.0f, 12.0f / 50888.0f, + 1.0f / 50888.0f, + 12.0f / 50888.0f, 148.0f / 50888.0f, 665.0f / 50888.0f, + 1097.0f / 50888.0f, 665.0f / 50888.0f, 148.0f / 50888.0f, + 12.0f / 50888.0f, + 55.0f / 50888.0f, 665.0f / 50888.0f, 2981.0f / 50888.0f, + 4915.0f / 50888.0f, 2981.0f / 50888.0f, 665.0f / 50888.0f, + 55.0f / 50888.0f, + 90.0f / 50888.0f, 1097.0f / 50888.0f, 4915.0f / 50888.0f, + 8104.0f / 50888.0f, 4915.0f / 50888.0f, 1097.0f / 50888.0f, + 90.0f / 50888.0f, + 55.0f / 50888.0f, 665.0f / 50888.0f, 2981.0f / 50888.0f, + 4915.0f / 50888.0f, 2981.0f / 50888.0f, 665.0f / 50888.0f, + 55.0f / 50888.0f, + 12.0f / 50888.0f, 148.0f / 50888.0f, 665.0f / 50888.0f, + 1097.0f / 50888.0f, 665.0f / 50888.0f, 148.0f / 50888.0f, + 12.0f / 50888.0f, + 1.0f / 50888.0f, 12.0f / 50888.0f, 55.0f / 50888.0f, + 90.0f / 50888.0f, 55.0f / 50888.0f, 12.0f / 50888.0f, + 1.0f / 50888.0f } +}; + +// Kernel 函数:_saliencyMapByDiffValueKer(差值法计算显著性值) +// 计算图像中每个像素值的显著性值。以每个像素为中心,计算其与邻域 radius 内所有 +// 像素的灰度差值;对所有差值按照降序进行排序,去掉排序中先头的若干值和末尾的若 +// 干值(通过设置 highPercent 和 lowPercent),只保留中间部分的排序结果,计算平 +// 均值作为显著性值,形成一个初期的 saliency map。然后改变 radius 值,重复上述 +// 计算,得到若干个初期 saliency map,将所有的 saliency map 进行累加平均,就得 +// 到最终的平均 saliency map,输出到 outimg 中。 +static __global__ void // Kernel 函数无返回值。 +_saliencyMapByDiffValueKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + int *radius, // 模板半径 + int iteration, // 迭代次数 + float hightpercent, // 数组的高位段 + float lowpercent // 数组的低位段 +); + +// Kernel 函数:_saliencyMapByDiffValueKer(差值法计算显著性值) +// 计算图像中每个像素值的显著性值。以每个像素为中心,计算其与邻域 radius 内所有 +// 像素的灰度差值;不进行数组的筛选,计算所有差值的平均值作为显著性值,形成一个 +// 初期的 saliency map。然后改变 radius 大小,重复上述计算,就会得到若干个初期 +// saliency map,将所有的 saliency map 进行累加平均,就得到最终的平均显著性图 +// saliency map,输出到 outimg 中。 +static __global__ void // Kernel 函数无返回值。 +_saliencyMapByDiffValueKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + int *radius, // 模板半径 + int iteration // 迭代次数 +); + +// Kernel 函数:_saliencyMapBySmoothKer(高斯平滑法计算显著值) +// 计算图像中每个像素值的显著性值。利用高斯平滑滤波对原始图像进行处理,设置 +// smoothWidth 表示平滑尺度大小,将平滑结果与邻域算数几何平均的图像进行整体差分 +// ,就得到一个初期的 saliency map。改变 smoothWidth 的值,重复上述计算,得到若 +// 干个初期 saliency map,将所有的 saliency map 进行累加平均,就得到最终的平均 +// saliency map,输出到 outimg 中。 +static __global__ void // Kernel 函数无返回值。 +_saliencyMapBySmoothKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + int *smoothwidth, // 平滑模板 + int iteration // 迭代次数 +); + +// Kernel 函数:_saliencyMapAverageKer(计算平均显著性值) +// 将差值法计算显著性值和高斯平滑法计算显著值的结果进行加权平均,weightSM1 是 +// 差值法计算显著性值的权重,weightSM2 是高斯平滑法计算显著值的权重。加权结果 +// 保存到 sm2img 中。 +static __global__ void // Kernel 函数无返回值。 +_saliencyMapAverageKer( + ImageCuda sm1img, // 输入图像 + ImageCuda sm2img, // 输出图像 + float weightsm1, // 差值法计算显著性值的权重 + float weightsm2 // 高斯平滑法计算显著值的权重 +); + +// Kernel 函数:_regionSaliencyKer(计算区域累计显著性值) +// 计算每个区域所有像素值的累计显著性值。默认区域的最大个数为 256。 +static __global__ void // Kernel 函数无返回值。 +_regionSaliencyKer( + ImageCuda smimg, // 输入图像 + ImageCuda connimg, // 输出图像 + unsigned int *regionaverg // 区域显著性累计数组 +); + +// Kernel 函数:_regionAverageKer(计算区域的平均显著性值) +// 通过区域累计显著性数组除以区域面积,得到区域平均显著性值。 +static __global__ void // Kernel 函数无返回值。 +_regionAverageKer( + unsigned int *regionaverg, // 区域显著性累计数组 + unsigned int *regionarea, // 区域面积数组 + unsigned int *flag, // 判断是否满足阈值标识位 + int saliencythred // 区域显著性阈值大小 +); + +// Kernel 函数: _regionShowKer(显示显著性区域) +// 根据标识数组,将显著性区域的灰度值设为 255,背景设为 0。 +static __global__ void +_regionShowKer( + ImageCuda inimg, // 输入图像 + unsigned int *flaglabel // 标识数组 +); + +// Kernel 函数:_saliencyMapByDiffValueKer(差值法计算显著性值) +static __global__ void _saliencyMapByDiffValueKer( + ImageCuda inimg, ImageCuda outimg, int *radius, int iteration) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来记录当前处理像素的灰度值。 + unsigned char *curinptr; + curinptr = inimg.imgMeta.imgData + dstc + dstr * inimg.pitchBytes; + + // 存放邻域像素的灰度值。 + unsigned char neighpixel; + + // 邻域内所有灰度值的差值。 + int diffvalue; + // 差值累积和。 + float tempSum = 0; + // 邻域像素个数。 + int neighbors; + + for (int num = 0; num < iteration; num++) { + int r = radius[num]; + // 计算邻域差值之和。 + float diffsum = 0; + // 计算邻域像素个数。 + neighbors = (2 * r - 1) * (2 * r - 1); + + // 对当前像素的 (2 * r - 1) * (2 * r - 1)领域,计算出 + // 各像素值以及相应的个数。 + for (int j = dstr - (r - 1); j <= dstr + (r - 1); j++) { + for (int i = dstc - (r - 1); i <= dstc + (r - 1); i++) { + // 判断当前像素是否越界。 + if (j >= 0 && j < inimg.imgMeta.height && + i >= 0 && i < inimg.imgMeta.width) { + // 循环计算每个邻域的灰度值差值。 + neighpixel = *(inimg.imgMeta.imgData + i + + j * inimg.pitchBytes); + diffvalue = *curinptr - neighpixel; + if (diffvalue < 0) + diffvalue = -diffvalue; + // 累加邻域内的差值。 + diffsum += diffvalue; + } + } + } + // 计算一次迭代的显著性值。 + diffsum = diffsum / neighbors; + + // 将多次迭代的结果累加。 + tempSum += diffsum; + } + + // 多次迭代结果计算平均显著性值输出到图像中。 + unsigned char *curoutptr; + curoutptr = outimg.imgMeta.imgData + dstc + dstr * outimg.pitchBytes; + *curoutptr = (int)(tempSum + 0.5f) / iteration; +} + +// Kernel 函数:_saliencyMapByDiffValueKer(差值法计算显著性值) +static __global__ void _saliencyMapByDiffValueKer( + ImageCuda inimg, ImageCuda outimg, int *radius, int iteration, + float hightpercent, float lowpercent) +{ + + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中, + // c 表示 column, r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来记录当前处理像素的灰度值。 + unsigned char *curinptr; + curinptr = inimg.imgMeta.imgData + dstc + dstr * inimg.pitchBytes; + + // 存放邻域像素的灰度值。 + unsigned char neighpixel; + + // 邻域内所有灰度值的差值。 + int diffvalue; + // 差值累积和。 + float tempSum = 0; + // 邻域像素个数。 + int neighbors; + + for (int num = 0; num < iteration; num++) { + int r = radius[num]; + // 定义数组,下标代表图像灰度值,数组里存相应的个数 + int count[256] = { 0 }; + + // 计算邻域像素个数。 + neighbors = (2 * r - 1) * (2 * r - 1); + + // 对当前像素的 (2 * r - 1) * (2 * r - 1)领域,计算出 + // 各像素值以及相应的个数。 + for (int j = dstr - (r - 1); j <= dstr + (r - 1); j++) { + for (int i = dstc - (r - 1); i <= dstc + (r - 1); i++) { + // 判断当前像素是否越界。 + if (j >= 0 && j < inimg.imgMeta.height && + i >= 0 && i < inimg.imgMeta.width) { + // 循环计算每个邻域的灰度值差值。 + neighpixel = *(inimg.imgMeta.imgData + i + + j * inimg.pitchBytes); + diffvalue = *curinptr - neighpixel; + if (diffvalue < 0) + diffvalue = -diffvalue; + // 当前灰度值差值的计数器加 1。 + count[diffvalue]++; + } + } + } + + // 去掉排序结果中先头的若干值和末尾的若干值。 + int hp = (int)(hightpercent * neighbors + 0.5f); + int lp = (int)(lowpercent * neighbors + 0.5f); + // 定义一些临时变量。 + int lpcount = 0, hpcount = 0; + // lp 和 hp 完成标识位。 + bool lpover = false, hpover = false; + // lp 和 hp 结果索引。 + int lpindex = 0, hpindex = 0; + int lpresidual = 0, hpresidual = 0; + + // 循环查找数组,找到 lp 和 hp 位置。 + for (int lpi = 0; lpi < 256; lpi++) { + // 筛选结束。 + if (lpover == true && hpover == true) + break; + + // 处理低段数据 lp。 + lpcount += count[lpi]; + if (lpover == false && lpcount >= lp) { + lpindex = lpi + 1; + lpover = true; + lpresidual = lpi * (lpcount - lp); + } + + // 处理高段数据 hp。 + int hpi = 255 - lpi; + hpcount += count[hpi]; + if (hpover == false && hpcount >= hp) { + hpindex = hpi - 1; + hpover = true; + hpresidual = hpi * (hpcount - hp); + } + } + // 如果 lp 大于 hp,则错误退出。 + if (lpindex > hpindex) + return; + + // 计算保留部分的均值。 + float sum = lpresidual + hpresidual; + for (int j = lpindex; j <= hpindex; j++) { + sum += count[j] * j; + } + + // 计算一次迭代的显著性值。 + sum = sum / (neighbors - lp - hp); + + // 将多次迭代的结果累加。 + tempSum += sum; + } + + // 多次迭代结果计算平均显著性值输出到图像中。 + unsigned char *curoutptr; + curoutptr = outimg.imgMeta.imgData + dstc + dstr * outimg.pitchBytes; + *curoutptr = (int)(tempSum + 0.5f) / iteration; +} + +// Host 成员方法:saliencyMapByDiffValue(差值法计算显著值) +__host__ int SalientRegionDetect::saliencyMapByDiffValue(Image *inimg, + Image *outimg) +{ + // 检查输入图像,输出图像是否为空。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查输入参数是否为空。 + if (this->radius == NULL || iterationSM1 == 0) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一。 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / DEF_BLOCK_X; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / DEF_BLOCK_Y; + + // 在 Device 端申请模板半径数组。 + int *devradius; + cudaError_t cudaerrcode; + cudaerrcode = cudaMalloc((void** )&devradius, iterationSM1 * sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 将 Host 上的 radius 拷贝到 Device 上的 devradius 中。 + cudaerrcode = cudaMemcpy(devradius, radius, iterationSM1 * sizeof (int), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + if (this->isSelect == true) { + // 调用核函数,筛选差值数组,计算每个像素的显著性值。 + _saliencyMapByDiffValueKer<<>>( + insubimgCud, outsubimgCud, devradius, + iterationSM1, highPercent, lowPercent); + } else { + // 调用核函数,不筛选差值数组,计算每个像素的显著性值。 + _saliencyMapByDiffValueKer<<>>( + insubimgCud, outsubimgCud, devradius, iterationSM1); + } + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(devradius); + return CUDA_ERROR; + } + + // 释放 Device 端半径空间。 + cudaFree(devradius); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Kernel 函数:_saliencyMapBySmoothKer(高斯平滑法计算显著值) +static __global__ void _saliencyMapBySmoothKer( + ImageCuda inimg, ImageCuda outimg, int *smoothwidth, int iteration) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算 + // 资源,另一方面防止由于段错误导致程序崩溃。 + if (dstc >= inimg.imgMeta.width || dstr >= inimg.imgMeta.height) + return; + + // 用来保存临时像素点的坐标的 x 和 y 分量。 + int dx, dy; + + // 存放邻域像素的灰度值。 + unsigned char neighpixel; + + float gaussitera = 0.0f, meanitera = 0.0f; + for (int num = 0; num < iteration; num++) { + int w = smoothwidth[num]; + int neighbors = w * w; + // 获取高斯模板权重。 + float *gaussweight = _gaussCoeffDev[num]; + // 算术平均权重。 + float meanweight = 1.0f / neighbors; + // 统计加权和。 + float gausssum = 0.0f, meansum = 0.0f; + // 循环处理邻域内每个像素点。 + for (int i = 0; i < neighbors; i++) { + // 分别计算每一个点的横坐标和纵坐标 + dx = dstc + i % w - 1; + dy = dstr + i / w - 1; + + // 先判断当前像素是否越界,如果越界,则跳过,扫描下一个点。 + if (dx >= 0 && dx < inimg.imgMeta.width && + dy >= 0 && dy < inimg.imgMeta.height) { + // 根据 dx 和 dy 获取邻域像素的灰度值。 + neighpixel = *(inimg.imgMeta.imgData + dx + + dy * inimg.pitchBytes); + // 累积一次迭代中高斯平滑的加权和。 + gausssum += neighpixel * gaussweight[i]; + // 累积一次迭代中算术平均的加权和。 + meansum += neighpixel * meanweight; + } + } + + // 累积多次迭代的结果。 + gaussitera += gausssum; + meanitera += meansum; + } + + // 多次迭代结果计算平均显著性值输出到图像中。 + unsigned char *curoutptr; + curoutptr = outimg.imgMeta.imgData + dstc + dstr * outimg.pitchBytes; + *curoutptr = (unsigned char)((gaussitera - meanitera) / iteration + 0.5f); + +} + +// Host 成员方法:saliencyMapBySmooth(高斯平滑法计算显著值) +__host__ int SalientRegionDetect::saliencyMapBySmooth(Image *inimg, + Image *outimg) +{ + // 检查输入图像,输出图像是否为空 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查输入参数 smoothWidth 是否为空。 + if (this->smoothWidth == NULL || iterationSM2 == 0) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一。 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / DEF_BLOCK_X; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / DEF_BLOCK_Y; + + // 在 Device 端分配平滑模板数组。 + int *devwidth; + cudaError_t cudaerrcode; + cudaerrcode = cudaMalloc((void** )&devwidth, iterationSM2 * sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 将 Host 上的 smoothWidth 拷贝到 Device 上的 devwidth 中。 + cudaerrcode = cudaMemcpy(devwidth, smoothWidth, iterationSM2 * sizeof (int), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 调用核函数,计算高斯平滑平均值。 + _saliencyMapBySmoothKer<<>>( + insubimgCud, outsubimgCud, devwidth, iterationSM2); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(devwidth); + return CUDA_ERROR; + } + + // 释放 Device 端空间。 + cudaFree(devwidth); + + // 处理完毕,退出。 + return NO_ERROR; +} + +// Kernel 函数:_saliencyMapAverageKer(计算平均显著性值) +static __global__ void _saliencyMapAverageKer( + ImageCuda inimg, ImageCuda outimg, float w1, float w2) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + // 读取第一个输出坐标点对应的像素值。 + unsigned char outtemp; + outtemp = outimg.imgMeta.imgData[outidx]; + + // 一个线程处理四个像素点. + // 将差值法计算显著性值和高斯平滑法计算显著值的结果进行加权平均,weightSM1 + // 是差值法计算显著性值的权重,weightSM2 是高斯平滑法计算显著值的权重。 + // 线程中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = + (unsigned char)(intemp * w1 + outtemp * w2 + 0.5f); + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + outtemp = outimg.imgMeta.imgData[outidx]; + + // 将显著性值输出到输出图像中。 + outimg.imgMeta.imgData[outidx] = + (unsigned char)(intemp * w1 + outtemp * w2 + 0.5f); + } +} + +// Kernel 函数:_regionSaliencyKer(计算区域累计显著性值) +static __global__ void _regionSaliencyKer( + ImageCuda smimg, ImageCuda connimg, unsigned int *regionsacy) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= smimg.imgMeta.width || r >= smimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int smidx = r * smimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int connidx = r * connimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char smtemp; + smtemp = smimg.imgMeta.imgData[smidx]; + unsigned char conntemp; + conntemp = connimg.imgMeta.imgData[connidx]; + + // 一个线程处理四个像素点。 + // 计算区域的累计显著性值。 + // 线程中处理的第一个点。 + regionsacy[conntemp] += smtemp ; + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= connimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + smidx += smimg.pitchBytes; + connidx += connimg.pitchBytes; + smtemp = smimg.imgMeta.imgData[smidx]; + conntemp = connimg.imgMeta.imgData[connidx]; + + // 如果输入图像的该像素值等于 label, 则将其拷贝到输出图像中; + // 否则将输出图像中该位置清 0。 + regionsacy[conntemp] += smtemp ; + } +} + +// Kernel 函数:_regionAverageKer(计算区域的平均显著性值) +static __global__ void _regionAverageKer( + unsigned int *regionaverg, unsigned int *regionarea, + unsigned int *flag, int saliencythred) +{ + // 读取线程号。 + int tid = threadIdx.x; + + // 通过区域累计显著性数组除以区域面积,得到区域平均显著性值。 + if (regionarea[tid] > 0) + regionaverg[tid] = ((float)regionaverg[tid] + 0.5f) / regionarea[tid]; + + if (regionaverg[tid] > saliencythred) + flag[tid] = 1; + else + flag[tid] = 0; +} + +// Kernel 函数: _regionShowKer(显示显著性区域) +static __global__ void _regionShowKer(ImageCuda inimg, unsigned int *flaglabel) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + + // 一个线程处理四个像素点. + // 如果输入图像的该像素值对应的 flag 等于 0, 则将像素值设为 0; + // 否则设为 255。 + // 线程中处理的第一个点。 + if (flaglabel[inimg.imgMeta.imgData[inidx]] == 0) + inimg.imgMeta.imgData[inidx] = 0; + else + inimg.imgMeta.imgData[inidx] = 255; + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= inimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + + // 如果输入图像的该像素值对应的 flag 等于 0, 则将像素值设为 0; + // 否则设为 255。 + if (flaglabel[inimg.imgMeta.imgData[inidx]] == 0) + inimg.imgMeta.imgData[inidx] = 0; + else + inimg.imgMeta.imgData[inidx] = 255; + } +} + +// Host 成员方法:saliencyRegionDetect(显著性区域检测) +__host__ int SalientRegionDetect::saliencyRegionDetect(Image *inimg, + Image *outimg) +{ + // 检查输入图像,输出图像是否为空 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查输入参数 smoothWidth 是否为空。 + if (this->smoothWidth == NULL || this->radius == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码。 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一。 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 申请 SM1 和 SM2 中间图像。 + Image *sm1, *sm2; + ImageBasicOp::newImage(&sm1); + ImageBasicOp::newImage(&sm2); + ImageBasicOp::makeAtCurrentDevice(sm1, inimg->width, inimg->height); + ImageBasicOp::makeAtCurrentDevice(sm2, inimg->width, inimg->height); + + // 差值法计算显著值。 + errcode = saliencyMapByDiffValue(inimg, sm1); + if (errcode != NO_ERROR) + return errcode; + + // 高斯平滑法计算显著值。 + errcode = saliencyMapBySmooth(inimg, sm2); + if (errcode != NO_ERROR) + return errcode; + + // 提取 sm1 图像的 ROI 子图像。 + ImageCuda sm1subimgCud; + errcode = ImageBasicOp::roiSubImage(sm1, &sm1subimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取 sm2 图像的 ROI 子图像。 + ImageCuda sm2subimgCud; + errcode = ImageBasicOp::roiSubImage(sm2, &sm2subimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一。 + if (sm1subimgCud.imgMeta.width > sm2subimgCud.imgMeta.width) + sm1subimgCud.imgMeta.width = sm2subimgCud.imgMeta.width; + else + sm2subimgCud.imgMeta.width = sm1subimgCud.imgMeta.width; + + if (sm1subimgCud.imgMeta.height > sm2subimgCud.imgMeta.height) + sm1subimgCud.imgMeta.height = sm2subimgCud.imgMeta.height; + else + sm2subimgCud.imgMeta.height = sm1subimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (sm2subimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (sm2subimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 合并两种计算显著性值的算法,加权求和,结果保存在 sm2 中。 + _saliencyMapAverageKer<<>>(sm1subimgCud, sm2subimgCud, + weightSM1, weightSM2); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + ImageBasicOp::deleteImage(sm1); + ImageBasicOp::deleteImage(sm2); + return CUDA_ERROR; + } + + // 利用连通区域分割 saliency map。 + ConnectRegion cr; + cr.setThreshold(this->threshold); + cr.setMinArea(this->minRegion); + cr.setMaxArea(this->maxRegion); + cr.connectRegion(sm2, outimg); + + // 计算每个区域的平均显著性值。 + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + unsigned int *alldevicedata; + unsigned int *devhistogram, *devregionAverg, *devflag; + cudaerrcode = cudaMalloc((void** )&alldevicedata, + 3 * GRAY_LEVEL * sizeof (unsigned int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevicedata, 0, + 3 * GRAY_LEVEL * sizeof (unsigned int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 通过偏移读取 devhistogram 内存空间。 + devhistogram = alldevicedata; + + // 通过直方图计算区域面积,保存到 devhistogram 中。 + Histogram hist; + errcode = hist.histogram(outimg, devhistogram, 0); + if (errcode != NO_ERROR) + return errcode; + + // 计算每个区域的累积显著性值。 + devregionAverg = alldevicedata + GRAY_LEVEL; + _regionSaliencyKer<<>>(sm2subimgCud, outsubimgCud, + devregionAverg); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + ImageBasicOp::deleteImage(sm1); + ImageBasicOp::deleteImage(sm2); + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + devflag = devregionAverg + GRAY_LEVEL; + // 计算每个区域的平均显著性值。 + _regionAverageKer<<<1, GRAY_LEVEL>>>(devregionAverg, devhistogram, + devflag, saliencyThred); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + ImageBasicOp::deleteImage(sm1); + ImageBasicOp::deleteImage(sm2); + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 将显著性区域设为白色,背景设为黑色。 + _regionShowKer<<>>(outsubimgCud, devflag); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + ImageBasicOp::deleteImage(sm1); + ImageBasicOp::deleteImage(sm2); + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 释放中间图像。 + ImageBasicOp::deleteImage(sm1); + ImageBasicOp::deleteImage(sm2); + + // 释放显存上的临时空间 + cudaFree(alldevicedata); + + return NO_ERROR; +} diff --git a/okano_3_0/SalientRegionDetect.h b/okano_3_0/SalientRegionDetect.h new file mode 100644 index 0000000..5a23184 --- /dev/null +++ b/okano_3_0/SalientRegionDetect.h @@ -0,0 +1,487 @@ +// SalientRegionDetect.h +// 创建人:刘宇 +// +// 显著区域检测(SalientRegionDetect) +// 功能说明:根据图像中的领域灰度值计算像素的显著性值,分割得到显著性区域, +// 用二值图形式显示结果。 +// +// 修订历史: +// 2012年12月05日(刘宇) +// 初始版本 +// 2013年02月24日(刘宇) +// 修正一些成员变量 + +#ifndef __SALIENTREGIONDETECT_H__ +#define __SALIENTREGIONDETECT_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:SalientRegionDetect +// 继承自:无 +// 首先计算 saliency map ,包括两种算法,一种是根据领域的灰度值差值排序并平均, +// 一种是根据高斯平滑计算,将两种方法的结果加权平均就得到最终的 saliency map, +// 然后分割 saliency map 得到多个显著性区域,最后根据区域平均显著性阈值和区域 +// 面积,筛选出最终需要的显著性区域,并用二值化显示,即显著性区域设置为白色, +// 其它区域为黑色。 +class SalientRegionDetect { + +protected: + + // 成员变量:highPercent(数组的高位段) + // 指定砍掉排序后数组的高位段的 highPercent,范围是 [0, 1]。 + float highPercent; + + // 成员变量:lowPercent(数组的低位段) + // 指定砍掉排序后数组的低位段的 lowPercent,范围是 [0, 1]。 + float lowPercent; + + // 成员变量:radius(模版半径) + // 模版半径数组,例如 radius = {5, 8, 13,......}。 + int *radius; + + // 成员变量:iterationSM1(radius 的迭代次数) + // 改变模版半径 radius 的值,迭代计算的次数。 + int iterationSM1; + + // 成员变量:isSelect(筛选数组标识位) + // 如果其值为 1,则通过 highPercent 和 lowPercent 筛选数组,否则不筛选。 + bool isSelect; + + // 成员变量:smoothWidth(平滑模版大小) + // 模版半径数组,例如 smoothWidth = {5, 8, 13,......}。 + int *smoothWidth; + + // 成员变量:iterationSM2(iterationSM2 的迭代次数) + // 改变模版半径 iterationSM2 的值,迭代计算的次数。 + int iterationSM2; + + // 成员变量:weightSM1(sm1 的权重) + // saliency map1 的权重。 + float weightSM1; + + // 成员变量:weightSM2(sm2 的权重) + // saliency map2 的权重。 + float weightSM2; + + // 成员变量:threshold(给定阈值) + // 进行区域连通的给定值,当两个点满足八邻域关系, + // 且灰度值之差的绝对值小于该值时,这两点属于同一区域。 + int threshold; + + // 成员变量:minRegion(区域最小面积的阈值) + // 显著性区域面积的最小阈值。 + int minRegion; + + // 成员变量:maxRegion(区域最大面积的阈值) + // 显著性区域面积的最大阈值。 + int maxRegion; + + // 成员变量:saliencyThred(区域平均显著性值的阈值大小) + // 显著性区域平均显著性值的阈值大小。 + int saliencyThred; + +public: + // 构造函数:SalientRegionDetect + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + SalientRegionDetect() + { + // 使用默认值为类的各个成员变量赋值。 + this->highPercent = 0.1; // 数组的高位段默认为 0.1 + this->lowPercent = 0.3; // 数组的低位段默认为 0.3 + this->radius = NULL; // 模版半径默认为空 + this->iterationSM1 = 0; // radius 迭代次数默认为 0 + this->isSelect = false; // 筛选数组标识位默认为 false + this->smoothWidth = NULL; // 平滑模版大小默认为空 + this->iterationSM2 = 0; // smoothWidth 迭代次数默认为 0 + this->weightSM1 = 0.5; // SM1 的权重默认为 0.5 + this->weightSM2 = 0.5; // SM2 的权重默认为 0.5 + this->threshold = 3; // 给定阈值默认为0 + this->minRegion = 100; // 区域最小面积的阈值大小默认为 100 + this->maxRegion = 500000; // 区域最大面积的阈值大小默认为 500000 + this->saliencyThred = 1; // 区域平均显著性值的阈值大小默认为 1 + } + + + // 构造函数:SalientRegionDetect + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + SalientRegionDetect( + float highpercent, // 数组的高位段 + float lowpercent, // 数组的低位段 + int *radius, // 模版半径 + int iterationsm1, // radius 迭代次数 + bool isselect, // 筛选数组标识位 + int *smoothwidth, // 平滑模版大小 + int iterationsm2, // smoothWidth 迭代次数 + float weightsm1, // SM1 的权重 + float weightsm2, // SM2 的权重 + int threshold, // 给定的标记阈值 + int minregion, // 区域最小面积的阈值大小 + int maxregion, // 区域最大面积的阈值大小 + int saliencythred // 区域平均显著性值的阈值大小 + ) { + // 使用默认值为类的各个成员变量赋值。 + this->highPercent = 0.1; // 数组的高位段默认为 0.1 + this->lowPercent = 0.3; // 数组的低位段默认为 0.3 + this->radius = NULL; // 模版半径默认为空 + this->iterationSM1 = 0; // radius 迭代次数默认为 0 + this->isSelect = false; // 筛选数组标识位默认为 false + this->smoothWidth = NULL; // 平滑模版大小默认为空 + this->iterationSM2 = 0; // smoothWidth 迭代次数默认为 0 + this->weightSM1 = 0.5; // SM1 的权重默认为 0.5 + this->weightSM2 = 0.5; // SM2 的权重默认为 0.5 + this->threshold = 3; // 给定的标记阈值 + this->minRegion = 100; // 区域最小面积的阈值大小默认为 100 + this->maxRegion = 500000; // 区域最大面积的阈值大小默认为 500000 + this->saliencyThred = 1; // 区域平均显著性值的阈值大小默认为 1 + + // 根据参数列表中的值设定成员变量的初值 + setHighPercent(highpercent); + setLowPercent(lowpercent); + setRadius(radius); + setIterationSM1(iterationsm1); + setIsSelect(isselect); + setSmoothWidth(smoothwidth); + setIterationSM2(iterationsm2); + setWeightSM1(weightsm1); + setWeightSM2(weightsm2); + setThreshold(threshold); + setMinRegion(minregion); + setMaxRegion(maxregion); + setSaliencyThred(saliencythred); + } + + // 成员方法:getHighPercent(获取数组的高位段) + // 获取成员变量 highPercent 的值。 + __host__ __device__ float // 返回值:成员变量 highPercent 的值 + getHighPercent() const + { + // 返回 highPercent 成员变量的值。 + return this->highPercent; + } + + // 成员方法:setHighPercent(设置数组的高位段) + // 设置成员变量 highPercent 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setHighPercent( + float highpercent // 设定新的数组的高位段 + ) { + // 将 highPercent 成员变量赋成新值 + this->highPercent = highpercent; + + return NO_ERROR; + + } + + // 成员方法:getLowPercent(获取数组的低位段) + // 获取成员变量 lowPercent 的值。 + __host__ __device__ float // 返回值:成员变量 lowPercent 的值 + getLowPercent() const + { + // 返回 lowPercent 成员变量的值。 + return this->lowPercent; + } + + // 成员方法:setLowPercent(设置数组的低位段) + // 设置成员变量 lowPercent 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setLowPercent( + float lowpercent // 设定新的数组的低位段 + ) { + // 将 lowPercent 成员变量赋成新值 + this->lowPercent = lowpercent; + + return NO_ERROR; + } + + // 成员方法:getRadius(获取模版半径) + // 获取成员变量 radius 的值。 + __host__ __device__ int * // 返回值:成员变量 radius 的值 + getRadius() const + { + // 返回 radius 成员变量的值。 + return this->radius; + } + + // 成员方法:setRadius(设置模版半径) + // 设置成员变量 radius 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setRadius( + int *radius // 设定新的模版半径 + ) { + // 将 radius 成员变量赋成新值 + this->radius = radius; + + return NO_ERROR; + } + + // 成员方法:getIterationSM1(获取 radius 迭代次数) + // 获取成员变量 iterationSM1 的值。 + __host__ __device__ int // 返回值:成员变量 iterationSM1 的值 + getIterationSM1() const + { + // 返回 iterationM1 成员变量的值。 + return this->iterationSM1; + } + + // 成员方法:setIterationSM1(设置 radius 迭代次数) + // 设置成员变量 iterationSM1 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setIterationSM1( + int iterationsm1 // 设定新的迭代次数 + ) { + // 将 iterationM1 成员变量赋成新值 + this->iterationSM1 = iterationsm1; + + return NO_ERROR; + } + + // 成员方法:getIsSelect(获取 isSelect 筛选标识位) + // 获取成员变量 isSelect 的值。 + __host__ __device__ bool // 返回值:成员变量 isSelect 的值 + getIsSelect() const + { + // 返回 isSelect 成员变量的值。 + return this->isSelect; + } + + + // 成员方法:setIsSelect(设置 isSelect 筛选标识位) + // 设置成员变量 isSelect 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setIsSelect( + bool isselect // 设定新的筛选标识位 + ) { + // 将 isSelect 成员变量赋成新值 + this->isSelect = isselect; + + return NO_ERROR; + } + + // 成员方法:getSmoothWidth(获取平滑模版大小) + // 获取成员变量 smoothWidth 的值。 + __host__ __device__ int * // 返回值:成员变量 smoothWidth 的值 + getSmoothWidth() const + { + // 返回 smoothWidth 成员变量的值。 + return this->smoothWidth; + } + + // 成员方法:setSmoothWidth(设置平滑模版大小) + // 设置成员变量 smoothWidth 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setSmoothWidth( + int *smoothwidth // 设定新的平滑模版大小 + ) { + // 将 smoothWidth 成员变量赋成新值 + this->smoothWidth = smoothwidth; + + return NO_ERROR; + } + + // 成员方法:getIterationSM2(获取 iterationSM2 迭代次数) + // 获取成员变量 iterationSM2 的值。 + __host__ __device__ int // 返回值:成员变量 iterationSM2 的值 + getIterationSM2() const + { + // 返回 iterationM2 成员变量的值。 + return this->iterationSM2; + } + + // 成员方法:setIterationSM2(设置 iterationSM2 迭代次数) + // 设置成员变量 iterationSM2 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setIterationSM2( + int iterationsm2 // 设定新的迭代次数 + ) { + // 将 iterationSM2 成员变量赋成新值 + this->iterationSM2 = iterationsm2; + + return NO_ERROR; + } + + // 成员方法:getWeightSM1(获取 SM1 的权重) + // 获取成员变量 weightSM1 的值。 + __host__ __device__ float // 返回值:成员变量 weightSM1 的值 + getWeightSM1() const + { + // 返回 weightSM1 成员变量的值。 + return this->weightSM1; + } + + // 成员方法:setWeightSM1(设置 SM1 的权重) + // 设置成员变量 weightSM1 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setWeightSM1( + float weightsm1 // 设定新的 SM1 的权重 + ) { + // 将 weightSM1 成员变量赋成新值 + this->weightSM1 = weightsm1; + + return NO_ERROR; + } + + // 成员方法:getWeightSM2(获取 SM2 的权重) + // 获取成员变量 weightSM2 的值。 + __host__ __device__ float // 返回值:成员变量 weightSM2 的值 + getWeightSM2() const + { + // 返回 weightSM2 成员变量的值。 + return this->weightSM2; + } + + // 成员方法:setWeightSM2(设置 SM2 的权重) + // 设置成员变量 weightSM2 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setWeightSM2( + float weightsm2 // 设定新的 SM2 的权重 + ) { + // 将 weightSM2 成员变量赋成新值 + this->weightSM2 = weightsm2; + + return NO_ERROR; + } + + // 成员方法:getThreshold(读取阈值) + // 读取 threshold 成员变量的值。 + __host__ __device__ int // 返回值:当前 threshold 成员变量的值。 + getThreshold() const + { + // 返回 threshold 成员变量的值。 + return this->threshold; + } + + // 成员方法:setThreshold(设置阈值) + // 设置 threshold 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setThreshold( + int threshold // 指定的阈值大小。 + ) { + // 将 threshold 成员变量赋值成新值 + this->threshold = threshold; + + return NO_ERROR; + } + + // 成员方法:getMinRegion(获取区域最小面积的阈值) + // 获取成员变量 minRegion 的值。 + __host__ __device__ int // 返回值:成员变量 minRegion 的值 + getMinRegion() const + { + // 返回 minRegion 成员变量的值。 + return this->minRegion; + } + + // 成员方法:setMinRegion(设置区域最小面积的阈值) + // 设置成员变量 minRegion 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setMinRegion( + int minregion // 设定新的区域最小面积的阈值 + ) { + // 将 minRegion 成员变量赋成新值 + this->minRegion = minregion; + + return NO_ERROR; + } + + // 成员方法:getMaxRegion(获取区域最大面积的阈值) + // 获取成员变量 maxRegion 的值。 + __host__ __device__ int // 返回值:成员变量 maxRegion 的值 + getMaxRegion() const + { + // 返回 maxRegion 成员变量的值。 + return this->maxRegion; + } + + // 成员方法:setMaxRegion(设置区域最大面积的阈值) + // 设置成员变量 maxRegion 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setMaxRegion( + int maxregion // 设定新的区域最大面积的阈值 + ) { + // 将 maxRegion 成员变量赋成新值 + this->maxRegion = maxregion; + + return NO_ERROR; + } + + // 成员方法:getSaliencyThred(获取区域平均显著性值的阈值大小) + // 获取成员变量 saliencyThred 的值。 + __host__ __device__ int // 返回值:成员变量 saliencyThred 的值 + getSaliencyThred() const + { + // 返回 saliencyThred 成员变量的值。 + return this->saliencyThred; + } + + // 成员方法:setSaliencyThred(设置区域平均显著性值的阈值大小) + // 设置成员变量 saliencyThred 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setSaliencyThred( + int saliencythred // 设定新的区域平均显著性值的阈值大小 + ) { + // 将 saliencyThred 成员变量赋成新值 + this->saliencyThred = saliencythred; + + return NO_ERROR; + } + + // Host 成员方法:saliencyMapByDiffValue(差值法计算显著值) + // 计算图像中每个像素值的显著性值。以每个像素为中心,计算其与邻域r内所有 + // 像素的灰度差值;对所有差值按照降序进行排序,去掉排序中先头的若干值和 + // 末尾的若干值(通过设置 highPercent 和 lowPercent),只保留中间部分的 + // 排序结果。对所有的像素进行这样的计算,形成一个初期的 saliency map。 + // 然后改变 r 值,重复上述计算,得到若干个初期 saliency map,将所有的 + // saliency map 进行累加平均,就得到最终的平均 saliency map,设为 SM1。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + saliencyMapByDiffValue( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // Host 成员方法:saliencyMapBySmooth(高斯平滑法计算显著值) + // 计算图像中每个像素值的显著性值。利用高斯平滑滤波对原始图像进行处理, + // 设置 sigma 表示平滑尺度大小,将平滑结果与算数几何平均的图像进行整体 + // 差分,就得到一个初期的 saliency map。改变 sigma 的值,重复上述计算, + // 得到若干个初期 saliency map,将所有的 saliency map 进行累加平均, + // 就得到最终的平均 saliency map,设为 SM2。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + saliencyMapBySmooth( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + + // Host 成员方法:saliencyMapBySmooth(高斯平滑法计算显著值) + // 显著性区域的总方法。首先计算 saliency map ,包括两种算法,一种是根据 + // 领域的灰度值差值排序并平均,一种是根据高斯平滑计算,将两种方法的结果 + // 加权平均就得到最终的 saliency map,然后分割 saliency map 得到多个显著 + // 性区域,最后根据区域平均显著性阈值和区域面积,筛选出最终需要的显著性区 + // 域,并用二值化显示,即显著性区域设置为白色,其它区域为黑色。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + saliencyRegionDetect( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif diff --git a/okano_3_0/ScanArray.cu b/okano_3_0/ScanArray.cu new file mode 100644 index 0000000..aa53dac --- /dev/null +++ b/okano_3_0/ScanArray.cu @@ -0,0 +1,1746 @@ +// ScanArray.cu +// 找出图像中给定点集的包围矩形 + +#include "ScanArray.h" +#include "OperationFunctor.h" +#include +#include +using namespace std; + +#define BLOCK_SIZE 1024 + +// 宏:NUM_BANKS +// 定义了bank的数量。 +#define NUM_BANKS 16 + +// 宏:LOG_NUM_BANKS +// 定义了bank的对数值。 +#define LOG_NUM_BANKS 4 + +// Kernel 函数: _scanNaiveKer(数组扫描的简单版本) +// 简单版本的 scan 实现,每个线程处理一个元素。运行 log(n) 步, +// 加 n * (log(n) - 1) 次。需要 2 * n 长度的贡献内存。 +template < class T, class Operation > +__global__ void // Kernel 函数无返回值 +_scanNaiveKer( + T *outarray, // 输入数组 + T *inarray, // 输出数组 + T *blocksum, // 扫描的中间结果数组,用于存放每块扫描结果的最后一 + // 个值,不处理最后一个线程块。少于一块时不处理。 + int n, // 元素个数,数组长度 + Operation op, // 运算符类型 + bool backward // 判断是否为后序扫描 +); + +// Kernel 函数: _scanWorkEfficientKer(数组扫描的效率版本) +// 效率版本的 scan 实现,每个线程处理两个元素。复杂度是 O(log(n)),需要加法的次 +// 数为 O(n)。共享内存长度为 n,使用 balanced tree 算法。 +// 来源一为:Blelloch,1990 "Prefix Sums and Their Applications"。 +// http://www.cs.cmu.edu/~blelloch/papers/Ble93.pdf +// 来源二为:Prins and Chatterjee PRAM course notes: +// https://www.cs.unc.edu/~prins/Classes/633/Handouts/pram.pdf +template < class T, class Operation > +__global__ void // Kernel 函数无返回值 +_scanWorkEfficientKer( + T *outarray, // 输入数组 + T *inarray, // 输出数组 + T *blocksum, // 扫描的中间结果数组,用于存放每块扫描结果的最后一 + // 个值,不处理最后一个线程块。少于一块时不处理。 + int n, // 元素个数,数组长度 + Operation op, // 运算符类型 + bool backward // 判断是否为后序扫描 +); + +// Kernel 函数: _scanOptKer(数组扫描的优化版本) +// 优化版本的 scan 实现,每个线程处理两个原色。复杂度是 O(log(n)),需要加法的次 +// 数为 O(n)。共享内存不检查冲突。 +// 使用 balanced tree 算法。 +// 来源一为:Blelloch,1990 "Prefix Sums and Their Applications"。 +// http://www.cs.cmu.edu/~blelloch/papers/Ble93.pdf +// 来源二为:Prins and Chatterjee PRAM course notes: +// https://www.cs.unc.edu/~prins/Classes/633/Handouts/pram.pdf +template < class T, class Operation > +__global__ void // Kernel 函数无返回值 +_scanOptKer( + T *outarray, // 输入数组 + T *inarray, // 输出数组 + T *blocksum, // 扫描的中间结果数组,用于存放每块扫描结果的最后一 + // 个值,不处理最后一个线程块。少于一块时不处理。 + int n, // 元素个数,数组长度 + Operation op, // 运算类型 + bool backward // 判断是否为后序扫描 +); + +// Kernel 函数: _scanBetterKer(数组扫描的较优版本) +// 优化版本的 scan 实现,每个线程处理两个原色。复杂度是 O(log(n)),需要加法的次 +// 数为 O(n)。共享内存的长度为了避免冲突,设计为 n + n / NUM_BANKS。 +// 使用 balanced tree 算法。 +// 来源一为:Blelloch,1990 "Prefix Sums and Their Applications"。 +// http://www.cs.cmu.edu/~blelloch/papers/Ble93.pdf +// 来源二为:Prins and Chatterjee PRAM course notes: +// https://www.cs.unc.edu/~prins/Classes/633/Handouts/pram.pdf +template < class T, class Operation > +__global__ void // Kernel 函数无返回值 +_scanBetterKer( + T *outarray, // 输入数组 + T *inarray, // 输出数组 + T *blocksum, // 扫描的中间结果数组,用于存放每块扫描结果的最后一 + // 个值,不处理最后一个线程块。少于一块时不处理。 + int n, // 元素个数,数组长度 + Operation op, // 运算类型 + bool backward // 判断是否为后序扫描 +); + +// 函数:scanComputeGold(CPU 端的 inclusive 类型数组计算) +// 对一个数组进行扫描,对所有元素进行某操作的遍历,操作包含自身。 +template < class T, class Operation > +__host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 +scanComputeGold( + T *inarray, // 输入数组 + T *reference, // 输出数组 + const unsigned int len, // 数组的长度,处理元素的个数。 + Operation op, // 运算类型 + bool backward // 判断是否为后序扫描 +); + +// Kernel 函数: _addBackKer(中间结果数组加回操作) +// 对一个数组进行初始扫描后,将中间结果的小数组(原扫描数组每段最后一个元素)加 +// 回到原扫描数组。 +template < class T, class Operation > +__global__ void // Kernel 函数无返回值 +_addBackKer( + T *array, // 初始扫描后的数组。 + T *lastelemarray, // 中间结果数组,原扫描数组每段的最后一个元素提 + // 取出来即为中间结果数组。 + int n, // 元素个数,数组长度 + int packnum, // 扫描核函数每块计算能力,即核函数每块的处理长 + // 度与线程块大小的比值。 + Operation op, // 运算类型 + bool backward // 判断是否为后序扫描 +); + +// Kernel 函数: _scanNaiveKer(数组扫描的简单版本) +template < class T, class Operation > +__global__ void _scanNaiveKer(T *outarray, T *inarray, T *blocksum, int n, + Operation op, bool backward) +{ + // 声明共享内存。 + extern __shared__ unsigned char sharedmemo[]; + // 转化为模板类型的共享内存。 + T *sharedmem = (T *)sharedmemo; + + // 数组索引(块内索引为 threadIdx.x)。 + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + // 定义共享内存中计算位置的两个变量。 + // 共享内存大小是两倍的输入长度,所以通过 pout 和 pin 指针来控制计算位置。 + T *pout = sharedmem; + T *pin = sharedmem + blockDim.x; + + // 将需要计算的值从输入加载到共享内存上。大于数组长度的位置置为 0。 + if (idx < n) + pout[threadIdx.x] = inarray[idx]; + else + pout[threadIdx.x] = op.identity(); + + // 扫描过程,通过偏移量的控制,在两倍输入长度的共享内存上进行切换计算。 + // 每次循环偏移量扩大 2 倍,这样能够实现不断的层次累加。最终实现 scan 的处 + // 理效果。 + if (!backward) { + for (int offset = 1; offset < blockDim.x; offset *= 2) { + // 共享内存大小是两倍的输入长度,所以通过 pout 和 pin 指针交换来换位 + // 置进行处理,即计算值不覆盖原值。 + T *ptemp; + ptemp = pout; + pout = pin; + pin = ptemp; + __syncthreads(); + + // 将所有当前的 scan 计算值,从共享内存的一侧复制到另一侧 + // 一侧指共享内存采用两倍的输入数组的长度,即 double buffer。 + pout[threadIdx.x] = pin[threadIdx.x]; + + // 如果线程索引大于偏移,那么要计算当前位置和当前位置减去偏移量处的 + // 加和 + if (threadIdx.x >= offset) + pout[threadIdx.x] = op(pout[threadIdx.x], + pin[threadIdx.x - offset]); + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx >= n) + return; + // 将结果从共享内存写入到输出。 + outarray[idx] = pout[threadIdx.x]; + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的最后一个线程,将每段处理的最后一个元素写入中间结果数组。最后一 + // 个线程块不进行处理。 + if (threadIdx.x == blockDim.x - 1 && + blockIdx.x < gridDim.x - 1) { + blocksum[blockIdx.x] = pout[threadIdx.x]; + } + } else { + for (int offset = 1; offset < blockDim.x; offset *= 2) { + // 共享内存大小是两倍的输入长度,所以通过 pout 和 pin 指针交换来换位 + // 置进行处理,即计算值不覆盖原值。 + T *ptemp; + ptemp = pout; + pout = pin; + pin = ptemp; + __syncthreads(); + + // 将所有当前的 scan 计算值,从共享内存的一侧复制到另一侧 + // 一侧指共享内存采用两倍的输入数组的长度,即 double buffer。 + pout[threadIdx.x] = pin[threadIdx.x]; + + // 如果线程索引加上偏移量小于块长,那么要计算当前位置和当前位置加上 + // 偏移量处的加和 + if (threadIdx.x + offset < blockDim.x) + pout[threadIdx.x] = op(pin[threadIdx.x], + pin[threadIdx.x + offset]); + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx >= n) + return; + // 将结果从共享内存写入到输出。 + outarray[idx] = pout[threadIdx.x]; + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的第一个线程,将每段处理的第一个元素写入中间结果数组。第一个线 + // 程块不进行处理。 + if (threadIdx.x == 0 && blockIdx.x > 0) { + blocksum[blockIdx.x - 1] = pout[threadIdx.x]; + } + } +} + +// Kernel 函数: _scanWorkEfficientKer(数组扫描的效率版本) +template < class T, class Operation > +__global__ void _scanWorkEfficientKer(T *outarray, T *inarray, + T *blocksum, int n, Operation op, + bool backward) +{ + // 声明共享内存。 + extern __shared__ unsigned char sharedmemo[]; + // 转化为模板类型的共享内存。 + T *sharedmem = (T *)sharedmemo; + + // 定义块内索引。 + int baseidx = 2 * blockIdx.x * blockDim.x; + int inidx = 2 * threadIdx.x; + int idx = baseidx + inidx; + + // 定义偏移量 offset。 + int offset = 1; + + // 定义核函数每块的处理数组长度。 + int length = blockDim.x * 2; + + // 将需要计算的值从输入加载到共享内存上。每个线程处理两个元素(相邻元素) + sharedmem[inidx] = (idx < n) ? inarray[idx] : op.identity(); + sharedmem[inidx + 1] = (idx + 1 < n) ? inarray[idx + 1] : op.identity(); + + // scan 的累加过程,自底向上进行累加操作。 + if (!backward) { + for (int d = blockDim.x; d > 0; d >>= 1) { + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行累加。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * (inidx + 1) - 1; + int bi = offset * (inidx + 2) - 1; + + // 累加。通过这样的过程使得最终最后一位的值是之前所有值 scan 操 + // 作的结果。 + sharedmem[bi] = op(sharedmem[bi], sharedmem[ai]); + } + + // 偏移量每次扩大 2 倍。 + offset *= 2; + } + + // 配合自顶向下的回扫过程,清除最后一个位置上的值。 + if (threadIdx.x == 0) { + // 根据运算类型不同,最后一个位置赋为不同的单位元。 + sharedmem[length - 1] = op.identity(); + } + + // 自顶向下回扫,这样能够使每一位上算出需要的 scan 值。 + // 回扫即把上一步的计算结果进行累加。 + for (int d = 1; d < length; d *= 2) { + // 回扫过程中,每次偏移量缩小2倍。 + offset >>= 1; + + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行回扫累加的过程。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * (inidx + 1) - 1; + int bi = offset * (inidx + 2) - 1; + + // 将 ai 位置处的值拷贝出来加到 bi 处,ai 的新值为 bi 原值。 + T t = sharedmem[ai]; + sharedmem[ai] = sharedmem[bi]; + sharedmem[bi] = op(sharedmem[bi], t); + } + } + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx >= n) + return; + + // 将结果从共享内存写入到输出。 + outarray[idx] = sharedmem[inidx + 1]; + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx + 1 >= n) + return; + // 判断是否为当前块处理数组的最后一个元素 + if ((inidx + 1) == (2 * blockDim.x - 1)) + // 是最后一个元素的话,需要与对应输入数组位置上的元素进行运算 + outarray[idx + 1] = op(sharedmem[inidx + 1], inarray[idx + 1]); + else + // 每个线程处理的下一个元素,将结果从共享内存写入到输出。 + outarray[idx + 1] = sharedmem[inidx + 2]; + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的最后一个线程,将每段处理的最后一个元素写入中间结果数组。最后一 + // 个线程块不进行处理。 + if (threadIdx.x == blockDim.x - 1 && + blockIdx.x < gridDim.x - 1) { + blocksum[blockIdx.x] = outarray[idx + 1]; + } + } else { + for (int d = blockDim.x; d > 0; d >>= 1) { + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行累加。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * inidx; + int bi = offset * (inidx + 1); + + // 累加。通过这样的过程使得最终第一位的值是之前所有值 scan 操 + // 作的结果。 + sharedmem[ai] = op(sharedmem[bi], sharedmem[ai]); + } + + // 偏移量每次扩大 2 倍。 + offset *= 2; + } + + // 配合自顶向下的回扫过程,清除第一个位置上的值。 + if (threadIdx.x == 0) { + // 根据运算类型不同,第一个位置赋为不同的单位元。 + sharedmem[0] = op.identity(); + } + + // 自顶向下回扫,这样能够使每一位上算出需要的 scan 值。 + // 回扫即把上一步的计算结果进行累加。 + for (int d = 1; d < length; d *= 2) { + // 回扫过程中,每次偏移量缩小2倍。 + offset >>= 1; + + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行回扫累加的过程。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * inidx; + int bi = offset * (inidx + 1); + + // 将 bi 位置处的值拷贝出来加到 ai 处,bi 的新值为 ai 原值。 + T t = sharedmem[bi]; + sharedmem[bi] = sharedmem[ai]; + sharedmem[ai] = op(sharedmem[ai], t); + } + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx >= n) + return; + // 判断是否为当前块处理数组的第一个元素 + if (inidx == 0) + // 是第一个元素的话,需要与对应输入数组位置上的元素进行运算 + outarray[idx] = op(sharedmem[inidx], inarray[idx]); + else + // 将结果从共享内存写入到输出。 + outarray[idx] = sharedmem[inidx - 1]; + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx + 1 < n) { + // 每个线程处理的下一个元素,将结果从共享内存写入到输出。 + outarray[idx + 1] = sharedmem[inidx]; + } + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的第一个线程,将每段处理的第一个元素写入中间结果数组。第一个线程 + // 块不进行处理。 + if (threadIdx.x == 0 && + blockIdx.x > 0) { + blocksum[blockIdx.x - 1] = outarray[idx]; + } + } +} + +// Kernel 函数: _scanOptKer(数组扫描的优化版本) +template < class T, class Operation > +__global__ void _scanOptKer(T *outarray, T *inarray, + T *blocksum, int n, Operation op, bool backward) +{ + // 声明共享内存。 + extern __shared__ unsigned char sharedmemo[]; + // 转化为模板类型的共享内存。 + T *sharedmem = (T *)sharedmemo; + + // 定义块内索引。 + int baseidx = 2 * blockIdx.x * blockDim.x; + + // 定义核函数每块的处理数组长度。 + int length = blockDim.x * 2; + + // 定义计算位置的索引(块内)。 + int ai = threadIdx.x; + int bi = threadIdx.x + blockDim.x; + + // 定义数组索引(块外)。 + int aindex = baseidx + ai; + int bindex = baseidx + bi; + + // 将需要计算的值从输入加载到共享内存上。每个线程处理两个元素。 + sharedmem[ai] = (aindex < n) ? inarray[aindex] : op.identity(); + sharedmem[bi] = (bindex < n) ? inarray[bindex] : op.identity(); + + // 定义偏移值 offset。 + int offset = 1; + + if (!backward) { + // scan 的累加过程,自底向上进行累加操作。 + for (int d = blockDim.x; d > 0; d >>= 1) { + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行累加。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * (2 * threadIdx.x + 1) - 1; + int bi = offset * (2 * threadIdx.x + 2) - 1; + + // 累加。通过这样的过程使得最终最后一位的值是之前所有值 scan 操 + // 作的结果。 + sharedmem[bi] = op(sharedmem[bi], sharedmem[ai]); + } + + // 偏移量每次扩大 2 倍。 + offset *= 2; + } + + // 配合自顶向下的回扫过程,清除最后一个位置上的值。 + if (threadIdx.x == 0) { + int index = length - 1; + // 根据运算符类型,数组最后一个位置设为不同的单位元 + sharedmem[index] = op.identity(); + } + + // 自顶向下回扫,这样能够使每一位上算出需要的 scan 值。 + // 回扫即把上一步的计算结果进行累加。 + for (int d = 1; d < length; d *= 2) { + // 回扫过程中,每次偏移量缩小2倍。 + offset /= 2; + + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行回扫累加的过程。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * (2 * threadIdx.x + 1) - 1; + int bi = offset * (2 * threadIdx.x + 2) - 1; + + // 将 ai 位置处的值拷贝出来加到 bi 处,ai 的新值为 bi 原值。 + T t = sharedmem[ai]; + sharedmem[ai] = sharedmem[bi]; + sharedmem[bi] = op(sharedmem[bi], t); + } + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (aindex >= n) + return; + // 将结果从共享内存写入到输出。 + outarray[aindex] = sharedmem[ai + 1]; + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (bindex >= n) + return; + // 判断是否为当前块处理数组的最后一个元素 + if (bi == (2 * blockDim.x - 1)) + // 是最后一个元素的话,需要与对应输入数组位置上的元素进行运算 + outarray[bindex] = op(sharedmem[bi], inarray[bindex]); + else + // 每个线程处理的下一个元素,将结果从共享内存写入到输出。 + outarray[bindex] = sharedmem[bi + 1]; + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的最后一个线程,将每段处理的最后一个元素写入中间结果数组。最后一个线 + // 程块不进行处理。 + if (threadIdx.x == blockDim.x - 1 && + blockIdx.x < gridDim.x - 1) { + blocksum[blockIdx.x] = outarray[bindex]; + } + } else { + // scan 的累加过程,自底向上进行累加操作。 + for (int d = blockDim.x; d > 0; d >>= 1) { + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行累加。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * 2 * threadIdx.x; + int bi = offset * (2 * threadIdx.x + 1); + + // 累加。通过这样的过程使得最终第一位的值是之前所有值 scan 操 + // 作的结果。 + sharedmem[ai] = op(sharedmem[bi], sharedmem[ai]); + } + + // 偏移量每次扩大 2 倍。 + offset *= 2; + } + + // 配合自顶向下的回扫过程,清除第一个位置上的值。 + if (threadIdx.x == 0) { + int index = 0; + // 根据运算符类型,数组第一个位置设为不同的单位元 + sharedmem[index] = op.identity(); + } + + // 自顶向下回扫,这样能够使每一位上算出需要的 scan 值。 + // 回扫即把上一步的计算结果进行累加。 + for (int d = 1; d < length; d *= 2) { + // 回扫过程中,每次偏移量缩小2倍。 + offset /= 2; + + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行回扫累加的过程。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ai = offset * 2 * threadIdx.x; + int bi = offset * (2 * threadIdx.x + 1); + + // 将 bi 位置处的值拷贝出来加到 ai 处,bi 的新值为 ai 原值。 + T t = sharedmem[bi]; + sharedmem[bi] = sharedmem[ai]; + sharedmem[ai] = op(sharedmem[ai], t); + } + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (aindex >= n) + return; + // 判断是否为当前块处理数组的第一个元素 + if (ai == 0) + // 是第一个元素的话,需要与对应输入数组位置上的元素进行运算 + outarray[aindex] = op(sharedmem[ai], inarray[aindex]); + else + // 将结果从共享内存写入到输出。 + outarray[aindex] = sharedmem[ai - 1]; + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (bindex < n) { + // 每个线程处理的下一个元素,将结果从共享内存写入到输出。 + outarray[bindex] = sharedmem[bi - 1]; + } + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的第一个线程,将每段处理的第一个元素写入中间结果数组。第一个线 + // 程块不进行处理。 + if (threadIdx.x == 0 && + blockIdx.x > 0) { + blocksum[blockIdx.x - 1] = outarray[aindex]; + } + } +} + +// 宏:CONFLICT_FREE_OFFSET +// 定义此是为了更严格的避免 bank conflicts,即使在树的低层上。 +#ifdef ZERO_BANK_CONFLICTS +# define CONFLICT_FREE_OFFSET(index) \ + (((index) >> LOG_NUM_BANKS) + ((index) >> (2 * LOG_NUM_BANKS))) +#else +# define CONFLICT_FREE_OFFSET(index) ((index) >> LOG_NUM_BANKS) +#endif + +// Kernel 函数: _scanBetterKer(数组扫描的Better版本) +template < class T, class Operation > +__global__ void _scanBetterKer(T *outarray, T *inarray, + T *blocksum, int n, Operation op, bool backward) +{ + // 声明共享内存。 + extern __shared__ unsigned char sharedmemo[]; + // 转化为模板类型的共享内存。 + T *sharedmem = (T *)sharedmemo; + + // 本地变量,基索引。 + int baseidx = 2 * blockIdx.x * blockDim.x; + int idx = threadIdx.x + blockDim.x; + + // 定义核函数每块的处理数组长度。 + int length = blockDim.x * 2; + + // 定义计算位置的索引(块内,加上 bankOffset)。 + int ai = threadIdx.x + CONFLICT_FREE_OFFSET(threadIdx.x); + int bi = idx + CONFLICT_FREE_OFFSET(idx); + + // 定义数组索引(块外)。 + int aindex = baseidx + threadIdx.x; + int bindex = aindex + blockDim.x; + + // 将需要计算的值从输入加载到共享内存上。每个线程处理两个元素。 + sharedmem[ai] = (aindex < n) ? inarray[aindex] : op.identity(); + sharedmem[bi] = (bindex < n) ? inarray[bindex] : op.identity(); + + // 定义偏移值 offset。 + int offset = 1; + + if (!backward) { + // scan 的累加过程,自底向上进行累加操作。 + for (int d = blockDim.x; d > 0; d >>= 1) { + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行累加。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ci = offset * (2 * threadIdx.x + 1) - 1; + int di = offset * (2 * threadIdx.x + 2) - 1; + + // 避免 bank conflicts,修改计算位置索引。 + ci += CONFLICT_FREE_OFFSET(ci); + di += CONFLICT_FREE_OFFSET(di); + + // 累加。通过这样的过程使得最终最后一位的值是之前所有值 scan 操 + // 作的结果。 + sharedmem[di] = op(sharedmem[di], sharedmem[ci]); + } + + // 偏移量每次扩大 2 倍。 + offset *= 2; + } + + // 配合自顶向下的回扫过程,清除最后一个位置上的值。 + if (threadIdx.x == 0) { + int index = length - 1; + + // 避免 bank conflicts,重新计算索引。 + index += CONFLICT_FREE_OFFSET(index); + + // 根据运算符类型,数组最后一个位置设为不同的单位元。 + sharedmem[index] = op.identity(); + } + + // 自顶向下回扫,这样能够使每一位上算出需要的 scan 值。 + // 回扫即把上一步的计算结果进行累加。 + for (int d = 1; d < length; d *= 2) { + // 回扫过程中,每次偏移量缩小2倍。 + offset /= 2; + + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行回扫累加的过程。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ci = offset * (2 * threadIdx.x + 1) - 1; + int di = offset * (2 * threadIdx.x + 2) - 1; + + // 避免 bank conflicts,重新计算索引。 + ci += CONFLICT_FREE_OFFSET(ci); + di += CONFLICT_FREE_OFFSET(di); + + // 将 ai 位置处的值拷贝出来加到 bi 处,ai 的新值为 bi 原值。 + T t = sharedmem[ci]; + sharedmem[ci] = sharedmem[di]; + sharedmem[di] = op(sharedmem[di], t); + } + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (aindex >= n) + return; + // 将结果从共享内存写入到输出。 + outarray[aindex] = op(sharedmem[ai], inarray[aindex]); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (bindex >= n) + return; + // 每个线程处理的下一个元素,将结果从共享内存写入到输出。 + outarray[bindex] = op(sharedmem[bi], inarray[bindex]); + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + // 每块的最后一个线程,将每段处理的最后一个元素写入中间结果数组。最后一个线 + // 程块不进行处理。 + if (threadIdx.x == blockDim.x - 1 && + blockIdx.x < gridDim.x - 1) { + blocksum[blockIdx.x] = outarray[bindex]; + } + } else { + // scan 的累加过程,自底向上进行累加操作。 + for (int d = blockDim.x; d > 0; d >>= 1) { + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行累加。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ci = offset * 2 * threadIdx.x; + int di = offset * (2 * threadIdx.x + 1); + + // 避免 bank conflicts,修改计算位置索引。 + ci += CONFLICT_FREE_OFFSET(ci); + di += CONFLICT_FREE_OFFSET(di); + + // 累加。通过这样的过程使得最终第一位的值是之前所有值 scan 操 + // 作的结果。 + sharedmem[ci] = op(sharedmem[di], sharedmem[ci]); + } + + // 偏移量每次扩大 2 倍。 + offset *= 2; + } + + // 配合自顶向下的回扫过程,清除第一个位置上的值。 + if (threadIdx.x == 0) { + int index = 0; + + // 避免 bank conflicts,重新计算索引。 + index += CONFLICT_FREE_OFFSET(index); + + // 根据运算符类型,数组第一个位置设为不同的单位元。 + sharedmem[index] = op.identity(); + } + + // 自顶向下回扫,这样能够使每一位上算出需要的 scan 值。 + // 回扫即把上一步的计算结果进行累加。 + for (int d = 1; d < length; d *= 2) { + // 回扫过程中,每次偏移量缩小2倍。 + offset /= 2; + + // 进行块内同步。 + __syncthreads(); + + // 当线程索引小于处理的范围 d,进行回扫累加的过程。 + if (threadIdx.x < d) { + // 计算处理位置的索引。 + int ci = offset * 2 * threadIdx.x; + int di = offset * (2 * threadIdx.x + 1); + + // 避免 bank conflicts,重新计算索引。 + ci += CONFLICT_FREE_OFFSET(ci); + di += CONFLICT_FREE_OFFSET(di); + + // 将 di 位置处的值拷贝出来加到 ci 处,di 的新值为 ci 原值。 + T t = sharedmem[di]; + sharedmem[di] = sharedmem[ci]; + sharedmem[ci] = op(sharedmem[ci], t); + } + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (aindex >= n) + return; + // 处理的第一个元素,需要与对应输入数组位置上的元素进行运算 + outarray[aindex] = op(sharedmem[ai], inarray[aindex]); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (bindex < n) { + // 每个线程处理的下一个元素,将结果从共享内存写入到输出。 + outarray[bindex] = op(sharedmem[bi], inarray[bindex]); + } + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blocksum == NULL) + return; + + // 每块的第一个线程,将每段处理的第一个元素写入中间结果数组。第一个线 + // 程块不进行处理。 + if (threadIdx.x == 0 && + blockIdx.x > 0) { + blocksum[blockIdx.x - 1] = outarray[baseidx]; + } + + } +} + +// 函数:scanComputeGold(CPU 端的 inclusive 类型数组计算) +template < class T, class Operation > +__host__ int scanComputeGold(T *inarray, T *reference, + const unsigned int len, Operation op, + bool backward) +{ + // 计数器变量 + int i; + + if (!backward) { + // 初始化第一个输出元素为 inarray[0] + reference[0] = inarray[0]; + for (i = 1; i < len; ++i) { + // 前序迭代累加计算 + reference[i] = op(inarray[i], reference[i-1]); + } + } else { + // 初始化最后一个输出元素为 inarray[len - 1] + reference[len - 1] = inarray[len - 1]; + for (i = len - 2; i >= 0; i--) { + // 后序迭代累加计算 + reference[i] = op(inarray[i], reference[i+1]); + } + } + + // 处理完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _addBackKer(中间结果数组加回操作) +template < class T, class Operation > +__global__ void _addBackKer(T *array, T *lastelemarray, int n, + int packnum, Operation op, bool backward) +{ + // 声明共享内存。用来存放中间结果小数组中的元素,也就是原数组的每段最后 + // 一个或第一个元素。 + __shared__ T lastelement[1]; + + if (!backward) { + // 用每块的第一个线程来读取每块前一块的最后一个元素,从中间结果数组中读 + // 取。 + if (threadIdx.x == 0) + lastelement[0] = lastelemarray[blockIdx.x]; + + // 计算需要进行块间累加位置索引(块外的数组索引)。 + unsigned int idx = (blockIdx.x + 1) * (blockDim.x * packnum) + + threadIdx.x; + + // 块内同步。 + __syncthreads(); + + // 每个线程处理两个元素,将中间结果数组中的值加回到原数组。 + for (int i = 0; i < packnum; i++) { + // 如果索引大于处理数组长度,则退出。 + if (idx >= n) + break; + + // 将中间结果加回。 + array[idx] = op(array[idx],lastelement[0]); + + // 计算每个线程处理的下一个元素的索引值。 + idx += blockDim.x; + } + } else { + // 用每块的第一个线程来读取每块后一块的第一个元素,从中间结果数组中读 + // 取。 + if (threadIdx.x == 0) { + lastelement[0] = lastelemarray[blockIdx.x]; + } + + // 计算需要进行块间累加位置索引(块外的数组索引)。 + unsigned int idx = blockIdx.x * (blockDim.x * packnum) + threadIdx.x; + + // 块内同步。 + __syncthreads(); + + // 每个线程处理两个元素,将中间结果数组中的值加回到原数组。 + for (int i = 0; i < packnum; i++) { + // 如果索引大于处理数组长度,则退出。 + if (idx >= n) + break; + + // 将中间结果加回。 + array[idx] = op(array[idx], lastelement[0]); + + // 计算每个线程处理的下一个元素的索引值。 + idx += blockDim.x; + } + } +} + +// Host 成员方法:addBack(float 型的中间结果加回) +template< class Operation > +__host__ int ScanArray::addBack(float *array, float *lastelemarray, + int numelements, int blocksize, int packnum, + Operation op, bool backward) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (array == NULL || lastelemarray == NULL) + return NULL_POINTER; + + // 检查处理的数组长度,如果小于 0 出错。 + if (numelements < 0) + return INVALID_DATA; + + // 计算线程块大小。 + int gridsize = (numelements + blocksize * packnum - 1) / + (blocksize * packnum) - 1; + + // 判断 gridsize 大小,如果小于 1,则不用进行加回操作。返回正确。 + if (gridsize < 1) + return NO_ERROR; + + // 调用 _addBackKer 核函数,将中间结果数组加回到原扫描数组。 + _addBackKer<<>>(array, lastelemarray, + numelements, packnum, op, backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:addBack(int 型的中间结果加回) +template< class Operation > +__host__ int ScanArray::addBack(int *array, int *lastelemarray, + int numelements, int blocksize, int packnum, + Operation op, bool backward) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (array == NULL || lastelemarray == NULL) + return NULL_POINTER; + + // 检查处理的数组长度,如果小于 0 出错。 + if (numelements < 0) + return INVALID_DATA; + + // 计算线程块大小。 + int gridsize = (numelements + blocksize * packnum - 1) / + (blocksize * packnum) - 1; + + // 判断 gridsize 大小,如果小于 1,则不用进行加回操作。返回正确。 + if (gridsize < 1) + return NO_ERROR; + + // 调用 _addBackKer 核函数,将中间结果数组加回到原扫描数组。 + _addBackKer<<>>(array, lastelemarray, + numelements, packnum, op, backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 宏:SCANFREE +// 如果出错,就释放之前申请的内存。 +#define SCANFREE do { \ + if (gridsize > 1) \ + cudaFree(blocksumDev); \ + if (hostinarray) \ + cudaFree(inarrayDev); \ + if (hostoutarray) \ + cudaFree(outarrayDev); \ + if (!hostinarray) \ + delete inarrayHost; \ + if (!hostoutarray) \ + delete outarrayHost; \ + } while (0) + +// Host 成员方法:scanArray(float 型的数组扫描) +template< class Operation > +__host__ int ScanArray::scanArray(float *inarray, float *outarray, + int numelements, Operation op, bool backward, + bool hostinarray, bool hostoutarray) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (inarray == NULL || outarray == NULL) + return NULL_POINTER; + + // 本程序实现的 4 种方法可处理的数组长度。必须加以判断控制。 + if (numelements < 0) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 局部变量。 + const unsigned int memsize = sizeof (float) * numelements; + unsigned int extraspace; + + // 计算共享内存的长度。 + unsigned int sharedmemsize = 0; + + // 定义设备端的输入输出数组指针,当输入输出指针在 Host 端时,在设备端申请对 + // 应大小的数组。 + float *inarrayDev = NULL; + float *outarrayDev = NULL; + + // 定义主机端的输入输出数组指针,当输入输出指针在 Device 端时,在主机端申请 + // 对应大小的数组。 + float *inarrayHost = NULL; + float *outarrayHost = NULL; + + // 这里 scan 实现只支持单个线程块的计算,这里的 gridsize 可以设置的大于 1, + // 从而让多个线程块都运行相同程序来测速。计算调用 Kernel 函数的线程块的尺寸 + // 和线程块的数量。 + int gridsize; + int blocksize; + + // 局部变量,中间结果存放数组。长度会根据线程块大小来确定。 + float *blocksumDev = NULL; + + // 中间结果数组的长度。 + int blocksumsize; + + // scan 算法中每个线程块的计算能力。核函数每块处理长度与线程块大小的比值。 + int packnum; + + // 针对 CPU 端的实现类型,选择路径进行处理。 + if (scanType == CPU_IN_SCAN) { + // 判断当前 inarray 数组是否存储在 Host 端。若不是,则需要在 Host 端 + // 为数组申请一段空间;若该数组是在 Host 端,则直接使用。 + if (!hostinarray) { + // 为输入数组在 Host 端申请内存。 + inarrayHost = new float[memsize]; + // 将输入数组拷贝到主机端内存。 + cuerrcode = cudaMemcpy(inarrayHost, inarray, memsize, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在主机端,则将指针传给主机端指针。 + inarrayHost = inarray; + } + + // 判断当前 outarray 数组是否存储在 Host 端。若不是,则需要在 Host 端 + // 为数组申请一段空间;若该数组是在 Host 端,则直接使用。 + if (!hostoutarray) { + // 为输出数组在 Host 端申请内存。 + outarrayHost = new float[memsize]; + // 将输出数组拷贝到主机端内存。 + cuerrcode = cudaMemcpy(outarrayHost, outarray, memsize, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在主机端,则将指针传给主机端指针。 + outarrayHost = outarray; + } + + + // 调用 inclusive 版的 scan 函数 + errcode = scanComputeGold(inarrayHost, outarrayHost, + numelements, op, backward); + // 出错则返回错误码。 + if (errcode != NO_ERROR) { + // 释放内存 + SCANFREE; + return errcode; + } + + // 执行结束 + return NO_ERROR; + } + + // 判断当前 inarray 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device端,则直接使用。 + if (hostinarray) { + // 为输入数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&inarrayDev, memsize); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + // 将输入数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(inarrayDev, inarray, memsize, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + inarrayDev = inarray; + } + + // 判断当前 outarray 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device端,则直接使用。 + if (hostoutarray) { + // 为输出数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&outarrayDev, memsize); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + outarrayDev = outarray; + } + + // 针对不同的实现类型,选择不同的路径进行处理。 + switch(scanType) { + // 使用简单版本的 scan 实现。 + case NAIVE_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 简单版本每个线程处理一个元素。 + blocksize = BLOCK_SIZE; + packnum = 1; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + blocksize * packnum - 1) / blocksize); + sharedmemsize = sizeof (float) * blocksize * packnum; + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(float)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanNaiveKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 使用效率版本的 scan 实现。 + case EFFICIENT_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 效率版本每个线程处理两个元素。 + blocksize = BLOCK_SIZE; + packnum = 2; + + // 计算线程块大小和共享内存长度。 + sharedmemsize = sizeof (float) * (blocksize * packnum); + gridsize = max(1, (numelements + packnum * blocksize - 1) / + (packnum * blocksize)); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(float)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanWorkEfficientKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 使用优化版本的 scan 实现。 + case OPTIMIZE_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 优化版本每个线程处理两个元素。 + blocksize = BLOCK_SIZE; + packnum = 2; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + packnum * blocksize - 1) / + (packnum * blocksize)); + sharedmemsize = sizeof (float) * (blocksize * packnum); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(float)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanOptKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 使用优化版本的 scan 实现。 + case BETTER_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 优化版本每个线程处理两个元素。 + blocksize = BLOCK_SIZE; + packnum = 2; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + packnum * blocksize - 1) / + (packnum * blocksize)); + extraspace = blocksize * packnum / NUM_BANKS;; + sharedmemsize = sizeof (float) * (blocksize * packnum + extraspace); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(float)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanBetterKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 其他方式情况下,直接返回非法数据错误。 + default: + if (hostinarray) + cudaFree(inarrayDev); + if (hostoutarray) + cudaFree(outarrayDev); + return INVALID_DATA; + } + + + // 如果处理的 gird 尺寸大于 1,那么对于扫描中间结果数组进行一次 scan + // 操作和加回操作。 + if (gridsize > 1) { + // 递归调用扫描函数。此时输入输出数组皆为中间结果数组。 + // 这里的递归调用不会调用多次,数组的规模是指数倍减小的。 + errcode = scanArray(blocksumDev, blocksumDev, blocksumsize, op, + backward, false, false); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + SCANFREE; + return errcode; + } + + // 调用加回函数,将各块的扫描中间结果加回到输出数组。 + errcode = addBack(outarrayDev, blocksumDev, numelements, + blocksize, packnum, op, backward); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + SCANFREE; + return errcode; + } + + // 释放中间结果数组的设备端内存。 + cudaFree(blocksumDev); + } + + // 如果 outarray 在 Host 端,将结果拷贝到输出。 + if (hostoutarray) { + // 将结果从设备端内存拷贝到输出数组。 + cuerrcode = cudaMemcpy(outarray, outarrayDev, memsize, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + if (hostinarray) + cudaFree(inarrayDev); + cudaFree(outarrayDev); + return cuerrcode; + } + } + + // 释放 Device 内存。需要判断输入输出参数是否在 host 端。 + if (hostinarray) + cudaFree(inarrayDev); + if (hostoutarray) + cudaFree(outarrayDev); + + // 处理完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:scanArray(int 型的数组扫描) +template< class Operation > +__host__ int ScanArray::scanArray(int *inarray, int *outarray, + int numelements, Operation op, bool backward, + bool hostinarray, bool hostoutarray) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (inarray == NULL || outarray == NULL) + return NULL_POINTER; + + // 本程序实现的 4 种方法可处理的数组长度。必须加以判断控制。 + if (numelements < 0) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 局部变量。 + int memsize = sizeof (int) * numelements; + int extraspace; + + // 计算共享内存的长度。 + int sharedmemsize = 0; + + // 定义设备端的输入输出数组指针,当输入输出指针在 Host 端时,在设备端申请对 + // 应大小的数组。 + int *inarrayDev = NULL; + int *outarrayDev = NULL; + + // 定义主机端的输入输出数组指针,当输入输出指针在 Device 端时,在主机端申请 + // 对应大小的数组。 + int *inarrayHost = NULL; + int *outarrayHost = NULL; + + // 这里 scan 实现只支持单个线程块的计算,这里的 gridsize 可以设置的大于 1, + // 从而让多个线程块都运行相同程序来测速。计算调用 Kernel 函数的线程块的尺寸 + // 和线程块的数量。 + int gridsize; + int blocksize; + + // 局部变量,中间结果存放数组。长度会根据线程块大小来确定。 + int *blocksumDev = NULL; + + // 中间结果数组的长度。 + int blocksumsize; + + // scan 算法中每个线程块的计算能力。核函数每块处理长度与线程块大小的比值。 + int packnum; + + // 针对 CPU 端实现类型,选择路径进行处理。 + if (scanType == CPU_IN_SCAN) { + // 判断当前 inarray 数组是否存储在 Host 端。若不是,则需要在 Host 端 + // 为数组申请一段空间;若该数组是在 Host 端,则直接使用。 + if (!hostinarray) { + // 为输入数组在 Host 端申请内存。 + inarrayHost = new int[memsize]; + // 将输入数组拷贝到主机端内存。 + cuerrcode = cudaMemcpy(inarrayHost, inarray, memsize, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在主机端,则将指针传给主机端指针。 + inarrayHost = inarray; + } + + // 判断当前 outarray 数组是否存储在 Host 端。若不是,则需要在 Host 端 + // 为数组申请一段空间;若该数组是在 Host 端,则直接使用。 + if (!hostoutarray) { + // 为输出数组在 Host 端申请内存。 + outarrayHost = new int[memsize]; + // 将输出数组拷贝到主机端内存。 + cuerrcode = cudaMemcpy(outarrayHost, outarray, memsize, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在主机端,则将指针传给主机端指针。 + outarrayHost = outarray; + } + + // 调用 inclusive 版的 scan 函数 + errcode = scanComputeGold(inarrayHost, outarrayHost, + numelements, op, backward); + // 出错则返回错误码。 + if (errcode != NO_ERROR) { + // 释放内存 + SCANFREE; + return errcode; + } + + // 执行结束 + return NO_ERROR; + } + + // 判断当前 inarray 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device端,则直接使用。 + if (hostinarray) { + // 为输入数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&inarrayDev, memsize); + if (cuerrcode != cudaSuccess) + return cuerrcode; + + // 将输入数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(inarrayDev, inarray, memsize, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + inarrayDev = inarray; + } + + // 判断当前 outarray 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device端,则直接使用。 + if (hostoutarray) { + // 为输出数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&outarrayDev, memsize); + if (cuerrcode != cudaSuccess) + return cuerrcode; + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + outarrayDev = outarray; + } + + // 针对不同的实现类型,选择不同的路径进行处理。 + switch(scanType) { + // 使用简单版本的 scan 实现。 + case NAIVE_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 简单版本每个线程处理一个元素。 + blocksize = BLOCK_SIZE; + packnum = 1; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + blocksize * packnum - 1) / blocksize); + sharedmemsize = sizeof (int) * blocksize * packnum; + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(int)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanNaiveKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 使用效率版本的 scan 实现。 + case EFFICIENT_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 效率版本每个线程处理两个元素。 + blocksize = BLOCK_SIZE; + packnum = 2; + + // 计算线程块大小和共享内存长度。 + sharedmemsize = sizeof (int) * (blocksize * packnum); + gridsize = max(1, (numelements + packnum * blocksize - 1) / + (packnum * blocksize)); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(int)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanWorkEfficientKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 使用优化版本的 scan 实现。 + case OPTIMIZE_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 优化版本每个线程处理两个元素。 + blocksize = BLOCK_SIZE; + packnum = 2; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + packnum * blocksize - 1) / + (packnum * blocksize)); + sharedmemsize = sizeof (int) * (blocksize * packnum); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(int)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanOptKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 使用优化版本的 scan 实现。 + case BETTER_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 优化版本每个线程处理两个元素。 + blocksize = BLOCK_SIZE; + packnum = 2; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + packnum * blocksize - 1) / + (packnum * blocksize)); + extraspace = blocksize * packnum / NUM_BANKS;; + sharedmemsize = sizeof (int) * (blocksize * packnum + extraspace); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每段处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocksumDev, + blocksumsize * sizeof(int)); + if (cuerrcode != cudaSuccess) { + cudaFree(blocksumDev); + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的数组扫描。 + // 这里需要判断输入输出指针是否在设备端。 + _scanBetterKer<<>>( + outarrayDev, inarrayDev, blocksumDev, numelements, op, + backward); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + SCANFREE; + return CUDA_ERROR; + } + break; + + // 其他方式情况下,直接返回非法数据错误。 + default: + if (hostinarray) + cudaFree(inarrayDev); + if (hostoutarray) + cudaFree(outarrayDev); + return INVALID_DATA; + } + + + // 如果处理的 gird 尺寸大于 1,那么对于扫描中间结果数组进行一次 scan + // 操作和加回操作。 + if (gridsize > 1) { + // 递归调用扫描函数。此时输入输出数组皆为中间结果数组。 + // 这里的递归调用不会调用多次,数组的规模是指数倍减小的。 + errcode = scanArray(blocksumDev, blocksumDev, blocksumsize, op, + backward, false, false); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + SCANFREE; + return errcode; + } + + // 调用加回函数,将各块的扫描中间结果加回到输出数组。 + errcode = addBack(outarrayDev, blocksumDev, numelements, + blocksize, packnum, op, backward); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + SCANFREE; + return errcode; + } + + // 释放中间结果数组的设备端内存。 + cudaFree(blocksumDev); + } + + // 如果 outarray 在 Host 端,将结果拷贝到输出。 + if (hostoutarray) { + // 将结果从设备端内存拷贝到输出数组。 + cuerrcode = cudaMemcpy(outarray, outarrayDev, memsize, + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + if (hostinarray) + cudaFree(inarrayDev); + cudaFree(outarrayDev); + return cuerrcode; + } + } + + // 释放 Device 内存。需要判断输入输出参数是否在 host 端。 + if (hostinarray) + cudaFree(inarrayDev); + if (hostoutarray) + cudaFree(outarrayDev); + + // 处理完毕退出。 + return NO_ERROR; +} + +// 函数:为了使模板运算能够连接通过,通过此函数预处理,将用到模板的函数实例化。 +void example() +{ + // 定义 ScanArray 对象 + ScanArray s; + // 数组长度 + unsigned int num_elements = 1024; + // 开辟空间的大小 + const unsigned int mem_size = sizeof(float) * num_elements; + const unsigned int mem_size1 = sizeof(int) * num_elements; + // 为 float 型输入输出指针开辟空间 + float *inarray = new float[mem_size]; + float *outarray = new float[mem_size]; + // 为 int 型输入输出指针开辟空间 + int *inarray1 = new int[mem_size1]; + int *outarray1 = new int[mem_size1]; + // 设置扫描类型为 NAIVE_SCAN + s.setScanType(NAIVE_SCAN); + // 默认输入输出数组均在 host 端 + bool inhost, outhost; + inhost = true; + outhost = true; + // 新建加法和乘法运算对象 + add_class a; + multi_class m; + max_class max; + min_class min; + + add_class a1; + multi_class m1; + max_class max1; + min_class min1; + + // 用 float 型和 int 型分别调用加法和乘法的仿函数。 + s.scanArray(inarray, outarray, num_elements, a, false, inhost, outhost); + s.scanArray(inarray1, outarray1, num_elements, a1, false, inhost, outhost); + s.scanArray(inarray, outarray, num_elements, m, false, inhost, outhost); + s.scanArray(inarray1, outarray1, num_elements, m1, false, inhost, outhost); + s.scanArray(inarray, outarray, num_elements, max, false, inhost, outhost); + s.scanArray(inarray1, outarray1, num_elements, max1, false, inhost, outhost); + s.scanArray(inarray, outarray, num_elements, min, false, inhost, outhost); + s.scanArray(inarray1, outarray1, num_elements, min1, false, inhost, outhost); +} + +// 取消前面的宏定义。 +#undef SCANFREE + diff --git a/okano_3_0/ScanArray.h b/okano_3_0/ScanArray.h new file mode 100644 index 0000000..b260e61 --- /dev/null +++ b/okano_3_0/ScanArray.h @@ -0,0 +1,338 @@ +// ScanArray.h +// 创建人:刘瑶 +// +// 数组扫描(ScanArray) +// 功能说明:对一个数组进行扫描,对所有元素进行某操作的遍历。 +// 举例:数组 1, 4, 5, 7, 10 进行 exclusive scan 的结果是:0, 1, 5, 10, 17。 +// 参照 Mark Harris 的论文 “Parallel Prefix Sum (Scan) with CUDA” 进进行实现。 +// 声明只支持单个一维线程块规模的数组扫描,不支持多块扫描和非满块扫描(要求单个 +// 线程块必须为满块)。 +// +// 修订历史: +// 2012年11月24日(刘瑶) +// 初始版本 +// 2012年11月30日(刘瑶) +// 添加了完整的代码注释,进行了部分修改。 +// 2012年12月17日(刘瑶) +// 添加了算法对于多个线程块处理的实现,完成部分代码。 +// 2012年12月19日(刘瑶) +// 添加了扫描后的中间结果加回到扫描数组的功能,添加了部分注释。 +// 2012年12月24日(刘瑶) +// 修改了部分代码,规范了代码格式及注释。 +// 2012年01月05日(刘瑶) +// 修改数组扫描的成员方法,支持函数重载,支持 int 和 float 型的数组扫描。 +// 核函数采用模板,支持 int 和 float 型的数组扫描。修改了部分注释。 +// 2012年01月09日(刘瑶,王雪菲) +// 增加了 Scan 的 int 类型的 Exclusive 扫描,即不包含自身的扫描。 +// 2012年03月14日(刘瑶) +// 修正了 OPTIMIZE_SCAN 和 BETTER_SCAN 两种类型的扫描,之前调用 scanArray +// 却得到 scanArrayExclusive 的结果,调用 scanArrayExclusive 结果错位。现在 +// 更正了这两个问题。 +// 2012年03月14日(刘瑶) +// 再次修正了 OPTIMIZE_SCAN 和 BETTER_SCAN 两种类型的扫描,结合凸壳的扫描 +// 重新发现了这个错误。现在更正了这两个问题。 +// 2013年05月10日(王春峰) +// 增加了 CPUIN_SCAN 和 CPUEX_SCAN 两种类型的扫描,增加了 CPU 端校验的函数 +// 2013年05月11日(王春峰) +// 保留了 CPU_IN_SCAN,删除了 CPUEX_SCAN。scanArray 可以调用 包括 +// CPU_IN_SCAN 在内的五种 inclusive 类型,scanArrayExclusive 可以通过调用 +// scanArray 来实现。为 scanArrayExclusive 函数增加了模板类。修改了一些代码 +// 规范。 +// 2013年05月13日(王春峰) +// 增加了仿函数的的模板运算,支持加法和乘法的 scan 运算。修改了一些运算单位 +// 元的赋值。 +// 2013年05月24日(王春峰) +// 增加了仿函数的的最大值和最小值运算。增加了 Naive 版本和 workefficient 版 +// 本的后序扫描操作。 +// 2013年06月18日(王春峰) +// 增加了五种方法扫描的 backward 版本。相应的修改了 addback 核函数。修改了一 +// 些代码规范。 + +#ifndef __SCANARRAY_H__ +#define __SCANARRAY_H__ + +#include "ErrorCode.h" +#include "OperationFunctor.h" +#include +using namespace std; + +// 宏:NAIVE_SCAN +// 用于设置 ScanArray 类中 scanType 成员变量,告知类的实例选用简单版本的 +// ScanArray 实现。 +#define NAIVE_SCAN 1 + +// 宏:EFFICIENT_SCAN +// 用于设置 ScanArray 类中 scanType 成员变量,告知类的实例选用效率版本的 +// ScanArray 实现。 +#define EFFICIENT_SCAN 2 + +// 宏:OPTIMIZE_SCAN +// 用于设置 ScanArray 类中 scanType 成员变量,告知类的实例选用优化版本的 +// ScanArray 实现。 +#define OPTIMIZE_SCAN 3 + +// 宏:BETTER_SCAN +// 用于设置 ScanArray 类中 scanType 成员变量,告知类的实例选用较优版本的 +// ScanArray 实现。 +#define BETTER_SCAN 4 + +// 宏:CPU_IN_SCAN +// 用于设置 ScanArray 类中 scanType 成员变量,告知类的实例选用 CPU inclusive 版 +// 本的 ScanArray 实现。 +#define CPU_IN_SCAN 5 + +// 类:ScanArray +// 继承自:无 +// 对一个数组进行扫描,对所有元素进行某操作的遍历。根据论文中提到的三种方法,实 +// 现 4 种方法。1:简单版本的扫描实现,每个线程处理一个元素。2:效率版本的扫描实 +// 现,每个线程处理两个元素。3:优化版本的扫描实现,每个线程处理两个元素。 +// 4:消除 bank conflicts 版本的扫描实现,每个线程处理两个元素。这三种方法都支持 +// 多块任意长度的计算。 +class ScanArray { + +protected: + + // 成员变量:scanType(实现类型) + // 设定四种扫描方法实现类型中的一种,在调用 scan 扫描函数的时候,使用对应的 + // 实现方式。 + int scanType; + + // Host 成员方法:addBack(float 型的中间结果加回) + // 对一个数组进行扫描,将中间结果的小数组加回到扫描数组。 + template< class Operation > + __host__ int + addBack( + float *array, // 初始扫描后的数组,float 型。 + float *lastelemarray, // 中间结果数组,原扫描数组每段的最后一个元 + // 素提取出来即为中间结果数组,float 型。 + int numelements, // 扫描数组的长度。 + int blocksize, // 线程块的大小。 + int packnum, // 扫描核函数每块计算能力,即核函数每块的处 + // 理长度与线程块大小的比值。 + Operation op, // 运算类型 + bool backward // 判断是否为后序扫描 + ); + + // Host 成员方法:addBack(int 型的中间结果加回) + // 对一个数组进行扫描,将中间结果的小数组加回到扫描数组。 + template< class Operation > + __host__ int + addBack( + int *array, // 初始扫描后的数组,int 型 + int *lastelemarray, // 中间结果数组,原扫描数组每段的最后一个元 + // 素提取出来即为中间结果数组,int 型 + int numelements, // 扫描数组的长度。 + int blocksize, // 线程块的大小。 + int packnum, // 扫描核函数每块计算能力,即核函数每块的处 + // 理长度与线程块大小的比值。 + Operation op, // 运算类型 + bool backward // 判断是否为后序扫描 + ); + +public: + + // 构造函数:ScanArray + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + ScanArray() + { + // 使用默认值为类的各个成员变量赋值。 + this->scanType = NAIVE_SCAN; // 实现类型的默认值为 NAIVE_CAN, + // 优化扫描。 + } + + // 构造函数:ScanArray + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + ScanArray( + int scanType // 实现类型 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->scanType = NAIVE_SCAN; // 实现类型的默认值为 NAIVE_SCAN, + // 优化扫描。 + + // 根据参数列表中的值设定成员变量的初值 + this->setScanType(scanType); + } + + // 成员方法:getScanType(读取实现类型) + // 读取 scanType 成员变量的值。 + __host__ __device__ int // 返回值:当前 scanType 成员变量的值。 + getScanType() const + { + // 返回 ScanType 成员变量的值。 + return this->scanType; + } + + // 成员方法:setScanType(设置实现类型) + // 设置 scanType 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setScanType( + int scanType // 实现类型 + ) { + // 检查输入参数是否合法 + if (scanType != NAIVE_SCAN && scanType != EFFICIENT_SCAN && + scanType != OPTIMIZE_SCAN && scanType != BETTER_SCAN && + scanType != CPU_IN_SCAN) + return INVALID_DATA; + + // 将 scanType 成员变量赋成新值 + this->scanType = scanType; + return NO_ERROR; + } + + // Host 成员方法:scanArray(float 型的数组扫描) + // 对一个 float 型数组进行扫描,对所有元素进行某操作的遍历。 + template< class Operation > + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + scanArray( + float *inarray, // 输入数组,float 型。 + float *outarray, // 输出数组,float 型。 + int numelements, // 数组的长度,处理元素的个数。 + Operation op, // 运算类型 + bool backward, // 判断是否为后序扫描 + bool hostinarray = true, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostoutarray = true // 判断 outarray 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:scanArray(int 型的数组扫描) + // 对一个 int 型数组进行扫描,对所有元素进行某操作的遍历。 + template< class Operation > + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + scanArray( + int *inarray, // 输入数组,int 型 + int *outarray, // 输出数组,int 型 + int numelements, // 数组的长度,处理元素的个数。 + Operation op, // 运算类型 + bool backward, // 判断是否为后序扫描 + bool hostinarray = true, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostoutarray = true // 判断 outarray 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:scanArrayExclusive(Exclusive 类型数组扫描) + // 对一个数组进行扫描,对所有元素进行某操作的遍历,操作不包含自身。 + // 相当于在 Scan Inclusive 的版本上将输出后移一位,然后第一位赋值为 0。 + // 需要输出数组在申请时长度比输入数组加 1 个单位。用来存放第一个位置的 0。 + template < class T, class Operation > + inline __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + scanArrayExclusive( + T *inarray, // 输入数组 + T *outarray, // 输出数组 + int numelements, // 数组的长度,处理元素的个数。 + Operation op, // 运算类型 + bool backward = false, // 判断是否为后序扫描 + bool hostinarray = true, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostoutarray = true // 判断 outarray 是否是 Host 端的指针, + // 默认为“是”。 + ) { + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + if (!backward) { + // 调用 Scan Inclusive 的版本。输出数组传指针为第一个位置处开始。 + errcode = ScanArray::scanArray(inarray, (outarray + 1), + numelements, op, backward, + hostinarray, hostoutarray); + + // 判断当前 outarray 数组是否存储在 Host 端。若是,直接在 Host 端操作 + if (hostoutarray) { + // 首位赋值为 0。 + outarray[0] = op.identity(); + // 如果该数组是在 Device 端,则直接在 Device 端使用。 + } else { + // 首位赋值为 0。 + cuerrcode = cudaMemset(outarray, op.identity(), sizeof(T)); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + } + } else { + // 调用 Scan Inclusive 的版本。输入数组传指针为第一个位置处开始。 + errcode = ScanArray::scanArray(inarray + 1, outarray, + numelements, op, backward, + hostinarray, hostoutarray); + + // 判断当前 outarray 数组是否存储在 Host 端。若是,直接在 Host 端操作 + if (hostoutarray) { + // 末位赋值为 0。 + outarray[numelements - 1] = op.identity(); + // 如果该数组是在 Device 端,则直接在 Device 端使用。 + } else { + // 末位赋值为 0。 + cuerrcode = cudaMemset(outarray + numelements - 1, + op.identity(), sizeof(T)); + if (cuerrcode != cudaSuccess) + return CUDA_ERROR; + } + } + + // 出错则返回错误码。 + if (errcode != NO_ERROR) + return errcode; + + // 处理完毕退出。 + return NO_ERROR; + } + + // Host 成员方法:scanCorrectCheck(GPU 端的 scan 扫描数组结果检测) + // 对两个数组进行扫描,对所有元素依次进行比较,得出检测结果。 + template < class T, class Operation > + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 -1。若检测结果出错,返 + // 回错误位置。 + scanCorrectCheck( + T *inarray, // 输入数组 + T *array_g, // gpu 端计算数组 + T *array_c, // cpu 端计算数组 + const unsigned int len, // 数组的长度,处理元素的个数。 + Operation op, // 运算类型 + bool backward, // 判断是否为后序扫描 + bool hostinarray = true, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostoutarray = true, // 判断 outarray 是否是 Host 端的指针, + // 默认为“是”。 + bool isinclusive =true // 判断是否为 inclusive 版本 + ) { + // 设置实现类型为 CPU_IN_SCAN + this->setScanType(CPU_IN_SCAN); + + int errcode; + if (isinclusive) + // 调用 cpu 端计算 scan 数组的函数 + errcode = ScanArray::scanArray(inarray, array_c, len, op, backward, + hostinarray, hostoutarray); + else + // 调用 cpu 端计算 exclusive 版本的 scan 数组的函数 + errcode = ScanArray::scanArrayExclusive(inarray, array_c, len, op, + backward, hostinarray, + hostoutarray); + + // 出错则返回错误码。 + if (errcode != NO_ERROR) + return errcode; + + // 计数器变量 + unsigned int i; + // 两端的数组进行一一比对,得出检测结果。 + for (i = 0; i < len; ++i) { + if (array_g[i] != array_c[i]) + return i + 1; + } + // 执行结束 + return -1; + } +}; + +#endif + diff --git a/okano_3_0/Segmentation.cu b/okano_3_0/Segmentation.cu new file mode 100644 index 0000000..19047f4 --- /dev/null +++ b/okano_3_0/Segmentation.cu @@ -0,0 +1,739 @@ +// Segmentation.cu +// 实现二分类 + +#include "Segmentation.h" + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 256 +#define DEF_BLOCK_Y 1 + + +// 核函数:_countW1Ker(统计各个向量 W1 近旁的向量个数) +// 该方法首先计算当前向量与其他各个向量之间的三个距离度量(坐标的欧式距离, +// 特征值的欧式距离以及向量的夹角),如果在 W1 范围内,则将当前 index 的 +// count 个数加一,最终得到统计结果 +static __global__ void // Kernel 函数没有返回值 +_countW1Ker( + Segmentation segmentation, // 分割操作类 + FeatureVecArray infeaturevecarray, // 输入特征向量 + int *w1counts // 记录 W1 范围内的向量个数 +); + +// 核函数:_labelVectorsKer(标记各个向量的分类) +// 该函数根据传入的已标记的向量的 index ,比较各个向量如他们的距离如果在 +// 指定的 W2 范围内,则将该向量标记为同一类的。在启动这个核函数的时候 +// 横向的坐标表示各个待标记的向量,纵向的坐标表示各个已经标记的向量 +static __global__ void // Kernel 函数没有返回值 +_labelVectorsKer( + Segmentation segmentation, // 分割操作类 + FeatureVecArray infeaturevecarray, // 输入特征向量 + unsigned char *tmpbl, // 需要标记的值 + int *tmpvecs, // 当前已经标记的向量的 index 数组 + int tmpsize // 当前已经标记的向量的数组的大小 +); + +// 核函数:_countAppointW1Ker(统计指定向量 W1 近旁的向量个数) +// 该函数根据传入的标记值数组,选择性地计算未标记的向量和其它未标记向量之间的 +// 三个距离度量(坐标的欧式距离,特征值的欧式距离以及向量夹角),如果在 W1 +// 范围内,则将当前 index 的 cout 个数加一,最终得到统计结果 +static __global__ void // Kernel 函数没有返回值 +_countAppointW1Ker( + Segmentation segmentation, // 分割操作类 + FeatureVecArray infeaturevecarray, // 输入特征向量 + unsigned char *tmplbl, // 临时标记数组 + int *w1counts // 记录 W1 范围内的向量个数 +); + +// 核函数:_segregateKer(对向量进行最终的分割) +// 该核函数,根据之前初步的分类结果,对每一个向量,统计其 W2 范围内的 +// 类别1和类别2的向量的个数,根据二者个数来判定当前向量最终被划分到哪个类别中 +static __global__ void // Kernel 函数没有返回值 +_segregateKer( + Segmentation segmentation, // 分割操作类 + FeatureVecArray infeaturevecarray, // 输入特征向量 + unsigned char *tmp1lbl, // 类别1的临时标记数组 + unsigned char *tmp2lbl, // 类别2的临时标记数组 + int *outlabel // 用于输出的分类结果数组 +); + +// 核函数:_countW1Ker(统计各个向量 W1 近旁的向量个数) +static __global__ void _countW1Ker( + Segmentation segmentation, FeatureVecArray infeaturevecarray, + int *w1counts) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= infeaturevecarray.count) + return; + + int x1 = infeaturevecarray.x[index]; // 当前处理向量的横坐标 + int y1 = infeaturevecarray.y[index]; // 当前处理向量的纵坐标 + float cv1 = infeaturevecarray.CV[index]; // 当前处理向量的 CV + float sd1 = infeaturevecarray.SD[index]; // 当前处理向量的 SD + float nc1 = infeaturevecarray.NC[index]; // 当前处理向量的 NC + int count = 0; // 当前向量 W1 范围内的向量个数 + + // 统计当前向量 W1 范围内的向量个数 + for (int i = 0; i < infeaturevecarray.count; i++) { + int x2 = infeaturevecarray.x[i]; // 当前比较向量的横坐标 + int y2 = infeaturevecarray.y[i]; // 当前比较向量的纵坐标 + float cv2 = infeaturevecarray.CV[i]; // 当前比较向量的 CV + float sd2 = infeaturevecarray.SD[i]; // 当前比较向量的 SD + float nc2 = infeaturevecarray.NC[i]; // 当前比较向量的 NC + + // 计算两个向量之间坐标的欧式距离 + float d1 = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + + // 计算两个向量之间特征值的欧式距离 + float d2 = sqrt((cv2 - cv1) * (cv2 - cv1) + (sd2 -sd1) * (sd2 -sd1) + + (nc2 - nc1) * (nc2 - nc1)); + + // 计算两个向量之间夹角的 cos 值 + float d3 = (x1 * x2 + y1 * y2 + cv1 * cv2 + sd1 * sd2 + nc1 * nc2) / + sqrt(x1 * x1 + y1 * y1 + cv1 * cv1 + sd1 * sd1 + nc1 * nc1) / + sqrt(x2 * x2 + y2 * y2 + cv2 * cv2 + sd2 * sd2 + nc2 * nc2); + + // 判断两个向量之间的距离是否在 W1 范围内 + if (d1 < segmentation.getBw1().spaceWidth && + d2 < segmentation.getBw1().rangeWidth && + d3 < segmentation.getBw1().angleWidth) + count++; + } + + // 将统计值写入统计值数组 + w1counts[index] = count; +} + +// 核函数:_labelVectorsKer(标记各个向量的分类) +static __global__ void _labelVectorsKer( + Segmentation segmentation, FeatureVecArray infeaturevecarray, + unsigned char *tmplbl, int *tmpvecs, int tmpsize) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= infeaturevecarray.count) + return; + + // 计算当前需要寻找的已标记向量的 index + int index_y = blockIdx.y * blockDim.y + threadIdx.y; + + // 如果纵向坐标超过了 tmpsize则不做处理直接返回 + if (index_y >= tmpsize) + return; + + // 获取当前种子向量的 index + int seedindex = tmpvecs[index_y]; + + int x1 = infeaturevecarray.x[index]; // 当前处理向量的横坐标 + int y1 = infeaturevecarray.y[index]; // 当前处理向量的纵坐标 + float cv1 = infeaturevecarray.CV[index]; // 当前处理向量的 CV + float sd1 = infeaturevecarray.SD[index]; // 当前处理向量的 SD + float nc1 = infeaturevecarray.NC[index]; // 当前处理向量的 NC + + int x2 = infeaturevecarray.x[seedindex]; // 当前种子向量的横坐标 + int y2 = infeaturevecarray.y[seedindex]; // 当前种子向量的纵坐标 + float cv2 = infeaturevecarray.CV[seedindex]; // 当前种子向量的 CV + float sd2 = infeaturevecarray.SD[seedindex]; // 当前种子向量的 SD + float nc2 = infeaturevecarray.NC[seedindex]; // 当前种子向量的 NC + + // 计算两个向量之间坐标的欧式距离 + float d1 = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + + // 计算两个向量之间特征值的欧式距离 + float d2 = sqrt((cv2 - cv1) * (cv2 - cv1) + (sd2 -sd1) * (sd2 -sd1) + + (nc2 - nc1) * (nc2 - nc1)); + + // 计算两个向量之间夹角的 cos 值 + float d3 = (x1 * x2 + y1 * y2 + cv1 * cv2 + sd1 * sd2 + nc1 * nc2) / + sqrt(x1 * x1 + y1 * y1 + cv1 * cv1 + sd1 * sd1 + nc1 * nc1) / + sqrt(x2 * x2 + y2 * y2 + cv2 * cv2 + sd2 * sd2 + nc2 * nc2); + + // 判断两个向量之间的距离是否在 W2 范围内,如果是则标记该向量 + if (d1 < segmentation.getBw2().spaceWidth && + d2 < segmentation.getBw2().rangeWidth && + d3 < segmentation.getBw2().angleWidth) + tmplbl[index] = 1; +} + +// 核函数:_countAppointW1Ker(统计指定向量 W1 近旁的向量个数) +static __global__ void _countAppointW1Ker( + Segmentation segmentation, FeatureVecArray infeaturevecarray, + unsigned char *tmplbl, int *w1counts) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= infeaturevecarray.count) + return; + + // 将每个向量近旁个数初始化为0 + w1counts[index] = 0; + + // 如果当前 index 已经被标记,不做处理直接返回 + if (tmplbl[index] == 1) + return; + + int x1 = infeaturevecarray.x[index]; // 当前处理向量的横坐标 + int y1 = infeaturevecarray.y[index]; // 当前处理向量的纵坐标 + float cv1 = infeaturevecarray.CV[index]; // 当前处理向量的 CV + float sd1 = infeaturevecarray.SD[index]; // 当前处理向量的 SD + float nc1 = infeaturevecarray.NC[index]; // 当前处理向量的 NC + int count = 0; // 当前向量 W1 范围内的向量个数 + + // 统计当前向量 W1 范围内的向量个数 + for (int i = 0; i < infeaturevecarray.count; i++) { + // 忽略已经被标记的向量 + if (tmplbl[i] == 1) + continue; + + int x2 = infeaturevecarray.x[i]; // 当前比较向量的横坐标 + int y2 = infeaturevecarray.y[i]; // 当前比较向量的纵坐标 + float cv2 = infeaturevecarray.CV[i]; // 当前比较向量的 CV + float sd2 = infeaturevecarray.SD[i]; // 当前比较向量的 SD + float nc2 = infeaturevecarray.NC[i]; // 当前比较向量的 NC + + // 计算两个向量之间坐标的欧式距离 + float d1 = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + + // 计算两个向量之间特征值的欧式距离 + float d2 = sqrt((cv2 - cv1) * (cv2 - cv1) + (sd2 -sd1) * (sd2 -sd1) + + (nc2 - nc1) * (nc2 - nc1)); + + // 计算两个向量之间夹角的 cos 值 + float d3 = (x1 * x2 + y1 * y2 + cv1 * cv2 + sd1 * sd2 + nc1 * nc2) / + sqrt(x1 * x1 + y1 * y1 + cv1 * cv1 + sd1 * sd1 + nc1 * nc1) / + sqrt(x2 * x2 + y2 * y2 + cv2 * cv2 + sd2 * sd2 + nc2 * nc2); + + // 判断两个向量之间的距离是否在 W1 范围内 + if (d1 < segmentation.getBw1().spaceWidth && + d2 < segmentation.getBw1().rangeWidth && + d3 < segmentation.getBw1().angleWidth) + count++; + } + + // 将统计值写入统计值数组 + w1counts[index] = count; +} + +// 核函数:_segregateKer(对向量进行最终的分割) +static __global__ void _segregateKer( + Segmentation segmentation, FeatureVecArray infeaturevecarray, + unsigned char *tmp1lbl, unsigned char *tmp2lbl, int *outlabel) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= infeaturevecarray.count) + return; + + // 将每个向量近旁个数初始化为0 + outlabel[index] = 0; + + int x1 = infeaturevecarray.x[index]; // 当前处理向量的横坐标 + int y1 = infeaturevecarray.y[index]; // 当前处理向量的纵坐标 + float cv1 = infeaturevecarray.CV[index]; // 当前处理向量的 CV + float sd1 = infeaturevecarray.SD[index]; // 当前处理向量的 SD + float nc1 = infeaturevecarray.NC[index]; // 当前处理向量的 NC + long sprtcount1 = 0; // W2 范围内暂定1类别的向量个数 + long sprtcount2 = 0; // W2 范围内暂定2类别的向量个数 + float distsum1 = 0.0; // W2 范围内当前向量和暂定1类别 + // 的平方差之和 + float distsum2 = 0.0; // W2 范围内当前向量和暂定2类别 + // 的平方差之和 + + // 统计当前向量 W2 范围内的属于类别1和类别2的向量个数 + for (int i = 0; i < infeaturevecarray.count; i++) { + + int x2 = infeaturevecarray.x[i]; // 当前比较向量的横坐标 + int y2 = infeaturevecarray.y[i]; // 当前比较向量的纵坐标 + float cv2 = infeaturevecarray.CV[i]; // 当前比较向量的 CV + float sd2 = infeaturevecarray.SD[i]; // 当前比较向量的 SD + float nc2 = infeaturevecarray.NC[i]; // 当前比较向量的 NC + + // 计算两个向量之间坐标的欧式距离 + float d1 = sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + + // 计算两个向量之间特征值的欧式距离 + float d2 = sqrt((cv2 - cv1) * (cv2 - cv1) + (sd2 -sd1) * (sd2 -sd1) + + (nc2 - nc1) * (nc2 - nc1)); + + // 计算两个向量之间夹角的 cos 值 + float d3 = (x1 * x2 + y1 * y2 + cv1 * cv2 + sd1 * sd2 + nc1 * nc2) / + sqrt(x1 * x1 + y1 * y1 + cv1 * cv1 + sd1 * sd1 + nc1 * nc1) / + sqrt(x2 * x2 + y2 * y2 + cv2 * cv2 + sd2 * sd2 + nc2 * nc2); + + // 判断两个向量之间的距离是否在 W2 范围内 + if (d1 < segmentation.getBw2().spaceWidth && + d2 < segmentation.getBw2().rangeWidth && + d3 < segmentation.getBw2().angleWidth) { + if (tmp1lbl[i] == 1) { + sprtcount1++; + distsum1 += d2 * d2; + } + + if (tmp2lbl[i] == 1) { + sprtcount2++; + distsum2 += d2 * d2; + } + } + } + + // 如果当前向量周围只有类别1的向量,则当前向量最终标记为1 + if (sprtcount1 > 0 && sprtcount2 == 0) { + outlabel[index] = 1; + return; + } + + // 如果当前向量周围只有类别1的向量,则当前向量最终标记为1 + if (sprtcount2 > 0 && sprtcount1 == 0) { + outlabel[index] = 2; + return; + } + + // 当前向量周围类别1相对于类别2占优势地位,则当前向量最终标记为1 + if (sprtcount1 > segmentation.getBeta() * sprtcount2) { + outlabel[index] = 1; + return; + } + + // 当前向量周围类别2相对类别1占优势地位,则当前向量最终标记为2 + if (sprtcount2 > segmentation.getBeta() * sprtcount1) { + outlabel[index] = 2; + return; + } + + // 标记特征值平方差的值 + if (distsum1 / powf(sprtcount1, segmentation.getAlpha()) <= + distsum2 / powf(sprtcount2, segmentation.getAlpha())) + outlabel[index] = 1; + else + outlabel[index] = 2; +} + + +// 宏:FREE_LOCAL_MEMORY_SEGREGATE(清理局部申请的设备端或者主机端内存) +// 该宏用于清理在 segregate 过程中申请的设备端或者主机端内存空间 +#define FREE_LOCAL_MEMORY_SEGREGATE do { \ + if ((w1counts) != NULL) \ + delete [] (w1counts); \ + if ((w1countsdev) != NULL) \ + cudaFree((w1countsdev)); \ + if ((tmp1vecs) != NULL) \ + delete [] (tmp1vecs); \ + if ((tmp2vecs) != NULL) \ + delete [] (tmp2vecs); \ + if ((tmp1vecsdev) != NULL) \ + cudaFree((tmp1vecsdev)); \ + if ((tmp2vecsdev) != NULL) \ + cudaFree((tmp2vecsdev)); \ + if ((tmp1lbl) != NULL) \ + delete [] (tmp1lbl); \ + if ((tmp1lbldev) != NULL) \ + cudaFree((tmp1lbldev)); \ + if ((tmp2lbl) != NULL) \ + delete [] (tmp2lbl); \ + if ((tmp2lbldev) != NULL) \ + cudaFree((tmp2lbldev)); \ + if ((outlabeldev) != NULL) \ + cudaFree((outlabeldev)); \ +}while (0) + + +// Host 成员方法:segregate(图像分割) +__host__ int Segmentation::segregate(FeatureVecArray *featurevecarray, + int *outlabel) +{ + // 检查输入的参数是否为 NULL ,如果为 NULL 直接报错返回 + if (featurevecarray == NULL || outlabel == NULL) + return NULL_POINTER; + + // 检查输入的参数是否为合法,如果不合法直接报错返回 + if (featurevecarray->count <= 0) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + int *w1counts = NULL; // 每个向量 W1 范围内的向量个数 + int *w1countsdev = NULL; // w1counts 对应的设备端指针 + int count = featurevecarray->count; // 向量的个数 + + // 定义类别1和类别2的暂定数组,该数组存储暂时被标记为1和2的向量的 index + int *tmp1vecs = NULL; // 存储暂时被划分到类别1的向量的 index + int *tmp2vecs = NULL; // 存储暂时被划分到类别2的向量的 index + int *tmp1vecsdev = NULL; // 设备端存储暂时被划分到类别1的向量的 index + int *tmp2vecsdev = NULL; // 设备端存储暂时被划分到类别2的向量的 index + + // 定义暂定类别1和类别2的标记数组,该数组标记对应的 index 是否属于某类别 + unsigned char *tmp1lbl = NULL; // 主机端类别1标记数组 + unsigned char *tmp1lbldev = NULL; // 设备端类别1标记数组 + unsigned char *tmp2lbl = NULL; // 主机端类别2标记数组 + unsigned char *tmp2lbldev = NULL; // 设备端类别2标记数组 + + // 定义设备端最终标记数组 + int *outlabeldev = NULL; // 设备端最终标记数组 + + // 在主机端申请 W1 统计数组空间 + w1counts = new int[count]; + if (w1counts == NULL) + return UNKNOW_ERROR; + + // 在设备端申请 W1 统计数组空间 + errcode = cudaMalloc((void **)&w1countsdev, count * sizeof(int)); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (count + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + + // 调用核函数,统计各个向量 W1 范围内的向量个数 + _countW1Ker<<>>(*this, *featurevecarray, + w1countsdev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将统计结果拷回到 Host 端 + errcode = cudaMemcpy(w1counts, w1countsdev, + count * sizeof(int), + cudaMemcpyDeviceToHost); + + // 若拷贝失败则返回报错 + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + int seed1index = 0; // 种子点1对应的 index + int maxcounts = 0; // W1 距离内向量最多的向量所对应的个数 + + // 遍历统计数组,找出最大统计值对应的 index 即为种子点1的 index + for (int i = 0; i < count; i++) { + if (w1counts[i] > maxcounts) { + maxcounts = w1counts[i]; + seed1index = i; + } + } + + int tmp1size = 0; // 记录当前类别暂存数组中元素的个数,默认值为0 + int tmp2size = 0; // 记录当前类别暂存数组中元素的个数,默认值为0 + + // 为1类别的暂存数组申请 host 空间 + tmp1vecs = new int[count]; + if (tmp1vecs == NULL) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return UNKNOW_ERROR; + } + + // 将种子点1加入到类别1的暂存数组中, 同时将 tmp1size 加1 + tmp1vecs[tmp1size++] = seed1index; + + // 为1类别的暂存数组申请 device 空间 + errcode = cudaMalloc((void **)&tmp1vecsdev, sizeof(int) * count); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将主机端的暂存数组(有意义的部分)拷贝到 device 端 + errcode = cudaMemcpy(tmp1vecsdev, tmp1vecs, sizeof(int) * tmp1size, + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 为1类别的标记数组申请 Host 空间 + tmp1lbl = new unsigned char[count]; + if (tmp1lbl == NULL) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return UNKNOW_ERROR; + } + + // 初始化1类别标记数组的值 + for (int i = 0; i < count; i++) { + tmp1lbl[i] = 0; + } + tmp1lbl[seed1index] = 1; + + // 为1类别的标记数组申请 device 空间 + errcode = cudaMalloc((void **)&tmp1lbldev, sizeof(unsigned char) * count); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将 host 端的标记数组拷贝到 device 端 + errcode = cudaMemcpy(tmp1lbldev, tmp1lbl, sizeof(unsigned char) * count, + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 循环标记暂时属于类别1的向量,直到暂时属于类别1的向量的个数不再增加 + while (1) { + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (count + blocksize.x - 1) / blocksize.x; + gridsize.y = (tmp1size + blocksize.y - 1) / blocksize.y; + + // 调用核函数,标记类别1的向量 + _labelVectorsKer<<>>(*this, *featurevecarray, + tmp1lbldev, tmp1vecsdev, + tmp1size); + + // 检查核函数调用是否出错 + errcode = cudaGetLastError(); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 拷贝标记值数组到 Host 端 + errcode = cudaMemcpy(tmp1lbl, tmp1lbldev, sizeof(unsigned char) * count, + cudaMemcpyDeviceToHost); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 定义当前已经被标记的数组大小 + int tmp1sizenow = 0; + + // 遍历标记值数组,将已经标记的值添加到已标记数组中 + for (int i = 0; i < count; i++) { + if (tmp1lbl[i] == 1) + tmp1vecs[tmp1sizenow++] = i; + } + + // 如果两次的大小没有发生变化,则跳出循环 + if (tmp1sizenow == tmp1size) + break; + + // 将当前 size 赋给原 size + tmp1size = tmp1sizenow; + + // 将已标记数组拷贝到 Device 端 + errcode = cudaMemcpy(tmp1vecsdev, tmp1vecs, tmp1size * sizeof(int), + cudaMemcpyHostToDevice); + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (count + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + + // 调用核函数,寻找类别2的种子点,在不需要重新申请新的统计数组空间, + // 可以直接使用上一步中的统计数组,覆盖掉其数据,因为其数据已经不需要了 + _countAppointW1Ker<<>>(*this, *featurevecarray, + tmp1lbldev, w1countsdev); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将统计结果拷回到 Host 端 + errcode = cudaMemcpy(w1counts, w1countsdev, + count * sizeof(int), + cudaMemcpyDeviceToHost); + + // 若拷贝失败则返回报错 + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + int seed2index = 0; // 种子点2对应的 index + maxcounts = 0; // W1 距离内向量最多的向量所对应的个数 + + // 遍历统计数组,找出最大统计值对应的 index 即为种子点2的 index + for (int i = 0; i < count; i++) { + if (w1counts[i] > maxcounts) { + maxcounts = w1counts[i]; + seed2index = i; + } + } + + // 为2类别的暂存数组申请 host 空间 + tmp2vecs = new int[count]; + if (tmp2vecs == NULL) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return UNKNOW_ERROR; + } + + // 将种子点2加入到类别2的暂存数组中, 同时将 tmp2size 加1 + tmp2vecs[tmp2size++] = seed2index; + + // 为2类别的暂存数组申请 device 空间 + errcode = cudaMalloc((void **)&tmp2vecsdev, sizeof(int) * count); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将主机端的暂存数组(有意义的部分)拷贝到 device 端 + errcode = cudaMemcpy(tmp2vecsdev, tmp2vecs, sizeof(int) * tmp2size, + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 为2类别的标记数组申请 Host 空间 + tmp2lbl = new unsigned char[count]; + if (tmp2lbl == NULL) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return UNKNOW_ERROR; + } + + // 初始化2类别标记数组的值 + for (int i = 0; i < count; i++) { + tmp2lbl[i] = 0; + } + tmp2lbl[seed2index] = 1; + + // 为2类别的标记数组申请 device 空间 + errcode = cudaMalloc((void **)&tmp2lbldev, sizeof(unsigned char) * count); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将 host 端的标记数组拷贝到 device 端 + errcode = cudaMemcpy(tmp2lbldev, tmp2lbl, sizeof(unsigned char) * count, + cudaMemcpyHostToDevice); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 循环标记暂时属于类别2的向量,直到暂时属于类别2的向量的个数不再增加 + while (1) { + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (count + blocksize.x - 1) / blocksize.x; + gridsize.y = (tmp1size + blocksize.y - 1) / blocksize.y; + + // 调用核函数,标记类别2的向量 + _labelVectorsKer<<>>(*this, *featurevecarray, + tmp2lbldev, tmp2vecsdev, + tmp2size); + + // 检查核函数调用是否出错 + errcode = cudaGetLastError(); + if (errcode != cudaSuccess){ + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 拷贝标记值数组到 Host 端 + errcode = cudaMemcpy(tmp2lbl, tmp2lbldev, sizeof(unsigned char) * count, + cudaMemcpyDeviceToHost); + if (errcode != cudaSuccess){ + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 定义当前已经被标记的数组大小 + int tmp2sizenow = 0; + + // 遍历标记值数组,将已经标记的值添加到已标记数组中 + for (int i = 0; i < count; i++) { + if (tmp2lbl[i] == 1) + tmp2vecs[tmp2sizenow++] = i; + } + + // 如果两次的大小没有发生变化,则跳出循环 + if (tmp2sizenow == tmp2size) + break; + + // 将当前 size 赋给原 size + tmp2size = tmp2sizenow; + + // 将已标记数组拷贝到 Device 端 + errcode = cudaMemcpy(tmp2vecsdev, tmp2vecs, tmp2size * sizeof(int), + cudaMemcpyHostToDevice); + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (count + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + + // 为设备端的最终标记数组申请空间 + errcode = cudaMalloc((void **)&outlabeldev, sizeof(int) * count); + if (errcode != cudaSuccess) { + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 调用核函数,完成最终分类 + _segregateKer<<>>(*this, *featurevecarray, + tmp1lbldev, tmp2lbldev, outlabeldev); + + // 检查核函数调用是否出错 + errcode = cudaGetLastError(); + if (errcode != cudaSuccess){ + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 将最终分类标记数组拷贝到 Host 端 + errcode = cudaMemcpy(outlabel, outlabeldev, sizeof(int) * count, + cudaMemcpyDeviceToHost); + if (errcode != cudaSuccess) { + // 释放申请的内存,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + return CUDA_ERROR; + } + + // 清理内存空间,防止内存泄漏 + FREE_LOCAL_MEMORY_SEGREGATE; + + return NO_ERROR; +} + \ No newline at end of file diff --git a/okano_3_0/Segmentation.h b/okano_3_0/Segmentation.h new file mode 100644 index 0000000..6367538 --- /dev/null +++ b/okano_3_0/Segmentation.h @@ -0,0 +1,208 @@ +// Segmentation.h +// 创建人:邱孝兵 +// +// 二分类(Segmentation) +// 功能说明:SmoothVector 处理过程完了后,生成了一个结果 Vector 集合(收束点集合) +// 针对该收束点集合,根据给定的范围限定数据结构(包含:坐标范围,range 范围, +// 向量,偏角三个指标),由种子点出发对它们进行分割。 +// +// 修订历史: +// 2012年12月14日(邱孝兵) +// 初始版本 +// 2012年12月20日(邱孝兵) +// 删除 lvl1 和 lvl2 两个无用的类变量 +// 2013年01月03日(邱孝兵) +// 完成 _countW1Ker 和 _labelVectorsKer 两个核函数 +// 2013年01月04日(邱孝兵) +// 完成 _countAppointW1Ker 和 _countAppointW1Ker 两个核函数 + +#include "Image.h" +#include "ErrorCode.h" +#include "FeatureVecArray.h" + +#ifndef __SEGMENTATION_H__ +#define __SEGMENTATION_H__ + +// 结构体:BandWidth(分割的时候所依据的范围) +// 该结构体中定义了,在对收束点集合进行二分类的时候所依据的范围, +// 包括坐标范围,特征值范围和向量偏角范围三个指标 +typedef struct BandWidth_st { + float spaceWidth; // 坐标范围,向量坐标的欧式距离范围 + float rangeWidth; // 特征值范围,三维特征值向量的欧式距离范围 + float angleWidth; // 向量偏角范围,以 cos 值度量 +} BandWidth; + +// 类:Segmentation +// 继承自:无 +// SmoothVector 处理过程完了后,生成了一个结果 Vector 集合(收束点集合) +// 针对该收束点集合,根据给定的范围限定数据结构(包含:坐标范围,range 范围, +// 向量,偏角三个指标),由种子点出发对它们进行分割。 +class Segmentation { + +protected: + + // 成员变量:alpha(外部指定系数) + // 根据相邻点的 label 判定某个点的 class 时需要用到个一个参数。 + float alpha; + + // 成员变量:beta(外部指定系数) + // 根据相邻点的 label 判定某个点的 class 时需要用到个一个参数。 + float beta; + + // 成员变量:bw1(寻找种子点的范围限定) + // 分类时根据该值寻找种子点 + BandWidth bw1; + + // 成员变量:bw2(分类时使用的范围限定) + // 分类时需要使用该值。 + BandWidth bw2; + +public: + + // 构造函数:Segmentation + // 无参数版本的构造函数,所有成员变量均初始化为默认值 + __host__ __device__ + Segmentation() { + // 无参数的构造函数,使用默认值初始化各个变量 + this->alpha = 1; + this->beta = 1; + this->bw1.spaceWidth = 100; + this->bw1.rangeWidth = 0.5; + this->bw1.angleWidth = 0.5; + this->bw2.spaceWidth = 100; + this->bw2.rangeWidth = 0.5; + this->bw2.angleWidth = 0.5; + } + + // 构造函数:Segmentation + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + Segmentation( + float alpha, // 外部参数 alpha + float beta, // 外部参数 beta + BandWidth bw1, // 寻找 class - 1 的种子点的范围 + BandWidth bw2 // 分类时使用的范围 + ) { + // 使用默认值初始化各个变量 + this->alpha = 1; + this->beta = 1; + this->bw1.spaceWidth = 100; + this->bw1.rangeWidth = 0.5; + this->bw1.angleWidth = 0.5; + this->bw2.spaceWidth = 100; + this->bw2.rangeWidth = 0.5; + this->bw2.angleWidth = 0.5; + + // 调用 seters 给各个成员变量赋值 + this->setAlpha(alpha); + this->setBeta(beta); + this->setBw1(bw1); + this->setBw2(bw2); + } + + // 成员方法:getAlpha (获取 alpha 的值) + // 获取成员变量 alpha 的值 + __host__ __device__ float // 返回值:成员变量 alpha 的值 + getAlpha() const + { + // 返回成员变量 alpha 的值 + return this->alpha; + } + + // 成员方法:setAlpha(设置 alpha 的值) + // 设置成员变量 alpha 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setAlpha( + float alpha // 外部参数 alpha + ) { + // 设置成员变量 beta 的值 + this->alpha = alpha; + + return NO_ERROR; + } + + // 成员方法:getBeta (获取 beta 的值) + // 获取成员变量 beta 的值 + __host__ __device__ float // 返回值:成员变量 beta 的值 + getBeta() const + { + // 返回成员变量 beta 的值 + return this->beta; + } + + // 成员方法:setBeta(设置 beta 的值) + // 设置成员变量 beta 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setBeta( + float beta // 外部参数 beta + ) { + // 设置成员变量 beta 的值 + this->beta = beta; + + return NO_ERROR; + } + + // 成员方法:getBw1( 获取 bw1 的值) + // 设置成员变量 bw1 的值 + __host__ __device__ BandWidth // 返回值,成员变量 bw1 的值 + getBw1() const + { + // 返回成员变量 bw1 的值 + return this->bw1; + } + + // 成员方法:setBw1(设置 bw1 的值) + // 设置成员变量 bw1 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setBw1( + BandWidth bw1 // 用于寻找种子点的范围 + ) { + // 设置成员变量 bw1 的值 + this->bw1.spaceWidth = bw1.spaceWidth; + this->bw1.rangeWidth = bw1.rangeWidth; + this->bw1.angleWidth = bw1.angleWidth; + + return NO_ERROR; + } + + // 成员方法:getBw2( 获取 bw2 的值) + // 设置成员变量 bw2 的值 + __host__ __device__ BandWidth // 返回值,成员变量 bw2 的值 + getBw2() const + { + // 返回成员变量 bw2 的值 + return this->bw2; + } + + // 成员方法:setBw2(设置 bw2 的值) + // 设置成员变量 bw2 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setBw2( + BandWidth bw2 // 用于分类的范围 + ) { + // 设置成员变量 bw1 的值 + this->bw2.spaceWidth = bw2.spaceWidth; + this->bw2.rangeWidth = bw2.rangeWidth; + this->bw2.angleWidth = bw2.angleWidth; + + return NO_ERROR; + } + + // 成员函数:segregate(对收束点集进行二分类) + // SmoothVector 处理过程完了后,生成了一个结果 Vector 集合(收束点集合) + // 针对该收束点集合,根据给定的范围限定数据结构(包含:坐标范围, + // range 范围,向量,偏角三个指标),由种子点出发对它们进行二分类。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + segregate( + FeatureVecArray *featurevecarray, // 输入的收束点集 + int *outlabel // 输出的标记数组 + ); +}; + +#endif \ No newline at end of file diff --git a/okano_3_0/SegmentedScan.cu b/okano_3_0/SegmentedScan.cu new file mode 100644 index 0000000..98c2602 --- /dev/null +++ b/okano_3_0/SegmentedScan.cu @@ -0,0 +1,679 @@ +// SegmentedScan.cu +// 数组分段扫描 + +#include "SegmentedScan.h" +#include +#include +#include +using namespace std; + +// 宏:SEG_SCAN_PACK +// 定义了核函数中每段处理的数量。 +#define SEG_SCAN_PACK 16 + +// 宏:SEG_SCAN_PACK_NUM +// 定义了核函数中处理的段数。 +#define SEG_SCAN_PACK_NUM 64 + +// 宏:SEG_SCAN_BLOCKSIZE +// 定义了线程块大小。 +#define SEG_SCAN_BLOCKSIZE (SEG_SCAN_PACK * SEG_SCAN_PACK_NUM) + +// 宏:SEG_DEBUG_CPU_PRINT(CPU 版本调试打印开关) +// 打开该开关则会在 CPU 版本运行时打印相关的信息,以参考调试程序;如果注释掉该 +// 宏,则 CPU 不会打印这些信息,但这会有助于程序更快速的运行。 +// #define SEG_DEBUG_CPU_PRINT + +// Kernel 函数: _segmentedScanMatrixKer(数组分段扫描的矩阵方法版本) +// 矩阵方法的SegmentedScan 实现。具体的算法为核函数用 SEG_SCAN_PACK_NUM 个线程 +// 进行扫描,将输入的数组分为 SEG_SCAN_PACK_NUM 段,每段的 SEG_SCAN_PACK 个元素 +// 采用串行的方法进行分段扫描。 +__global__ void // Kernel 函数无返回值 +_segmentedScanMatrixKer( + float *inarray, // 输入数组。 + int *label, // 输入数组的分段标签值。 + int *index, // 输入数组的位置索引。 + int n, // 数组的长度,处理元素的个数。 + float *maxdist, // 输出,分段扫描后的最大垂直距离数组, + int *maxdistidx, // 输出,分段扫描侯的最大垂距的索引值数组。 + float *blockmaxdist, // 中间结果,每块最后位置的最大垂距。 + int *blocklabel, // 中间结果,每块最后位置处的标签。 + int *blockmaxdistidx // 中间结果,每块最后位置处的垂距索引。 +); + +// Kernel 函数: _segmentedScanBackKer(将中间结果进行返回分段扫描) +// 对一个数组进行扫描,结合中间结果的小数组,对输出数组进行返回式的分段扫描。 +__global__ void // Kernel 函数无返回值 +_segmentedScanBackKer( + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的扫描后最大垂距点的位置索引。 + int *label, // 输入数组的分段标签值。 + float *blockmaxdist, // 中间结果,每块最后位置的最大垂距。 + int *blocklabel, // 中间结果,每块最后位置处的标签。 + int *blockmaxdistidx, // 中间结果,每块最后位置处的垂距索引。 + int numelements // 扫描数组的长度。 +); + +// Kernel 函数: _segmentedScanMatrixKer(数组分段扫描的矩阵方法版本) +__global__ void _segmentedScanMatrixKer(float *inarray, int *label, + int *index, int n, + float *maxdist, int *maxdistidx, + float *blockmaxdist, int *blocklabel, + int *blockmaxdistidx) +{ + // 声明共享内存。shd开头的指针表示当前点的信息,长度为块大小。 + // shdcol开头的指针表示每段数组最后一个元素的信息,长度为 PACK_NUM。 + extern __shared__ float shdmem[]; + float *shdmaxdist = shdmem; + float *shdcolmaxdist = &shdmaxdist[blockDim.x]; + int *shdlabel = (int*)&shdcolmaxdist[SEG_SCAN_PACK_NUM]; + int *shdcollabel = &shdlabel[blockDim.x]; + int *shdindex = &shdcollabel[SEG_SCAN_PACK_NUM]; + int *shdcolindex = &shdindex[blockDim.x]; + + // 基础索引。表示每块的起始位置索引。 + int baseidx = blockIdx.x * blockDim.x; + + // 块外的数组索引。 + int idx = threadIdx.x + baseidx; + + // 采用矩阵方法分段扫描,每段的头索引。 + int packidx = SEG_SCAN_PACK * threadIdx.x; + + // 本地变量。表示目前扫描过的已知的最大垂距,区域标签,索引。 + float curmaxdist; + int curlabel; + int curindex; + + // 本地变量,特殊值,用来给超出数组长度的点赋值或者给不需要处理的点赋值。 + float pmaxdist = -100; + int plabel = -1; + int pindex = -1; + + // 将需要计算的值从输入加载到共享内存上。 + if (idx < n) { + shdmaxdist[threadIdx.x] = inarray[idx]; + shdlabel[threadIdx.x] = label[idx]; + // 如果记录最大垂距位置索引的指针为空,用当前点的实际索引对共享内存进行 + // 赋值。 + if (index == NULL) + shdindex[threadIdx.x] = idx; + // 否则用输入对共享内存进行赋值。 + else + shdindex[threadIdx.x] = index[idx]; + // 超出数组长度的点赋特殊值。 + } else { + shdmaxdist[threadIdx.x] = pmaxdist; + shdlabel[threadIdx.x] = plabel; + shdindex[threadIdx.x] = pindex; + } + + // 进行块内同步。 + __syncthreads(); + + // 用 SEG_SCAN_PACK_NUM 个线程对每段进行 segmented scan,段内为 + // SEG_SCAN_PACK 个元素的串行扫描。 + if (threadIdx.x < SEG_SCAN_PACK_NUM) { + // 记录每段 SEG_SCAN_PACK 中开始位置处的值,作为目前已知最大垂距的点的 + // 信息。 + curmaxdist = shdmaxdist[packidx]; + curlabel = shdlabel[packidx]; + curindex = shdindex[packidx]; + + // 对每段 SEG_SCAN_PACK 进行串行扫描。 + for (int i = packidx + 1; i < packidx + SEG_SCAN_PACK; i++) + { + // 如果当前点的区域标签和目前已知的最大垂距的点的区域标签不同, + // 那么重新记录目前已知最大垂距点的信息。 + // 或者区域标签相同,当前点的垂距大于目前已知的最大垂距点的垂距, + // 那么重新记录目前已知最大垂距点的信息。 + if (shdlabel[i] != curlabel || shdmaxdist[i] > curmaxdist) { + curmaxdist = shdmaxdist[i]; + curlabel = shdlabel[i]; + curindex = shdindex[i]; + // 否则就更改当前点的最大垂距点位置的索引,更新当前点记录的已知的最 + // 大垂距值。 + } else { + shdindex[i] = curindex; + shdmaxdist[i] = curmaxdist; + } + } + + // 将每段 SEG_SCAN_PACK 进行分段扫描后的记录值(目前已知的最大垂距点信 + // 息)写入列数组。 + shdcolmaxdist[threadIdx.x] = curmaxdist; + shdcollabel[threadIdx.x] = curlabel; + shdcolindex[threadIdx.x] = curindex; + } + + // 进行块内同步。 + __syncthreads(); + + // 用第 0 个线程对保存每段 SEG_SCAN_PACK 中已知信息的列数组 shdcol 进行串行 + // 分段扫描 + if (threadIdx.x == 0) { + // 串行扫描,扫描长度为 PACK_NUM。 + for(int i = 1; i < SEG_SCAN_PACK_NUM; i++) { + // 从上之下对列数组进行扫描,比较相邻的两个点的区域标签和最大垂距, + // 如果属于同一区域, 并且最大距离小于前一个记录值,那么就改写当前 + // 的记录。 + if (shdcollabel[i] == shdcollabel[i - 1] && + shdcolmaxdist[i] < shdcolmaxdist[i - 1]) { + shdcolmaxdist[i] = shdcolmaxdist[i - 1]; + shdcollabel[i] = shdcollabel[i - 1]; + shdcolindex[i] = shdcolindex[i - 1]; + } + } + } + + // 进行块内同步。 + __syncthreads(); + + // 用 SEG_SCAN_PACK_NUM 个线程对每段 SEG_SCAN_PACK 进行回扫,将列数组经过分 + // 段扫描后的更新值,与每段 SEG_SCAN_PACK 的值进行比较,进行更新分段扫描。 + if (threadIdx.x < SEG_SCAN_PACK_NUM) { + // 对于第一段,不需要用更新的列数组结果进行比较,故赋予特殊值。 + if (threadIdx.x == 0) { + curmaxdist = pmaxdist; + curlabel = plabel; + curindex = pindex; + // 对于之后的每段,需要跟前一段的目前扫描到的最大垂距点的信息进行更新, + // 所以把列数组中保存的,当前段的前一段的更新信息赋值给目前所能找到的最 + // 大垂距点信息。 + } else { + curmaxdist = shdcolmaxdist[threadIdx.x - 1]; + curlabel = shdcollabel[threadIdx.x - 1]; + curindex = shdcolindex[threadIdx.x - 1]; + } + + // 对每行进行串行的更新扫描 + for (int i = packidx; i < packidx + SEG_SCAN_PACK; i++) { + // 比较当前点和目前已知最大垂距点的区域标签和垂距, + // 如果属于同一区域, 并且当前点的垂距小于目前已知信息,那么就改写 + // 当前点的记录。 + if (curlabel == shdlabel[i] && shdmaxdist[i] < curmaxdist) { + shdmaxdist[i] = curmaxdist; + shdindex[i] = curindex; + // 否则,就 break 掉。 + } else { + break; + } + } + } + + // 进行块内同步。 + __syncthreads(); + + // 超出数组长度 n 的值不进行写入,直接返回。 + if (idx >= n) + return; + // 将结果从共享内存写入到输出。包括本区域内的最大垂距和本区域内的最大垂距的 + // 点的位置索引。 + maxdist[idx] = shdmaxdist[threadIdx.x]; + maxdistidx[idx] = shdindex[threadIdx.x]; + + // 如果中间结果数组为空,不进行处理直接返回。 + if (blockmaxdist == NULL) + return; + // 如果大于一个线程块,用第 0 个线程把每块的最后一个点的信息记录到中间结果 + // 数组。 + if (blockIdx.x < gridDim.x - 1 && threadIdx.x == 0) { + blockmaxdist[blockIdx.x] = shdcolmaxdist[SEG_SCAN_PACK_NUM - 1]; + blocklabel[blockIdx.x] = shdcollabel[SEG_SCAN_PACK_NUM - 1]; + blockmaxdistidx[blockIdx.x] = shdcolindex[SEG_SCAN_PACK_NUM - 1]; + } +} + +// Kernel 函数: _segmentedScanBackKer(将中间结果进行返回分段扫描) +__global__ void _segmentedScanBackKer(float *maxdist, int *maxdistidx, + int *label, float *blockmaxdist, + int *blocklabel, int *blockmaxdistidx, + int numelements) +{ + // 声明共享内存。用来存放中间结果小数组中的元素,也就是输入的原数组的每块最 + // 后一个元素。共包含三个信息。 + __shared__ float shdcurmaxdist[1]; + __shared__ int shdcurlabel[1]; + __shared__ int shdcurmaxdistindex[1]; + + + // 状态位,用来标记上一块的最后一个元素的标签值是否和本段第一个元素的标签值 + // 相同。 + __shared__ int state[1]; + + // 计算需要进行块间累加位置索引(块外的数组索引)。 + int idx = (blockIdx.x + 1) * blockDim.x + threadIdx.x; + + // 用每块的第一个线程来读取每块前一块的最后一个元素,从中间结果数组中读取。 + if (threadIdx.x == 0) { + shdcurmaxdist[0] = blockmaxdist[blockIdx.x]; + shdcurlabel[0] = blocklabel[blockIdx.x]; + shdcurmaxdistindex[0] = blockmaxdistidx[blockIdx.x]; + // 用 state 来记录上一块的最后一个元素的标签值是否和本段第一个元素的 + // 标签值相同,相同则为 1,不同则为 0。 + state[0] = (label[idx] == shdcurlabel[0]); + } + + // 块内同步。 + __syncthreads(); + + // 如果状态位为 0,说明上一块和本块无关,不在一个区域内,直接返回。 + if (state[0] == 0) + return; + // 如果数组索引大于数组长度,直接返回。 + if (idx >= numelements) + return; + // 如果当前位置处的标签值和目前已知的最大垂距的标签值相同,并且垂距小于目前 + // 已知的最大垂距,那么更新当前位置处的最大垂距记录和最大垂距位置的索引。 + if (label[idx] == shdcurlabel[0] && maxdist[idx] < shdcurmaxdist[0]) { + maxdist[idx] = shdcurmaxdist[0]; + maxdistidx[idx] = shdcurmaxdistindex[0]; + } +} + +// Host 成员方法:segmentedScanBack(将中间结果进行返回分段扫描) +__host__ int SegmentedScan::segmentedScanBack(float *maxdist, int *maxdistidx, + int *label, float *blockmaxdist, + int *blocklabel, + int *blockmaxdistidx, + int numelements) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (maxdist == NULL || maxdistidx == NULL || label == NULL || + blockmaxdist == NULL || blocklabel == NULL || blockmaxdistidx == NULL) + return NULL_POINTER; + + // 检查处理的数组长度,如果小于 0 出错。 + if (numelements < 0) + return INVALID_DATA; + + // 计算线程块大小。 + int gridsize = max(1, (numelements + SEG_SCAN_BLOCKSIZE - 1) / + SEG_SCAN_BLOCKSIZE); + + // 判断 gridsize 大小,如果小于 1,则不用进行加回操作。返回正确。 + if (gridsize < 1) + return NO_ERROR; + + // 调用 _segmentedScanBackKer 核函数,将中间结果数组加回到原扫描数组。 + _segmentedScanBackKer<<>>( + maxdist, maxdistidx, label, blockmaxdist, blocklabel, + blockmaxdistidx, numelements); + + // 判断是否出错。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:segmentedScan(数组分段扫描) +// 输入输出均在 CPU 端,不做 GPU 端考虑。以后可考虑添加输入输出在 device 端情况 +// 不涉及中间变量和加回过程,串行代码。 +__host__ int SegmentedScan::segmentedScanCpu(float *inarray, int *label, + int *index, float *maxdist, + int *maxdistidx, int numelements, + bool hostinarray, bool hostlabel, + bool hostindex, bool hostmaxdist, + bool hostmaxdistidx) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (inarray == NULL || label == NULL || maxdist == NULL || + maxdistidx == NULL) + return NULL_POINTER; + + // 本程序实现的方法可处理的数组长度,加以判断控制。 + if (numelements < 0) + return INVALID_DATA; + + // 本地变量 + int idx; + float curmaxdist; + int curlabel; + int curindex; + + // 如果记录最大垂距位置索引的指针为空,用当前点的实际索引进行赋值。 + if (index == NULL) { + // 申请数组 + index = new int[numelements]; + // 申请失败 + if (index == NULL) { + return OUT_OF_MEM; + } + + // 对索引数组赋值 + for (idx = 0; idx < numelements; idx++) { + index[idx] = idx; +#ifdef SEG_DEBUG_CPU_PRINT + cout << "[Cpu]index " << idx << " is " << index[idx] << endl; +#endif + } + } + + // 当前信息初始化 + curmaxdist = inarray[0]; + curlabel = label[0]; + curindex = index[0]; + + // 输出首位初始化 + maxdist[0] = curmaxdist; + maxdistidx[0] = curindex; +#ifdef SEG_DEBUG_CPU_PRINT + cout << "[CPU]0 maxdist is " << maxdist[0] << endl; + cout << "[CPU]0 maxdistidx is " << maxdistidx[0] << endl; +#endif + + // 从第一个点开始处理 + for (idx = 1; idx < numelements; idx++) { + // 如果此点标签与当前标签不符,或者此点的垂距大于当前最大垂距, + // 更新当前最大垂距,当前标签,当前最大垂距的位置索引。 + if (label[idx] != curlabel || inarray[idx] > curmaxdist) { + curmaxdist = inarray[idx]; + curlabel = label[idx]; + curindex = index[idx]; + } + // 对输出赋值 + maxdist[idx] = curmaxdist; + maxdistidx[idx] = curindex; +#ifdef SEG_DEBUG_CPU_PRINT + cout << idx << "[CPU]maxdist is " << maxdist[idx] << endl; + cout << idx << "[CPU]maxdistidx is " << maxdistidx[idx] << endl; +#endif + } + + // 释放内存 + delete index; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 宏:FAIL_SEGMENTED_SCAN_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_SEGMENTED_SCAN_FREE do { \ + if (gridsize > 1) { \ + if(blockmaxdistDev != NULL) \ + cudaFree(blockmaxdistDev); \ + if(blocklabelDev != NULL) \ + cudaFree(blocklabelDev); \ + if(blockmaxdistidxDev != NULL) \ + cudaFree(blockmaxdistidxDev); \ + } \ + if (hostinarray && inarrayDev != NULL) \ + cudaFree(inarrayDev); \ + if (hostlabel && labelDev != NULL) \ + cudaFree(labelDev); \ + if (hostindex && indexDev != NULL) \ + cudaFree(indexDev); \ + if (hostmaxdist && maxdistDev != NULL) \ + cudaFree(maxdistDev); \ + if (hostmaxdistidx && maxdistidxDev != NULL) \ + cudaFree(maxdistidxDev); \ + } while (0) + +// Host 成员方法:segmentedScan(数组分段扫描) +__host__ int SegmentedScan::segmentedScan(float *inarray, int *label, + int *index, float *maxdist, + int *maxdistidx, int numelements, + bool hostinarray, bool hostlabel, + bool hostindex, bool hostmaxdist, + bool hostmaxdistidx) +{ + // 检查输入和输出是否为 NULL,如果为 NULL 直接报错返回。 + if (inarray == NULL || label == NULL || maxdist == NULL || + maxdistidx == NULL) + return NULL_POINTER; + + // 本程序实现的方法可处理的数组长度,加以判断控制。 + if (numelements < 0) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 计算共享内存的长度。 + unsigned int sharedmemsize = 0; + + // 定义设备端的输入输出数组指针,当输入输出指针在 Host 端时,在设备端申请对 + // 应大小的数组。 + float *inarrayDev = NULL; + int *labelDev = NULL; + int *indexDev = NULL; + float *maxdistDev = NULL; + int *maxdistidxDev = NULL; + + // 线程块的大小尺寸。 + int gridsize = 0; + int blocksize; + + // 局部变量,中间结果存放数组。长度会根据线程块大小来确定。 + float *blockmaxdistDev = NULL; + int *blocklabelDev = NULL; + int *blockmaxdistidxDev = NULL; + + // 中间结果数组的长度。 + int blocksumsize; + + // 判断当前 inarray 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device 端,则直接使用。 + if (hostinarray) { + // 为输入数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&inarrayDev, + sizeof (float) * numelements); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + + // 将输入数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(inarrayDev, inarray, + sizeof (float) * numelements, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + inarrayDev = inarray; + } + + // 判断当前 label 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device 端,则直接使用。 + if (hostlabel) { + // 为输入数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&labelDev, sizeof (int) * numelements); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + + // 将输入数组拷贝到设备端内存。 + cuerrcode = cudaMemcpy(labelDev, label, sizeof (int) * numelements, + cudaMemcpyHostToDevice); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + labelDev = label; + } + + // 判断当前 label 数组是否存储在 Host 端。若在设备端,则直接使用。若在 Host + // 端,则不处理。 + if (!hostindex) { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + indexDev = index; + } + + // 判断当前 maxdist 数组是否存储在 Host 端。若是,则需要在 Device 端为数组 + // 申请一段空间;若该数组是在 Device 端,则直接使用。 + if (hostmaxdist) { + // 为输出数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&maxdistDev, + sizeof (float) * numelements); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + maxdistDev = maxdist; + } + + // 判断当前 maxdistidx 数组是否存储在 Host 端。若是,则需要在 Device 端为数 + // 组申请一段空间;若该数组是在 Device 端,则直接使用。 + if (hostmaxdistidx) { + // 为输出数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&maxdistidxDev, + sizeof (int) * numelements); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } else { + // 如果在设备端,则将指针传给对应的设备端统一指针。 + maxdistidxDev = maxdistidx; + } + + // 针对不同的实现类型,选择不同的路径进行处理。 + switch(segmentedScanType) { + // 使用矩阵方法的 segmentedscan 实现。 + case MATRIX_SEGMENTED_SCAN: + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + // 矩阵方法分段扫描版本线程块大小。 + blocksize = SEG_SCAN_BLOCKSIZE; + + // 计算线程块大小和共享内存长度。 + gridsize = max(1, (numelements + blocksize - 1) / blocksize); + sharedmemsize = (sizeof (float) + 2 * sizeof (int)) * + (blocksize + SEG_SCAN_PACK_NUM); + + // 如果扫描所需要的线程的 grid 尺寸大于 1,就需要进行加回操作,就需要申 + // 请存放中间结果的数组。 + if (gridsize > 1) { + // 需要将每块处理的最后一个元素取出,最后一个线程块不进行处理。 + blocksumsize = gridsize - 1; + + // 为存放中间结果的 3 个数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blockmaxdistDev, + blocksumsize * sizeof(float)); + // 出错则释放申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + + // 为存放区域标签的中间数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blocklabelDev, + blocksumsize * sizeof(int)); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + + // 为存放最大垂距位置索引的中间结果数组在设备端申请内存。 + cuerrcode = cudaMalloc((void **)&blockmaxdistidxDev, + blocksumsize * sizeof(int)); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } + + // 调用 Kernel 函数,完成实际的分段数组扫描。 + _segmentedScanMatrixKer<<>>( + inarrayDev, labelDev, indexDev, numelements, + maxdistDev, maxdistidxDev, + blockmaxdistDev, blocklabelDev, blockmaxdistidxDev); + + // 判断核函数是否出错。 + if (cudaGetLastError() != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return CUDA_ERROR; + } + break; + + // 其他方式情况下,直接返回非法数据错误。 + default: + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return INVALID_DATA; + } + + if (gridsize > 1) { + // 递归调用分段扫描函数。此时输入输出数组皆为中间结果数组。 + // 这里的递归调用不会调用多次,数组的规模是指数倍减小的。 + errcode = segmentedScan(blockmaxdistDev, blocklabelDev, + blockmaxdistidxDev, blockmaxdistDev, + blockmaxdistidxDev, blocksumsize, + false, false, false, false, false); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return errcode; + } + + // 调用加回函数,将各块的扫描中间结果加回到输出数组。 + errcode = segmentedScanBack(maxdistDev, maxdistidxDev, labelDev, + blockmaxdistDev, blocklabelDev, + blockmaxdistidxDev, numelements); + if (errcode != NO_ERROR) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return errcode; + } + } + + // 如果 maxdist 数组在 Host 端,将结果拷贝到输出。 + if (hostmaxdist) { + // 将结果从设备端内存拷贝到输出数组。 + cuerrcode = cudaMemcpy(maxdist, maxdistDev, + sizeof (float) * numelements, + cudaMemcpyDeviceToHost); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } + + // 如果 maxdistidx 数组在 Host 端,将结果拷贝到输出。 + if (hostmaxdistidx) { + // 将结果从设备端内存拷贝到输出数组。 + cuerrcode = cudaMemcpy(maxdistidx, maxdistidxDev, + sizeof (int) * numelements, + cudaMemcpyDeviceToHost); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SEGMENTED_SCAN_FREE; + return cuerrcode; + } + } + + // 释放 Device 内存。需要判断输入输出参数是否在 host 端。 + FAIL_SEGMENTED_SCAN_FREE; + + // 处理完毕退出。 + return NO_ERROR; +} + +// 取消前面的宏定义。 +#undef FAIL_SEGMENTED_SCAN_FREE + diff --git a/okano_3_0/SegmentedScan.h b/okano_3_0/SegmentedScan.h new file mode 100644 index 0000000..67e0791 --- /dev/null +++ b/okano_3_0/SegmentedScan.h @@ -0,0 +1,291 @@ +// SegmentedScan.h +// 创建人:刘瑶 +// +// 数组分段扫描(SegmentedScan) +// 功能说明:对一个数组进行分段扫描,对所有元素进行某操作的遍历。 +// 举例:数组 1, 7, 5, 13, 10 的标签值为 0, 0, 0, 3, 3, 进行 inclusive +// segmented scan 的结果是:1, 7, 7, 13, 13。 +// 参照 Y Dotsenko 的论文 "fast scan algorithms on graphics processors" 实现。 +// 支持任意长度的数组分段扫描。 +// +// 修订历史: +// 2012年12月29日(刘瑶) +// 初始版本 +// 2012年12月30日(刘瑶) +// 修改了核函数的部分错误。完善了代码注释。 +// 2012年12月31日(刘瑶) +// 添加了将中间结果进行返回分段扫描的函数。修改了部分代码。 +// 2013年12月23日(刘瑶) +// 添加了 CPU 串行版本分段扫描接口。 + +#ifndef __SEGMENTEDSCAN_H__ +#define __SEGMENTEDSCAN_H__ + +#include "ErrorCode.h" +#include + +// 宏:MATRIX_SEGMENTED_SCAN +// 用于设置 SegmentedScan 类中 segmentedScanType 成员变量,告知类的实例选用矩阵 +// 方法的 SegmentedScan 实现。 +// 参考文献:Y Dotsenko, NK Govindaraju, PP Sloan, and etc. Fast scan +// algorithms on graphics processors. ICS, 2008, pp. 205-213. +// 论文被引用次数:60 +#define MATRIX_SEGMENTED_SCAN 1 + +// 用于设置 SegmentedScan 类中 segmentedScanType +// 成员变量,告知类的实例选用运算转换法的分段扫描,未实现 +// 参考文献:S Sengupta, M Harris, and M Garland. Efficient parallel scan +// algorithms for GPUs. NVR, 2008. +// 论文被引用次数:55 +#define OPERATOR_TRANSFORMATION_SEGMENTED_SCAN 2 + +// 用于设置 SegmentedScan 类中 segmentedScanType +// 成员变量,告知类的实例选用直接法的分段扫描,未实现 +// 参考文献:S Sengupta, M Harris, and M Garland. Efficient parallel scan +// algorithms for GPUs. NVR, 2008. +// 论文被引用次数:55 +#define DIRECT_SEGMENTED_SCAN 3 + +// 类:SegmentedScan +// 继承自:无 +// 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 +// 输入数组包括 3 个,当前点的垂距,当前点的所属区域的标签,当前区域内最大垂距 +// 点的位置索引。输出数组包括 2 个,当前所属区域内的最大垂距,当前区域内的最大 +// 垂距的位置索引。目前只实现了矩阵方法的分段扫描。 +class SegmentedScan { + +protected: + + // 成员变量:segmentedScanType(实现类型) + // 设定分段扫描方法实现类型中的一种,在调用 segmentedscan 扫描函数的时候, + // 使用对应的实现方式。 + int segmentedScanType; + + // Host 成员方法:segmentedScanBack(将中间结果进行返回分段扫描) + // 对一个数组进行扫描,结合中间结果的小数组,对输出数组进行返回式的分段 + // 扫描。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScanBack( + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的扫描后最大垂距点的位置索引。 + int *label, // 输入数组的分段标签值。 + float *blockmaxdist, // 中间结果,每块最后位置的最大垂距。 + int *blocklabel, // 中间结果,每块最后位置处的标签。 + int *blockmaxdistidx, // 中间结果,每块最后位置处的垂距索引。 + int numelements // 扫描数组的长度。 + ); + +public: + + // 构造函数:SegmentedScan + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + SegmentedScan() + { + // 使用默认值为类的各个成员变量赋值。 + this->segmentedScanType = MATRIX_SEGMENTED_SCAN; // 实现类型的默认值为 + // 矩阵法的分段扫描 + } + + // 构造函数:SegmentedScan + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + SegmentedScan( + int segmentedScanType // 实现类型 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->segmentedScanType = MATRIX_SEGMENTED_SCAN; // 实现类型的默认值为 + // 矩阵分段扫描法。 + // 根据参数列表中的值设定成员变量的初值 + this->setSegmentedScanType(segmentedScanType); + } + + // 成员方法:getScanType(读取实现类型) + // 读取 segmentedScanType 成员变量的值。 + __host__ __device__ int // 返回值:当前 segmentedScanType 成员变量的值。 + getSegmentedScanType() const + { + // 返回 ScanType 成员变量的值。 + return this->segmentedScanType; + } + + // 成员方法:setScanType(设置实现类型) + // 设置 segmentedScanType 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setSegmentedScanType( + int segmentedScanType // 实现类型 + ) { + // 检查输入参数是否合法 + if (segmentedScanType != MATRIX_SEGMENTED_SCAN) + return INVALID_DATA; + + // 将 segmentedScanType 成员变量赋成新值 + this->segmentedScanType = segmentedScanType; + return NO_ERROR; + } + + // Host 成员方法:SegmentedScan(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScan( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + int *index, // 输入数组的位置索引。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的分段扫描后的最大垂距的点的位置 + // 索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostinarray, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostlabel, // 判断 label 是否是 Host 端的指针, + // 默认为“是”。 + bool hostindex, // 判断 index 是否是 Host 端的指针, + // 默认为“是”。 + bool hostmaxdist, // 判断 hostmaxdist 是否是 Host 端的指 + // 针,默认为“是”。 + bool hostmaxdistidx // 判断 hostmaxdistidx 是否是 Host 端的 + // 指针,默认为“是”。 + ); + + // Host 成员方法:SegmentedScans(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + inline __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScan( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + int *index, // 输入数组的位置索引。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的扫描后最大垂距点的位置索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostall = true // 判断所有参数是否是 Host 端指针。 + ) { + // 调用分段扫描的成员方法。输入输出参数全部在 host 端的判断变量一致。 + return segmentedScan(inarray, label, index, + maxdist, maxdistidx, numelements, + hostall, hostall, hostall, hostall, hostall); + } + + // Host 成员方法:SegmentedScan(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScan( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的分段扫描后的最大垂距的点的位置 + // 索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostinarray, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostlabel, // 判断 label 是否是 Host 端的指针, + // 默认为“是”。 + bool hostmaxdist, // 判断 hostmaxdist 是否是 Host 端的指 + // 针,默认为“是”。 + bool hostmaxdistidx // 判断 hostmaxdistidx 是否是 Host 端的 + // 指针,默认为“是”。 + ) { + // 调用分段扫描的成员方法。输入输出参数全部在 host 端的判断变量一致。 + return segmentedScan(inarray, label, NULL, + maxdist, maxdistidx, numelements, + hostinarray, hostlabel, false, hostmaxdist, + hostmaxdistidx); + } + + // Host 成员方法:SegmentedScan(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + inline __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScan( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的扫描后最大垂距点的位置索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostall = true // 判断所有参数是否是 Host 端指针。 + ) { + // 调用分段扫描的成员方法。输入输出参数全部在 host 端的判断变量一致。 + return segmentedScan(inarray, label, NULL, + maxdist, maxdistidx, numelements, + hostall, hostall, hostall, hostall, hostall); + } + + // Host 成员方法:SegmentedScan(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScanCpu( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + int *index, // 输入数组的位置索引。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的分段扫描后的最大垂距的点的位置 + // 索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostinarray, // 判断 inarray 是否是 Host 端的指针, + // 默认为“是”。 + bool hostlabel, // 判断 label 是否是 Host 端的指针, + // 默认为“是”。 + bool hostindex, // 判断 index 是否是 Host 端的指针, + // 默认为“是”。 + bool hostmaxdist, // 判断 hostmaxdist 是否是 Host 端的指 + // 针,默认为“是”。 + bool hostmaxdistidx // 判断 hostmaxdistidx 是否是 Host 端的 + // 指针,默认为“是”。 + ); + + // Host 成员方法:segmentedScanCpu(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + inline __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScanCpu( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + int *index, // 输入数组的位置索引。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的分段扫描后的最大垂距的点的位置 + // 索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostall = true // 判断所有参数是否是 Host 端指针。 + ) { + // 调用分段扫描的成员方法。输入输出参数全部在 host 端的判断变量一致。 + return segmentedScanCpu(inarray, label, index, + maxdist, maxdistidx, numelements, + hostall, hostall, hostall, hostall, hostall); + } + + // Host 成员方法:segmentedScanCpu(数组分段扫描) + // 对一个数组进行分段扫描,对所有元素进行某操作的遍历。 + inline __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回NO_ERROR。 + segmentedScanCpu( + float *inarray, // 输入数组 + int *label, // 输入数组的分段标签值。 + float *maxdist, // 输出的分段扫描后的最大垂直距离数组, + // 表示每段中的垂距最大的值。 + int *maxdistidx, // 输出的扫描后最大垂距点的位置索引。 + int numelements, // 数组的长度,处理元素的个数。 + bool hostall = true // 判断所有参数是否是 Host 端指针。 + ) { + // 调用分段扫描的成员方法。输入输出参数全部在 host 端的判断变量一致。 + return segmentedScanCpu(inarray, label, NULL, + maxdist, maxdistidx, numelements, + hostall, hostall, hostall, hostall, hostall); + } +}; + +#endif + diff --git a/okano_3_0/SelectShape.cu b/okano_3_0/SelectShape.cu new file mode 100644 index 0000000..315da61 --- /dev/null +++ b/okano_3_0/SelectShape.cu @@ -0,0 +1,632 @@ +// SelectShape.cu +// 实现形状选择算法 + +#include "SelectShape.h" + +#include +using namespace std; + + +// 宏:MAX_LABEL +// 特征值数组中标记的最大值,默认为 256。 +#ifndef MAX_LABEL +#define MAX_LABEL 256 +#endif + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + + +// Kernel 函数: _selectShapeByIndexKer(根据索引值进行形状区域拷贝) +// 查询输入图像中像素值(标记值)等于 label 的像素,拷贝至输出图像中; +// 否则将输出图像中该位置清0。 +static __global__ void // Kernel 函数无返回值。 +_selectShapeByIndexKer( + ImageCuda inimg, // 输入图像。 + ImageCuda outimg, // 输出图像。 + int label // 待查询的标记值。 +); + +// Kernel 函数: _setLabelByValueKer(根据特征值进行形状区域拷贝) +// 查询 rank 数组中特征值等于 value 的项,将其对应 flag 标记设为 1; +// 否则为0。 +static __global__ void // Kernel 函数无返回值。 +_setLabelByValueKer( + int *rank, // 特征值数组。 + int value, // 待查询的特征值。 + unsigned char *flaglabel // 标记数组。 +); + +// Kernel 函数: _selectShapeByValueKer(根据特征值进行形状区域拷贝) +// 查询输入图像中区域的特征值等于 value,则将该区域拷贝至输出图像中; +// 否则将输出图像中该位置清 0。 +static __global__ void // Kernel 函数无返回值。 +_selectShapeByValueKer( + ImageCuda inimg, // 输入图像。 + ImageCuda outimg, // 输出图像。 + unsigned char *flaglabel // 标记数组。 +); + +// Kernel 函数: _shapeClearByLabelKer(清除区域标记) +// 如果 flag 等于 0, 则设置其对应区域的像素值为 0;否则不做任何改变。 +static __global__ void // Kernel 函数无返回值。 +_shapeClearByLabelKer( + ImageCuda inimg, // 输入图像。 + unsigned char *flaglabel // 标记数组。 +); + +// Kernel 函数: _setLabelByMinMaxKer(根据最大最小范围进行形状区域拷贝) +// 查询 rank 数组中特征值在最大最小范围内的项,将其对应 flag 标记设为 1; +// 否则为 0。 +static __global__ void // Kernel 函数无返回值。 +_setLabelByMinMaxKer( + int *rank, // 特征值数组。 + int minvalue, // 最小特征值。 + int maxvalue, // 最大特征值。 + unsigned char *flaglabel // 标记数组。 +); + +// Kernel 函数: _selectShapeByIndexKer(根据索引值进行形状区域拷贝) +static __global__ void _selectShapeByIndexKer( + ImageCuda inimg, ImageCuda outimg, int label) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素点. + // 如果输入图像的该像素值等于 label, 则将其拷贝到输出图像中; + // 否则将输出图像中该位置清 0。 + // 线程中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = (intemp == label ? intemp : 0); + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 如果输入图像的该像素值等于 label, 则将其拷贝到输出图像中; + // 否则将输出图像中该位置清 0。 + outimg.imgMeta.imgData[outidx] = (intemp == label ? intemp : 0); + } +} + +// Host 成员方法:selectShapeByIndex(根据标记索引形状) +__host__ int SelectShape::selectShapeByIndex(Image *inimg, Image *outimg) +{ + // 检查图像是否为 NULL。 + if (inimg == NULL) + return NULL_POINTER; + + // 检查 rank 数组是否为空。 + if (this->rank == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 通过输入的索引值找到相对应的形状区域标记值。 + int label = this->rank[2 * this->index + 1]; + + // 如果输入图像不等于输出图像,并且输出图像不为空。 + if (inimg != outimg && outimg != NULL) { + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入 + // 图像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据标记 label 进行形状区域拷贝。 + _selectShapeByIndexKer<<>>( + insubimgCud, outsubimgCud, label); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + // 如果输入图像等于输出图像,或者输出图像为空。 + } else { + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数根据标记 label 进行形状区域拷贝。 + _selectShapeByIndexKer<<>>( + insubimgCud, insubimgCud, label); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + return NO_ERROR; +} + +// Kernel 函数: _setLabelByValueKer(根据特征值进行形状区域拷贝) +static __global__ void _setLabelByValueKer( + int *rank, int value, unsigned char *flaglabel) +{ + // 获取线程号。 + int tid = threadIdx.x; + + // 如果特征值等于 value,将其对应 flag 标记设为1。 + if (rank[2 * tid] == value) + flaglabel[rank[2 * tid + 1]] = 1; +} + +// Kernel 函数: _selectShapeByValueKer(根据特征值进行形状区域拷贝) +static __global__ void _selectShapeByValueKer( + ImageCuda inimg, ImageCuda outimg, unsigned char *flaglabel) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + int tid = blockDim.x * threadIdx.y + threadIdx.x; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 将 flag 数组保存到 share 内存中,加快读取速度。 + __shared__ unsigned char shared[MAX_LABEL]; + // 将 flag 数组拷贝到 share 内存中。 + shared[tid & ((1 << 8)-1)] = flaglabel[tid & ((1 << 8)-1)]; + __syncthreads(); + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素点. + // 如果输入图像的该像素值对应的 flag 等于1, 则将其拷贝到输出图像中; + // 否则将输出图像的该像素值设为 0。 + // 线程中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = (shared[intemp] == 1 ? intemp : 0); + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 如果输入图像的该像素值对应的 flag 等于1, 则将其拷贝到输出图像中; + // 否则将输出图像的该像素值设为0。 + outimg.imgMeta.imgData[outidx] = (shared[intemp] == 1 ? intemp : 0); + } +} + +// Kernel 函数: _shapeClearByLabelKer(清除区域标记) +static __global__ void _shapeClearByLabelKer( + ImageCuda inimg, unsigned char *flaglabel) +{ + // 计算想成对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + + // 一个线程处理四个像素点. + // 如果输入图像的该像素值对应的 flag 等于 0, 则将像素值设为 0; + // 否则保持原来的像素值。 + // 线程中处理的第一个点。 + if (flaglabel[inimg.imgMeta.imgData[inidx]] == 0) + inimg.imgMeta.imgData[inidx] = 0; + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++r >= inimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + + // 如果输入图像的该像素值对应的 flag 等于 0, 则将像素值设为 0; + // 否则保持原来的像素值。 + if (flaglabel[inimg.imgMeta.imgData[inidx]] == 0) + inimg.imgMeta.imgData[inidx] = 0; + } +} + +// Host 成员方法:selectShapeByValue(根据特征值查找形状) +__host__ int SelectShape::selectShapeByValue(Image *inimg, Image *outimg) +{ + // 检查图像是否为 NULL。 + if (inimg == NULL) + return NULL_POINTER; + + // 检查 rank 数组是否为空。 + if (this->rank == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + int *alldevicedata; + unsigned char *devflaglabel; + int *devRank; + cudaerrcode = cudaMalloc((void** )&alldevicedata, + (2 * this->pairsnum + MAX_LABEL) * sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevicedata, 0, + (2 * this->pairsnum + MAX_LABEL) + * sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 通过偏移读取 devRank 内存空间。 + devRank = alldevicedata; + // 将 Host 上的 rank 拷贝到 Device 上的 devRank 中。 + cudaerrcode = cudaMemcpy(devRank, this->rank, + 2 * this->pairsnum * sizeof (int), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 通过偏移读取 devflaglabel 内存空间。 + devflaglabel = (unsigned char*)(alldevicedata + 2 * this->pairsnum); + + // 调用核函数,在 devRank数组中查询 value 值,并获取其标记值。 + _setLabelByValueKer<<<1, this->pairsnum>>>( + devRank, this->value, devflaglabel); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 如果输入图像不等于输出图像,并且输出图像不为空。 + if (inimg != outimg && outimg != NULL) { + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入 + // 图像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据特征值 value 进行形状区域拷贝。 + _selectShapeByValueKer<<>>( + insubimgCud, outsubimgCud, devflaglabel); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + // 如果输入图像等于输出图像,或者输出图像为空。 + } else { + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据特征值 value 进行形状区域拷贝。 + _shapeClearByLabelKer<<>>( + insubimgCud, devflaglabel); + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + } + // 释放 Device 上的临时空间 alldevicedata。 + cudaFree(alldevicedata); + return NO_ERROR; +} + +// Kernel 函数: _setLabelByMinMaxKer(根据最大最小范围进行形状区域拷贝) +static __global__ void _setLabelByMinMaxKer( + int *rank, int minvalue, int maxvalue, + unsigned char *flaglabel) +{ + // 获取线程号。 + int tid = threadIdx.x; + + // 如果特征值在最小最大范围内,将其对应 flag 标记设为 1。 + if (rank[2 * tid] >= minvalue && rank[2 * tid] <= maxvalue) + flaglabel[rank[2 * tid + 1]] = 1; +} + +// Host 成员方法:selectShapeByMinMax(根据特征值最大最小范围查找形状) +__host__ int SelectShape::selectShapeByMinMax(Image *inimg, Image *outimg) +{ + // 检查图像是否为 NULL。 + if (inimg == NULL) + return NULL_POINTER; + + // 检查 rank 数组是否为空。 + if (this->rank == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为输 + // 入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 在 Device 上分配临时空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + int *alldevicedata; + unsigned char *devflaglabel; + int *devRank; + cudaerrcode = cudaMalloc((void** )&alldevicedata, + (2 * this->pairsnum + MAX_LABEL) + * sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 初始化 Device 上的内存空间。 + cudaerrcode = cudaMemset(alldevicedata, 0, + (2 * this->pairsnum + MAX_LABEL) + * sizeof (int)); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 通过偏移读取 devRank 内存空间。 + devRank = alldevicedata; + // 将 Host 上的 rank 拷贝到 Device 上的 devRank 中。 + cudaerrcode = cudaMemcpy(devRank, this->rank, + 2 * this->pairsnum * sizeof (int), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) + return cudaerrcode; + + // 通过偏移读取 devflaglabel 内存空间。 + devflaglabel = (unsigned char*)(alldevicedata + 2 * this->pairsnum); + + // 调用核函数,在 devRank数组中查询 在最大最小范围内的 value 值, + // 并获取其标记值。 + _setLabelByMinMaxKer<<<1, this->pairsnum>>>( + devRank, this->minvalue, this->maxvalue, devflaglabel); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 如果输入图像不等于输出图像,并且输出图像不为空。 + if (inimg != outimg && outimg != NULL) { + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入 + // 图像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据特征值标记进行形状区域拷贝。 + _selectShapeByValueKer<<>>( + insubimgCud, outsubimgCud, devflaglabel); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 如果输入图像等于输出图像,或者输出图像为空。 + } else { + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (insubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (insubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据特征值标记进行形状区域拷贝。 + _shapeClearByLabelKer<<>>( + insubimgCud, devflaglabel); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + } + // 释放 Device 上的临时空间 alldevicedata。 + cudaFree(alldevicedata); + + return NO_ERROR; +} diff --git a/okano_3_0/SelectShape.h b/okano_3_0/SelectShape.h new file mode 100644 index 0000000..c4c7b31 --- /dev/null +++ b/okano_3_0/SelectShape.h @@ -0,0 +1,280 @@ +// SelectShape.h +// 创建人:刘宇 +// +// 形状选择(SelectShape) +// 功能说明:根据输入参数不同选择满足条件的形状区域。该算法一共包括三种不同的 +// 实现:(1)根据根据特征数组的下标索引;(2)根据形状特征值进行查 +// 找;(3)根据特征值的最大到最小的范围进行查找。 +// +// 修订历史: +// 2012年08月10日 (刘宇) +// 初始版本。 +// 2012年08月28日 (刘宇) +// 完善注释规范。 +// 2012年09月06日 (刘宇) +// 修改 ROI 处理。 +// 2012年10月25日 (刘宇) +// 修正 __device__ 方法的定义位置和 blocksize 和 gridsize 中一处潜在的错误 +// 2012年11月13日(刘宇) +// 在核函数执行后添加 cudaGetLastError 判断语句 +// 2012年11月23日(刘宇) +// 添加输入输出参数的空指针判断 + +#ifndef __SELECTSHAPE_H__ +#define __SELECTSHAPE_H__ + +#include "Image.h" +#include "ErrorCode.h" + + +// 类:SelectShape(选择形状算法类) +// 继承自:无 +// 该类包含了对图像各个区域的形状进行选取的操作。包括根据特征数组的下标索引; +// 包括根据形状特征值进行查找;以及包括根据特征值的最大到最小的范围进行查找; +// 一个图像上可以有很多形状区域,区域上的所有点具有相同的标记 label ,区域的 +// 特征值有多种,例如面积等。 +class SelectShape { + +protected: + + // 成员变量:rank(形状特征值数组) + // 形状特征值数组,以(特征值,标记)的键值对形式存储的形状特征值数组。 + int *rank; + + // 成员变量:pairsnum(特征数组中键值对的个数) + // 特征值数组中键值对的个数,即形状区域的个数。 + int pairsnum; + + // 成员变量:index(下标索引值) + // 特征值数组中标记 label 的下标索引值。 + int index; + + // 成员变量:value(查找的特征值大小) + // 查找特征值数组时的特征值大小。 + int value; + + // 成员变量:minvalue 和 maxvalue(查找的最小和最大特征值) + // 查找特征值数组时的特征值最大最小的范围。 + int minvalue, maxvalue; + +public: + + // 构造函数:SelectShape + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + SelectShape() + { + // 使用默认值为类的各个成员变量赋值。 + rank = NULL; // 形状特征值数组默认为空 + pairsnum = 0; // 键值对的个数默认为 0 + index = 0; // 下标索引值默认为 0 + value = 0; // 查找的特征值大小默认为 0 + minvalue = 0; // 查找的最小特征值默认为 0 + maxvalue = 0; // 查找的最大特征值默认为 0 + } + + // 构造函数:SelectShape + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + SelectShape( + int *rank, // 形状特征值数组 + int pairsnum, // 特征数组中键值对的个数 + int index, // 下标索引值 + int value, // 查找的特征值大小 + int minvalue, int maxvalue // 查找的最小和最大特征值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法的初 + // 始值而使系统进入一个未知的状态。 + this->rank = NULL; // 形状特征值数组默认为空 + this->pairsnum = 0; // 键值对的个数默认为 0 + this->index = 0; // 下标索引值默认为 0 + this->value = 0; // 查找的特征值大小默认为 0 + this->minvalue = 0; // 查找的最小特征值默认为 0 + this->maxvalue = 0; // 查找的最大特征值默认为 0 + + // 根据参数列表中的值设定成员变量的初值 + setRank(rank); + setPairsnum(pairsnum); + setIndex(index); + setValue(value); + setMinvalue(minvalue); + setMaxvalue(maxvalue); + } + + // 成员方法:getRank(读取特征值数组) + // 读取 rank 成员变量的值。 + __host__ __device__ int * // 返回值:当前 rank 成员变量的值。 + getRank() const + { + // 返回 rank 成员变量的值。 + return this->rank; + } + + // 成员方法:setRank(设置特征值数组) + // 设置 rank 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setRank( + int *rank // 指定的特征值数组。 + ) { + // 检查输入参数是否合法 + if (rank == NULL) + return INVALID_DATA; + + // 将 rank 成员变量赋成新值 + this->rank = rank; + + return NO_ERROR; + } + + // 成员方法:getpairsnum(读取特征值数组中键值对的个数) + // 读取 pairsnum 成员变量的值。 + __host__ __device__ int // 返回值:当前 pairsnum 成员变量的值。 + getPairsnum() const + { + // 返回 parisNum 成员变量的值。 + return this->pairsnum; + } + + // 成员方法:setpairsnum(设置特征值数组中键值对的个数) + // 设置 pairsnum 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setPairsnum( + int pairsnum // 指定的特征值数组中键值对的个数。 + ) { + // 检查输入参数是否合法 + if (pairsnum == 0) + return INVALID_DATA; + + // 将 pairsnum 成员变量赋成新值 + this->pairsnum = pairsnum; + + return NO_ERROR; + } + + // 成员方法:getIndex(读取特征值数组中下标索引值) + // 读取 index 成员变量的值。 + __host__ __device__ int // 返回值:当前 index 成员变量的值。 + getIndex() const + { + // 返回 index 成员变量的值。 + return this->index; + } + + // 成员方法:setIndex(设置特征值数组中下标索引值) + // 设置 index 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setIndex( + int index // 指定的特征值数组中下标索引值。 + ) { + // 将 index 成员变量赋成新值 + this->index = index; + + return NO_ERROR; + } + + // 成员方法:getValue(读取查找特征值数组时的特征值大小) + // 读取 value 成员变量的值。 + __host__ __device__ int // 返回值:当前 value 成员变量的值。 + getValue() const + { + // 返回 value 成员变量的值。 + return this->value; + } + + // 成员方法:setValue(设置查找特征值数组时的特征值大小) + // 设置 value 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setValue( + int value // 指定的查找特征值数组时的特征值大小。 + ) { + // 将 value 成员变量赋成新值 + this->value = value; + + return NO_ERROR; + } + + // 成员方法:getminvalue(读取查找特征值数组时的最小特征值) + // 读取 minvalue 成员变量的值。 + __host__ __device__ int // 返回值:当前 minvalue 成员变量的值。 + getMinvalue() const + { + // 返回 minvalue 成员变量的值。 + return this->minvalue; + } + + // 成员方法:setminvalue(设置查找特征值数组时的最小特征值) + // 设置 minvalue 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMinvalue( + int minvalue // 指定的查找特征值数组时的最小特征值。 + ) { + // 将 minvalue 成员变量赋成新值 + this->minvalue = minvalue; + + return NO_ERROR; + } + + // 成员方法:getmaxvalue(读取查找特征值数组时的最大特征值) + // 读取 maxvalue 成员变量的值。 + __host__ __device__ int // 返回值:当前 maxvalue 成员变量的值。 + getMaxvalue() const + { + // 返回 maxvalue 成员变量的值。 + return this->maxvalue; + } + + // 成员方法:setmaxvalue(设置查找特征值数组时的最大特征值) + // 设置 maxvalue 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setMaxvalue( + int maxvalue // 指定的查找特征值数组时的最大特征值。 + ) { + // 将 maxvalue 成员变量赋成新值 + this->maxvalue = maxvalue; + + return NO_ERROR; + } + + // Host 成员方法:selectShapeByIndex(根据标记索引形状) + // 根据参数 index ,索引特征值数组 rank 中的标记值,将满足条件的形状区域拷贝 + // 到输出图像中。如果输出图像 outimg 为空或者等于输入图像 inimg,则将输出结 + // 果覆盖到输入图像 inimg上。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + selectShapeByIndex( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); + + // Host 成员方法:selectShapeByValue(根据特征值查找形状) + // 根据参数 value ,查找特征值数组 rank 中的特征值,将满足条件的形状区域拷贝 + // 到输出图像中,可能有多个区域。如果输出图像 outimg 为空或者等于输入图像 + // inimg,则将输出结果覆盖到输入图像 inimg上。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + selectShapeByValue( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); + + // Host 成员方法:selectShapeByMinMax(根据特征值最大最小范围查找形状) + // 根据参数 minvalue 和 maxvalue ,查找特征值数组 rank 中的特征值,将满足 + // 条件的形状区域拷贝到输出图像中,可能有多个区域。如果输出图像 outimg 为空 + // 或者等于输入图像inimg,则将输出结果覆盖到输入图像 inimg上。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + selectShapeByMinMax( + Image *inimg, // 输入图像。 + Image *outimg // 输出图像。 + ); +}; + + +#endif diff --git a/okano_3_0/SimpleBrightnessGradient.cu b/okano_3_0/SimpleBrightnessGradient.cu new file mode 100644 index 0000000..6c213c4 --- /dev/null +++ b/okano_3_0/SimpleBrightnessGradient.cu @@ -0,0 +1,143 @@ +// SimpleBrightnessGradient.cu +// 实现简单亮度渐变算法 + +#include "SimpleBrightnessGradient.h" +#include + +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 16 +#define DEF_BLOCK_Y 16 + +// Kernel 函数: SimpleBrightnessGradient (简单亮度渐变) +static __global__ void // Kernel 函数无返回值 +_simpleBrightnessGradientKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg // 输出图像 +); + +// Kernel 函数:SimpleBrightnessGradient(简单亮度渐变) +static __global__ void _simpleBrightnessGradientKer(ImageCuda inimg, + ImageCuda outimg) +{ + // c 和 r 分别表示线程处理的像素点的坐标的 x 和 y 分量 + //(其中,c 表示 column, r 表示 row)。由于采用并行度缩减策略 , + // 令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行上, + // 因此,对于r 需要进行乘 4 的计算 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理, + //一方面节省计算资源,一方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + + // 计算输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + + // 读取输入坐标点对应的像素值 + unsigned char inpixel; + inpixel = inimg.imgMeta.imgData[inidx]; + + unsigned char intemp; + const unsigned char temp = sqrtf(inimg.imgMeta.width * + inimg.imgMeta.width + + inimg.imgMeta.height * inimg.imgMeta.height); + intemp = (1 - (1 / temp) * sqrtf(c * c + r * r)) * 255 + + (1 / temp) * sqrtf(c * c + r * r) * inpixel; + + //输出图像像素 + outimg.imgMeta.imgData[outidx] = intemp; + + // 处理剩下的 3 个点 + for(int i = 1; i < 4; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查 + if (r + i > inimg.imgMeta.height) + return; + + // 获取当前列的下一行的位置指针 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + + intemp = (1 - (1 / temp) * sqrtf(c * c + r * r)) * 255 + + (1 / temp) * sqrtf(c * c + r * r) * inpixel; + + //输出图像像素 + outimg.imgMeta.imgData[outidx] = intemp; + } +} + +// Host 成员方法: SimpleBrightnessGradient(简单亮度渐变) +__host__ int SimpleBrightnessGradient::simpleBrightnessGradient(Image *inimg, + Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 计算 roi 子图的宽和高 + int roiwidth = inimg->roiX2 - inimg->roiX1; + int roiheight = inimg->roiY2 - inimg->roiY1; + + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice(outimg, roiwidth, + roiheight); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / + blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数 + _simpleBrightnessGradientKer<<>>(insubimgCud, + outsubimgCud); + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + diff --git a/okano_3_0/SimpleBrightnessGradient.h b/okano_3_0/SimpleBrightnessGradient.h new file mode 100644 index 0000000..211974e --- /dev/null +++ b/okano_3_0/SimpleBrightnessGradient.h @@ -0,0 +1,46 @@ +// EdgeDetection.h +// 创建者:焦利颖 +// +// 算法名称:简单亮度渐变(SimpleBrightnessGradient) +// 功能说明:模拟图像左上角增加一光源,  +// 使得图像从左上角到右下角亮度逐渐变化 +// G2 = W * 255 + (1-W)* G1;(0<=W<=1) +// 此处 G2 为目标灰度值,G1 为原始灰度值; +// W 为权重,离光源越远权重越小 +// (核函数内开方函数: sqrtf(x);) +// +// 修订历史: +// 2014年09月23日(焦利颖) +// 初始版本。 + +#ifndef __SIMPLEBRIGHTNESSGRADIENT_H__ +#define __SIMPLEBRIGHTNESSGRADIENT_H__ + +#include "Image.h" + +// 类:SimpleBrightnessGradient (亮度渐变类) +// 继承自:无 +// 该类定义了简单亮度渐变的方法 +class SimpleBrightnessGradient { +public: + + // 构造函数:SimpleBrightnessGradient + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值 + __host__ __device__ + SimpleBrightnessGradient() + { + // 无任何操作。 + } + + // Host 成员方法:SimpleBrightnessGradient(亮度渐变) + // 实现图像的亮度渐变 + __host__ int // 返回值:若函数正确执行,返回 NO_ERROR, + //     否则返回相应的错误码 + simpleBrightnessGradient( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/SimpleRegionDetect.cu b/okano_3_0/SimpleRegionDetect.cu new file mode 100644 index 0000000..915da3b --- /dev/null +++ b/okano_3_0/SimpleRegionDetect.cu @@ -0,0 +1,348 @@ +// SimpleRegionDetect.cu +// 实现图像的阈值分割 + +#include "SimpleRegionDetect.h" +#include "BilateralFilter.h" +#include "BilinearInterpolation.h" +#include "SmallestDirRect.h" +#include "ConnectRegion.h" +#include "FreckleFilter.h" +#include "Morphology.h" +#include "Image.h" +#include "ImageDiff.h" +#include "DownSampleImage.h" +#include "LocalCluster.h" +#include "TemplateFactory.h" +#include "Histogram.h" +#include "Threshold.h" + +#include +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_WRITE_IMG +// 定义是否将标记分割后的图像写入文件,默认关闭 +// #define DEF_WRITE_IMG + +// 宏:DEF_MAX_RATIO +// 定义长短径的最大比例,默认为 20,超出则认为不是所需的区域 +#define DEF_MAX_RATIO 20 + + +// Host 成员方法:hasValidRect(检测区域长短径函数) +__host__ int SimpleRegionDetect::hasValidRect(Image *inimg, bool *result) +{ + // 若指针未初始化,返回空指针异常 + if (result == NULL) + return NULL_POINTER; + + // 提取输入图像的子图像 + ImageCuda insubimgCud; + int errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + // 若图像数据在设备端,直接退出 + if (insubimgCud.deviceId >= 0) { + errcode = ImageBasicOp::copyToHost(inimg); + if (errcode != NO_ERROR) + return errcode; + } + int x1 = insubimgCud.imgMeta.width, x2 = 0, + y1 = insubimgCud.imgMeta.height, y2 = 0; + for (int i = 0; i < insubimgCud.imgMeta.width; i++) { + for (int j = 0; j < insubimgCud.imgMeta.height;j++) { + // 计算坐标对应点的位置 + int inidx = j * insubimgCud.pitchBytes + i; + // 像素值不为 0 时,比较其坐标信息 + if (insubimgCud.imgMeta.imgData[inidx]) { + x1 = i < x1 ? i : x1; + x2 = i > x2 ? i : x2; + y1 = j < y1 ? j : y1; + y2 = j > y2 ? j : y2; + } + } + } + // 矩形的宽度,保证为非负整数 + int width = x2 > x1 ? x2 - x1 : x1 - x2; + // 矩形的高度 + int height = y2 > y1 ? y2 - y1 : y1 - y2; + // 二者之中有一个为 0 时,找不到符合要求的矩形 + if (width == 0 || height == 0) { + *result = false; + return NO_ERROR; + } + + // 计算长径和短径的比例 + int ratio = width > height ? width / height : height / width; + // 返回最后的比较结果 + *result = (ratio <= DEF_MAX_RATIO) ? true : false; + return NO_ERROR; +} + +// Host 成员方法:cutImages(图像分割函数) +__host__ int SimpleRegionDetect::cutImages(Image *inimg, Image *outimg, + int closeSize, DirectedRect **rects, int *count) +{ + // 直方图统计表 + unsigned int gram[256]; + // 错误代码 + int errcode; + // 外接矩形的检出数量 + *count = 0; + + errcode = hist.histogram(inimg, gram); + if (errcode != NO_ERROR) + return errcode; + + // 图像有效区域的实际宽度 + int actualwidth = outimg->roiX2 - outimg->roiX1; + // 图像有效区域的实际高度 + int actualheight = outimg->roiY2 - outimg->roiY1; + // 灰度分割的存储临时数据的图像 + Image* temp; + errcode = ImageBasicOp::newImage(&temp); + if (errcode != NO_ERROR) + return errcode; + errcode = ImageBasicOp::makeAtCurrentDevice(temp, actualwidth, + actualheight); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + return errcode; + } + + for (int i = 1; i < 256; i++) { + // 若像素值对应的统计数大于 0,进行阈值分割 + if (gram[i] > 0) { + // 将最大最小阈值设置为同一值,进行单阈值分割 + errcode = th.setMaxPixelVal(i); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + return errcode; + } + errcode = th.setMinPixelVal(i); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + return errcode; + } + errcode = th.threshold(inimg, temp, 0, 255); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + return errcode; + } + + // 对分割后的图像进行 CLOSE 运算 + errcode = morph.close(temp, outimg); + // 若运算失败,将模板放回 + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + TemplateFactory::putTemplate(tpl); + return errcode; + } + + // 检测包含区域矩形是否符合要求 + bool isvalid = false; + hasValidRect(outimg, &isvalid); + +#ifdef DEF_WRITE_IMG + // 将符合要求的图像写入文件 + if (isvalid) { + // 格式化文件名,并将其保存至当前目录的 imgs 目录下 + char filename[128]; + sprintf(filename, "imgs/th_%d.bmp", i); + ImageBasicOp::writeToFile(filename, outimg); + } +#endif + + // 对符合要求的图像检测其最小外接矩形 + if (isvalid) { + // 使用最小外接矩形类检出,由于是二值图像,对前景区域检出即可 + errcode = sdr.smallestDirRect(outimg, rects[*count], true); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + TemplateFactory::putTemplate(tpl); + return errcode; + } + + // 外接矩形数目增加 + (*count)++; + } + } + } + // 正常结束时也需将模板放回 + TemplateFactory::putTemplate(tpl); + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(temp); + return NO_ERROR; +} + +// Host 成员方法:detectRegion(区域检测) +__host__ int SimpleRegionDetect::detectRegion( + Image *inimg, DirectedRect **regionsdetect, int *regioncount) +{ + // 若指针未初始化,返回空指针异常 + if (regionsdetect == NULL || regioncount == NULL) + return NULL_POINTER; + + // 错误代码 + int errcode; + + // 保存中间数据的临时图像,其中 tempimg1 和 tempimg2 交替使用 + Image *tempimg1, *tempimg2; + + errcode = ImageBasicOp::newImage(&tempimg1); + if (errcode != NO_ERROR) + return errcode; + errcode = ImageBasicOp::makeAtCurrentDevice(tempimg1, inimg->width, + inimg->height); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + return errcode; + } + + errcode = ImageBasicOp::newImage(&tempimg2); + if (errcode != NO_ERROR) + return errcode; + errcode = ImageBasicOp::makeAtCurrentDevice(tempimg2, inimg->width, + inimg->height); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + int scale = this->getScale(); + // 若缩小倍数不合法,返回错误码退出 + if (scale < 1) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return INVALID_DATA; + } + + // 调整大图的 roi 以接收缩小后的图像 + tempimg1->roiX2 = inimg->width / scale; + tempimg1->roiY2 = inimg->height / scale; + + // 初始化图像缩小类,并调用其中的 Dominance 缩小方法 + errcode = downimg.dominanceDownSImg(inimg, tempimg1); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 若 FreckleFilter 的滤波半径合法,则调用 FreckFilter 对缩小图像优化,需要 + // 注意的是若设置了 radius,需保证其 varThreshold 和 matchErrThreshold 也已 + // 正确设置 + if (this->getRadius() > 0) { + // 此处使用 tempimg2 来接收可选算法的输出 + tempimg2->roiX2 = inimg->width / scale; + tempimg2->roiY2 = inimg->height / scale; + // 若调用出错则返回出错代码并退出 + errcode = freckle.freckleFilter(tempimg1, tempimg2); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 交换图像指针,这样不会对下一步的调用产生影响 + Image *tempimg = tempimg1; + tempimg1 = tempimg2; + tempimg2 = tempimg; + } + // 将 roi 设置为正常大小,以接收上一步放大的图像 + tempimg2->roiX2 = inimg->width; + tempimg2->roiY2 = inimg->height; + // 使用双线性插值方式进行图像的放大 + errcode = biInterpo.setScale(scale); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + // 需要根据上一步的参数来决定输入图像 + errcode = biInterpo.doInterpolation(tempimg1, tempimg2); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 以插值放大后的图像为背景做图像的差分 + tempimg1->roiX2 = inimg->width; + tempimg1->roiY2 = inimg->height; + errcode = diff.imageDiff(inimg, tempimg2, tempimg1); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 对差分图像 调用 local clustering 算法去除其中的不明显区域 + errcode = cluster.localCluster(tempimg1, tempimg2); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 若滤波半径以及重复次数均合法,调用双边滤波进行优化 + if (this->getFilterRadius() > 0 && this->getRepeat() > 0) { + errcode = biFilter.doFilter(tempimg2, tempimg1); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 交换图像指针,这样不会对下一步的调用产生影响 + Image *tempimg = tempimg1; + tempimg1 = tempimg2; + tempimg2 = tempimg; + } + + // 调用连通域标记算法进行标记 + errcode = conn.connectRegion(tempimg2, tempimg1); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 对标记后的图像进行分割并检出其最小有向外接矩形 + errcode = cutImages(tempimg1, tempimg2, closeSize, regionsdetect, + regioncount); + if (errcode != NO_ERROR) { + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + return errcode; + } + + // 释放申请的空间,防止内存泄露 + ImageBasicOp::deleteImage(tempimg1); + ImageBasicOp::deleteImage(tempimg2); + + return NO_ERROR; +} diff --git a/okano_3_0/SimpleRegionDetect.h b/okano_3_0/SimpleRegionDetect.h new file mode 100644 index 0000000..7ab5415 --- /dev/null +++ b/okano_3_0/SimpleRegionDetect.h @@ -0,0 +1,728 @@ +// SimpleRegionDetect.h +// 创建人:邓建平 +// +// 简单的区域检测(SimpleRegionDetect) +// 功能说明:根据给出的区域大小以及指定的长短径要求,对输入图像进行一些预处理 +// 后使用连通域合并与标记,对于符合面积要求的已标记连通域求出其最小 +// 外接矩形,最终返回符合长短径要求的最小外接矩形的结构化表示,在图 +// 像的预处理过程中,可以通过设定对应参数来确定是否调用某些算法来对 +// 前一个处理步骤的输出图像进行优化,例如通过设置repeat参数可以设置 +// 是否使用双边滤波,还可以设置radius来确定是否使用中值滤波 +// +// 修订历史: +// 2012年09月22日(邓建平) +// 初始版本 +// 2012年10月12日(邓建平) +// 使用全局存储保存高斯表,加入运行时间测试 +// 2012年10月17日(邓建平) +// 将欧氏距离使用表结构存储在全局存储中,时间提高明显,同时将滤波半径设置 +// 2013年01月07日(邓建平) +// 算法整合完毕,可输出有效数据,完成了简易区域检测的初始版本 +// 2013年04月17日(邓建平) +// 加入了新的 SmallestDirRect 算法(原 BoundingRect ),检测的结果准确性提 +// 高,稳定性更强 +// 2013年04月19日(邓建平) +// 修改了代码的一部分格式错误,将中间类的声明放在主类中,同时对一些函数的 +// 返回值以及命名进行了修改,提高了代码的可读性与健壮性 +// 2013年04月22日(邓建平) +// 修改了代码的格式错误,完善了错误检查 +// 2013年05月25日(邓建平) +// 解决了编译时的 warning 问题 + +#ifndef __SIMPLEREGIONDETECT_H__ +#define __SIMPLEREGIONDETECT_H__ + +#include "Image.h" +#include "Rectangle.h" +#include "SimpleRegionDetect.h" +#include "BilateralFilter.h" +#include "BilinearInterpolation.h" +#include "SmallestDirRect.h" +#include "ConnectRegion.h" +#include "FreckleFilter.h" +#include "Morphology.h" +#include "Image.h" +#include "ImageDiff.h" +#include "DownSampleImage.h" +#include "LocalCluster.h" +#include "TemplateFactory.h" +#include "Histogram.h" +#include "Threshold.h" +#include "ErrorCode.h" + +// 类:SimpleRegionDetect(简单的区域检测) +// 继承自:无 +// 根据给出的区域大小以及指定的长短径要求,对输入图像进行一些预处理后 +// 使用连通域合并与标记,对于符合面积要求的已标记连通域求出其最小外接 +// 矩形,最终返回符合长短径要求的最小外接矩形的结构化表示,在图像的预 +// 处理过程中,可以通过设定对应参数来确定是否调用某些算法来对前一个处 +// 理步骤的输出图像进行优化,例如通过设置 repeat 参数可以设置是否使用双 +// 边滤波,还可以设置 radius 来确定是否使用中值滤波 +class SimpleRegionDetect { + +protected: + + // 成员变量:sdr(最小外接矩形) + // 计算检出区域的最小有向外接矩形 + SmallestDirRect sdr; + + // 成员变量:hist(直方图统计) + // 使用直方图检测经连通域标记后的面积大小,以进行分割 + Histogram hist; + + // 成员变量:morph(形态学变换) + // 对分割后的图像进行 CLOSE 运算,提高区域的完整性 + Morphology morph; + + // 成员变量:th(阈值分割) + // 完成图像分割,把被标记的区域单独分割出来 + Threshold th; + + // 成员变量:downimg(图像缩小) + // 采用 DOMINANCE 方法对图像进行缩小 + DownSampleImage downimg; + + // 成员变量:freckle(FreckleFilter) + // 使用 FreckleFilter 对缩小后的图像进行滤波 + FreckleFilter freckle; + + // 成员变量:biInterpo(双线性插值) + // 完成双线性插值放大,基于 Texture 的硬件插值实现 + BilinearInterpolation biInterpo; + + // 成员变量:diff(图像差分) + // 用放大后的图像作为背景图像,计算原图与背景图像的差分图像 + ImageDiff diff; + + // 成员变量:cluster(Local Clustering) + // 对差分图像调用 Local Clustering 算法进行 clustering + LocalCluster cluster; + + // 成员变量:biFilter(双边滤波) + // 对差分图像进行双边滤波 + BilateralFilter biFilter; + + // 成员变量:conn(连通域标记) + // 标记差分后的图像中的连通区域 + ConnectRegion conn; + + // 成员变量:tpl(运算模板) + // 使用模板工厂获取 CLOSE 运算的模板 + Template *tpl; + + // 成员变量:longSide(有向外接矩形长径),shortSide(有向外接矩形短径) + // 这两个变量用于对检出的矩形进行筛选,分别表示长径的最大值和短径的最小值 + // 默认为 1 和 648,即不过滤 + int longSide, shortSide; + + // 成员变量:closeSize(闭运算的模板大小) + // 默认值为 1 ,参数设置必须合法有效,闭运算是区域检测算法中必不可少的一步 + int closeSize; + + // Host 成员方法:hasValidRect(检测区域长短径函数) + // 检测图像中包含区域的外接矩形的长短径,外接矩形的四个顶点取区域中点的最值 + // ,即以横坐标和纵坐标的最小值作为左上顶点,最大值作为右下顶点 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + hasValidRect( + Image *inimg, // 待检测图像 + bool *result // 检测结果,true 表示长短径符合要求,false 表示不符合 + ); + + // Host 成员方法:cutImages(图像分割函数) + // 对检出的连通域进行 0 - 255 LEVEL 的灰度分割,并检出其最小外接矩形 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + cutImages( + Image *inimg, // 连通域标记后的图像 + Image *outimg, // 闭运算的输出图像 + int closeSize, // 对分割后的图像进行 CLOSE 运算的模板大小 + DirectedRect **rects, // 检出的最小有向外接矩形 + int *count // 检出的外接矩形的数量 + ); + +public: + + // 构造函数:SimpleRegionDetect + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ + SimpleRegionDetect() + { + // 使用默认值为类的各个成员变量赋值 + longSide = 1; // 长径的默认值为 1 + shortSide = 648; // 短径的默认值为 648 + closeSize = 1; // 闭运算模板大小默认值为 1 + + // 配置连通域标记 conn + this->setMinArea(1000); // 最小区域面积的的默认值为 1000 + this->setMaxArea(100000); // 最大区域面积的的默认值为 100000 + this->setRegionTh(2); // 连通域标记的阈值默认为 2 ,测试效果好 + // 配置图像缩小类 downimg 和 双线性插值 biInterpo + this->setScale(1); // 缩小倍数默认值为 1 + // 配置 freckle 滤波器 freckle + this->freckle.setRadius(0); // FreckleFilter 中的圆周半径的默认值为 0 + // 配置 local clustering + this->setDest(100); // 坐标范围的默认值为 100 + this->setCount(8); // 方向数的默认值为 8 + this->setProBlack(10); // 黑色像素值的默认值为 10 + this->setProWhite(250); // 白色像素值的默认值为 250 + this->setGapThred(0); // 邻域内像素差值的阈值的默认值为 0 + this->setDiffeThred(0); // 差分阈值的默认值为 0 + // 配置双边滤波 biFilter + this->setRadius(0); // 滤波半径的默认值为 0 + this->setRepeat(0); // 重复次数的默认值为 0 + // 配置运算模板 tpl + this->setCloseSize(1); // 模板大小的默认值为 1 + } + + // 构造函数:SimpleRegionDetect + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ + SimpleRegionDetect( + int minarea, int maxarea, // 区域面积的最小和最大值 + int longside, int shortside, // 长径,短径 + int scale, // 缩小倍数 + unsigned char regionth, // 连通域标记阈值 + int dest, int count, // 坐标范围,方向数 + unsigned char problack, // 黑色像素值 + unsigned char prowhite, // 白色像素值 + unsigned char gapthred, // 中心像素与邻域像素差值的阈值 + unsigned char diffethred, // 差分阈值 + int closesize // 闭运算模板大小 + ) { + // 使用默认值为类的各个成员变量赋值 + longSide = 1; // 长径的默认值为 1 + shortSide = 648; // 短径的默认值为 648 + closeSize = 1; // 闭运算模板大小默认值为 1 + + // 配置连通域标记 conn + this->setMinArea(1000); // 最小区域面积的的默认值为 1000 + this->setMaxArea(100000); // 最大区域面积的的默认值为 100000 + this->setRegionTh(2); // 连通域标记的阈值默认为 2 ,测试效果好 + // 配置图像缩小类 downimg 和 双线性插值 biInterpo + this->setScale(1); // 缩小倍数默认值为 1 + // 配置 freckle 滤波器 freckle + this->freckle.setRadius(0); // FreckleFilter 中的圆周半径的默认值为 0 + // 配置 local clustering + this->setDest(100); // 坐标范围的默认值为 100 + this->setCount(8); // 方向数的默认值为 8 + this->setProBlack(10); // 黑色像素值的默认值为 10 + this->setProWhite(250); // 白色像素值的默认值为 250 + this->setGapThred(0); // 邻域内像素差值的阈值的默认值为 0 + this->setDiffeThred(0); // 差分阈值的默认值为 0 + // 配置双边滤波 biFilter + this->setRadius(0); // 滤波半径的默认值为 0 + this->setRepeat(0); // 重复次数的默认值为 0 + // 配置运算模板 tpl + this->setCloseSize(1); // 模板大小的默认值为 1 + + // 根据参数列表中的值设定成员变量的初值 + this->setMinArea(minarea); + this->setMaxArea(maxarea); + this->setLongSide(longside); + this->setShortSide(shortside); + this->setScale(scale); + this->setRegionTh(regionth); + this->setDest(dest); + this->setCount(count); + this->setProBlack(problack); + this->setProWhite(prowhite); + this->setGapThred(gapthred); + this->setDiffeThred(diffethred); + this->setCloseSize(closeSize); + } + + // 成员方法:getMinArea(读取连通域面积最小值) + // 读取成员变量 conn 中的 minArea 属性 + __host__ __device__ int // 返回: 当前 minArea 成员变量的值 + getMinArea() const + { + // 返回 minArea 成员变量的值 + return conn.getMinArea(); + } + + // 成员方法:setMinArea(设置连通域面积最小值) + // 设置成员变量 conn 中的 minArea 属性 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setMinArea( + int minarea // 连通域面积大小 + ) { + // 将 minArea 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (minarea < 0) + return INVALID_DATA; + + // 更新成员变量 conn 中的 minArea 属性 + return conn.setMinArea(minarea); + } + + // 成员方法:getMaxArea(读取连通域面积最大值) + // 读取成员变量 conn 中的 maxArea 属性 + __host__ __device__ int // 返回: 当前 maxArea 成员变量的值 + getMaxArea() const + { + // 返回 maxArea 成员变量的值 + return conn.getMaxArea(); + } + + // 成员方法:setMaxArea(设置连通域面积最大值) + // 设置成员变量 conn 中的 maxArea 属性 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setMaxArea( + int maxarea // 连通域面积大小 + ) { + // 将 maxArea 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (maxarea < this->getMinArea()) + return INVALID_DATA; + + // 更新成员变量 conn 中的 maxArea 属性 + return conn.setMaxArea(maxarea); + } + + // 成员方法:getLongSide(读取长径) + // 读取 longSide 成员变量的值。 + __host__ __device__ int // 返回: 当前 longSide 成员变量的值 + getLongSide() const + { + // 返回 longSide 成员变量的值 + return longSide; + } + + // 成员方法:setLongSide(设置长径) + // 设置 longSide 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setLongSide( + int longside // 长径 + ) { + // 将 longSide 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (longside < this->shortSide) + return INVALID_DATA; + + // 将 longSide 成员变量赋成新值 + this->longSide = longside; + return NO_ERROR; + } + + // 成员方法:getShortSide(读取短径) + // 读取 shortSide 成员变量的值。 + __host__ __device__ int // 返回: 当前 shortSide 成员变量的值 + getShortSide() const + { + // 返回 shortSide 成员变量的值 + return shortSide; + } + + // 成员方法:setShortSide(设置短径) + // 设置 shortSide 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setShortSide( + int shortside // 短径 + ) { + // 将 shortSide 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (shortside < 0) + return INVALID_DATA; + + // 将 shortSide 成员变量赋成新值 + this->shortSide = shortside; + return NO_ERROR; + } + + // 成员方法:getScale(读取缩小倍数) + // 读取成员变量 biInterpo 中的 scale 属性 + __host__ __device__ int // 返回: 当前 scale 成员变量的值 + getScale() const + { + // 返回 scale 成员变量的值 + return biInterpo.getScale(); + } + + // 成员方法:setRadius(设置缩小倍数) + // 设置 scale 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setScale( + int scale // 中值滤波圆周半径 + ) { + // 将 scale 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (scale < 1) + return INVALID_DATA; + + // 更新成员变量 biInterpo 中的 scale 属性 + int errcode = biInterpo.setScale(scale); + if(errcode != NO_ERROR) + return errcode; + // 更新成员变量 downimg 中的 times 属性 + errcode = downimg.setTimes(scale); + if(errcode != NO_ERROR) + return errcode; + return NO_ERROR; + } + + // 成员方法:getRadius(读取中值滤波圆周半径) + // 读取 radius 成员变量的值。 + __host__ __device__ int // 返回: 当前 radius 成员变量的值 + getRadius() const + { + // 返回 radius 成员变量的值 + return freckle.getRadius(); + } + + // 成员方法:setRadius(设置中值滤波圆周半径) + // 设置成员变量 freckle 中的 radius 属性 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setRadius( + int radius // 中值滤波圆周半径 + ) { + // 将 radius 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (radius < 0) + return INVALID_DATA; + + // 更新成员变量 freckle 中的 radius 属性 + return freckle.setRadius(radius); + } + + // 成员方法:getVarThreshold(读取方差阈值) + // 读取成员变量 freckle 中的 varThreshold 属性 + __host__ __device__ float // 返回: 当前 varThreshold 成员变量的值 + getVarThreshold() const + { + // 返回 varThreshold 成员变量的值 + return freckle.getVarTh(); + } + + // 成员方法:setVarThreshold(设置方差阈值) + // 设置成员变量 freckle 中的 varThreshold 属性 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setVarThreshold( + float varthreshold // 方差阈值 + ) { + // 将 varThreshold 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (varthreshold <= 0.0f || varthreshold > 1.0f) + return INVALID_DATA; + + // 更新成员变量 freckle 中的 varThreshold 属性 + return freckle.setVarTh(varthreshold); + } + + // 成员方法:getMatchErrThreshold(读取匹配差阈值) + // 读取成员变量 freckle 中的 matchErrThreshold 属性 + __host__ __device__ float // 返回: 当前 matchErrThreshold 成员变量的值 + getMatchErrThreshold() const + { + // 返回 matchErrThreshold 成员变量的值 + return freckle.getMatchErrTh(); + } + + // 成员方法:setMatchErrThreshold(设置匹配差阈值) + // 设置成员变量 freckle 中的 matchErrThreshold 属性 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setMatchErrThreshold( + float matcherrthreshold // 匹配差阈值 + ) { + // 将 matchErrThreshold 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (matcherrthreshold <= 0.0f || matcherrthreshold > 1.0f) + return INVALID_DATA; + + // 更新成员变量 freckle 中的 matchErrThreshold 属性 + return freckle.setMatchErrTh(matcherrthreshold); + } + + // 成员方法:getRegionTh(读取连通域标记的阈值) + // 读取 regionTh 成员变量的值。 + __host__ __device__ int // 返回: 当前 regionTh 成员变量的值 + getRegionTh() const + { + // 返回 regionTh 成员变量的值 + return conn.getThreshold(); + } + + // 成员方法:setRegionTh(设置连通域标记的阈值) + // 设置 regionTh 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setRegionTh( + unsigned char regionth // 匹配长度参数 + ) { + // 更新成员变量 conn 中的 regionTh 属性 + return conn.setThreshold(regionth); + } + + // 成员方法:getDest(读取 local clustering 各方向坐标范围) + // 读取 dest 成员变量的值。 + __host__ __device__ int // 返回: 当前 dest 成员变量的值 + getDest() const + { + // 返回 dest 成员变量的值 + return cluster.getPntRange(); + } + + // 成员方法:setDest(设置 local clustering 各方向坐标范围) + // 设置 dest 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setDest( + int dest // 各方向坐标范围 + ) { + // 将 dest 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (dest > 100) + return INVALID_DATA; + + // 更新成员变量 cluster 中的 dest 属性 + return cluster.setPntRange(dest); + } + + // 成员方法:getCount(读取 local clustering 方向数) + // 读取 count 成员变量的值。 + __host__ __device__ int // 返回: 当前 count 成员变量的值 + getCount() const + { + // 返回 count 成员变量的值 + return cluster.getPntCount(); + } + + // 成员方法:setCount(设置 local clustering 方向数) + // 设置 count 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setCount( + int count // local clustering 方向数 + ) { + // 将 count 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (count < 0 || count > 8) + return INVALID_DATA; + + // 更新成员变量 cluster 中的 count 属性 + return cluster.setPntCount(count); + } + + // 成员方法:getProBlack(读取黑色像素值) + // 读取 proBlack 成员变量的值。 + __host__ __device__ unsigned char // 返回: 当前 proBlack 成员变量 + // 的值 + getProBlack() const + { + // 返回 proBlack 成员变量的值 + return cluster.getProBlack(); + } + + // 成员方法:setProBlack(设置黑色像素值) + // 设置 proBlack 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 + setProBlack( + unsigned char problack // 黑色像素值 + ) { + // 更新成员变量 cluster 中的 proBlack 属性 + return cluster.setProBlack(problack); + } + + // 成员方法:getProWhite(读取白色像素值) + // 读取 proWhite 成员变量的值。 + __host__ __device__ unsigned char // 返回: 当前 proWhite 成员变量 + // 的值 + getProWhite() const + { + // 返回 proWhite 成员变量的值 + return cluster.getProWhite(); + } + + // 成员方法:setProWhite(设置白色像素值) + // 设置 proWhite 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正 + // 确执行,返回 NO_ERROR。 + setProWhite( + unsigned char prowhite // 白色像素值 + ) { + // 更新成员变量 cluster 中的 proWhite 属性 + return cluster.setProWhite(prowhite); + } + + // 成员方法:getGapThred(读取中心像素与邻域像素差值的阈值) + // 读取 gapThred 成员变量的值。 + __host__ __device__ unsigned char // 返回: 当前 gapThred 成员变量的值 + getGapThred() const + { + // 返回 gapThred 成员变量的值 + return cluster.getGapThred(); + } + + // 成员方法:setGapThred(设置中心像素与邻域像素差值的阈值) + // 设置 gapThred 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setGapThred( + unsigned char gapthred // 中心像素与邻域像素差值的阈值 + ) { + // 更新成员变量 cluster 中的 gapThred 属性 + return cluster.setGapThred(gapthred); + } + + // 成员方法:getDiffeThred(读取差分阈值) + // 读取 diffeThred 成员变量的值。 + __host__ __device__ unsigned char // 返回: 当前 diffeThred 成员变量的值 + getDiffeThred() const + { + // 返回 diffeThred 成员变量的值 + return cluster.getDiffeThred(); + } + + // 成员方法:setDiffeThred(设置差分阈值) + // 设置 diffeThred 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setDiffeThred( + unsigned char diffethred // 差分阈值 + ) { + // 更新成员变量 cluster 中的 diffeThred 属性 + return cluster.setDiffeThred(diffethred); + } + + // 成员方法:getSigmaSpace(读取空域参数) + // 读取 sigmaSpace 成员变量的值。 + __host__ float // 返回: 当前 sigmaSpace 成员变量的值 + getSigmaSpace() const + { + // 返回 sigmaSpace 成员变量的值 + return biFilter.getSigmaSpace(); + } + + // 成员方法:setSigmaSpace(设置空域参数) + // 设置 sigmaSpace 成员变量的值。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setSigmaSpace( + float sigmaspace // 空域参数 + ) { + // 更新成员变量 biFilter 中的 sigmaSpace 属性 + return biFilter.setSigmaSpace(sigmaspace); + } + + // 成员方法:getSigmaRange(读取颜色域参数) + // 读取 sigmaRange 成员变量的值。 + __host__ float // 返回: 当前 sigmaRange 成员变量的值 + getSigmaRange() const + { + // 返回 sigmaRange 成员变量的值 + return biFilter.getSigmaRange(); + } + + // 成员方法:setSigmaRange(设置颜色域参数) + // 设置 sigmaRange 成员变量的值。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setSigmaRange( + float sigmarange // 颜色域参数 + ) { + // 更新成员变量 biFilter 中的 sigmaRange 属性 + return biFilter.setSigmaRange(sigmarange); + } + + // 成员方法:getFilterRadius(读取滤波半径) + // 读取 filterRadius 成员变量的值。 + __host__ __device__ int // 返回: 当前 filterRadius 成员变量的值 + getFilterRadius() const + { + // 返回 filterRadius 成员变量的值 + return biFilter.getRadius(); + } + + // 成员方法:setFilterRadius(设置滤波半径) + // 设置 filterRadius 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setFilterRadius( + int filterradius // 颜色域参数 + ) { + // 将 filterRadius 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (filterradius < 0 || filterradius > DEF_FILTER_RANGE) + return INVALID_DATA; + + // 更新成员变量 biFilter 中的 filterRadius 属性 + return biFilter.setRadius(filterradius); + } + + // 成员方法:getRepeat(读取迭代次数) + // 读取 repeat 成员变量的值。 + __host__ __device__ int // 返回: 当前 repeat 成员变量的值 + getRepeat() const + { + // 返回 repeat 成员变量的值 + return biFilter.getRepeat(); + } + + // 成员方法:setRepeat(设置迭代次数) + // 设置 repeat 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setRepeat( + int repeat // 迭代次数 + ) { + // 将 repeat 成员变量赋成新值 + // 若设置的值不在区间内则属于非法数据 + if (repeat < 1) + return INVALID_DATA; + + // 更新成员变量 biFilter 中的 repeat 属性 + return biFilter.setRepeat(repeat); + } + + // 成员方法:getCloseSize(读取闭运算模板大小) + // 读取 closeSize 成员变量的值。 + __host__ __device__ int // 返回: 当前 length 成员变量的值 + getCloseSize() const + { + // 返回 closeSize 成员变量的值 + return closeSize; + } + + // 成员方法:setCloseSize(设置闭运算模板大小) + // 设置 closeSize 成员变量的值。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setCloseSize( + int closesize // 闭运算模板大小 + ) { + if(closesize < 0) + return INVALID_DATA; + int errcode = TemplateFactory::getTemplate(&tpl, TF_SHAPE_BOX, + closesize, NULL); + if (errcode != NO_ERROR) { + TemplateFactory::putTemplate(tpl); + return errcode; + } + this->closeSize = closesize; + return NO_ERROR; + } + + // Host 成员方法:detectRegion(区域检测) + // 对输入图像进行预处理之后使用检测算法匹配计算,将符合要求的最小外接有向矩 + // 形加入到方法的输出参数中 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + detectRegion( + Image *inimg, // 输入图像 + DirectedRect **regionsdetect, // 接收算法输出的有向矩形指针 + int *regioncount // 检测区域数目 + ); + +}; + +#endif diff --git a/okano_3_0/SmallestDirRect.cu b/okano_3_0/SmallestDirRect.cu new file mode 100644 index 0000000..724a9f4 --- /dev/null +++ b/okano_3_0/SmallestDirRect.cu @@ -0,0 +1,2021 @@ +// SmallestDirRect.cu +// 最小有向外接矩形实现。 + +#include "SmallestDirRect.h" +#include "CoordiSet.h" +#include +#include +#include +using namespace std; + +// 宏:SDR_BLOCKSIZE +// 定义了核函数线程块的大小。 +#define DEF_BLOCK_1D 512 + +// 宏:SDR_LARGE_ENOUGH +// 定义了一个足够大的正整数,该整数在使用过程中被认为是无穷大。 +#define SDR_LARGE_ENOUGH ((1 << 30) - 1) + +// 宏:SDR_DEBUG_KERNEL_PRINT(Kernel 调试打印开关) +// 打开该开关则会在 Kernel 运行时打印相关的信息,以参考调试程序;如果注释掉该 +// 宏,则 Kernel 不会打印这些信息,但这会有助于程序更快速的运行。 +//#define SDR_DEBUG_KERNEL_PRINT + +// Kernel 函数: _sdrComputeBoundInfoKer(计算凸壳点集中每相邻两点的旋转矩阵 +// 信息,进而计算新坐标系下凸壳的有向外接矩形的边界信息) +// 根据输入的凸壳点,计算顺时针相邻两点的构成的直线与 x 轴的角度,同时计算 +// 旋转矩阵信息。在此基础上,计算新坐标系下各点的坐标。从而计算每个有向外接 +// 矩形的边界点的坐标信息。 +static __global__ void // Kernel 函数无返回值。 +_sdrComputeBoundInfoKer( + CoordiSet convexcst, // 输入凸壳点集。 + RotationInfo rotateinfo[], // 输出,旋转矩阵信息数组。 + BoundBox bbox[] // 输出,找出的包围矩形的边界坐标信息数组。 +); + +// Kernel 函数: _sdrComputeSDRKer(计算包围矩形中面积最小的) +// 根据输入的目前的每个包围矩形的长短边长度,计算最小有向外接矩形的标号索引。 +static __global__ void // Kernel 函数无返回值。 +_sdrComputeSDRKer( + int cstcnt, // 输入,点集中点的数量。 + BoundBox bbox[], // 输入,找出的包围矩形的边界坐标信息。 + int *index // 输出,计算出的最小有向外接矩形的标号索引。 +); + +// Kernel 函数: _sdrComputeBoundInfoKer(计算凸壳点集中每相邻两点的旋转矩阵 +// 信息,进而计算新坐标系下凸壳的有向外接矩形的边界信息) +static __global__ void _sdrComputeBoundInfoKer( + CoordiSet convexcst, RotationInfo rotateinfo[], BoundBox bbox[]) +{ + // 当前 block 的索引,在 x 上 block 的索引表示各个凸壳点的索引。 + int r = blockIdx.x; + + // 检查索引值是否越界。如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (r >= convexcst.count) + return; + + // 当前凸壳点的下一个点的索引。 + int nextidx; + + // 当前点与下一点的 x、y 坐标差值。 + float deltax, deltay; + + // 当前点与下一点间距离。 + float sidelength; + + // 旋转角度的余弦值和正弦值。 + float cosalpha, sinalpha; + + // 声明 Shared Memory,并分配各个指针。 + extern __shared__ float shdmem[]; + float *shdcos = shdmem; + float *shdsin = shdcos + 1; + float *shdradian = shdsin + 1; + + if (threadIdx.x == 0) { + // 当前索引值加 1,求得下一点索引值。 + nextidx = r + 1; + + // 若当前点为点集中最后一点,则下一点为起始点。 + if (nextidx == convexcst.count) + nextidx = 0; + + // 计算当前点和下一点 x, y 坐标差值。 + deltax = convexcst.tplData[nextidx * 2] - + convexcst.tplData[r * 2]; + deltay = convexcst.tplData[nextidx * 2 + 1] - + convexcst.tplData[r * 2 + 1]; + + // 如果解的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (deltax < 0) { + deltax = -deltax; + deltay = -deltay; + } + + // 计算当前点和下一点间距离。 + sidelength = sqrtf(deltax * deltax + deltay * deltay); + + // 计算旋转角度的余弦、正弦值。 + cosalpha = deltax / sidelength; + sinalpha = deltay / sidelength; + + // 根据计算得到的正弦值计算角度,将旋转矩阵信息存入到 Shared Memory + // 和 Global Memory 参数 + rotateinfo[r].cos = shdcos[0] = cosalpha; + rotateinfo[r].sin = shdsin[0] = sinalpha; + rotateinfo[r].radian = shdradian[0] = asin(sinalpha); + } + + // 同步所有线程,使初始化 Shared Memory 的结果对所有线程可见。 + __syncthreads(); + + // 计算当前块内线程的下标。在 x 维上该 Kernel 计算边界值点, + // 必须以单 Block 运行,避免跨 block 同步引发的同步问题。 + int c = threadIdx.x; + + // 声明包围矩形。 + BoundBox tmpbbox; + + // 当前 Thread 处理的若干个点中找到的局部极值点。初始化。 + tmpbbox.left = tmpbbox.bottom = SDR_LARGE_ENOUGH; + tmpbbox.right = tmpbbox.top = -SDR_LARGE_ENOUGH; + + // 当前点在新坐标系下的新坐标。 + float curx, cury; + + // 迭代处理该线程所要处理的所有坐标点,这些坐标点是间隔 blockDim.x + // 个的各个坐标点。 + while (c < convexcst.count) { + // 从 Global Memory 中读取坐标值,从 Shared Memory 读取旋转信息值, + // 并计算当前点在新坐标系下的新坐标。 + curx = convexcst.tplData[2 * c] * shdcos[0] + + convexcst.tplData[2 * c + 1] * shdsin[0]; + + cury = convexcst.tplData[2 * c] * (-shdsin[0]) + + convexcst.tplData[2 * c + 1] * shdcos[0]; + + // 判断该坐标值的大小,和已经找到的极值做比较,更新极值。 + tmpbbox.left = min(tmpbbox.left, curx); + tmpbbox.right = max(tmpbbox.right, curx); + tmpbbox.bottom = min(tmpbbox.bottom, cury); + tmpbbox.top = max(tmpbbox.top, cury); + + // 更新 idx,在下一轮迭代时计算下一个点。 + c += blockDim.x; + } + + // 至此,所有 Thread 都得到了自己的局部极值,现在需要将极值放入 + // Shared Memory 中,以便下一步进行归约处理。 + + // 分配 Shared Memory 给各个指针。 + float *shdbboxleft = shdradian + 1; + float *shdbboxright = shdbboxleft + blockDim.x; + float *shdbboxbottom = shdbboxright + blockDim.x; + float *shdbboxtop = shdbboxbottom + blockDim.x; + + // 将局部结果拷贝到 Shared Memory 中。 + c = threadIdx.x; + shdbboxleft[c] = tmpbbox.left; + shdbboxright[c] = tmpbbox.right; + shdbboxbottom[c] = tmpbbox.bottom; + shdbboxtop[c] = tmpbbox.top; + + // 同步所有线程,使初始化Shared Memory 的结果对所有线程可见。 + __syncthreads(); + + // 下面进行折半归约迭代。这里要求 blockDim.x 必须为 2 的整数次幂。 + int currdsize = blockDim.x / 2; + // 和当前线程间隔 currdsize 位置处的索引。 + int inidx; + for (/*currdsize*/; currdsize >= 1; currdsize /= 2) { + if (c < currdsize) { + inidx = c + currdsize; + // 将两个局部结果归约成一个局部结果。 + shdbboxleft[c] = min(shdbboxleft[c], shdbboxleft[inidx]); + shdbboxright[c] = max(shdbboxright[c], shdbboxright[inidx]); + shdbboxbottom[c] = min(shdbboxbottom[c], shdbboxbottom[inidx]); + shdbboxtop[c] = max(shdbboxtop[c], shdbboxtop[inidx]); + } + + // 同步线程,使本轮迭代归约的结果对所有线程可见。 + __syncthreads(); + } + + // 打印当前的最值点,检查中间结果。 + if (c == 0) + // 调试打印。 +#ifdef SDR_DEBUG_KERNEL_PRINT + printf("Kernel[computeBdInf]:(%3d, %3d) LRBT (%7.3f,%7.3f,%7.3f,%7.3f)\n", + r, c, shdbboxleft[c], shdbboxright[c], shdbboxbottom[c], + shdbboxtop[c]); +#endif + + // 将边界值传递给 Global Memory 参数,每个线程块的第一个线程会进行这个操作。 + if (c == 0) { + bbox[r].left = shdbboxleft[c]; + bbox[r].right = shdbboxright[c]; + bbox[r].bottom = shdbboxbottom[c]; + bbox[r].top = shdbboxtop[c]; + } +} + +// Host 成员方法:sdrComputeBoundInfo(计算新坐标系下凸壳的有向外接矩形的边界信 +// 息) +__host__ int SmallestDirRect::sdrComputeBoundInfo( + CoordiSet *convexcst, RotationInfo rotateinfo[], + BoundBox bbox[]) +{ + // 检查坐标集和旋转矩阵是否为空,若为空则直接返回。 + if (convexcst == NULL || rotateinfo == NULL || bbox == NULL) + return NULL_POINTER; + + // 如果输入点集中不含有任何的坐标点,则直接退出。 + if (convexcst->count < 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将 convexcst 拷贝到 Device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 计算启动 Kernel 函数所需要的 Block 尺寸与数量。 + size_t blocksize; + blocksize = DEF_BLOCK_1D; + + size_t gridsize; + gridsize = DEF_BLOCK_1D; + + // 分配共享内存大小。 + int sharedmemsize = (4 * DEF_BLOCK_1D + 3) * sizeof (float); + + // 启动 Kernel 函数,完成计算。 + _sdrComputeBoundInfoKer<<>>( + *convexcst, rotateinfo, bbox); + + // 检查 Kernel 函数执行是否正确。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 运行完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:sdrComputeBoundInfoCpu(计算新坐标系下凸壳的有向外接矩形的边界 +// 信息) +__host__ int SmallestDirRect::sdrComputeBoundInfoCpu( + CoordiSet *convexcst, RotationInfo rotateinfo[], + BoundBox bbox[]) +{ + // 检查坐标集和旋转矩阵是否为空,若为空则直接返回。 + if (convexcst == NULL || rotateinfo == NULL || bbox == NULL) + return NULL_POINTER; + + // 如果输入点集中不含有任何的坐标点,则直接退出。 + if (convexcst->count < 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 将 convexcst 拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) + return errcode; + + int idx; + int cstidx; + // 当前凸壳点的下一个点的索引。 + int nextidx; + + // 当前点与下一点的 x、y 坐标差值。 + float deltax, deltay; + + // 当前点与下一点间距离。 + float sidelength; + + // 旋转角度的余弦值和正弦值。 + float cosalpha, sinalpha; + + // 声明包围矩形。 + BoundBox tmpbbox; + + // 当前点在新坐标系下的新坐标。 + float curx, cury; + + for (idx = 0; idx < convexcst->count; idx++) { + // 当前索引值加 1,求得下一点索引值。 + nextidx = idx + 1; + + // 若当前点为点集中最后一点,则下一点为起始点。 + if (nextidx == convexcst->count) + nextidx = 0; + + // 计算当前点和下一点 x, y 坐标差值。 + deltax = convexcst->tplData[nextidx * 2] - + convexcst->tplData[idx * 2]; + deltay = convexcst->tplData[nextidx * 2 + 1] - + convexcst->tplData[idx * 2 + 1]; + + // 如果解的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (deltax < 0) { + deltax = -deltax; + deltay = -deltay; + } + + // 计算当前点和下一点间距离。 + sidelength = sqrtf(deltax * deltax + deltay * deltay); + + // 计算旋转角度的余弦、正弦值。 + cosalpha = deltax / sidelength; + sinalpha = deltay / sidelength; + + // 根据计算得到的正弦值计算角度,将旋转矩阵信息存入到参数 + rotateinfo[idx].cos = cosalpha; + rotateinfo[idx].sin = sinalpha; + rotateinfo[idx].radian = asin(sinalpha); + + // 每次均初始化。 + tmpbbox.left = tmpbbox.bottom = SDR_LARGE_ENOUGH; + tmpbbox.right = tmpbbox.top = -SDR_LARGE_ENOUGH; + + for (cstidx = 0; cstidx < convexcst->count; cstidx++) { + // 读取坐标值和旋转信息值, + // 并计算当前点在新坐标系下的新坐标。 + curx = convexcst->tplData[2 * cstidx] * rotateinfo[idx].cos + + convexcst->tplData[2 * cstidx + 1] * rotateinfo[idx].sin; + + cury = convexcst->tplData[2 * cstidx] * (-rotateinfo[idx].sin) + + convexcst->tplData[2 * cstidx + 1] * rotateinfo[idx].cos; + + // 判断该坐标值的大小,和已经找到的极值做比较,更新极值。 + tmpbbox.left = min(tmpbbox.left, curx); + tmpbbox.right = max(tmpbbox.right, curx); + tmpbbox.bottom = min(tmpbbox.bottom, cury); + tmpbbox.top = max(tmpbbox.top, cury); + } + + // 最值赋值 + bbox[idx].left = tmpbbox.left; + bbox[idx].right = tmpbbox.right; + bbox[idx].bottom = tmpbbox.bottom; + bbox[idx].top = tmpbbox.top; + } + + // 运行完毕退出。 + return NO_ERROR; +} + +// Kernel 函数: _sdrComputeSDRKer(计算包围矩形中面积最小的) +static __global__ void _sdrComputeSDRKer( + int cstcnt, BoundBox bbox[], int *index) +{ + // 计算当前线程的下标,该 Kernel 必须以单 Block 运行,因此不涉及到 Block 相 + // 关的变量。 + int idx = threadIdx.x; + + // 当前 Thread 处理的若干个矩形中找到的最小的矩形面积。 + float cursdrarea = SDR_LARGE_ENOUGH; + + // 当前线程计算得到的矩形面积。 + float curarea; + + // 当前线程记录的最小矩形面积的索引,初始化为 idx。 + int cursdrindex = idx; + + // 当前线程对应的点计算得到的长宽。 + float length1, length2; + + // 迭代处理该线程所要处理的所有矩形,这些矩形是间隔 blockDim.x 个索引的各个 + // 矩形。 + while (idx < cstcnt) { + // 从 Global Memory 中读取极值,计算长宽。 + length1 = bbox[idx].right - bbox[idx].left; + length2 = bbox[idx].top - bbox[idx].bottom; + // 计算当前的矩形面积。 + curarea = length1 * length2; + + // 判断该面积的大小,和已经找到的最小面积做比较,更新最小面积及索引。 + cursdrindex = (curarea <= cursdrarea) ? idx : cursdrindex; + cursdrarea = min(curarea, cursdrarea); + + // 更新 idx,在下一轮迭代时计算下一个点。 + idx += blockDim.x; + } + + // 至此,所有 Thread 都得到了自己的局部最小面积及索引,现在需要将这些点放入 + // Shared Memory 中,以便下一步进行归约处理。 + + // 声明 Shared Memory,并分配各个指针。 + extern __shared__ float shdmem[]; + float *shdarea = shdmem; + int *shdidx = (int *)(shdarea + blockDim.x); + + // 将局部结果拷贝到 Shared Memory 中。 + idx = threadIdx.x; + shdarea[idx] = cursdrarea; + shdidx[idx] = cursdrindex; + + // 同步所有线程,使初始化Shared Memory 的结果对所有线程可见。 + __syncthreads(); + + // 下面进行折半归约迭代。这里要求 blockDim.x 必须为 2 的整数次幂。 + int currdsize = blockDim.x / 2; + // 和当前线程间隔 currdsize 位置处的索引。 + int inidx; + for (/* currdsize */; currdsize > 0; currdsize >>= 1) { + if (idx < currdsize) { + inidx = idx + currdsize; + // 将两个局部结果归约成一个局部结果。 + shdidx[idx] = (shdarea[idx] <= shdarea[inidx]) ? + shdidx[idx] : shdidx[inidx]; + shdarea[idx] = min(shdarea[idx], shdarea[inidx]); + + // 输出结果进行验证。 +#ifdef SDR_DEBUG_KERNEL_PRINT + printf("Kernel[computeSDR]: ReduceSize %3d," + "(%3d) CurSdrArea %7.3f CurSdrId %3d\n", + "(%3d) CurReSdrArea %7.3f CurReSdrId %3d\n", + currdsize, idx, shdarea[idx], shdidx[idx], + inidx, shdarea[inidx], shdidx[inidx]); +#endif + } + + // 同步线程,使本轮迭代归约的结果对所有线程可见。 + __syncthreads(); + } + + // 将最小面积的索引传递给 Global Memory 参数,第一个线程会进行这个操作。 + if (idx == 0) { + index[0] = shdidx[idx]; + + // 调试打印。 +#ifdef SDR_DEBUG_KERNEL_PRINT + printf("Kernel[computeSDR]: SDR index %5d\n", index[0]); +#endif + } + +} + +// Host 成员方法:sdrComputeSDR(计算有向外接矩形中面积最小的) +__host__ int SmallestDirRect::sdrComputeSDR( + int cstcnt, BoundBox bbox[], int *index) +{ + // 检查坐标集和旋转矩阵是否为空,若为空则直接返回。 + if (bbox == NULL || index == NULL) + return NULL_POINTER; + + // 如果点集数量小于 1,直接退出。 + if (cstcnt < 1) + return INVALID_DATA; + + // 计算启动 Kernel 函数所需要的 Block 尺寸与数量。 + size_t blocksize = DEF_BLOCK_1D; + size_t gridsize = 1; + + // 共享内存大小。 + int shdmemsize = DEF_BLOCK_1D * (sizeof (float) + sizeof (int)); + + // 启动 Kernel 函数,完成计算。 + _sdrComputeSDRKer<<>>( + cstcnt, bbox, index); + + // 检查 Kernel 函数执行是否正确。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 运行完毕退出。 + return NO_ERROR; +} + +// Host 成员方法:sdrComputeSDRCpu(计算有向外接矩形中面积最小的) +__host__ int SmallestDirRect::sdrComputeSDRCpu( + int cstcnt, BoundBox bbox[], int *index) +{ + // 检查坐标集和旋转矩阵是否为空,若为空则直接返回。 + if (bbox == NULL || index == NULL) + return NULL_POINTER; + + // 如果点集数量小于 1,直接退出。 + if (cstcnt < 1) + return INVALID_DATA; + + // 索引,初始化为 0。 + int idx = 0; + + // 当前 Thread 处理的若干个矩形中找到的最小的矩形面积。 + float cursdrarea = SDR_LARGE_ENOUGH; + + // 当前线程计算得到的矩形面积。 + float curarea; + + // 当前线程记录的最小矩形面积的索引,初始化为 idx。 + int cursdrindex = idx; + + // 当前线程对应的点计算得到的长宽。 + float length1, length2; + + for (idx = 0; idx < cstcnt; idx++) { + // 读取极值,计算长宽。 + length1 = bbox[idx].right - bbox[idx].left; + length2 = bbox[idx].top - bbox[idx].bottom; + // 计算当前的矩形面积。 + curarea = length1 * length2; + + // 判断该面积的大小,和已经找到的最小面积做比较,更新最小面积及索引。 + cursdrindex = (curarea <= cursdrarea) ? idx : cursdrindex; + cursdrarea = min(curarea, cursdrarea); + } + + // 输出赋值 + index[0] = cursdrindex; + + // 运行完毕退出。 + return NO_ERROR; +} + +// 宏:FAIL_SDRPARAMONCVX_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_SDRPARAMONCVX_FREE do { \ + if (devtemp != NULL) \ + cudaFree(devtemp); \ + } while (0) + +// Host 成员方法:sdrParamOnConvex(求凸壳点集的最小有向外接矩形的参数) +__host__ int SmallestDirRect::sdrParamOnConvex( + CoordiSet *convexcst, BoundBox *bbox, RotationInfo *rotinfo) +{ + // 检查输入,输出是否为空。 + if (convexcst == NULL || bbox == NULL || rotinfo == NULL) + return NULL_POINTER; + + // 如果输入点集中不含有任何的坐标点,则直接退出。 + if (convexcst->count < 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 用来记录最小有向外接矩形在整个结果中的索引。 + int index = 0; + + // 中间变量的设备端数组。存放旋转矩阵信息,包围盒顶点,索引。 + RotationInfo *rotateinfoDev = NULL; + BoundBox *bboxDev = NULL; + int *indexDev = NULL; + + // 中间变量申请 Device 内存空间,并将这些空间分配给各个中间变量。 + float *devtemp = NULL; + size_t datasize = (sizeof (RotationInfo) + sizeof (BoundBox)) * + convexcst->count + sizeof (int); + cuerrcode = cudaMalloc((void **)&devtemp, datasize); + if (cuerrcode != cudaSuccess) { + FAIL_SDRPARAMONCVX_FREE; + return CUDA_ERROR; + } + + // 为各个中间变量分配内存空间,采用这种一次申请一个大空间的做法是为了减少申 + // 请内存的开销,同时也减少因内存对齐导致的内存浪费。 + rotateinfoDev = (RotationInfo *)(devtemp); + bboxDev = (BoundBox *)(rotateinfoDev + convexcst->count); + indexDev = (int *)(bboxDev + convexcst->count); + + // 将输入坐标集拷贝到 device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRPARAMONCVX_FREE; + return errcode; + } + + // 调用计算凸壳点集中每相邻两点的旋转矩阵信息,进而计算新坐标系下 + // 凸壳的有向外接矩形的边界信息的函数。 + errcode = this->sdrComputeBoundInfo(convexcst, rotateinfoDev, bboxDev); + if (errcode != NO_ERROR) { + FAIL_SDRPARAMONCVX_FREE; + return errcode; + } + + // 调用计算最小有向外接矩形的函数。 + errcode = this->sdrComputeSDR(convexcst->count, bboxDev, indexDev); + if (errcode != NO_ERROR) { + FAIL_SDRPARAMONCVX_FREE; + return errcode; + } + + // 将最小有向外接矩形在所有结果中的索引,拷贝到 host 端。 + cuerrcode = cudaMemcpy(&index, indexDev, sizeof (int), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_SDRPARAMONCVX_FREE; + return CUDA_ERROR; + } + + // 将最小有向外接矩形的四个顶点,拷贝到主存端。 + cuerrcode = cudaMemcpy(bbox, &bboxDev[index], sizeof (BoundBox), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_SDRPARAMONCVX_FREE; + return CUDA_ERROR; + } + + // 将最小有向外接矩形的旋转信息,拷贝到主存端。 + cuerrcode = cudaMemcpy(rotinfo, &rotateinfoDev[index], + sizeof (RotationInfo), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + FAIL_SDRPARAMONCVX_FREE; + return CUDA_ERROR; + } + + // 释放内存 + cudaFree(devtemp); + + // 退出。 + return NO_ERROR; +} +#undef FAIL_SDRPARAMONCVX_FREE + +// 宏:FAIL_SDRPARAMONCVXCPU_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_SDRPARAMONCVXCPU_FREE do { \ + if (temp != NULL) \ + delete temp; \ + } while (0) + +// Host 成员方法:sdrParamOnConvexCpu(求凸壳点集的最小有向外接矩形的参数) +__host__ int SmallestDirRect::sdrParamOnConvexCpu( + CoordiSet *convexcst, BoundBox *bbox, RotationInfo *rotinfo) +{ + // 检查输入,输出是否为空。 + if (convexcst == NULL || bbox == NULL || rotinfo == NULL) + return NULL_POINTER; + + // 如果输入点集中不含有任何的坐标点,则直接退出。 + if (convexcst->count < 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 用来记录最小有向外接矩形在整个结果中的索引。 + int index = 0; + + // 中间变量的设备端数组。存放旋转矩阵信息,包围盒顶点,索引。 + RotationInfo *rotateinfoHost = NULL; + BoundBox *bboxHost = NULL; + int *indexHost = NULL; + + // 中间变量申请 Host 内存空间,并将这些空间分配给各个中间变量。 + float *temp = NULL; + size_t datasize = (sizeof (RotationInfo) + sizeof (BoundBox)) * + convexcst->count + sizeof (int); + + // liuyao debug + temp = new float[datasize]; + if (temp == NULL) { + return OUT_OF_MEM;; + } + + // 为各个中间变量分配内存空间,采用这种一次申请一个大空间的做法是为了减少申 + // 请内存的开销,同时也减少因内存对齐导致的内存浪费。 + rotateinfoHost = (RotationInfo *)(temp); + bboxHost = (BoundBox *)(rotateinfoHost + convexcst->count); + indexHost = (int *)(bboxHost + convexcst->count); + + // 将输入坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRPARAMONCVXCPU_FREE; + return errcode; + } + + // 调用计算凸壳点集中每相邻两点的旋转矩阵信息,进而计算新坐标系下 + // 凸壳的有向外接矩形的边界信息的函数。 + errcode = this->sdrComputeBoundInfoCpu(convexcst, rotateinfoHost, bboxHost); + if (errcode != NO_ERROR) { + FAIL_SDRPARAMONCVXCPU_FREE; + return errcode; + } + + // 调用计算最小有向外接矩形的函数。 + errcode = this->sdrComputeSDRCpu(convexcst->count, bboxHost, indexHost); + if (errcode != NO_ERROR) { + FAIL_SDRPARAMONCVXCPU_FREE; + return errcode; + } + + // 输出赋值 + index = indexHost[0]; + bbox[0] = bboxHost[index]; + rotinfo[0] = rotateinfoHost[index]; + + // 释放内存 + delete temp; + + // 退出。 + return NO_ERROR; +} +#undef FAIL_SDRPARAMONCVXCPU_FREE + +// 宏:FAIL_SDRONCVX_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_SDRONCVX_FREE do { \ + if (!hostrect && recthost != NULL) \ + delete [] recthost; \ + } while (0) + +// Host 成员方法:smallestDirRectOnConvex(求凸壳点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectCpuOnConvex( + CoordiSet *convexcst, Quadrangle *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (convexcst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (convexcst->count <= 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 定义 Host 端的输出数组指针,这里计算量比较小,所以统一采取在 host 端 + // 根据最小有向包围矩形的顶点和旋转矩阵信息计算输出的包围矩形各个参数。 + Quadrangle *recthost = NULL; + + // 判断输出矩形是否存储在 Device 端。若不是,则需要在 Host 端为输出矩形 + // 申请一段空间;若该数组是在 Host 端,则直接使用。 + if (hostrect) { + // 如果在 Host 端,则将指针传给对应的 Host 端统一指针。 + recthost = outrect; + } else { + // 为输入数组在 Host 端申请内存。 + recthost = new Quadrangle[1]; + // 出错则报错返回。 + if (recthost == NULL) { + return OUT_OF_MEM; + } + + // 将输出数组拷贝到 Host 端内存。 + cuerrcode = cudaMemcpy(recthost, outrect, + sizeof (Quadrangle), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 如果输入凸壳点集中只含有 2 个坐标点,则特殊处理。 + if (convexcst->count == 2) { + // 四个顶点赋值为两个坐标点的坐标。四个顶点有两对重合。 + recthost->points[0][0] = recthost->points[1][0] = convexcst->tplData[0]; + recthost->points[0][1] = recthost->points[1][1] = convexcst->tplData[1]; + recthost->points[2][0] = recthost->points[3][0] = convexcst->tplData[2]; + recthost->points[2][1] = recthost->points[3][1] = convexcst->tplData[3]; + + // 计算两点坐标差值。 + int deltax = convexcst->tplData[0] - convexcst->tplData[2]; + int deltay = convexcst->tplData[1] - convexcst->tplData[3]; + + // 如果解的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (deltax < 0) { + deltax = -deltax; + deltay = -deltay; + } + + // 计算当前点和下一点间距离。 + float sidelength = sqrtf(deltax * deltax + deltay * deltay); + + // 计算旋转角度的正弦值。 + float sinalpha = deltay / sidelength; + + // 该角度的弧度值。从而计算最小有向外接矩形的角度。 + float radian = asin(sinalpha); + recthost->angle = RECT_RAD_TO_DEG(radian); + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (Quadrangle), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 特殊情况,不用计算下列步骤,退出。 + return NO_ERROR; + } + + // 局部变量,用来记录面积最小的有向外接矩形和对应的旋转信息。 + BoundBox bbox; + RotationInfo rotinfo; + + // 将输入坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 调用求凸壳点集的最小有向外接矩形参数的函数。 + errcode = this->sdrParamOnConvexCpu(convexcst, &bbox, &rotinfo); + if (errcode != NO_ERROR) + return errcode; + + // 计算最小有向外接矩形的角度。 + recthost->angle = RECT_RAD_TO_DEG(rotinfo.radian); + + // 计算最小有向外接矩形的边界点值。 + float points[4][2]; + points[0][0] = bbox.left; + points[0][1] = bbox.top; + points[1][0] = bbox.right; + points[1][1] = bbox.top; + points[2][0] = bbox.right; + points[2][1] = bbox.bottom; + points[3][0] = bbox.left; + points[3][1] = bbox.bottom; + + // 打印临时顶点信息。 +#ifdef SDR_DEBUG_KERNEL_PRINT + cout << "temprect info: " << endl; + cout << points[0][0] << "," << points[0][1] << endl; + cout << points[1][0] << "," << points[1][1] << endl; + cout << points[2][0] << "," << points[2][1] << endl; + cout << points[3][0] << "," << points[3][1] << endl; +#endif + + // 临时存放的四个顶点值。 + float tempvertex[4][2]; + + // 计算旋转后最小有向外接矩形的四个顶点值。 + RECT_ROTATE_POINT(points[0], tempvertex[0], rotinfo); + RECT_ROTATE_POINT(points[1], tempvertex[1], rotinfo); + RECT_ROTATE_POINT(points[2], tempvertex[2], rotinfo); + RECT_ROTATE_POINT(points[3], tempvertex[3], rotinfo); + + // 求中心坐标。 + float boxcenter[2]; + boxcenter[0] = (tempvertex[0][0] + tempvertex[1][0] + tempvertex[2][0] + + tempvertex[3][0]) / 4.0f; + boxcenter[1] = (tempvertex[0][1] + tempvertex[1][1] + tempvertex[2][1] + + tempvertex[3][1]) / 4.0f; + + // 计算所得的包围盒的四个顶点逆时针排列,寻找右上点的索引值。 + int rightupidx; + // 如果是垂直于坐标轴的菱形,也就是对角的 x 坐标相等,需要特殊处理。 + // 如果第 0 个和第 2 个点的 x 坐标相等。 + if (tempvertex[0][0] == tempvertex[2][0]) { + // 如果第 0 个的 y 坐标更大。 + if (tempvertex[0][1] > boxcenter[1]) + // 右上点的索引值为 0。 + rightupidx = 0; + // 如果第 2 个的 y 坐标更大。 + else + // 右上点的索引值为 2。 + rightupidx = 2; + // 如果第 1 个和第 3 个点的 x 坐标相等。 + } else if (tempvertex[1][0] == tempvertex[3][0]) { + // 如果第 1 个的 y 坐标更大。 + if (tempvertex[1][1] > boxcenter[1]) + // 右上点的索引值为 1。 + rightupidx = 1; + // 如果第 3 个的 y 坐标更大。 + else + // 右上点的索引值为 3。 + rightupidx = 3; + // 如果没有 x 或者 y 坐标相等的特殊情况。 + } else { + // 如果第 0 个点的 x,y 坐标均大于中心点坐标。 + if (tempvertex[0][0] > boxcenter[0] && tempvertex[0][1] > boxcenter[1]) + // 右上点的索引值为 0。 + rightupidx = 0; + // 如果第 1 个点的 x,y 坐标均大于中心点坐标。 + else if (tempvertex[1][0] > boxcenter[0] && + tempvertex[1][1] > boxcenter[1]) + // 右上点的索引值为 1。 + rightupidx = 1; + // 如果第 2 个点的 x,y 坐标均大于中心点坐标。 + else if (tempvertex[2][0] > boxcenter[0] && + tempvertex[2][1] > boxcenter[1]) + // 右上点的索引值为 2。 + rightupidx = 2; + // 如果第 3 个点的 x,y 坐标均大于中心点坐标。 + else + // 右上点的索引值为 3。 + rightupidx = 3; + } + + // 按照算得的右上点索引值,对四个顶点的 x,y 坐标进行分别的向下向上取整处理 + // 右上点,x 向上取整,y 向上取整。 + recthost->points[rightupidx][0] = + (int)ceil(tempvertex[rightupidx][0]); + recthost->points[rightupidx][1] = + (int)ceil(tempvertex[rightupidx][1]); + // 右下点,x 向上取整,y 向下取整。 + recthost->points[(rightupidx + 1) % 4][0] = + (int)ceil(tempvertex[(rightupidx + 1) % 4][0]); + recthost->points[(rightupidx + 1) % 4][1] = + (int)floor(tempvertex[(rightupidx + 1) % 4][1]); + // 左下点,x 向下取整,y 向下取整。 + recthost->points[(rightupidx + 2) % 4][0] = + (int)floor(tempvertex[(rightupidx + 2) % 4][0]); + recthost->points[(rightupidx + 2) % 4][1] = + (int)floor(tempvertex[(rightupidx + 2) % 4][1]); + // 左上点,x 向下取整,y 向上取整。 + recthost->points[(rightupidx + 3) % 4][0] = + (int)ceil(tempvertex[(rightupidx + 3) % 4][0]); + recthost->points[(rightupidx + 3) % 4][1] = + (int)floor(tempvertex[(rightupidx + 3) % 4][1]); + + // 计算矩形的长宽。 + float length1 = bbox.right - bbox.left; + float length2 = bbox.top - bbox.bottom; + + // 角度是跟 length1 边平行的,需求为角度的方向平行于长边。当 length1 不是 + // 长边时,做出调整。 + if (length1 < length2) { + // 旋转角度为负时,加上 90 度。 + if (recthost->angle < 0.0f) + recthost->angle += 90.0f; + // 旋转角度为正时,减去 90 度。 + else + recthost->angle -= 90.0f; + } + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (Quadrangle), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRectOnConvex(求凸壳点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectOnConvex( + CoordiSet *convexcst, Quadrangle *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (convexcst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (convexcst->count <= 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 定义 Host 端的输出数组指针,这里计算量比较小,所以统一采取在 host 端 + // 根据最小有向包围矩形的顶点和旋转矩阵信息计算输出的包围矩形各个参数。 + Quadrangle *recthost = NULL; + + // 判断输出矩形是否存储在 Device 端。若不是,则需要在 Host 端为输出矩形 + // 申请一段空间;若该数组是在 Host 端,则直接使用。 + if (hostrect) { + // 如果在 Host 端,则将指针传给对应的 Host 端统一指针。 + recthost = outrect; + } else { + // 为输入数组在 Host 端申请内存。 + recthost = new Quadrangle[1]; + // 出错则报错返回。 + if (recthost == NULL) { + return OUT_OF_MEM; + } + + // 将输出数组拷贝到 Host 端内存。 + cuerrcode = cudaMemcpy(recthost, outrect, + sizeof (Quadrangle), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 如果输入凸壳点集中只含有 2 个坐标点,则特殊处理。 + if (convexcst->count == 2) { + // 四个顶点赋值为两个坐标点的坐标。四个顶点有两对重合。 + recthost->points[0][0] = recthost->points[1][0] = convexcst->tplData[0]; + recthost->points[0][1] = recthost->points[1][1] = convexcst->tplData[1]; + recthost->points[2][0] = recthost->points[3][0] = convexcst->tplData[2]; + recthost->points[2][1] = recthost->points[3][1] = convexcst->tplData[3]; + + // 计算两点坐标差值。 + int deltax = convexcst->tplData[0] - convexcst->tplData[2]; + int deltay = convexcst->tplData[1] - convexcst->tplData[3]; + + // 如果解的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (deltax < 0) { + deltax = -deltax; + deltay = -deltay; + } + + // 计算当前点和下一点间距离。 + float sidelength = sqrtf(deltax * deltax + deltay * deltay); + + // 计算旋转角度的正弦值。 + float sinalpha = deltay / sidelength; + + // 该角度的弧度值。从而计算最小有向外接矩形的角度。 + float radian = asin(sinalpha); + recthost->angle = RECT_RAD_TO_DEG(radian); + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (Quadrangle), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 特殊情况,不用计算下列步骤,退出。 + return NO_ERROR; + } + + // 局部变量,用来记录面积最小的有向外接矩形和对应的旋转信息。 + BoundBox bbox; + RotationInfo rotinfo; + + // 将输入坐标集拷贝到 device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 调用求凸壳点集的最小有向外接矩形参数的函数。 + errcode = this->sdrParamOnConvex(convexcst, &bbox, &rotinfo); + if (errcode != NO_ERROR) + return errcode; + + // 计算最小有向外接矩形的角度。 + recthost->angle = RECT_RAD_TO_DEG(rotinfo.radian); + + // 计算最小有向外接矩形的边界点值。 + float points[4][2]; + points[0][0] = bbox.left; + points[0][1] = bbox.top; + points[1][0] = bbox.right; + points[1][1] = bbox.top; + points[2][0] = bbox.right; + points[2][1] = bbox.bottom; + points[3][0] = bbox.left; + points[3][1] = bbox.bottom; + + // 打印临时顶点信息。 +#ifdef SDR_DEBUG_KERNEL_PRINT + cout << "temprect info: " << endl; + cout << points[0][0] << "," << points[0][1] << endl; + cout << points[1][0] << "," << points[1][1] << endl; + cout << points[2][0] << "," << points[2][1] << endl; + cout << points[3][0] << "," << points[3][1] << endl; +#endif + + // 临时存放的四个顶点值。 + float tempvertex[4][2]; + + // 计算旋转后最小有向外接矩形的四个顶点值。 + RECT_ROTATE_POINT(points[0], tempvertex[0], rotinfo); + RECT_ROTATE_POINT(points[1], tempvertex[1], rotinfo); + RECT_ROTATE_POINT(points[2], tempvertex[2], rotinfo); + RECT_ROTATE_POINT(points[3], tempvertex[3], rotinfo); + + // 求中心坐标。 + float boxcenter[2]; + boxcenter[0] = (tempvertex[0][0] + tempvertex[1][0] + tempvertex[2][0] + + tempvertex[3][0]) / 4.0f; + boxcenter[1] = (tempvertex[0][1] + tempvertex[1][1] + tempvertex[2][1] + + tempvertex[3][1]) / 4.0f; + + // 计算所得的包围盒的四个顶点逆时针排列,寻找右上点的索引值。 + int rightupidx; + // 如果是垂直于坐标轴的菱形,也就是对角的 x 坐标相等,需要特殊处理。 + // 如果第 0 个和第 2 个点的 x 坐标相等。 + if (tempvertex[0][0] == tempvertex[2][0]) { + // 如果第 0 个的 y 坐标更大。 + if (tempvertex[0][1] > boxcenter[1]) + // 右上点的索引值为 0。 + rightupidx = 0; + // 如果第 2 个的 y 坐标更大。 + else + // 右上点的索引值为 2。 + rightupidx = 2; + // 如果第 1 个和第 3 个点的 x 坐标相等。 + } else if (tempvertex[1][0] == tempvertex[3][0]) { + // 如果第 1 个的 y 坐标更大。 + if (tempvertex[1][1] > boxcenter[1]) + // 右上点的索引值为 1。 + rightupidx = 1; + // 如果第 3 个的 y 坐标更大。 + else + // 右上点的索引值为 3。 + rightupidx = 3; + // 如果没有 x 或者 y 坐标相等的特殊情况。 + } else { + // 如果第 0 个点的 x,y 坐标均大于中心点坐标。 + if (tempvertex[0][0] > boxcenter[0] && tempvertex[0][1] > boxcenter[1]) + // 右上点的索引值为 0。 + rightupidx = 0; + // 如果第 1 个点的 x,y 坐标均大于中心点坐标。 + else if (tempvertex[1][0] > boxcenter[0] && + tempvertex[1][1] > boxcenter[1]) + // 右上点的索引值为 1。 + rightupidx = 1; + // 如果第 2 个点的 x,y 坐标均大于中心点坐标。 + else if (tempvertex[2][0] > boxcenter[0] && + tempvertex[2][1] > boxcenter[1]) + // 右上点的索引值为 2。 + rightupidx = 2; + // 如果第 3 个点的 x,y 坐标均大于中心点坐标。 + else + // 右上点的索引值为 3。 + rightupidx = 3; + } + + // 按照算得的右上点索引值,对四个顶点的 x,y 坐标进行分别的向下向上取整处理 + // 右上点,x 向上取整,y 向上取整。 + recthost->points[rightupidx][0] = + (int)ceil(tempvertex[rightupidx][0]); + recthost->points[rightupidx][1] = + (int)ceil(tempvertex[rightupidx][1]); + // 右下点,x 向上取整,y 向下取整。 + recthost->points[(rightupidx + 1) % 4][0] = + (int)ceil(tempvertex[(rightupidx + 1) % 4][0]); + recthost->points[(rightupidx + 1) % 4][1] = + (int)floor(tempvertex[(rightupidx + 1) % 4][1]); + // 左下点,x 向下取整,y 向下取整。 + recthost->points[(rightupidx + 2) % 4][0] = + (int)floor(tempvertex[(rightupidx + 2) % 4][0]); + recthost->points[(rightupidx + 2) % 4][1] = + (int)floor(tempvertex[(rightupidx + 2) % 4][1]); + // 左上点,x 向下取整,y 向上取整。 + recthost->points[(rightupidx + 3) % 4][0] = + (int)ceil(tempvertex[(rightupidx + 3) % 4][0]); + recthost->points[(rightupidx + 3) % 4][1] = + (int)floor(tempvertex[(rightupidx + 3) % 4][1]); + + // 计算矩形的长宽。 + float length1 = bbox.right - bbox.left; + float length2 = bbox.top - bbox.bottom; + + // 角度是跟 length1 边平行的,需求为角度的方向平行于长边。当 length1 不是 + // 长边时,做出调整。 + if (length1 < length2) { + // 旋转角度为负时,加上 90 度。 + if (recthost->angle < 0.0f) + recthost->angle += 90.0f; + // 旋转角度为正时,减去 90 度。 + else + recthost->angle -= 90.0f; + } + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (Quadrangle), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRectCpuOnConvex(求凸壳点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectCpuOnConvex( + CoordiSet *convexcst, DirectedRect *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (convexcst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (convexcst->count <= 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 定义 Host 端的输出数组指针,这里计算量比较小,所以统一采取在 host 端 + // 根据最小有向包围矩形的顶点和旋转矩阵信息计算输出的包围矩形各个参数。 + DirectedRect *recthost = NULL; + + // 判断输出矩形是否存储在 Device 端。若不是,则需要在 Host 端为输出矩形 + // 申请一段空间;若该数组是在 Host 端,则直接使用。 + if (hostrect) { + // 如果在 Host 端,则将指针传给对应的 Host 端统一指针。 + recthost = outrect; + } else { + // 为输入数组在 Host 端申请内存。 + recthost = new DirectedRect[1]; + // 出错则报错返回。 + if (recthost == NULL) { + return OUT_OF_MEM; + } + + // 将输出数组拷贝到 Host 端内存。 + cuerrcode = cudaMemcpy(recthost, outrect, + sizeof (DirectedRect), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 如果输入凸壳点集中只含有 2 个坐标点,则特殊处理。 + if (convexcst->count == 2) { + // 中心点坐标为两个点的中点。 + recthost->centerPoint[0] = convexcst->tplData[0] + + convexcst->tplData[2]; + recthost->centerPoint[1] = convexcst->tplData[1] + + convexcst->tplData[3]; + + // 计算两点坐标差值。 + int deltax = convexcst->tplData[0] - convexcst->tplData[2]; + int deltay = convexcst->tplData[1] - convexcst->tplData[3]; + + // 如果解的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (deltax < 0) { + deltax = -deltax; + deltay = -deltay; + } + + // 计算当前点和下一点间距离。 + float sidelength = sqrtf(deltax * deltax + deltay * deltay); + + // 计算旋转角度的正弦值。 + float sinalpha = deltay / sidelength; + + // 该角度的弧度值。从而计算最小有向外接矩形的角度。 + float radian = asin(sinalpha); + recthost->angle = RECT_RAD_TO_DEG(radian); + + // 该包围矩形的边长。 + recthost->length1 = (int)sidelength; + recthost->length2 = 0; + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (DirectedRect), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 特殊情况,不用计算下列步骤,退出。 + return NO_ERROR; + } + + // 局部变量,用来记录面积最小的有向外接矩形和对应的旋转信息。 + BoundBox bbox; + RotationInfo rotinfo; + + // 将输入坐标集拷贝到 Host 端。 + errcode = CoordiSetBasicOp::copyToHost(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 调用求凸壳点集的最小有向外接矩形参数的函数。 + errcode = this->sdrParamOnConvexCpu(convexcst, &bbox, &rotinfo); + if (errcode != NO_ERROR) + return errcode; + + // 计算最小有向外接矩形的角度。 + recthost->angle = RECT_RAD_TO_DEG(rotinfo.radian); + + // 计算中心坐标。 + float boxcenter[2]; + boxcenter[0] = (bbox.left + bbox.right) / 2.0f; + boxcenter[1] = (bbox.top + bbox.bottom) / 2.0f; + RECT_ROTATE_POINT(boxcenter, recthost->centerPoint, rotinfo); + + // 计算矩形的长宽。 + recthost->length1 = (int)(bbox.right - bbox.left); + recthost->length2 = (int)(bbox.top - bbox.bottom); + + // 选择长的作为矩形的长。 + if (recthost->length1 < recthost->length2) { + // 长短边进行交换。 + int length_temp; + length_temp = recthost->length1; + recthost->length1 = recthost->length2; + recthost->length2 = length_temp; + + // 角度是跟 length1 边平行的,需求为角度的方向平行于长边。当 length1 + // 不是长边时,做出调整。 + // 旋转角度为负时,加上 90 度。 + if (recthost->angle < 0.0f) + recthost->angle += 90.0f; + // 旋转角度为正时,减去 90 度。 + else + recthost->angle -= 90.0f; + } + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (DirectedRect), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRectOnConvex(求凸壳点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectOnConvex( + CoordiSet *convexcst, DirectedRect *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (convexcst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (convexcst->count <= 1 || convexcst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + cudaError_t cuerrcode; + int errcode; + + // 定义 Host 端的输出数组指针,这里计算量比较小,所以统一采取在 host 端 + // 根据最小有向包围矩形的顶点和旋转矩阵信息计算输出的包围矩形各个参数。 + DirectedRect *recthost = NULL; + + // 判断输出矩形是否存储在 Device 端。若不是,则需要在 Host 端为输出矩形 + // 申请一段空间;若该数组是在 Host 端,则直接使用。 + if (hostrect) { + // 如果在 Host 端,则将指针传给对应的 Host 端统一指针。 + recthost = outrect; + } else { + // 为输入数组在 Host 端申请内存。 + recthost = new DirectedRect[1]; + // 出错则报错返回。 + if (recthost == NULL) { + return OUT_OF_MEM; + } + + // 将输出数组拷贝到 Host 端内存。 + cuerrcode = cudaMemcpy(recthost, outrect, + sizeof (DirectedRect), + cudaMemcpyDeviceToHost); + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 如果输入凸壳点集中只含有 2 个坐标点,则特殊处理。 + if (convexcst->count == 2) { + // 中心点坐标为两个点的中点。 + recthost->centerPoint[0] = convexcst->tplData[0] + + convexcst->tplData[2]; + recthost->centerPoint[1] = convexcst->tplData[1] + + convexcst->tplData[3]; + + // 计算两点坐标差值。 + int deltax = convexcst->tplData[0] - convexcst->tplData[2]; + int deltay = convexcst->tplData[1] - convexcst->tplData[3]; + + // 如果解的 x 在第二或者第三象限,转化坐标到第四或者第一象限。 + if (deltax < 0) { + deltax = -deltax; + deltay = -deltay; + } + + // 计算当前点和下一点间距离。 + float sidelength = sqrtf(deltax * deltax + deltay * deltay); + + // 计算旋转角度的正弦值。 + float sinalpha = deltay / sidelength; + + // 该角度的弧度值。从而计算最小有向外接矩形的角度。 + float radian = asin(sinalpha); + recthost->angle = RECT_RAD_TO_DEG(radian); + + // 该包围矩形的边长。 + recthost->length1 = (int)sidelength; + recthost->length2 = 0; + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (DirectedRect), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 特殊情况,不用计算下列步骤,退出。 + return NO_ERROR; + } + + // 局部变量,用来记录面积最小的有向外接矩形和对应的旋转信息。 + BoundBox bbox; + RotationInfo rotinfo; + + // 将输入坐标集拷贝到 device 端。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(convexcst); + if (errcode != NO_ERROR) + return errcode; + + // 调用求凸壳点集的最小有向外接矩形参数的函数。 + errcode = this->sdrParamOnConvex(convexcst, &bbox, &rotinfo); + if (errcode != NO_ERROR) + return errcode; + + // 计算最小有向外接矩形的角度。 + recthost->angle = RECT_RAD_TO_DEG(rotinfo.radian); + + // 计算中心坐标。 + float boxcenter[2]; + boxcenter[0] = (bbox.left + bbox.right) / 2.0f; + boxcenter[1] = (bbox.top + bbox.bottom) / 2.0f; + RECT_ROTATE_POINT(boxcenter, recthost->centerPoint, rotinfo); + + // 计算矩形的长宽。 + recthost->length1 = (int)(bbox.right - bbox.left); + recthost->length2 = (int)(bbox.top - bbox.bottom); + + // 选择长的作为矩形的长。 + if (recthost->length1 < recthost->length2) { + // 长短边进行交换。 + int length_temp; + length_temp = recthost->length1; + recthost->length1 = recthost->length2; + recthost->length2 = length_temp; + + // 角度是跟 length1 边平行的,需求为角度的方向平行于长边。当 length1 + // 不是长边时,做出调整。 + // 旋转角度为负时,加上 90 度。 + if (recthost->angle < 0.0f) + recthost->angle += 90.0f; + // 旋转角度为正时,减去 90 度。 + else + recthost->angle -= 90.0f; + } + + // 如果输出矩形在 Device 端,将结果拷贝到输出。 + if (!hostrect) { + // 将结果从 Host 端内存拷贝到输出。 + cuerrcode = cudaMemcpy(outrect, recthost, + sizeof (DirectedRect), + cudaMemcpyHostToDevice); + // 出错则释放之前申请的内存。 + if (cuerrcode != cudaSuccess) { + // 释放之前申请的内存。 + FAIL_SDRONCVX_FREE; + return cuerrcode; + } + } + + // 释放之前申请的 Host 端内存。需要判断输出参数是否在 host 端。 + FAIL_SDRONCVX_FREE; + + // 退出。 + return NO_ERROR; +} +#undef FAIL_SDRONCVX_FREE + +// 宏:FAIL_SDRONCST_FREE +// 该宏用于完成下面函数运行出现错误退出前的内存清理工作。 +#define FAIL_SDRONCST_FREE do { \ + if (convexcst != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(convexcst); \ + } while (0) +// Host 成员方法:smallestDirRectCpu(求给定点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectCpu( + CoordiSet *cst, Quadrangle *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (cst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (cst->count <= 1 || cst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 凸壳点集。 + CoordiSet *convexcst; + + // 创建凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 宏:SDR_USE_CPU_CONVEXHULL + // 该开关宏用于指示是否在后续步骤中使用 CPU 版本的 ConvexHull 函数。 +#define SDR_USE_CPU_CONVEXHULL + + // 初始化 LABEL 数组。 +#ifdef SDR_USE_CPU_CONVEXHULL + // 给该凸壳点集开辟合适的内存空间。 + //errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + errcode = CoordiSetBasicOp::makeAtHost(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。 + errcode = this->cvHull.convexHullCpu(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#else + // 给该凸壳点集开辟合适的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。GPU 版本 + errcode = this->cvHull.convexHull(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#endif +#undef SDR_USE_CPU_CONVEXHULL + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRectCpuOnConvex(convexcst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 清除凸壳点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(convexcst); + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRect(求给定点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRect( + CoordiSet *cst, Quadrangle *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (cst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (cst->count <= 1 || cst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 凸壳点集。 + CoordiSet *convexcst; + + // 创建凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 宏:SDR_USE_CPU_CONVEXHULL + // 该开关宏用于指示是否在后续步骤中使用 CPU 版本的 ConvexHull 函数。 +//#define SDR_USE_CPU_CONVEXHULL + + // 初始化 LABEL 数组。 +#ifdef SDR_USE_CPU_CONVEXHULL + // 给该凸壳点集开辟合适的内存空间。 + //errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + errcode = CoordiSetBasicOp::makeAtHost(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。 + errcode = this->cvHull.convexHullCpu(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#else + // 给该凸壳点集开辟合适的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。GPU 版本 + errcode = this->cvHull.convexHull(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#endif +#undef SDR_USE_CPU_CONVEXHULL + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRectOnConvex(convexcst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 清除凸壳点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(convexcst); + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRectCpu(求给定点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectCpu( + CoordiSet *cst, DirectedRect *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (cst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (cst->count <= 1 || cst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 凸壳点集。 + CoordiSet *convexcst; + + // 创建凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 宏:SDR_USE_CPU_CONVEXHULL + // 该开关宏用于指示是否在后续步骤中使用 CPU 版本的 ConvexHull 函数。 +//#define SDR_USE_CPU_CONVEXHULL + + // 初始化 LABEL 数组。 +#ifdef SDR_USE_CPU_CONVEXHULL + // 给该凸壳点集开辟合适的内存空间。 + //errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + errcode = CoordiSetBasicOp::makeAtHost(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。 + errcode = this->cvHull.convexHullCpu(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#else + // 给该凸壳点集开辟合适的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + //errcode = CoordiSetBasicOp::makeAtHost(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。 + errcode = this->cvHull.convexHull(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#endif +#undef SDR_USE_CPU_CONVEXHULL + + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRectCpuOnConvex(convexcst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 清除凸壳点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(convexcst); + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRect(求给定点集的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRect( + CoordiSet *cst, DirectedRect *outrect, bool hostrect) +{ + // 检查输入,输出是否为空。 + if (cst == NULL || outrect == NULL) + return NULL_POINTER; + + // 如果输入点集中不包含任何点或者只含 1 个坐标点,则报错退出。 + if (cst->count <= 1 || cst->tplData == NULL) + return INVALID_DATA; + + // 局部变量,错误码。 + int errcode; + + // 凸壳点集。 + CoordiSet *convexcst; + + // 创建凸壳点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 宏:SDR_USE_CPU_CONVEXHULL + // 该开关宏用于指示是否在后续步骤中使用 CPU 版本的 ConvexHull 函数。 +//#define SDR_USE_CPU_CONVEXHULL + + // 初始化 LABEL 数组。 +#ifdef SDR_USE_CPU_CONVEXHULL + // 给该凸壳点集开辟合适的内存空间。 + //errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + errcode = CoordiSetBasicOp::makeAtHost(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。 + errcode = this->cvHull.convexHullCpu(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#else + // 给该凸壳点集开辟合适的内存空间。 + errcode = CoordiSetBasicOp::makeAtCurrentDevice(convexcst, cst->count); + //errcode = CoordiSetBasicOp::makeAtHost(convexcst, cst->count); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 调用求凸壳的函数。 + errcode = this->cvHull.convexHull(cst, convexcst); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } +#endif +#undef SDR_USE_CPU_CONVEXHULL + + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRectOnConvex(convexcst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONCST_FREE; + return errcode; + } + + // 清除凸壳点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(convexcst); + + // 退出。 + return NO_ERROR; +} +#undef FAIL_SDRONCST_FREE + +// 宏:FAIL_SDRONIMG_FREE +// 该宏用于完成下面函数运行出现错误退出前的内存清理工作。 +#define FAIL_SDRONIMG_FREE do { \ + if (cst != NULL) \ + CoordiSetBasicOp::deleteCoordiSet(cst); \ + } while (0) + +// Host 成员方法:smallestDirRectCpu(求像素值给定的对象的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectCpu( + Image *inimg, Quadrangle *outrect, bool hostrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + + // 新建点集。 + CoordiSet *cst; + + // 构造点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用图像转点集的函数。 + errcode = this->imgCvt.imgConvertToCst(inimg, cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRectCpu(cst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 清除点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(cst); + + // 退出。 + return NO_ERROR; +} + +__host__ int SmallestDirRect::smallestDirRect( + Image *inimg, Quadrangle *outrect, bool hostrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + + // 新建点集。 + CoordiSet *cst; + + // 构造点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用图像转点集的函数。 + errcode = this->imgCvt.imgConvertToCst(inimg, cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRect(cst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 清除点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(cst); + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRectCpu(求像素值给定的对象的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRectCpu( + Image *inimg, DirectedRect *outrect, bool hostrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + + // 新建点集。 + CoordiSet *cst; + + // 构造点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用图像转点集的函数。 + errcode = this->imgCvt.imgConvertToCst(inimg, cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRectCpu(cst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 清除点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(cst); + + // 退出。 + return NO_ERROR; +} + +// Host 成员方法:smallestDirRect(求像素值给定的对象的最小有向外接矩形) +__host__ int SmallestDirRect::smallestDirRect( + Image *inimg, DirectedRect *outrect, bool hostrect) +{ + // 检查输入图像和输出包围矩形是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outrect == NULL) + return NULL_POINTER; + + // 局部变量,错误码。 + int errcode; + + // 新建点集。 + CoordiSet *cst; + + // 构造点集。 + errcode = CoordiSetBasicOp::newCoordiSet(&cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用图像转点集的函数。 + errcode = this->imgCvt.imgConvertToCst(inimg, cst); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 调用求给定凸壳点集的最小有向外接矩形的函数。 + errcode = smallestDirRect(cst, outrect, hostrect); + if (errcode != NO_ERROR) { + FAIL_SDRONIMG_FREE; + return errcode; + } + + // 清除点集所占用的内容空间。 + CoordiSetBasicOp::deleteCoordiSet(cst); + + // 退出。 + return NO_ERROR; +} +#undef FAIL_SDRONIMG_FREE + diff --git a/okano_3_0/SmallestDirRect.h b/okano_3_0/SmallestDirRect.h new file mode 100644 index 0000000..2db4de6 --- /dev/null +++ b/okano_3_0/SmallestDirRect.h @@ -0,0 +1,326 @@ +// SmallestDirRect.h +// 创建者:刘瑶 +// +// 计算最小有向外接矩形(SmallestDirRect) +// 功能说明:对于已知点集,计算点集的最小有向外接矩形,并最终输出最小有向外接矩 +// 形的信息。 +// 核心算法采用 Rotating Clipers 算法。 +// +// 修订历史: +// 2013年01月04日(刘瑶) +// 初始版本。搭建框架。 +// 2013年01月09日(刘瑶) +// 完善接口设计,完成核函数设计。 +// 2013年01月12日(王雪菲,刘瑶) +// 完成计算相邻凸壳点构成的凸壳边的角度信息的函数。 +// 2013年03月17日(刘瑶) +// 完成最小有向外接矩形的初步计算。 +// 2013年03月18日(刘瑶) +// 修改了部分接口,完成其他接口的最小有向外接矩形的计算。 +// 2013年03月19日(刘瑶) +// 修正了旋转角度的计算 bug,旋转角度方向现在统一为跟包围矩形的长边平行。 +// 修改了包围矩形的四个顶点的取整舍入问题,分别处理。 +// 2013年03月21日(刘瑶) +// 修改了部分代码结构错误。 +// 2013年03月21日(刘瑶) +// 将核函数合并,由目前的 3 个核函数合并为 2 个。计算凸壳点集中每相邻两点的 +// 旋转矩阵信息的核函数和计算新坐标系下凸壳的有向外接矩形的边界信息的核函数 +// 合并,用第二个核函数中每个线程块的第一个线程来计算第一个核函数所需要求的 +// 旋转矩阵信息,利用共享内存的优势。 +// 2013年03月21日(刘瑶) +// 对 4 种点集输入接口,添加了点集的输入检查,处理输入点集为 1 或者 2 个点 +// 的特殊情况。 +// 2013年03月22日(刘瑶) +// 根据图像转换点集的函数接口,修改了部分代码,修改了构造函数和 get 与 set +// 函数,删掉了成员变量像素阈值。 +// 2013年03月23日(刘瑶) +// 修改了最小有向外接矩形的 6 个接口,添加了判断输出是否是 host 端的指针, +// 对于不同的情况分别计算。 +// 2013年03月24日(刘瑶) +// 根据修改的图像转换点集的函数接口,修改对应的调用代码。 +// 2013年09月21日(于玉龙) +// 修正了计算 VALUE 值的一处 BUG。 +// 2013年12月23日(于玉龙) +// 增加了最小有向外接矩形串行算法接口。 + +#ifndef __SMALLESTDIRRECT_H__ +#define __SMALLESTDIRRECT_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "CoordiSet.h" +#include "ConvexHull.h" +#include "Rectangle.h" +#include "ImgConvert.h" + +// 类:SmallestDirRect +// 继承自:无 +// 根据给定的对象的像素值,找出图像中的所要包围的对象,最终输出最小有向外接矩形 +// 的信息。 +class SmallestDirRect { + +protected: + + // 成员变量:imgCvt(图像与坐标集转换) + // 根据给定的图像和阈值,转换成坐标集形式。 + ImgConvert imgCvt; + + // 成员变量:cvHull(凸壳) + // 凸壳,主要计算给定点集的凸壳。 + ConvexHull cvHull; + + // 成员变量:value(图像转换点集的像素阈值) + // 用于图像转换点集的像素阈值。 + unsigned char value; + + // Host 成员方法:sdrComputeBoundInfo(计算凸壳点集中每相邻两点的旋转矩阵 + // 信息,进而计算新坐标系下凸壳的有向外接矩形的边界信息) + // 根据输入的凸壳点,计算顺时针相邻两点的构成的直线与 x 轴的角度,同时计算 + // 旋转矩阵信息。在此基础上,计算新坐标系下各点的坐标。从而计算每个有向外接 + // 矩形的边界点的坐标信息。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + sdrComputeBoundInfo( + CoordiSet *convexcst, // 输入凸壳点集。 + RotationInfo rotateinfo[], // 输出,旋转矩阵信息数组。 + BoundBox bbox[] // 输出,找出的有向外接矩形的边界坐标 + // 信息数组。 + ); + + // Host 成员方法:sdrComputeBoundInfoCpu(计算凸壳点集中每相邻两点的旋转矩阵 + // 信息,进而计算新坐标系下凸壳的有向外接矩形的边界信息) + // 根据输入的凸壳点,计算顺时针相邻两点的构成的直线与 x 轴的角度,同时计算 + // 旋转矩阵信息。在此基础上,计算新坐标系下各点的坐标。从而计算每个有向外接 + // 矩形的边界点的坐标信息。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + sdrComputeBoundInfoCpu( + CoordiSet *convexcst, // 输入凸壳点集。 + RotationInfo rotateinfo[], // 输出,旋转矩阵信息数组。 + BoundBox bbox[] // 输出,找出的有向外接矩形的边界坐标 + // 信息数组。 + ); + + // Host 成员方法:sdrComputeSDR(计算有向外接矩形中面积最小的) + // 根据输入的目前的每个有向外接矩形的长短边长度,计算最小有向外接矩形的 + // 标号索引。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + sdrComputeSDR( + int cstcnt, // 输入,点集中点的数量。 + BoundBox bbox[], // 输入,找出的有向外接矩形的边界坐标信息。 + int *index // 输出,计算出的最小有向外接矩形的标号索引。 + ); + + // Host 成员方法:sdrComputeSDRCpu(计算有向外接矩形中面积最小的) + // 根据输入的目前的每个有向外接矩形的长短边长度,计算最小有向外接矩形的 + // 标号索引。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + sdrComputeSDRCpu( + int cstcnt, // 输入,点集中点的数量。 + BoundBox bbox[], // 输入,找出的有向外接矩形的边界坐标信息。 + int *index // 输出,计算出的最小有向外接矩形的标号索引。 + ); + + // Host 成员方法:sdrParamOnConvex(求凸壳点集的最小有向外接矩形的参数) + // 根据输入的凸壳点集,利用选择卡尺算法,找出点集的最小有向外接矩形的参数。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + sdrParamOnConvex( + CoordiSet *convexcst, // 输入凸壳点集。 + BoundBox *bbox, // 有向外接矩形的边界点的坐标信息。 + RotationInfo *rotinfo // 旋转信息。 + ); + + // Host 成员方法:sdrParamOnConvexCpu(求凸壳点集的最小有向外接矩形的参数) + // 根据输入的凸壳点集,利用选择卡尺算法,找出点集的最小有向外接矩形的参数。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + sdrParamOnConvexCpu( + CoordiSet *convexcst, // 输入凸壳点集。 + BoundBox *bbox, // 有向外接矩形的边界点的坐标信息。 + RotationInfo *rotinfo // 旋转信息。 + ); + +public: + + // 构造函数:SmallestDirRect + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + SmallestDirRect() + { + // 使用默认值为类的各个成员变量赋值。 + this->imgCvt.clearAllConvertFlags(); // 清除所有的转换标志位 + this->value = 128; // 设置转换阈值。 + this->imgCvt.setConvertFlags(this->value, 255); // 置位某范围 + // 内的转换标志位。 + } + + // 成员方法:getValue(获取图像转换点集的像素阈值) + // 获取图像转换点集的像素阈值。 + __host__ __device__ unsigned char // 返回值:图像转换点集的像素阈值。 + getValue() const + { + // 返回图像转换点集的像素阈值。 + return this->value; + } + + // 成员方法:setValue(设置图像转换点集的像素阈值) + // 设置图像转换点集的像素阈值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setValue( + unsigned char value // 设定新的图像转换点集的像素阈值。 + ) { + // 根据阈值设置转换标志位。 + if (value < this->value) + this->imgCvt.setConvertFlags(value, this->value - 1); + else if (value > this->value) + this->imgCvt.clearConvertFlags(this->value, value - 1); + + // 将图像转换点集的像素阈值赋成新值。 + this->value = value; + + return NO_ERROR; + } + + // Host 成员方法:smallestDirRect(求像素值给定的对象的最小有向外接矩形) + // 根据给定的阈值在图像中找出对象,求出对象的凸壳,找出对象的最小有向 + // 外接矩形 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + smallestDirRect( + Image *inimg, // 输入图像。 + Quadrangle *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectCpu(求像素值给定的对象的最小有向外接矩形) + // 根据给定的阈值在图像中找出对象,求出对象的凸壳,找出对象的最小有向 + // 外接矩形 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + smallestDirRectCpu( + Image *inimg, // 输入图像。 + Quadrangle *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRect(求像素值给定的对象的最小有向外接矩形) + // 根据给定的阈值在图像中找出对象,求出对象的凸壳,找出对象的最小有向 + // 外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + smallestDirRect( + Image *inimg, // 输入图像。 + DirectedRect *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectCpu(求像素值给定的对象的最小有向外接矩形) + // 根据给定的阈值在图像中找出对象,求出对象的凸壳,找出对象的最小有向 + // 外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + smallestDirRectCpu( + Image *inimg, // 输入图像。 + DirectedRect *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRect(求给定点集的最小有向外接矩形) + // 根据输入的点集,求出点集的凸壳,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + smallestDirRect( + CoordiSet *cst, // 输入点集。 + Quadrangle *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectCpu(求给定点集的最小有向外接矩形) + // 根据输入的点集,求出点集的凸壳,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + smallestDirRectCpu( + CoordiSet *cst, // 输入点集。 + Quadrangle *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRect(求给定点集的最小有向外接矩形) + // 根据输入的点集,求出点集的凸壳,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + smallestDirRect( + CoordiSet *cst, // 输入点集。 + DirectedRect *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectCpu(求给定点集的最小有向外接矩形) + // 根据输入的点集,求出点集的凸壳,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + smallestDirRectCpu( + CoordiSet *cst, // 输入点集。 + DirectedRect *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectOnConvex(求凸壳点集的最小有向外接矩形) + // 根据输入的凸壳点集,利用选择卡尺算法,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + smallestDirRectOnConvex( + CoordiSet *convexcst, // 输入凸壳点集。 + Quadrangle *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectCpuOnConvex(求凸壳点集的最小有向外接矩形) + // 根据输入的凸壳点集,利用选择卡尺算法,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + smallestDirRectCpuOnConvex( + CoordiSet *convexcst, // 输入凸壳点集。 + Quadrangle *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectOnConvex(求凸壳点集的最小有向外接矩形) + // 根据输入的凸壳点集,利用选择卡尺算法,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + smallestDirRectOnConvex( + CoordiSet *convexcst, // 输入凸壳点集。 + DirectedRect *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); + + // Host 成员方法:smallestDirRectCpuOnConvex(求凸壳点集的最小有向外接矩形) + // 根据输入的凸壳点集,利用选择卡尺算法,找出点集的最小有向外接矩形。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + smallestDirRectCpuOnConvex( + CoordiSet *convexcst, // 输入凸壳点集。 + DirectedRect *outrect, // 有向外接矩形。 + bool hostrect = true // 判断 outrect 是否是 Host 端的指针, + // 默认为“是”。 + ); +}; + +#endif + diff --git a/okano_3_0/SmoothVector.cu b/okano_3_0/SmoothVector.cu new file mode 100644 index 0000000..0341b6f --- /dev/null +++ b/okano_3_0/SmoothVector.cu @@ -0,0 +1,265 @@ +// SmoothVector.cu +// 实现特征向量平滑 + +#include "SmoothVector.h" +#include "Matrix.h" +#include "ErrorCode.h" + + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 256 +#define DEF_BLOCK_Y 1 + + +// 核函数: _initFeatureKer(初始化三个特征值图像) +// 该方法根据给定的 FeatureVecArray 在设备端初始化三个特征值图像 +static __global__ void // Kernel 函数无返回值 +_initFeatureKer( + FeatureVecArray infeaturevecarray, // 输入特征向量 + MatrixCuda cvmatrixcuda, // CV 的暂存图像 + MatrixCuda sdmatrixcuda, // SD 的暂存图像 + MatrixCuda ncmatrixcuda // NC 的暂存图像 +); + +// 核函数: _meanShiftKer(平滑特征向量) +// 该方法以指定坐标集所规定的图像范围内的各 PIXEL 的初始特征向量为基础,进行 +// meanshift 操作,每一个像素对应一个核函数,在核函数中针对该像素对应的特征值 +// 进行,根据给定的操作参数进行若干次的迭代运算,得到一个收敛特征向量 +static __global__ void // Kernel 函数无返回值 +_meanShiftKer( + SmoothVector smoothvector, // 平滑操作类 + FeatureVecArray infeaturevecarray, // 输入特征向量 + FeatureVecArray outfeaturevecarray, // 输出特征向量 + MatrixCuda cvmatrixcuda, // CV 的暂存图像 + MatrixCuda sdmatrixcuda, // SD 的暂存图像 + MatrixCuda ncmatrixcuda // NC 的暂存图像 +); + +// 核函数: _initFeatureKer(初始化三个特征值图像) +static __global__ void _initFeatureKer( + FeatureVecArray infeaturevecarray, MatrixCuda cvmatrixcuda, + MatrixCuda sdmatrixcuda, MatrixCuda ncmatrixcuda) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= infeaturevecarray.count) + return; + + // 取出当前的像素对应特征向量的 X, Y 坐标 + int x = infeaturevecarray.x[index]; + int y = infeaturevecarray.y[index]; + + // 使用记号变量便于代码书写 + int pitch = cvmatrixcuda.pitchWords; + + // 将当前点的特征值赋给三个特征值暂存向量中 + cvmatrixcuda.matMeta.matData[x + y * pitch] = + infeaturevecarray.CV[index]; + sdmatrixcuda.matMeta.matData[x + y * pitch] = + infeaturevecarray.SD[index]; + ncmatrixcuda.matMeta.matData[x + y * pitch] = + infeaturevecarray.NC[index]; +} + + +// 核函数: _meanShiftKer(平滑特征向量) +static __global__ void _meanShiftKer( + SmoothVector smoothvector, FeatureVecArray infeaturevecarray, + FeatureVecArray outfeaturevecarray, MatrixCuda cvmatrixcuda, + MatrixCuda sdmatrixcuda, MatrixCuda ncmatrixcuda) +{ + + // 计算当前 Thread 所对应的坐标集中的点的位置 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果 index 超过了处理的点的个数不做处理直接返回 + if(index >= infeaturevecarray.count) + return; + + // 获取 matrix 的 width 和 height + int width = cvmatrixcuda.matMeta.width; + int height = cvmatrixcuda.matMeta.height; + + // 取出当前的像素对应特征向量的 X, Y 坐标 + int x = infeaturevecarray.x[index]; + int y = infeaturevecarray.y[index]; + + // 获取当前 matrixcuda 的 pitchWords 方便后面定位 pixel + int pitch = cvmatrixcuda.pitchWords; + + // 获取当前操作类 smoothvector 中的 relativeWeight 参数 + float weight = smoothvector.getRelativeWeight(); + + // 使用三个 float 型的临时变量将运算的中间结果暂存 + float cv, sd, nc; + + for (int i = 0; i < smoothvector.getShiftArraySize(); i++) { + // 获取当前操作类 smoothvector 中三个 操作数组中的值 + int count = smoothvector.getShiftCounts()[i]; + int space = smoothvector.getSpaceBands()[i]; + float range = smoothvector.getRangeBands()[i]; + + // 为暂存变量赋值 + cv = cvmatrixcuda.matMeta.matData[x + y * pitch]; + sd = sdmatrixcuda.matMeta.matData[x + y * pitch]; + nc = ncmatrixcuda.matMeta.matData[x + y * pitch]; + + // 每次迭代 ShiftCounts 次 + for (int j = 0; j < count; j++) { + // 定义统计求和存储变量 + float xsum = 0.0f; // 五维向量 A 中 Y 坐标的迭代结果 + float ysum = 0.0f; // 五维向量 A 中 X 坐标的迭代结果 + float cvsum = 0.0f; // 五维向量 A 中特征值 CV 的迭代结果 + float sdsum = 0.0f; // 五维向量 A 中特征值 SD 的迭代结果 + float ncsum = 0.0f; // 五维向量 A 中特征值 NC 的迭代结果 + float bjsum = 0.0f; // 标量 B 的迭代结果 + + // 每次迭代中要遍历当前点的 space 邻域 + for (int k = y - space; k <= y + space; k++) { + for (int l = x - space; l <= x + space; l++) { + // 使用记号变量便于代码书写 + float cvtmp = + cvmatrixcuda.matMeta.matData[l + k * pitch]; + float sdtmp = + sdmatrixcuda.matMeta.matData[l + k * pitch]; + float nctmp = + ncmatrixcuda.matMeta.matData[l + k * pitch]; + + // 根据算法文档,计算邻域向量和当前对应向量的欧式距离 + // 由于在计算向量 A 和标量 B 的时候都需要用到这个欧式距离 + // 故提取公共运算部分,减少重复计算 + float tmp = expf(((x - l) * (x - l) + (y - k) * (y - k)) / + (space * space) * (-weight) - + ((cv - cvtmp) * (cv - cvtmp) + + (sd - sdtmp) * (sd - sdtmp) + + (sd - sdtmp) * (sd - sdtmp)) / + (range * range) * (1 - weight)); + + // 计算五维向量 Aj + xsum += l * tmp; + ysum += k * tmp; + cvsum += cvtmp * tmp; + sdsum += sdtmp * tmp; + ncsum += nctmp * tmp; + + // 计算 Bj + bjsum += tmp; + } + } + // 根据公式处理 Bj + bjsum += 0.001f; + + // 根据公式处理 Aj + xsum /= bjsum; + ysum /= bjsum; + cvsum /= bjsum; + sdsum /= bjsum; + ncsum /= bjsum; + + // 更新 feature + x = (int) xsum; + y = (int) ysum; + cv = cvsum; + sd = sdsum; + nc = ncsum; + + // 如果超过边界则自动取边界上的值 + if (x < space) + x = space; + else if (x >= width - space) + x = width - space - 1; + if (y < space) + y = space; + else if (y >= height - space) + y = height - space - 1; + } + } + + // 把运算结果赋值给 outfeaturevecarray + outfeaturevecarray.x[index] = x; + outfeaturevecarray.y[index] = y; + outfeaturevecarray.CV[index] = cv; + outfeaturevecarray.SD[index] = sd; + outfeaturevecarray.NC[index] = nc; +} + +// Host 成员方法: meanshift(均值偏移) +__host__ int SmoothVector::meanshift(FeatureVecArray *infeaturevecarray, + FeatureVecArray *outfeaturevecarray, int width, int height) +{ + // 检查输入参数是否为 NULL, 如果为 NULL 直接报错返回 + if (infeaturevecarray == NULL || outfeaturevecarray == NULL) + return NULL_POINTER; + + // 检查输入参数是否合法,如果不合法直接报错返回 + if (width < 0 || height < 0) + return INVALID_DATA; + + // 检查操作类参数是否有正确的值,如果没有直接返回错误 + if (this->shiftArraySize == 0 || this->spaceBands == NULL || + this->rangeBands == NULL || this->shiftCounts == NULL) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + Matrix *cvmatrix, *sdmatrix, *ncmatrix; // 特征值矩阵 + + // 创建特征值 matrix 指针 + errcode = MatrixBasicOp::newMatrix(&cvmatrix); + if (errcode != NO_ERROR) + return CUDA_ERROR; + errcode = MatrixBasicOp::newMatrix(&sdmatrix); + if (errcode != NO_ERROR) + return CUDA_ERROR; + errcode = MatrixBasicOp::newMatrix(&ncmatrix); + if (errcode != NO_ERROR) + return CUDA_ERROR; + + // 在设备端申请 matrix 空间 + errcode = MatrixBasicOp::makeAtCurrentDevice(cvmatrix, width, height); + if (errcode != NO_ERROR) + return CUDA_ERROR; + errcode = MatrixBasicOp::makeAtCurrentDevice(sdmatrix, width, height); + if (errcode != NO_ERROR) + return CUDA_ERROR; + errcode = MatrixBasicOp::makeAtCurrentDevice(ncmatrix, width, height); + if (errcode != NO_ERROR) + return CUDA_ERROR; + + // 创建 MatrixCuda 指针 + MatrixCuda *cvmatrixcuda, *sdmatrixcuda, *ncmatrixcuda; // 特征值 + // 设备端矩阵 + // 通过预定义的宏将 Matrix 指针转化为 MatrixCuda 类型的指针 + cvmatrixcuda = MATRIX_CUDA(cvmatrix); + sdmatrixcuda = MATRIX_CUDA(sdmatrix); + ncmatrixcuda = MATRIX_CUDA(ncmatrix); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = infeaturevecarray->count / blocksize.x + 1; + gridsize.y = 1; + + // 调用核函数,初始化三个特征值图像 + _initFeatureKer<<>>( + *infeaturevecarray, *cvmatrixcuda, *sdmatrixcuda, *ncmatrixcuda); + + // 调用核函数,进行 meanshift + _meanShiftKer<<>>( + *this, *infeaturevecarray, *outfeaturevecarray, + *cvmatrixcuda, *sdmatrixcuda, *ncmatrixcuda); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 内存清理 + MatrixBasicOp::deleteMatrix(cvmatrix); + MatrixBasicOp::deleteMatrix(sdmatrix); + MatrixBasicOp::deleteMatrix(ncmatrix); + + return NO_ERROR; +} diff --git a/okano_3_0/SmoothVector.h b/okano_3_0/SmoothVector.h new file mode 100644 index 0000000..6cd3dae --- /dev/null +++ b/okano_3_0/SmoothVector.h @@ -0,0 +1,267 @@ +// SmoothVector.h +// 创建人:邱孝兵 +// +// 平滑向量的生成(Smooth Vector) +// 功能说明:以 FeatureVecCalc 中计算出来的特征向量的第一个为初始中心, +// 根据给定的参数对其进行均值偏移操作,从而得到一个收束点,由于每个像素 +// 都可以得到一个收束点,最后我们得到了一个收束点集,即为所求。 +// +// 修订历史: +// 2012年10月16日(邱孝兵) +// 初始版本。 +// 2012年10月25日(邱孝兵) +// 将求初始特征向量这部分功能分离到 FeatureVecCalc 类中。 +// 2012年11月22日(邱孝兵) +// 修改一些格式问题,修改构造函数赋值方式 +// 2012年12月21日(邱孝兵) +// 根据河边需求的进一步确认,修改了 host 函数的参数,增加输出参数 +// 2012年12月28日(邱孝兵) +// 修改部分格式问题 + +#include "Image.h" +#include "ErrorCode.h" +#include "FeatureVecCalc.h" +#include "FeatureVecArray.h" + +#ifndef __SMOOTHVECTOR_H__ +#define __SMOOTHVECTOR_H__ + + +// 类:SmoothVector +// 继承自:无 +// 以 FeatureVecCalc 中计算出来的特征向量的第一个为初始中心,根据给定的 +// 参数对其进行均值偏移操作,从而得到一个收束点,由于每个像素都可以得到 +// 一个收束点,最后我们得到了一个收束点集,即为所求。 +class SmoothVector { + +protected: + + // 成员变量:relativeWeight(相对权重) + // 在均值偏移(mean shift)的过程中,space 和 range 的相对权重 + float relativeWeight; + + // 成员变量:shiftArraySize(控制 shift 过程的数组的大小) + // 在均值偏移(mean shift)的过程中,处理操作序列有三个长度相同的控制 + // 数组,该变量表示这三个数组的大小 + int shiftArraySize; + + // 成员变量:spaceBands(每次 shift 的邻域宽度) + // 均值偏移过程中,操作序列每次邻域选择的大小为元素的数组。 + int *spaceBands; + + // 成员变量:rangeBands(每次 shift 的外部参数) + // 均值偏移过程中,操作序列每次运算的参数为元素的数组。 + float *rangeBands; + + // 成员变量:shiftCounts(每组 band pair shift 的次数) + // 均值偏移过程中,每组 band pair 进行反复迭代计算的次数为 + // 元素的数组,即原需求文档中的 iter 。 + int *shiftCounts; + +public: + + // 构造函数:SmoothVector + // 无参数版本的构造函数,所有成员变量均初始化为默认值 + __host__ __device__ + SmoothVector() + { + // 给各个成员变量赋默认值 + this->relativeWeight = 0.1f; + this->shiftArraySize = 0; + this->spaceBands = NULL; + this->rangeBands = NULL; + this->shiftCounts = NULL; + } + + // 构造函数:SmoothVector + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + SmoothVector( + float relativeWeight, // 相对权重 + int shiftArraySize, // 操作控制数组大小 + int *spaceBands, // shift 邻域大小 + float *rangeBands, // shift 参数 + int *shiftCounts // shift 重复次数 + ) { + // 根据参数给各个成员变量赋值 + this->relativeWeight = 0.1f; + this->shiftArraySize = 0; + this->spaceBands = NULL; + this->rangeBands = NULL; + this->shiftCounts = NULL; + + // 根据参数列表中的值设定成员变量的初值 + this->setRelativeWeight(relativeWeight); + this->setShiftArraySize(shiftArraySize); + this->setSpaceBands(spaceBands, shiftArraySize); + this->setRangeBands(rangeBands, shiftArraySize); + this->setShiftCounts(shiftCounts, shiftArraySize); + } + + // 成员方法:getRelativeWeight (获取 relativeWeight 的值) + // 获取成员变量 relativeWeight 的值 + __host__ __device__ float // 返回值:成员变量 relativeWeight 的值 + getRelativeWeight() const + { + // 返回成员变量 relativeWeight 的值 + return relativeWeight; + } + + // 成员方法:setRelativeWeight (设置 relativeWeight 的值) + // 设置成员变量 relativeWeight 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setRelativeWeight( + float relativeWeight // 相对权重的大小 + ) { + // 判断传入 relativeWeight 是是0和1之间的小数 + // 如果不是返回 INVALID_DATA 。 + if (relativeWeight <= 0.0 || relativeWeight > 1.0) + return INVALID_DATA; + + // 设置成员变量 relativeWeight 的值 + this->relativeWeight = relativeWeight; + + return NO_ERROR; + } + + // 成员方法:getShiftArraySize (获取 shiftArraySize 的值) + // 获取成员变量 shiftArraySize 的值 + __host__ __device__ int // 返回值:成员变量 shiftArraySize 的值 + getShiftArraySize() const + { + // 返回成员变量 shiftArraySize 的值 + return shiftArraySize; + } + + // 成员方法:setShiftArraySize (设置 shiftArraySize 的值) + // 设置成员变量 shiftArraySize 的值,在这里有个约定,就是先设定数组大小,再 + // 去设定各个操作数组,因为在设定操作数组的时候需要检查新设定的操作数组大小 + // 是否满足要求这样在就必须保证数组大小提前设定一个正确的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR 。 + setShiftArraySize( + int shiftArraySize // 操作控制数组大小 + ) { + // 如果新的 arraySize 比 原来的 arraySize 大, + // 则将三个数组的指针设置为 NULL ,防止出现不一致状态 + if (this->shiftArraySize < shiftArraySize) { + this->spaceBands = NULL; + this->rangeBands = NULL; + this->shiftCounts = NULL; + } + + // 设置成员变量 shiftArraySize 的值 + this->shiftArraySize = shiftArraySize; + + return NO_ERROR; + } + + // 成员方法:getSpaceBands(获取 spaceBands 的值) + // 获取成员变量 spaceBands 的值 + __host__ __device__ int * // 返回值:成员变量 spaceBands 的值 + getSpaceBands() const + { + // 返回成员变量 spaceBands 的值 + return this->spaceBands; + } + + // 成员方法:setSpaceBands(设置 spaceBands 的值) + // 设置成员变量 spaceBands 的值,同时传入的参数还有该数组的长度 + // shiftArraySize 设置的时候会检查该值,和当前的数组长度是否匹配,若不匹配则 + // 不设置 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR 。 + setSpaceBands( + int *spaceBands, // shift 邻域大小 + int shiftArraySize // 操作控制数组大小 + ) { + // 判断传入 shiftArraySize 和当前 shiftArraySize 是否匹配 + // 如果不匹配返回 INVALID_DATA 。 + if (this->shiftArraySize != shiftArraySize) + return INVALID_DATA; + + // 设置 spaceBands + this->spaceBands = spaceBands; + + return NO_ERROR; + } + + // 成员方法:getRangeBands(获取 rangeBands 的值) + // 获取成员变量 rangeBands 的值 + __host__ __device__ float * // 返回值:成员变量 RangeBands 的值 + getRangeBands() const + { + // 返回成员变量 rangeBands 的值 + return this->rangeBands; + } + + // 成员方法:setRangeBands(设置 rangeBands 的值) + // 设置成员变量 rangeBands 的值,同时传入的参数还有该数组的长度 + // shiftArraySize 设置的时候会检查该值,和当前的数组长度是否匹配,若不匹配则 + // 不设置 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR 。 + setRangeBands( + float *rangeBands, // shift 参数数组 + int shiftArraySize // 操作控制数组大小 + ) { + // 判断传入 shiftArraySize 和当前 shiftArraySize 是否匹配 + // 如果不匹配返回 INVALID_DATA 。 + if (this->shiftArraySize != shiftArraySize) + return INVALID_DATA; + + // 设置 rangeBands + this->rangeBands = rangeBands; + + return NO_ERROR; + } + + + // 成员方法:getShiftCounts(获取 shiftCounts 的值) + // 获取成员变量 shiftCounts 的值 + __host__ __device__ int * // 返回值:成员变量 shiftCounts 的值 + getShiftCounts() const + { + // 返回成员变量 shiftCounts 的值 + return this->shiftCounts; + } + + // 成员方法:setShiftCounts(设置 shiftCounts 的值) + // 设置成员变量 shiftCounts 的值,同时传入的参数还有该数组的长度 + // shiftArraySize 设置的时候会检查该值,和当前的数组长度是否匹配,若不匹配则 + // 不设置 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返 + // 回 NO_ERROR 。 + setShiftCounts( + int *shiftCounts, // shift 操作次数 + int shiftArraySize // 操作控制数组大小 + ) { + // 判断传入 shiftArraySize 和当前 shiftArraySize 是否匹配 + // 如果不匹配返回 INVALID_DATA 。 + if (this->shiftArraySize != shiftArraySize) + return INVALID_DATA; + + // 设置 shiftCounts + this->shiftCounts = shiftCounts; + + return NO_ERROR; + } + + // 成员方法:meanshift(均值偏移) + // 该方法以 FeatureVecCalc 中计算出来的特征向量为初始输入值,针对每一个像素 + // 根据给的外部参数 spaceBands(shift 邻域大小)、rangeBands(shift 参数)、 + // shiftCounts(shift 重复次数)进行指定次数的均值偏移得到一个收束点。 + __host__ int // 返回值:函数是否正确执行, + // 若函数正确执行,返回 + // NO_ERROR 。 + meanshift( + FeatureVecArray *infeaturevecarray, // 输入特征向量 + FeatureVecArray *outfeaturevecarray, // 输出特征向量 + int width, // 图像的宽度 + int height // 图像的高度 + ); +}; + +#endif diff --git a/okano_3_0/SortArray.cu b/okano_3_0/SortArray.cu new file mode 100644 index 0000000..6cabe8a --- /dev/null +++ b/okano_3_0/SortArray.cu @@ -0,0 +1,1047 @@ +// SortArray.cu +// 实现并行排序算法 + +#include "SortArray.h" + +#include +#include +using namespace std; + + +// Kernel 函数: _bitonicSortByAscendKer(双调升序排序) +// 实现并行双调排序,按照升序排序。套用 template 模板以便对多类型数据进行处理。 +template < typename Type > +static __global__ void +_bitonicSortByAscendKer( + Type *inarray, // 输入数组。 + Type *outarray, // 排序后的输出数组。 + int length // 数组长度,必须是 2 的整数次方。 +); + +// Kernel 函数: _bitonicSortByDescendKer(双调降序排序) +// 实现并行双调排序,按照降序排序。套用 template 模板以便对多类型数据进行处理。 +template < typename Type > +static __global__ void +_bitonicSortByDescendKer( + Type *inarray, // 输入数组。 + Type *outarray, // 排序后的输出数组。 + int length // 数组长度,必须是 2 的整数次方。 +); + +// Host 静态方法:_bitonicSort(并行双调排序模板函数) +// 并行双调排序模板函数,CLASS 成员方法调用此 Host 函数模板。 +template < typename Type > +static __host__ int +_bitonicSort( + Type *inarray, // 输出数组 + Type *outarray, // 输入数组 + int ishost, // 判断输入和输出数组位置 + int sortflag, // 排序标记 + int length // 排序数组长度 +); + +// Kernel 函数: _oddEvenMergeSortByAscendKer(Batcher's 奇偶合并升序排序) +// 实现并行 Batcher's 奇偶合并排序,按照升序排序。套用 template 模板以便对 +// 多类型数据进行处理。 +template < typename Type > +static __global__ void +_oddEvenMergeSortByAscendKer( + Type *inarray, // 输入数组。 + Type *outarray, // 排序后的输出数组。 + int length, // 数组长度,必须是 2 的幂次方。 + int tempp, // 输入参数。 + int tempq // 输入参数。 +); + +// Kernel 函数: _oddEvenMergeSortByDescendKer(Batcher's 奇偶合并降序排序) +// 实现并行 Batcher's 奇偶合并排序,按照降序排序。套用 template 模板以便对 +// 多类型数据进行处理。 +template < typename Type > +static __global__ void +_oddEvenMergeSortByDescendKer( + Type *inarray, // 输入数组。 + Type *outarray, // 排序后的输出数组。 + int length, // 数组长度,必须是 2 的幂次方。 + int tempp, // 输入参数。 + int tempq // 输入参数。 +); + +// Host 静态方法:_oddEvenMergeSort(Batcher's 奇偶合并降序排序模板函数) +// Batcher's 奇偶合并降序排序模板函数,CLASS 成员方法调用此 Host 函数模板。 +template < typename Type > +static __host__ int +_oddEvenMergeSort( + Type *inarray, // 输出数组 + Type *outarray, // 输入数组 + int ishost, // 判断输入和输出数组位置 + int sortflag, // 排序标记 + int length // 排序数组长度 +); + +// Kernel 函数: _shearSortRowAscKer(行升序排序) +// 对二维数据矩阵的每一行进行双调排序。套用 template 模板以便对多类型数据 +// 进行处理。 +template < typename Type > +static __global__ void +_shearSortRowAscKer( + Type *inarray, // 输入数组。 + int lensec // 矩阵行数。 +); + +// Kernel 函数: _shearSortRowDesKer(行降序排序) +// 对二维数据矩阵的每一行进行双调排序。套用 template 模板以便对多类型数据 +// 进行处理。 +template < typename Type > +static __global__ void +_shearSortRowDesKer( + Type *inarray, // 输入数组。 + int lensec // 矩阵行数。 +); + +// Kernel 函数: _shearSortColAscKer(列升序排序) +// 对二维数据矩阵的每一列进行双调排序。套用 template 模板以便对多类型数据 +// 进行处理。 +template < typename Type > +static __global__ void +_shearSortColAscKer( + Type *inarray, // 输入数组。 + int length, // 矩阵列数。 + int lensec // 矩阵行数。 +); + +// Kernel 函数: _shearSortColDesKer(列降序排序) +// 对二维数据矩阵的每一列进行双调排序。套用 template 模板以便对多类型数据 +// 进行处理。 +template < typename Type > +static __global__ void +_shearSortColDesKer( + Type *inarray, // 输入数组。 + int length, // 矩阵列数。 + int lensec // 矩阵行数。 +); + +// Kernel 函数: _shearToPosKer(转换数据形式) +// 将一维数据转换成二维矩阵。套用 template 模板以便对多类型数据进行处理。 +template < typename Type > +static __global__ void +_shearToPosKer( + Type *inarray, // 输入数组。 + Type *outarray, // 矩阵列数。 + int lensec // 矩阵行数。 +); + +// Host 静态方法:_shearSortLoop(shear 排序核心函数) +// shear 排序的核心函数,需要判断是升序还是降序。 +template < typename Type > +static __host__ int +_shearSortLoop( + Type *inarray, // 输入数组。 + Type *outarray, // 输出数组。 + int length, // 矩阵列数。 + int lensec, // 矩阵行数。 + int sortflag // 排序标识。 +); + +// Host 静态方法:_shearSort(并行 shear 排序模板函数) +// 并行双调排序模板函数,CLASS 成员方法调用此 Host 函数模板。 +template < typename Type > +static __host__ int +_shearSort( + Type *inarray, // 输出数组 + Type *outarray, // 输入数组 + int ishost, // 判断输入和输出数组位置 + int sortflag, // 排序标记 + int length, // 排序数组长度 + int lensec // 排序矩阵的宽度 +); + +// Kernel 函数: _bitonicSortByAscendKer(双调升序排序) +template < typename Type > +static __global__ void _bitonicSortByAscendKer(Type *inarray, Type *outarray, + int length) +{ + // 读取线程号。 + int tid = threadIdx.x; + + // 声明共享内存,加快数据存取速度。 + extern __shared__ unsigned char sharedascend[]; + // 转化为模板类型的共享内存。 + Type *shareddata = (Type *)sharedascend; + + // 将全局内存中的数组拷贝到共享内存了。 + shareddata[tid] = inarray[tid]; + __syncthreads(); + + int k, ixj, j; + Type temp; + + // 并行双调排序,升序排序。 + for (k = 2; k <= length; k <<= 1) { + // 双调合并。 + for (j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 tid 进行比较交换的位置。 + ixj = tid ^ j; + if (ixj > tid) { + // 如果 (tid & k) == 0,按照升序交换两项。 + if ((tid & k) == 0 && (shareddata[tid] > shareddata[ixj])) { + // 交换数组项。 + temp = shareddata[tid]; + shareddata[tid] = shareddata[ixj]; + shareddata[ixj] = temp; + // 如果 (tid & k) == 0,按照降序交换两项。 + } else if ((tid & k) != 0 && + shareddata[tid] < shareddata[ixj]) { + // 交换数组项。 + temp = shareddata[tid]; + shareddata[tid] = shareddata[ixj]; + shareddata[ixj] = temp; + } + } + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + outarray[tid] = shareddata[tid]; +} + +// Kernel 函数: _bitonicSortByDescendKer(双调降序排序) +template < typename Type > +static __global__ void _bitonicSortByDescendKer(Type *inarray, Type *outarray, + int length) +{ + // 读取线程号。 + int tid = threadIdx.x; + + // 声明共享内存,加快数据存取速度。 + extern __shared__ unsigned char shareddescend[]; + // 转化为模板类型的共享内存。 + Type *shareddata = (Type *)shareddescend; + + // 将全局内存中的数组拷贝到共享内存了。 + shareddata[tid] = inarray[tid]; + __syncthreads(); + + int k, ixj, j; + Type temp; + + // 并行双调排序,降序排序。 + for (k = 2; k <= length; k <<= 1) { + // 双调合并。 + for (j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 tid 进行比较交换的位置。 + ixj = tid ^ j; + if (ixj > tid) { + // 如果 (tid & k) == 0,按照降序交换两项。 + if ((tid & k) == 0 && (shareddata[tid] < shareddata[ixj])) { + // 交换数组项。 + temp = shareddata[tid]; + shareddata[tid] = shareddata[ixj]; + shareddata[ixj] = temp; + // 如果 (tid & k) == 0,按照升序交换两项。 + } else if ((tid & k) != 0 && + shareddata[tid] > shareddata[ixj]) { + // 交换数组项。 + temp = shareddata[tid]; + shareddata[tid] = shareddata[ixj]; + shareddata[ixj] = temp; + } + } + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + outarray[tid] = shareddata[tid]; +} + +// Host 静态方法:_bitonicSort(并行双调排序模板函数) +template < typename Type > +static __host__ int _bitonicSort(Type *inarray, Type *outarray, int ishost, + int sortflag, int length) +{ + // 检查输入输出参数是否为空。 + if (inarray == NULL || outarray == NULL) + return NULL_POINTER; + + // 如果输入输出数组在 Host 端。 + if (ishost) { + // 在 Device 上分配空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + Type *alldevicedata, *devinarray, *devoutarray; + cudaerrcode = cudaMalloc((void **)&alldevicedata, + 2 * length * sizeof (Type)); + if (cudaerrcode != cudaSuccess) + return CUDA_ERROR; + + // 通过偏移读取 Device 端内存空间。 + devinarray = alldevicedata; + devoutarray = alldevicedata + length; + + //将 Host 上的 inarray 拷贝到 Device 上的 devinarray 中。 + cudaerrcode = cudaMemcpy(devinarray, inarray, + length * sizeof (Type), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + if (sortflag == SORT_ARRAY_TYPE_ASC) { + // 双调升序排序。 + _bitonicSortByAscendKer<<< + 1, length, length * sizeof (Type)>>>( + devinarray, devoutarray, length); + } else if (sortflag == SORT_ARRAY_TYPE_DESC) { + // 双调降序排序。 + _bitonicSortByDescendKer<<< + 1, length, length * sizeof (Type)>>>( + devinarray, devoutarray, length); + } + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + //将 Device上的 devoutarray 拷贝到 Host上。 + cudaerrcode = cudaMemcpy(outarray, devoutarray, + length * sizeof (Type), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 释放显存上的临时空间。 + cudaFree(alldevicedata); + + // 如果输入输出数组在 Device 端。 + } else { + if (sortflag == SORT_ARRAY_TYPE_ASC) { + // 双调升序排序。 + _bitonicSortByAscendKer<<< + 1, length, length * sizeof (Type)>>>( + inarray, outarray, length); + } else if (sortflag == SORT_ARRAY_TYPE_DESC) { + // 双调降序排序。 + _bitonicSortByDescendKer<<< + 1, length, length * sizeof (Type)>>>( + inarray, outarray, length); + } + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + return NO_ERROR; +} + +// 成员方法:bitonicSort(并行双调排序) +__host__ int SortArray::bitonicSort(int *inarray, int *outarray) +{ + // 调用模板函数并返回。 + return _bitonicSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:bitonicSort(并行双调排序) +__host__ int SortArray::bitonicSort(float *inarray, float *outarray) +{ + // 调用模板函数并返回。 + return _bitonicSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:bitonicSort(并行双调排序) +__host__ int SortArray::bitonicSort(unsigned char *inarray, + unsigned char *outarray) +{ + // 调用模板函数并返回。 + return _bitonicSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:bitonicSort(并行双调排序) +__host__ int SortArray::bitonicSort(char *inarray, char *outarray) +{ + // 调用模板函数并返回。 + return _bitonicSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:bitonicSort(并行双调排序) +__host__ int SortArray::bitonicSort(double *inarray, double *outarray) +{ + // 调用模板函数并返回。 + return _bitonicSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// Kernel 函数: _oddEvenMergeSortByAscendKer(Batcher's 奇偶合并升序排序) +template < typename Type > +static __global__ void _oddEvenMergeSortByAscendKer( + Type *inarray, Type *outarray, int length, int tempp, int tempq) +{ + // 读取线程号CUDA_ERROR。 + int tid = threadIdx.x; + + // 声明共享内存,加快数据存取速度。 + extern __shared__ unsigned char sharedoddascend[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedoddascend; + shared[tid] = inarray[tid]; + __syncthreads(); + + // 声明临时变量。 + int p, q, r, d; + Type temp; + + // 并行Batcher's 奇偶合并排序,升序排序。 + for (p = tempp; p >= 1; p >>= 1) { + // r 是标记位。 + r = 0; + // d 是步长。 + d = p; + for (q = tempq; q >= p; q >>= 1) { + if ((tid < length - d) && ((tid & p) == r) && + shared[tid] > shared[tid + d]) { + // 交换数据项。 + temp = shared[tid]; + shared[tid] = shared[tid + d]; + shared[tid + d] = temp; + } + d = q - p; + r = p; + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + outarray[tid] = shared[tid]; +} + +// Kernel 函数: _oddEvenMergeSortByDescendKer(Batcher's 奇偶合并降序排序) +template < typename Type > +static __global__ void _oddEvenMergeSortByDescendKer( + Type *inarray, Type *outarray, int length, int tempp, int tempq) +{ + // 读取线程号。 + int tid = threadIdx.x; + + // 声明共享内存,加快数据存取速度。 + extern __shared__ unsigned char sharedodddescend[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedodddescend; + shared[tid] = inarray[tid]; + __syncthreads(); + + // 声明临时变量。 + int p , q, r, d; + Type temp; + // 并行 Batcher's 奇偶合并排序,降序排序。 + for (p = tempp; p >= 1; p >>= 1) { + // r 是标记位。 + r = 0; + // d 是步长。 + d = p; + for (q = tempq; q >= p; q >>= 1) { + if ((tid < length - d) && ((tid & p) == r) && + shared[tid] < shared[tid + d]) { + // 交换数据项。 + temp = shared[tid]; + shared[tid] = shared[tid + d]; + shared[tid + d] = temp; + } + d = q - p; + r = p; + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + outarray[tid] = shared[tid]; +} + +// Host 静态方法:_oddEvenMergeSort(Batcher's 奇偶合并降序排序模板函数) +template < typename Type > +static __host__ int _oddEvenMergeSort(Type *inarray, Type *outarray, + int ishost, int sortflag, int length) +{ + // 检查输入输出参数是否为空。 + if (inarray == NULL || outarray == NULL) + return NULL_POINTER; + + // 奇偶合并排序参数。 + int t, tempp, tempq; + t = log((float)length) / log(2.0f); + tempp = 1 << (t - 1); + tempq = 1 << (t - 1); + + // 如果输入输出数组在 Host 端。 + if (ishost) { + // 在 Device 上分配空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + Type *alldevicedata, *devinarray, *devoutarray; + cudaerrcode = cudaMalloc((void **)&alldevicedata, + 2 * length * sizeof (Type)); + if (cudaerrcode != cudaSuccess) + return CUDA_ERROR; + + // 通过偏移读取 Device 端内存空间。 + devinarray = alldevicedata; + devoutarray = alldevicedata + length; + + //将 Host 上的 inarray 拷贝到 Device 上的 devinarray 中。 + cudaerrcode = cudaMemcpy(devinarray, inarray, + length * sizeof (Type), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + if (sortflag == SORT_ARRAY_TYPE_ASC) { + // Batcher's 奇偶升序排序。 + _oddEvenMergeSortByAscendKer<<< + 1, length, length * sizeof (Type)>>>( + devinarray, devoutarray, length, tempp, tempq); + } else if (sortflag == SORT_ARRAY_TYPE_DESC) { + // Batcher's 奇偶降序排序。 + _oddEvenMergeSortByDescendKer<<< + 1, length, length * sizeof (Type)>>>( + devinarray, devoutarray, length, tempp, tempq); + } + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + //将 Device上的 devoutarray 拷贝到 Host上。 + cudaerrcode = cudaMemcpy(outarray, devoutarray, + length * sizeof (Type), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 释放显存上的临时空间。 + cudaFree(alldevicedata); + // 如果输入输出数组在 Device 端。 + } else { + if (sortflag == SORT_ARRAY_TYPE_ASC) { + // Batcher's 奇偶升序排序。 + _oddEvenMergeSortByAscendKer<<< + 1, length, length * sizeof (Type)>>>( + inarray, outarray, length, tempp, tempq); + } else if (sortflag == SORT_ARRAY_TYPE_DESC) { + // Batcher's 奇偶降序排序。 + _oddEvenMergeSortByDescendKer<<< + 1, length, length * sizeof (Type)>>>( + inarray, outarray, length, tempp, tempq); + } + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + + return NO_ERROR; +} + +// 成员方法:oddEvenMergeSort(并行 Batcher's 奇偶合并排序) +__host__ int SortArray::oddEvenMergeSort(int *inarray, int *outarray) +{ + // 调用模板函数并返回。 + return _oddEvenMergeSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:oddEvenMergeSort(并行 Batcher's 奇偶合并排序) +__host__ int SortArray::oddEvenMergeSort(float *inarray, float *outarray) +{ + // 调用模板函数并返回。 + return _oddEvenMergeSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:oddEvenMergeSort(并行 Batcher's 奇偶合并排序) +__host__ int SortArray::oddEvenMergeSort(unsigned char *inarray, + unsigned char *outarray) +{ + // 调用模板函数并返回。 + return _oddEvenMergeSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:oddEvenMergeSort(并行 Batcher's 奇偶合并排序) +__host__ int SortArray::oddEvenMergeSort(char *inarray, char *outarray) +{ + // 调用模板函数并返回。 + return _oddEvenMergeSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// 成员方法:oddEvenMergeSort(并行 Batcher's 奇偶合并排序) +__host__ int SortArray::oddEvenMergeSort(double *inarray, double *outarray) +{ + // 调用模板函数并返回。 + return _oddEvenMergeSort(inarray, outarray, this->ishost, this->sortflag, + this->length); +} + +// Kernel 函数: _shearSortRowAscKer(行升序排序) +template < typename Type > +static __global__ void _shearSortRowAscKer(Type *inarray, int lensec) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + // 将全局内存中的数组拷贝到共享内存了。 + extern __shared__ unsigned char sharedrowasc[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedrowasc; + if (cid < lensec) + shared[cid] = inarray[rid * lensec + cid]; + __syncthreads(); + + // 声明临时变量。 + int ixj; + Type temp; + // 偶数行升序排序。 + if (rid % 2 == 0) { + for (int k = 2; k <= lensec; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照升序交换两项。 + if ((cid & k) == 0 && (shared[cid] > shared[ixj])) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + // 如果 (cid & k) == 0,按照降序交换两项。 + } else if ((cid & k) != 0 && shared[cid] < shared[ixj]) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + } + } + __syncthreads(); + } + } + // 奇数行降序排序。 + } else { + for (int k = 2; k <= lensec; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (shared[cid] < shared[ixj])) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && shared[cid] > shared[ixj]) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + } + } + __syncthreads(); + } + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + if (cid +static __global__ void _shearSortRowDesKer(Type *inarray, int lensec) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + // 将全局内存中的数组拷贝到共享内存了。 + extern __shared__ unsigned char sharedrowdes[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedrowdes; + if (cid < lensec) + shared[cid] = inarray[rid * lensec + cid]; + __syncthreads(); + + // 声明临时变量 + int ixj; + Type temp; + // 偶数行降序排序。 + if (rid % 2 == 0) { + for (int k = 2; k <= lensec; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (shared[cid] < shared[ixj])) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && shared[cid] > shared[ixj]) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + } + } + __syncthreads(); + } + } + // 奇数行升序排序。 + } else { + for (int k = 2; k <= lensec; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (shared[cid] > shared[ixj])) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && shared[cid] < shared[ixj]) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + } + } + __syncthreads(); + } + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + if (cid < lensec) + inarray[rid * lensec + cid] = shared[cid]; +} + +// Kernel 函数: _shearSortColAscKer(列升序排序) +template < typename Type > +static __global__ void _shearSortColAscKer(Type *inarray, + int length, int lensec) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + if (rid >= lensec) + return; + + // 将全局内存中的数组拷贝到共享内存了。 + extern __shared__ unsigned char sharedcolasc[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedcolasc; + if (cid < length) + shared[cid] = inarray[rid + cid * lensec]; + __syncthreads(); + + // 声明临时变量。 + int ixj; + Type temp; + // 并行双调排序,升序排序。 + for (int k = 2; k <= length; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照升序交换两项。 + if ((cid & k) == 0 && (shared[cid] > shared[ixj])) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + // 如果 (cid & k) == 0,按照降序交换两项。 + } else if ((cid & k) != 0 && shared[cid] < shared[ixj]) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + } + } + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + if (cid < length) + inarray[rid + cid * lensec] = shared[cid]; +} + +// Kernel 函数: _shearSortColDesKer(列降序排序) +template < typename Type > +static __global__ void _shearSortColDesKer(Type *inarray, + int length, int lensec) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + if (rid >= lensec) + return; + + // 将全局内存中的数组拷贝到共享内存了。 + extern __shared__ unsigned char sharedcoldes[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedcoldes; + if (cid < length) + shared[cid] = inarray[rid + cid * lensec]; + __syncthreads(); + + // 声明临时变量。 + int ixj; + Type temp; + // 并行双调排序,降序排序。 + for (int k = 2; k <= length; k <<= 1) { + // 双调合并。 + for (int j = k >> 1; j > 0; j >>= 1) { + // ixj 是与当前位置 cid 进行比较交换的位置。 + ixj = cid ^ j; + if (ixj > cid) { + // 如果 (cid & k) == 0,按照降序交换两项。 + if ((cid & k) == 0 && (shared[cid] < shared[ixj])) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + // 如果 (cid & k) == 0,按照升序交换两项。 + } else if ((cid & k) != 0 && shared[cid] > shared[ixj]) { + // 交换数组项。 + temp = shared[cid]; + shared[cid] = shared[ixj]; + shared[ixj] = temp; + } + } + __syncthreads(); + } + } + // 将共享内存中的排序后的数组拷贝到全局内存中。 + if (cid < length) + inarray[rid + cid * lensec] = shared[cid]; +} + +// Kernel 函数: _shearToPosKer(转换数据形式) +template < typename Type > +static __global__ void _shearToPosKer(Type *inarray, Type *outarray, + int lensec) +{ + // 读取线程号和块号。 + int cid = threadIdx.x; + int rid = blockIdx.x; + + // 将全局内存中的数组拷贝到共享内存了。 + extern __shared__ unsigned char sharedpos[]; + // 转化为模板类型的共享内存。 + Type *shared = (Type *)sharedpos; + shared[cid] = inarray[rid * lensec + cid]; + __syncthreads(); + + // 偶数行赋值。 + if (rid % 2 == 0) + outarray[rid * lensec + cid] = shared[cid]; + // 奇数行赋值。 + else + outarray[rid * lensec + cid] = shared[lensec - 1 - cid]; +} + +// Host 静态方法:_shearSortLoop(shear 排序核心函数) +template < typename Type > +static __host__ int _shearSortLoop(Type *inarray, Type * outarray, + int length, int lensec, int sortflag) +{ + // 计算二维数组中长和宽的较大值。 + int judge = (length > lensec) ? length : lensec; + + // 将输入的一维数组转换成二维数组,便于后面的排序操作。 + _shearToPosKer<<>>( + inarray, outarray, lensec); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + for (int i = length; i >= 1; i >>= 1) { + if (sortflag == 2) { + // 首先进行列排序。 + _shearSortColAscKer<<>>( + outarray, length, lensec); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 然后进行行排序。 + _shearSortRowAscKer<<>>( + outarray, lensec); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } else { + // 首先进行列排序。 + _shearSortColDesKer<<>>( + outarray, length, lensec); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 然后进行行排序。 + _shearSortRowDesKer<<>>( + outarray, lensec); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + } + } + // 整理排序后的数组。 + _shearToPosKer<<>>( + outarray, outarray, lensec); + + // 若调用 CUDA 出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + return NO_ERROR; +} + +// Host 静态方法:_shearSort(并行 shear 排序模板函数) +template < typename Type > +static __host__ int _shearSort(Type *inarray, Type *outarray, bool ishost, + int sortflag, int length, int lensec) +{ + // 检查输入输出数组是否为空。 + if (inarray == NULL || outarray == NULL) + return NULL_POINTER; + + // 检查算法参数的有效性。 + if ((sortflag != 1 && sortflag != 2) || (length % 2 != 0) || lensec < 0) + return INVALID_DATA; + + // 数据项总个数。 + int datalength = length * lensec; + // 局部变量,错误码。 + int errcode; + + if (ishost) { + // 在 Device 上分配空间。一次申请所有空间,然后通过偏移索引各个数组。 + cudaError_t cudaerrcode; + Type *alldevicedata, *devinarray, *devoutarray; + cudaerrcode = cudaMalloc((void **)&alldevicedata, + 2 * datalength * sizeof (Type)); + if (cudaerrcode != cudaSuccess) + return CUDA_ERROR; + + // 通过偏移读取 Device 端内存空间。 + devinarray = alldevicedata; + devoutarray = alldevicedata + datalength; + + //将 Host 上的 inarray 拷贝到 Device 上的 devinarray 中。 + cudaerrcode = cudaMemcpy(devinarray, inarray, + datalength * sizeof (Type), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + + // 调用排序核心函数。 + errcode = _shearSortLoop(devinarray, devoutarray, + length, lensec, sortflag); + if (errcode != NO_ERROR) { + cudaFree(alldevicedata); + return errcode; + } + + //将 Device上的 devoutarray 拷贝到 Host 上。 + cudaerrcode = cudaMemcpy(outarray, devoutarray, + datalength * sizeof (Type), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + cudaFree(alldevicedata); + return CUDA_ERROR; + } + + // 释放显存上的临时空间。 + cudaFree(alldevicedata); + return NO_ERROR; + + } else { + // 调用排序核心函数。 + errcode = _shearSortLoop(inarray, outarray, + length, lensec, sortflag); + if (errcode != NO_ERROR) + return errcode; + } + + return NO_ERROR; +} + +// 成员方法:shearSort(并行 shear 排序) +__host__ int SortArray::shearSort(int *inarray, int *outarray) +{ + // 调用模板函数并返回。 + return _shearSort(inarray, outarray, this->ishost, this->sortflag, + this->length, this->lensec); +} + +// 成员方法:shearSort(并行 shear 排序) +__host__ int SortArray::shearSort(float *inarray, float *outarray) +{ + // 调用模板函数并返回。 + return _shearSort(inarray, outarray, this->ishost, this->sortflag, + this->length, this->lensec); +} + +// 成员方法:shearSort(并行 shear 排序) +__host__ int SortArray::shearSort(unsigned char *inarray, + unsigned char *outarray) +{ + // 调用模板函数并返回。 + return _shearSort(inarray, outarray, this->ishost, this->sortflag, + this->length, this->lensec); +} + +// 成员方法:shearSort(并行 shear 排序) +__host__ int SortArray::shearSort(char *inarray, char *outarray) +{ + // 调用模板函数并返回。 + return _shearSort(inarray, outarray, this->ishost, this->sortflag, + this->length, this->lensec); +} + +// 成员方法:shearSort(并行 shear 排序) +__host__ int SortArray::shearSort(double *inarray, double *outarray) +{ + // 调用模板函数并返回。 + return _shearSort(inarray, outarray, this->ishost, this->sortflag, + this->length, this->lensec); +} + diff --git a/okano_3_0/SortArray.h b/okano_3_0/SortArray.h new file mode 100644 index 0000000..5425f00 --- /dev/null +++ b/okano_3_0/SortArray.h @@ -0,0 +1,370 @@ +// SortArray.h +// 创建人:刘宇 +// +// 并行排序(SortArray) +// 功能说明:实现并行排序算法,包括:双调排序,Batcher's 奇偶合并排序, +// 以及 shear 排序。其中,双调排序和 Batcher's 奇偶合并排序 +// 的数组长度不能超过一个块内的 shared 内存最大限制(一般为 1024)。 +// 当数组个数大于 1024 时,可以调用 shear 排序,其最大限制为 +// 1024×1024 的矩阵。 +// +// 修订历史: +// 2012年08月18日(刘宇) +// 初始版本。 +// 2012年08月31日(刘宇,杨伟光) +// 完善注释规范。 +// 2012年09月08日(于玉龙) +// 修改了一些格式不规范的地方。 +// 2012年10月25日 (刘宇) +// 修正了 __device__ 方法的定义位置,防止了跨文件访问出现未定义的错误。 +// 2012年10月27日 (侯怡婷,刘宇) +// 纠正 shear 排序的实现方式。 +// 2012年11月13日(刘宇) +// 在核函数执行后添加 cudaGetLastError 判断语句 +// 2012年11月23日(刘宇) +// 添加输入输出参数的空指针判断 +// 2013年01月17日(杨伟光) +// 添加模版类操作,重载多种排序的数据类型 +// 2013年04月12日(杨伟光) +// 排序添加了 char 和 double 两种数据类型 +// 2013年04月14日(杨伟光,刘宇) +// 修改了一些格式不规范的地方 +// 2013年04月17日(杨伟光) +// 修改了代码中隐含的几处错误 + +#ifndef __SORTARRAY_H__ +#define __SORTARRAY_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 宏:SORT_ARRAY_TYPE_ASC +// 排序标识,升序排序。 +#define SORT_ARRAY_TYPE_ASC 2 + +// 宏:SORT_ARRAY_TYPE_DESC +// 排序标识,降序排序。 +#define SORT_ARRAY_TYPE_DESC 1 + +// 类:SortArray(排序类) +// 继承自:无 +// 实现并行排序算法,包括:双调排序,Batcher's 奇偶合并排序,以及 shear 排序。 +// 注意输入的数组长度必须是 2 的幂次方(排序算法本身要求)。 +class SortArray { + +protected: + + // 成员变量:length(排序数组长度) + // 要求必须是 2 的幂次方。 + int length; + + // 成员变量:lensec(排序矩阵的宽度) + // 默认是一维数组,所以 lensec 等于 1。 + // 要求必须是 2 的幂次方。 + int lensec; + + // 成员变量:sortflag(排序标记) + // sortflag 等于 1,降序排序;sortflag 等于 2,升序排序。 + int sortflag; + + // 成员变量:ishost(判断输入和输出数组位置) + // 当 ishost 等于 true 时,表示输入输出数组在 Host 端,否则 ishost 等于 + // false,表示在 Device 端。 + bool ishost; + +public: + // 构造函数:SortArray + // 无参数构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + SortArray() + { + // 使用默认值为类的各个成员变量赋值。 + this->length = 0; // 排序数组长度默认为 0。 + this->lensec = 1; // 排序矩阵的宽度默认为 1,表示一维数组。 + this->sortflag = 1; // 排序标识,默认为 1,降序排序。 + this->ishost = true; // 判断输入和输出数组位置标记值,默认为 true。 + } + + // 构造函数:SortArray + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + SortArray ( + int length, // 排序数组长度(具体解释见成员变量) + int lensec, // 排序矩阵的宽度(具体解释见成员变量) + int sortflag, // 排序标记(具体解释见成员变量) + bool ishost // 输入和输出数组位置标记(具体解释见成员变量) + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非 + // 法的初始值而使系统进入一个未知的状态。 + this->length = 0; // 排序数组长度默认为 0。 + this->lensec = 1; // 排序矩阵的宽度默认为 1,表示一维数组。 + this->sortflag = 1; // 排序标识,默认为 1,降序排序。 + this->ishost = true; // 判断输入和输出数组位置标记值,默认为 true。 + + // 根据参数列表中的值设定成员变量的初值 + setLength(length); + setLensec(lensec); + setSortflag(sortflag); + setIshost(ishost); + } + + // 成员方法:getLength(读取排序数组长度) + // 读取 lentgh 成员变量。 + __host__ __device__ int // 返回值:当前 length 成员变量的值。 + getLength() const + { + // 返回 length 成员变量的值。 + return this->length; + } + + // 成员方法:setLength(设置排序数组长度) + // 设置 lentgh 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setLength( + int length // 指定的排序数组长度 length。 + ) { + // 如果长度不是 2 的幂次方,则表示非法数据。 + if (length & (length - 1) != 0) + return INVALID_DATA; + + // 将 length 成员变量赋成新值 + this->length = length; + + return NO_ERROR; + } + + // 成员方法:getLensec(读取排序矩阵的宽度) + // 读取 lensec 成员变量。 + __host__ __device__ int // 返回值:当前 lensec 成员变量的值。 + getLensec() const + { + // 返回 lensec 成员变量的值。 + return this->lensec; + } + + // 成员方法:setLensec(设置排序矩阵的宽度) + // 设置 lensec 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setLensec( + int lensec // 指定的排序矩阵的宽度 lensec。 + ) { + // 如果行数小于 0, 则表示非法数据。 + if (lensec < 0) + return INVALID_DATA; + + // 将 lensec 成员变量赋成新值 + this->lensec = lensec; + + return NO_ERROR; + } + + // 成员方法:getSortflag(读取排序标记) + // 读取 sortflag 成员变量。 + __host__ __device__ int // 返回值:当前 sortflag 成员变量的值。 + getSortflag() const + { + // 返回 sortflag 成员变量的值。 + return this->sortflag; + } + + // 成员方法:setSortflag(设置排序标记) + // 设置 sortflag 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setSortflag( + int sortflag // 指定的排序标记。 + ) { + // 排序标识只能为 1 和 2。 + if (sortflag != SORT_ARRAY_TYPE_DESC && + sortflag != SORT_ARRAY_TYPE_ASC) + return INVALID_DATA; + + // 将 sortflag 成员变量赋成新值 + this->sortflag = sortflag; + + return NO_ERROR; + } + + // 成员方法:getIshost(读取判断输入和输出数组位置标记值) + // 读取 ishost 成员变量。 + __host__ __device__ bool // 返回值:当前 ishost 成员变量的值。 + getIshost() const + { + // 返回 ishost 成员变量的值。 + return this->ishost; + } + + // 成员方法:setIshost(设置判断输入和输出数组位置标记值) + // 设置 ishost 成员变量。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setIshost( + bool ishost // 指定的判断输入和输出数组位置标记值。 + ) { + // 将 ishost 成员变量赋成新值 + this->ishost = ishost; + + return NO_ERROR; + } + + // Host 成员方法:bitonicSort(并行双调排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 int 整型。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + bitonicSort( + int *inarray, // 输入数组。 + int *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:bitonicSort(并行双调排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 float 浮点型。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + bitonicSort( + float *inarray, // 输入数组。 + float *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:bitonicSort(并行双调排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 unsigned char 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + bitonicSort( + unsigned char *inarray, // 输入数组。 + unsigned char *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:bitonicSort(并行双调排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 char 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + bitonicSort( + char *inarray, // 输入数组。 + char *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:bitonicSort(并行双调排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 double 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + bitonicSort( + double *inarray, // 输入数组。 + double *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:oddEvenMergeSort(并行Batcher's 奇偶合并排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 int 整型。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + oddEvenMergeSort( + int *inarray, // 输入数组。 + int *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:oddEvenMergeSort(并行Batcher's 奇偶合并排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 float 浮点型。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + oddEvenMergeSort( + float *inarray, // 输入数组。 + float *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:oddEvenMergeSort(并行Batcher's 奇偶合并排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 unsigned char 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + oddEvenMergeSort( + unsigned char *inarray, // 输入数组。 + unsigned char *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:oddEvenMergeSort(并行Batcher's 奇偶合并排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 char 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + oddEvenMergeSort( + char *inarray, // 输入数组。 + char *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:oddEvenMergeSort(并行Batcher's 奇偶合并排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。 + // 排序后的数组输出到 outarray 中。数据类型是 double 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + oddEvenMergeSort( + double *inarray, // 输入数组。 + double *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:shearSort(并行 shear 排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。shear 先进行 + // 列排序,在进行行排序,其中内部调用并行双调排序。排序矩阵最大为 + // 1024×1024 排序后的数组输出到 outarray 中。数据类型是 int 整型。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + shearSort( + int *inarray, // 输入数组。 + int *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:shearSort(并行 shear 排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。shear 先进行 + // 列排序,在进行行排序,其中内部调用并行双调排序。排序矩阵最大为 + // 1024×1024 排序后的数组输出到 outarray 中。数据类型是 float 浮点型。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + shearSort( + float *inarray, // 输入数组。 + float *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:shearSort(并行 shear 排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。shear 先进行 + // 列排序,在进行行排序,其中内部调用并行双调排序。排序矩阵最大为 + // 1024×1024 排序后的数组输出到 outarray 中。数据类型是 unsigned char 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + shearSort( + unsigned char *inarray, // 输入数组。 + unsigned char *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:shearSort(并行 shear 排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。shear 先进行 + // 列排序,在进行行排序,其中内部调用并行双调排序。排序矩阵最大为 + // 1024×1024 排序后的数组输出到 outarray 中。数据类型是 char 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + shearSort( + char *inarray, // 输入数组。 + char *outarray // 排序后的输出数组。 + ); + + // Host 成员方法:shearSort(并行 shear 排序) + // 输入 inarray 的长度必须是 2 的幂次方(这是由算法本身决定)。shear 先进行 + // 列排序,在进行行排序,其中内部调用并行双调排序。排序矩阵最大为 + // 1024×1024 排序后的数组输出到 outarray 中。数据类型是 double 类型。 + __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + shearSort( + double *inarray, // 输入数组。 + double *outarray // 排序后的输出数组。 + ); +}; + +#endif + diff --git a/okano_3_0/SuperSmooth.cu b/okano_3_0/SuperSmooth.cu new file mode 100644 index 0000000..5465d06 --- /dev/null +++ b/okano_3_0/SuperSmooth.cu @@ -0,0 +1,540 @@ +// SuperSmooth.cu +// 对图像进行超平滑处理。 + +#include "SuperSmooth.h" +#include "Template.h" +#include "TemplateFactory.h" + +#include +using namespace std; +#include +// 宏:LOCALCLUSTER_DEF_BLOCK_X 和 LOCALCLUSTER_DEF_BLOCK_Y +// 以及 LOCALCLUSTER_DEF_BLOCK_Z +// 定义了第 1 个 kernel 默认的线程块的尺寸,本算法中采用了三维的线程块。 +#define LOCALCLUSTER_DEF_BLOCK_X 32 +#define LOCALCLUSTER_DEF_BLOCK_Y 2 +#define LOCALCLUSTER_DEF_BLOCK_Z 4 + +// 宏:AVG_DEF_BLOCK_X 和 AVG_DEF_BLOCK_Y +// 定义了第 2 个 kernel 默认的线程块的尺寸,本算法中采用了二维的线程块。 +#define AVG_DEF_BLOCK_X 32 +#define AVG_DEF_BLOCK_Y 8 + +// 宏:SYN_DEF_BLOCK_X 和 SYN_DEF_BLOCK_Y +// 定义了第 3 个 kernel 默认的线程块的尺寸,本算法中采用了二维的线程块。 +#define SYN_DEF_BLOCK_X 32 +#define SYN_DEF_BLOCK_Y 8 + +// 宏:CNTTENPERCENT +// 定义了邻域窗口 10% 的点的数目。 +#define CNTTENPERCENT 121 + +// Device 数组:_argsLocalDev[4][4] +// 因为不同线程处理的点的坐标是不一样的,为了最大化的并行,特意提取出来一些参数, +// 有了这些参数可以增大并行化。 +const int static __device__ _argsLocalDev[4][4] = { + { 0, -1, 0, 1}, {-1, 0, 1, 0}, + {-1, 1, 1, -1}, {-1, -1, 1, 1} +}; + +// Kernel 函数:_localClusterKer(在点的 8 个方向上进行平滑操作) +// 在每一个点的八个方向上遍历一定数量的点,利用这些点的累加值进行算术处理,得到 +// 平滑后的图像值。 +static __global__ void // Kernel 函数无返回值 +_localClusterKer( + ImageCuda inimg, // 输入图像。 + ImageCuda outimg, // 输出图像。 + int diffthred, // 当前像素点和遭遇的点的像素值差相关的阈值。 + int diffcntthred, // 当前像素点的像素值与正遭遇点的像素值差大于等于 + // diffthred 的连续点的个数超过 diffcntthred 时停 + // 止该方向的遍历。 + unsigned char flag[], // 标记数组,初始值都为 0 + int ccntthred, // 它与当前像素点的像素值与正遭遇点的像素值差小于 + // diffthred 的点的个数有关。 + int searchscope // LocalCluster kernel中在每一个方向上搜索的最大 + // 范围。按照河边老师的需求该值小于 16。 +); + +// Kernel 函数:_minmaxAvgKer(利用点的邻域进行平滑处理) +// 对以当前计算点为中心的 window * window 范围内的点排序,计算前后各 10% 的点的 +// 平均值,并以此计算当前点的最终像素值。 +static __global__ void // Kernel 函数无返回值 +_minmaxAvgKer( + ImageCuda inimg, // 输入图像。 + ImageCuda avgimg, // 输出图像。 + Template atemplate // 模板,用来定位邻域点的坐标 +); + +// Kernel 函数:_synthImageKer(综合前两个核函数的结果,得到超平滑的结果) +// 根据前两个和函数的计算结果,得到最终的结果图像。 +static __global__ void // Kernel 函数无返回值 +_synthImageKer( + ImageCuda avgimg, // _minmaxAvgKer 得到的临时图像。 + ImageCuda outimg, // 输出图像。 + unsigned char flag[] // 通过第一个 kernel 得到的标记数组,每一个点都对 + // 应一个标记值,0 或者 1。 +); + +// Device 函数:_sortDev(选择排序,升序) +static __device__ int // 若正确执行该操作,则返回 NO_ERROR。 +_sortDev( + unsigned char pixelarr[], // 要排序的数组 + int length // 数组的长度 +); + +// Device 函数:_sortDev(选择排序,升序) +static __device__ int _sortDev( + unsigned char pixelarr[], int length) +{ + // 判断参数是否为空。 + if (pixelarr == NULL || length <= 0) + return INVALID_DATA; + + // 排序中用到的临时变量,记录数值值。 + unsigned char temp = 0; + // 临时变量,用来记录每一次循环找到的最小值的下标。 + int index = 0; + for (int i = 0; i < length; i++) { + index = i; + temp = pixelarr[i]; + for (int j = i + 1; j < length; j++) { + if (temp > pixelarr[j]) { + index = j; + temp = pixelarr[j]; + } + } + if (index != i) { + pixelarr[index] = pixelarr[i]; + pixelarr[i] = temp; + } + } + // 正确执行,返回 NO_ERROR。 + return NO_ERROR; +} + +// Kernel 函数:_localClusterKer(在点的 8 个方向上进行平滑操作) +static __global__ void _localClusterKer( + ImageCuda inimg, ImageCuda outimg, + int diffthred, int diffcntthred, unsigned char flag[], int ccntthred, + int searchscope) +{ + // 计算当前线程在 Grid 中的位置。其中,c 对应点的横坐标,r 对应点的纵坐标。 + // z 对应的是检索的方向。采用了一个线程处理四个点的策略。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + int z = threadIdx.z; + + // 申请共享内存。 + extern __shared__ unsigned char pixeltmpShd[]; + // 当前线程在线程块内部的索引 + int idxblk = threadIdx.z * blockDim.x * blockDim.y + + threadIdx.y * blockDim.x + threadIdx.x; + + // 当前线程需用到的共享内存,用来存放线程计算出来的该点可能像素值(可能不是 + // 最终值,因为在这些值中最接近当前点的值才被选择为最终值),并将其赋初值 0。 + // flagblkShd 用来为每一个线程标记,当 ccount > cCntThred 时将标记为 1。 + unsigned char *curpixelShd, *flagblkShd; + if (idxblk == 0) { + curpixelShd = pixeltmpShd; + flagblkShd = curpixelShd + LOCALCLUSTER_DEF_BLOCK_X * + LOCALCLUSTER_DEF_BLOCK_Y * LOCALCLUSTER_DEF_BLOCK_Z; + } + flagblkShd[idxblk] = 0; + // 块内同步。 + __syncthreads(); + + // 判断点的坐标是否出界。 + if (c < searchscope || c >= inimg.imgMeta.width - searchscope || + r < searchscope || r >= inimg.imgMeta.height - searchscope) + return; + + // 当前遭遇点的坐标 + int curc = c, curr = r; + + // 当前计算的第一个点的索引,取出像素值。 + int idx = r * inimg.pitchBytes + c; + // 声明数组用来存放当前计算的 4 个点的像素值。 + unsigned char pixel = inimg.imgMeta.imgData[idx]; + + // 分配共享内存,用来存放当前线程遭遇到的满足条件的点的像素值累加和。 + int sumv = 0; + + // 当前遭遇点的像素值和索引,都是临时变量在下面的计算中会用到。 + unsigned char curpixel = 0; + int curidx = idx; + + // 临时变量。 + int ccount = 0, dcount = 0; + + // 当前点和遭遇到的点的像素值差的绝对值。 + unsigned char diff = 0 ; + // 取出 _argsLocalDev 中的值到寄存器中,减少频繁访存带来的开销。 + int offx = _argsLocalDev[z][0]; + int offy = _argsLocalDev[z][1]; + + // 从(x,y)开始向上(左、左下、左上)方向扫描。 + for (int i = 1; i <= searchscope; i++) { + // 当前遭遇到的点的坐标。 + curc = c + offx * i; + curr = r + offy * i; + // 判断点的坐 标是否出界。 + if (curc < 0 || curc >= inimg.imgMeta.width || + curr < 0 || curr >= inimg.imgMeta.height) + break; + + // 当前遭遇到的点的索引和像素值。 + curidx = curc + curr * inimg.pitchBytes; + curpixel = inimg.imgMeta.imgData[curidx]; + // 计算当前点和遭遇点的像素值差的绝对值。 + diff = abs(pixel - curpixel); + // 若 diff 小于了 diffthred 则做以下的处理。 + // 若不满足条件则跳出循环。 + if (diff < diffthred) { + ccount++; + sumv += curpixel; + dcount = 0; + } else { + if (++dcount > diffcntthred) + break; + } + } + dcount = 0; + offx = _argsLocalDev[z][2]; + offy = _argsLocalDev[z][3]; + // 从(x,y)开始向下(右、右上、右下)方向扫描。 + for (int i = 1; i <= searchscope; i++) { + curc = c + offx * i; + curr = r + offy * i; + + // 判断点的坐 标是否出界。 + if (curc < 0 || curc >= inimg.imgMeta.width || + curr < 0 || curr >= inimg.imgMeta.height) + break; + + // 当前遭遇到的点的索引和像素值。 + curidx = curc + curr * inimg.pitchBytes; + curpixel = inimg.imgMeta.imgData[curidx]; + + // 计算当前点和遭遇点的像素值差的绝对值。 + diff = abs(pixel - curpixel); + // 若 diff 小于了 diffthred 则做以下的处理。 + // 若不满足条件则跳出循环。 + if (diff < diffthred) { + ccount++; + sumv += curpixel; + dcount = 0; + } else { + if (++dcount > diffcntthred) + break; + } + } + + if (ccount > ccntthred) { + flag[idx] = 1; + flagblkShd[idxblk] = 1; + // 计算当前点的可能值,并将其存放在共享内存中。 + unsigned char tmppixel = 0; + tmppixel = (unsigned char)(ccount == 0 ? (pixel + 1.0f) / 2.0f : + (pixel + sumv / ccount + 1.0f) / 2.0f); + curpixelShd[idxblk] = (tmppixel > 255 ? 255 : tmppixel); + } + // 块内同步 + __syncthreads(); + + // 在每一个点对应的 4 个之中找到与当前点最接近的值作为该点的最终值。此处只需 + // 用一个线程来实现。 + if (z != 0 || flag[idx] != 1) + return; + + int offinblk = blockDim.x * blockDim.y; // 局部变量,方便以下计算。 + // 从共享内存中将每一个点对应的 4 个值取出来存放在寄存器中。 + unsigned char temppixel[4] = { 0 }; + for (int i = idxblk, j = 0; i < idxblk + 4 * offinblk; + i += offinblk, j++) + temppixel[j] = (flagblkShd[i] == 1 ? curpixelShd[i] : 0); + + // 找到与当前点最接近的像素值。 + unsigned char ipixel = temppixel[0]; + for (int i = 1; i < 4; i++) + if (abs(pixel - ipixel) > abs(pixel - temppixel[i])) + ipixel = temppixel[i]; + + // 当标记值为 0 时,则不赋值。(因此根据本算法要求,当标记值为 0,该点的 + // 最终像素值由第二个核函数来计算得到) + outimg.imgMeta.imgData[idx] = ipixel; +} + +// Kernel 函数:_minmaxAvgKer(利用点的邻域进行平滑处理) +static __global__ void _minmaxAvgKer( + ImageCuda inimg, ImageCuda avgimg, Template atemplate) +{ + // 计算当前线程在 Grid 中的位置。其中,c 对应点的横坐标,r 对应点的纵坐标。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断是否越界。 + if (c < 0 || c >= inimg.imgMeta.width || + r < 0 || r >= inimg.imgMeta.height) + return; + + // 模板内坐标的个数。 + int length = atemplate.count; + + // 分配一个数组用来存放邻域范围的点的像素值。 + unsigned char pixelarr[CNTTENPERCENT]; + + // 当前像素点的和正遭遇到的点一维索引。 + int idx = r * avgimg.pitchBytes + c; + int index = 0; + + // 遭遇到的像素点的横纵坐标。 + int curc = c, curr = r; + + // 当前遭遇点的像素值。 + unsigned char curpixel = 0; + + // 临时变量,记录当前邻域内点的个数,pntcnt 与 length 不一样,length 表示的 + // 是模板内点的数目。 + int pntcnt = 0; + for (int i = 0; i < length; i++) { + curc = c + atemplate.tplData[2 * i]; + curr = r + atemplate.tplData[2 * i + 1]; + + // 判断目前遭遇到的点是否越界。 + if (curc < 0 || curc >= inimg.imgMeta.width || + curr < 0 || curr >= inimg.imgMeta.height) + continue; + + // 计算遭遇到的点的索引。 + index = curr * inimg.pitchBytes + curc; + curpixel = inimg.imgMeta.imgData[index]; + pixelarr[pntcnt++] = curpixel; + } + + // 排序。 + int err = _sortDev(pixelarr, pntcnt); + if (err != NO_ERROR) + return; + + // 计算邻域内 10% 的点数目。 + int cnt10percent = (int)(pntcnt * 0.1f + 0.5f); + + // 计算前后 10% 的平均值 + float sumhigh = pixelarr[pntcnt - 1], sumlow = pixelarr[0]; + for (int i = 1; i < cnt10percent; i++) { + sumlow += pixelarr[i]; + sumhigh += pixelarr[pntcnt - 1 - i]; + } + float high = (cnt10percent == 0 ? sumhigh : sumhigh / cnt10percent); + float low = (cnt10percent == 0 ? sumlow : sumlow / cnt10percent); + + // 计算点的最终值。 + float temp = (high + low) / 2.0f + 0.5f; + avgimg.imgMeta.imgData[idx] = (temp > 255 ? 255 : (unsigned char)temp); +} + +// Kernel 函数:_synthImageKer(综合前两个核函数的结果,得到超平滑的结果) +static __global__ void _synthImageKer( + ImageCuda avgimg, ImageCuda outimg, unsigned char *flag) +{ + // 计算当前线程在 Grid 中的位置。其中,c 对应点的横坐标,r 对应点的纵坐标。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = blockIdx.y * blockDim.y + threadIdx.y; + + // 判断是否越界。 + if (c < 0 || c >= avgimg.imgMeta.width || + r < 0 || r >= avgimg.imgMeta.height) + return; + + // 计算当前线程的一维索引,同时也对应图像中点的坐标。 + int idx = r * avgimg.pitchBytes + c; + + // 更新结果图像中的像素值。 + if (flag[idx] == 0) + outimg.imgMeta.imgData[idx] = avgimg.imgMeta.imgData[idx]; +} + +// 宏:FREE_SUPERSMOOTH(清理 Device 端或者 Host 端的内存) +// 该宏用于清理在 SuperSmooth 过程中申请的设备端或者主机端内存空间。 +#define FREE_SUPERSMOOTH do { \ + if (avgimg != NULL) \ + ImageBasicOp::deleteImage(avgimg); \ + if (flagDev != NULL) \ + cudaFree(flagDev); \ + if (atemplate != NULL) \ + TemplateFactory::putTemplate(atemplate); \ + if (stream[0] != NULL) \ + cudaStreamDestroy(stream[0]); \ + if (stream[1] != NULL) \ + cudaStreamDestroy(stream[1]); \ + } while (0) + +// Host 成员方法:superSmooth(对输入图像进行超平滑操作) +__host__ int SuperSmooth::superSmooth(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL + if (inimg == NULL || inimg->imgData == NULL) + return NULL_POINTER; + + // 输入图像的 ROI 区域尺寸 + int imgroix = inimg->roiX2 - inimg->roiX1; + int imgroiy = inimg->roiY2 - inimg->roiY1; + + int errcode; // 局部变量,接受自定义函数返回的错误值。 + cudaError_t cudaerr; // 局部变量,接受 CUDA 端返回的错误值。 + Image* avgimg = NULL; // 声明的临时图像。 + unsigned char *flagDev = NULL; // 声明一个 Device 端的标记数组。 + Template *atemplate = NULL; // 声明第二个 kernel 需要用到的模板。 + + // 创建两个流,一个用来执行第一个 kernel,另一个用来执行第二个 kernel。 + cudaStream_t stream[2]; + for (int i = 0; i < 2; i++) + cudaStreamCreate(&stream[i]); + + // 将输入图像复制到 device + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将 outimg 复制到 device + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和 + // 输入图像尺寸相同的图像 + if (errcode != NO_ERROR) { + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, imgroix, imgroiy); + // 如果创建图像也操作失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 声明一个临时 ImageCuda 对象,用来存放 _minmaxAvgKer 的结果。 + ImageCuda avgimgCud; + errcode = ImageBasicOp::newImage(&avgimg); + if (errcode != NO_ERROR) { + FREE_SUPERSMOOTH; + return errcode; + } + // 在 Device 端创建该图像。 + errcode = ImageBasicOp::makeAtCurrentDevice(avgimg, inimg->width, + inimg->height); + if (errcode != NO_ERROR) { + FREE_SUPERSMOOTH; + return errcode; + } + // 提取 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(avgimg, &avgimgCud); + if (errcode != NO_ERROR) { + FREE_SUPERSMOOTH; + return errcode; + } + + // 申请一个 GPU 端的标记数组,具有和图像一样的尺寸,初始值设置为 0。 + cudaerr = cudaMalloc((void **)&flagDev, insubimgCud.pitchBytes * + sizeof(unsigned char) * + insubimgCud.imgMeta.height); + if (cudaerr != cudaSuccess) { + FREE_SUPERSMOOTH; + return cudaerr; + } + // 在 Device 端为 flagDev 赋初值为 0。 + cudaerr = cudaMemset(flagDev, 0, insubimgCud.pitchBytes * + insubimgCud.imgMeta.height * sizeof(unsigned char)); + + if (cudaerr != cudaSuccess) { + FREE_SUPERSMOOTH; + return cudaerr; + } + + // 为第一个 kernel 分配线程 + dim3 blocksize1, gridsize1; + blocksize1.x = LOCALCLUSTER_DEF_BLOCK_X; + blocksize1.y = LOCALCLUSTER_DEF_BLOCK_Y; + blocksize1.z = LOCALCLUSTER_DEF_BLOCK_Z; + gridsize1.x = (insubimgCud.imgMeta.width + blocksize1.x - 1) / + blocksize1.x; + gridsize1.y = (insubimgCud.imgMeta.height + blocksize1.y - 1) / + blocksize1.y; + gridsize1.z = 1; + + // 计算第一个核函数的共享内存的大小。 + int memsize = LOCALCLUSTER_DEF_BLOCK_X * LOCALCLUSTER_DEF_BLOCK_Y * + LOCALCLUSTER_DEF_BLOCK_Z * 2 * sizeof(unsigned char); + + // 调用第一个核函数。 + _localClusterKer<<>>( + insubimgCud, outsubimgCud, this->diffThred, this->diffCntThred, + flagDev, this->cCntThred, this->searchScope); + if (cudaGetLastError() != cudaSuccess) { + FREE_SUPERSMOOTH; + return CUDA_ERROR; + } + + // 为第二个 kernel 分配线程 + dim3 blocksize2, gridsize2; + blocksize2.x = AVG_DEF_BLOCK_X; + blocksize2.y = AVG_DEF_BLOCK_Y; + gridsize2.x = (insubimgCud.imgMeta.width + blocksize2.x - 1) / + blocksize2.x; + gridsize2.y = (insubimgCud.imgMeta.height + blocksize2.y - 1) / + blocksize2.y; + + // 创建第二个 kernel 用到的模板。 + dim3 temsize(this->windowSize, this->windowSize, 1); + errcode = TemplateFactory::getTemplate(&atemplate, TF_SHAPE_BOX, + temsize, NULL); +//for (int i = 0; i < atemplate->count;i++) +//cout<tplData[2 * i] <<","<tplData[2 * i + 1]<>>( + insubimgCud, avgimgCud, *atemplate); + if (cudaGetLastError() != cudaSuccess) { + FREE_SUPERSMOOTH; + return CUDA_ERROR; + } + + //数据同步 + cudaThreadSynchronize(); + // 销毁流。 + for (int i = 0; i < 2; i++) + cudaStreamDestroy(stream[i]); + + cudaDeviceSynchronize(); + + // 为第三个 kernel 分配线程 + dim3 blocksize3, gridsize3; + blocksize3.x = SYN_DEF_BLOCK_X; + blocksize3.y = SYN_DEF_BLOCK_Y; + gridsize3.x = (insubimgCud.imgMeta.width + blocksize3.x - 1) / + blocksize3.x; + gridsize3.y = (insubimgCud.imgMeta.height + blocksize3.y - 1) / + blocksize3.y; + + // 调用第三个核函数。 + _synthImageKer<<>>(avgimgCud, outsubimgCud, flagDev); + if (cudaGetLastError() != cudaSuccess) { + FREE_SUPERSMOOTH; + return CUDA_ERROR; + } + + // 处理完毕,退出。 + return NO_ERROR; +} diff --git a/okano_3_0/SuperSmooth.h b/okano_3_0/SuperSmooth.h new file mode 100644 index 0000000..6ad4261 --- /dev/null +++ b/okano_3_0/SuperSmooth.h @@ -0,0 +1,263 @@ +// SuperSmooth.h +// 创建人:刘婷 + +// 超平滑(SuperSmooth) +// 功能说明:对于一幅图像,分别先做两种平滑处理,一种是在每一个点的八个方向上分 +// 别做平滑处理;另一种是利用点的邻域范围内的点做平滑(例如,对于 3*3 +// 邻域内的像素值排序,利用前 10% 的平均值和后 10% 的平均值进行处理), +// 最后再结合两种平滑的结果做最终的处理,得到最终的结果图像。 +// +// 修订历史: +// 2013年09月05日(刘婷) +// 编写头文件。 +// 2013年09月09日(刘婷) +// 实现第 1 个核函数 +// 2013年09月10日(刘婷) +// 实现第 2 个核函数 +// 2013年09月11日(刘婷) +// 实现第 3 个核函数 +// 2013年09月12日(刘婷) +// 调试代码,更改一些重要的 bug。 +// 2013年09月13日(刘婷) +// 更改源文件中的一些 bug。 +// 2013年09月14日(刘婷) +// 继续调试代码,修改文件规范。 +// 2013年12月05日(刘婷) +// 根据2013年11月5日河边老师新发来的文档做了更改,增加了属性searchScope,该 +// 属性在 LocalCluster 中表示每一个方向上搜索的最大范围。 + +#ifndef __SUPERSMOOTH_H__ +#define __SUPERSMOOTH_H__ + +#include "ErrorCode.h" +#include "Image.h" + +// 按照河边老师的文档内容,该宏为 LocalCluster 在每一个方向上的搜索范围。 +#define MAX_SEARCHSCOPE 16 + +// 类:SuperSmooth(超平滑) +// 继承自:无 +// 对于一幅图像,分别先做两种平滑处理,一种是在每一个点的八个方向上分别做平滑处 +// 理;另一种是利用点的邻域范围内的点做平滑(例如,对于 3*3 邻域内的像素值排序, +// 利用前 10% 的平均值和后 10% 的平均值进行处理),最后再结合两种平滑的结果做最 +// 终的处理,得到最终的结果图像。 +class SuperSmooth { + +protected: + + // 成员变量:diffThred(与像素值差相关的阈值) + // 与当前像素点和某方向上遭遇的点的像素值差相关的阈值。 + int diffThred; + + // 成员变量:diffCntThred(与点的个数相关的阈值) + // 当正遭遇点的像素值与当前计算点的像素值差大于等于 difftThred 的多个连续点 + // 的个数超过 diffCntThred 时停止该方向的遍历。 + int diffCntThred; + + // 成员变量:cCntThred(与点的个数相关的阈值,但不同于 diffCntThred) + // 它与当前像素点的像素值与正遭遇点的像素值差小于 diffThred 的点的个数有关。 + int cCntThred; + + // 成员变量:searchScope(LocalCluster kernel中每一个方向上搜索的最大范围) + // LocalCluster kernel中在每一个方向上搜索的最大范围。按照河边老师的需求该 + // 值小于 16。 + int searchScope; + + // 成员变量:windowSize(邻域尺寸) + // 利用邻域进行平滑操作的时候用来限定邻域大小。 + int windowSize; + +public: + + // 构造函数:SuperSmooth + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + SuperSmooth() + { + // 使用默认值为成员变量赋值。 + this->diffThred = 10; // 当前点与遭遇到的点的像素值差的阈值,设置初始 + // 值为 60。 + this->diffCntThred = 5; // 不满足 diffThred 的点个数的上限,若超过则停 + // 止该方向上的遍历,初始值设为 5。 + this->cCntThred = 15; // 在四个方向(左右,上下,两个对角线方向)满 + // 足 diffThred 要求的点的个数的下限,初始值 + // 设置为 10。 + this->windowSize = 5; // 邻域尺寸初始值设置为 5,即默认 3 * 3 的邻域 + // 范围。 + this->searchScope = 8; // LocalCluster kernel中在每一个方向上搜索的最 + // 大范围。 + } + + // 构造函数:SuperSmooth + // 有参数版本的构造函数。 + __host__ __device__ + SuperSmooth( + int diffthred, // 变量含义请见上 + int diffcntthred, // 变量含义请见上 + int ccntthred, // 变量含义请见上 + int windowsize, // 变量含义请见上 + int searchscope // 变量含义请见上 + ){ + // 使用默认值为成员变量赋值, + this->diffThred = 10; // 当前点与遭遇到的点的像素值差的阈值,设置初 + // 始值为 60。 + this->diffCntThred = 5; // 不满足 diffThred 的点个数的上限,若超过则 + // 停止该方向上的遍历,初始值设为 5。 + this->cCntThred = 15; // 在四个方向(左右,上下,两个对角线方向)满 + // 足 diffThred 要求的点的个数的下限,初始值 + // 设置为 10。 + this->windowSize = 5; // 邻域尺寸初始值设置为 5,即默认 3 * 3 的邻域 + // 范围。 + this->searchScope = 8; // LocalCluster kernel中在每一个方向上搜索的最 + // 大范围。 + // 使用给定的参数初始化。 + setDiffThred(diffthred); + setDiffCntThred(diffcntthred); + setCCntThred(ccntthred); + setWindowSize(windowsize); + setWindowSize(searchscope); + } + + // 成员方法:getDiffThred(获取成员变量 diffThred 的值) + // 获取成员变量 diffThred 的值。 + __host__ __device__ int // 返回值:成员变量 diffThred 的值。 + getDiffThred() const + { + // 返回成员变量 diffThred 的值。 + return this->diffThred; + } + + // 成员方法:setDiffThred(设置成员变量 diffThred 的值) + // 设置成员变量 diffThred 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setDiffThred( + int diffthred // 新的 diffThred 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (diffthred <= 0) + return INVALID_DATA; + + // 为成员变量 diffThred 赋新值。 + this->diffThred = diffthred; + + return NO_ERROR; + } + + // 成员方法:getDiffCntThred(获取成员变量 diffCntThred 的值) + // 获取成员变量 diffCntThred 的值。 + __host__ __device__ int // 返回值:成员变量 diffCntThred 的值。 + getDiffCntThred() const + { + // 返回成员变量 diffCntThred 的值。 + return this->diffCntThred; + } + + // 成员方法:setDiffCntThred(设置成员变量 diffCntThred 的值) + // 设置成员变量 diffCntThred 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setDiffCntThred( + int diffcntthred // 新的 diffCntThred 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (diffcntthred <= 0) + return INVALID_DATA; + + // 为成员变量 diffCntThred 赋新值。 + this->diffCntThred = diffcntthred; + + return NO_ERROR; + } + + // 成员方法:getCCntThred(获取成员变量 cCntThred 的值) + // 获取成员变量 cCntThred 的值。 + __host__ __device__ int // 返回值:成员变量 cCntThred 的值。 + getCCntThred() const + { + // 返回成员变量 cCntThred 的值。 + return this->cCntThred; + } + + // 成员方法:setCCntThred(设置成员变量 cCntThred 的值) + // 设置成员变量 cCntThred 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setCCntThred( + int ccntthred // 新的 cCntThred 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (ccntthred <= 0) + return INVALID_DATA; + + // 为成员变量 cCntThred 赋新值。 + this->cCntThred = ccntthred; + + return NO_ERROR; + } + + // 成员方法:getSearchScope(获取成员变量 SearchScope 的值) + // 获取成员变量 cCntThred 的值。 + __host__ __device__ int // 返回值:成员变量 SearchScope 的值。 + getSearchScope() const + { + // 返回成员变量 searchScope 的值。 + return this->searchScope; + } + + // 成员方法:setSearchScope(设置成员变量 searchScope 的值) + // 设置成员变量 searchScope 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setSearchScope( + int searchscope // 新的 searchScope 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (searchscope <= 0 || searchscope >= MAX_SEARCHSCOPE) + return INVALID_DATA; + + // 为成员变量 searchScope 赋新值。 + this->searchScope = searchscope; + + return NO_ERROR; + } + + // 成员方法:getWindowSize(获取成员变量 windowSize的值) + // 获取成员变量 windowSize 的值。 + __host__ __device__ int // 返回值:成员变量 windowSize 的值。 + getWindowSize() const + { + // 返回成员变量 windowSize 的值。 + return this->windowSize; + } + + // 成员方法:setWindow(设置成员变量 windowSize 的值) + // 设置成员变量 windowSize 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + setWindowSize( + int windowsize // 新的 windowSize 值 + ) { + // 判断参数是否合法,若不合法则报错。 + if (windowsize <= 0) + return INVALID_DATA; + + // 为成员变量 windowSize 赋新值。 + this->windowSize = windowsize; + + return NO_ERROR; + } + + // 成员方法:superSmooth(超平滑) + // 给定图像 ,对图像进行平滑处理,得到超级平滑后的图像。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + superSmooth( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + +}; + +#endif + diff --git a/okano_3_0/TemplateFactory.cu b/okano_3_0/TemplateFactory.cu new file mode 100644 index 0000000..7e54a2d --- /dev/null +++ b/okano_3_0/TemplateFactory.cu @@ -0,0 +1,1675 @@ +// TemplateFactory.cu +// 模板工厂产生各种形状的模板 + +#include "TemplateFactory.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:TF_ENABLE_KICK(开启替换规则) +// 开关宏,使能该宏,则在模板池存放满了模板以后,新的模板会替换不常用的模板,但 +// 这种做法可能会拉慢单次调用的性能;如果关闭该宏,则单次处理性能会提高,但是从 +// 总体来看,后续的模板频繁的申请与释放会带来内存处理的压力,总体性能可能会下 +// 降。将以在模板种类使用较少时关闭该宏;如果使用模板种类较多,建议使能该宏。 +#define TF_ENABLE_KICK + +// 结构体:TFTemplateVendor(生产模板的机器) +// 该结构体给出了模板制造的机制,提供了产生新模板、判断参数合法性等方法的接口, +// 各种模板都实现一个这样的结构提的实例,并实现这其中所有的函数指针。并将这个结 +// 构体实例添加到模板机器池中,这样通过外接的接口就可以调用到创造模板的这些函数 +// 接口的实现方法。 +typedef struct TFTemplateVendor_st { + bool (*isLegalParam)(dim3 size, // 不同的模板对于参数有着不同 + void *privated); // 的要求,该函数接口用于判断 + // 给定的参数对应当下的模板类 + // 型是否是合法的。例如,对于 + // 圆形模板我们要求直径要大于 + // 3 且为奇数。该函数返回真时 + // 说明参数是合法的。 + int (*getHashIndex)(dim3 size, // 由于不同类型的模板的参数结 + void *privated); // 构不尽相同,因此其 Hash 算 + // 法也会不同。该接口用于返回 + // 给定参数的 Hash 值,如果给 + // 定的参数不合法,或运算过程 + // 中发生错误,则返回错误码。 + // 返回的 Hash 值范围为 0 至 + // TF_VOL_SHAPE - 1 的整型 + // 数。 + Template *(*createTemplate)(dim3 size, // 该函数接口根据给定的参数返 + void *privated); // 回一个该尺寸的 Template。 + // 如果给定的参数不合法,或者 + // 计算过程中出现错误,则返回 + // NULL。 + bool (*isEqualSize)(dim3 size1, // 该函数用于检查给定的两组参 + void *privated1, // 数是否相等。由于不同的模板 + dim3 size2, // 对参数的要求不同,因此其各 + void *privated2); // 自都有着不同的判断方法,因 + // 此,我们将这个问题抛给具体 + // 模板,如果两个参数中有一个 + // 不合法则恒返回假。 + void *(*copyPrivated)(void *privated); // 由于专属参数 privated 满足 + // 普适性,只能以指针的形式给 + // 出,但如果在模板池中以不稳 + // 定的指针形式存储会给系统带 + // 来风险,因此此函数接口用来 + // 拷贝出一个仅在模板池内部使 + // 用的 privated,将这个参数 + // 存入模板池,是的系统的稳定 + // 性得到保证。 + bool (*deletePrivated)(void *privated); // 释放 privated 的内存。由于 + // 各种不同种类的模板具有不同 + // 的专属参数,因此需要不同的 + // 的释放函数,这个用于从模板 + // 池中踢出模板时使用。 +} TFTemplateVendor; + + +// 定义不同的模板(CLASS 的实现在文件末尾): + +// 标准模板生成器示例代码和一些辅助的函数: + +// Host 函数:_stretchDigit(抻拉二进制数) +// 抻拉一个二进制数,再各个二进制位之间补充若干个 0。该函数作为混悬数据的一个基 +// 础操作存在。二进制数被抻拉后,只保留能存储的低位数据,高位数据被舍去。 +static __host__ unsigned int // 数字被抻拉后的结果 +_stretchDigit( + unsigned int num, // 原始数据 + int extent // 抻拉的程度,即在各位间添加的 0 的个数 +); + +// Host 函数:_combineDigit(混悬一个三维向量) +// 混悬一个三维向量。所谓混悬,即将三个数据从低位到高位排列,混悬后得到的结果的 +// 第 0 至 2 位为输入参数中 x、y、z 分量的第 0 位,结果的第 3 至 5 位位输入参数 +// 中 x、y、z 分量的第 1 位,以此类推。这里可以选择混悬的数量,可选值包括 1、 +// 2、3,分别表示混悬 x 分量(即不混悬),混悬 x、y 分量,以及混悬 x、y、z 分 +// 量。 +static __host__ unsigned int // 混悬的结果 +_combineDigit( + dim3 num, // 输入的数据,三维向量 + int count // 需要混悬的数量,可选值为 1、2、3,对于小于 1 + // 的数,函数直接返回 num 中的 x 分量,对于大于 3 + // 的数,则按照 3 处理。 +); + +// Host 函数:_defIsLegalParam(标准参数判断函数) +// 给出一种一般情况下判断参数是否为合法的函数,对于没有特殊要求的模板类型,可以 +// 直接使用该函数而不需要自己再重新写一个函数。 +static __host__ bool // 返回值:由于没有给定具体的模板类型,该函数会恒返回真 +_defIsLegalParam( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_defGetHashIndex(标准 Hash 算法函数) +// 该函数根据 size 的三维数据进行混合,然后通过取模运算得到合理的 Hash 值,该函 +// 数并不将 private 的值考虑进 Hash 值的计算过程中。 +static __host__ int // 返回值:Hash 值,如果出现错误则该函数返回负数。 +_defGetHashIndex( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_defCreateTemplate(标准模板生成函数) +// 该函数只是用来作为演示这一类函数的书写格式,并不会真正的返回一个模板,该函数 +// 只会返回 NULL。 +static __host__ Template * // 返回值:生成的模板,但该函数只会返回 NULL。 +_defCreateTemplate( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_defIsEqualSize(标准的尺寸相等判断函数) +// 该函数是标准的判断两个尺寸是否相等的函数。该函数通过比较两个尺寸参数的各个维 +// 度上是否相等,以及两个专属参数是否地址相同来判断两个尺寸是否相同,这是一种最 +// 通常的判断方式。 +static __host__ bool // 返回值:给定的两个尺寸是否是一样的。 +_defIsEqualSize( + dim3 size1, // 第一个尺寸的尺寸参数 + void *privated1, // 第一个尺寸的专属参数 + dim3 size2, // 第二个尺寸的尺寸参数 + void *privated2 // 第二个尺寸的专属参数 +); + +// Host 函数:_defCopyPrivated(标准的专属参数拷贝函数) +// 该函数只是用来作为演示这一类函数的书写格式,并不会真正的进行拷贝工作,返回的 +// 指针恒为 NULL。 +static __host__ void * // 返回值:拷贝后和输入参数内容完全一致的新的地址空间的 + // 指针,但本函数只会返回 NULL。 +_defCopyPrivated( + void *privated // 待拷贝的专属参数。 +); + +// Host 函数:_defDeletePrivated(标准的专属参数释放函数) +// 该函数只是用来作为演示这一类函数的书写格式。对于 NULL 参数,该函数不进行任何 +// 操作,对于非 NULL 参数,该函数也不会进行任何处理,因为使用 delete 释放 +// void * 型指针会产生 Warning。 +static __host__ bool // 返回值:是否释放成功,该函数如果参数是 NULL 则返回 + // false。 +_defDeletePrivated( + void *privated // 待释放的专属函数 +); + +// Host 函数:_stretchDigit(抻拉二进制数) +static __host__ unsigned int _stretchDigit(unsigned int num, int extent) +{ + // 由于需要在结果中舍去高位数据,因此这里需要计算在给定的 extent 的情况下, + // 究竟旳多少位为有效位。 + int maxbitlim = 8 * sizeof (unsigned int) / (extent + 1); + + // BIT 游标,用来掩出指定位的 BIT 数据。 + unsigned int bitvernier = 0x1; + // 存放结果的数据。 + unsigned int resnum = 0x0; + + // 从低位开始循环,依次计算每一位的情况,然后逐位赋值给结果。 + for (int i = 0; i < maxbitlim; i++) { + // 通过 BIT 游标掩出当前位的 BIT 数据,然后左移,使得在结果中它和相邻位 + // 之间出现了 extent 个 0。 + resnum |= ((num & bitvernier) << (extent * i)); + + // 左移一位游标,以便下次循环时计算的是更高一位的数据。 + bitvernier <<= 1; + } + // 计算完毕,返回结果数据。 + return resnum; +} + +// Host 函数:_combineDigit(混悬一个三维向量) +static __host__ unsigned int _combineDigit(dim3 num, int count) +{ + // 存放输出结果的累加变量。 + unsigned int res = 0u; + + // 如果 count <= 1,则不需要任何处理,直接将 num.x 返回。 + if (count <= 1) + return num.x; + + // 如果 count >= 3,则将其归一化到 3,并先行处理 num.z(抻拉后通过移位放入 + // 相应的位置) + if (count >= 3) { + count = 3; + res = (_stretchDigit(num.z, count) << 2); + } + + // 此时可以确定 count >= 2,因此这里对 num.x 和 num.y 进行抻拉,并组合到一 + // 起。 + res |= _stretchDigit(num.x, count); + res |= (_stretchDigit(num.y, count) << 1); + + // 计算完毕,将结果返回。 + return res; +} + +// Host 全局变量:_defTemplateVendor(标准模板生成器) +// 归纳定义标准模板生成所需要的函数。 +static TFTemplateVendor _defTemplateVendor = { + _defIsLegalParam, + _defGetHashIndex, + _defCreateTemplate, + _defIsEqualSize, + _defCopyPrivated, + _defDeletePrivated +}; + +// Host 函数:_defIsLegalParam(标准参数判断函数) +static __host__ bool _defIsLegalParam(dim3/* size*/, void * /*privated*/) +{ + // 该函数直接返回 + return true; +} + +// Host 函数:_defGetHashIndex(标准 Hash 算法函数) +static __host__ int _defGetHashIndex(dim3 size, void * /*privated*/) +{ + // 直接将三维的 size 混悬后的数据返回。 + return _combineDigit(size, 3) % TF_VOL_SHAPE; +} + +// Host 函数:_defCreateTemplate(标准模板生成函数) +static __host__ Template *_defCreateTemplate(dim3/* size*/, + void * /*privated*/) +{ + // 该函数只是用来作为演示这一类函数的书写格式,并不会真正的返回一个模板,该 + // 函数只会返回 NULL。 + return NULL; +} + +// Host 函数:_defIsEqualSize(标准的尺寸相等判断函数) +static __host__ bool _defIsEqualSize(dim3 size1, void *privated1, + dim3 size2, void *privated2) +{ + // 本函数采用了一种最为通用的尺寸判断方式,即尺寸参数中各个维度要想等,并且 + // 专属参数的地址值要相等,才能酸味两个尺寸的相等。在实际中具体对于某个类型 + // 的模板来说,这个条件可能会进行一定程度的放宽。 + if (size1.x == size2.x && size1.y == size2.y && size1.z == size2.z && + privated1 == privated2) + return true; + else + return false; +} + +// Host 函数:_defCopyPrivated(标准的专属参数拷贝函数) +static __host__ void *_defCopyPrivated(void * /*privated*/) +{ + // 本演示函数只会返回 NULL。 + return NULL; +} + +// Host 函数:_defDeletePrivated(标准的专属参数释放函数) +static __host__ bool _defDeletePrivated(void *privated) +{ + // 如果输入的参数是 NULL,则直接返回。 + if (privated == NULL) + return false; + + // 使用 delete 关键字释放 privated,然后返回。 + //delete privated; + return true; +} + + +// 方形模板的定义: + +// Host 函数:_boxIsLegalParam(方形模板参数判断函数) +// 检查方形模板的参数是否合格,合格的模板要求尺寸参数的 z 分量为 1,专属参数为 +// NULL(因为方形模板没有专属参数) +static __host__ bool // 返回值:如果模板合法,则返回 true,否则返回 false +_boxIsLegalParam( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_boxGetHashIndex(方形模板 Hash 算法函数) +// 方形模板的 Hash 算法。该算法混悬尺寸参数的 x 和 y 分量,由于方形模板没有专属 +// 参数,所以在计算 Hash 的时候没有考虑专属参数。 +static __host__ int // 返回值:Hash 值,如果出现错误则该函数返回负数。 +_boxGetHashIndex( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_boxCreateTemplate(方形模板生成函数) +// 生成方形模板的函数。 +static __host__ Template * // 返回值:生成的模板,若无法生成模板会返回 NULL。 +_boxCreateTemplate( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_boxIsEqualSize(方形模板的尺寸相等判断函数) +// 方形模板使用了尺寸中的两个维度,因此该函数只会检查尺寸参数的 x 和 y 两个维度 +// 是否相等。 +static __host__ bool // 返回值:给定的两个尺寸是否是一样的。 +_boxIsEqualSize( + dim3 size1, // 第一个尺寸的尺寸参数 + void *privated1, // 第一个尺寸的专属参数 + dim3 size2, // 第二个尺寸的尺寸参数 + void *privated2 // 第二个尺寸的专属参数 +); + +// Host 函数:_boxCopyPrivated(方形模板的专属参数拷贝函数) +// 由于方形模板没有专属参数,并不会真正的进行拷贝工作,会直接返回 NULL。 +static __host__ void * // 返回值:直接返回 NULL。 +_boxCopyPrivated( + void *privated // 待拷贝的专属参数。 +); + +// Host 函数:_boxDeletePrivated(方形模板的专属参数释放函数) +// 由于方形模板没有专属参数,所以该函数不会释放任何的内存空间。如果给定的 +// privated 不是 NULL,则该函数会直接返回 NULL。 +static __host__ bool // 返回值:如果参数为 NULL 返回 true,否则返回 false。 +_boxDeletePrivated( + void *privated // 待释放的专属函数 +); + +// Host 全局变量:_boxTemplateVendor(方形模板生成器) +// 归纳定义方形模板生成所需要的函数。 +static TFTemplateVendor _boxTemplateVendor = { + _boxIsLegalParam, + _boxGetHashIndex, + _boxCreateTemplate, + _boxIsEqualSize, + _boxCopyPrivated, + _boxDeletePrivated +}; + +// Host 函数:_boxIsLegalParam(方形模板参数判断函数) +static __host__ bool _boxIsLegalParam(dim3 size, void *privated) +{ + // 如果尺寸参数的 z 分量不等于 1,或者专属变量不为 NULL 则该判定该参数是非 + // 法参数。 + if (size.z != 1 || privated != NULL) + return false; + // 如果 x 和 y 分量的尺寸小于 1,该参数也会被判定为非法。 + else if (size.x < 1 || size.y < 1) + return false; + else + return true; +} + +// Host 函数:_boxGetHashIndex(方形模板 Hash 算法函数) +static __host__ int _boxGetHashIndex(dim3 size, void * /*privated*/) +{ + // 混悬尺寸参数的 x 和 y 分量,由于方形模板没有专属参数,所以在计算 Hash 的 + // 时候没有考虑专属参数。 + return _combineDigit(size, 2) % TF_VOL_SHAPE; +} + +// Host 函数:_boxCreateTemplate(方形模板生成函数) +static __host__ Template *_boxCreateTemplate(dim3 size, void * /*privated*/) +{ + // 定义临时变指针量 boxtpl,用于模板返回值 + Template *boxtpl; + + // 申请一个新的模板 + int errcode; + errcode = TemplateBasicOp::newTemplate(&boxtpl); + if (errcode != NO_ERROR) + return NULL; + + // 计算模版中点的数量,并在 Host 上获得存储空间 + int count = size.x * size.y; + errcode = TemplateBasicOp::makeAtHost(boxtpl, count); + if (errcode != NO_ERROR) { + TemplateBasicOp::deleteTemplate(boxtpl); + return NULL; + } + + // 将坐标的指针和附加数据的指针取出,然后通过指针游标的方式写入数据 + int *ptsdata = boxtpl->tplData; + float *attdata = ATTACHED_DATA(boxtpl); + + // 计算方形模板中点集的范围。这里设定方形的中心点为原点。 + int startc = -((size.x - 1) / 2), endc = size.x / 2; + int startr = -((size.y - 1) / 2), endr = size.y / 2; + + // 为了使所有坐标点的附加数据加和值为 1,这里取坐标点数量的倒数为每个点的附 + // 加数据。 + float attdataconst = 1.0f / count; + + // 迭代赋值模板中的点集坐标数据和附加数据 + for (int r = startr; r < endr; r++) { + for (int c = startc; c < endc; c++) { + *(ptsdata++) = c; + *(ptsdata++) = r; + *(attdata++) = attdataconst; + } + } + + // 返回方形模板指针 + return boxtpl; +} + +// Host 函数:_boxIsEqualSize(方形模板的尺寸相等判断函数) +static __host__ bool _boxIsEqualSize(dim3 size1, void * /*privated1*/, + dim3 size2, void * /*privated2*/) +{ + // 由于方形只有两维的尺寸,因此这里只考虑 x 和 y 分量 + if (size1.x == size2.x && size1.y == size2.y) + return true; + else + return false; +} + +// Host 函数:_boxCopyPrivated(方形模板的专属参数拷贝函数) +static __host__ void * _boxCopyPrivated(void * /*privated*/) +{ + // 由于方形模板没有专属参数,因此不进行任何的拷贝工作,直接返回。 + return NULL; +} + +// Host 函数:_boxDeletePrivated(方形模板的专属参数释放函数) +static __host__ bool _boxDeletePrivated(void *privated) +{ + // 由于方形模板没有专属参数,因此不进行任何的内存释放,只是象征性的进行一下 + // 判断和返回结果。 + if (privated == NULL) + return true; + else + return false; +} + + +// 圆形模板的定义: + +// Host 函数:_circleIsLegalParam(圆形模板参数判断函数) +// 检查圆形模板的参数是否合格,合格的模板要求尺寸参数的 x 和 y 分量必须相等,且 +// 大于等于 3,z 分量为 1,专属参数为 NULL(因为圆形模板没有专属参数) +static __host__ bool // 返回值:如果模板合法,则返回 true,否则返回 false +_circleIsLegalParam( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_circleGetHashIndex(圆形模板 Hash 算法函数) +// 圆形模板的 Hash 算法。该函数只是将尺寸参数的 x 分量取模。 +static __host__ int // 返回值:Hash 值,如果出现错误则该函数返回负数。 +_circleGetHashIndex( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_circleCreateTemplate(圆形模板生成函数) +// 生成圆形模板的函数。 +static __host__ Template * // 返回值:生成的模板,若无法生成模板会返回 NULL。 +_circleCreateTemplate( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_circleIsEqualSize(圆形模板的尺寸相等判断函数) +// 圆形模板使用了尺寸中的两个维度,因此该函数只会检查尺寸参数的 x 维度是否相 +// 等。 +static __host__ bool // 返回值:给定的两个尺寸是否是一样的。 +_circleIsEqualSize( + dim3 size1, // 第一个尺寸的尺寸参数 + void *privated1, // 第一个尺寸的专属参数 + dim3 size2, // 第二个尺寸的尺寸参数 + void *privated2 // 第二个尺寸的专属参数 +); + +// Host 函数:_circleCopyPrivated(圆形模板的专属参数拷贝函数) +// 由于圆形模板没有专属参数,并不会真正的进行拷贝工作,会直接返回 NULL。 +static __host__ void * // 返回值:直接返回 NULL。 +_circleCopyPrivated( + void *privated // 待拷贝的专属参数。 +); + +// Host 函数:_circleDeletePrivated(圆形模板的专属参数释放函数) +// 由于圆形模板没有专属参数,所以该函数不会释放任何的内存空间。如果给定的 +// privated 不是 NULL,则该函数会直接返回 NULL。 +static __host__ bool // 返回值:如果参数为 NULL 返回 true,否则返回 false。 +_circleDeletePrivated( + void *privated // 待释放的专属函数 +); + +// Host 全局变量:_circleTemplateVendor(圆形模板生成器) +// 归纳定义圆形模板生成所需要的函数。 +static TFTemplateVendor _circleTemplateVendor = { + _circleIsLegalParam, + _circleGetHashIndex, + _circleCreateTemplate, + _circleIsEqualSize, + _circleCopyPrivated, + _circleDeletePrivated +}; + +// Host 函数:_circleIsLegalParam(圆形模板参数判断函数) +static __host__ bool _circleIsLegalParam(dim3 size, void *privated) +{ + // 如果尺寸参数的 z 分量不等于 1,或者专属变量不为 NULL 则该判定该参数是非 + // 法参数。 + if (size.z != 1 || privated != NULL) + return false; + // 如果 x 和 y 分量的尺寸小于 3,或者 x 和 y 分量不想等,该参数也会被判定为 + // 非法。 + else if (size.x < 3 || /*size.y < 3 || */size.x != size.y) + return false; + // 如果尺寸参数为偶数,该参数也会被判定为非法。 + else if (size.x % 2 == 0/* || size.y % 2 == 0*/) + return false; + else + return true; +} + +// Host 函数:_circleGetHashIndex(圆形模板 Hash 算法函数) +static __host__ int _circleGetHashIndex(dim3 size, void * /*privated*/) +{ + // 这里只是用了 x 分量,由于只可能是 x 大于 2 的奇数,因此这里将其除以 2, + // 可以更高效的利用空间。 + return ((size.x - 1) / 2) % TF_VOL_SHAPE; +} + +// Host 函数:_circleCreateTemplate(圆形模板生成函数) +static __host__ Template *_circleCreateTemplate(dim3 size, void * /*privated*/) +{ + // 得到圆形模板半径 + int radius = (size.x - 1) / 2; + radius = ((radius < 1) ? 1 : radius); + int radius2 = radius * radius; + + // 声明一些局部变量,用来保存模板的临时值。这些变量包括 tmptpldata,用来保 + // 存临时的坐标点信息,这段内存空间申请大小为所求圆形的外接矩形的大小; + // count 为游标,记录下一个 tmptpldata 的存储下标,当整个求解完成后,该值存 + // 储信息为整个圆形模板所占用的内存字数;x 和 y 为当前迭代的坐标,其起始位 + // 置为 (0, radius)。 + size_t maxdatasize = 2 * (2 * radius + 1) * (2 * radius + 1); + int *tmptpldata = new int[maxdatasize]; + int count = 0; + int x = 0, y = radius; + + // 如果临时数据空间申请失败,则无法进行后续的操作,则只能报错返回。 + if (tmptpldata == NULL) + return NULL; + + // 整个迭代过程采用经典的八分圆迭代法,即只迭代推导出圆的右上 1/8 部分(依 + // 右手坐标来说),即从 (0, raidus) 至 (sqrt(radius), sqrt(radius)) 段的 + // 点,其余的点都通过圆自身的对称性映射得到。如果迭代得到 (x, y) 为圆上一 + // 点,那么 (-x, y)、(x, -y)、(-x, -y)、(y, x)、(-y, x)、(y, -x)、(-y, -x) + // 也都将是圆上的点。由于本算法要得到一个实心圆体,所以,每得到一对关于 x + // 轴的对称点后,则填充之间的所有点。 + + // 这是算法的第一步,将 (0, radius) 和 (0, -radius) 计入其中。之所以要特殊 + // 对待是因为没有 0 和 -0 之区别,下段代码的 for 循环也是处理特殊的 0 点。 + tmptpldata[count++] = 0; + tmptpldata[count++] = radius; + tmptpldata[count++] = 0; + tmptpldata[count++] = -radius; + + // 计入 (radius, 0) 和 (-radius, 0) 并填充该行内这两点间的所有点。 + for (int ix = -radius; ix <= radius; ix++) { + tmptpldata[count++] = ix; + tmptpldata[count++] = 0; + } + + // 当 x < y 时,(x, y) 处于圆的右上方 1/8 的部分,我们只计算此 1/8 的部分, + // 其他的部分通过圆的对称性计算出来。 + while (x < y) { + // 计算下一个点。这里对于下一个点只有两种可能,一种是 (x + 1, y),另一 + // 种是 (x + 1, y - 1)。具体选择这两种中的哪一个,要看它们谁更接近真实 + // 的圆周曲线。这段代码就是计算这两种情况距离圆周曲线的距离平方(开平方 + // 计算太过复杂,却不影响这里的结果,因此我们没有进行开平方计算,而使用 + // 距离的平方值作为判断的条件)。 + x++; + int d1 = x * x + y * y - radius2; + int d2 = x * x + (y - 1) * (y - 1) - radius2; + d1 = ((d1 < 0) ? -d1 : d1); + d2 = ((d2 < 0) ? -d2 : d2); + + // 比较两个候选点相距圆周曲线的距离 + if (d1 < d2) { + // 如果点 (x + 1, y) 更加接近圆周曲线,则将首先将 90 度对称点的四个 + // 点写入到坐标集中,这里没有进行内部的填充,是水平方向上的内部点已 + // 经在前些步骤时被填充了 + tmptpldata[count++] = x; + tmptpldata[count++] = y; + tmptpldata[count++] = -x; + tmptpldata[count++] = y; + tmptpldata[count++] = x; + tmptpldata[count++] = -y; + tmptpldata[count++] = -x; + tmptpldata[count++] = -y; + } else { + // 如果点 (x + 1, y - 1) 更加接近圆周曲线,则需要将 (-x - 1, y - 1) + // 和 (x + 1, y - 1),以及 (-x - 1, 1 - y) 和 (x + 1, 1 - y) 之间 + // (含)的所有点都添加到坐标集中。 + y--; + + // 由于此前进行了 x++ 操作,所以 y-- 操作会导致 x > y 的情况产生, + // 显然 x > y 的情况都已经被其他的 45 度对称点所处理,因此,这里需 + // 惊醒该检查,一旦发现 x > y 则直接跳出循环。 + if (x > y) + break; + + // 将对应的 y > 0 和 y < 0 所在的横向内部坐标点计入到坐标集中。 + for (int ix = -x; ix <= x; ix++) { + tmptpldata[count++] = ix; + tmptpldata[count++] = y; + tmptpldata[count++] = ix; + tmptpldata[count++] = -y; + } + } + + // 处理 45 度的各个对称点的情况,因为每走一步都有 x + 1 的操作,所以处 + // 理 45 度对称点的时候都需要将对应的两点之间的横向内部点进行填充。但这 + // 里有一个例外的情况,就是当 x >= y 时,该行的点已经在其他计算的 90 度 + // 对称点中进行了处理,所有这些时候,就不需要在对 45 度对称点进行处理 + // 了。 + if (x < y) { + for (int iy = -y; iy <= y; iy++) { + tmptpldata[count++] = iy; + tmptpldata[count++] = x; + tmptpldata[count++] = iy; + tmptpldata[count++] = -x; + } + } + } + + // 申请一个新的 Template 用来承装这些圆形模板中的坐标点集。 + Template *restpl; + int errcode; + + // 申请新的模板。 + errcode = TemplateBasicOp::newTemplate(&restpl); + if (errcode != NO_ERROR) { + // 如果出现错误需要释放掉申请的临时空间以防止内存泄漏。 + delete[] tmptpldata; + return NULL; + } + + // 在 Device 内存上申请合适大小的空间,用来存放坐标数据。 + int ptscnt = count / 2; + errcode = TemplateBasicOp::makeAtHost(restpl, ptscnt); + if (errcode != NO_ERROR) { + // 如果操作失败,需要释放掉之前申请的临时坐标集数据空间和模板数据结构, + // 以防止内存泄漏。 + TemplateBasicOp::deleteTemplate(restpl); + delete[] tmptpldata; + return NULL; + } + + // 将临时坐标集中的坐标数据拷贝到模板的坐标数据中。 + memcpy(restpl->tplData, tmptpldata, count * sizeof (int)); + + // 取出模板的附加数据,然后为附加数据赋值为坐标点个数的倒数。 + float attdataconst = 1.0f / ptscnt; + float *attdata = ATTACHED_DATA(restpl); + + // 用迭代的方式将数据写入到附加数据中。 + for (int i = 0; i < ptscnt; i++) { + *(attdata++) = attdataconst; + } + + // 坐标数据已经拷贝到了需要返回给用户的模板中,因此,这个临时坐标集数据空间 + // 已经不再需要了,因此需要将其释放掉。 + delete[] tmptpldata; + + // 处理完毕,返回已经装配好的模板。 + return restpl; +} + +// Host 函数:_circleIsEqualSize(圆形模板的尺寸相等判断函数) +static __host__ bool _circleIsEqualSize(dim3 size1, void * /*privated1*/, + dim3 size2, void * /*privated2*/) +{ + // 由于圆形只有一维的尺寸,因此这里只考虑 x 分量 + if (size1.x == size2.x/* && size1.y == size2.y*/) + return true; + else + return false; +} + +// Host 函数:_circleCopyPrivated(圆形模板的专属参数拷贝函数) +static __host__ void * _circleCopyPrivated(void * /*privated*/) +{ + // 由于圆形模板没有专属参数,因此不进行任何的拷贝工作,直接返回。 + return NULL; +} + +// Host 函数:_circleDeletePrivated(圆形模板的专属参数释放函数) +static __host__ bool _circleDeletePrivated(void *privated) +{ + // 由于圆形模板没有专属参数,因此不进行任何的内存释放,只是象征性的进行一下 + // 判断和返回结果。 + if (privated == NULL) + return true; + else + return false; +} + + +// 环形模板的定义: + +// Host 函数:_arcIsLegalParam(环形模板参数判断函数) +// 检查环形模板的参数是否合格,合格的模板要求尺寸参数的 z 分量为 1,专属参数为 +// NULL(因为环形模板没有专属参数);此外环形模板要求 x 和 y 分量尺寸必须大于等 +// 于 1,且 y 分量应该大于 x 分量(用 y 分量来定义外环,x 分量来定义内环)。 +static __host__ bool // 返回值:如果模板合法,则返回 true,否则返回 false +_arcIsLegalParam( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_arcGetHashIndex(环形模板 Hash 算法函数) +// 环形模板的 Hash 算法。该算法混悬尺寸参数的 x 和 y 分量,由于环形模板没有专属 +// 参数,所以在计算 Hash 的时候没有考虑专属参数。 +static __host__ int // 返回值:Hash 值,如果出现错误则该函数返回负数。 +_arcGetHashIndex( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_arcCreateTemplate(环形模板生成函数) +// 生成环形模板的函数。 +static __host__ Template * // 返回值:生成的模板,若无法生成模板会返回 NULL。 +_arcCreateTemplate( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_arcIsEqualSize(环形模板的尺寸相等判断函数) +// 环形模板使用了尺寸中的两个维度,因此该函数只会检查尺寸参数的 x 和 y 两个维度 +// 是否相等。 +static __host__ bool // 返回值:给定的两个尺寸是否是一样的。 +_arcIsEqualSize( + dim3 size1, // 第一个尺寸的尺寸参数 + void *privated1, // 第一个尺寸的专属参数 + dim3 size2, // 第二个尺寸的尺寸参数 + void *privated2 // 第二个尺寸的专属参数 +); + +// Host 函数:_arcCopyPrivated(环形模板的专属参数拷贝函数) +// 由于环形模板没有专属参数,并不会真正的进行拷贝工作,会直接返回 NULL。 +static __host__ void * // 返回值:直接返回 NULL。 +_arcCopyPrivated( + void *privated // 待拷贝的专属参数。 +); + +// Host 函数:_arcDeletePrivated(环形模板的专属参数释放函数) +// 由于环形模板没有专属参数,所以该函数不会释放任何的内存空间。如果给定的 +// privated 不是 NULL,则该函数会直接返回 NULL。 +static __host__ bool // 返回值:如果参数为 NULL 返回 true,否则返回 false。 +_arcDeletePrivated( + void *privated // 待释放的专属函数 +); + +// Host 全局变量:_arcTemplateVendor(环形模板生成器) +// 归纳定义环形模板生成所需要的函数。 +static TFTemplateVendor _arcTemplateVendor = { + _arcIsLegalParam, + _arcGetHashIndex, + _arcCreateTemplate, + _arcIsEqualSize, + _arcCopyPrivated, + _arcDeletePrivated +}; + +// Host 函数:_arcIsLegalParam(环形模板参数判断函数) +static __host__ bool _arcIsLegalParam(dim3 size, void *privated) +{ + // 如果尺寸参数的 z 分量不等于 1,或者专属变量不为 NULL 则该判定该参数是非 + // 法参数。 + if (size.z != 1 || privated != NULL) + return false; + // 如果 x 和 y 分量的尺寸小于 1,或者 x 分量大于 y 分量,该参数也会被判定为 + // 非法。 + else if (size.x < 1 || size.y <= size.x) + return false; + // 由于 size 表示直径,因此,这里要求两个同心圆的直径必须皆为奇数。 + else if (size.x % 2 == 0 || size.y % 2 == 0) + return false; + else + return true; +} + +// Host 函数:_arcGetHashIndex(环形模板 Hash 算法函数) +static __host__ int _arcGetHashIndex(dim3 size, void * /*privated*/) +{ + // 混悬尺寸参数的 x 和 y 分量,由于方形模板没有专属参数,所以在计算 Hash 的 + // 时候没有考虑专属参数。由于 x 和 y 分量肯定为奇数,为了保证 Hash 算法的满 + // 满映射,这里分别将 x 和 y 尺寸分量右移一位,抛弃其最低位。 + size.x >>= 1; + size.y >>= 1; + return _combineDigit(size, 2) % TF_VOL_SHAPE; +} + +// Host 函数:_arcCreateTemplate(环形模板生成函数) +static __host__ Template *_arcCreateTemplate(dim3 size, void * /*privated*/) +{ + // 计算得到环形模板内侧圆的半径,以及半径的平方值。 + int sr = (size.x - 1) / 2; + sr = ((sr < 1) ? 1 : sr); + int sr2 = sr * sr; + + // 计算得到环形模板外侧圆的半径,以及半径的平方值。 + int lr = (size.y - 1) / 2; + lr = ((lr <= sr) ? (sr + 1) : lr); + int lr2 = lr * lr; + + // 申请用于存放坐标点的临时空间,为了防止内存越界访问,我们申请了最大可能的 + // 空间,即外侧圆的外接矩形的尺寸。 + int maxsize = 2 * (2 * lr + 1) * (2 * lr + 1); + int *tmptpldata = new int[maxsize]; + + // 初始化迭代求点所必须的一些局部变量。 + int count = 0; // 当前下标。随着各个坐标点的逐渐求出,该值不断递增,用来记 + // 录下一个存储位置的下标值 + int x = 0, sy = sr, ly = lr; // 算法依 x 为主迭代变量,自增 x 后求的合适的 + // y 值,由于存在内外两侧圆形,因此,这里设定 + // 两个 y 的变量,sy 表示内侧圆的 y 值,ly 表 + // 示外侧圆的 y 值。 + + // 整个迭代过程与求解圆形模板的方式相同,采用八分圆方法,通过迭代求解 1 / 8 + // 个圆形,然后通过圆的对称性得到圆的另外部分,在每求解一个坐标后,填充两个 + // 圆之间的部分坐标。 + // 由于内侧圆会比外侧圆更快的达到结束点,因此在达到结束点后,则内测圆取直线 + // x - y = 0 上的点。这样在填充内部点的时候才不会重复处理,将多余的重复点加 + // 入到坐标点集中。 + // _|____ / + // |求解\ / <-- 直线 x - y = 0 + // |区域 \/ + // _|___ /\ + // | \/ \ + // | /\ \ <-- 外侧圆 + // | / \ <-- 内侧圆 + // | / | | + // _|/_____|__|__ + + // 需要实现处理坐标轴上的点,由于 0 不分正负,所以不能通过下面迭代 while 循 + // 环的通用方法来实现。将两个半径之间的点加入坐标点集。 + for (int y = sr; y < lr; y++) { + tmptpldata[count++] = 0; + tmptpldata[count++] = y; + tmptpldata[count++] = 0; + tmptpldata[count++] = -y; + tmptpldata[count++] = y; + tmptpldata[count++] = 0; + tmptpldata[count++] = -y; + tmptpldata[count++] = 0; + } + + // 迭代直到外侧圆的 1 / 8 区域求解完毕。 + while (x < ly) { + // 自加 x,然后分别求解内侧圆和外侧圆的 y 坐标。 + x++; + + // 如果内侧圆还没有求解完成,则需要在两个可能的 y 坐标中选择 y 坐标。 + // 注意,由于上面的自加过程,x 已经更新为下一点的 x 坐标,但该判断语句 + // 需要使用原来的 x 坐标,因此,这里使用 x - 1 做为判断变量。 + if (x - 1 < sy) { + // 从两个可能的下一点坐标 (x + 1, y) 和 (x + 1, y - 1) 中选择一个更 + // 加接近圆形方程的坐标点做为下一点的坐标。 + int sd1 = abs(x * x + sy * sy - sr2); + int sd2 = abs(x * x + (sy - 1) * (sy - 1) - sr2); + sy = (sd1 <= sd2) ? sy : (sy - 1); + } + + // 如果 x >= sy 说明内侧圆已经求解完毕,因此这时应该按照直线 x - y = 0 + // 来计算,这才不会造成重复点。 + if (x >= sy) + sy = x; + + // 求解外侧圆的下一个点坐标,从两个可能的下一点坐标 (x + 1, y) 和 + // (x + 1, y - 1) 中选择一个更加接近圆形方程的坐标点做为下一点的坐标。 + int ld1 = abs(x * x + ly * ly - lr2); + int ld2 = abs(x * x + (ly - 1) * (ly - 1) - lr2); + ly = (ld1 <= ld2) ? ly : (ly - 1); + + // 如果 x > ly 说明外侧圆已经求解完毕,因此跳出迭代。 + if (x > ly) + break; + + // 将两个圆(或者外侧圆与直线 x - y = 0)当前点之间的坐标写入到坐标点集 + // 中。考虑到圆的对称性关系,这里将 8 个对称坐标点也同时写入了坐标点 + // 集。 + for (int y = sy; y < ly; y++) { + tmptpldata[count++] = x; + tmptpldata[count++] = y; + tmptpldata[count++] = -x; + tmptpldata[count++] = y; + tmptpldata[count++] = x; + tmptpldata[count++] = -y; + tmptpldata[count++] = -x; + tmptpldata[count++] = -y; + + // 如果当前点的 x 和 y 相等,那么其关于直线 x - y = 0 或 x + y = 0 + // 的对称点就是其自身,因此没有必要在次加入到坐标点集中。 + if (x == y) + continue; + tmptpldata[count++] = y; + tmptpldata[count++] = x; + tmptpldata[count++] = -y; + tmptpldata[count++] = x; + tmptpldata[count++] = y; + tmptpldata[count++] = -x; + tmptpldata[count++] = -y; + tmptpldata[count++] = -x; + } + } + + // 申请一个新的 Template 用来承装这些圆形模板中的坐标点集。 + Template *restpl; + int errcode; + + // 申请新的模板。 + errcode = TemplateBasicOp::newTemplate(&restpl); + if (errcode != NO_ERROR) { + // 如果出现错误需要释放掉申请的临时空间以防止内存泄漏。 + delete[] tmptpldata; + return NULL; + } + + // 在 Device 内存上申请合适大小的空间,用来存放坐标数据。 + int ptscnt = count / 2; + errcode = TemplateBasicOp::makeAtHost(restpl, ptscnt); + if (errcode != NO_ERROR) { + // 如果操作失败,需要释放掉之前申请的临时坐标集数据空间和模板数据结构, + // 以防止内存泄漏。 + TemplateBasicOp::deleteTemplate(restpl); + delete[] tmptpldata; + return NULL; + } + + // 将临时坐标集中的坐标数据拷贝到模板的坐标数据中。 + memcpy(restpl->tplData, tmptpldata, count * sizeof (int)); + + // 取出模板的附加数据,然后为附加数据赋值为坐标点个数的倒数。 + float attdataconst = 1.0f / ptscnt; + float *attdata = ATTACHED_DATA(restpl); + + // 用迭代的方式将数据写入到附加数据中。 + for (int i = 0; i < ptscnt; i++) { + *(attdata++) = attdataconst; + } + + // 坐标数据已经拷贝到了需要返回给用户的模板中,因此,这个临时坐标集数据空间 + // 已经不再需要了,因此需要将其释放掉。 + delete[] tmptpldata; + + // 处理完毕,返回已经装配好的模板。 + return restpl; +} + +// Host 函数:_arcIsEqualSize(环形模板的尺寸相等判断函数) +static __host__ bool _arcIsEqualSize(dim3 size1, void * /*privated1*/, + dim3 size2, void * /*privated2*/) +{ + // 由于方形只有两维的尺寸,因此这里只考虑 x 和 y 分量 + if (size1.x == size2.x && size1.y == size2.y) + return true; + else + return false; +} + +// Host 函数:_arcCopyPrivated(环形模板的专属参数拷贝函数) +static __host__ void *_arcCopyPrivated(void * /*privated*/) +{ + // 由于环形模板没有专属参数,因此不进行任何的拷贝工作,直接返回。 + return NULL; +} + +// Host 函数:_arcDeletePrivated(环形模板的专属参数释放函数) +static __host__ bool _arcDeletePrivated(void *privated) +{ + // 由于环形模板没有专属参数,因此不进行任何的内存释放,只是象征性的进行一下 + // 判断和返回结果。 + if (privated == NULL) + return true; + else + return false; +} + +// 高斯模板的定义: + +// Host 函数:_gaussIsLegalParam(高斯模板参数判断函数) +// 检查高斯模板的参数是否合格,合格的模板要求尺寸参数的 z 分量为 1,专属参数不 +// 能为NULL;此外高斯模板还要求 x 和 y 分量尺寸必须大于等于 1,且 y 分量必须等 +// 于 x 分量;另外高斯模板要求尺寸必须为奇数。 +static __host__ bool // 返回值:如果模板合法,则返回 true,否则返回 false +_gaussIsLegalParam( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_gaussGetHashIndex(高斯模板 Hash 算法函数) +// 高斯模板的 Hash 算法。该算法之使用尺寸参数的 x 分量,由于高斯模板使用专属 +// 参数,所以在计算 Hash 的时候也考虑专属参数。 +static __host__ int // 返回值:Hash 值,如果出现错误则该函数返回负数。 +_gaussGetHashIndex( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_gaussCreateTemplate(高斯模板生成函数) +// 生成高斯模板的函数。 +static __host__ Template * // 返回值:生成的模板,若无法生成模板会返回 NULL。 +_gaussCreateTemplate( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_gaussIsEqualSize(高斯模板的尺寸相等判断函数) +// 高斯模板使用了尺寸中的一个维度,因此该函数只会检查尺寸参数的 x 维度是否相 +// 等。同时还检查了 privated 在 float 型数据的条件下是否相等(即两个数的差的绝 +// 对值小于某一个很小的正数) +static __host__ bool // 返回值:给定的两个尺寸是否是一样的。 +_gaussIsEqualSize( + dim3 size1, // 第一个尺寸的尺寸参数 + void *privated1, // 第一个尺寸的专属参数 + dim3 size2, // 第二个尺寸的尺寸参数 + void *privated2 // 第二个尺寸的专属参数 +); + +// Host 函数:_gaussCopyPrivated(高斯模板的专属参数拷贝函数) +// 按照 float 型数据的方式,申请一个新的 float 型数据空间,并将 privated 所指向 +// 的 float 型数据拷贝到新的空间中。 +static __host__ void * // 返回值:如果 privated 为 NULL,则返回 NULL;否则返回 + // 新申请的数据空间。 +_gaussCopyPrivated( + void *privated // 待拷贝的专属参数。 +); + +// Host 函数:_gaussDeletePrivated(高斯模板的专属参数释放函数) +// 按照释放一个 float 型数据的地址空间的方法释放给定的空间。如果 privated 是 +// NULL 则不进行任何操作。 +static __host__ bool // 返回值:如果释放成功返回 true,对于 NULL 参数,返回 + // false。 +_gaussDeletePrivated( + void *privated // 待释放的专属函数 +); + +// Host 全局变量:_gaussTemplateVendor(高斯模板生成器) +// 归纳定义高斯模板生成所需要的函数。 +static TFTemplateVendor _gaussTemplateVendor = { + _gaussIsLegalParam, + _gaussGetHashIndex, + _gaussCreateTemplate, + _gaussIsEqualSize, + _gaussCopyPrivated, + _gaussDeletePrivated +}; + +// Host 函数:_gaussIsLegalParam(高斯模板参数判断函数) +static __host__ bool _gaussIsLegalParam(dim3 size, void *privated) +{ + // 由于高斯模板使用了 float 型的专属参数,因此需要保证 privated 不为 NULL。 + if (privated == NULL) + return false; + // 由于只是用了一个维度的尺寸变量,这里要求 z 维度必须为 1。 + else if (size.z != 1) + return false; + // 这里了要求尺寸必须大于等于 3 且 x 和 y 分量必须相等 + else if (size.x < 3 || size.y != size.x) + return false; + // 这里还要求尺寸必须为奇数。 + else if (size.x % 2 == 0) + return false; + // 如果附属数据的值等于 0,则判断为非法。 + else if (fabs(*((float *)privated)) < 1.0e-8f) + return false; + else + return true; +} + +// Host 函数:_gaussGetHashIndex(高斯模板 Hash 算法函数) +static __host__ int _gaussGetHashIndex(dim3 size, void *privated) +{ + // 如果高斯模板的专属参数为 NULL,则返回 -1 报错。 + if (privated == NULL) + return -1; + + // 这里将尺寸参数的 x 分量和专属参数进行异或拼合。考虑到 size.x 为大于等于 + // 3 的奇数,为了更加有效的利用存储空间,这里将 size.x / 2 - 1 以消除码距。 + return ((size.x / 2 - 1) ^ (int)(fabs(*(float *)privated) * 10.0f)) % + TF_VOL_SHAPE; +} + +// Host 函数:_gaussCreateTemplate(高斯模板生成函数) +static __host__ Template *_gaussCreateTemplate(dim3 size, void *privated) +{ + // 如果专属参数为 NULL 则报错返回。 + if (privated == NULL) + return NULL; + + // 取出专属参数的值,该值在为 float 型,在生成高斯模板的过程中,称为 sigma + float sigma = *((float *)privated); + // 如果 sigma 值等于 0 则无法完成后续计算,报错退出。 + if (fabs(sigma) < 1.0e-8f) + return NULL; + // 计算出 2 * sigma ^ 2,方便后续计算使用。 + float sigma22 = 2 * sigma * sigma; + + // 计算出半径尺寸。 + int radius = size.x / 2; + if (radius < 1) + radius = 1; + // 根据半径推算出边长和模板中点的总数量。 + int edgelen = 2 * radius + 1; + int maxptscnt = edgelen * edgelen; + + // 申请新的模板 + Template *restpl; + int errcode; + errcode = TemplateBasicOp::newTemplate(&restpl); + if (errcode != NO_ERROR) + return NULL; + + // 为新模板申请内存空间。 + errcode = TemplateBasicOp::makeAtHost(restpl, maxptscnt); + if (errcode != NO_ERROR) { + TemplateBasicOp::deleteTemplate(restpl); + return NULL; + } + + // 取出存放坐标点和附属数据的内存空间指针,这样可以通过游标指针方便对数据空 + // 间进行赋值操作。 + int *tpldata = restpl->tplData; + float *attdata = ATTACHED_DATA(restpl); + + // 首先将原点信息添加到模板中。 + *(tpldata++) = 0; + *(tpldata++) = 0; + *(attdata++) = 1.0f; + + // 之后依次从内向外逐渐的添加模板数据,之所以从内向外添加模板数据,是因为这 + // 样做可以实现很好的复用性,即,模板的前 i * i 个元素就是边长为 i 的高斯模 + // 板。 + for (int i = 1; i <= radius; i++) { + // 计算指定半径下的各个坐标点信息。这里,利用对称性,计算处一个边的坐标 + // 点然后利用对称性,得到其他的坐标点。为了防止拐角点重复计算,这里计算 + // 的范围为 -i - 1 到 i。 + for (int j = -i + 1; j <= i; j++) { + // 由于四个对称点的二阶模相等,因此预先计算出附属数据,以减少计算 + // 量。 + float curatt = exp(-(i * i + j * j) / sigma22); + + // 将对称的四个坐标点添加到模板中,同时添加附属数据。 + *(tpldata++) = i; + *(tpldata++) = j; + *(attdata++) = curatt; + *(tpldata++) = -i; + *(tpldata++) = j; + *(attdata++) = curatt; + *(tpldata++) = j; + *(tpldata++) = i; + *(attdata++) = curatt; + *(tpldata++) = j; + *(tpldata++) = -i; + *(attdata++) = curatt; + } + } + + // 计算完毕,返回新的模板。 + return restpl; +} + +// Host 函数:_gaussIsEqualSize(高斯模板的尺寸相等判断函数) +static __host__ bool _gaussIsEqualSize(dim3 size1, void *privated1, + dim3 size2, void *privated2) +{ + // 如果专属参数为 NULL,则恒返回 false 报错。 + if (privated1 == NULL || privated2 == NULL) + return false; + + // 如果按照 float 类型判断,两个专属参数不相等(即绝对值差大于某个小正 + // 数),则判定为不相等。 + if (fabs(*((float *)privated1) - *((float *)privated2)) >= 1.0e-6f) + return false; + + // 如果尺寸参数不相等,则会判定为两个参数不相等 + if (size1.x != size2.x/* || size1.y != size2.y*/) + return false; + + return true; +} + +// Host 函数:_gaussCopyPrivated(高斯模板的专属参数拷贝函数) +static __host__ void *_gaussCopyPrivated(void *privated) +{ + // 如果专属参数为 NULL,则直接返回 NULL。 + if (privated == NULL) + return NULL; + + // 申请一个 float 型的空间 + float *resptr = new float; + if (resptr == NULL) + return NULL; + + // 然后将数据拷贝如这个空间内。 + *resptr = *((float *)privated); + + // 返回这个已拷贝了数据的新申请的空间地址。 + return resptr; +} + +// Host 函数:_gaussDeletePrivated(高斯模板的专属参数释放函数) +static __host__ bool _gaussDeletePrivated(void *privated) +{ + // 如果专属参数为 NULL,则直接返回 NULL。 + if (privated == NULL) + return false; + + // 释放掉 privated 的空间。 + delete (float *)privated; + return true; +} + + +// 欧式模板的定义: + +// Host 函数:_euclideIsLegalParam(欧式模板参数判断函数) +// 检查欧式模板的参数是否合格,合格的模板要求尺寸参数的 z 分量为 1,专属参数不 +// 能为NULL;此外欧式模板还要求 x 和 y 分量尺寸必须大于等于 1,且 y 分量必须等 +// 于 x 分量或者 y 分量等于 1。 +static __host__ bool // 返回值:如果模板合法,则返回 true,否则返回 false +_euclideIsLegalParam( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_euclideGetHashIndex(欧式模板 Hash 算法函数) +// 欧式模板的 Hash 算法。该算法之使用尺寸参数的 x 分量,由于欧式模板使用专属 +// 参数,所以在计算 Hash 的时候也考虑专属参数。 +static __host__ int // 返回值:Hash 值,如果出现错误则该函数返回负数。 +_euclideGetHashIndex( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_euclideCreateTemplate(欧式模板生成函数) +// 生成欧式模板的函数。 +static __host__ Template * // 返回值:生成的模板,若无法生成模板会返回 NULL。 +_euclideCreateTemplate( + dim3 size, // 尺寸参数 + void *privated // 专属参数 +); + +// Host 函数:_euclideIsEqualSize(欧式模板的尺寸相等判断函数) +// 欧式模板使用了尺寸中的一个维度,因此该函数只会检查尺寸参数的 x 维度是否相 +// 等。同时还检查了 privated 在 float 型数据的条件下是否相等(即两个数的差的绝 +// 对值小于某一个很小的正数) +static __host__ bool // 返回值:给定的两个尺寸是否是一样的。 +_euclideIsEqualSize( + dim3 size1, // 第一个尺寸的尺寸参数 + void *privated1, // 第一个尺寸的专属参数 + dim3 size2, // 第二个尺寸的尺寸参数 + void *privated2 // 第二个尺寸的专属参数 +); + +// Host 函数:_euclideCopyPrivated(欧式模板的专属参数拷贝函数) +// 按照 float 型数据的方式,申请一个新的 float 型数据空间,并将 privated 所指向 +// 的 float 型数据拷贝到新的空间中。 +static __host__ void * // 返回值:如果 privated 为 NULL,则返回 NULL;否则返回 + // 新申请的数据空间。 +_euclideCopyPrivated( + void *privated // 待拷贝的专属参数。 +); + +// Host 函数:_euclideDeletePrivated(欧式模板的专属参数释放函数) +// 按照释放一个 float 型数据的地址空间的方法释放给定的空间。如果 privated 是 +// NULL 则不进行任何操作。 +static __host__ bool // 返回值:如果释放成功返回 true,对于 NULL 参数,返回 + // false。 +_euclideDeletePrivated( + void *privated // 待释放的专属函数 +); + +// Host 全局变量:_euclideTemplateVendor(欧式模板生成器) +// 归纳定义欧式模板生成所需要的函数。 +static TFTemplateVendor _euclideTemplateVendor = { + _euclideIsLegalParam, + _euclideGetHashIndex, + _euclideCreateTemplate, + _euclideIsEqualSize, + _euclideCopyPrivated, + _euclideDeletePrivated +}; + +// Host 函数:_euclideIsLegalParam(欧式模板参数判断函数) +static __host__ bool _euclideIsLegalParam(dim3 size, void *privated) +{ + // 由于高斯模板使用了 float 型的专属参数,因此需要保证 privated 不为 NULL。 + if (privated == NULL) + return false; + // 由于只是用了一个维度的尺寸变量,这里要求 z 维度必须为 1。 + else if (size.z != 1) + return false; + // 这里了要求尺寸必须大于等于 1 且 x 和 y 分量必须相等或者 y 分量等于 1。 + else if (size.x < 1 || (size.y != size.x && size.y != 1)) + return false; + // 如果附属数据的值等于 0,则判断为非法。 + else if (fabs(*((float *)privated)) < 1.0e-8f) + return false; + else + return true; +} + +// Host 函数:_euclideGetHashIndex(欧式模板 Hash 算法函数) +static __host__ int _euclideGetHashIndex(dim3 size, void *privated) +{ + // 如果高斯模板的专属参数为 NULL,则返回 -1 报错。 + if (privated == NULL) + return -1; + + // 这里将尺寸参数的 x 分量和专属参数进行异或拼合。 + return (size.x ^ (int)(fabs(*(float *)privated) * 10.0f)) % TF_VOL_SHAPE; +} + +// Host 函数:_euclideCreateTemplate(欧式模板生成函数) +static __host__ Template *_euclideCreateTemplate(dim3 size, void *privated) +{ + // 如果专属参数为 NULL 则报错返回。 + if (privated == NULL) + return NULL; + + // 如果尺寸小于 1,则无法完成有效的计算,报错返回。 + if (size.x < 1) + return NULL; + + // 取出专属参数的值,该值在为 float 型,在生成欧式模板的过程中,称为 sigma + float sigma = *((float *)privated); + // 如果 sigma 值等于 0 则无法完成后续计算,报错退出。 + if (fabs(sigma) < 1.0e-8f) + return NULL; + // 计算出 2 * sigma ^ 2,方便后续计算使用。 + float sigma22 = 2 * sigma * sigma; + + // 申请新的模板 + Template *restpl; + int errcode; + errcode = TemplateBasicOp::newTemplate(&restpl); + if (errcode != NO_ERROR) + return NULL; + + // 为新模板申请内存空间。 + errcode = TemplateBasicOp::makeAtHost(restpl, size.x); + if (errcode != NO_ERROR) { + TemplateBasicOp::deleteTemplate(restpl); + return NULL; + } + + // 取出存放坐标点和附属数据的内存空间指针,这样可以通过游标指针方便对数据空 + // 间进行赋值操作。 + int *tpldata = restpl->tplData; + float *attdata = ATTACHED_DATA(restpl); + + // 依次添加模板数据。 + for (int i = 0; i < size.x; i++) { + // 计算当前点对应的附属数据值。 + float curatt = exp(-(i * i) / sigma22); + + // 将坐标点添加到模板中,同时添加附属数据。 + *(tpldata++) = i; + *(tpldata++) = 0; + *(attdata++) = curatt; + } + + // 计算完毕,返回新的模板。 + return restpl; +} + +// Host 函数:_euclideEqualSize(欧式模板的尺寸相等判断函数) +static __host__ bool _euclideIsEqualSize(dim3 size1, void *privated1, + dim3 size2, void *privated2) +{ + // 如果专属参数为 NULL,则恒返回 false 报错。 + if (privated1 == NULL || privated2 == NULL) + return false; + + // 如果按照 float 类型判断,两个专属参数不相等(即绝对值差大于某个小正 + // 数),则判定为不相等。 + if (fabs(*((float *)privated1) - *((float *)privated2)) >= 1.0e-6f) + return false; + + // 如果尺寸参数不相等,则会判定为两个参数不相等 + if (size1.x != size2.x/* || size1.y != size2.y*/) + return false; + + return true; +} + +// Host 函数:_euclideCopyPrivated(欧式模板的专属参数拷贝函数) +static __host__ void *_euclideCopyPrivated(void *privated) +{ + // 如果专属参数为 NULL,则直接返回 NULL。 + if (privated == NULL) + return NULL; + + // 申请一个 float 型的空间 + float *resptr = new float; + if (resptr == NULL) + return NULL; + + // 然后将数据拷贝如这个空间内。 + *resptr = *((float *)privated); + + // 返回这个已拷贝了数据的新申请的空间地址。 + return resptr; +} + +// Host 函数:_euclideDeletePrivated(欧式模板的专属参数释放函数) +static __host__ bool _euclideDeletePrivated(void *privated) +{ + // 如果专属参数为 NULL,则直接返回 NULL。 + if (privated == NULL) + return false; + + // 释放掉 privated 的空间。 + delete (float *)privated; + return true; +} + + +// 综合归纳定义各个模板: + +// Host 全局变量:_templateVendorArray(模板生成器集合) +// 通过这个数组管理系统中所有的模板生成器,通过索引可以灵活的访问到系统中所有的 +// 模板生成器的函数。 +static TFTemplateVendor *_templateVendorArray[] = { + &_boxTemplateVendor, // 矩形模板生成器 + &_circleTemplateVendor, // 圆形模板生成器 + &_arcTemplateVendor, // 环形模板生成器 + &_gaussTemplateVendor, // 高斯模板生成器 + &_euclideTemplateVendor, // 欧式模板生成器 + &_defTemplateVendor // 标准模板生成器(用于演示的代码示例) +}; + + +// TemplateFactory CLASS 的实现方法 + +// 静态成员变量:tplPool(模板池) +// 初始化 tplPool 的值为 NULL。 +Template * +TemplateFactory::tplPool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE] = { NULL }; + +// 静态成员变量:sizePool(模板池尺寸参数) +// 初始化 sizePool 的值为 (0, 0, 0)。 +dim3 +TemplateFactory::sizePool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE] = { + dim3(0, 0, 0) +}; + +// 静态成员变量:privatePool(模板池专属参数) +// 初始化 privatePool 的值为 NULL。 +void * +TemplateFactory::privatePool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE] = { + NULL +}; + +// 静态成员变量:countPool(模板池使用计数器) +// 初始化 countPool 的值为 0。 +int +TemplateFactory::countPool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE] = { 0 }; + +// Host 静态方法:boostTemplateEntry(提升指定的模板条目) +__host__ bool TemplateFactory::boostTemplateEntry(int shape, int idx) +{ + // 检查形状参数和下标参数的合法性(由于这一内部函数在掉用前可以保证正确性, + // 因此我们注释了下面的代码) + //if (shape < 0 || shape >= TF_CNT_SHAPE || + // idx < 0 || idx >= TF_VOL_SHAPE * TF_SET_SHAPE) + // return false; + + // 如果给定的下标可以被组块尺寸整除则说明该下标为某个组块的开头一个下标,这 + // 个下标下的条目已经不能够在继续提升了,因为再提升就会进入其他的组块,从而 + // 造成后续处理的错误。 + if (idx % TF_SET_SHAPE == 0) + return false; + + // 进行两个模板条目的交换。 + // 首先将 idx 位置的条目保存到一个临时内存区域内。 + Template *tmptpl = tplPool[shape][idx]; + dim3 tmpsize = sizePool[shape][idx]; + void *tmppriv = privatePool[shape][idx]; + int tmpcount = countPool[shape][idx]; + // 再将 idx - 1 位置的标目保存到 idx 位置处。 + tplPool[shape][idx] = tplPool[shape][idx - 1]; + sizePool[shape][idx] = sizePool[shape][idx - 1]; + privatePool[shape][idx] = privatePool[shape][idx - 1]; + countPool[shape][idx] = countPool[shape][idx - 1]; + // 最后将存在来临时内存区域内的原 idx 位置的数据放入 idx - 1 处。 + tplPool[shape][idx - 1] = tmptpl; + sizePool[shape][idx - 1] = tmpsize; + privatePool[shape][idx - 1] = tmppriv; + countPool[shape][idx - 1] = tmpcount; + + // 处理完毕,退出返回 + return true; +} + +// Host 静态方法:getTemplate(根据参数得到需要的模板) +__host__ int TemplateFactory::getTemplate(Template **tpl, int shape, + dim3 size, void *privated) +{ + // 判断给定的形状是否合法 + if (shape < 0 || shape >= TF_CNT_SHAPE) + return INVALID_DATA; + + // 检查用于输出的模板指针是否为空。 + if (tpl == NULL) + return NULL_POINTER; + + // 判断尺寸是否合法 + if (!_templateVendorArray[shape]->isLegalParam(size, privated)) + return INVALID_DATA; + + int hashidx; // Hash 索引值 + int posidx; // 模板池的下标游标值,该值通过 Hash 索引值推算而得。 + int startposidx; // 模板池中存放所要查找模板的起始下标。 + int endposidx; // 模板池中存放所要查找模板的最后一个位置的下一个位置的下 + // 标。 + + // 调用模板生成器中的 Hash 函数生成索引 + hashidx = _templateVendorArray[shape]->getHashIndex(size, privated); + // 如果模板生成器返回的 Hash 值无法使用,则报错退出。 + if (hashidx < 0 || hashidx > TF_VOL_SHAPE) + return INVALID_DATA; + + // 由 Hash 索引定位模板的对应首位置与结束位置 + startposidx = hashidx * TF_SET_SHAPE; + endposidx = startposidx + TF_SET_SHAPE; + + // 循环查找模板池是否有对应模板 + for (posidx = startposidx; posidx < endposidx; posidx++) { + + // 如果发现当前位置的模板为 NULL,所有所要查找的模板尚未出现在模板池 + // 中,需要创建创建这个模板。 + if (tplPool[shape][posidx] == NULL) { + break; + + } else if (_templateVendorArray[shape]->isEqualSize( + sizePool[shape][posidx], privatePool[shape][posidx], + size, privated)) { + + // 在模板池中找到了需要的模板 + *tpl = tplPool[shape][posidx]; + countPool[shape][posidx]++; + +#ifdef TF_ENABLE_KICK + // 如果当前被选中的模板不处于模板池的首位置,则跟它前面的模板进行交 + // 换位置,使得这个模板不容易被替换出去。该段代码只在启动了替换机制 + // 后才会被执行。 + boostTemplateEntry(shape, posidx); +#endif + + // 找到模板后直接退出 + return NO_ERROR; + } + } + + // 如果在模板池中找到了对应的模板,则带么已经通过上面的 else if 分支返回到 + // 上层函数,因此下面的代码将处理没有找到对应模板的情况。 + + // 首先,创建对应的模板。这里先创建模板是因为如果一旦创建失败,就没有必要再 + // 去尝试这将这个模板放入到模板池中。 + *tpl = _templateVendorArray[shape]->createTemplate(size, privated); + // 检查得到的方形模板是否为空 + if (*tpl == NULL) + return OUT_OF_MEM; + + // 然后,如果模板池已经满了,那么必须从模板池中踢出一个模板,然后在放入新的 + // 模板。(当然,如果我们吧替换功能关闭了,那么该部分代码直接以没有找到替换 + // 位置的姿态离开该段代码) + if (posidx >= endposidx) { +#ifdef TF_ENABLE_KICK + // 一些要使用到的临时变量 + bool replace = false; // 标志位,表示是否找到和替换位置 + posidx = endposidx - 1; // 因为落在后面的模板是更为不常用的模板,因此 + // 我们设定起始下标为最末一个下标。 + int stopposidx = // 查找停止下标。为了防止不合理 + (startposidx + endposidx) / 2; // 的替换,我们令在前面的模板不 + // 会被替换掉。 + // 从后向前查找,找到一个暂时没有被使用的模板,然后替换掉他。这里使用 + // do-while 循环的形式,就是保证至少查找了最后一个条目。用来防止,如果 + // 组块设置得太小的时候会导致不进行查找直接认为没有找到合适的条目的情 + // 况。 + do { + // 如果当前的条目下的模板没有被使用。这里之所以要寻找没有使用的模 + // 板,是因为一旦将正在使用的模板踢出模板池,我们将无法控制这个模板 + // 何时应该释放:如果当场释放,那么虽然不会带来内存泄漏的问题,但是 + // 正在使用模板的程序可能会因为读不到数据而崩溃;如果过后释放,则仍 + // 就可能会导致其他使用者的崩溃。因此,这里我们目前只是寻找目前没有 + // 在使用中的模板,这样我们就能够安全的释放它,而不用担心内存泄漏或 + // 者使用者崩溃的问题。 + if (countPool[shape][posidx] == 0) { + // 设定标志位,并且删除该模板,腾出内存空间。 + replace = true; + + // 删除模板,防止内存泄漏。由于后续步骤马上就要改写模板池中的内 + // 容,因此这里注视掉了将模板池重置为 NULL 的代码,以减少一些不 + // 必要的代码执行。下面注释掉的专属变量重置为 NULL 的代码被注释 + // 掉的原因亦然。 + TemplateBasicOp::deleteTemplate(tplPool[shape][posidx]); + //tplPool[shape][posidx] = NULL; + + // 这里还需要释放掉模板池中的专属参数,因为这个空间都是在将模板 + // 加入到模板池中的时候才申请的,因此需要在内部将其释放掉。 + _templateVendorArray[shape]->deletePrivated( + privatePool[shape][posidx]); + //privatePool[shape][posidx] = NULL; + + // 找到合适的位置后直接从循环中跳出。 + break; + } + } while (posidx-- >= stopposidx); + + // 如果没有找到提供换位置,则将下标赋值为一个哑值。 + if (!replace) + posidx = -1; +#else + // 对于关闭了替换功能的代码,这里直接将下标置为一个哑值。 + posidx = -1; +#endif + } + + // 给模板池和模板对应参数赋值,这里首先要检查 posidx 是不是哑值,因为即便是 + // 在模板池内没有找到合适的位置来放置新的模板,我们也会创造一个模板来给用户 + // 使用,这样的模板会在调用 putTemplate 时被销毁(该函数会发现该模板没有在 + // 模板池中存在)。 + if (posidx >= startposidx && posidx < endposidx) { + tplPool[shape][posidx] = *tpl; + sizePool[shape][posidx] = size; + // 这里我们调用 copyPrivated 函数,因为用户输入的 privated 指针指向的数 + // 据是不稳定的,它可能会被改写,会被释放,这样我们的系统也会变得不稳 + // 定。为了防止这种不稳定性,我们将它拷贝一份出来,供模板池内部专用。 + privatePool[shape][posidx] = + _templateVendorArray[shape]->copyPrivated(privated); + countPool[shape][posidx] = 1; + } + + // 处理完毕,退出 + return NO_ERROR; +} + +// host 静态方法:putTemplate(放回通过 getTemplate 得到的模板) +__host__ int TemplateFactory::putTemplate(Template *tpl) +{ + // 如果输入的模板是 NULL,则直接返回。 + if (tpl == NULL) + return NULL_POINTER; + + // 查找模板池中有没有对应的模板,如果模板池中存在对应的模板,则将模板的使用 + // 计数器进行调整。 + for (int shape = 0; shape < TF_CNT_SHAPE; shape++) { + for (int i = 0; i < TF_VOL_SHAPE * TF_SET_SHAPE; i++) { + // 模板池中找到了对应模板,让模板计数器减 1。 + if (tplPool[shape][i] == tpl) { + if (countPool[shape][i] > 0) + countPool[shape][i]--; + + // 处理完毕,退出 + return NO_ERROR; + } + } + } + + // 模板此没有找到对应的模板,则直接释放该模板 + TemplateBasicOp::deleteTemplate(tpl); + + // 处理完毕,退出 + return NO_ERROR; +} diff --git a/okano_3_0/TemplateFactory.h b/okano_3_0/TemplateFactory.h new file mode 100644 index 0000000..270f123 --- /dev/null +++ b/okano_3_0/TemplateFactory.h @@ -0,0 +1,151 @@ +// TemplateFactory.h +// 创建者:于玉龙 +// +// 模板工厂(TemplateFactory) +// 功能说明:创建和得到各种形状的模板,暂时先实现了矩形模板(包括长方形和 +// 正方形)、 圆形模板、环形模板、高斯模板、欧式模板 +// +// 修订历史: +// 2012年11月17日(欧阳翔,于玉龙) +// 初始版本。 +// 2012年11月22日(欧阳翔) +// 在设计上做了修改,使用了函数指针数组,减少了重复代码。 +// 2012年11月23日(欧阳翔) +// 模板替换策略上做了修改,修正了一些格式错误。 +// 2012年11月24日(欧阳翔,于玉龙) +// 在模板特殊参数比较方法上做了修正 +// 2012年11月25日(欧阳翔) +// 修改了一些格式错误 +// 2012年11月28日(于玉龙) +// 修正了圆形模版生成算法,计算圆形模版更加准确。 +// 修正了代码中的部分格式错误。 +// 2012年12月05日(于玉龙) +// 聚合生成一类模板所需要的各种函数,组成一个有函数指针构成的结构体,方便不 +// 同类型模板的管理。 +// 2012年12月16日(于玉龙) +// 全面翻新内部代码,使用结构体中的函数指针来管理各个模板生成算法,修正了原 +// 代码中大量潜在的 Bug。 +// 修改“拱形模板”的中文名称为“环形模板”。 +// 2012年12月18日(于玉龙) +// 重新实现了环形模板,其计算量更小,求解更加准确合理。此外,修改了参数的含 +// 义,原尺寸参数为半径,现在指直径。 +// 2012年12月23日(于玉龙) +// 重新实现了高斯模板。 +// 修正了环形模版生成中一处潜在的计算错误。 +// 2012年12月24日(于玉龙) +// 修正了高斯模板计算过程中的一个错误。 +// 增加了欧式模板的实现。 + +#ifndef __TEMPLATEFACTORY_H__ +#define __TEMPLATEFACTORY_H__ + +#include "ErrorCode.h" +#include "Template.h" + +// 宏:TF_CNT_SHAPE(系统所支持的模版数量) +// 定义了模版工厂所支持的模版形状的数量。 +#define TF_CNT_SHAPE 5 + +// 宏:TF_VOL_SHAPE 和 TF_SET_SHAPE(模版工厂容量) +// 定义了模版工厂中的资源池所能容纳的模版数量。由于存储于资源池的模版采用哈希表 +// 组映射的方式存储,因此这里采用两个宏,分别来记录组的数量和每组内的模版数量。 +#define TF_VOL_SHAPE 128 +#define TF_SET_SHAPE 4 + +// 宏:模版工厂所支持的形状 +// 定义了所有模版工厂所支持的形状。目前的版本中我们包含了五种形状,分别为矩形、 +// 圆形、环形、高斯模版、欧式模板。 +#define TF_SHAPE_BOX 0 +#define TF_SHAPE_CIRCLE 1 +#define TF_SHAPE_ARC 2 +#define TF_SHAPE_GAUSS 3 +#define TF_SHAPE_EUCLIDE 4 + + +// 类:TemplateFactory(模板工厂) +// 继承自:无 +// 该类包含了对于产生不同形状的模板操作,包括矩形模板、圆形模板、环形模板、高 +// 斯模板等的创建,以及在模板池中查找以上模板操作。超过模板池容量后从对应模板 +// 适当位置开始替换需要新建的模板。 +class TemplateFactory { + +protected: + + // 静态成员变量:tplPool(模板池) + // 存储不同类型的模板,初始化成 NULL。做成二维数组的形式,Template 以 Hash + // 的形式存入资源池,可以方便写入,也方便查找。 + static Template *tplPool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE]; + + // 静态成员变量:sizePool(模板池尺寸参数) + // 这是模板对应的参数,查找时需要对比的。 + // 尺寸池,静态全局变量,对应于模板池,dim3类型,初始化为 (0, 0, 0) + static dim3 sizePool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE]; + + // 静态成员变量:privatePool(模板池专属参数) + // 模板对应的专属参数,查找时需要对比的。 + // 模板其他属性池,静态全局变量,对应于模板池,void * 类型,初始化为 NULL。 + static void *privatePool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE]; + + // 静态成员变量:countPool(模板池使用计数器) + // 模板计数,统计每一个模板的 getTemplate 的次数 + // 初始化为 0,当对应大小的池满,执行替换策略,若对应模板的 count 为 0, + // 则表示外部已经放回了得到的模板,可以执行替换,否则不能执行 + static int countPool[TF_CNT_SHAPE][TF_VOL_SHAPE * TF_SET_SHAPE]; + + // Host 静态方法:boostTemplateEntry(提升指定的模板条目) + // 将指定的模板池中模板的条目与其前面的条目进行交换,这可以使该模板条目处于 + // 更加有优势的地位,避免其被替换操作释放掉。 + static __host__ bool // 返回值:是否交还成功,如果模板处于某个组块的第一个 + // 模板的位置,则该模板就不会在进行提升操作,会返回 + // false + boostTemplateEntry( + int shape, // 形状 + int idx // 模板池中的下标 + ); + +public: + + // Host 静态方法:getTemplate(得到需要的模板) + // privated 是形状的特有参数,通常来说不使用。 + // 这是一个显示的内联函数,根据需要得到对应的模板,需要调用下面重载的 + static inline __host__ int // 返回值:函数是否正确执行,若函数正确执行 + // 返回 NO_ERROR。 + getTemplate( + Template **tpl, // 模板指针 + int shape, // 形状参数,表示想得到的形状 + size_t size, // 需要模板的尺寸大小 + void *privated = NULL // 模板区分形状的特有参数 + ) { + // 根据用户尺寸转化为 dim3 类型的尺寸大小 + dim3 tmpsize(size, size); + // 调用得到模板函数 + return getTemplate(tpl, shape, tmpsize, privated); + } + + // Host 静态方法:getTemplate(得到需要的模板) + // privated 是形状的特有参数,通常来说不使用。 + // 这个函数首先判断所需要的模板是否已经在 tplPool 中,若在,则直接返回; + // 若不在,调用 create 生成,返回这个刚生成的模板,并且将这个模板存放在 + // tplPool 对应的位置中。并且 countPool 对应位置需要加 1。 + static __host__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + getTemplate( + Template **tpl, // 模板指针 + int shape, // 形状参数,表示想得到的形状 + dim3 size, // 需要模板的尺寸大小 + void *privated = NULL // 模板区分形状的特有参数 + ); + + // Host 静态方法:putTemplate(放回通过 getTemplate 得到的模板) + // 主要是为了平衡一般性思维,由于前面有得到模板,这个逻辑上觉得需要放回 + // 如果查找模板不在 tplPool 池中,则直接释放模板空间。否则对应位置的 + // countPool中的值减 1。 + static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + putTemplate( + Template *tpl // 放回模板指针 + ); +}; + +#endif + diff --git a/okano_3_0/Thinning.cu b/okano_3_0/Thinning.cu new file mode 100644 index 0000000..f3011b5 --- /dev/null +++ b/okano_3_0/Thinning.cu @@ -0,0 +1,601 @@ +// Thinning.cu +// 实现二值图像的细化算法 + +#include "Thinning.h" +#include +using namespace std; + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_PATTERN_SIZE +// 定义了 PATTERN 表的默认大小。 +#define DEF_PATTERN_SIZE 512 + +// 宏:DEF_PATTERN_LEN +// 定义了 PATTERN 表的个数。 +#define DEF_PATTERN_LEN 4 + +// 宏:CST_IMG_WIDTH 和 CST_IMG_HEIGHT +// 定义了当输入参数为坐标集时,坐标集转化图像的大小。 +#define CST_IMG_WIDTH 1024 +#define CST_IMG_HEIGHT 1024 + +// static 变量:lutthin(PATTERN 表) +// 此一个 unsigned char 型数组的大小为 DEF_PATTERN_SIZE * 4 , 为 4 个 +// PATTERN 表,是 PATTERN 表细化算法里的四种判数断组。在这里将四个表合 +// 并为一个,便于将数据拷贝到 device 端。 +unsigned char lutthin[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, + 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, + 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, + 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, + 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, + 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, + 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, + 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, + 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, + 0, 0 + }; + +// Kernel 函数:_thinMatSubFirKer(实现 PATTERN 表删除算法) +// 根据算法、PATTERN 表 1、PATTERN 表 2 和 PATTERN 表 3 对图像进行第一步细化 +// 处理,对输出图像(已将输入图像完全拷贝到输出图像中)进行遍历,如果目标点的 +// 8 邻域满足 PATTERN 表 1、PATTERN 表 2 和 PATTERN 表 3 条件,则将 highpixel +// 置为 lowpixel 表示删除,否则不删除。并将第一步细化的结果存储在暂存图像 +// tempimg 中。 +static __global__ void +_thinMatSubFirKer( + ImageCuda outimg, // 输出图像 + ImageCuda tempimg, // 暂存图像(暂时存储 PATTERN 表删除算法 + // 第一步操作的结果) + unsigned char *devlutthin1, // PATTERN 表 1 + unsigned char *devlutthin2, // PATTERN 表 2 + unsigned char *devlutthin3, // PATTERN 表 3 + unsigned char highpixel, // 高像素 + unsigned char lowpixel // 低像素 +); + +// Kernel 函数:_thinMatSubSecKer(实现 PATTERN 表删除算法) +// 根据算法、PATTERN 表 1、PATTERN 表 2 和 PATTERN 表 4 对暂存图像进行第二次 +// 细化处理,遍历暂存图像,如果目标点的 8 邻域满足 PATTERN 表 1、PATTERN 表 2 +// 和 PATTERN 表 4 条件,将 highpixel 置为 lowpixel 表示删除,否则不删除。并 +// 将第二步细化的结果重新存储到输出图像 outimg 中。同时将细化的点数存储在 +// devchangecount 中。在核函数外对 devchangecount 进行判断,如果其值 为 0 时, +// 即图像中没有点可以删除时,停止迭代。 +static __global__ void +_thinMatSubSecKer( + ImageCuda tempimg, // 暂存图像(暂时存储 PATTERN 表删除算法 + // 第一步操作的结果) + ImageCuda outimg, // 输出图像 + unsigned char *devlutthin1, // PATTERN 表 1 + unsigned char *devlutthin2, // PATTERN 表 2 + unsigned char *devlutthin4, // PATTERN 表 4 + int *devchangecount, // 经过一次细化后细化点的个数(用于判断 + // 是否继续迭代) + unsigned char lowpixel // 低像素 +); + +// Kernel 函数:_thinMatSubFirKer(实现 PATTERN 表删除算法的第一步操作) +static __global__ void _thinMatSubFirKer(ImageCuda outimg, + ImageCuda tempimg, + unsigned char *devlutthin1, + unsigned char *devlutthin2, + unsigned char *devlutthin3, + unsigned char highpixel, + unsigned char lowpixel) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量 (其中,c 表示 + // column,r 表示 row )。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (dstc >= outimg.imgMeta.width - 1 || + dstr >= outimg.imgMeta.height - 1 || dstc < 1 || dstr < 1) + return; + + // 定义目标点位置的指针。 + unsigned char *outptr; + + // 获取当前像素点在图像中的相对位置。 + int curpos = dstr * outimg.pitchBytes + dstc; + + // 获取当前像素点在图像中的绝对位置。 + outptr = outimg.imgMeta.imgData + curpos ; + + // 如果目标像素点的像素值为低像素, 则不进行细化处理。 + if (*outptr != lowpixel) { + // 根据目标像素点 8 邻域的特性获取其在 PATTERN 表内的索引。 + int index = 0; + + // 由于图像是线性存储的,所以在这里先获得 8 邻域里三列的列索引值, + // 防止下面细化处理时重复计算。 + int posColumn1 = (dstr - 1) * outimg.pitchBytes; + int posColumn2 = posColumn1 + outimg.pitchBytes; + int posColumn3 = posColumn2 + outimg.pitchBytes; + + // 根据算法描述,对 8 邻域内的像素点赋予权重,对 8 邻域内的像素点进行 + // 遍历,将邻域内像素值为 highpixel 的像素点的权重值相加,即获得 8 邻 + // 域内像素特性在 PATTERN 表内对应的索引值。以此获得目标像素在 PATTERN + // 表中对应的值。 + if (outimg.imgMeta.imgData[dstc - 1 + posColumn1] != lowpixel) + index += 1; + if (outimg.imgMeta.imgData[dstc - 1 + posColumn2] != lowpixel) + index += 2; + if (outimg.imgMeta.imgData[dstc - 1 + posColumn3] != lowpixel) + index += 4; + if (outimg.imgMeta.imgData[dstc + posColumn1] != lowpixel) + index += 8; + if (outimg.imgMeta.imgData[dstc + posColumn2] != lowpixel) + index += 16; + if (outimg.imgMeta.imgData[dstc + posColumn3] != lowpixel) + index += 32; + if (outimg.imgMeta.imgData[dstc + 1 + posColumn1] != lowpixel) + index += 64; + if (outimg.imgMeta.imgData[dstc + 1 + posColumn2] != lowpixel) + index += 128; + if (outimg.imgMeta.imgData[dstc + 1 + posColumn3] != lowpixel) + index += 256; + + // 获得索引值在 PATTERN 表 1 、2 、3 内对应的值 + unsigned char replacedPix1 = devlutthin1[index]; + unsigned char replacedPix2 = devlutthin2[index]; + unsigned char replacedPix3 = devlutthin3[index]; + + // 根据获取的值得出初步细化结果,将结果中存储到暂存图像中。 + if (replacedPix1 == 1 && replacedPix2 == 1 && replacedPix3 == 1) + tempimg.imgMeta.imgData[curpos] = lowpixel; + else + tempimg.imgMeta.imgData[curpos] = highpixel; + } else { + // 如果目标点的像素点为 lowpixel ,则直接将目标点的像素值赋给暂存图像。 + tempimg.imgMeta.imgData[curpos] = lowpixel; + } +} + +// Kernel 函数:_thinMatSubSecKer(实现 PATTERN 表删除算法的第二步操作) +static __global__ void _thinMatSubSecKer(ImageCuda tempimg, + ImageCuda outimg, + unsigned char *devlutthin1, + unsigned char *devlutthin2, + unsigned char *devlutthin4, + int *devchangecount, + unsigned char lowpixel) +{ + // dstc 和 dstr 分别表示线程处理的像素点的坐标的 x 和 y 分量(其中, + // c 表示 column, r 表示 row)。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = blockIdx.y * blockDim.y + threadIdx.y; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源, + // 另一方面防止由于段错误导致程序崩溃。 + if (dstc >= tempimg.imgMeta.width - 1 || + dstr >= tempimg.imgMeta.height - 1 || dstc < 1 || dstr < 1) + return; + + // 定义目标点在暂存图像中位置的指针。 + unsigned char *temptr; + + // 获取当前像素点在暂存图像中的相对位置。 + int curpos = dstr * outimg.pitchBytes + dstc; + + // 获取当前像素点在图像中的绝对位置。 + temptr = tempimg.imgMeta.imgData + curpos; + + // 判断目标像素点是否删除,初始化为 false 。 + bool niv = false; + + // 如果暂存图像内目标像素点的像素值为 lowpixel , 则不进行第二步细化处理 + if (*temptr != lowpixel) { + // 根据暂存图像内目标像素点 8 邻域的特性获取其在 PATTERN 表内的索引。 + int index = 0; + + // 由于图像是线性存储的,所以在这里先获得 8 邻域里三列的列索引值, + // 防止下面细化处理时重复计算。 + int posColumn1 = (dstr - 1) * tempimg.pitchBytes; + int posColumn2 = posColumn1 + tempimg.pitchBytes; + int posColumn3 = posColumn2 + tempimg.pitchBytes; + + // 根据算法描述,对 8 邻域内的像素点赋予权重,对 8 邻域内的像素点进行 + // 遍历,将邻域内像素值为 highpixel 的像素点的权重值相加,即获得 8 + // 邻域内像素特性在 PATTERN 表内对应的索引值。以此获得目标像素在 + // PATTERN 表中对应的值。 + if (tempimg.imgMeta.imgData[dstc - 1 + posColumn1] != lowpixel) + index += 1; + if (tempimg.imgMeta.imgData[dstc - 1 + posColumn2] != lowpixel) + index += 2; + if (tempimg.imgMeta.imgData[dstc - 1 + posColumn3] != lowpixel) + index += 4; + if (tempimg.imgMeta.imgData[dstc + posColumn1] != lowpixel) + index += 8; + if (tempimg.imgMeta.imgData[dstc + posColumn2] != lowpixel) + index += 16; + if (tempimg.imgMeta.imgData[dstc + posColumn3] != lowpixel) + index += 32; + if (tempimg.imgMeta.imgData[dstc + 1 + posColumn1] != lowpixel) + index += 64; + if (tempimg.imgMeta.imgData[dstc + 1 + posColumn2] != lowpixel) + index += 128; + if (tempimg.imgMeta.imgData[dstc + 1 + posColumn3] != lowpixel) + index += 256; + + // 获得索引值在 PATTERN 表 1、 2 、4 内对应的值 + unsigned char replacedPix1 = devlutthin1[index]; + unsigned char replacedPix2 = devlutthin2[index]; + unsigned char replacedPix4 = devlutthin4[index]; + + // 根据从 PATTERN 表获取的值判断目标点是否删除。 + niv = !(replacedPix1 == 1 && replacedPix2 == 1 && replacedPix4 == 1); + } + + // 经过细化处理后,如果输出图像对应的像素点的像素值为 highpixel 而 niv 的 + // 值为 false,则将目标像素点置为 lowpixel。 + if (niv != (outimg.imgMeta.imgData[curpos] != lowpixel)) { + // 删除目标像素点。 + outimg.imgMeta.imgData[curpos] = lowpixel; + + // 记录删除点数的 devchangecount 值加 1 。 + atomicAdd(devchangecount, 1); + } +} + +// 宏:FAIL_THIN_IMAGE_FREE +// 如果出错,就释放之前申请的内存。 +#define FAIL_THIN_IMAGE_FREE do { \ + if (devlutthin != NULL) \ + cudaFree(devlutthin); \ + if (tempimg != NULL) \ + ImageBasicOp::deleteImage(tempimg); \ + if (devchangecount != NULL) \ + cudaFree(devchangecount); \ + } while (0) + +// 成员方法:thinMatlabLike(细化边界 - PATTERN 表法) +__host__ int Thinning::thinMatlabLike(Image *inimg, Image *outimg) +{ + // 局部变量,错误码。 + int errcode; + cudaError_t cudaerrcode; + + // 检查输入图像,输出图像是否为空。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 声明所有中间变量并初始化为空。 + unsigned char *devlutthin = NULL; + Image *tempimg = NULL; + int *devchangecount = NULL; + + // 在 Device 上为 4 个 PATTERN 表 分配空间。并且因为 PATTERN 表在所给的代码 + // 中已给出,且大小都为 DEF_PATTERN_SIZE .在这里直接一次申请所有空间 + //(DEF_PATTERN_SIZE * 4),然后通过偏移索引各个 PATTERN 表。 + unsigned char *devlutthin1, *devlutthin2, *devlutthin3, *devlutthin4; + cudaerrcode = cudaMalloc((void **)&devlutthin, + DEF_PATTERN_SIZE * DEF_PATTERN_LEN * + sizeof (unsigned char)); + if (cudaerrcode != cudaSuccess) + return CUDA_ERROR; + + // 通过偏移读取 Device 端内存空间。每次偏移一个 PATTERN 的长度,即 + // DEF_PATTERN_SIZE。 + devlutthin1 = devlutthin; + devlutthin2 = devlutthin1 + DEF_PATTERN_SIZE; + devlutthin3 = devlutthin2 + DEF_PATTERN_SIZE; + devlutthin4 = devlutthin3 + DEF_PATTERN_SIZE; + + // 将位于 host 的 PATTERN 表数据拷贝到 device 端。 + cudaerrcode = cudaMemcpy(devlutthin, lutthin, DEF_PATTERN_SIZE * + DEF_PATTERN_LEN * sizeof (unsigned char), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + FAIL_THIN_IMAGE_FREE; + return CUDA_ERROR; + } + + // 记录细化点数的变量,位于 host 端。 + int changeCount; + + // 记录细化点数的变量,位于 device 端。并为其申请空间。 + cudaerrcode = cudaMalloc((void **)&devchangecount, sizeof (int)); + if (cudaerrcode != cudaSuccess) { + FAIL_THIN_IMAGE_FREE; + return CUDA_ERROR; + } + + // 生成暂存图像。 + errcode = ImageBasicOp::newImage(&tempimg); + if (errcode != NO_ERROR) + return errcode; + errcode = ImageBasicOp::makeAtCurrentDevice(tempimg, inimg->width, + inimg->height); + if (errcode != NO_ERROR) { + FAIL_THIN_IMAGE_FREE; + return errcode; + } + + // 将输入图像 inimg 完全拷贝到输出图像 outimg ,并将 outimg 拷贝到 + // device 端。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg, outimg); + if (errcode != NO_ERROR) { + FAIL_THIN_IMAGE_FREE; + return errcode; + } + + // 提取输出图像 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) { + FAIL_THIN_IMAGE_FREE; + return errcode; + } + + // 提取暂存图像 + ImageCuda tempsubimgCud; + errcode = ImageBasicOp::roiSubImage(tempimg, &tempsubimgCud); + if (errcode != NO_ERROR) { + FAIL_THIN_IMAGE_FREE; + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 gridsize, blocksize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y - 1) / blocksize.y; + + // 赋值为 1,以便开始第一次迭代。 + changeCount = 1; + + // 开始迭代,当不可再被细化,即记录细化点数的变量 changeCount 的值为 0 时, + // 停止迭代。 + while (changeCount > 0) { + // 将 host 端的变量赋值为 0 ,并将值拷贝到 device 端的 devchangecount。 + changeCount = 0; + cudaerrcode = cudaMemcpy(devchangecount, &changeCount, sizeof (int), + cudaMemcpyHostToDevice); + if (cudaerrcode != cudaSuccess) { + FAIL_THIN_IMAGE_FREE; + return CUDA_ERROR; + } + + // 调用核函数,开始第一步细化操作。 + _thinMatSubFirKer<<>>(outsubimgCud, tempsubimgCud, + devlutthin1, devlutthin2, + devlutthin3, highPixel, + lowPixel); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错,结束迭代函数,释放申请的变量空间。 + FAIL_THIN_IMAGE_FREE; + return CUDA_ERROR; + } + + // 调用核函数,开始第二步细化操作。 + _thinMatSubSecKer<<>>(tempsubimgCud, outsubimgCud, + devlutthin1, devlutthin2, + devlutthin4, devchangecount, + lowPixel); + if (cudaGetLastError() != cudaSuccess) { + // 核函数出错,结束迭代函数,释放申请的变量空间 。 + FAIL_THIN_IMAGE_FREE; + return CUDA_ERROR; + } + + // 将位于 device 端的 devchangecount 拷贝到 host 端上的 changeCount + // 变量,进行迭代判断。 + cudaerrcode = cudaMemcpy(&changeCount, devchangecount, sizeof (int), + cudaMemcpyDeviceToHost); + if (cudaerrcode != cudaSuccess) { + FAIL_THIN_IMAGE_FREE; + return CUDA_ERROR; + } + + } + + // 细化结束后释放申请的变量空间。 + cudaFree(devlutthin); + cudaFree(devchangecount); + ImageBasicOp::deleteImage(tempimg); + + return NO_ERROR; +} + +// 取消前面的宏定义。 +#undef FAIL_THIN_IMAGE_FREE + +// 成员方法:thinMatlabLike(细化边界- PATTERN 表法) +__host__ int Thinning::thinMatlabLike(CoordiSet *incst, + CoordiSet *outcst) +{ + // 局部变量,错误码。 + int errcode; + + // 检查输入坐标集,输出坐标集是否为空。 + if (incst == NULL || outcst == NULL) + return NULL_POINTER; + + // 生成输入图像。 + Image *inimg; + errcode = ImageBasicOp::newImage(&inimg); + if (errcode != NO_ERROR) + return errcode; + + // 图像大小默认为 1024 * 1024。 + errcode = ImageBasicOp::makeAtHost(inimg, CST_IMG_WIDTH, + CST_IMG_HEIGHT); + if (errcode != NO_ERROR) { + // 遇到错误则释放 Device 端空间。 + ImageBasicOp::deleteImage(inimg); + return errcode; + } + + // 生成输出图像。 + Image *outimg; + errcode = ImageBasicOp::newImage(&outimg); + if (errcode != NO_ERROR) { + // 遇到错误则释放 Device 端空间。 + ImageBasicOp::deleteImage(inimg); + return errcode; + } + + // 图像大小默认为 1024 * 1024。 + errcode = ImageBasicOp::makeAtHost(outimg, CST_IMG_WIDTH, + CST_IMG_HEIGHT); + if (errcode != NO_ERROR) { + // 遇到错误则释放 Device 端空间。 + ImageBasicOp::deleteImage(inimg); + ImageBasicOp::deleteImage(outimg); + return errcode; + } + + // 利用转换器将输入坐标集转化为输入图像。 + errcode = this->imgCon.cstConvertToImg(incst, inimg); + if (errcode != NO_ERROR) { + // 遇到错误则释放 Device 端空间。 + ImageBasicOp::deleteImage(inimg); + ImageBasicOp::deleteImage(outimg); + return errcode; + } + + // 细化图像。 + errcode = this->thinMatlabLike(inimg, outimg); + if (errcode != NO_ERROR) { + // 遇到错误则释放 Device 端空间。 + ImageBasicOp::deleteImage(inimg); + ImageBasicOp::deleteImage(outimg); + return errcode; + } + + // 利用转换器将输出图像转化为输出坐标集。 + errcode = this->imgCon.imgConvertToCst(outimg, outcst); + if (errcode != NO_ERROR) { + // 遇到错误则释放 Device 端空间。 + ImageBasicOp::deleteImage(inimg); + ImageBasicOp::deleteImage(outimg); + return errcode; + } + + // 细化结束后释放申请的变量空间。 + ImageBasicOp::deleteImage(inimg); + ImageBasicOp::deleteImage(outimg); + + return NO_ERROR; +} + diff --git a/okano_3_0/Thinning.h b/okano_3_0/Thinning.h new file mode 100644 index 0000000..fc0a280 --- /dev/null +++ b/okano_3_0/Thinning.h @@ -0,0 +1,211 @@ +// Thinning.h +// 创建人:杨伟光 +// +// 细化图像边界(Thinning) +// 功能说明:将灰度图像转化为 2 值图像,其内的 HEIGHT PIXEL 表示 EDGE, +// 实现将 EDGE 细化成 1 PIXEL 宽度的功能。且 HEIGHT PIXEL 和 +// LOW PIXEL 可由用户自己定义。 +// +// 修订历史: +// 2012年12月04日(杨伟光) +// 初始版本。 +// 2012年12月05日(杨伟光) +// 修正了代码规范。 +// 2012年12月10日(杨伟光) +// 增加了 highPixel 和 lowPixel 两个成员变量,方便用户自己定义高像素 +// 和低像素,并完成 PATTERN 表删除算法内对 highPixel 和 lowPixel 的 +// 调用。 +// 2012年12月17日(杨伟光) +// 修正了代码规范。 +// 2012年12月18日(杨伟光) +// 添加 setHighLowPixel 函数。 +// 2012年12月18日(杨伟光) +// 删除 thinImageLike 算法。 +// 2013年01月03日(杨伟光) +// 添加坐标集输入参数形式。 +// 2013年03月31日(杨伟光) +// 参数为坐标集时的图像与坐标集互相转化部分替换为调用 imgConvert 实现。 + +#ifndef __THINNING_H__ +#define __THINNING_H__ + +#include "Image.h" +#include "ErrorCode.h" +#include "CoordiSet.h" +#include "ImgConvert.h" + +// 类:Thinning(细化图像边界算法) +// 继承自:无。 +// 实现了图像的细化算法。通过图像法和 PATTERN 表法对图像进行细化,实现将图像 +// 细化成一个像素宽度的功能。 +class Thinning { + +protected: + + // 成员变量:highPixel(高像素) + // 图像内高像素的像素值,可由用户定义。 + unsigned char highPixel; + + // 成员变量:lowPixel(低像素) + // 图像内低像素的像素值,可由用户定义。 + unsigned char lowPixel; + + // 成员变量:imgCon(图像与坐标集之间的转化器) + // 当参数为坐标集时,实现坐标集与图像的相互转化。 + ImgConvert imgCon; + +public: + + // 构造函数:Thinning + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + Thinning() + { + this->highPixel = 255; // 高像素值默认为 255。 + this->lowPixel = 0; // 低像素值默认为 0。 + + // 将转换器的标记数组清空,并将高像素位置为 true。 + this->imgCon.clearAllConvertFlags(); + this->imgCon.setConvertFlag(this->highPixel); + } + + // 构造函数:Thinning + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + Thinning( + unsigned char highpixel, // 高像素 + unsigned char lowpixel // 低像素 + ) { + this->highPixel = 255; // 高像素值默认为 255。 + this->lowPixel = 0; // 低像素值默认为 0。 + + // 将转换器的标记数组清空,并将高像素位置为 true。 + this->imgCon.clearAllConvertFlags(); + this->imgCon.setConvertFlag(this->highPixel); + + // 根据参数列表中的值设定成员变量的初值。 + this->setHighLowPixel(highPixel, lowPixel); + } + + // 成员函数:getHighPixel(获取高像素的值) + // 获取成员变量 highPixel 的值。 + __host__ __device__ unsigned char // 返回值:返回 hignPixel 的值。 + getHighPixel() const + { + // 返回 highPixel 成员变量的值。 + return highPixel; + } + + // 成员函数:setHighPixel(设置高像素) + // 设置成员变量 highPixel 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setHighPixel( + unsigned char highpixel // 高像素的像素值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highpixel == this->lowPixel) + return INVALID_DATA; + + // 将转换器内的原先高像素位置为 false。此做法可使转换器的只有一个高像素 + // 位。 + this->imgCon.clearConvertFlag(this->highPixel); + + // 将 highPixel 成员变量赋成新值 + this->highPixel = highpixel; + + // 将转换器的高像素与本算法同步。 + this->imgCon.setHighPixel(this->highPixel); + + // 将转换器的标记数组高像素位置为 true。 + this->imgCon.setConvertFlag(this->highPixel); + + return NO_ERROR; + } + + // 成员函数:getLowPixel(获取低像素的值) + // 获取成员变量 lowPixel 的值。 + __host__ __device__ unsigned char // 返回值:返回 lowPixel 的值。 + getLowPixel() const + { + // 返回 lowPixel 成员变量的值。 + return lowPixel; + } + + // 成员函数:setLowPixel(设置低像素) + // 设置成员变量 lowPixel 的值。 + __host__ __device__ int // 返回值:若函数正确执行,返回 NO_ERROR。 + setLowPixel( + unsigned char lowpixel // 低像素的像素值 + ) { + // 如果高像素和低像素相等,则报错。 + if (this->highPixel == lowpixel) + return INVALID_DATA; + + // 将 lowPixel 成员变量赋成新值。 + this->lowPixel = lowpixel; + + // 将转换器的低像素与本算法同步。 + this->imgCon.setLowPixel(this->lowPixel); + + return NO_ERROR; + } + + // 成员函数:setHighLowPixel(设置高低像素) + // 设置成员变量 highPixel 和 lowPixel 的值。 + __host__ __device__ int // 返回值:函数正确执行,返回 NO_ERROR。 + setHighLowPixel( + unsigned char highpixel, // 高像素的像素值 + unsigned char lowpixel // 低像素的像素值 + ) { + // 如果高像素和低像素相等,则报错。 + if (highpixel == lowpixel) + return INVALID_DATA; + + // 将转换器内的原先高像素位置为 false。此做法可使转换器的只有一个高像素 + // 位。 + this->imgCon.clearConvertFlag(this->highPixel); + + // 将 highPixel 成员变量赋成新值。 + this->highPixel = highpixel; + + // 将 lowPixel 成员变量赋成新值。 + this->lowPixel = lowpixel; + + // 将转换器的高像素和低像素与本算法同步。 + this->imgCon.setHighPixel(this->highPixel); + this->imgCon.setLowPixel(this->lowPixel); + + // 将转换器的标记数组高像素位置为 true。 + this->imgCon.setConvertFlag(this->highPixel); + + return NO_ERROR; + } + + // 成员方法:thinMatlabLike(细化边界 - PATTERN 表法) + // 该算法是 Thinning 算法的另一个子算法。通过建立 4 个 PATTERN 表,使用 + // 3 × 3 的模板,模板包含 9 个像素,对模板中像素赋予权重值,通过判断目标 + // 点周围 8 邻域的特性得出该点在 PATTERN 表中的索引,根据索引值在 + // PATTERN 表中对应的值来判断该点是否删除。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + thinMatlabLike( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // 成员方法:thinMatlabLike(细化边界 - PATTERN 表法) + // 该算法是 Thinning 算法的另一个子算法。通过建立 4 个 PATTERN 表,使用 + // 3 × 3 的模板,模板包含 9 个像素,对模板中像素赋予权重值,通过判断目标 + // 点周围 8 邻域的特性得出该点在 PATTERN 表中的索引,根据索引值在 + // PATTERN 表中对应的值来判断该点是否删除。输入参数可为坐标集的形式。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回NO_ERROR。 + thinMatlabLike( + CoordiSet *incst, // 输入图像,坐标集形式 + CoordiSet *outcst // 输出图像,坐标集形式 + ); +}; + +#endif + diff --git a/okano_3_0/Threshold.cu b/okano_3_0/Threshold.cu new file mode 100644 index 0000000..a89c650 --- /dev/null +++ b/okano_3_0/Threshold.cu @@ -0,0 +1,438 @@ +// Threshold.cu +// 实现图像的阈值分割 + +#include "Threshold.h" +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +// 有输出图像但无高低像素值(low,high) +static __global__ void // kernel 函数无返回值 +_thresholdKer( + ImageCuda in, // 输入图像 + ImageCuda out, // 输出图像 + unsigned char minpixel, // 最小像素值 + unsigned char maxpixel // 最大像素值 +); + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +// 无输出图像且无高低像素值(low, high) +static __global__ void // kernel 函数无返回值 +_thresholdKer( + ImageCuda inout, // 输入输出图像 + unsigned char minpixel, // 最小像素值 + unsigned char maxpixel // 最大像素值 +); + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +// 有输出图像且有高低像素值(low, high) +static __global__ void // kernel 函数无返回值 +_thresholdKer( + ImageCuda in, // 输入图像 + ImageCuda out, // 输出图像 + unsigned char minpixel, // 最小像素值 + unsigned char maxpixel, // 最大像素值 + unsigned char low, // 低像素值 + unsigned char high // 高像素值 +); + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +// 无输出图像但有高低像素值(low, high) +static __global__ void // kernel 函数无返回值 +_thresholdKer( + ImageCuda inout, // 输入输出图像 + unsigned char minpixel, // 最小像素值 + unsigned char maxpixel, // 最大像素值 + unsigned char low, // 低像素值 + unsigned char high // 高像素值 +); + + +// Kernel 函数:_thresholdKer(使用ImageCuda实现的阈值分割) +static __global__ void _thresholdKer( + ImageCuda in, ImageCuda out, unsigned char minpixel, + unsigned char maxpixel) +{ + // 计算想成对应的输出点的位置,其中 dstc 和 dstr 分别表示线程处理的像素点的 + // 坐标的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并 + // 行度缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 + // 4 行上,因此,对于 dstr 需要进行乘 4 计算。 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (dstc >= in.imgMeta.width || dstr >= in.imgMeta.height) + return; + + // 计算第一个输入坐标点和输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * in.pitchBytes + dstc; + int outidx = dstr * out.pitchBytes + dstc; + + // 根据点的像素值进行阈值分割 + if(in.imgMeta.imgData[dstidx] < minpixel || in.imgMeta.imgData[dstidx] > + maxpixel) + out.imgMeta.imgData[outidx] = 0; + else + out.imgMeta.imgData[outidx] = in.imgMeta.imgData[dstidx]; + + // 处理剩下的三个像素点。 + for (int i = 0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各点 + // 之间没有变化,故不用检查。 + if (++dstr >= out.imgMeta.height) + return; + + // 计算输入坐标点以及输出坐标点,由于只有 y 分量增加 1,所以下标只需要加 + // 上对应的 pitch 即可,不需要在进行乘法计算 + dstidx += in.pitchBytes; + outidx += out.pitchBytes; + + // 若输入点像素在阈值范围内,输出点像素即为对应输入点像素,否则为 0 + if(in.imgMeta.imgData[dstidx] < minpixel || + in.imgMeta.imgData[dstidx] > maxpixel) + out.imgMeta.imgData[outidx] = 0; + else + out.imgMeta.imgData[outidx] = in.imgMeta.imgData[dstidx]; + } +} + +// Host 成员方法:threshold(阈值分割) +// 未指定高低像素值且输出图像不为 NULL 的阈值分割。 +__host__ int Threshold::threshold(Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 如果输出图像为NULL,直接调用 In—Place 版本的成员方法。 + if (outimg == NULL) + return threshold(inimg); + + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用对应的 kernel 函数进行计算 + _thresholdKer<<>>( + insubimgCud, outsubimgCud, minPixelVal, maxPixelVal); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + return NO_ERROR; +} + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +static __global__ void _thresholdKer( + ImageCuda inout, unsigned char minpixel, unsigned char maxpixel) +{ + // 计算对应输出点的下标 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 越界检查,若越界则不作任何处理直接退出 + if (dstc >= inout.imgMeta.width || dstr >= inout.imgMeta.height) + return; + + int dstidx = dstr * inout.pitchBytes + dstc; + + // 在图像本身上进行阈值分割,不在阈值内置0否则保持不变 + if(inout.imgMeta.imgData[dstidx] < minpixel || + inout.imgMeta.imgData[dstidx] > maxpixel) + inout.imgMeta.imgData[dstidx] = 0; + + // 处理剩下的三个点 + for (int i = 0; i < 3; i++) { + + if (++dstr >= inout.imgMeta.height) + return; + // 计算输入坐标点,由于只有 y 分量增加 1,所以下标只需要加 + // 上一个 pitch 即可,不需要在进行乘法计算 + dstidx += inout.pitchBytes; + + //若输入点像素在阈值范围内,输出点像素保持不变,否则为 0 + if(inout.imgMeta.imgData[dstidx] < minpixel || + inout.imgMeta.imgData[dstidx] > maxpixel) + inout.imgMeta.imgData[dstidx] = 0; + } +} + +// Host 成员方法:threshold(阈值分割) +__host__ int Threshold::threshold(Image *inoutimg) +{ + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inoutimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的子图像 + ImageCuda inoutimgCud; + errcode = ImageBasicOp::roiSubImage(inoutimg, &inoutimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (inoutimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (inoutimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用对应的 kernel 函数进行计算 + _thresholdKer<<>>(inoutimgCud, minPixelVal, + maxPixelVal); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + return NO_ERROR; +} + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +static __global__ void _thresholdKer( + ImageCuda in, ImageCuda out, unsigned char minpixel, + unsigned char maxpixel, unsigned char low, unsigned char high) +{ + // 计算对应输出点的下标 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 越界检查,若越界则不作任何处理直接退出 + if (dstc >= in.imgMeta.width || dstr >= in.imgMeta.height) + return; + + // 计算第一个输入坐标点和输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * in.pitchBytes + dstc; + int outidx = dstr * out.pitchBytes + dstc; + + // 根据输入图像进行阈值分割,不在阈值内置为低像素:low否则置为高像素:high + if(in.imgMeta.imgData[dstidx] < minpixel || in.imgMeta.imgData[dstidx] > + maxpixel) + out.imgMeta.imgData[outidx] = low; + else + out.imgMeta.imgData[outidx] = high; + + // 处理剩下的三个点 + for (int i = 0; i < 3; i++) { + + if (++dstr >= out.imgMeta.height) + return; + + // 计算输入坐标点以及输出坐标点,由于只有 y 分量增加 1,所以下标只需要 + // 加上对应的 pitch 即可,不需要在进行乘法计算 + dstidx += in.pitchBytes; + outidx += out.pitchBytes; + + // 若输入点像素在阈值范围内,输出点像素为高像素:high,否则为低像素: + // low + if(in.imgMeta.imgData[dstidx] < minpixel || + in.imgMeta.imgData[dstidx] > maxpixel) + out.imgMeta.imgData[outidx] = low; + else + out.imgMeta.imgData[outidx] = high; + } +} + +// Host 成员方法:threshold(阈值分割) +__host__ int Threshold::threshold( + Image *inimg, Image *outimg, unsigned char low, unsigned char high) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL) + return NULL_POINTER; + + // 如果输出图像为NULL,直接调用 In—Place 版本的成员方法。 + if (outimg == NULL) + return threshold(inimg, low, high); + + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的子图像 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的子图像 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用对应的 kernel 函数进行计算 + _thresholdKer<<>>( + insubimgCud, outsubimgCud, minPixelVal, maxPixelVal,low,high); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + return NO_ERROR; +} + +// Kernel 函数:_thresholdKer(使用 ImageCuda 实现的阈值分割) +static __global__ void _thresholdKer( + ImageCuda inout, unsigned char minpixel, unsigned char maxpixel, + unsigned char low, unsigned char high) +{ + // 计算对应输出点的下标 + int dstc = blockIdx.x * blockDim.x + threadIdx.x; + int dstr = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 越界检查,若越界则不作任何处理直接退出 + if (dstc >= inout.imgMeta.width || dstr >= inout.imgMeta.height) + return; + + // 计算第一个输出坐标点对应的图像数据数组下标。 + int dstidx = dstr * inout.pitchBytes + dstc; + + // 在图像自身上进行阈值分割,不在阈值内置为低像素:low 否则置为高像素:high + if(inout.imgMeta.imgData[dstidx] < minpixel || + inout.imgMeta.imgData[dstidx] > maxpixel) + inout.imgMeta.imgData[dstidx] = low; + else + inout.imgMeta.imgData[dstidx] = high; + + // 处理剩下的三个点 + for (int i = 0; i < 3; i++) { + + if (++dstr >= inout.imgMeta.height) + return; + + // 计算输入坐标点以及输出坐标点,由于只有 y 分量增加 1,所以下标只需要加 + // 上对应的 pitch 即可,不需要在进行乘法计 + dstidx += inout.pitchBytes; + + // 在图像自身上进行阈值分割,不在阈值内置为低像素:low 否则置为高像素: + // high + if(inout.imgMeta.imgData[dstidx] < minpixel || + inout.imgMeta.imgData[dstidx] > maxpixel) + inout.imgMeta.imgData[dstidx] = low; + else + inout.imgMeta.imgData[dstidx] = high; + } +} + +// Host 成员方法:threshold(阈值分割) +__host__ int Threshold::threshold(Image *inoutimg, unsigned char low, + unsigned char high) +{ + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inoutimg); + if (errcode != NO_ERROR) + return errcode; + + // 提取输入图像的ROI子图像 + ImageCuda inoutimgCud; + errcode = ImageBasicOp::roiSubImage(inoutimg, &inoutimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (inoutimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (inoutimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用对应的 kernel 函数进行计算 + _thresholdKer<<>>( + inoutimgCud, minPixelVal, maxPixelVal, low, high); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + return NO_ERROR; +} diff --git a/okano_3_0/Threshold.h b/okano_3_0/Threshold.h new file mode 100644 index 0000000..8d2d638 --- /dev/null +++ b/okano_3_0/Threshold.h @@ -0,0 +1,150 @@ +// Threshold.h +// 创建人:邓建平 +// +// 阈值分割(Threshold) +// 功能说明:根据图像的某点的像素值是否在阈值内决定该点分割后的像素值,根据项目 +// 要求,分割处理分为两种情况:(1)、未指定高低像素值,某点像素值在 +// 阈值范围内时,不做改变,否则将改点像素值置 0; (2)、指定高低像素 +// 值某点像素值在阈值范围内时,该点像素值置为高像素值,否则置为低像素 +// 值 +// +// 修订历史: +// 2012年09月01日(邓建平) +// 初始版本 +// 2012年09月04日(邓建平) +// 根据需求重构了类,修正了大量的格式错误 +// 2102年10月25日(邓建平) +// 按照最新版的编码规范对代码进行了调整,并修正了一些之前未发现的格式错误 + +#ifndef __THRESHOLD_H__ +#define __THRESHOLD_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:Threshold(阈值分割) +// 继承自:无 +// 根据图像的某点的像素值是否在阈值内决定该点分割后的像素值,根据项目 要求,分 +// 割处理分为两种情况:(1)、未指定高低像素值,某点像素值在阈值范围内时,不做 +// 改变,否则将改点像素值置 0; (2)、指定高低像素值,某点像素值在阈值范围内时, +// 该点像素值置为高像素值,否则置为低像素值 +class Threshold { + +protected: + + // 成员变量:最小像素值,最大像素值,指定阈值的边界(不包括其本身) + unsigned char minPixelVal,maxPixelVal; + +public: + + // 构造函数:Threshold + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + Threshold() + { + // 使用默认值为类的各个成员变量赋值。 + this->maxPixelVal = 0; // 最大像素值默认值为 0 + this->minPixelVal = 0; // 最小像素值默认值为 0 + } + + // 构造函数:Threshold + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中还 + // 是可以改变的。 + __host__ __device__ + Threshold( + unsigned char minpixelval, // 最小像素值 + unsigned char maxpixelval // 最大像素值 + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->maxPixelVal = 0; // 最大像素值默认值为 0 + this->minPixelVal = 0; // 最小像素值默认值为 0 + + // 根据参数列表中的值设定成员变量的初值 + this->setMinPixelVal(minPixelVal); + this->setMaxPixelVal(maxpixelval); + } + + // 成员方法:getMinPixelVal(读取最小像素值) + // 读取 minPixelVal 成员变量的值。 + __host__ __device__ unsigned char // 返回: 当前 minPixelVal 成员变量的值 + getMinPixelVal() const + { + // 返回 minPixelVal 成员变量的值。 + return this->minPixelVal; + } + + // 成员方法:setMinPixelVal(设置最小像素值) + // 设置 minPixelVal 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setMinPixelVal( + unsigned char minpixelval // 最小像素值 + ) { + // 将 minPixelVal 成员变量赋成新值 + this->minPixelVal = minpixelval; + return NO_ERROR; + } + + // 成员方法:getMaxPixelVal(读取最大像素值) + // 读取 maxPixelVal 成员变量的值。 + __host__ __device__ unsigned char // 返回: 当前 maxPixelVal 成员变量的值 + getMaxPixelVal() const + { + // 返回 maxPixelVal 成员变量的值。 + return this->maxPixelVal; + } + + // 成员方法:setMaxPixelVal(设置最大像素值) + // 设置 maxPixelVal 成员变量的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR。 + setMaxPixelVal( + unsigned char maxpixelval // 最大像素值 + ) { + // 将 maxPixelVal 成员变量赋成新值 + this->maxPixelVal = maxpixelval; + return NO_ERROR; + } + + // Host 成员方法:threshold(阈值分割) + // 未指定高低像素值且输出图像不为 NULL 的阈值分割。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + threshold( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // Host 成员方法:threshold(阈值分割) + // 未指定高低像素值且输出图像为 NULL 的阈值分割。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + threshold( + Image *inoutimg // 输入输出图像 + ); + + // Host 成员方法:threshold(阈值分割) + // 已指定高低像素值(low, high)且输出图像不为 NULL 的阈值分割。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + threshold( + Image *inimg, // 输入图像 + Image *outimg, // 输出图像 + unsigned char low, // 低像素值 + unsigned char high // 高像素值 + ); + + // Host 成员方法:threshold(阈值分割) + // 已指定高低像素值(low, high)且输出图像为 NULL 的阈值分割。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR。 + threshold( + Image *inoutimg, // 输入输出图像 + unsigned char low, // 低像素值 + unsigned char high // 高像素值 + ); +}; + + +#endif diff --git a/okano_3_0/ThresholdSegmentation.cu b/okano_3_0/ThresholdSegmentation.cu new file mode 100644 index 0000000..daf01dc --- /dev/null +++ b/okano_3_0/ThresholdSegmentation.cu @@ -0,0 +1,172 @@ +#include "ThresholdSegmentation.h" + +#include +#include +#include +using namespace std; + +#include "ErrorCode.h" + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了默认的线程块的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// Kernel 函数:_thresholdSegmentationKer(二值化) +// 根据给定的阈值对图像进行二值化处理。如果像素的灰度值与 T 之差小于 D, +// 此像素的灰度值赋值为 255。否则,此像素的灰度值赋值为 0。 +static __global__ void // Kernel 函数无返回值 +_thresholdSegmentationKer( + ImageCuda inimg, // 输入图像 + ImageCuda outimg, // 输出图像 + unsigned char T, // 灰度阈值 + unsigned char D +); + +// Kernel 函数: _thresholdSegmentationKer(二值化) +static __global__ void _thresholdSegmentationKer( + ImageCuda inimg, ImageCuda outimg, + unsigned char T, unsigned char D) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= inimg.imgMeta.width || r >= inimg.imgMeta.height) + return; + + // 计算第一个输入坐标点对应的图像数据数组下标。 + int inidx = r * inimg.pitchBytes + c; + // 计算第一个输出坐标点对应的图像数据数组下标。 + int outidx = r * outimg.pitchBytes + c; + // 读取第一个输入坐标点对应的像素值。 + unsigned char intemp; + intemp = inimg.imgMeta.imgData[inidx]; + + // 一个线程处理四个像素点. + // 如果输入图像的该位置的像素值如果像素的灰度值与 T 之差小于 D, + // 则将输出图像中对应位置的像素值置为 255; + // 否则将输出图像中对应位置的像素值置为 0。 + // 线程中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = (abs(intemp - T) < D ? 255 : 0); + + // 处理剩下的三个像素点。 + for (int i =0; i < 3; i++) { + // 这三个像素点,每个像素点都在前一个的下一行,而 x 分量保持不变。因 + // 此,需要检查这个像素点是否越界。检查只针对 y 分量即可,x 分量在各 + // 点之间没有变化,故不用检查。 + if (++r >= outimg.imgMeta.height) + return; + + // 根据上一个像素点,计算当前像素点的对应的输出图像的下标。由于只有 y + // 分量增加 1,所以下标只需要加上一个 pitch 即可,不需要在进行乘法计 + // 算。 + inidx += inimg.pitchBytes; + outidx += outimg.pitchBytes; + intemp = inimg.imgMeta.imgData[inidx]; + + // 如果输入图像的该位置的像素值如果像素的灰度值与 T 之差小于 D, + // 则将输出图像中对应 + // 位置的像素值置为 255;否则将输出图像中对应位置的像素值置为 0。线程 + // 中处理的第一个点。 + outimg.imgMeta.imgData[outidx] = (abs(intemp - T) < D ? 255 : 0); + } +} + +// Host 成员方法:thresholdSegmentation(阈值分割处理) +__host__ int ThresholdSegmentation::thresholdSeg_parallel( + Image *inimg, Image *outimg) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + // 这一段代码进行图像的预处理工作。图像的预处理主要完成在 Device 内存上为 + // 输入和输出图像准备内存空间,以便盛放数据。 + int errcode; // 局部变量,错误码 + + // 将输入图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(inimg); + if (errcode != NO_ERROR) + return errcode; + + // 将输出图像拷贝入 Device 内存。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + // 如果输出图像无数据(故上面的拷贝函数会失败),则会创建一个和输入图 + // 像的 ROI 子图像尺寸相同的图像。 + errcode = ImageBasicOp::makeAtCurrentDevice( + outimg, inimg->roiX2 - inimg->roiX1, + inimg->roiY2 - inimg->roiY1); + // 如果创建图像也操作失败,则说明操作彻底失败,报错退出。 + if (errcode != NO_ERROR) + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + ImageCuda insubimgCud; + errcode = ImageBasicOp::roiSubImage(inimg, &insubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 提取输出图像的 ROI 子图像。 + ImageCuda outsubimgCud; + errcode = ImageBasicOp::roiSubImage(outimg, &outsubimgCud); + if (errcode != NO_ERROR) + return errcode; + + // 根据子图像的大小对长,宽进行调整,选择长度小的长,宽进行子图像的统一 + if (insubimgCud.imgMeta.width > outsubimgCud.imgMeta.width) + insubimgCud.imgMeta.width = outsubimgCud.imgMeta.width; + else + outsubimgCud.imgMeta.width = insubimgCud.imgMeta.width; + + if (insubimgCud.imgMeta.height > outsubimgCud.imgMeta.height) + insubimgCud.imgMeta.height = outsubimgCud.imgMeta.height; + else + outsubimgCud.imgMeta.height = insubimgCud.imgMeta.height; + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (outsubimgCud.imgMeta.width + blocksize.x - 1) / blocksize.x; + gridsize.y = (outsubimgCud.imgMeta.height + blocksize.y * 4 - 1) / + (blocksize.y * 4); + + // 调用核函数,根据阈值 threshold 进行二值化处理。 + _thresholdSegmentationKer<<>>( + insubimgCud, outsubimgCud, T, D); + + // 若调用 CUDA 出错返回错误代码 + if (cudaGetLastError() != cudaSuccess) + return CUDA_ERROR; + + // 处理完毕,退出。 + return NO_ERROR; +} + +__host__ int ThresholdSegmentation::thresholdSeg_serial(Image *inimg, Image *outimg ) +{ + // 检查输入图像是否为 NULL,如果为 NULL 直接报错返回。 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + int i, j, index; + for(i = 0;i < inimg->width; i++) + for (j = 0; j < inimg->height;j++) { + index = j * inimg->width + i; + if ((inimg->imgData[index] - T > - D) && + (inimg->imgData[index] - T < D)) + outimg->imgData[index] = 255; + else + outimg->imgData[index] = 0; + } + + // 处理完毕,退出。 + return NO_ERROR; +} diff --git a/okano_3_0/ThresholdSegmentation.h b/okano_3_0/ThresholdSegmentation.h new file mode 100644 index 0000000..4cfe1e5 --- /dev/null +++ b/okano_3_0/ThresholdSegmentation.h @@ -0,0 +1,126 @@ +// ThresholdSegmentation.h +// 创建人:王媛媛 +// +// 阈值化(ThresholdSegmentation) +// 功能说明:根据设定的阈值T, D,如果像素的灰度值与 T 之差小于 D, +// 此像素的灰度值赋值为 255。否则,此像素的灰度值赋值为 0。 +// +// 修订历史: +// 2013年09月09日(王媛媛) +// 初始版本 +// 2013年09月22日(王媛媛) +// 修订注释 + +#ifndef __THRESHOLDSEGMENTATION_H__ +#define __THRESHOLDSEGMENTATION_H__ + +#include "Image.h" +#include "ErrorCode.h" + +// 类:ThresholdSegmentation +// 继承自:无 +// 根据设定的阈值,对灰度图像进行二值化处理,得到二值图像。 +class ThresholdSegmentation { + +protected: + + // 成员变量:T,D + // 二值化判断的标准,范围是 [0, 255]。 + unsigned char T; + unsigned char D; + +public: + // 构造函数:ThresholdSegmentation + // 无参数版本的构造函数,所有的成员变量皆初始化为默认值。 + __host__ __device__ + ThresholdSegmentation() + { + // 使用默认值为类的各个成员变量赋值。 + this->T = 200; + this->D = 50; + } + + // 构造函数:ThresholdSegmentation + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + ThresholdSegmentation( + unsigned char T, + unsigned char D + ) { + // 使用默认值为类的各个成员变量赋值,防止用户在构造函数的参数中给了非法 + // 的初始值而使系统进入一个未知的状态。 + this->T = 200; + this->D = 50; + + // 根据参数列表中的值设定成员变量的初值 + setT(T); + setD(D); + } + + // 成员方法:getT(获取灰度阈值) + // 获取成员变量 T 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 T 的值 + getT() const + { + // 返回 T 成员变量的值。 + return this->T; + } + + // 成员方法:setT(设置灰度阈值) + // 设置成员变量 T 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setT( + unsigned char T // 设定新的灰度阈值 + ) { + // 将 T 成员变量赋成新值 + this->T = T; + return NO_ERROR; + } + + // 成员方法:getD(获取灰度阈值) + // 获取成员变量 D 的值。 + __host__ __device__ unsigned char // 返回值:成员变量 D 的值 + getD() const + { + // 返回 D 成员变量的值。 + return this->D; + } + + // 成员方法:setD(设置灰度阈值) + // 设置成员变量 D 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执 + // 行,返回 NO_ERROR。 + setD( + unsigned char D // 设定新的灰度阈值 + ) { + // 将 T 成员变量赋成新值 + this->D = D; + + return NO_ERROR; + } + + // Host 成员方法:thresholdSeg_parallel(阈值分割并行处理) + // 根据阈值对图像进行二值化处理。如果像素的灰度值与 T 之差小于 D, + // 此像素的灰度值赋值为 255。否则,此像素的灰度值赋值为 0。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + thresholdSeg_parallel( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); + + // Host 成员方法:thresholdSeg_serial(阈值分割串行处理) + // 根据阈值对图像进行二值化处理。如果像素的灰度值与 T 之差小于 D, + // 此像素的灰度值赋值为 255。否则,此像素的灰度值赋值为 0。 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + thresholdSeg_serial( + Image *inimg, // 输入图像 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/Thumbs.db b/okano_3_0/Thumbs.db new file mode 100644 index 0000000..3fc79cf Binary files /dev/null and b/okano_3_0/Thumbs.db differ diff --git a/okano_3_0/TorusSegmentation.cu b/okano_3_0/TorusSegmentation.cu new file mode 100644 index 0000000..bb8cc66 --- /dev/null +++ b/okano_3_0/TorusSegmentation.cu @@ -0,0 +1,381 @@ +// TorusSegmentation.cu +// 实现圆环二分类 + +#include "TorusSegmentation.h" + +// 宏:DEF_BLOCK_1D +// 定义了默认的 1D 线程块的尺寸。 +#define DEF_BLOCK_1D 256 + +// 宏:DEF_BLOCK_X 和 DEF_BLOCK_Y +// 定义了二维结构的并行线程块默认的尺寸。 +#define DEF_BLOCK_X 32 +#define DEF_BLOCK_Y 8 + +// 宏:DEF_BLACK 和 DEF_WHITE +// 定义了黑色和白色的像素值。 +#define DEF_BLACK 0 +#define DEF_WHITE 255 + + +// 核函数:_initLblMatToZeroKer(初始化标记矩阵为全 0) +// 在设备端初始化标记矩阵为全零。 +static __global__ void // Kernel 函数无返回值。 +_initLblMatToZeroKer( + ImageCuda outlblimgcud // 坐标集位置标记矩阵 +); + +// 核函数:_initLblMatKer(根据坐标集初始化标记矩阵) +// 在设备端初始化标记矩阵,坐标集内部的点初始化标记为 1。 +static __global__ void // Kernel 函数无返回值。 +_initLblMatKer( + CoordiSet incoordiset, // 输入坐标集 + ImageCuda outlblimgcud // 坐标集位置标记矩阵 +); + +// 核函数:_torusSegLblKer(标记坐标集实现二分类) +// 在设备端通过考查当前像素邻域是否都在坐标集内,标记分类。 +static __global__ void // Kernel 函数无返回值。 +_torusSegLblKer( + ImageCuda inlblimgcud, // 输入坐标集位置标记矩阵 + CoordiSet incoordiset, // 输入坐标集 + TorusSegmentation ts, // 分割操作类 + unsigned char *outlbl // 输出标记数组 +); + +// 核函数:_labelToImgKer(将分割结果反映到图像上) +// 该核函数,根据之前分割得到的标记数组,将分割结果映射到图像上去,在 +// CoordiSet 中记录了在图像中该点的位置,将对应位置的像素二值化为标记值。 +static __global__ void +_labelToImgKer( // Kernel 函数没有返回值。 + CoordiSet incoordiset, // 输入坐标集 + unsigned char *inlabel, // 输入的分类结果数组 + ImageCuda outimgcud // 用于标记的图像 +); + +// 核函数:_initLblMatToZeroKer(初始化标记矩阵为全 0) +static __global__ void _initLblMatToZeroKer( + ImageCuda outlblimgcud) +{ + // 计算线程对应的输出点的位置,其中 c 和 r 分别表示线程处理的像素点的坐标 + // 的 x 和 y 分量(其中,c 表示 column;r 表示 row)。由于我们采用了并行度 + // 缩减的策略,令一个线程处理 4 个输出像素,这四个像素位于统一列的相邻 4 行 + // 上,因此,对于 r 需要进行乘 4 计算。 + int c = blockIdx.x * blockDim.x + threadIdx.x; + int r = (blockIdx.y * blockDim.y + threadIdx.y) * 4; + + // 检查第一个像素点是否越界,如果越界,则不进行处理,一方面节省计算资源,一 + // 方面防止由于段错误导致的程序崩溃。 + if (c >= outlblimgcud.imgMeta.width || r >= outlblimgcud.imgMeta.height) + return; + + for (int i = 0; i < 4; i++) { + // 给当前标记矩阵标记为零。 + outlblimgcud.imgMeta.imgData[r * outlblimgcud.pitchBytes + c] = 0; + + // 继续处理该线程中下一行同一列的点。 + r++; + + // 检查是否越界 + if (r >= outlblimgcud.imgMeta.height) + return; + } +} + +// 核函数:_initLblMatKer(根据坐标集初始化标记矩阵) +static __global__ void _initLblMatKer( + CoordiSet incoordiset, ImageCuda outlblimgcud) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前索引超过了坐标集中的点的个数,直接返回。 + if(index >= incoordiset.count) + return; + + // 计算该点在原图像中的位置。 + int xcrd = incoordiset.tplData[2 * index]; + int ycrd = incoordiset.tplData[2 * index + 1]; + + // 将标记矩阵中对应位置的点标记为 1。 + outlblimgcud.imgMeta.imgData[ycrd * outlblimgcud.pitchBytes + xcrd] = 1; +} + +// 核函数:_torusSegLblKer(标记坐标集实现二分类) +static __global__ void _torusSegLblKer( + ImageCuda inlblimgcud, CoordiSet incoordiset, + TorusSegmentation ts, unsigned char *outlbl) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前索引超过了坐标集中的点的个数,直接返回。 + if (index >= incoordiset.count) + return; + + // 计算该点在原图像中的位置。 + int xcrd = incoordiset.tplData[2 * index]; + int ycrd = incoordiset.tplData[2 * index + 1]; + + // 获取邻域宽度。 + int neighborsize = ts.getNeighborSize(); + + // 获取标记图像尺寸。 + int width = inlblimgcud.imgMeta.width; + int height = inlblimgcud.imgMeta.height; + int pitch = inlblimgcud.pitchBytes; + + // 如果当前像素邻域宽度超过了物理范围,直接标记为 1 类别,核函数返回。 + if (xcrd + neighborsize >= width || xcrd - neighborsize < 0 || + ycrd + neighborsize >= height || ycrd - neighborsize < 0) { + outlbl[index] = 1; + return; + } + + // 遍历当前点的 neighborsize 邻域,发现坐标集外的点即将当前点标记为 1 类别。 + for (int i = ycrd - neighborsize; i <= ycrd + neighborsize; i++) { + for (int j = xcrd - neighborsize; j <= xcrd + neighborsize; j++) { + if (inlblimgcud.imgMeta.imgData[i * pitch + j] == 0) { + outlbl[index] = 1; + return; + } + } + } + + // 其余的情况标记为 2 类别。 + outlbl[index] = 2; +} + +// 核函数:_labelToImgKer(将分割结果反映到图像上) +static __global__ void _labelToImgKer( + CoordiSet incoordiset, unsigned char *inlabel, ImageCuda outimgcud) +{ + // 计算当前 Thread 所对应的坐标集中的点的位置。 + int index = blockIdx.x * blockDim.x + threadIdx.x; + + // 如果当前索引超过了坐标集中的点的个数,直接返回。 + if (index >= incoordiset.count) + return; + + // 计算该点在原图像中的位置。 + int xcrd = incoordiset.tplData[2 * index]; + int ycrd = incoordiset.tplData[2 * index + 1]; + + // 获取标记图像 pitch。 + int pitch = outimgcud.pitchBytes; + + // 根据标记值设置对应图像的像素值。 + if (inlabel[index] == 1) + outimgcud.imgMeta.imgData[ycrd * pitch + xcrd] = DEF_WHITE; + else + outimgcud.imgMeta.imgData[ycrd * pitch + xcrd] = DEF_BLACK; +} + + +// 宏:FREE_LOCAL_MEMORY_TORUS_SEGREGATE(清理局部申请的设备端或者主机端内存) +// 该宏用于清理在 torusSegregate 过程中申请的设备端或者主机端内存空间。 +#define FREE_LOCAL_MEMORY_TORUS_SEGREGATE do { \ + if ((lblimg) != NULL) \ + ImageBasicOp::deleteImage((lblimg)); \ + if ((outlbldev) != NULL) \ + cudaFree((outlbldev)); \ + } while (0) + + +// Host 成员函数:torusSegregate(对圆环进行二分割) +__host__ int TorusSegmentation::torusSegregate( + int width, int height, CoordiSet *incoordiset, unsigned char *outlbl) +{ + // 检查指针是否为空。 + if (incoordiset == NULL || outlbl == NULL) + return NULL_POINTER; + + // 检查参数是否合法。 + if (width <= 0 || height <= 0 || incoordiset->count <= 0) + return INVALID_DATA; + + // 局部变量 count, 简化代码书写。 + int count = incoordiset->count; + + // 申明局部变量。 + unsigned char *outlbldev; // 设备端标记数组 + Image *lblimg; // 坐标集范围二维标记矩阵 + int errcode; // 错误码 + + // 将坐标集拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(incoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 创建坐标集范围二维标记矩阵指针。 + errcode = ImageBasicOp:: newImage(&lblimg); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE; + return CUDA_ERROR; + } + + // 在设备端坐标集范围二维标记矩阵指针。 + errcode = ImageBasicOp::makeAtCurrentDevice(lblimg, width, height); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE; + return CUDA_ERROR; + } + + // 获取设备端标记矩阵。 + ImageCuda lblimgcud; // 坐标集范围设备端标记矩阵 + errcode = ImageBasicOp::roiSubImage(lblimg, &lblimgcud); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE; + return CUDA_ERROR; + } + + // 计算调用初始化矩阵的核函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (width + blocksize.x - 1) / blocksize.x; + gridsize.y = (height + blocksize.y * 4 - 1) / (blocksize.y * 4); + + // 调用核函数,初始化标记矩阵为零。 + _initLblMatToZeroKer<<>>(lblimgcud); + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + blocksize.x = DEF_BLOCK_1D; + blocksize.y = 1; + gridsize.x = count / blocksize.x + 1; + gridsize.y = 1; + + // 调用核函数,使用坐标集初始化标记矩阵对应位置为 1。 + _initLblMatKer<<>>(*incoordiset, lblimgcud); + + // 在设备端申请标记数组。 + errcode = cudaMalloc((void **)&outlbldev, count * sizeof(unsigned char)); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE; + return CUDA_ERROR; + } + + // 调用核函数,进行圆环区域二分类。 + _torusSegLblKer<<>>(lblimgcud, *incoordiset, + *this, outlbldev); + + // 将标记值拷贝到主机端。 + errcode = cudaMemcpy(outlbl, outlbldev, count * sizeof(unsigned char), + cudaMemcpyDeviceToHost); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE; + return CUDA_ERROR; + } + + // 内存清理。 + FREE_LOCAL_MEMORY_TORUS_SEGREGATE; + + return NO_ERROR; +} + + +// 宏:FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG(清理申请的设备端或主机端内存) +// 该宏用于清理在 torusSegregateToImg 过程中申请的设备端或者主机端内存空间。 +#define FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG do { \ + if ((outlabel) != NULL) \ + delete [] (outlabel); \ + if ((outlabeldev) != NULL) \ + cudaFree((outlabeldev)); \ + }while (0) + + +// Host 成员函数:torusSegregateToImg(对圆环进行二分割,结果体现到图像上) +__host__ int TorusSegmentation::torusSegregateToImg( + int width, int height, CoordiSet *incoordiset, Image *outimg) +{ + // 检查指针是否为空。 + if (incoordiset == NULL || outimg == NULL) + return NULL_POINTER; + + // 检查参数是否合法。 + if (width <= 0 || height <= 0 || incoordiset->count <= 0) + return INVALID_DATA; + + // 局部变量 count, 简化代码书写。 + int count = incoordiset->count; + + // 定义局部变量。 + int errcode; // 错误码 + cudaError_t cuerrcode; // CUDA 错误码 + unsigned char *outlabel = NULL; // 主机端标记数组 + unsigned char *outlabeldev = NULL; // 设备端标记数组 + ImageCuda insubimgCud; // ImgCuda 对象 + + // 将坐标集拷贝到 Device 内存中。 + errcode = CoordiSetBasicOp::copyToCurrentDevice(incoordiset); + if (errcode != NO_ERROR) + return errcode; + + // 申请主机端标记数组空间。 + outlabel = new unsigned char[count]; + if (outlabel == NULL) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return OUT_OF_MEM; + } + + // 调用圆环分割 host 函数。 + errcode = torusSegregate(width, height, incoordiset, outlabel); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return errcode; + } + + // 申请设备端标记数组空间。 + cuerrcode = cudaMalloc((void **)&outlabeldev, + sizeof(unsigned char) * count); + if (cuerrcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return cuerrcode; + } + + // 将标记数组拷贝到设备端。 + cuerrcode = cudaMemcpy(outlabeldev, outlabel, sizeof(unsigned char) * count, + cudaMemcpyHostToDevice); + if (cuerrcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return cuerrcode; + } + + // 将输出图像拷贝到 Device 内存中。 + errcode = ImageBasicOp::copyToCurrentDevice(outimg); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return errcode; + } + + // 提取输入图像的 ROI 子图像。 + errcode = ImageBasicOp::roiSubImage(outimg, &insubimgCud); + if (errcode != NO_ERROR) { + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return errcode; + } + + // 计算调用 Kernel 函数的线程块的尺寸和线程块的数量。 + dim3 blocksize, gridsize; + blocksize.x = DEF_BLOCK_X; + blocksize.y = DEF_BLOCK_Y; + gridsize.x = (count + blocksize.x - 1) / blocksize.x; + gridsize.y = 1; + + // 调用核函数,将标记数组映射到图像上。 + _labelToImgKer<<>>(*incoordiset, outlabeldev, + insubimgCud); + + // 若调用 CUDA 出错返回错误代码。 + if (cudaGetLastError() != cudaSuccess) { + // 释放申请的内存,防止内存泄漏。 + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + return CUDA_ERROR; + } + + // 释放内存。 + FREE_LOCAL_MEMORY_TORUS_SEGREGATE_TO_IMG; + + return NO_ERROR; +} + diff --git a/okano_3_0/TorusSegmentation.h b/okano_3_0/TorusSegmentation.h new file mode 100644 index 0000000..ed9327c --- /dev/null +++ b/okano_3_0/TorusSegmentation.h @@ -0,0 +1,118 @@ +// TorusSegmentation.h +// 创建人:邱孝兵 +// +// 圆环分割(TorusSegmentation) +// 功能说明:这个类用于进行圆环分割。其输入为一个图像,和代表该图像中一个圆环形 +// 区域的坐标集,目标为将该环形区域划分为两个部分,其中一个分割闭合包围该圆环 +// 内部区域。分割是根据当前坐标集的点一定邻域范围内是否有坐标集外的点,如果有即 +// 标记为同一个类别,这样就可以同时找出一个包围环和一个被包围环,实现目标。 +// +// 修订历史: +// 2013年03月28日(邱孝兵) +// 初始版本 +// 2013年04月01日(邱孝兵) +// 修改部分代码格式问题 + + +#include "Image.h" +#include "ErrorCode.h" +#include "CoordiSet.h" + + +#ifndef __TORUS_SEGMENTATION_H__ +#define __TORUS_SEGMENTATION_H__ + + +// 类:TorusSegmentation +// 继承自:无 +// 这个类用于进行圆环分割。其输入为一个图像,和代表该图像中一个圆环形 +// 区域的坐标集,目标为将该环形区域划分为两个部分,其中一个分割闭合包围该圆环 +// 内部区域。分割是根据当前坐标集的点一定邻域范围内是否有坐标集外的点,如果有即 +// 标记为同一个类别,这样就可以同时找出一个包围环和一个被包围环,实现目标。 +class TorusSegmentation { + +protected: + + // 成员变量:neighborSize(邻域宽度) + // 定义用于检查是否边缘的宽度,超过当前处理的坐标集的点 + // neighborSize 范围内有超出坐标集范围的点就确定为边缘。 + int neighborSize; + +public: + + // 构造函数:TorusSegmentation + // 无参数版本的构造函数,所有成员变量均初始化为默认值。 + __host__ __device__ + TorusSegmentation() { + // 无参数的构造函数,使用默认值初始化各个变量。 + this->neighborSize = 1; + } + + // 构造函数:TorusSegmentation + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值在程序运行过程中 + // 还是可以改变的。 + __host__ __device__ + TorusSegmentation( + int neighborsize // 邻域宽度 + ) { + // 使用默认值初始化各个变量。 + this->neighborSize = 1; + + // 调用 seters 给各个成员变量赋值。 + this->setNeighborSize(neighborsize); + } + + // 成员方法:getnNighborSize(获取 neighborSize 的值) + // 获取成员变量 neighborSize 的值。 + __host__ __device__ int // 返回值:成员变量 neighborSize 的值。 + getNeighborSize() const + { + // 返回成员变量 neighborSize 的值。 + return this->neighborSize; + } + + // 成员方法:setNeighborSize(设置 neighborSize 的值) + // 设置成员变量 neighborSize 的值。 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR。 + setNeighborSize( + int neighborSize // 外部参数 neighborSize + ) { + // 检查数据有效性。 + if (neighborSize <= 0) + return INVALID_DATA; + + // 设置成员变量 beta 的值。 + this->neighborSize = neighborSize; + return NO_ERROR; + } + + // 成员函数:torusSegregate(对圆环进行二分割) + // 该方法实现圆环分割。其输入为一个图像,和代表该图像中一个圆环形 + // 区域的坐标集,目标为将该环形区域划分为两个部分,其中一个分割闭合 + // 包围该圆环内部区域。分割是根据当前坐标集的点一定邻域范围内是否 + // 有坐标集外的点,如果有即标记为同一个类别,剩下的标记为另一个类别。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + torusSegregate( + int width, // 坐标集所在图像的宽度 + int height, // 坐标集所在图像的高度 + CoordiSet *incoordiset, // 输入坐标集 + unsigned char *outlbl // 输出特征向量 + ); + + // 成员函数:torusSegregateToImg(对圆环进行二分割,结果体现到图像上) + // 在 torusSegregate 方法中,标记结果存储在一个线性数组中,这里为了直观 + // 重新将结果体现到二维图像中去。 + __host__ int // 返回值:函数是否正确执行,若函数 + // 正确执行,返回 NO_ERROR。 + torusSegregateToImg( + int width, // 坐标集所在图像的宽度 + int height, // 坐标集所在图像的高度 + CoordiSet *incoordiset, // 输入坐标集 + Image *outimg // 输出图像 + ); +}; + +#endif + diff --git a/okano_3_0/WorkAndObjectMatch.cu b/okano_3_0/WorkAndObjectMatch.cu new file mode 100644 index 0000000..cf233f9 --- /dev/null +++ b/okano_3_0/WorkAndObjectMatch.cu @@ -0,0 +1,555 @@ +// WorkAndObjectMatch.cu +// WORK and OBJECT 的匹配操作 + +#include "WorkAndObjectMatch.h" + +#include "AffineTrans.h" +#include "DownSampleImage.h" +#include "RoiCopy.h" +#include "RotateTable.h" + +// Host 全局常量:_scalModulus(扩缩系数) +// 对 TEST 图像进行扩缩时使用的扩缩系数 +// 暂时未实现扩缩操作,先注释掉 +//static const float _scalModulus[] = { 0.80f, 0.85f, 0.90f, 0.95f, 1.0f, +// 1.05f, 1.10f, 1.15f, 1.20f }; + +// Host 全局常量:_scalModulusCount(扩缩系数的数量) +// 对 TEST 图像进行扩缩的扩缩系数的数量 +static const int _scalModulusCount = 9; + +// Host 函数:_shrink(对一组图像分别进行 1 / N 缩小) +// 对给定的一组图像进行 1 / N 缩小 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR +_shrink( + Image **inimg, // 输入的一组图像 + Image **outimg, // 输出的一组经过 1 / N 缩小的图像 + int imgcount, // 输入图像的数量 + int tiems // 需要缩小的图像的倍数 +); + +// Host 函数:_deleteBigImageArray(删除一个大图片数组) +// 删除一个存放图片的大数组 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 NO_ERROR +_deleteBigImageArray( + Image **img, // 存放图片的大数组 + int imgcount // 数组的大小 +); + +// Host 函数:_createBigImageArray(创建一个大图片的数组) +// 创建一个存放图片的大的数组 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR +_createBigImageArray( + Image ***img, // 存放图片的数组的指针 + int imgcount // 需要创建的数组的大小 +); + +// Host 函数:_getBestMatchTestIndex(获取相关系数最大的结果) +// 获取相关系数最大的结果 +static __host__ int // 返回值:最大相关系数的索引 +_getBestMatchTestIndex( + MatchRes *res, // 匹配得到的一组结果 + int rescount // 结果的数量 +); + +// Host 函数:_scalAndProjective(对图像进行扩缩和射影变换) +// 对图像进行不同的扩缩和射影变换,扩缩系数由 _scalModulus 指定 +static __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR +_scalAndProjective( + Image *inimg, // 输入图像 + Image **outimg, // 经过不同扩缩和射影变换得到的一组输出图像 + int imgcount // 输出图像的个数 +); + +// Host 函数:_createAffineImages(生成 2 * anglecount 个回转图像) +// 对 TEST 图像生成 2 * angleCount 个角度的回转图像,回转角的范围是 +// angle - 0.2 * anglecount ~ angle + 0.2 * anglecount +static __host__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR +_createAffineImages( + Image *test, // 输入图像 + int twx, int twy, // 回转中心的横坐标和纵坐标 + float angle, // 基准回转角 + int rwidth, int rheight, // 回转后的图像的宽和高 + int anglecount, // 需要回转的角度的数量 + Image **rotatetest // 输出图像,回转后的图像 +); + +// Host 函数:_shrink(对一组图像分别进行 1 / N 缩小) +static __host__ int _shrink(Image **inimg, Image **outimg, int imgcount, + int times) +{ + // 判断 inimg 和 outimg 是否为空,若为空,则返回错误 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + // 判断参数是否合法,若不合法,直接返回错误 + if (imgcount <= 0 || times <= 0) + return INVALID_DATA; + + // 定义一个用来进行 1 / N 缩小操作的对象 + DownSampleImage shrink(times); + + // 依次对每一输入图像进行 1 / N 缩小操作 + for (int i = 0; i < imgcount; i++) { + // 使用概率法进行 1 / N 缩小 + errcode = shrink.probabilityDownSImg(inimg[i], outimg[i]); + // 若 1 / N 缩小操作失败,则直接返回 + if (errcode != NO_ERROR) + return errcode; + } + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// Host 函数:_deleteBigImageArray(删除一个大图片数组) +static __host__ int _deleteBigImageArray(Image **img, int imgcount) +{ + // 判断 img 是否为空,若为空,则返回错误 + if (img == NULL) + return NULL_POINTER; + + // 依次删除数组里的每一张图像 + for (int i = 0; i < imgcount; i++) + ImageBasicOp::deleteImage(img[i]); + // 删除存放图片的数组 + delete [] img; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// Host 函数:_createBigImageArray(创建一个大图片的数组) +static __host__ int _createBigImageArray(Image ***img, int imgcount) +{ + // 判断 img 是否为空,若为空,则返回错误 + if (img == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + // 为数组申请空间 + *img = new Image *[imgcount]; + // 若申请空间失败,返回错误 + if (*img == NULL) + return OUT_OF_MEM; + + // 依次创建指定数量的图片 + for (int i = 0; i < imgcount; i++) { + errcode = ImageBasicOp::newImage(&((*img)[i])); + // 若创建图片失败,删除先前创建的图片,然后返回 + if (errcode != NO_ERROR) { + _deleteBigImageArray(*img, imgcount); + return errcode; + } + } + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// Host 函数:_getBestMatchTestIndex(获取相关系数最大的结果) +static __host__ int _getBestMatchTestIndex(MatchRes *res, int rescount) +{ + // 判断 res 是否为空,若为空,则返回错误 + if (res == NULL) + return NULL_POINTER; + + // 默认相关系数最大的位置为第 0 个结果 + int maxindex = 0; + // 记录最大相关系数的值,默认为第 0 个结果的相关系数 + float max = res[maxindex].coefficient; + // 依次和所有其他的结果比较 + for (int i = 1; i < rescount; i++) { + // 若发现当前结果的相关系数比记录的最大值大,则将当前的相关系数赋值 + // 给 max,同时记录当前结果的索引 + if (max < res[i].coefficient) { + max = res[i].coefficient; + maxindex = i; + } + } + + // 返回具有最大相关系数的结果的索引 + return maxindex; +} + +// Host 函数:_scalAndProjective(对图像进行扩缩和射影变换) +static __host__ int _scalAndProjective(Image *inimg, Image **outimg, + int imgcount) +{ + // 判断 inimg 和 outimg 是否为空,若为空,则返回错误 + if (inimg == NULL || outimg == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + // 此处暂时未实现扩缩和射影变换,只是单纯的拷贝图片 + for (int i = 0; i < imgcount; i++) { + errcode = ImageBasicOp::copyToHost(inimg, outimg[i]); + if (errcode != NO_ERROR) { + return errcode; + } + } + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// Host 函数:_createAffineImages(生成 2 * anglecount 个回转图像) +static __host__ int _createAffineImages(Image *test, int twx, int twy, + float angle, int rwidth, int rheight, + int anglecount, Image **rotatetest) +{ + // 判断 test 和 rotatetest 是否为空,若为空,则返回错误 + if (test == NULL || rotatetest == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + // 检查参数是否合法,若不合法,直接返回错误 + if (test == NULL || rotatetest == NULL) + return NULL_POINTER; + + // 定义一个进行回转操作的对象 + AffineTrans affine; + // 设置旋转前的平移向量 + affine.setX(test->width / 2 - twx); + affine.setY(test->height / 2 - twy); + + // 生成 2 * anglecount 个回转图像,角度分别为 + // angle - 0.2 * anglecount ~ angle + 0.2 * anglecount + for (int i = 0; i < 2 * anglecount; i++) { + // 创建一个临时图片 + Image *t; + errcode = ImageBasicOp::newImage(&t); + // 如果申请空间失败,则直接返回错误 + if (errcode != NO_ERROR) + return errcode; + // 设置回旋的角度为 + errcode = affine.setAlpha(angle + 0.2 * (i - anglecount)); + // 若设置失败,则直接返回错误 + if (errcode != NO_ERROR) + return errcode; + // 对输入图像进行回转 + errcode = affine.rotateShift(test, t); + // 若回转失败,则直接返回错误 + if (errcode != NO_ERROR) + return errcode; + // 设置回转后的图像的子图的大小 + t->roiX1 = t->width / 2 - rwidth / 2; + t->roiY1 = t->height / 2 - rheight / 2; + t->roiX2 = t->roiX1 + rwidth; + t->roiY2 = t->roiY1 + rheight; + // 将子图从临时图像中 clip 出来 + RoiCopy copy; + errcode = copy.roiCopyAtDevice(t, rotatetest[i]); + // 删除临时图像 + ImageBasicOp::deleteImage(t); + // 若拷贝子图失败,则直接返回 + if (errcode != NO_ERROR) + return errcode; + } + + // 处理完毕,直接返回 + return NO_ERROR; +} + +// 成员方法: 获取 TEST 图像中的 WORK 图像 +__host__ int WorkAndObjectMatch::getMatchWork(Image *test, Image *work) +{ + // 检查 test 和 res 是否为空,若为空,则返回错误 + if (test == NULL || work == NULL) + return NULL_POINTER; + + int errcode; // 局部变量,错误码 + + // 定义一个大的图像数组,为后面的操作提供所需要的图像空间 + Image **bigimagesarray; + // 标记 bigimagesarray 数组的大小 + int bigimagessize; + // 图像数组的游标 + Image **cursor; + + // 计算图像数组的大小 + bigimagessize = normalWork->count + 2 * _scalModulusCount + 2 * angleCount; + // 创建指定大小的图像数组,为后面的操作提供图像空间 + errcode = _createBigImageArray(&bigimagesarray, bigimagessize); + // 若图像数组创建失败,则直接返回 + if (errcode != NO_ERROR) + return errcode; + + // 初始化游标为当前图像地址 + cursor = bigimagesarray; + + // 对标准的 WORK 图像进行 1 / 8 缩小 + + // 存储 1 / 8 缩小的标准 WORK 图像 + Image **shrinknormalwork; + // 从图像数组中获取空间 + shrinknormalwork = cursor; + // 更新游标的位置 + cursor += normalWork->count; + + // 对一组标准的 WORK 图像进行 1 / 8 图像缩小 + errcode = _shrink(normalWork->images, shrinknormalwork, + normalWork->count, 8); + // 若 1 / 8 图像缩小操作失败,则删除图像数组,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return errcode; + } + + // 对 TEST 图像进行扩缩和射影变换 + + // 存储 TEST 图像进行不同扩缩系数的扩缩和射影变换得到的图像 + Image **scalprojecttest; + // 从图像数组中获取空间 + scalprojecttest = cursor; + // 更新游标的位置 + cursor += _scalModulusCount; + + // 对 TEST 图像进行不同的扩缩和射影变换 + errcode = _scalAndProjective(test, scalprojecttest, _scalModulusCount); + // 若扩缩和射影变换操作失败,则删除图像数组的空间,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return errcode; + } + + // 对 TEST 的各个变形的图像进行 1 / 8 缩小 + + // 存储对 TEST 图像的各个变形的图像进行 1 / 8 缩小后的图像 + Image **shrinkscalprojecttest; + // 从图像数组中获取空间 + shrinkscalprojecttest = cursor; + // 更新游标的位置 + cursor += _scalModulusCount; + + // 对变形的各个 TEST 图像进行 1 / 8 缩小操作 + errcode = _shrink(scalprojecttest, shrinkscalprojecttest, + _scalModulusCount, 8); + // 如果 1 / 8 操作失败,则删除图像数组,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return errcode; + } + + // 用 1 / 8 缩小的,具有不同回旋角的 WORK 图像分别在 1 / 8 缩小的 TEST 图像 + // 进行匹配 + + // 申请一段内存空间,用来存储匹配得到的结果 + MatchRes *workmatchres = new MatchRes[_scalModulusCount]; + // 若申请内存失败,则删除图像数组,然后返回错误 + if (workmatchres == NULL) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return OUT_OF_MEM; + } + + // 定义一个用来进行图像匹配的对象 + ImageMatch match; + // 设置旋转表 + match.setRotateTable(normalWork->rotateTable); + // 设置摄动范围的宽 + match.setDWidth(normalWork->dWidth); + // 设置摄动范围的高 + match.setDHeight(normalWork->dHeight); + // 设置摄动中心的横坐标 + match.setDX(normalWork->dX); + // 设置摄动中心的纵坐标 + match.setDY(normalWork->dY); + // 设置匹配需要的 TEMPLATE 图像 + errcode = match.setTemplateImage(shrinknormalwork, normalWork->count); + // 如果设置 TEMPLATE 图像失败,则释放先前申请的内存空间,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + delete [] workmatchres; + return errcode; + } + + // 用 1 / 8 缩小的,具有不同回旋角的 WORK 图像分别在 1 / 8 缩小的经过扩缩和 + // 摄影变换 TEST 图像进行匹配 + for (int i = 0; i < _scalModulusCount; i++) { + errcode = match.imageMatch(shrinkscalprojecttest[i], &workmatchres[i], + NULL); + // 若匹配操作失败,则返回释放先前申请的空间,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + delete [] workmatchres; + return errcode; + } + } + + // 找到最佳匹配的 TEST 图像的索引 + int besttestindex = _getBestMatchTestIndex(workmatchres, _scalModulusCount); + // 获取扩缩和摄影变换后, 1 / 8 缩小前的最佳匹配的 TEST 图像 + Image *matchtest = scalprojecttest[besttestindex]; + + // 对扩缩和摄影变换后,缩小前的最佳匹配的 TEST 图像生成 2 * angleCount 个 + // 角度的回转图像 + + // 用来存储得到的 2 * angleCount 个回转角的回转图像 + Image **rotatetest; + // 从图像数组中获取空间 + rotatetest = cursor; + // 更新游标 + cursor += 2 * angleCount; + // 计算 1 / 8 缩小前在 TEST 图像的匹配中心,由 1 / 8 缩小的 TEST 图像匹配 + // 得到的最佳匹配中心乘 8 得到 + int twx = workmatchres[besttestindex].matchX * 8; + int twy = workmatchres[besttestindex].matchY * 8; + // 获取 1 / 8 缩小的 TEST 图像匹配得到旋转角 + float angle = workmatchres[besttestindex].angle; + // 设置图像回转后的大小,这里设置为标准 WORK 图像的 1.5 倍 + int rwidth = normalWork->images[0]->width * 3 / 2; + int rheight = normalWork->images[0]->height * 3 / 2; + + // 删除先前申请的用来记录匹配得到的结果的空间 + delete [] workmatchres; + // 创建 2 * angleCount 个角度的回转图像 + errcode = _createAffineImages(matchtest, twx, twy, angle, rwidth, rheight, + angleCount, rotatetest); + // 如果创建失败,则释放图像数组的空间,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return NO_ERROR; + } + + // 分别用标准 WORK 图像对 2 * angleCount 个角度的回转图像进行匹配 + + // 设置旋转表,由于之前已经对图像进行了回转,所以这里旋转角设置为 0 + RotateTable table(0.0f, 0.0f, 0.2f, rwidth * 2, rheight * 2); + // 创建用来进行图像匹配操作的对象 + ImageMatch rmatch(&table, rwidth, rheight, rwidth / 2, rheight / 2, 3, + 0, 0, 0, 0); + // 设置匹配的 TEMPLATE 图像 + errcode = rmatch.setTemplateImage(normalWork->images, normalWork->count); + // 如果设置失败,释放图像数组空间,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return errcode; + } + // 申请用来存储匹配得到的结果的空间 + workmatchres = new MatchRes[2 * angleCount]; + // 分别用标准 WORK 图像对 2 * angleCount 个角度的回转图像进行匹配 + for (int i = 0; i < 2 * angleCount; i++) { + errcode = rmatch.imageMatch(rotatetest[i], &workmatchres[i], NULL); + // 如果匹配失败,则释放之前申请的空间,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + delete [] workmatchres; + return errcode; + } + } + + // 找出 rotatetest 中的最大匹配的图像的索引 + int bestrotatetestindex = _getBestMatchTestIndex(workmatchres, + 2 * angleCount); + // 获取最佳匹配的图像 + Image *matchrotatetest = rotatetest[bestrotatetestindex]; + // 计算最佳匹配图像的回转角 + float bestangle = angle + 0.2 * (bestrotatetestindex - angleCount); + // 获取最佳匹配的中心 + int cx = workmatchres[bestrotatetestindex].matchX; + int cy = workmatchres[bestrotatetestindex].matchY; + // 删除之前申请的存放匹配结果的内存空间 + delete [] workmatchres; + + // 对得到的最佳匹配的 rotatetest 图像进行方向矫正处理 + + // 计算回转的移动向量 + int tx = rwidth / 2 - cx; + int ty = rheight / 2 - cy; + // 定义用来进行回转操作的对象 + AffineTrans aff(AFFINE_SOFT_IPL, tx, ty, -bestangle); + // 对 rotatetest 图像进行方向矫正处理 + errcode = aff.rotateShift(matchrotatetest, work); + // 如果回转操作失败,则删除图像数组,然后返回错误 + if (errcode != NO_ERROR) { + _deleteBigImageArray(bigimagesarray, bigimagessize); + return errcode; + } + + // 删除图像数组 + _deleteBigImageArray(bigimagesarray, bigimagessize); + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + +// 成员方法:workAndObjectMatch(进行 WORK and OBJECT 进行匹配操作) +__host__ int WorkAndObjectMatch::workAndObjectMatch(Image *test, + MatchRes *res, int rescount) +{ + // 检查 test 和 res 是否为空,若为空,则返回错误 + if (test == NULL || res == NULL) + return NULL_POINTER; + + // 检查 rescount 是否合法,若不合法,则返回错误 + if (rescount <= 0) + return INVALID_DATA; + + // 检查 normalWork 和 objects 是否为空,若为空,则返回错误 + if (normalWork == NULL || objects == NULL) + return NULL_POINTER; + + // 检查标准 WORK 图像的数量是否合法,若不合法,直接返回错误 + if (normalWork->count <= 0) + return INVALID_DATA; + + int errcode; // 局部变量,错误码 + Image *work; // 局部变量,TEST 图像中的 WORK 图像 + // 为 work 申请空间 + errcode = ImageBasicOp::newImage(&work); + // 若失败,则直接返回错误 + if (errcode != NO_ERROR) + return errcode; + + // 获取 TEST 图片中的 WORK 图片 + errcode = getMatchWork(test, work); + // 若获取失败,则释放 work 图像,然后返回错误 + if (errcode != NO_ERROR) { + ImageBasicOp::deleteImage(work); + return errcode; + } + + // 计算 objectCount 和 rescount 的最小值,用来指定匹配的次数 + int objectcount = objectCount < rescount ? objectCount : rescount; + // 分别用各个 OBJECT 图像对 work 图像进行匹配 + for (int i = 0; i < objectcount; i++) { + // 定义一个用来匹配的对象 + ImageMatch match; + // 设置匹配的摄动范围的宽 + match.setDWidth(objects[i].dWidth); + // 设置匹配的摄动范围的高 + match.setDHeight(objects[i].dHeight); + // 设置摄动中心的横坐标 + match.setDX(objects[i].dX); + // 设置摄动中心的纵坐标 + match.setDY(objects[i].dY); + // 设置旋转表 + match.setRotateTable(objects[i].rotateTable); + // 设置 TEMPLATE 图像 + errcode = match.setTemplateImage(objects[i].images, objects[i].count); + // 若设置失败,则释放 work 图像的空间,然后返回错误 + if (errcode != NO_ERROR) { + ImageBasicOp::deleteImage(work); + return errcode; + } + // 用 OBJECT 图像对 work 图像进行匹配 + errcode = match.imageMatch(work, &res[i], NULL); + // 若匹配发生错误,则释放 work 图像的空间,然后返回错误 + if (errcode != NO_ERROR) { + ImageBasicOp::deleteImage(work); + return errcode; + } + } + + // 释放 work 图像的空间 + ImageBasicOp::deleteImage(work); + // 处理完毕,返回 NO_ERROR + return NO_ERROR; +} + diff --git a/okano_3_0/WorkAndObjectMatch.h b/okano_3_0/WorkAndObjectMatch.h new file mode 100644 index 0000000..6ecfe1c --- /dev/null +++ b/okano_3_0/WorkAndObjectMatch.h @@ -0,0 +1,226 @@ +// WorkAndObjectMatch.h +// 创建人:罗劼 +// +// WORK and OBJETC 匹配(WORK and OBJETC match) +// 功能说明:给定一张 TEST 图像,通过给定的一组标准的 WORK 图像,用这组标准的 +// WORK 图像对 TEST 图像进行匹配,得到 TEST 中 WORK 图像的位置,然后 +// 将 WORK 图像从 TEST 图像中 clip 出来,之后的匹配都是针对 WORK 图像 +// 进行匹配。再给定一组 OBJECT 图像,每个 OBJECT 分别对 WORK 图像进行 +// 匹配,每个 OBJECT 有不同的 TEMPLATE 样本,找到每个 OBJECT 中与 WORK +// 最匹配的 TEMPLATE 以及所使用的旋转角 +// +// 修订历史: +// 2012年12月13日(罗劼) +// 初始版本 +// 2012年12月20日(李冬) +// 修正了代码中函数调用的错误。 +// 2013年03月08日(侯怡婷) +// 更新缩小图像算法的名字。 +// 2013年04月19日(于玉龙) +// 更新了 DownSmapleImage 的函数名。 + +#ifndef __WORKANDOBJTCTMATCH_H__ +#define __WORKANDOBJTCTMATCH_H__ + +#include "ErrorCode.h" +#include "Image.h" +#include "ImageMatch.h" +#include "RotateTable.h" + +// 结构体:ImagesInfo(保存一组 TEMPLATE 的信息) +// 用来保存一组在大小、方向上有差异的 TEMPLATE 的信息,包括这组 TEMPLATE 的图像 +// 数据、TEMPLATE 的数量,匹配使用的旋转表、匹配的摄动范围、匹配的摄动中心 +typedef struct ImagesInfo_st { + Image **images; // 这组 TEMPLATE 的图像数据 + int count; // 这组 TEMPLATE 图像的数量 + RotateTable *rotateTable; // 这组 TEMPLATE 所使用的旋转表 + int dWidth; // TEMPLATE 进行匹配时的摄动范围的宽 + int dHeight; // TEMPLATE 进行匹配时的摄动范围的高 + int dX; // TEMPLATE 进行匹配时摄动中心的横坐标 + int dY; // TEMPLATE 进行匹配时摄动中心的纵坐标 +} ImagesInfo; + +// 类:WorkAndObjectMatch +// 继承自:无 +// 给定一张 TEST 图像,通过给定的一组标准的 WORK 图像,用这组标准的WORK 图像 +// 对 TEST 图像进行匹配,得到 TEST 中 WORK 图像的位置,然后将 WORK 图像从 TEST +// 图像中 clip 出来,之后的匹配都是针对 WORK 图像进行匹配。再给定一组 OBJECT 图 +// 像,每个 OBJECT 分别对 WORK 图像进行匹配,每个 OBJECT 有不同的 TEMPLATE 样 +// 本,找到每个 OBJECT 中与 WORK最匹配的 TEMPLATE 以及所使用的旋转角 +class WorkAndObjectMatch { + +protected: + + // 成员变量:normalWork(一组标准的 WORK) + // 一组标准的 WORK 图像信息,用来对 TEST 图像进行匹配,在 TEST 图像找到 + // WORK 图像 + ImagesInfo *normalWork; + + // 成员变量:objects(OBJECT 图像) + // 每个 OBJECT 图像有不同的 TEMPLATE,这些 TEMPLATE 在大小、方向上存在差异, + // 所以每个 OBJECT 是用 ImagesInfo 来存储 + ImagesInfo *objects; + + // 成员变量:objectCount(OBJECT 的数量) + // 用来记录 OBJECT 的数量 + int objectCount; + + // 成员变量:angleCount(设置对变形的 TEST 图像生成的图像的数量) + // 对匹配得到的变形的 TEST 图像用 AFFINE 生成 2 * angleCount 个角度的回旋 + // 图像,旋转角的单位是 0.2,范围为 + // θ - 0.2 * angleCount ~ θ + 0.2 * angleCount + int angleCount; + + // 成员函数:setDefParameter(设置成员变量的默认值) + // 为所有的成员变量设置默认值 + __host__ __device__ void // 返回值:无 + setDefParameter() + { + this->normalWork = NULL; // 设置 normalWork 的默认值为空 + this->objects = NULL; // 设置 objects 的默认值为空 + this->objectCount = 0; // 设置 OBJECT 的默认值为 0 + this->angleCount = 10; // 设置 angleCount 的默认值为 10 + } + + // 成员方法:getMatchWork(获取 TEST 图像中的 WORK 图像) + // 获取 TEST 图像中的 WORK 图像,并从 TEST 图像中将 WORK 图像 clip 出来 + __host__ int // 返回指:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR + getMatchWork( + Image *test, // TEST 图像 + Image *work // WORK 图像 + ); + +public: + + // 构造函数:WorkAndObjectMatch + // 无参数版本的构造函数,所有的成员变量都使用默认值 + __host__ __device__ + WorkAndObjectMatch() + { + // 为所有的成员变量设置默认值 + setDefParameter(); + } + + // 构造函数:WorkAndObjectMatch + // 有参数版本的构造函数,根据需要给定各个参数,这些参数值可以在程序运行过程 + // 中改变 + __host__ __device__ + WorkAndObjectMatch( + ImagesInfo *normalwork, // 标准的 WORK 图像 + ImagesInfo *objects, // OBJECT 图像 + int objectcount, // OBJECT 数量 + int anglecount // angleCount 的值 + ) { + // 使用默认值初始化各成员变量 + setDefParameter(); + + // 根据参数列表中的值设定成员变量的初值 + setNormalWork(normalwork); + setObject(objects, objectcount); + setAngleCount(anglecount); + } + + // 成员方法:getNormalWork(获取标准 WORK 图像信息的指针) + // 获取成员变量 normalWork 的值 + __host__ __device__ ImagesInfo * // 返回值:成员变量 normalWork 的值 + getNormalWork() const + { + // 返回成员变量 normalWork 的值 + return this->normalWork; + } + + // 成员方法:setNormalWork(设置标准 WORK 图像的信息) + // 设置 normalWork 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确 + // 执行,返回 NO_ERROR + setNormalWork( + ImagesInfo *normalwork // 新的标准 WORK 图像 + ) { + // 设置新的 normalWork 的值 + this->normalWork = normalwork; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; + } + + // 成员方法:getObject(获取 OBJECT 的信息) + // 获取 objects 的值 + __host__ __device__ ImagesInfo * // 返回值:成员变量 objects 的值 + getObject() const + { + // 返回成员变量 objects 的值 + return this->objects; + } + + // 成员方法:getObjectCount(获取 OBJECT 的数量) + // 获取 objectCount 的值 + __host__ __device__ int // 返回值:成员变量 objectCount 的值 + getObjectCount() const + { + // 返回成员变量 objectCount 的值 + return this->objectCount; + } + + // 成员方法:setObject(设置 OBJECT 的信息) + // 设置 objects 和 objectCount 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行, + // 返回 NO_ERROR + setObject( + ImagesInfo *objects, // 新的 OBJECT 的信息 + int objectcount // 新的 OBJECT 的数量 + ) { + // 检查 objectcount 是否合法,若不合法,直接返回 + if (objectcount <= 0) + return INVALID_DATA; + + // 设置新的 objects 的值 + this->objects = objects; + // 设置新的 objectCount 的值 + this->objectCount = objectcount; + + // 处理完毕,返回 NO_ERROR + return NO_ERROR; + } + + // 成员方法:getAngleCount(获取对变形 TEST 图像需要生成的角度的数量) + // 获取 angleCount 的值 + __host__ __device__ int // 返回值:返回 angleCount 的值 + getAngleCount() const + { + // 返回 angleCount 的值 + return this->angleCount; + } + + // 成员方法:setAngleCount(设置需要对变形的 TEST 生成的不用旋转角的数量) + // 设置 angleCount 的值 + __host__ __device__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR + setAngleCount( + int anglecount // 新的 angleCount 的值 + ) { + // 判断 anglecount 的值是否合法,若不合法,直接返回错误 + if (anglecount <= 0) + return INVALID_DATA; + + // 更新 angleCount 的值 + this->angleCount = anglecount; + + // 处理完毕,返回 + return NO_ERROR; + } + + // 成员方法:workAndObjectMatch(进行 WORK and OBJECT 进行匹配操作) + // 进行 WORK and OBJECT 进行匹配操作的主方法,如果 rescount 小于 objectCount + // 的值,则只对前 rescount 的 OBJECT 进行处理 + __host__ int // 返回值:函数是否正确执行,若函数正确执行,返回 + // NO_ERROR + workAndObjectMatch( + Image *test, // TEST 图像数据 + MatchRes *res, // 存放每个 OBJECT 匹配后得到的结果 + int rescount // res 数组的长度 + ); +}; + +#endif + diff --git a/okano_3_0/main.cu b/okano_3_0/main.cu new file mode 100644 index 0000000..7bca360 --- /dev/null +++ b/okano_3_0/main.cu @@ -0,0 +1,61 @@ +#include +using namespace std; + +#include "Image.h" +#include "Template.h" +#include "CombineImage.h" + +#define ROUND_NUM 1 +#define INIMG_CNT 3 +char *infilename[] = { "okano01.bmp", "okano02.bmp", "hist_in.bmp" }; + +int main() +{ + CombineImage ci; + + cudaEvent_t start, stop; + float elapsedTime = 0.0; + + Image *inimg[INIMG_CNT]; + for (int i = 0; i < INIMG_CNT; i++) { + ImageBasicOp::newImage(&inimg[i]); + ImageBasicOp::readFromFile(infilename[i], inimg[i]); + } + + Image *outimg; + ImageBasicOp::newImage(&outimg); + //ImageBasicOp::makeAtCurrentDevice(outimg, 648, 482); + + cout << "AA" << endl; + for (int i = 0; i <= ROUND_NUM; i++) { + cudaEventCreate(&start); + cudaEventCreate(&stop); + //cout << "Test start!" << endl; + + cudaEventRecord(start, 0); + + ci.combineImageMax(inimg, INIMG_CNT, outimg); + + cudaEventRecord(stop, 0); + cudaEventSynchronize(stop); + //cout << cudaGetErrorString(cudaGetLastError()) << endl; + cudaEventElapsedTime(&elapsedTime, start, stop); + + cout << elapsedTime << endl; + + cudaEventDestroy(start); + cudaEventDestroy(stop); + + } + + ImageBasicOp::copyToHost(outimg); + ImageBasicOp::writeToFile("out.bmp", outimg); + + for (int i = 0; i < INIMG_CNT; i++) + ImageBasicOp::deleteImage(inimg[i]); + ImageBasicOp::deleteImage(outimg); + + + return 0; +} +