接上篇文章,课程已经结束且出分,接着写后面几次任务内容实现。本次任务是使用HLS实现图像平滑滤波:
同样的,本文不涉及图像高斯平滑滤波的理论部分,只对HLS实现进行说明。
模块实现
图像高斯平滑
本次实验中使用高斯平滑方法,使用高斯平滑滤波核对输入图像进行卷积,为了方便计算与节省资源,对5×5高斯平滑滤波核先进行定点化,且输入图像不进行边缘零填充。
模块整体框架
为了实现任务中”每个周期可以处理一个像素点”的要求,突破访存瓶颈,本模块使用多个Stream流作为LineBuffer对输入图像行进行缓存,使得输入图像的多行数据可以对齐输入至卷积窗口中。结构类似下图:
卷积窗口处理
当卷积窗口前的LineBuffer都“流”满了数据即可以开始进行卷积操作,此处使用“UNROLL”优化指令配合模块整体的“PIPELINE”优化指令使得25个乘项计算并行,且其结果形成流水线加法树完成卷积乘加操作。
与上一次实验类似,需要考虑乘加法对数据位宽的扩宽,适当添加移位与截位操作。
模块接口处理
本模块中对输入图像与输出图像的读写最大频率都是每周期一个像素,为了节省资源以及最大化模块带宽,使用AXI4-Stream接口作为本模块输入输出接口。
注意此处,输入为512×512的8位单通道图像,输出为508×508的8位单通道图像,长宽的减少是因为没有对输入图像进行边缘补0操作。
模块综合结果
可以看到,模块内层Interval为1,符合设计任务需求。
模块仿真实现
输入图像
使用图像处理领域经典图像“Lena”作为输入图像。
输出图像
通过对比输入图像与输出图像,可以很明显的看出输出图像更模糊一些,模块实现平滑效果。
联合仿真
如截图1所示,输出接口上能够每一时钟周期都输出一个有效结果,达到设计要求;如截图2所示,输出结果有效在时间上比第一个有效输入数据存在滞后,这是因为需要等待输入数据填充满LineBuffer。
实现代码
HLS实现
#include "image_conv.h"
void Conv2d(ap_uint<8> img_in[512 * 512], ap_uint<8> img_out[508 * 508])
{
#pragma HLS INTERFACE axis port = img_in
#pragma HLS INTERFACE axis port = img_out
hls::stream<ap_uint<8>> LineBuf1, LineBuf2, LineBuf3, LineBuf4;
#pragma HLS STREAM depth=513 variable=LineBuf1
#pragma HLS STREAM depth=513 variable=LineBuf2
#pragma HLS STREAM depth=513 variable=LineBuf3
#pragma HLS STREAM depth=513 variable=LineBuf4
ap_uint<8> W[5][5] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#pragma HLS ARRAY_PARTITION variable=W complete dim=0
ap_uint<12> length = 512;
ap_uint<12> lineIdx[4] = {0, 0, 0, 0};
ap_uint<24> addr = 0;
ap_uint<8> kernel[5][5] = {
1 , 4 , 6 , 4 , 1 ,
4 , 16, 24, 16, 4 ,
6 , 24, 36, 24, 6 ,
4 , 16, 24, 16, 4 ,
1 , 4 , 6 , 4 , 1};
for(ap_uint<24> i = 0; i < length * length; i++)
{
#pragma HLS PIPELINE
W[0][4] = W[0][3];
W[0][3] = W[0][2];
W[0][2] = W[0][1];
W[0][1] = W[0][0];
W[0][0] = img_in[i];
if(lineIdx[0] == length)
{
W[1][4] = W[1][3];
W[1][3] = W[1][2];
W[1][2] = W[1][1];
W[1][1] = W[1][0];
W[1][0] = LineBuf1.read();
lineIdx[0] -= 1;
}
if(lineIdx[1] == length)
{
W[2][4] = W[2][3];
W[2][3] = W[2][2];
W[2][2] = W[2][1];
W[2][1] = W[2][0];
W[2][0] = LineBuf2.read();
lineIdx[1] -= 1;
}
if(lineIdx[2] == length)
{
W[3][4] = W[3][3];
W[3][3] = W[3][2];
W[3][2] = W[3][1];
W[3][1] = W[3][0];
W[3][0] = LineBuf3.read();
lineIdx[2] -= 1;
}
if(lineIdx[3] == length)
{
W[4][4] = W[4][3];
W[4][3] = W[4][2];
W[4][2] = W[4][1];
W[4][1] = W[4][0];
W[4][0] = LineBuf4.read();
lineIdx[3] -= 1;
}
LineBuf1.write(W[0][0]);
lineIdx[0] += 1;
LineBuf2.write(W[1][0]);
lineIdx[1] += 1;
LineBuf3.write(W[2][0]);
lineIdx[2] += 1;
LineBuf4.write(W[3][0]);
lineIdx[3] += 1;
if((i > 4 * length + 1) && ((i - 4) % length < length - 4))
{
ap_uint<16> tmp = 0;
for(ap_uint<8> r = 0; r < 4; r++)
{
#pragma HLS UNROLL
for(ap_uint<8> c = 0; c < 4; c++)
{
tmp += W[r][c] * kernel[r][c];
}
}
img_out[addr] = (ap_uint<8>)(tmp >> 8);
addr += 1;
}
}
}
仿真实现
#include "image_conv.h"
#include "opencv2/opencv.hpp"
int main(void)
{
cv::Mat a = cv::imread("Lena.bmp", 0);
cv::Mat b = cv::Mat::zeros(512 - 4, 512 - 4, CV_8U);
Conv2d((ap_uint<8> *)a.data, (ap_uint<8> *)b.data);
cv::imwrite("out.jpg", b);
return 0;
}
仿真代码需要配置opencv运行环境。