接上篇文章,接着更新,本次任务是使用HLS实现图像旋转功能:
模块实现
图像旋转
对于输入图像坐标(X0,Y0),顺时针旋转角度a后坐标计算公式如下图所示。显然,旋转后的图像尺寸会变大。
在本次实验中,先预设好输出图像大小,将输入图像与输出图像的像素中心点设为坐标原点。对输出图像进行遍历,计算出输入图像对应的坐标点,如果该坐标值属于输入图像范围内,则取输入图像该点周围的四个像素点的值进行计算得到输出像素值。
模块综合结果——实现1
第一种实现方法在每一次输出计算中通过AXI接口依次访问所需的四个像素点(假设图像数据存储在片外存储器)。综合结果如下图。
可以看到在最内层循环中无法达到II=1,这是因为需要同时取出4个数进行运算,而接口无法同时对4个地址进行访存读取。
模块综合结果——实现2
因为在本次实验任务中,输入图像的尺寸为64×64,即使将其全部读入也不会使用过多的资源。因此,可以先将图像全部读入缓存,在进行图像旋转计算。而且在将顶层接口设置为AXI4-Master总线时,这种连续读入操作可以通过总线的连续突发等特性大大减少读入周期。
综合结果如下图。
可以看到,实现2方案可以实现II=1,模块整体Latency也大幅下降,但是BRAM资源的使用提升明显,这就是典型的空间换时间。
当然这里只是举例了两种最简单的实现方法,实际上也可以通过一些缓存设计方法达到II=1且片内存储占用较少的设计目的。
模块优化
在本实验中,HLS编译器自动对模块的流水、存储进行了优化或展开,并没有添加除接口指定以外的优化指令。
模块仿真实现
测试图像
打开画图随便画一个64×64的箭头图像作为测试图像。
仿真结果
与上一次图像平滑实验类似,使用opencv库编写测试激励。经过仿真,上述两种实现方式仿真结果一致。
联合仿真波形——实现1
如上图,红框中分别是一次图像旋转函数的执行过程,读写通道同时进行。
联合仿真波形——实现2
如上图,红框中分别是一次图像旋转函数的执行过程,可以看到读通道先执行了输入图像的读入流程,随后过了一段时间,写通道开始输出结果。
对比
对比实现1与实现2的联合仿真结果,实现2所用时间仅为实现1的三分之一,对于64*64的图像输入情况,实现2方法有着更小小的处理延时。
实现代码
HLS实现
下面是实现1方法的代码,实现2只需要稍作修改即可。
#include "rotate.h"
const ap_int<8> img_row = 96;
const ap_int<8> img_col = 96;
const ap_int<8> img_m = img_row >> 1;
const ap_int<8> img_n = img_col >> 1;
const ap_int<8> img_size = 64;
const ap_int<8> img_size2 = 32;
void Rotate(ap_uint<8> *img_in, ap_uint<8> *img_out, ap_fixed<16, 4> rad)
{
#pragma HLS INTERFACE mode=m_axi depth=4096 port=img_in
#pragma HLS INTERFACE mode=m_axi depth=9216 port=img_out
// ap_uint<8> img_buf[4096];
ap_fixed<8, 2> sina, cosa;
ap_fixed<16, 8, AP_RND, AP_SAT> xr, yr;
ap_int<8> xrr, yrr;
sina = hls::sin(rad);
cosa = hls::cos(rad);
// memcpy(img_buf, img_in, 4096);
for(ap_int<8> i = -img_m; i < img_m; i++)
{
for(ap_int<8> j = -img_n; j < img_n; j++)
{
xr = cosa * j + sina * i + img_size2;
yr = cosa * i - sina * j + img_size2;
if(xr >= 0 && xr < img_size && yr >= 0 && yr < img_size)
{
xrr = xr.to_ap_int();
yrr = yr.to_ap_int();
ap_uint<8> idx1 = img_in[yrr * img_size + xrr];
ap_uint<8> idx2 = img_in[yrr * img_size + xrr + 1];
ap_uint<8> idx3 = img_in[yrr * img_size + xrr + img_size];
ap_uint<8> idx4 = img_in[yrr * img_size + xrr + img_size + 1];
img_out[(i + img_m) * img_col + j + img_n] = (idx1 >> 2) + (idx2 >> 2) + (idx3 >> 2) + (idx4 >> 2);
}
}
}
}
仿真实现
#include "rotate.h"
#include "opencv2/opencv.hpp"
int main(void)
{
cv::Mat input = cv::imread("in.bmp", 0);
cv::Mat output15 = cv::Mat::zeros(96, 96, CV_8U);
cv::Mat output45 = cv::Mat::zeros(96, 96, CV_8U);
ap_fixed<16, 4> rad15 = 0.2618;//(15/180)*pi
ap_fixed<16, 4> rad45 = 0.7854;//(45/180)*pi
Rotate((ap_uint<8> *)input.data, (ap_uint<8> *)output15.data, rad15);
Rotate((ap_uint<8> *)input.data, (ap_uint<8> *)output45.data, rad45);
cv::imwrite("out15.jpg", output15);
cv::imwrite("out45.jpg", output45);
return 0;
}