ZYNQ开发板上实施 基于卷积神经网络(CNN)或BP神经网络(MLP)的本地图像(minis和cifa10)识别 工程完整代码:包括Python网络训练,权值文件和测试文件导出,vivado,SDK,Vitis工程。 开发板适配两类:正点原子7020领航者v2或者赛灵思官方7020 zedboard。

先整模型训练(以LeNet为例):

import torch.nn as nn

class LeNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)  # CIFAR10是三通道
        self.pool = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = x.view(-1, 16*5*5)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        return self.fc3(x)

这里有个坑要注意:ZYNQ的FPGA部分处理浮点太奢侈,训练完记得做权重量化。用这个脚本把权重转成int8:

def quantize_weights(model):
    for param in model.parameters():
        param.data = torch.clamp(param.data, -1, 1)  # 限制范围
        param.data = (param.data * 127).round().byte()  # 转8位定点

导出模型权重到C头文件是必须操作:

def save_weights_to_h(model, filename):
    with open(filename, 'w') as f:
        f.write("#ifndef WEIGHTS_H\n#define WEIGHTS_H\n\n")
        for name, param in model.named_parameters():
            data = param.data.numpy().astype(np.int8).flatten()
            f.write(f"const int8_t {name.replace('.', '_')}[] = {{\n")
            f.write(','.join(map(str, data.tolist())))
            f.write("\n};\n\n")
        f.write("#endif\n")

硬件端部署才是重头戏。在Vitis里搞个加速器,直接上AXI-DMA传数据。PL部分用HLS写卷积加速器:

void conv2d(stream<ap_int<8>> &in, stream<ap_int<8>> &out,
            const int8_t *weight, int in_ch, int out_ch) {
    #pragma HLS PIPELINE II=1
    static ap_int<8> line_buffer[3][32][32];  // 行缓存
    // ...卷积计算逻辑...
}

注意这里用了行缓存策略,解决图像数据流处理时的时序问题。FPGA开发最讲究流水线设计,II=1确保每个时钟周期都能处理新数据。

ZYNQ开发板上实施 基于卷积神经网络(CNN)或BP神经网络(MLP)的本地图像(minis和cifa10)识别 工程完整代码:包括Python网络训练,权值文件和测试文件导出,vivado,SDK,Vitis工程。 开发板适配两类:正点原子7020领航者v2或者赛灵思官方7020 zedboard。

SDK端的C代码要处理图像输入:

// 从SD卡读取28x28 MNIST图片
uint8_t img_buf[784];
f_read(&fil, img_buf, 784, &bytesread);

// 归一化到-1~1范围并量化
int8_t input[784];
for(int i=0; i<784; i++){
    input[i] = (int8_t)((img_buf[i]/127.5) - 1) * 127);
}

// 调用硬件加速IP
Xil_DCacheFlush();  // 重要!保证数据同步
XConv_Start(&conv_inst);

测试时发现,正点原子板子的DDR3带宽比Zedboard高20%,所以同样的模型在领航者板子上能跑到35fps,而Zedboard只有29fps。如果遇到性能瓶颈,可以尝试以下优化:

  1. 将全连接层拆分成多级流水
  2. 对权重进行8位压缩存储
  3. 使用乒乓操作重叠数据传输和计算

最后上板实测,用原子哥的OV5640摄像头拍张图,通过串口打印识别结果:

Detected: 7 (98.2% confidence)
Inference time: 28ms

整个过程从训练到部署约需两天,其中80%时间花在硬件调试。记住:每次改完PL部分,一定要重新导出硬件平台到SDK,否则死都不知道怎么死的!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐