保姆级教程十二:USB摄像头接入!ZYNQ+OpenCV+FPGA硬件加速图像处理实战(视觉终极篇)

保姆级教程十二:USB摄像头接入!ZYNQ+OpenCV+FPGA硬件加速图像处理实战(视觉终极篇)

文章目录

  • 保姆级教程十二:USB摄像头接入!ZYNQ+OpenCV+FPGA硬件加速图像处理实战(视觉终极篇)
    • [🛠️ 第一步:让 Linux 内核认识你的 USB 摄像头](#🛠️ 第一步:让 Linux 内核认识你的 USB 摄像头)
    • [📷 第二步:硬件连接与开机查岗](#📷 第二步:硬件连接与开机查岗)
    • [🐍 第三步:Python + OpenCV + FPGA 终极融合代码](#🐍 第三步:Python + OpenCV + FPGA 终极融合代码)
    • [🚀 第四步:运行并见证奇迹](#🚀 第四步:运行并见证奇迹)
    • [❓ 进阶答疑 (FAQ)](#❓ 进阶答疑 (FAQ))

如果你一路跟到了第十二篇,请先给自己鼓个掌!你即将超越 90% 的 ZYNQ 初学者。

很多新手学 FPGA 图像处理,卡在了怎么把摄像头的图像传给 FPGA。今天我们不搞复杂的 HDMI 或 MIPI 摄像头底层时序,我们就用最普通的、几十块钱的免驱 USB 摄像头(WebCam) ,结合上一篇配置好的 OpenCV,打造一条真正的 "硬加速机器视觉流水线"

我们要实现的功能:
USB摄像头拍照 -> Python(OpenCV)提取灰度图 -> DMA 瞬间搬运到 FPGA -> FPGA 硬件乘法器将所有像素变亮(乘2) -> DMA 搬回内存 -> Python 保存为图片!

发车!


🛠️ 第一步:让 Linux 内核认识你的 USB 摄像头

虽然 USB 摄像头在 Windows 下是免驱的,但在 PetaLinux 里,我们必须手动给内核"打个勾",开启 UVC (USB Video Class) 驱动。

  1. 在 Ubuntu 虚拟机的 PetaLinux 工程目录下,输入命令配置内核:

    bash 复制代码
    petalinux-config -c kernel
  2. 在弹出的蓝色菜单中,依次进入以下路径(按回车进入,按 Y 选中为 [*],按 Esc 退出当前层):

    • Device Drivers --->
    • Multimedia support --->
    • Media USB Adapters --->
    • 找到并勾选 [*] USB Video Class (UVC)
  3. 保存退出

  4. 重新编译系统并打包生成 BOOT.BINimage.ub

    bash 复制代码
    petalinux-build
    petalinux-package --boot --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot --force
  5. 把新系统拷入 SD 卡。


📷 第二步:硬件连接与开机查岗

  1. 找一个普通的 USB 摄像头 ,插到 ZYNQ-7030 开发板的 USB HOST / USB OTG 接口上。
    (注意:有些开发板的 USB 接口默认是 Device 模式,旁边可能有个跳线帽或拨码开关,需要拨到 HOST 模式,具体请看板子说明书)

  2. 给开发板上电,登录 root 账户。

  3. 在终端输入终极查岗命令:

    bash 复制代码
    ls /dev/video*

    如果你在屏幕上看到了 /dev/video0,恭喜你!! 你的摄像头已经成功被 ZYNQ 识别,可以开始视觉开发了!


🐍 第三步:Python + OpenCV + FPGA 终极融合代码

还记得我们在第 9 篇用 HLS 写的那个 硬件乘法器(所有数据乘以 2) 吗?

如果把这个乘法器用在图像上会发生什么?像素值乘以 2 = 画面亮度翻倍!

我们现在就用 Python 写一段极其优雅的代码,把图片扔给这个硬件去处理!

在开发板终端输入 vim fpga_vision.py,粘贴以下代码:

python 复制代码
import cv2
import numpy as np
import mmap
import os
import struct
import time

# --- 物理地址定义 (与第10篇一致) ---
DMA_REG_BASE = 0x40400000  # DMA 控制寄存器
HLS_REG_BASE = 0x40000000  # HLS 乘法器(亮度调节器)
DMA_MEM_BASE = 0x10000000  # 我们在设备树预留的火车站
TX_BUFFER    = DMA_MEM_BASE + 0x0000000 # 发送区
RX_BUFFER    = DMA_MEM_BASE + 0x0800000 # 接收区

# --- DMA 寄存器偏移量 ---
MM2S_CR, MM2S_SA, MM2S_LENGTH = 0x00, 0x18, 0x28
S2MM_CR, S2MM_DA, S2MM_LENGTH = 0x30, 0x48, 0x58

# ==========================================
# 1. OpenCV 捕获图像并预处理
# ==========================================
print("[1] 正在初始化 USB 摄像头...")
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cap.release() # 拍完一张就释放摄像头

if not ret:
    print("❌ 摄像头画面获取失败!")
    exit()

# 转换为灰度图,并调整为 512x512 大小
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
resized = cv2.resize(gray, (512, 512))

# ⚠️ 核心细节:我们的 HLS IP核接口是 32位 (AXI_VAL)
# OpenCV 的灰度图默认是 8位 (uint8),我们要把它转换成 32位 (uint32) 才能完美喂给 FPGA
img_32bit = resized.astype(np.uint32) 

# 保存一张原始图片作为对比
cv2.imwrite("original.jpg", resized)
print("[2] 图像预处理完成,尺寸: 512x512,准备发往 FPGA...")

# ==========================================
# 2. 内存映射 (打通 Python 与底层硬件)
# ==========================================
fd = os.open("/dev/mem", os.O_RDWR | os.O_SYNC)

# 映射控制寄存器 (4KB) 和 图像收发内存 (每块 1MB,512*512*4字节刚好是 1MB)
dma_reg = mmap.mmap(fd, 4096, offset=DMA_REG_BASE)
hls_reg = mmap.mmap(fd, 4096, offset=HLS_REG_BASE)
tx_mem  = mmap.mmap(fd, 1024*1024, offset=TX_BUFFER)
rx_mem  = mmap.mmap(fd, 1024*1024, offset=RX_BUFFER)

# ==========================================
# 3. 将图像装载到 DMA 发送区
# ==========================================
tx_mem.seek(0)
# tobytes() 会把 NumPy 矩阵直接变成底层字节流,瞬间写满 1MB 内存!
tx_mem.write(img_32bit.tobytes())

# ==========================================
# 4. 点火!唤醒 HLS 并启动 DMA 传输
# ==========================================
print("[3] 正在启动 FPGA 硬件加速器进行图像处理...")
# 唤醒 HLS 乘法器 (写入 0x81)
hls_reg.seek(0)
hls_reg.write(struct.pack('<I', 0x81))

def write_dma(offset, value):
    dma_reg.seek(offset)
    dma_reg.write(struct.pack('<I', value))

# 启动 DMA
write_dma(MM2S_CR, 1)
write_dma(S2MM_CR, 1)
# 告诉 DMA 地址在哪里
write_dma(MM2S_SA, TX_BUFFER)
write_dma(S2MM_DA, RX_BUFFER)

# 告诉 DMA 传输多大?512 * 512 个像素 * 4 字节 = 1048576 字节
transfer_bytes = 512 * 512 * 4
write_dma(S2MM_LENGTH, transfer_bytes) # 先开接收
write_dma(MM2S_LENGTH, transfer_bytes) # 再开发送

# 等待 FPGA 处理完毕 (对于 1MB 数据,DMA 传输其实只需不到 10 毫秒)
time.sleep(0.1)

# ==========================================
# 5. 回收处理完的图像数据
# ==========================================
print("[4] 从 FPGA 接收处理结果...")
rx_mem.seek(0)
result_bytes = rx_mem.read(transfer_bytes)

# 将底层字节流重新还原成 512x512 的 32 位矩阵
result_32bit = np.frombuffer(result_bytes, dtype=np.uint32).reshape(512, 512)

# ⚠️ 核心细节:像素乘 2 后可能超过 255 导致画面花屏(溢出)
# 我们使用 np.clip 把它限制在 0~255 之间,然后再转回 8位 图像
result_8bit = np.clip(result_32bit, 0, 255).astype(np.uint8)

# 保存 FPGA 加速处理后的图片
cv2.imwrite("fpga_processed.jpg", result_8bit)
print("[5] 大功告成!已保存 original.jpg 和 fpga_processed.jpg")

# 6. 打扫战场
dma_reg.close()
hls_reg.close()
tx_mem.close()
rx_mem.close()
os.close(fd)

🚀 第四步:运行并见证奇迹

在开发板终端直接运行:

bash 复制代码
python3 fpga_vision.py

等待两秒钟,你会看到屏幕打印:

text 复制代码
[1] 正在初始化 USB 摄像头...
[2] 图像预处理完成,尺寸: 512x512,准备发往 FPGA...
[3] 正在启动 FPGA 硬件加速器进行图像处理...
[4] 从 FPGA 接收处理结果...
[5] 大功告成!已保存 original.jpg 和 fpga_processed.jpg

此时输入 ls,你会发现当前目录下多了两张照片:

  • original.jpg:摄像头拍下的原图。
  • fpga_processed.jpg:经过 FPGA 硬件乘法器处理后的图。

怎么看这两张图呢?

利用我们之前强推的终端神器 MobaXterm !它的左侧边栏自带 SFTP 文件传输功能。

你只需要在左侧列表找到这两个 .jpg 文件,双击它们,或者直接拖拽到你的 Windows 电脑桌面上。

打开图片你会惊奇地发现:
fpga_processed.jpg 的整体画面亮度,完美地变成了 original.jpg 的两倍!


❓ 进阶答疑 (FAQ)

Q1:代码里为什么要进行 8位 和 32位 的来回转换?

  • 这正是软硬件协同最需要注意的数据位宽对齐。OpenCV 灰度图的每一个像素占 1 个字节(8-bit)。
  • 但是,我们在第 9 篇用 Vitis HLS 生成乘法器时,默认使用了 ap_axiu<32,1,1,1>(32-bit 的 AXI 流)。如果我们直接把 8 位的数据灌进去,FPGA 会把 4 个像素拼成 1 个数字去乘,画面出来的绝对是乱码花屏。
  • 企业级优化 :如果你觉得在 Python 里转 32 位浪费内存,正确的做法是回到 Vitis HLS,把接口改为 ap_axiu<8,1,1,1> 重新生成 IP 核。这样 DMA 就可以原封不动地搬运 OpenCV 的 8 位数据了!

Q2:如果我想让 FPGA 做更牛逼的操作(比如边缘检测),该怎么做?

  • 思路完全一样!你需要回到 Vitis HLS,利用 HLS 自带的 xfOpenCV 库(Xilinx 专门为 FPGA 优化的 OpenCV 硬件库),写一个 Sobel 边缘检测的 C++ 函数。
  • 导出 IP 核,替换掉现在的"乘法器",重新编译 Linux。
  • 此时你 Python 里的代码一行都不用改! DMA 送进去的依然是图像,收回来的就是 FPGA 瞬间抽取的硬件级边缘轮廓线了!

结语:

兄弟们,祝贺你!当你看到那两张图片的一瞬间,你已经跨越了"单片机玩家"的门槛,正式成为了一名**"具备异构计算能力的 AI/机器视觉底层工程师"**。

相关推荐
weiwei228442 小时前
基于Python的PC微信自动化探索:uiautomation+OpenCV+EasyOCR
opencv·easyocr·uiautomation
Java面试题总结3 小时前
Go图像处理基础: image包深度指南
图像处理·算法·golang
归零鸟3 小时前
全国重点风景名胜区地图
图像处理
欧阳子遥5 小时前
OpenCV 复杂背景下的轮廓提取
人工智能·opencv·计算机视觉
穿过锁扣的风5 小时前
【完整带注释版】图像直方图绘制教程(OpenCV+Matplotlib)
笔记·python·opencv
sali-tec5 小时前
C# 基于OpenCv的视觉工作流-章39-FL特征匹配
图像处理·人工智能·opencv·算法·计算机视觉
Pyeako5 小时前
基于Qt和PaddleOCR的工业视觉识别报警系统开发
人工智能·python·深度学习·数码相机·opencv·ocr·pyqt5
CoderIsArt6 小时前
FPGA-based 量子电路仿真
fpga开发
碎碎思16 小时前
升级版流水灯:用FPGA控制上千颗RGB LED
fpga开发