FPGA实现LeNet数字识别(一)测试数据准备

以下是准备LeNet数字识别的单通道卷积网络测试数据的步骤和方法:

图片数据准备

由于LeNet神经网络在训练过程中对输入图像数据进行了归一化处理,因此我们在进行FPGA代码测试时也应对测试图片需要进行归一化处理,常见的做法是将像素值缩放到[0,1]或[-1,1]区间。对于灰度图像,测试图像数据如下。

权重导出为Q8.8格式 

 Q8.8格式是一种定点数表示方法,其中8位表示整数部分,8位表示小数部分。将浮点权重转换为Q8.8格式的步骤如下:

输入图像先被归一化到0~1浮点范围,通过乘以256并四舍五入转换为Q8.8格式。例如:

  • 0.5 → 0.5×256 = 128(即0x0080,表示+0.5)
  • 1.0 → 256(即0x0100,表示+1.0)

数值转换细节

np.clip确保数值在16位有符号整数范围内(-32768~32767)。负值示例:

  • -0.5 → -128(存储为0xFF80,二进制补码表示)

每个像素值写入HEX文件时采用4位十六进制小写格式,通过val & 0xFFFF确保只保留低16位。例如:

  • 255 → 0x00ff
  • -32768 → 0x8000

测试图片test_0_7.jpg转换数据代码如下:

# img2hex_q8_8.py  (28×28 灰度输入,输出 image.hex)
import numpy as np, cv2, argparse

ap = argparse.ArgumentParser()
ap.add_argument("--in",  required=True, help="原图像路径")
ap.add_argument("--out", default="image.hex")
args = ap.parse_args()

img = cv2.imread(args.__dict__["in"], cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img, (28,28), interpolation=cv2.INTER_AREA)
img = img.astype(np.float32) / 255.0                       # 0~1
img_q = np.round(img * 256).astype(np.int16)               # Q8.8
img_q = np.clip(img_q, -32768, 32767)

with open(args.out, "w") as f:
    for val in img_q.flatten():                            # 行优先
        f.write(f"{val & 0xFFFF:04x}\n")

print("写出 HEX OK:", args.out)

权重数据准备

通过解析LeNet模型的state_dict,获取每一层的权重参数。使用Python字典保存不同网络层的参数, 将 LeNet-2 模型的权重参数量化并导出为 HEX 格式文件。量化采用 Q8.8 定点数格式(16 位有符号整数),权重按层分离存储,便于硬件部署或嵌入式系统加载。

重点事项!!!

在FPGA测试过程中,权重数据与图像数据的对应关系至关重要,确保两者顺序一致才能正确完成卷积运算。权重文件的保存顺序通常遵循输出通道优先、输入通道次之、最后是卷积核大小的顺序。这种顺序设计是为了高效利用硬件资源,避免数据访问冲突。

权重保存顺序示例: 输出通道(O) → 输入通道(I) → 卷积核高度(H) → 卷积核宽度(W)

保存文件为:

  • conv1.weight 从形状 (6,1,5,5) 展平为 150 个元素
  • conv1.bias 6个元素
  • conv2.weight 从形状 (16,6,5,5) 展平为 2400 个元素
  • conv2.bias 16个元素
  • fc.weight 按行优先展开(10 类×256 维=2560 元素)
  • 最大池化层用于降低特征图的空间维度,不具备参数文件
#!/usr/bin/env python3
# export_lenet2_onefile_hex.py
# -----------------------------------------
# 将 LeNet-2 权重量化为 Q8.8,并按层导出单一 HEX 文件
# -----------------------------------------
import os, argparse, numpy as np, torch


def quantize_int16(t: torch.Tensor, scale=256):
    q = torch.round(t * scale).to(torch.int32)
    q = torch.clamp(q, -32768, 32767).to(torch.int16)
    return q.cpu().numpy()


def save_hex(arr: np.ndarray, path: str):
    arr = arr.flatten()
    with open(path, "w") as f:
        for v in arr:
            f.write(f"{(v & 0xFFFF):04x}\n")
    print(f"  ✔  {path:<20} {arr.size:>6} words")


def main(pth_path: str, out_dir: str = "hex"):
    os.makedirs(out_dir, exist_ok=True)
    state = torch.load(pth_path, map_location="cpu")

    # ---- Conv1 weight (6,1,5,5)  → 150 ----
    w1 = state["conv1.weight"].reshape(-1)          # OC-major
    save_hex(quantize_int16(w1), os.path.join(out_dir, "conv1.weight.hex"))
    save_hex(quantize_int16(state["conv1.bias"]),   os.path.join(out_dir, "conv1.bias.hex"))

    # ---- Conv2 weight (16,6,5,5) → 2400 ----
    w2 = state["conv2.weight"].reshape(-1)          # OC-major
    save_hex(quantize_int16(w2), os.path.join(out_dir, "conv2.weight.hex"))
    save_hex(quantize_int16(state["conv2.bias"]),   os.path.join(out_dir, "conv2.bias.hex"))

    # ---- FC weight (10,256) → 2560 ----
    w_fc = state["fc.weight"].reshape(-1)           # row-major (class0 行 → class9 行)
    save_hex(quantize_int16(w_fc), os.path.join(out_dir, "fc.weight.hex"))
    save_hex(quantize_int16(state["fc.bias"]), os.path.join(out_dir, "fc.bias.hex"))

    print("\n全部导出完成。")


if __name__ == "__main__":
    ap = argparse.ArgumentParser(description="导出 LeNet-2 HEX 权重(Q8.8)")
    ap.add_argument("model", help=".pth 权重文件")
    ap.add_argument("-o", "--out", default="hex", help="输出目录(默认 hex)")
    args = ap.parse_args()
    main(args.model, args.out)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ujs_probulic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值