对于彩色转灰度,有一个很著名的心理学公式:Gray = R0.299 + G0.587 + B*0.114;
来自于RGB888 to YCbCr的算法公式,我们可以直接把算法移植到FPGA上,但是我们都知道FPGA无法进行浮点运算,所以我们采取将整个式子右端先都扩大256倍,然后再右移8位,这样就得到了FPGA擅长的乘法运算和加法运算了。
如果是RGB565,需要先将RGB565转化成RGB888,避免精度损失较大:
24bit RGB888 -> 16bit RGB565 的转换(只取高位)
24ibt RGB888 {R7 R6 R5 R4 R3 R2 R1 R0} {G7 G6 G5 G4 G3 G2 G1 G0} {B7 B6 B5 B4 B3 B2 B1 B0}
16bit RGB656 {R7 R6 R5 R4 R3} {G7 G6 G5 G4 G3 G2} {B7 B6 B5 B4 B3}
同样也可以恢复回去。
16bit RGB565 -> 24bit RGB888 的转换(高位补低位)
16bit RGB656 {R4 R3 R2 R1 R0} {G5 G4 G3 G2 G1 G0} {B4 B3 B2 B1 B0}
24ibt RGB888 {R4 R3 R2 R1 R0 R2 R1 R0} {G5 G4 G3 G2 G1 G0 G1 G0} {B4 B3 B2 B1 B0 B2 B1 B0}
使用移位算法来避免浮点运算,并保证算法的精度;
习惯上使用16位精度,2的16次幂是65536,所以这样计算系数:
0.299 * 65536 = 19595.264 ≈ 19595;
0.587 * 65536 + (0.264) = 38469.632 + 0.264 = 38469.896 ≈ 38469;
0.114 * 65536 + (0.896) = 7471.104 + 0.896 = 7472。
可能很多人看见了,我所使用的舍入方式不是四舍五入。四舍五入会有较大的误差,应该将以前的计算结果的误差一起计算进去,舍入方式是去尾法:
写成表达式是:Gray = (R19595 + G38469 + B*7472) >> 16。
2至20位精度的系数:
Gray = (R*1 + G*2 + B*1) >> 2
Gray = (R*2 + G*5 + B*1) >> 3
Gray = (R*4 + G*10 + B*2) >> 4
Gray = (R*9 + G*19 + B*4) >> 5
Gray = (R*19 + G*37 + B*8) >> 6
Gray = (R*38 + G*75 + B*15) >> 7
Gray = (R*76 + G*150 + B*30) >> 8
Gray = (R*153 + G*300 + B*59) >> 9
Gray = (R*306 + G*601 + B*117) >> 10
Gray = (R*612 + G*1202 + B*234) >> 11
Gray = (R*1224 + G*2405 + B*467) >> 12
Gray = (R*2449 + G*4809 + B*934) >> 13
Gray = (R*4898 + G*9618 + B*1868) >> 14
Gray = (R*9797 + G*19235 + B*3736) >> 15
Gray = (R*19595 + G*38469 + B*7472) >> 16
Gray = (R*39190 + G*76939 + B*14943) >> 17
Gray = (R*78381 + G*153878 + B*29885) >> 18
Gray = (R*156762 + G*307757 + B*59769) >> 19
Gray = (R*313524 + G*615514 + B*119538) >> 20
仔细观察上面的表格,这些精度实际上是一样的:3与4、7与8、10与11、13与14、19与20。
所以16位运算下最好的计算公式是使用7位精度,比先前那个系数缩放100倍的精度高,而且速度快:Gray = (R38 + G75 + B*15) >> 7。
其实最有意思的还是那个2位精度的,完全可以移位优化:Gray = (R + (WORD)G<<1 + B) >> 2。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/10/22 16:52:47
// Design Name:
// Module Name: RGB2Gray
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
// Gray = R*0.299 + G*0.587 + B*0.114
// Gray = (R*76 + G*150 + B*30) >> 8 使用8位精度来进行运算
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module RGB2Gray
#(
parameter Pixel_Width = 24
)
(
input I_clk,
input I_reset_p,
input I_pixel_data_valid,
input [Pixel_Width-1:0] I_pixel_data_RGB,//RGB 888 [23-16,15-8,7-0]
output reg O_pixel_data_valid,
output [7:0] O_pixel_data_Gray
);
reg [14:0] R_mult;// 7bit * 8 bit
reg [15:0] G_mult;//8bit * 8bit
reg [14:0] B_mult;//5bit * 8bit
reg [16:0] gray_temp;
reg pixel_data_valid_d1;
always@(posedge I_clk)
begin
if(I_reset_p)
begin
R_mult <= 'h0;
G_mult <= 'h0;
B_mult <= 'h0;
end
else
begin
R_mult <= I_pixel_data_RGB[Pixel_Width-1-:8] * 76;
G_mult <= I_pixel_data_RGB[15:8] * 150;
B_mult <= I_pixel_data_RGB[7:0] * 30;
end
end
always@(posedge I_clk)
begin
if(I_reset_p)
gray_temp <= 'h0;
else
gray_temp <= R_mult + G_mult + B_mult;
end
always@(posedge I_clk)
begin
pixel_data_valid_d1 <= I_pixel_data_valid;
O_pixel_data_valid <= pixel_data_valid_d1;
end
assign O_pixel_data_Gray = (gray_temp[16])? 8'hff : gray_temp[15:8];// >> 8 bit
endmodule
如果是RGB565,需要先转化为RGB888:,代码如下:
wire [7:0] RGB_R;
wire [7:0] RGB_G;
wire [7:0] RGB_B;
assign RGB_R = {I_pixel_data_RGB[15:11],I_pixel_data_RGB[13:11]};
assign RGB_G = {I_pixel_data_RGB[10:5],I_pixel_data_RGB[6:5]};
assign RGB_B = {I_pixel_data_RGB[4:0],I_pixel_data_RGB[2:0]};
always@(posedge I_clk)
begin
if(I_reset_p)
begin
R_mult <= 'h0;
G_mult <= 'h0;
B_mult <= 'h0;
end
else
begin
R_mult <= RGB_R * 76;
G_mult <= RGB_G * 150;
B_mult <= RGB_B * 30;
end
end
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/10/22 17:52:24
// Design Name:
// Module Name: RGB2Gray_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module RGB2Gray_tb(
);
parameter Pixel_Width = 24;
reg I_clk;
reg I_reset_p;
reg I_pixel_data_valid;
//reg [Pixel_Width-1:0] I_pixel_data_RGB;//RGB 888 [23-16,15-8,7-0]
wire O_pixel_data_valid;
wire [7:0] O_pixel_data_Gray;
reg [16:0] addra;
wire [23:0] douta;
reg start;
integer number_file;
RGB2Gray RGB2Gray_inst(
.I_clk(I_clk),
.I_reset_p(I_reset_p),
.I_pixel_data_valid(I_pixel_data_valid),
.I_pixel_data_RGB(douta),
.O_pixel_data_valid(O_pixel_data_valid),
.O_pixel_data_Gray(O_pixel_data_Gray)
);
image_ram image_ram_inst(
.clka(I_clk), // input wire clka
.ena(1'b1), // input wire ena
.wea(1'b0), // input wire [0 : 0] wea
.addra(addra), // input wire [16 : 0] addra
.dina(), // input wire [23 : 0] dina
.douta(douta) // output wire [23 : 0] douta
);
initial begin
I_clk = 0;
I_reset_p = 1;
start = 0;
#10;
I_reset_p = 0;
#20;
@(posedge I_clk) start = 1;
end
always #5 I_clk <= ~I_clk;
always@(posedge I_clk)
begin
if(I_reset_p)
addra <= 17'd0;
else if(start)
if(addra < 17'd128000)
addra <= addra + 1;
end
always@(posedge I_clk)
begin
if(I_reset_p)
I_pixel_data_valid <= 1'b0;
else if(start && addra < 17'd128000 )
I_pixel_data_valid <= 1'b1;
else
I_pixel_data_valid <= 1'b0;
end
//always@(posedge I_clk)
// begin
// if(addra == 17'd128000)
// begin
// $display("the image RGB TO GRAY is done!!!\n");
// $display("the cost time is %t",$time);
// $stop;
// end
// end
initial begin
number_file = $fopen("RGB2Gray.txt","w");
repeat(129000) @(posedge I_clk)
begin
if(O_pixel_data_valid)
begin
$fwrite(number_file,"%d\n",O_pixel_data_Gray);//注意是$fwrite 而不是$write
end
end
#10
$display("the image RGB TO GRAY is done!!!\n");
$display("the cost time is %t",$time);
$fclose(number_file);//只有$fclose 才可以写进去数据或更改原有数据
end
//always@(posedge I_clk)
// begin
// if(O_pixel_data_valid)
// begin
// $fwrite(number_file,"%d\n",O_pixel_data_Gray);//注意是$fwrite 而不是$write
// end
// end
endmodule
clear all;
close all;
src = imread('lena640x200.bmp');
gray = rgb2gray(src);
r = src(:,:,1);
g = src(:,:,2);
b = src(:,:,3);
imshow(src);
[m,n,k] = size(src);
N = m*n; %m行,n列
data_lenth = 8; %数据位宽
data_r = reshape(r',1,N); %数字矩阵改为1行N列
data_g = reshape(g',1,N);
data_b = reshape(b',1,N);
data_gray = reshape(gray',1,N);
fin = fopen('lena.coe','wt');
fprintf(fin,'MEMORY_INITIALIZATION_RADIX=16;\n');
fprintf(fin,'MEMORY_INITIALIZATION_VECTOR=\n')
for i = 1:N-1
fprintf(fin,'%x%x%x,\n',data_r(i),data_g(i),data_b(i));
end
fprintf(fin,'%x%x%x;',data_r(N),data_g(N),data_b(N));%最后一行是;结束
fclose(fin);
f_gray = fopen('lena_matlab_gray.txt','wt');
for i = 1:N
fprintf(f_gray,'%x ',data_gray(i));
end
fclose(f_gray);
clear all;
close all;
src_rgb = imread('lena640x200.bmp');
subplot(2,2,1),imshow(src_rgb), title('image-RGB')
gray = rgb2gray(src_rgb);
matlab_gray = rgb2gray(src_rgb);
[m,n] = size(matlab_gray);
subplot(2,2,2),imshow(matlab_gray), title('image-matlab-gray')
RGB2Gray_FPGA = load('RGB2Gray.txt');%verilog 产生的灰度图
RGB2Gray_FPGA_M = reshape(RGB2Gray_FPGA,n,m);%reshape 是一列一列的排列数据的,从左到右
RGB2Gray_FPGA_M = uint8(RGB2Gray_FPGA_M');
subplot(2,2,3),imshow(RGB2Gray_FPGA_M), title('image-RGB2Gray-FPGA')
diff = matlab_gray - RGB2Gray_FPGA_M;
subplot(2,2,4),imshow(diff), title('diff')
index =find(diff);
[n_index,a] = size(index);
fprintf('the diff pixel num is %d\n',n_index);
diff_sub = diff;
N = m*n;
for i = 1:n_index
if(diff_sub(index(i)) == 1)
diff_sub(index(i)) = 0;
end
end
index_sub = find(diff_sub);
[n_index_sub,b] = size(index_sub);
fprintf('the diff_sub pixel num is %d\n',n_index_sub);