SVM简介
面部识别是一个经常讨论的计算机科学话题,并且由于计算机处理能力的指数级增长而成为人们高度关注的话题。面部识别在机器人、生物安全和汽车工业等许多领域都有广泛的应用,涉及对输入图像应用数学算法,提取不同的特征,表明所提供的图片中是否存在人脸。方向梯度直方图(HOG)是一种传统算法,用于提取图像特征,例如像素方向,并且可以与线性支持向量机(SVM)一起使用来将输入图像识别为人脸或不是人脸。
我们将使用下面图像作为参考和测试:
图像处理
卷积
两个函数的卷积是一种重要的数学运算,在信号处理中广泛应用。在计算机图形和图像处理领域,我们通常使用离散函数(例如图像)并应用离散形式的卷积来消除高频噪声、锐化细节或检测边缘。
卷积是对两个信号 f 和 g 的数学运算,定义为:
在图像领域,我们可以将卷积想象为单个像素与其相邻像素之间的关系。这种关系主要应用搜索颜色变化、亮度差异和像素周期性等独特特征检测。
下图说明了使用小型 3 x 3 内核的卷积滤波器。滤波器被定义为一个矩阵,其中中心项对中心像素进行加权,其他项定义相邻像素的权重。我们也可以说 3×3 核的半径为 1,因为在卷积过程中只考虑“一环”邻域。在图像边界要定义卷积的行为,其中内核映射到图像外部未定义的值。
使用 3 x 3 窗口和 3 x 3 内核的卷积运算可以定义如下:
static int convolve(unsigned int window[3][3], int kernel[3][3]) { int result = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { result+= window[i][j] * kernel[i][j]; } } return result; }
为了对整个图像进行卷积运算,可以应用滑动窗口技术。从第一个像素开始,每 8 个临近像素被分组为一个方形窗口,窗口内的输入像素与内核进行卷积,产生一个像素值放置在输出图像中。重复此步骤直到图像结束。
Sobel-索贝尔
边缘检测是检测灰度图像中不连续性的最常见方法。边缘被定义为位于两个区域之间的特定边界上的一组连接的像素。
如果输入图像是彩色图像,则在应用卷积运算之前,将其转换为灰度图像。
假设每个像素都使用 32 位无符号整数表示,则 RGB 转换为灰度的代码如下所示:
#define R(pixel) (((pixel) >> 16) & 0xFF) #define G(pixel) (((pixel) >> 8) & 0xFF) #define B(pixel) (((pixel) ) & 0xFF) float rgb2gray(unsigned int pixel) { return (R(pixel) * 0.2989 + G(pixel) * 0.5870 + B(pixel) * 0.1440); }
运行后,测试图像将如下所示:
Sobel 算子是边缘检测中最常用的算子之一。Sobel 算子使用两个 3×3 内核与原始图像进行卷积来计算导数的近似值 - 一个用于水平变化,另一个用于垂直变化。如果我们将 A 定义为源图像,G x和 G y是两个图像,每个点分别包含水平和垂直导数近似值,则计算如下:
通过前面的卷积函数,我们可以使用以下代码计算输出图像:
int dx = convolve(window, kernel_x); int dy = convolve(window, kernel_y);
其中窗口定义为 3 x 3 滑动窗口,内核是 Sobel 算子使用的内核:
static int kernel_x[3][3] = { { 1, 2, 1}, { 0, 0, 0}, {-1, -2, -1} }; static int kernel_y[3][3] = { { 1, 0, -1}, { 2, 0, -2}, { 1, 0, -1} };
卷积计算后得到的图像如下:
正如所看到的,垂直和水平细节得到增强并且更易于观察。尽管它有帮助,但我们需要一个更独特的特征图像,仅代表边缘。
下一步将组合这两个图像并获得双向变化图。我们可以通过计算每个像素值的大小或强度以及当前像素与边缘线中的另一个像素链接的方向或角度来做到这一点。
在图像中的每个点,可以使用以下方法组合所得的近似值来给出幅度:
以及使用的角度:
squareroot 和 atan2 函数都已在 HLS 命名空间中实现:
unsigned int magnitude = hls::sqrt(dx*dx + dy*dy); int angle = hls::atan2(dx,dy);
结果是:
幅度 角度
我们已经得到边缘更加集中的图像。尽管如此,在多种形式的领域,边缘会变得更宽。我们需要使用一种称为非极大值抑制的技术来抑制这些错误边缘:
unsigned int nms(unsigned int mag[3][3], int ang) { unsigned int q,r; q = r = 255; if ((0 <= ang < 23) || (158 < ang <= 180)) { q = mag[1][2]; r = mag[1][0]; } else if (223 <= ang < 68) { q = mag[2][0]; r = mag[0][2]; } else if ( 68 <= ang < 113) { q = mag[0][1]; r = mag[2][1]; } else if ( 113 <= ang < 158) { q = mag[0][0]; r = mag[2][2]; } if (mag[1][1] >= q && mag[1][1] >= r) return mag[1][1]; return 0; }
现在边缘更薄、更简洁。
实施
如前所述,输入图像以数据流的形式逐像素输入。为了应用卷积运算,我们需要将数据打包在 3 x 3 窗口下。可以使用具有两个缓冲区的架构来实现这一点,其中元素数量等于宽度,如果我们的输入图像:
这里将有两个辅助函数用于移动行缓冲区和滑动窗口:
static void shift_w(unsigned int window[3][3], unsigned int v1, unsigned int v2, unsigned int v3) { window[0][0] = window[0][1]; window[0][1] = window[0][2]; window[0][2] = v1; window[1][0] = window[1][1]; window[1][1] = window[1][2]; window[1][2] = v2; window[2][0] = window[2][1]; window[2][1] = window[2][2]; window[2][2] = v3; } static void shift_b(unsigned int line_buffer[2][1280], int pos, unsigned int val) { line_buffer[0][pos] = line_buffer[1][pos]; line_buffer[1][pos] = val; }
最后,我们可以将整个过程打包成一个 HLS 函数(代码见附件)。
得到了代码后,还应该对其进行测试。GIMP (https://www.oschina.net/p/gimp?hmsr=aladdin1e1)有一个非常酷的功能,可以直接将图像导出为头文件。假设我们将测试图像导出到文件 image.h 下,就可以利用如下代码实现我们要测试的功能(代码见文末)。
验证 HLS IP 的另一种方法是直接在 FPGA 上进行验证。
第一步是创建block design并将合成的 Sobel IP 添加到存储库:
添加已实现的 IP,其中一个 DMA 向其提供数据,另一个读取输出:
生成比特流后就可以验证功能。
生成的图像应与模拟图像相似。
现在我们需要实现一个直接从相机输入的架构。
第一个组件是 Znyq 处理系统和用于配置相机接口的 i2c 控制器:
在图像流方面,需要一个 MIPI 控制器和一个 Demosaic IP 将流转换为 RGB24:
最后添加我们的图像处理IP和VDMA:
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !