Vivado HLS 调用 OpenCV 完全指南:语法详解 + 工程实战 + 踩坑总结

本文面向 FPGA 图像处理开发者,系统讲解 HLS 内置 OpenCV 库的语法规则、工程搭建、可综合代码写法与优化技巧,可直接用于技术博客发布。

前言

在 FPGA 嵌入式视觉开发中,从零手写图像处理算子(滤波、边缘检测、颜色转换)效率极低。Xilinx 提供的 hls_opencv 库,是标准 OpenCV 的硬件可综合子集,让开发者可以用几乎和软件 OpenCV 一致的语法编写算法,再通过 HLS 综合为硬件流水线,大幅缩短算法落地周期。

本文覆盖从基础语法到完整工程的全流程,附带可直接运行的 Sobel 边缘检测实战代码,以及高频踩坑解决方案。

一、基础认知:HLS 中的 OpenCV 是什么?

  1. 本质hls_opencv.h 是 Vivado HLS/Vitis HLS 内置的硬件友好型图像处理库,封装了 50+ 常用 OpenCV 算子,所有函数均可综合为 RTL 电路。
  2. 边界:仅支持基础图像处理算子,不包含 SIFT、DNN 等复杂高级功能;所有矩阵尺寸必须在编译期确定,不支持动态内存。
  3. 分工
    • 可综合代码:只能使用 hls:: 命名空间下的算子与数据类型
    • 仿真 Testbench:可使用标准 cv:: 命名空间读写图片,用于功能验证
  4. 版本匹配:HLS 仿真依赖标准 OpenCV 库,需和 HLS 版本对应(如 2019.2 对应 OpenCV 3.4.x,2022.1 对应 OpenCV 4.x)。

二、环境准备

  1. 安装 Vivado HLS 或 Vitis HLS(推荐 2019.2 / 2020.2 稳定版)
  2. 安装对应版本的标准 OpenCV 库,仅用于 C 仿真,不参与硬件综合
  3. 工程中引入唯一核心头文件:

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_BGR2GRAYHLS_GRAY2BGRHLS_BGR2HSVHLS_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 工程创建

  1. 打开 Vivado HLS,点击 Create New Project,设置工程名与路径。
  2. 选择目标 FPGA 器件(如 Zynq 系列 xc7z020clg484-1),设置顶层函数名(如 img_sobel)。
  3. 点击 Finish 完成工程创建。

4.2 仿真环境配置

  1. 右键 Source 目录 → New File,添加可综合源码(.h + .cpp)。
  2. 右键 Test Bench 目录 → New File,添加仿真测试代码。
  3. 打开 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

4.3 验证与导出

  1. 点击 Run C Simulation,执行软件仿真验证算法功能。
  2. 点击 Run Synthesis,执行硬件综合,查看 LUT、BRAM、DSP 资源占用与时序报告。
  3. 功能与时序达标后,点击 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;
}

六、性能优化技巧

  1. DATAFLOW 数据流优化 :对多步图像处理流水线添加 #pragma HLS dataflow,让各算子并行执行,吞吐量提升数倍,是视频处理的核心优化手段。
  2. 行缓存替代全图缓存 :卷积类算子 HLS 会自动生成行缓存,自定义算子推荐使用 hls::LineBuffer + hls::Window,大幅降低 BRAM 占用。
  3. 循环流水线与展开 :自定义循环添加 #pragma HLS pipeline II=1 实现单周期启动间隔,配合 #pragma HLS unroll 提升并行度。
  4. 定点化替代浮点:FPGA 浮点运算资源开销大,将阈值、卷积系数量化为定点整数,可显著减少 DSP 占用。
  5. 接口位宽适配:灰度图使用 8bit AXI 流,彩色图使用 24bit,避免总线带宽浪费。

七、常见问题与避坑指南

  1. C 仿真正常,综合报 unsupported function

    • 原因:可综合代码中调用了 cv:: 函数、printfvector 等不可综合语法。
    • 解决:仅在 Testbench 中使用标准 OpenCV 和 C++ 标准库,顶层函数全程使用 hls:: 接口。
  2. BRAM 资源占用爆炸

    • 原因:高分辨率下全图 hls::Mat 会占用大量片上存储。
    • 解决:启用 DATAFLOW 流式处理;将大图像拆分为块处理;自定义算子使用行缓存。
  3. 仿真链接错误 undefined reference to cv::xxx

    • 原因:未正确配置 OpenCV 库链接路径,或版本不匹配。
    • 解决:检查 LDFLAGS 配置,确保 OpenCV 大版本与 HLS 要求一致。
  4. 输出图像花屏、行错位

    • 原因:AXI 流的 tusertlast 同步信号不匹配,或行列尺寸不一致。
    • 解决:使用官方 AXIvideo2Mat 转换函数,确保输入输出流参数一致。
  5. hls::Mat 不能用变量定义尺寸

    • 原因:模板参数必须是编译期常量。
    • 解决:用宏或 const int 定义尺寸;多分辨率场景通过宏切换编译不同版本。

八、总结

hls_opencv 极大降低了 FPGA 图像处理的开发门槛,让开发者可以复用成熟的 OpenCV 算法,快速完成硬件加速落地。掌握基础语法后,可进一步探索自定义算子开发、多算子级联流水线优化,以及对接 VDMA、MIPI、HDMI 等硬件接口,搭建完整的嵌入式视觉系统。