引言
FPGA比较擅长的是作定点数整数运算,那么对于带有小数部分的乘加运算。一般都选择先扩大若干倍,而后将运算结果缩小若干倍实现。
应用案例,真彩图转灰度图的心理学计算公式:
Gray = 0.299R + 0.587G + 0.114B
本文给出具体的设计、仿真源码(Verilog HDL)。结合MATLAB平台验证结果的准确性。
Verilog 编译仿真平台:Vivado 2018.3
MATLAB版本:2022a
设计
// ==============================================================================
// 功能描述:真彩图 转 灰度图
// 作 者:Xu Y. B.
// 计算公式:Gray = 0.299R + 0.587G + 0.114B
// 思 路:采用定点运算,先将系数扩大若干倍,最后的结果在缩小若干倍
// ==============================================================================
`timescale 1ns / 1ps
module RGB_2_GRAY_MDL #(
// ---- ---- ---- ---- 模块可重载参数 ---- ---- ---- ----
parameter P_PIXEL_DATA_WIDTH = 24, //像素数据位宽 ,RGB 分量等位宽
parameter P_SCALE_FACTOR = 1024, //系数缩放因子
parameter P_GRAY_DATA_WIDTH = 8 //输出灰度数据位宽
)(
// ---- ---- ---- ---- 模块端口 ---- ---- ---- ----
input I_OPR_CLK,
input I_OPR_RSTN,
input I_PIXEL_VAL,
input [P_PIXEL_DATA_WIDTH-1:0] I_PIXEL_DATA,// R:MSB B:LSB
output reg O_GRAY_VAL,
output [P_GRAY_DATA_WIDTH-1:0] O_GRAY_DATA
);
// ---- ---- ---- ---- 内部参数 ---- ---- ---- ----
localparam LP_RIGHT_SHIFT_VALUE = $clog2(P_SCALE_FACTOR);
integer INT_COEF_R = 0.299*P_SCALE_FACTOR;
integer INT_COEF_G = 0.587*P_SCALE_FACTOR;
integer INT_COEF_B = 0.114*P_SCALE_FACTOR;
localparam LP_GRAY_RES_WIDTH = FUNC_CAL_GRAY_RES_WIDTH(P_SCALE_FACTOR,P_PIXEL_DATA_WIDTH);
// ---- ---- ---- ---- 内部信号 ---- ---- ---- ----
reg [LP_GRAY_RES_WIDTH-1:0] R_GRAY_RES;
// ---- ---- ---- ---- 内部逻辑 ---- ---- ---- ----
always @ (posedge I_OPR_CLK)
begin
if(~I_OPR_RSTN)
begin
O_GRAY_VAL <= 1'b0;
R_GRAY_RES <= {LP_GRAY_RES_WIDTH{1'b0}};
end
else
begin
if(I_PIXEL_VAL)
begin
O_GRAY_VAL <= I_PIXEL_VAL;
R_GRAY_RES <= (I_PIXEL_DATA[P_PIXEL_DATA_WIDTH-1-:8] * INT_COEF_R
+I_PIXEL_DATA[P_PIXEL_DATA_WIDTH/3+:8] * INT_COEF_G
+I_PIXEL_DATA[0+:8] * INT_COEF_B)>>LP_RIGHT_SHIFT_VALUE;//此处组合逻辑延迟较大 , 待优化
end
else
begin
O_GRAY_VAL <= 1'b0;
R_GRAY_RES <= {LP_GRAY_RES_WIDTH{1'b0}};
end
end
end
assign O_GRAY_DATA = R_GRAY_RES[0+:P_GRAY_DATA_WIDTH];
// ---- ---- ---- ---- 函数/任务 ---- ---- ---- ----
// 计算最终结果的右移数值
function integer FUNC_CAL_GRAY_RES_WIDTH;
input integer SCALE_FACTOR;
input integer PIXEL_DATA_WIDTH;
begin
FUNC_CAL_GRAY_RES_WIDTH = PIXEL_DATA_WIDTH/3 + $clog2(SCALE_FACTOR) + 1 + 1;
end
endfunction
endmodule
设计源码遵循参数化程序设计的规范,可以设置缩放因子以及输出位宽等。在使用时,直接调用即可,不用调整内部逻辑和参数。
注意,3项乘加运算的逻辑在较高频率的时钟下,可能会存在建立时间/保持时间为例。
此处提供一个思路,采用流水线结构进行运算,即先分别计算3项的乘法,然后再计算3项的加法,计算加法时需要注意,由于相加的加数个数不等于2的整数次幂,故可计算两个加数的和,然后将第三个加数延迟一拍后再与和相加,得到最终的结果。如此一来,时序的问题可以缓解,但是带来的后果就是计算延迟增大。
仿真
// ==============================================================================
// 功能描述:测试 RGB_2_GRAY_MDL 模块
// 作 者:Xu Y. B.
// 计算公式:像素数据激励
// 思 路:
// ==============================================================================
`timescale 1ns / 1ps
module TB_RGB_2_GRAY_MDL();
parameter P_PIXEL_DATA_WIDTH = 24; //像素数据位宽 ,RGB 分量等位宽
parameter P_SCALE_FACTOR = 1024; //系数缩放因子
parameter P_GRAY_DATA_WIDTH = 8; //输出灰度数据位宽
reg I_OPR_CLK;
reg I_OPR_RSTN;
reg I_PIXEL_VAL;
reg [P_PIXEL_DATA_WIDTH-1:0] I_PIXEL_DATA;// R:MSB B:LSB
wire O_GRAY_VAL;
wire [P_GRAY_DATA_WIDTH-1:0] O_GRAY_DATA;
reg [P_PIXEL_DATA_WIDTH-1:0] R_PIXEL_DATA[24366:1];
// 产生激励时钟
initial I_OPR_CLK = 1'b0;
always #5 I_OPR_CLK = ~I_OPR_CLK;
// 数据读取
initial
begin
$readmemb("D:/A_Vivado_WorkSpace/DSP_BASIC_STUDY/MAT_FILE/IMAGE_PIXEL_DATA.txt",R_PIXEL_DATA);
end
// 复位、数据控制
initial
begin
I_OPR_RSTN = 1'b0;
// I_PIXEL_VAL = 0;
// I_PIXEL_DATA = 0;
#109;
I_OPR_RSTN = 1'b1;
// @(posedge I_OPR_CLK)
// I_PIXEL_VAL <= 1;
// I_PIXEL_DATA <= {8'd121,8'd99,8'd230};
// @(posedge I_OPR_CLK)
// I_PIXEL_VAL <= 1;
// I_PIXEL_DATA <= {8'd101,8'd90,8'd210};
// @(posedge I_OPR_CLK)
// I_PIXEL_VAL <= 1;
// I_PIXEL_DATA <= {8'd151,8'd69,8'd240};
// @(posedge I_OPR_CLK)
// I_PIXEL_VAL <= 1;
// I_PIXEL_DATA <= {8'd221,8'd109,8'd20};
@(negedge O_GRAY_VAL)
I_PIXEL_VAL <= 0;
#290;
$finish;
end
integer K=1;
always @ (posedge I_OPR_CLK)
begin
if(~I_OPR_RSTN)
begin
I_PIXEL_VAL <= 0;
I_PIXEL_DATA <= 0;
end
else if(K<=24366)
begin
I_PIXEL_VAL <= 1;
I_PIXEL_DATA <= R_PIXEL_DATA[K];
K <= K+1;
end
else
begin
K = K;
I_PIXEL_VAL <= 0;
I_PIXEL_DATA <= 0;
end
end
integer FILE_ID ;
initial
begin
FILE_ID = $fopen("D:/A_Vivado_WorkSpace/DSP_BASIC_STUDY/MAT_FILE/GRAY_DATA.txt","w+");
while(~O_GRAY_VAL | ~I_OPR_RSTN) @(posedge I_OPR_CLK);
while(O_GRAY_VAL)
begin
$fdisplayb(FILE_ID,O_GRAY_DATA);
@(posedge I_OPR_CLK);
end
$fclose(FILE_ID);
end
RGB_2_GRAY_MDL #(
.P_PIXEL_DATA_WIDTH(P_PIXEL_DATA_WIDTH),
.P_SCALE_FACTOR (P_SCALE_FACTOR),
.P_GRAY_DATA_WIDTH (P_GRAY_DATA_WIDTH)
) INST_RGB_2_GRAY_MDL (
.I_OPR_CLK (I_OPR_CLK),
.I_OPR_RSTN (I_OPR_RSTN),
.I_PIXEL_VAL (I_PIXEL_VAL),
.I_PIXEL_DATA (I_PIXEL_DATA),
.O_GRAY_VAL (O_GRAY_VAL),
.O_GRAY_DATA (O_GRAY_DATA)
);
endmodule
此部分的验证提供了两种方式:
1、已经被注释的部分,只是给几个数据送入模块中验证计算的准确性;
2、读取txt文件中的像素数据,送入模块中进行运算,运算结果保存至另一个txt文件中,然后对比MATLAB和FPGA计算的误差。(此部分具体的实现往下看)
对比验证
matlab 生成测试数据以及分析对比的源码:
%% ==================== 像素数据读取存储 ====================
% 作者:Xu Y. B.
% 说明:
% -1- 读取真彩图的RGB数据,拼接为24位二进制数,存入txt文件
% -2- 对真彩图作灰度处理,并与FPGA处理结果作对比
% =============================================================
%% CLEAR
clc;
clearvars;
close all;
%% 图片读取
FILE_PATH = "C:\Users\XYB\Pictures\高清壁纸Z\wallhaven-5758y8.jpg";
IMAGE_DATA = imread (FILE_PATH,"jpg");
figure;
imshow(IMAGE_DATA);
title("原真彩图")
% 图片截取
PIXEL_DATA = IMAGE_DATA(240:425,868:998,:);
figure;
subplot(121)
imshow(PIXEL_DATA);
title("截取的真彩图")
subplot(122)
IM2GRAY = im2gray(PIXEL_DATA);
imshow(IM2GRAY)
title("截取的真彩图转灰度图")
%% 数据转化存储
PIXEL_DATA_R = PIXEL_DATA(:,:,1);
PIXEL_DATA_G = PIXEL_DATA(:,:,2);
PIXEL_DATA_B = PIXEL_DATA(:,:,3);
PIXEL_DATA_R_BIN = dec2bin(reshape(PIXEL_DATA_R,[],1));
PIXEL_DATA_G_BIN = dec2bin(reshape(PIXEL_DATA_G,[],1));
PIXEL_DATA_B_BIN = dec2bin(reshape(PIXEL_DATA_B,[],1));
PIXEL_DATA_BIN = strcat(strcat(PIXEL_DATA_R_BIN,PIXEL_DATA_G_BIN),PIXEL_DATA_B_BIN);
WRITE_PATH = "D:\A_Vivado_WorkSpace\DSP_BASIC_STUDY\MAT_FILE\IMAGE_PIXEL_DATA.txt";
writematrix(PIXEL_DATA_BIN,WRITE_PATH,"WriteMode","overwrite","FileType","text")
%% FPGA处理结果读取
READ_PATH = "D:\A_Vivado_WorkSpace\DSP_BASIC_STUDY\MAT_FILE\GRAY_DATA.txt";
GRAY_DATA_FPGA = uint8(bin2dec(readmatrix(READ_PATH,"OutputType","string")));
GRAY_IMAG = reshape(GRAY_DATA_FPGA,186,[]);
figure;
subplot(121)
imshow(GRAY_IMAG)
title("FPGA处理结果")
subplot(122)
imshow(IM2GRAY)
title("MATLAB处理结果")
GRAY_PROC_ERR = uint8(abs(double(GRAY_IMAG)-double(IM2GRAY)));
figure;
mesh(GRAY_PROC_ERR);
title("FPGA MATLAB处理结果差异三维图")
figure;
imagesc(GRAY_PROC_ERR)
title("FPGA MATLAB处理结果差异平面")
原图:
图片截取以及MATLAB的灰度处理:
FPGA处理结果与MATLAB处理结果对比:
误差分析:
综合以上的分析验证结果,基于Verilog HDL 设计的模块,可以准确地将真彩图转化为灰度图,与MATLAB计算结果相差的最大值为1,大概率是由于四舍五入的精度问题导致。此问题只能通过增大缩放因子来缓解,比如将1024倍的缩放因子改为2048。
说明
以上所有代码中对应的文件路径都需要个性化更改,不可以直接复制粘贴使用,否则会报错:找不到路径对应的文件。
由于本人能力有限,如果有更好的想法或者在使用中遇到问题,都可以在评论区里留言交流~~~