本文面向 FPGA 图像处理开发者,系统讲解 HLS 内置 OpenCV 库的语法规则、工程搭建、可综合代码写法与优化技巧,可直接用于技术博客发布。
前言
在 FPGA 嵌入式视觉开发中,从零手写图像处理算子(滤波、边缘检测、颜色转换)效率极低。Xilinx 提供的 hls_opencv 库,是标准 OpenCV 的硬件可综合子集,让开发者可以用几乎和软件 OpenCV 一致的语法编写算法,再通过 HLS 综合为硬件流水线,大幅缩短算法落地周期。
本文覆盖从基础语法到完整工程的全流程,附带可直接运行的 Sobel 边缘检测实战代码,以及高频踩坑解决方案。
一、基础认知:HLS 中的 OpenCV 是什么?
- 本质 :
hls_opencv.h是 Vivado HLS/Vitis HLS 内置的硬件友好型图像处理库,封装了 50+ 常用 OpenCV 算子,所有函数均可综合为 RTL 电路。 - 边界:仅支持基础图像处理算子,不包含 SIFT、DNN 等复杂高级功能;所有矩阵尺寸必须在编译期确定,不支持动态内存。
- 分工 :
- 可综合代码:只能使用
hls::命名空间下的算子与数据类型 - 仿真 Testbench:可使用标准
cv::命名空间读写图片,用于功能验证
- 可综合代码:只能使用
- 版本匹配:HLS 仿真依赖标准 OpenCV 库,需和 HLS 版本对应(如 2019.2 对应 OpenCV 3.4.x,2022.1 对应 OpenCV 4.x)。
二、环境准备
- 安装 Vivado HLS 或 Vitis HLS(推荐 2019.2 / 2020.2 稳定版)
- 安装对应版本的标准 OpenCV 库,仅用于 C 仿真,不参与硬件综合
- 工程中引入唯一核心头文件:
cpp
运行
#include "hls_opencv.h"
三、核心语法详解
3.1 核心数据类型
3.1.1 hls::Mat 图像矩阵
hls::Mat 是核心数据结构,对应标准 OpenCV 的 cv::Mat,但行列数作为模板参数,编译期固定。
定义语法:
cpp
运行
template<int ROWS, int COLS, int PIXEL_TYPE>
class Mat;
ROWS/COLS:图像行列数,必须是编译期常量(宏 /const int)PIXEL_TYPE:像素格式,常用类型对照表:
表格
| HLS 像素类型 | 对应标准 OpenCV 类型 | 含义 |
|---|---|---|
HLS_8UC1 |
CV_8UC1 |
8 位无符号单通道(灰度图) |
HLS_8UC3 |
CV_8UC3 |
8 位无符号三通道(BGR 彩色图) |
HLS_8UC4 |
CV_8UC4 |
8 位无符号四通道(带 Alpha 通道) |
HLS_16SC1 |
CV_16SC1 |
16 位有符号单通道 |
HLS_32FC1 |
CV_32FC1 |
32 位浮点单通道 |
常用成员函数:
cpp
运行
// 读取指定坐标像素值
void read(int row, int col, Scalar<N, T> &val);
// 写入指定坐标像素值
void write(int row, int col, Scalar<N, T> &val);
// 仿真专用:直接从 cv::Mat 导入数据
void write(const cv::Mat &cv_mat);
// 仿真专用:直接导出为 cv::Mat
cv::Mat read();
// 获取行列数
int rows;
int cols;
3.1.2 像素与辅助类型
- hls::Scalar :像素值类型,模板参数为通道数 + 数据类型,例如
hls::Scalar<3, unsigned char>对应三通道 8 位像素。 - hls::Size / hls::Point :尺寸、坐标类型,用法和
cv::Size/cv::Point完全一致。
3.1.3 AXI 视频流类型
FPGA 视频系统通常使用 AXI-Stream 接口传输图像数据,HLS 提供了流与 Mat 的互转能力:
cpp
运行
// 引入AXI流数据类型头文件
#include "ap_axi_sdata.h"
// 定义24位彩色AXI流像素:数据位宽24,附带同步信号
typedef ap_axiu<24, 1, 1, 1> axis_pixel_t;
typedef hls::stream<axis_pixel_t> axis_stream_t;
// 流 <-> Mat 转换函数
void AXIvideo2Mat(axis_stream_t &stream, Mat<...> &mat);
void Mat2AXIvideo(Mat<...> &mat, axis_stream_t &stream);
3.2 常用图像处理函数语法
所有函数均位于 hls:: 命名空间,参数格式与标准 OpenCV 高度一致,常量前缀为 HLS_(而非 CV_)。
颜色空间转换
cpp
运行
template<int ROWS, int COLS, int SRC_T, int DST_T>
void cvtColor(Mat<ROWS, COLS, SRC_T> &src,
Mat<ROWS, COLS, DST_T> &dst,
int code);
- 常用转换码:
HLS_BGR2GRAY、HLS_GRAY2BGR、HLS_BGR2HSV、HLS_BGR2RGB
图像滤波
cpp
运行
// 高斯模糊
template<int ROWS, int COLS, int T>
void GaussianBlur(Mat<ROWS, COLS, T> &src, Mat<ROWS, COLS, T> &dst,
Size ksize, double sigmaX, double sigmaY = 0);
// Sobel 边缘检测
template<int ROWS, int COLS, int SRC_T, int DST_T>
void Sobel(Mat<ROWS, COLS, SRC_T> &src, Mat<ROWS, COLS, DST_T> &dst,
int ddepth, int dx, int dy, int ksize = 3,
double scale = 1, double delta = 0);
// 中值滤波
template<int ROWS, int COLS, int T>
void medianBlur(Mat<ROWS, COLS, T> &src, Mat<ROWS, COLS, T> &dst, int ksize);
// 腐蚀 / 膨胀
template<int ROWS, int COLS, int T>
void erode(Mat<ROWS, COLS, T> &src, Mat<ROWS, COLS, T> &dst,
Mat kernel, Point anchor = Point(-1,-1), int iterations = 1);
template<int ROWS, int COLS, int T>
void dilate(Mat<ROWS, COLS, T> &src, Mat<ROWS, COLS, T> &dst,
Mat kernel, Point anchor = Point(-1,-1), int iterations = 1);
阈值与几何变换
cpp
运行
// 阈值分割
template<int ROWS, int COLS, int T>
double threshold(Mat<ROWS, COLS, T> &src, Mat<ROWS, COLS, T> &dst,
double thresh, double maxval, int type);
// 常用type:HLS_THRESH_BINARY、HLS_THRESH_OTSU、HLS_THRESH_BINARY_INV
// 图像缩放
template<int ROWS_SRC, int COLS_SRC, int ROWS_DST, int COLS_DST, int T>
void resize(Mat<ROWS_SRC, COLS_SRC, T> &src, Mat<ROWS_DST, COLS_DST, T> &dst,
int interpolation = HLS_INTER_LINEAR);
// 图像翻转
template<int ROWS, int COLS, int T>
void flip(Mat<ROWS, COLS, T> &src, Mat<ROWS, COLS, T> &dst, int flipCode);
特征检测
cpp
运行
// Canny 边缘检测(仅支持单通道8位输入输出)
template<int ROWS, int COLS>
void Canny(Mat<ROWS, COLS, HLS_8UC1> &src, Mat<ROWS, COLS, HLS_8UC1> &dst,
double low_thresh, double high_thresh,
int aperture_size = 3, bool L2gradient = false);
3.3 与标准 OpenCV 的核心差异
表格
| 特性 | 标准 OpenCV (cv::) | HLS OpenCV (hls::) |
|---|---|---|
| 矩阵尺寸 | 运行时动态分配 | 编译期模板固定,不可修改 |
| 命名空间 | cv:: |
hls:: |
| 常量前缀 | CV_ |
HLS_ |
| 可综合性 | 不可综合,仅软件运行 | 全部可综合为硬件电路 |
| 内存管理 | 动态内存分配 | 静态分配,无堆内存 |
| 执行模式 | CPU 串行执行 | 硬件并行流水线 |
四、从零搭建 HLS-OpenCV 工程
4.1 工程创建
- 打开 Vivado HLS,点击 Create New Project,设置工程名与路径。
- 选择目标 FPGA 器件(如 Zynq 系列
xc7z020clg484-1),设置顶层函数名(如img_sobel)。 - 点击 Finish 完成工程创建。
4.2 仿真环境配置
- 右键 Source 目录 → New File,添加可综合源码(
.h+.cpp)。 - 右键 Test Bench 目录 → New File,添加仿真测试代码。
- 打开 Project → Project Settings → Simulation :
- CFLAGS 中添加 OpenCV 头文件路径:
-I/你的OpenCV路径/include - LDFLAGS 中添加库链接:
-L/你的OpenCV路径/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs - Linux 环境可直接用:
pkg-config --cflags --libs opencv
- CFLAGS 中添加 OpenCV 头文件路径:
4.3 验证与导出
- 点击 Run C Simulation,执行软件仿真验证算法功能。
- 点击 Run Synthesis,执行硬件综合,查看 LUT、BRAM、DSP 资源占用与时序报告。
- 功能与时序达标后,点击 Export RTL,导出为 Vivado 可调用的 AXI 接口 IP 核。
五、实战示例:Sobel 边缘检测(流处理版)
以下为完整可运行代码,采用 AXI-Stream 接口,可直接对接摄像头、HDMI 等视频流。
5.1 可综合头文件 img_sobel.h
cpp
运行
#ifndef IMG_SOBEL_H
#define IMG_SOBEL_H
#include "hls_opencv.h"
#include "ap_axi_sdata.h"
// 图像尺寸(编译期固定,可根据需求修改)
#define IMG_ROWS 1080
#define IMG_COLS 1920
// AXI-Stream 类型定义
typedef ap_axiu<24, 1, 1, 1> axis_pixel_t;
typedef hls::stream<axis_pixel_t> axis_stream_t;
// 顶层函数声明
void img_sobel(axis_stream_t &src_stream, axis_stream_t &dst_stream);
#endif
5.2 可综合源码 img_sobel.cpp
cpp
运行
#include "img_sobel.h"
void img_sobel(axis_stream_t &src_stream, axis_stream_t &dst_stream)
{
// 综合指令:配置接口协议
#pragma HLS INTERFACE axis port=src_stream
#pragma HLS INTERFACE axis port=dst_stream
#pragma HLS INTERFACE s_axilite port=return bundle=CTRL
// 中间图像矩阵
hls::Mat<IMG_ROWS, IMG_COLS, HLS_8UC3> img_bgr;
hls::Mat<IMG_ROWS, IMG_COLS, HLS_8UC1> img_gray;
hls::Mat<IMG_ROWS, IMG_COLS, HLS_8UC1> img_sobel;
// 核心优化:数据流流水线,多步算子并行执行
#pragma HLS dataflow
// 1. AXI流转为Mat矩阵
hls::AXIvideo2Mat(src_stream, img_bgr);
// 2. BGR彩色转灰度
hls::cvtColor(img_bgr, img_gray, HLS_BGR2GRAY);
// 3. Sobel 边缘检测(XY方向合并,3x3核)
hls::Sobel(img_gray, img_sobel, HLS_8U, 1, 1, 3);
// 4. 结果转回AXI流输出
hls::Mat2AXIvideo(img_sobel, dst_stream);
}
5.3 仿真 Testbench tb_img_sobel.cpp
cpp
运行
#include "img_sobel.h"
#include <opencv2/opencv.hpp>
int main()
{
// 读取测试图片(仅仿真使用,不参与综合)
cv::Mat src_img = cv::imread("test.jpg");
if (src_img.empty()) {
printf("错误:无法读取测试图片\n");
return -1;
}
// 缩放至预设尺寸
cv::resize(src_img, src_img, cv::Size(IMG_COLS, IMG_ROWS));
// 构造输入输出流
axis_stream_t src_stream, dst_stream;
// cv::Mat → hls::Mat → AXI 流
hls::Mat<IMG_ROWS, IMG_COLS, HLS_8UC3> src_hls;
src_hls.write(src_img);
hls::Mat2AXIvideo(src_hls, src_stream);
// 调用硬件算法函数
img_sobel(src_stream, dst_stream);
// AXI 流 → hls::Mat → cv::Mat
hls::Mat<IMG_ROWS, IMG_COLS, HLS_8UC1> dst_hls;
hls::AXIvideo2Mat(dst_stream, dst_hls);
cv::Mat dst_img = dst_hls.read();
// 保存并显示结果
cv::imwrite("sobel_result.jpg", dst_img);
cv::imshow("输入图像", src_img);
cv::imshow("Sobel边缘检测结果", dst_img);
cv::waitKey(0);
printf("仿真完成,结果已保存\n");
return 0;
}
六、性能优化技巧
- DATAFLOW 数据流优化 :对多步图像处理流水线添加
#pragma HLS dataflow,让各算子并行执行,吞吐量提升数倍,是视频处理的核心优化手段。 - 行缓存替代全图缓存 :卷积类算子 HLS 会自动生成行缓存,自定义算子推荐使用
hls::LineBuffer+hls::Window,大幅降低 BRAM 占用。 - 循环流水线与展开 :自定义循环添加
#pragma HLS pipeline II=1实现单周期启动间隔,配合#pragma HLS unroll提升并行度。 - 定点化替代浮点:FPGA 浮点运算资源开销大,将阈值、卷积系数量化为定点整数,可显著减少 DSP 占用。
- 接口位宽适配:灰度图使用 8bit AXI 流,彩色图使用 24bit,避免总线带宽浪费。
七、常见问题与避坑指南
-
C 仿真正常,综合报 unsupported function
- 原因:可综合代码中调用了
cv::函数、printf、vector等不可综合语法。 - 解决:仅在 Testbench 中使用标准 OpenCV 和 C++ 标准库,顶层函数全程使用
hls::接口。
- 原因:可综合代码中调用了
-
BRAM 资源占用爆炸
- 原因:高分辨率下全图
hls::Mat会占用大量片上存储。 - 解决:启用 DATAFLOW 流式处理;将大图像拆分为块处理;自定义算子使用行缓存。
- 原因:高分辨率下全图
-
仿真链接错误 undefined reference to cv::xxx
- 原因:未正确配置 OpenCV 库链接路径,或版本不匹配。
- 解决:检查 LDFLAGS 配置,确保 OpenCV 大版本与 HLS 要求一致。
-
输出图像花屏、行错位
- 原因:AXI 流的
tuser、tlast同步信号不匹配,或行列尺寸不一致。 - 解决:使用官方
AXIvideo2Mat转换函数,确保输入输出流参数一致。
- 原因:AXI 流的
-
hls::Mat 不能用变量定义尺寸
- 原因:模板参数必须是编译期常量。
- 解决:用宏或
const int定义尺寸;多分辨率场景通过宏切换编译不同版本。
八、总结
hls_opencv 极大降低了 FPGA 图像处理的开发门槛,让开发者可以复用成熟的 OpenCV 算法,快速完成硬件加速落地。掌握基础语法后,可进一步探索自定义算子开发、多算子级联流水线优化,以及对接 VDMA、MIPI、HDMI 等硬件接口,搭建完整的嵌入式视觉系统。