保姆级教程十二USB摄像头接入ZYNQOpenCVFPGA硬件加速图像处理实战视觉终极篇文章目录保姆级教程十二USB摄像头接入ZYNQOpenCVFPGA硬件加速图像处理实战视觉终极篇️ 第一步让 Linux 内核认识你的 USB 摄像头 第二步硬件连接与开机查岗 第三步Python OpenCV FPGA 终极融合代码 第四步运行并见证奇迹❓ 进阶答疑 (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)驱动。在 Ubuntu 虚拟机的 PetaLinux 工程目录下输入命令配置内核petalinux-config-ckernel在弹出的蓝色菜单中依次进入以下路径按回车进入按Y选中为[*]按Esc退出当前层Device Drivers ---Multimedia support ---Media USB Adapters ---找到并勾选[*] USB Video Class (UVC)保存退出。重新编译系统并打包生成BOOT.BIN和image.ubpetalinux-build petalinux-package--boot--fsblimages/linux/zynq_fsbl.elf--fpgaimages/linux/system.bit --u-boot--force把新系统拷入 SD 卡。 第二步硬件连接与开机查岗找一个普通的USB 摄像头插到 ZYNQ-7030 开发板的USB HOST / USB OTG接口上。(注意有些开发板的 USB 接口默认是 Device 模式旁边可能有个跳线帽或拨码开关需要拨到 HOST 模式具体请看板子说明书)给开发板上电登录root账户。在终端输入终极查岗命令ls/dev/video*如果你在屏幕上看到了/dev/video0恭喜你你的摄像头已经成功被 ZYNQ 识别可以开始视觉开发了 第三步Python OpenCV FPGA 终极融合代码还记得我们在第 9 篇用 HLS 写的那个硬件乘法器所有数据乘以 2吗如果把这个乘法器用在图像上会发生什么像素值乘以 2 画面亮度翻倍我们现在就用 Python 写一段极其优雅的代码把图片扔给这个硬件去处理在开发板终端输入vim fpga_vision.py粘贴以下代码importcv2importnumpyasnpimportmmapimportosimportstructimporttime# --- 物理地址定义 (与第10篇一致) ---DMA_REG_BASE0x40400000# DMA 控制寄存器HLS_REG_BASE0x40000000# HLS 乘法器(亮度调节器)DMA_MEM_BASE0x10000000# 我们在设备树预留的火车站TX_BUFFERDMA_MEM_BASE0x0000000# 发送区RX_BUFFERDMA_MEM_BASE0x0800000# 接收区# --- DMA 寄存器偏移量 ---MM2S_CR,MM2S_SA,MM2S_LENGTH0x00,0x18,0x28S2MM_CR,S2MM_DA,S2MM_LENGTH0x30,0x48,0x58# # 1. OpenCV 捕获图像并预处理# print([1] 正在初始化 USB 摄像头...)capcv2.VideoCapture(0)ret,framecap.read()cap.release()# 拍完一张就释放摄像头ifnotret:print(❌ 摄像头画面获取失败)exit()# 转换为灰度图并调整为 512x512 大小graycv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)resizedcv2.resize(gray,(512,512))# ⚠️ 核心细节我们的 HLS IP核接口是 32位 (AXI_VAL)# OpenCV 的灰度图默认是 8位 (uint8)我们要把它转换成 32位 (uint32) 才能完美喂给 FPGAimg_32bitresized.astype(np.uint32)# 保存一张原始图片作为对比cv2.imwrite(original.jpg,resized)print([2] 图像预处理完成尺寸: 512x512准备发往 FPGA...)# # 2. 内存映射 (打通 Python 与底层硬件)# fdos.open(/dev/mem,os.O_RDWR|os.O_SYNC)# 映射控制寄存器 (4KB) 和 图像收发内存 (每块 1MB512*512*4字节刚好是 1MB)dma_regmmap.mmap(fd,4096,offsetDMA_REG_BASE)hls_regmmap.mmap(fd,4096,offsetHLS_REG_BASE)tx_memmmap.mmap(fd,1024*1024,offsetTX_BUFFER)rx_memmmap.mmap(fd,1024*1024,offsetRX_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))defwrite_dma(offset,value):dma_reg.seek(offset)dma_reg.write(struct.pack(I,value))# 启动 DMAwrite_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_bytes512*512*4write_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_bytesrx_mem.read(transfer_bytes)# 将底层字节流重新还原成 512x512 的 32 位矩阵result_32bitnp.frombuffer(result_bytes,dtypenp.uint32).reshape(512,512)# ⚠️ 核心细节像素乘 2 后可能超过 255 导致画面花屏(溢出)# 我们使用 np.clip 把它限制在 0~255 之间然后再转回 8位 图像result_8bitnp.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) 第四步运行并见证奇迹在开发板终端直接运行python3 fpga_vision.py等待两秒钟你会看到屏幕打印[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_axiu32,1,1,132-bit 的 AXI 流。如果我们直接把 8 位的数据灌进去FPGA 会把 4 个像素拼成 1 个数字去乘画面出来的绝对是乱码花屏。企业级优化如果你觉得在 Python 里转 32 位浪费内存正确的做法是回到 Vitis HLS把接口改为ap_axiu8,1,1,1重新生成 IP 核。这样 DMA 就可以原封不动地搬运 OpenCV 的 8 位数据了Q2如果我想让 FPGA 做更牛逼的操作比如边缘检测该怎么做思路完全一样你需要回到 Vitis HLS利用 HLS 自带的xfOpenCV库Xilinx 专门为 FPGA 优化的 OpenCV 硬件库写一个 Sobel 边缘检测的 C 函数。导出 IP 核替换掉现在的“乘法器”重新编译 Linux。此时你 Python 里的代码一行都不用改DMA 送进去的依然是图像收回来的就是 FPGA 瞬间抽取的硬件级边缘轮廓线了结语兄弟们祝贺你当你看到那两张图片的一瞬间你已经跨越了“单片机玩家”的门槛正式成为了一名**“具备异构计算能力的 AI/机器视觉底层工程师”**。
保姆级教程十二:USB摄像头接入!ZYNQ+OpenCV+FPGA硬件加速图像处理实战(视觉终极篇)
保姆级教程十二USB摄像头接入ZYNQOpenCVFPGA硬件加速图像处理实战视觉终极篇文章目录保姆级教程十二USB摄像头接入ZYNQOpenCVFPGA硬件加速图像处理实战视觉终极篇️ 第一步让 Linux 内核认识你的 USB 摄像头 第二步硬件连接与开机查岗 第三步Python OpenCV FPGA 终极融合代码 第四步运行并见证奇迹❓ 进阶答疑 (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)驱动。在 Ubuntu 虚拟机的 PetaLinux 工程目录下输入命令配置内核petalinux-config-ckernel在弹出的蓝色菜单中依次进入以下路径按回车进入按Y选中为[*]按Esc退出当前层Device Drivers ---Multimedia support ---Media USB Adapters ---找到并勾选[*] USB Video Class (UVC)保存退出。重新编译系统并打包生成BOOT.BIN和image.ubpetalinux-build petalinux-package--boot--fsblimages/linux/zynq_fsbl.elf--fpgaimages/linux/system.bit --u-boot--force把新系统拷入 SD 卡。 第二步硬件连接与开机查岗找一个普通的USB 摄像头插到 ZYNQ-7030 开发板的USB HOST / USB OTG接口上。(注意有些开发板的 USB 接口默认是 Device 模式旁边可能有个跳线帽或拨码开关需要拨到 HOST 模式具体请看板子说明书)给开发板上电登录root账户。在终端输入终极查岗命令ls/dev/video*如果你在屏幕上看到了/dev/video0恭喜你你的摄像头已经成功被 ZYNQ 识别可以开始视觉开发了 第三步Python OpenCV FPGA 终极融合代码还记得我们在第 9 篇用 HLS 写的那个硬件乘法器所有数据乘以 2吗如果把这个乘法器用在图像上会发生什么像素值乘以 2 画面亮度翻倍我们现在就用 Python 写一段极其优雅的代码把图片扔给这个硬件去处理在开发板终端输入vim fpga_vision.py粘贴以下代码importcv2importnumpyasnpimportmmapimportosimportstructimporttime# --- 物理地址定义 (与第10篇一致) ---DMA_REG_BASE0x40400000# DMA 控制寄存器HLS_REG_BASE0x40000000# HLS 乘法器(亮度调节器)DMA_MEM_BASE0x10000000# 我们在设备树预留的火车站TX_BUFFERDMA_MEM_BASE0x0000000# 发送区RX_BUFFERDMA_MEM_BASE0x0800000# 接收区# --- DMA 寄存器偏移量 ---MM2S_CR,MM2S_SA,MM2S_LENGTH0x00,0x18,0x28S2MM_CR,S2MM_DA,S2MM_LENGTH0x30,0x48,0x58# # 1. OpenCV 捕获图像并预处理# print([1] 正在初始化 USB 摄像头...)capcv2.VideoCapture(0)ret,framecap.read()cap.release()# 拍完一张就释放摄像头ifnotret:print(❌ 摄像头画面获取失败)exit()# 转换为灰度图并调整为 512x512 大小graycv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)resizedcv2.resize(gray,(512,512))# ⚠️ 核心细节我们的 HLS IP核接口是 32位 (AXI_VAL)# OpenCV 的灰度图默认是 8位 (uint8)我们要把它转换成 32位 (uint32) 才能完美喂给 FPGAimg_32bitresized.astype(np.uint32)# 保存一张原始图片作为对比cv2.imwrite(original.jpg,resized)print([2] 图像预处理完成尺寸: 512x512准备发往 FPGA...)# # 2. 内存映射 (打通 Python 与底层硬件)# fdos.open(/dev/mem,os.O_RDWR|os.O_SYNC)# 映射控制寄存器 (4KB) 和 图像收发内存 (每块 1MB512*512*4字节刚好是 1MB)dma_regmmap.mmap(fd,4096,offsetDMA_REG_BASE)hls_regmmap.mmap(fd,4096,offsetHLS_REG_BASE)tx_memmmap.mmap(fd,1024*1024,offsetTX_BUFFER)rx_memmmap.mmap(fd,1024*1024,offsetRX_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))defwrite_dma(offset,value):dma_reg.seek(offset)dma_reg.write(struct.pack(I,value))# 启动 DMAwrite_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_bytes512*512*4write_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_bytesrx_mem.read(transfer_bytes)# 将底层字节流重新还原成 512x512 的 32 位矩阵result_32bitnp.frombuffer(result_bytes,dtypenp.uint32).reshape(512,512)# ⚠️ 核心细节像素乘 2 后可能超过 255 导致画面花屏(溢出)# 我们使用 np.clip 把它限制在 0~255 之间然后再转回 8位 图像result_8bitnp.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) 第四步运行并见证奇迹在开发板终端直接运行python3 fpga_vision.py等待两秒钟你会看到屏幕打印[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_axiu32,1,1,132-bit 的 AXI 流。如果我们直接把 8 位的数据灌进去FPGA 会把 4 个像素拼成 1 个数字去乘画面出来的绝对是乱码花屏。企业级优化如果你觉得在 Python 里转 32 位浪费内存正确的做法是回到 Vitis HLS把接口改为ap_axiu8,1,1,1重新生成 IP 核。这样 DMA 就可以原封不动地搬运 OpenCV 的 8 位数据了Q2如果我想让 FPGA 做更牛逼的操作比如边缘检测该怎么做思路完全一样你需要回到 Vitis HLS利用 HLS 自带的xfOpenCV库Xilinx 专门为 FPGA 优化的 OpenCV 硬件库写一个 Sobel 边缘检测的 C 函数。导出 IP 核替换掉现在的“乘法器”重新编译 Linux。此时你 Python 里的代码一行都不用改DMA 送进去的依然是图像收回来的就是 FPGA 瞬间抽取的硬件级边缘轮廓线了结语兄弟们祝贺你当你看到那两张图片的一瞬间你已经跨越了“单片机玩家”的门槛正式成为了一名**“具备异构计算能力的 AI/机器视觉底层工程师”**。