在相机技术中,有三大关键技术:自动对焦(AF)、自动曝光(AE)和自动白平衡(AWB),并称3A技术。白平衡是摄像领域一个非常重要的概念,通过它可以解决色彩还原和色调处理的一系列问题。
许多人在使用数码摄像机拍摄的时候都会遇到这样的问题,在日光灯的房间里拍摄的影像会显得发绿,在室内钨丝灯光下拍摄出来的景物会偏黄,而在日光阴影处拍摄到的照片则莫名其妙地偏蓝,其原因就在于白平衡设置上,如下图所示。本文主要记录了通过FPGA实现自动白平衡算法的过程。
自动白平衡算法有很多种,如:灰度世界法、完美反射法和动态阈值法等。考虑到最终需要通过FPGA实现,因此算法的计算过程简捷是十分必要的。在上面提到的算法中,灰度世界法实现起来不仅过程简捷而且计算量小,所以最终采用灰度世界法作为自动白平衡实现方法。
灰度世界法假设:对于一幅有着丰富色彩的图片,图像上 R,G,B R , G , B 三个通道的平均值应该等于值 K K 。对于值 K K 的选择有多种方法,一种方法是将其定义为各通道最大值的一半,即 K=128 K = 128 ,另一种方法是将待处理图片三个通道的均值作为 K K 。为了计算简单,本文取第一种方法来确定 K K 值,即 K=128 K = 128 。灰度世界法实现过程如下:
分别计算每个通道的增益:
将图像中所有像素乘上增益计算得到新像素:
在进行FPGA设计之前需要先通过MATLAB进行算法仿真,弄清楚算法的效果和执行过程。为了得知算法的每一步的执行过程,在通过MATLAB执行的时候没有调用图像处理函数,而是利用最基本的语句来实现。通过MATLAB实现时可以拆分为一下步骤:
下面是具体实现:
首先读取图像数据并显示。
rgb_data=imread('F:/test1.jpg');
[Height,Length] = size(rgb_data(:,:,1));
subplot(1,2,1);
imshow(rgb_data);
title('原图像');
然后累加每个通道的灰度值。
Rsum = 0;
Gsum = 0;
Bsum = 0;
Rsum = double(Rsum);
Gsum = double(Gsum);
Bsum = double(Bsum);
for i = 1 : Height
for j = 1 : Length
Rsum = Rsum + double(rgb_data(i,j,1));
Gsum = Gsum + double(rgb_data(i,j,2));
Bsum = Bsum + double(rgb_data(i,j,3));
end
end
得到累加值之后除以图像的分辨率得到每个通道的均值。
Raver = Rsum / (Height*Length);
Gaver = Gsum / (Height*Length);
Baver = Bsum / (Height*Length);
然后用128除以每个通道的均值得到每个通道的增益。
Rgain = 128 / Raver;
Ggain = 128 / Gaver;
Bgain = 128 / Baver;
最后将原图像每个通道的像素同每个通道的增益相乘得到新图像并显示。
rgb_data(:,:,1) = rgb_data(:,:,1) * Rgain;
rgb_data(:,:,2) = rgb_data(:,:,2) * Ggain;
rgb_data(:,:,3) = rgb_data(:,:,3) * Bgain;
subplot(1,2,2);
imshow(rgb_data);
title('处理后图像');
至此,MATLAB程序就写完了,效果如下图所示,计算得到的三个通道的增益为:
这里将三个通道的增益贴出是为了后面进行FPGA设计时得出的增益要和MATLAB得出的结果一致。
在进行Modelsim仿真的时候需要读取图像数据,而Modelsim中无法直接读取图像,因此需要将图像数据转换成txt文件。这里通过matlab将RGB图片中三个通道的数据分别存到三个txt文件中以供Modelsim读取,需要注意的是在modelsim中读取到内存中的数据为16进制,因此在写入的时候需要将十进制转换为16进制,matlab代码如下:
RGB_data = imread('test.jpg');
R_data = RGB_data(:,:,1);
G_data = RGB_data(:,:,2);
B_data = RGB_data(:,:,3);
[Height,Length] = size(R_data);
R_hex = dec2hex(R_data);
G_hex = dec2hex(G_data);
B_hex = dec2hex(B_data);
dlmwrite('image2txt_R.txt',R_hex,'delimiter','','newline','pc');
dlmwrite('image2txt_G.txt',G_hex,'delimiter','','newline','pc');
dlmwrite('image2txt_B.txt',B_hex,'delimiter','','newline','pc');
运行程序之后在程序所在目录下会生成三个txt文件,分别对应图像的三个RGB通道。
经过了MATLAB仿真之后已经对算法的执行流程有深入了解,开始通过FPGA来实现整个算法。在通过FPGA实现的时候算法的思想同MATLAB中一致,但是实现的流程和方式却有很大的区别,原因在于通过Verilog实现出来的是数字电路,需要站在数字逻辑的角度去实现。
首先定义输入输出接口,如下表所示。
类型 | 信号名 | 作用 |
---|---|---|
reg | Clk | 时钟输入 |
reg | Rst | 复位信号,低电平有效 |
reg | f_end | 图像帧结束信号 |
reg | l_end | 图像行数据结束信号 |
reg | pix_en | 图像数据有效信号,为高表示该像素有效 |
reg | iR、iG、iB | 图像三个输入数据通道,分别对应R、G、B,每个通道八位 |
wire | f_end_o | 图像帧结束信号输出 |
wire | l_end_o | 图像行数据结束信号输出 |
wire | pix_en_o | 图像数据有效信号输出 |
wire | oR、oG、oB | 图像三个输出数据通道,分别对应R、G、B,每个通道八位 |
module awb_design(
input wire Clk,
input wire Rst,
input wire f_end,
input wire l_end,
input wire pix_en,
input wire[7:0] iR,
input wire[7:0] iG,
input wire[7:0] iB,
output reg f_end_o,
output reg l_end_o,
output reg pix_en_o,
output wire[7:0] oR,
output wire[7:0] oG,
output wire[7:0] oB
);
由于帧结束信号是一个长周期的脉冲信号,为10个时钟周期的高电平,在最终输出结果的时候需要对结束信号进行单次触发,也就是帧结束信号过来的时候执行一次输出操作,因此需要提取帧结束信号的边缘。首先对帧结束信号锁两拍,也就是移位两次,然后对两次位移信号进行与或操作,时序图和代码如下。
从输出结果来看,每来一次帧结束信号f_end就会产生一个时钟周期的f_end_pluse信号,对于always进程来说,f_end_pluse信号只会触发一次always执行。
reg f_end_r0;
reg f_end_r1;
wire f_end_pulse;
always @ (posedge Clk or negedge Rst)
begin
if(!Rst)
begin
f_end_r0 <= 1'b0;
f_end_r1 <= 1'b0;
end
else
begin
f_end_r0 <= f_end;
f_end_r1 <= f_end_r0;
end
end
assign f_end_pulse = f_end_r0 & (~f_end_r1);
为了计算三个通道的灰度平均值,需要累加三个通道的灰度值。定义三个累加器对三个通道中每个像素灰度值进行累加。在这里需要注意的是累加器的位宽需要计算好,不能有溢出的情况发生,像素的个数乘以最大灰度值小于累加器即可。
reg[31:0] AccR;
reg[31:0] AccG;
reg[31:0] AccB;
always @ (posedge Clk or negedge Rst)
begin
if(!Rst)
begin
AccR <= 32'd0;
AccG <= 32'd0;
AccB <= 32'd0;
end
else
begin
if(pix_en == 1)
begin
AccR <= AccR + iR;
AccG <= AccG + iG;
AccB <= AccB + iB;
end
else if(f_end_r1 == 1)
begin
AccR <= 32'd0;
AccG <= 32'd0;
AccB <= 32'd0;
end
else
begin
AccR <= AccR;
AccG <= AccG;
AccB <= AccB;
end
end
end
得到三个通道灰度累加值之后除以像素个数就可以得到每个通道的均值,但是在FPGA中要实现除法是一件很麻烦的事情,费时费力还费资源。除法不好实现但是乘法对于FPGA来说要简单的多,在verilog中可以直接采用和C语言一样的乘法运算符来实现,对于xilinx的FPGA来说,最终综合的时候会将乘法通过FPGA内部的DSP运算单元实现。所以在这里我们将除法转换为乘法,通过乘以像素个数的倒数来实现计算每个通道的均值。
由于倒数为小数,因此这里涉及到了定点数的计算。所谓定点数就是将小数位数固定,这样就会产生误差,因此我们需要选择合适的小数位数来保证误差足够小。以本设计为例,定点数为32位,1位整数位,31位小数位。计算如下:
测试图片的分辨率为:
倒数为:
将倒数转换为16进制,并截取高32位得到的值为:
然后将这个数转换为十进制小数为:
从这里不难看出经过截位之后会产生一定的误差,但是只要这个误差在我们的接受范围以内就能够接受。实现代码如下,首先定义了存放均值的临时变量AverageR_temp、AverageG_temp和AverageB_temp,注意这三个变量的位宽,由于是乘法,因此存储乘法结果的变量的位宽定义为两个乘数位宽的和。累加器的位宽为32位,图像分辨率的倒数位宽也为32位,因此这三个变量定义为64位。这样做的目的是保证计算结果不会发生溢出错误。
该部分的计算发生在帧结束信号f_end为高电平期间,当f_end为高电平是表明一帧图像数据传输完毕,相应的三个通道灰度值累加完毕。由于求得的平均值最大不会超过255,因此舍弃低31位小数位,只保留8位整数位,即取计算结果的31~38位作为求出的平均值。这部分是通过组合逻辑实现,为了防止路径延时大于时钟周期,我们在always中实现截位操作,目的是通过寄存器作一级缓存。
parameter IamgeSize = 32'h00003D2E; // 1/ImageSize = 0.0000072983117,1位整数位,31位小数位
wire[63:0] AverageR_temp;
wire[63:0] AverageG_temp;
wire[63:0] AverageB_temp;
reg[7:0] AverageR; //R通道均值
reg[7:0] AverageG; //G通道均值
reg[7:0] AverageB; //B通道均值
assign AverageR_temp = (f_end == 1) ? (AccR * IamgeSize) : 64'd0;
assign AverageG_temp = (f_end == 1) ? (AccG * IamgeSize) : 64'd0;
assign AverageB_temp = (f_end == 1) ? (AccB * IamgeSize) : 64'd0;
always @ (posedge Clk or negedge Rst) //寄存器缓存
begin
if(!Rst)
begin
AverageR <= 8'd0;
AverageG <= 8'd0;
AverageB <= 8'd0;
end
else
begin
AverageR <= AverageR_temp[38:31]; //截取整数位
AverageG <= AverageG_temp[38:31];
AverageB <= AverageB_temp[38:31];
end
end
得到每个通道的平均值之后只需要用128除以平均值就可以得到每个通道的增益了,但是这里遇到的问题和上面的一样,我们还是将除法转换为乘法。即用128除以每个通道平均值的倒数,而这里通道平均值的范围在1~255,已经不是一个定值,所以这里做了一个查找表,把1~255的倒数存在一张表中,输入1~255整数就可以得到其倒数。查找表通过单独的模块Reciprocal实现,这部分会在后面详细说明。代码如下。
wire[31:0] AverageR_recip; //R通道倒数
wire[31:0] AverageG_recip; //G通道倒数
wire[31:0] AverageB_recip; //B通道倒数
Reciprocal Reciprocal_R(
.Average(AverageR),
.Recip(AverageR_recip)
);
Reciprocal Reciprocal_G(
.Average(AverageG),
.Recip(AverageG_recip)
);
Reciprocal Reciprocal_B(
.Average(AverageB),
.Recip(AverageB_recip)
);
得到每个通道均值的倒数之后乘以128就可以计算每个通道的增益了,该部分乘法比较简单,直接将倒数左移7位即可。由于需要左移7位,为了防止溢出,所以定义增益的位宽为32+7=39bit。代码如下。
reg[38:0] Rgain;
reg[38:0] Ggain;
reg[38:0] Bgain;
always @ (posedge Clk or negedge Rst)
begin
if(!Rst)
begin
Rgain <= 39'd0;
Ggain <= 39'd0;
Bgain <= 39'd0;
end
else
begin
if(f_end_pulse == 1)
begin
Rgain <= {AverageR_recip[31:0],7'd0}; //左移7位,相当于乘以128
Ggain <= {AverageG_recip[31:0],7'd0};
Bgain <= {AverageB_recip[31:0],7'd0};
end
else
begin
Rgain <= Rgain;
Ggain <= Ggain;
Bgain <= Bgain;
end
end
end
计算得到每个通道的增益之后就可以对下一帧图像进行白平衡处理,将图像每个像素的每个通道的灰度值乘以对应的通达增益。这里的思想是:对于视频来说,相邻两帧之间的图像差别较小,可以认为当前图像统计出的通道增益可以适用于下一帧图像,代码如下。最后输出的时候做了一点处理,当计算完得到的新像素值如果大于255,那就让该像素值为255。
wire[40:0] oRTemp;
wire[40:0] oGTemp;
wire[40:0] oBTemp;
assign oRTemp = iR * Rgain;
assign oGTemp = iG * Ggain;
assign oBTemp = iB * Bgain;
assign oR = oRTemp[40:31] > 255 ? 255 : oRTemp[38:31];
assign oG = oGTemp[40:31] > 255 ? 255 : oGTemp[38:31];
assign oB = oBTemp[40:31] > 255 ? 255 : oBTemp[38:31];
计算倒数的模块采用查找表实现,因此需要计算1~255的倒数。如果采用手工一个一个去计算十分的费时费力,在这里我们通过matlab来实现所有倒数的计算,并且输出为verilog语句格式,直接复制粘贴即可。matlab代码如下:
% ===================================
% 求2到255的倒数,并转化为二进制存到txt文件中
% ===================================
fid = fopen('Data.txt','w');
for j = 2 : 255
innum = 1/j;
N = 31;
count=0;
tempnum=innum;
record=zeros(1,N);
while(N)
count=count+1;%长度小于N
if(count>N)
N=0;
end
tempnum=tempnum*2;%小数转换为二进制,乘2取整
if tempnum>1
record(count)=1;
tempnum=tempnum-1;
elseif(tempnum==1)
record(count)=1;
N=0;%stop loop
else
record(count)=0;
end
end
fprintf(fid,'d%d: Recip = 32b',j);
fprintf(fid,'%d',0);
for i = 1 : 31
fprintf(fid,'%d',record(i));
end
fprintf(fid,';');
fprintf(fid,'\r\n');
end
fclose(fid);
写入到txt文件中的数据格式为:
d2: Recip = 32b01000000000000000000000000000000;
d3: Recip = 32b00101010101010101010101010101010;
d4: Recip = 32b00100000000000000000000000000000;
d5: Recip = 32b00011001100110011001100110011001;
d6: Recip = 32b00010101010101010101010101010101;
.
.
.
将上述输出结果粘贴到Reciprocal模块中:
module Reciprocal(
input wire[7:0] Average, //均值
output reg[31:0] Recip //倒数,1位整数位,31位小数位
);
always @ (*)
begin
case(Average)
8'd0: Recip = 32'b00000000000000000000000000000000;
8'd1: Recip = 32'b10000000000000000000000000000000;
8'd2: Recip = 32'b01000000000000000000000000000000;
8'd3: Recip = 32'b00101010101010101010101010101010;
8'd4: Recip = 32'b00100000000000000000000000000000;
8'd5: Recip = 32'b00011001100110011001100110011001;
8'd6: Recip = 32'b00010101010101010101010101010101;
8'd7: Recip = 32'b00010010010010010010010010010010;
8'd8: Recip = 32'b00010000000000000000000000000000;
8'd9: Recip = 32'b00001110001110001110001110001110;
8'd10: Recip = 32'b00001100110011001100110011001100;
8'd11: Recip = 32'b00001011101000101110100010111010;
8'd12: Recip = 32'b00001010101010101010101010101010;
8'd13: Recip = 32'b00001001110110001001110110001001;
8'd14: Recip = 32'b00001001001001001001001001001001;
8'd15: Recip = 32'b00001000100010001000100010001000;
8'd16: Recip = 32'b00001000000000000000000000000000;
8'd17: Recip = 32'b00000111100001111000011110000111;
8'd18: Recip = 32'b00000111000111000111000111000111;
8'd19: Recip = 32'b00000110101111001010000110101111;
8'd20: Recip = 32'b00000110011001100110011001100110;
8'd21: Recip = 32'b00000110000110000110000110000110;
8'd22: Recip = 32'b00000101110100010111010001011101;
8'd23: Recip = 32'b00000101100100001011001000010110;
8'd24: Recip = 32'b00000101010101010101010101010101;
8'd25: Recip = 32'b00000101000111101011100001010001;
8'd26: Recip = 32'b00000100111011000100111011000100;
8'd27: Recip = 32'b00000100101111011010000100101111;
8'd28: Recip = 32'b00000100100100100100100100100100;
8'd29: Recip = 32'b00000100011010011110111001011000;
8'd30: Recip = 32'b00000100010001000100010001000100;
8'd31: Recip = 32'b00000100001000010000100001000010;
8'd32: Recip = 32'b00000100000000000000000000000000;
8'd33: Recip = 32'b00000011111000001111100000111110;
8'd34: Recip = 32'b00000011110000111100001111000011;
8'd35: Recip = 32'b00000011101010000011101010000011;
8'd36: Recip = 32'b00000011100011100011100011100011;
8'd37: Recip = 32'b00000011011101011001111100100010;
8'd38: Recip = 32'b00000011010111100101000011010111;
8'd39: Recip = 32'b00000011010010000011010010000011;
8'd40: Recip = 32'b00000011001100110011001100110011;
8'd41: Recip = 32'b00000011000111110011100000110001;
8'd42: Recip = 32'b00000011000011000011000011000011;
8'd43: Recip = 32'b00000010111110100000101111101000;
8'd44: Recip = 32'b00000010111010001011101000101110;
8'd45: Recip = 32'b00000010110110000010110110000010;
8'd46: Recip = 32'b00000010110010000101100100001011;
8'd47: Recip = 32'b00000010101110010011000100000101;
8'd48: Recip = 32'b00000010101010101010101010101010;
8'd49: Recip = 32'b00000010100111001011110000010100;
8'd50: Recip = 32'b00000010100011110101110000101000;
8'd51: Recip = 32'b00000010100000101000001010000010;
8'd52: Recip = 32'b00000010011101100010011101100010;
8'd53: Recip = 32'b00000010011010100100001110011111;
8'd54: Recip = 32'b00000010010111101101000010010111;
8'd55: Recip = 32'b00000010010100111100100000100101;
8'd56: Recip = 32'b00000010010010010010010010010010;
8'd57: Recip = 32'b00000010001111101110000010001111;
8'd58: Recip = 32'b00000010001101001111011100101100;
8'd59: Recip = 32'b00000010001010110110001111001011;
8'd60: Recip = 32'b00000010001000100010001000100010;
8'd61: Recip = 32'b00000010000110010010111000101001;
8'd62: Recip = 32'b00000010000100001000010000100001;
8'd63: Recip = 32'b00000010000010000010000010000010;
8'd64: Recip = 32'b00000010000000000000000000000000;
8'd65: Recip = 32'b00000001111110000001111110000001;
8'd66: Recip = 32'b00000001111100000111110000011111;
8'd67: Recip = 32'b00000001111010010001001100011010;
8'd68: Recip = 32'b00000001111000011110000111100001;
8'd69: Recip = 32'b00000001110110101110011000000111;
8'd70: Recip = 32'b00000001110101000001110101000001;
8'd71: Recip = 32'b00000001110011011000010101101000;
8'd72: Recip = 32'b00000001110001110001110001110001;
8'd73: Recip = 32'b00000001110000001110000001110000;
8'd74: Recip = 32'b00000001101110101100111110010001;
8'd75: Recip = 32'b00000001101101001110100000011011;
8'd76: Recip = 32'b00000001101011110010100001101011;
8'd77: Recip = 32'b00000001101010011000111011110110;
8'd78: Recip = 32'b00000001101001000001101001000001;
8'd79: Recip = 32'b00000001100111101100100011101001;
8'd80: Recip = 32'b00000001100110011001100110011001;
8'd81: Recip = 32'b00000001100101001000101100001111;
8'd82: Recip = 32'b00000001100011111001110000011000;
8'd83: Recip = 32'b00000001100010101100101110010000;
8'd84: Recip = 32'b00000001100001100001100001100001;
8'd85: Recip = 32'b00000001100000011000000110000001;
8'd86: Recip = 32'b00000001011111010000010111110100;
8'd87: Recip = 32'b00000001011110001010010011001000;
8'd88: Recip = 32'b00000001011101000101110100010111;
8'd89: Recip = 32'b00000001011100000010111000000101;
8'd90: Recip = 32'b00000001011011000001011011000001;
8'd91: Recip = 32'b00000001011010000001011010000001;
8'd92: Recip = 32'b00000001011001000010110010000101;
8'd93: Recip = 32'b00000001011000000101100000010110;
8'd94: Recip = 32'b00000001010111001001100010000010;
8'd95: Recip = 32'b00000001010110001110110100100011;
8'd96: Recip = 32'b00000001010101010101010101010101;
8'd97: Recip = 32'b00000001010100011101000001111110;
8'd98: Recip = 32'b00000001010011100101111000001010;
8'd99: Recip = 32'b00000001010010101111110101101010;
8'd100: Recip = 32'b00000001010001111010111000010100;
8'd101: Recip = 32'b00000001010001000110111110000110;
8'd102: Recip = 32'b00000001010000010100000101000001;
8'd103: Recip = 32'b00000001001111100010001011001011;
8'd104: Recip = 32'b00000001001110110001001110110001;
8'd105: Recip = 32'b00000001001110000001001110000001;
8'd106: Recip = 32'b00000001001101010010000111001111;
8'd107: Recip = 32'b00000001001100100011111000110100;
8'd108: Recip = 32'b00000001001011110110100001001011;
8'd109: Recip = 32'b00000001001011001001111110110100;
8'd110: Recip = 32'b00000001001010011110010000010010;
8'd111: Recip = 32'b00000001001001110011010100001011;
8'd112: Recip = 32'b00000001001001001001001001001001;
8'd113: Recip = 32'b00000001001000011111101101111000;
8'd114: Recip = 32'b00000001000111110111000001000111;
8'd115: Recip = 32'b00000001000111001111000001101010;
8'd116: Recip = 32'b00000001000110100111101110010110;
8'd117: Recip = 32'b00000001000110000001000110000001;
8'd118: Recip = 32'b00000001000101011011000111100101;
8'd119: Recip = 32'b00000001000100110101110010000001;
8'd120: Recip = 32'b00000001000100010001000100010001;
8'd121: Recip = 32'b00000001000011101100111101010110;
8'd122: Recip = 32'b00000001000011001001011100010100;
8'd123: Recip = 32'b00000001000010100110100000010000;
8'd124: Recip = 32'b00000001000010000100001000010000;
8'd125: Recip = 32'b00000001000001100010010011011101;
8'd126: Recip = 32'b00000001000001000001000001000001;
8'd127: Recip = 32'b00000001000000100000010000001000;
8'd128: Recip = 32'b00000001000000000000000000000000;
8'd129: Recip = 32'b00000000111111100000001111111000;
8'd130: Recip = 32'b00000000111111000000111111000000;
8'd131: Recip = 32'b00000000111110100010001100101100;
8'd132: Recip = 32'b00000000111110000011111000001111;
8'd133: Recip = 32'b00000000111101100110000000111101;
8'd134: Recip = 32'b00000000111101001000100110001101;
8'd135: Recip = 32'b00000000111100101011100111010110;
8'd136: Recip = 32'b00000000111100001111000011110000;
8'd137: Recip = 32'b00000000111011110010111010110111;
8'd138: Recip = 32'b00000000111011010111001100000011;
8'd139: Recip = 32'b00000000111010111011110110110010;
8'd140: Recip = 32'b00000000111010100000111010100000;
8'd141: Recip = 32'b00000000111010000110010110101100;
8'd142: Recip = 32'b00000000111001101100001010110100;
8'd143: Recip = 32'b00000000111001010010010110011000;
8'd144: Recip = 32'b00000000111000111000111000111000;
8'd145: Recip = 32'b00000000111000011111110001111000;
8'd146: Recip = 32'b00000000111000000111000000111000;
8'd147: Recip = 32'b00000000110111101110100101011100;
8'd148: Recip = 32'b00000000110111010110011111001000;
8'd149: Recip = 32'b00000000110110111110101101100001;
8'd150: Recip = 32'b00000000110110100111010000001101;
8'd151: Recip = 32'b00000000110110010000000110110010;
8'd152: Recip = 32'b00000000110101111001010000110101;
8'd153: Recip = 32'b00000000110101100010101110000000;
8'd154: Recip = 32'b00000000110101001100011101111011;
8'd155: Recip = 32'b00000000110100110110100000001101;
8'd156: Recip = 32'b00000000110100100000110100100000;
8'd157: Recip = 32'b00000000110100001011011010011111;
8'd158: Recip = 32'b00000000110011110110010001110100;
8'd159: Recip = 32'b00000000110011100001011010001010;
8'd160: Recip = 32'b00000000110011001100110011001100;
8'd161: Recip = 32'b00000000110010111000011100100111;
8'd162: Recip = 32'b00000000110010100100010110000111;
8'd163: Recip = 32'b00000000110010010000011111011010;
8'd164: Recip = 32'b00000000110001111100111000001100;
8'd165: Recip = 32'b00000000110001101001100000001100;
8'd166: Recip = 32'b00000000110001010110010111001000;
8'd167: Recip = 32'b00000000110001000011011100101111;
8'd168: Recip = 32'b00000000110000110000110000110000;
8'd169: Recip = 32'b00000000110000011110010010111011;
8'd170: Recip = 32'b00000000110000001100000011000000;
8'd171: Recip = 32'b00000000101111111010000000101111;
8'd172: Recip = 32'b00000000101111101000001011111010;
8'd173: Recip = 32'b00000000101111010110100100010000;
8'd174: Recip = 32'b00000000101111000101001001100100;
8'd175: Recip = 32'b00000000101110110011111011100111;
8'd176: Recip = 32'b00000000101110100010111010001011;
8'd177: Recip = 32'b00000000101110010010000101000011;
8'd178: Recip = 32'b00000000101110000001011100000010;
8'd179: Recip = 32'b00000000101101110000111110111011;
8'd180: Recip = 32'b00000000101101100000101101100000;
8'd181: Recip = 32'b00000000101101010000100111100110;
8'd182: Recip = 32'b00000000101101000000101101000000;
8'd183: Recip = 32'b00000000101100110000111101100011;
8'd184: Recip = 32'b00000000101100100001011001000010;
8'd185: Recip = 32'b00000000101100010001111111010011;
8'd186: Recip = 32'b00000000101100000010110000001011;
8'd187: Recip = 32'b00000000101011110011101011011101;
8'd188: Recip = 32'b00000000101011100100110001000001;
8'd189: Recip = 32'b00000000101011010110000000101011;
8'd190: Recip = 32'b00000000101011000111011010010001;
8'd191: Recip = 32'b00000000101010111000111101101001;
8'd192: Recip = 32'b00000000101010101010101010101010;
8'd193: Recip = 32'b00000000101010011100100001001010;
8'd194: Recip = 32'b00000000101010001110100000111111;
8'd195: Recip = 32'b00000000101010000000101010000000;
8'd196: Recip = 32'b00000000101001110010111100000101;
8'd197: Recip = 32'b00000000101001100101010111000100;
8'd198: Recip = 32'b00000000101001010111111010110101;
8'd199: Recip = 32'b00000000101001001010100111001111;
8'd200: Recip = 32'b00000000101000111101011100001010;
8'd201: Recip = 32'b00000000101000110000011001011110;
8'd202: Recip = 32'b00000000101000100011011111000011;
8'd203: Recip = 32'b00000000101000010110101100110001;
8'd204: Recip = 32'b00000000101000001010000010100000;
8'd205: Recip = 32'b00000000100111111101100000001001;
8'd206: Recip = 32'b00000000100111110001000101100101;
8'd207: Recip = 32'b00000000100111100100110010101101;
8'd208: Recip = 32'b00000000100111011000100111011000;
8'd209: Recip = 32'b00000000100111001100100011100001;
8'd210: Recip = 32'b00000000100111000000100111000000;
8'd211: Recip = 32'b00000000100110110100110001101111;
8'd212: Recip = 32'b00000000100110101001000011100111;
8'd213: Recip = 32'b00000000100110011101011100100010;
8'd214: Recip = 32'b00000000100110010001111100011010;
8'd215: Recip = 32'b00000000100110000110100011001000;
8'd216: Recip = 32'b00000000100101111011010000100101;
8'd217: Recip = 32'b00000000100101110000000100101110;
8'd218: Recip = 32'b00000000100101100100111111011010;
8'd219: Recip = 32'b00000000100101011010000000100101;
8'd220: Recip = 32'b00000000100101001111001000001001;
8'd221: Recip = 32'b00000000100101000100010110000000;
8'd222: Recip = 32'b00000000100100111001101010000101;
8'd223: Recip = 32'b00000000100100101111000100010011;
8'd224: Recip = 32'b00000000100100100100100100100100;
8'd225: Recip = 32'b00000000100100011010001010110011;
8'd226: Recip = 32'b00000000100100001111110110111100;
8'd227: Recip = 32'b00000000100100000101101000111000;
8'd228: Recip = 32'b00000000100011111011100000100011;
8'd229: Recip = 32'b00000000100011110001011101111001;
8'd230: Recip = 32'b00000000100011100111100000110101;
8'd231: Recip = 32'b00000000100011011101101001010010;
8'd232: Recip = 32'b00000000100011010011110111001011;
8'd233: Recip = 32'b00000000100011001010001010011100;
8'd234: Recip = 32'b00000000100011000000100011000000;
8'd235: Recip = 32'b00000000100010110111000000110100;
8'd236: Recip = 32'b00000000100010101101100011110010;
8'd237: Recip = 32'b00000000100010100100001011111000;
8'd238: Recip = 32'b00000000100010011010111001000000;
8'd239: Recip = 32'b00000000100010010001101011000111;
8'd240: Recip = 32'b00000000100010001000100010001000;
8'd241: Recip = 32'b00000000100001111111011110000000;
8'd242: Recip = 32'b00000000100001110110011110101011;
8'd243: Recip = 32'b00000000100001101101100100000101;
8'd244: Recip = 32'b00000000100001100100101110001010;
8'd245: Recip = 32'b00000000100001011011111100110111;
8'd246: Recip = 32'b00000000100001010011010000001000;
8'd247: Recip = 32'b00000000100001001010100111111001;
8'd248: Recip = 32'b00000000100001000010000100001000;
8'd249: Recip = 32'b00000000100000111001100100110000;
8'd250: Recip = 32'b00000000100000110001001001101110;
8'd251: Recip = 32'b00000000100000101000110010111111;
8'd252: Recip = 32'b00000000100000100000100000100000;
8'd253: Recip = 32'b00000000100000011000010010001101;
8'd254: Recip = 32'b00000000100000010000001000000100;
8'd255: Recip = 32'b00000000100000001000000010000000;
default:Recip = 32'b00000000000000000000000000000000;
endcase
end
endmodule
在FPAG设计验证的时候,testbench是相当重要的一部分。如果把设计文件当做设计好的电路板,那么testbench就是测试电路板的外围电路,提供必要的电源和信号激励源。
首先定义接口,reg变量会连接到设计文件的输入,因为需要在always进程中产生信号激励,wire变量会连接到设计文件的输出,方便仿真时观察输出波形。
reg Clk;
reg Rst;
reg f_end;
reg l_end;
reg pix_en;
reg[7:0] iR;
reg[7:0] iG;
reg[7:0] iB;
wire f_end_o;
wire l_end_o;
wire pix_en_o;
wire[7:0] oR;
wire[7:0] oG;
wire[7:0] oB;
然后进行初始化,在初始化中我们需要对所有reg型变量赋初值,否则在仿真时会出现错误,同时还要将图像数据读入,并产生复位信号,代码如下。为了将图像数据读入,定义了三个寄存器组,位宽八位,由于图像大小为454*302=137108,因此深度为137108。
reg[7:0] R_data[0:137107];
reg[7:0] G_data[0:137107];
reg[7:0] B_data[0:137107];
integer fid_R;
integer fid_G;
integer fid_B;
initial
begin
Clk = 0;
Rst = 1;
f_end = 0;
l_end = 0;
pix_en = 0;
iR = 8'd0;
iG = 8'd0;
iB = 8'd0;
//读取图像R通道数据
$readmemh("C:/Users/Administrator/Desktop/AWB Test/AWB simulate/ImageData/image2txt_R.txt",R_data);
//读取图像G通道数据
$readmemh("C:/Users/Administrator/Desktop/AWB Test/AWB simulate/ImageData/image2txt_G.txt",G_data);
//读取图像B通道数据
$readmemh("C:/Users/Administrator/Desktop/AWB Test/AWB simulate/ImageData/image2txt_B.txt",B_data);
//打开输出txt文件
fid_R = $fopen("C:/Users/Administrator/Desktop/AWB Test/AWB simulate/ImageData/txt2image_R.txt");
fid_G = $fopen("C:/Users/Administrator/Desktop/AWB Test/AWB simulate/ImageData/txt2image_G.txt");
fid_B = $fopen("C:/Users/Administrator/Desktop/AWB Test/AWB simulate/ImageData/txt2image_B.txt");
//产生复位信号
#10
Rst = 0;
#20
Rst = 1;
end
产生测试时钟,频率为50MHz,因此10ns时钟信号翻转一次,周期为20ns。
always #10 Clk = ~Clk; //产生测试时钟,50MHz
接下来产生控制信号并将图像数据按照格式发送出去。在这里定义了两个变量PixCount和ImageCount。PixCount是像素计数器,每发送一个像素的数据,PixCount加1,当PixCount为137107时表明一帧图像发送完毕,同时产生帧结束信号f_end,这里帧结束信号为10个时钟周期的高电平。ImageCount为图像帧计数器,每发完一帧图像,计数器加1。由于自动白平衡是对整幅图像进行统计,因此行结束信号l_end在这里没有用,不做处理。时序图和代码如下。
reg[17:0] PixCount;
reg[7:0] ImageCount;
always @ (posedge Clk or negedge Rst)
begin
if(!Rst)
begin
PixCount <= 18'd0;
ImageCount <= 8'd0;
end
else
begin
if(PixCount > 18'd137107 && PixCount < 18'd137117)
begin
PixCount <= PixCount + 1'b1;
pix_en <= 0;
f_end <= 1;
end
else if(PixCount == 18'd137117)
begin
PixCount <= 18'd0;
ImageCount <= ImageCount + 1'b1;
end
else
begin
iR <= R_data[PixCount];
iG <= G_data[PixCount];
iB <= B_data[PixCount];
pix_en <= 1;
PixCount <= PixCount + 1'b1;
f_end <= 0;
end
end
end
为了将处理后的图像显示出来,还需要将处理完的数据存储到txt文件中,和读入一样,将三个R、G、B通道的数据分别写入到三个txt文件中,然后通过Matlab读取并显示出来。由于图像第一帧是在计算每个通道的增益,第二帧才算对图像进行白平衡处理,因此判断当ImageCount等于1的时候表明开始输出处理后的图像数据,将每个像素的数据输出。
always @ (posedge Clk)
begin
if(ImageCount == 1 && pix_en == 1)
begin
$fwrite(fid_R,"%d\n",oR);
$fwrite(fid_G,"%d\n",oG);
$fwrite(fid_B,"%d\n",oB);
end
else if(ImageCount == 2)
$stop;
end
最后就是在testbench中调用设计文件了,也就是实例化,设计文件名为awb_design,实例化后的名称为awb_design_m0。
awb_design awb_design_m0(
.Clk(Clk),
.Rst(Rst),
.f_end(f_end),
.l_end(l_end),
.pix_en(pix_en),
.iR(iR),
.iG(iG),
.iB(iB),
.f_end_o(f_end_o),
.l_end_o(l_end_o),
.pix_en_o(pix_en_o),
.oR(oR),
.oG(oG),
.oB(oB)
);
至此,testbench编写完毕,从以上步骤可以看出,testbench实际上是给设计文件提供必要的信号输入从而保证设计模块能够正常的运行起来,这些信号输入是需要根据实际的情况来设计。
需要注意的是:在testbench中有很多的语句是不可综合的,仅仅只供仿真时使用,例如产生时钟信号的语句,该语句的思想是每过10ns时钟信号翻转一次,以此产生20ns周期的时钟。但是这在设计可综合的verilog语句中是错误的写法,因为对于可综合成网表电路的语句来说,所有信号的变化都是基于输入信号的边沿的,这个信号可以是时钟也可以是组合逻辑输入,因此如果所有的输入没有变化,那么输出也就不可能发生变化。
always #10 Clk = ~Clk; //产生测试时钟,50MHz
运行仿真,首先启动示波器查看计算得到的三个通道的增益。
从仿真结果得到的三个通道的增益为:
将其按照31位小数位转换为十进制小数如下:
和一开始matlab仿真得到的结果差别很小,在容许的范围内。然后查看modelsim仿真实际输出效果,运行仿真之后会输出处理后的图像数据,这些数据存储在三个txt文件中,分别对应图像三个通道。我们通过Matlab将这些输入读入,然后还原成一幅完整的图片显示出来。matlab代码如下:
clear all
R_data = importdata('txt2image_R.txt');
G_data = importdata('txt2image_G.txt');
B_data = importdata('txt2image_B.txt');
count = 0;
for i = 1 : 454
for j = 1 : 302
count = count + 1;
RimageData(j,i) = R_data(count);
GimageData(j,i) = G_data(count);
BimageData(j,i) = B_data(count);
end
end
ImageData(:,:,1) = RimageData;
ImageData(:,:,2) = GimageData;
ImageData(:,:,3) = BimageData;
ImageData = uint8(ImageData);
imshow(ImageData);
最终输出的图像如下图所示,可以看出效果和matlab仿真结果基本一致。
灰度世界法通过matlab实现非常简单,但是通过FPGA实现的话其中有一些地方需要注意。在这之中最主要的是计算方法的转换,由于在FPGA中实现除法比较麻烦,因此在本设计中通过将除法转换为乘法来实现的。还有各个变量的位宽定义都需要考虑,以防计算结果发生溢出。除此之外最重要的是一整套仿真方法的学习,将matlab和modelsim结合起来使用会大大提高仿真效率。