FPGA 上使用 SVM 进行图像处理

时间:2024-03-20 09:05:17

8b5057b102520417c3fa6a3f1d132e17.jpeg

SVM简介

面部识别是一个经常讨论的计算机科学话题,并且由于计算机处理能力的指数级增长而成为人们高度关注的话题。面部识别在机器人、生物安全和汽车工业等许多领域都有广泛的应用,涉及对输入图像应用数学算法,提取不同的特征,表明所提供的图片中是否存在人脸。方向梯度直方图(HOG)是一种传统算法,用于提取图像特征,例如像素方向,并且可以与线性支持向量机(SVM)一起使用来将输入图像识别为人脸或不是人脸。

1cde0d63a41927801c713afaf7720613.jpeg

我们将使用下面图像作为参考和测试:

0649e71fcc42afdfa047b56779209327.png

图像处理

卷积

两个函数的卷积是一种重要的数学运算,在信号处理中广泛应用。在计算机图形和图像处理领域,我们通常使用离散函数(例如图像)并应用离散形式的卷积来消除高频噪声、锐化细节或检测边缘。

卷积是对两个信号 f 和 g 的数学运算,定义为:

2d2a97481e923824d6cbb2d0e8ab08dc.png

在图像领域,我们可以将卷积想象为单个像素与其相邻像素之间的关系。这种关系主要应用搜索颜色变化、亮度差异和像素周期性等独特特征检测。

下图说明了使用小型 3 x 3 内核的卷积滤波器。滤波器被定义为一个矩阵,其中中心项对中心像素进行加权,其他项定义相邻像素的权重。我们也可以说 3×3 核的半径为 1,因为在卷积过程中只考虑“一环”邻域。在图像边界要定义卷积的行为,其中内核映射到图像外部未定义的值。

122c4887170aa7abf7283e964d58f821.png

使用 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 个临近像素被分组为一个方形窗口,窗口内的输入像素与内核进行卷积,产生一个像素值放置在输出图像中。重复此步骤直到图像结束。

6f0e5cd082df7a82fddff63a4adfcb3a.png

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);
}

运行后,测试图像将如下所示:

43e04c28d7bcd3d8b19320df846eea6b.png

Sobel 算子是边缘检测中最常用的算子之一。Sobel 算子使用两个 3×3 内核与原始图像进行卷积来计算导数的近似值 - 一个用于水平变化,另一个用于垂直变化。如果我们将 A 定义为源图像,G x和 G y是两个图像,每个点分别包含水平和垂直导数近似值,则计算如下:

0aec741fff13bdb76c73b55698e70611.png

通过前面的卷积函数,我们可以使用以下代码计算输出图像:

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}
};

卷积计算后得到的图像如下:

29568a0596da700d617461c2a5f3148d.png

正如所看到的,垂直和水平细节得到增强并且更易于观察。尽管它有帮助,但我们需要一个更独特的特征图像,仅代表边缘。

下一步将组合这两个图像并获得双向变化图。我们可以通过计算每个像素值的大小或强度以及当前像素与边缘线中的另一个像素链接的方向或角度来做到这一点。

在图像中的每个点,可以使用以下方法组合所得的近似值来给出幅度:

5234dac2bac74ddd911ba731c95e7031.png

以及使用的角度:

49d410a7c24e80cb42fec0595ddef03e.png

squareroot 和 atan2 函数都已在 HLS 命名空间中实现:

unsigned int magnitude = hls::sqrt(dx*dx + dy*dy);
int angle = hls::atan2(dx,dy);

结果是:

a818b39b487edf4cd82c5bddc2e295ec.png幅度 356c6550bae20436c5e81dd59017e2e3.png角度

我们已经得到边缘更加集中的图像。尽管如此,在多种形式的领域,边缘会变得更宽。我们需要使用一种称为非极大值抑制的技术来抑制这些错误边缘:

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;
}

现在边缘更薄、更简洁。

055a2a15d3c7b3e6ee53349615873a5e.png

实施

如前所述,输入图像以数据流的形式逐像素输入。为了应用卷积运算,我们需要将数据打包在 3 x 3 窗口下。可以使用具有两个缓冲区的架构来实现这一点,其中元素数量等于宽度,如果我们的输入图像:

3254e660f27ce4474117f0afb563fc02.png

这里将有两个辅助函数用于移动行缓冲区和滑动窗口:

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 添加到存储库:

026dafeb6af45fc899b87c9da1952fa1.png

添加已实现的 IP,其中一个 DMA 向其提供数据,另一个读取输出:

70f18d3da1ada67c21ed8812691ef888.png27a4d63c90cfa3e44bb42554c025bde7.png6a6aaeee44c506f8a51feba73ead780d.png

生成比特流后就可以验证功能。

生成的图像应与模拟图像相似。

现在我们需要实现一个直接从相机输入的架构。

第一个组件是 Znyq 处理系统和用于配置相机接口的 i2c 控制器:

794c64daf3d20615ebe039df4063938f.png63cf37b6865f61c0eee1283ad37043a0.pngd71ad1568c44fd63a6f1a0088da2564e.png

在图像流方面,需要一个 MIPI 控制器和一个 Demosaic IP 将流转换为 RGB24:

cb6b1ffb1b3a4514ad9df9c5e18475ab.png4864cad5a92d8c5d70fb32fb30ef60d7.png1a0291092c3d5236dea9fd9375b1a985.png

最后添加我们的图像处理IP和VDMA:

a220fe225c97e58c4bf67fc3d299f0e7.pngb4e2c110dfafc0f06f29b2bd9fee24e0.png

HOG

后续单独文章介绍,敬请关注~

SVM-支持向量机

在机器学习中,支持向量机(SVM,也称为支持向量网络)是具有相关学习算法的监督学习模型,用于分析用于分类和回归分析的数据。给定一组训练样本,每个样本都标记为属于两个类别中的一个或另一个,SVM 训练算法会构建一个模型,将新样本分配给一个类别或另一个类别,使其成为非概率二元线性分类器(尽管方法例如 Platt 缩放可以在概率分类设置中使用 SVM)。SVM 模型将示例表示为空间中的点,进行映射,以便将不同类别的示例划分为尽可能宽的清晰间隙。然后,新的示例被映射到同一空间,并根据它们所在的间隙一侧预测属于某个类别。

代码

https://github.com/cuciureansergiu/kv260_svm

相关文章