前言
具体关于色域的知识就不细说了,简单来讲YUV中Y通道可以理解为就是图像的灰度图,因此,将RGB转化为YUV是求彩色图的灰度直方图、进行二值化操作等的基础。
HDMI时序生成模块
这里先介绍一下仿真时用于生成HDMI时序,用这个时序来向数据处理模块输入数据,一是可以做到通过同步信号简化对图像数据的管理,二是可以让测试的数据处理模块更方便的适配用HDMI显示的图像处理工程。
HDMI的单帧时序如图,具体时序知识不细说,下面代码实现的功能简单理解就是当data_req(数据请求)信号拉高时,开始向模块内输入图像数据,连续输入一行的数据,然后直接输出图像数据,当输出数据时拉高hdmi_de(数据有效)信号。因为本次是单帧的图像,所以hdmi_vs(场同步)信号以及hdmi_hs(行同步)信号是用不到的。
要注意不同尺寸的图像对应的参数不同,本模块适用于1280*720像素的图像
module hdmi_tim_gen(
input clk ,
input rst_n ,
input [23:0] data_in ,
output hdmi_hs , //行同步信号
output hdmi_vs , //场同步信号
output hdmi_de , //数据使能
output [23:0] hdmi_data , //图像数据
output reg data_req
);
//1280*720 分辨率时序参数
parameter H_SYNC = 11'd40;
parameter H_BACK = 11'd220;
parameter H_DISP = 11'd1280;
parameter H_FRONT = 11'd110;
parameter H_TOTAL = 11'd1650;
parameter V_SYNC = 11'd5;
parameter V_BACK = 11'd20;
parameter V_DISP = 11'd720;
parameter V_FRONT = 11'd5;
parameter V_TOTAL = 11'd750;
reg [11:0] cnt_h;
reg [11:0] cnt_v;
reg [10:0] pixel_xpos;
reg [10:0] pixel_ypos;
assign hdmi_de = data_req;
assign hdmi_hs = ( cnt_h < H_SYNC ) ? 1'b0 : 1'b1;
assign hdmi_vs = ( cnt_v < V_SYNC ) ? 1'b0 : 1'b1;
//图像数据输出
assign hdmi_data = hdmi_de ? data_in : 24'd0;
//请求像素点数据输入
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
data_req <= 1'b0;
else if(((cnt_h >= H_SYNC + H_BACK - 2'd2) && (cnt_h < H_SYNC + H_BACK + H_DISP - 2'd2))
&& ((cnt_v >= V_SYNC + V_BACK) && (cnt_v < V_SYNC + V_BACK+V_DISP)))
data_req <= 1'b1;
else
data_req <= 1'b0;
end
//像素点x坐标
always@ (posedge clk or negedge rst_n) begin
if(!rst_n)
pixel_xpos <= 11'd0;
else if(data_req)
pixel_xpos <= cnt_h + 2'd2 - H_SYNC - H_BACK ;
else
pixel_xpos <= 11'd0;
end
//像素点y坐标
always@ (posedge clk or negedge rst_n) begin
if(!rst_n)
pixel_ypos <= 11'd0;
else if((cnt_v >= (V_SYNC + V_BACK)) && (cnt_v < (V_SYNC + V_BACK + V_DISP)))
pixel_ypos <= cnt_v + 1'b1 - (V_SYNC + V_BACK) ;
else
pixel_ypos <= 11'd0;
end
//行计数器对像素时钟计数
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_h <= 11'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 11'd0;
end
end
//场计数器对行计数
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_v <= 11'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 11'd0;
end
end
endmodule
RGB转YUV模块
前面也说过了,YUV中的Y通道就是图像的灰度图,以此,单独将Y通道输出就可以得到彩色图像的灰度图
module rgb2yuv
(
input clk ,
input rst_n ,
input vs_i ,
input de_i ,
input [23:0] data_i ,
output vs_o ,
output de_o ,
output [23:0] data_yuv ,
output [7:0] data_gray
);
//YUV中Y指明亮度,也就是灰度值,(U,V)指色度,即饱和度和色调
reg [15:0] rgb_r_m0, rgb_r_m1, rgb_r_m2;
reg [15:0] rgb_g_m0, rgb_g_m1, rgb_g_m2;
reg [15:0] rgb_b_m0, rgb_b_m1, rgb_b_m2;
reg [15:0] img_y0 ;
reg [15:0] img_cb0;
reg [15:0] img_cr0;
reg [ 7:0] img_y1 ;
reg [ 7:0] img_cb1;
reg [ 7:0] img_cr1;
reg [ 2:0] vs_i_d;
reg [ 2:0] de_i_d ;
//wire define
wire [ 7:0] rgb888_r;
wire [ 7:0] rgb888_g;
wire [ 7:0] rgb888_b;
wire [ 7:0] img_y;
wire [ 7:0] img_cb;
wire [ 7:0] img_cr;
assign rgb888_r = data_i[23:16];
assign rgb888_g = data_i[15:8];
assign rgb888_b = data_i[7:0];
//同步输出数据接口信号
assign vs_o = vs_i_d[2] ;
assign de_o = de_i_d[2] ;
assign img_y = img_y1 ;
assign img_cb = img_cb1 ;
assign img_cr = img_cr1 ;
assign data_yuv = {img_y,img_cb,img_cr};
assign data_gray = img_y;
/********************************************************
RGB888 to YCbCr
Y = 0.299R +0.587G + 0.114B
Cb = 0.568(B-Y) + 128 = -0.172R-0.339G + 0.511B + 128
CR = 0.713(R-Y) + 128 = 0.511R-0.428G -0.083B + 128
Y = (77 *R + 150*G + 29 *B)>>8
Cb = (-43*R - 85 *G + 128*B)>>8 + 128
Cr = (128*R - 107*G - 21 *B)>>8 + 128
Y = (77 *R + 150*G + 29 *B )>>8
Cb = (-43*R - 85 *G + 128*B + 32768)>>8
Cr = (128*R - 107*G - 21 *B + 32768)>>8
*********************************************************/
//step1 计算括号内的各乘法项
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rgb_r_m0 <= 16'd0;
rgb_r_m1 <= 16'd0;
rgb_r_m2 <= 16'd0;
rgb_g_m0 <= 16'd0;
rgb_g_m1 <= 16'd0;
rgb_g_m2 <= 16'd0;
rgb_b_m0 <= 16'd0;
rgb_b_m1 <= 16'd0;
rgb_b_m2 <= 16'd0;
end
else begin
rgb_r_m0 <= rgb888_r * 8'd77 ;
rgb_r_m1 <= rgb888_r * 8'd43 ;
rgb_r_m2 <= rgb888_r * 8'd128;
rgb_g_m0 <= rgb888_g * 8'd150;
rgb_g_m1 <= rgb888_g * 8'd85 ;
rgb_g_m2 <= rgb888_g * 8'd107;
rgb_b_m0 <= rgb888_b * 8'd29 ;
rgb_b_m1 <= rgb888_b * 8'd128;
rgb_b_m2 <= rgb888_b * 8'd21 ;
end
end
//step2 括号内各项相加
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
img_y0 <= 16'd0;
img_cb0 <= 16'd0;
img_cr0 <= 16'd0;
end
else begin
img_y0 <= rgb_r_m0 + rgb_g_m0 + rgb_b_m0;
img_cb0 <= rgb_b_m1 - rgb_r_m1 - rgb_g_m1 + 16'd32768;
img_cr0 <= rgb_r_m2 - rgb_g_m2 - rgb_b_m2 + 16'd32768;
end
end
//step3 括号内计算的数据右移8位
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
img_y1 <= 8'd0;
img_cb1 <= 8'd0;
img_cr1 <= 8'd0;
end
else begin
img_y1 <= img_y0 [15:8];
img_cb1 <= img_cb0[15:8];
img_cr1 <= img_cr0[15:8];
end
end
//延时3拍以同步数据信号
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
vs_i_d <= 3'd0;
de_i_d <= 3'd0;
end
else begin
vs_i_d <= {vs_i_d[1:0], vs_i};
de_i_d <= {de_i_d[1:0], de_i};
end
end
endmodule
仿真TB文件
注意文件读取和保存的路径
关于一些仿真中用的系统命令可以参考我的testbench中一些常用的系统命令-CSDN博客这篇
本次仿真读取的是txt文件,需要通过matlab或者python将图像转化为txt,具体实现程序可看本人主页文章
`timescale 1ns/1ns
module pic_tb();
//hdmi_tim_gen模块接口
reg clk,rst_n ;
reg [23:0] data_in ;
wire hdmi_hs,hdmi_vs,hdmi_de ;
wire [23:0] hdmi_data ;
wire data_req ;
//rgb2yuv模块接口
reg vs_i,de_i ;
wire vs_o,de_o ;
wire [23:0] data_yuv ;
wire [7:0] data_gray ;
//延迟1clk,与data同步
always @(posedge clk)begin
vs_i <= hdmi_vs;
de_i <= hdmi_de;
end
initial begin
clk = 1;
rst_n = 0;
#20 rst_n = 1;
end
always #10 clk = ~clk;
reg [23:0] img[0:1280*720-1];
reg [31:0] addr;
initial begin
$readmemh("D:/pic/img2txt.txt",img);
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
addr <= 0 ;
data_in <= 0 ;
end
else if(data_req)begin
data_in <= img[addr];
addr <= addr + 1;
if(addr == (1280*720-1))
addr <= 0;
end
end
integer file_out_yuv;
integer file_out_gray;
reg [31:0] out_addr;
initial begin
file_out_yuv = $fopen("D:/pic/fileout_yuv.txt","w");
file_out_gray = $fopen("D:/pic/fileout_gray.txt","w");
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
out_addr <= 0;
end
else if(out_addr == 1280*720)begin
$display("task finish \n");
$fclose(file_out_yuv);
$fclose(file_out_gray);
$finish;
end
else if(de_o)begin
out_addr <= out_addr + 1;
$fwrite(file_out_yuv,"%x\n",data_yuv);
$fwrite(file_out_gray,"%x\n",data_gray);
end
end
//例化hdmi_tim_gen模块
hdmi_tim_gen u_hdmi_tim_gen(
.clk (clk),
.rst_n (rst_n),
//input
.data_in (data_in),
//output
.hdmi_hs (hdmi_hs),
.hdmi_vs (hdmi_vs),
.hdmi_de (hdmi_de),
.hdmi_data (hdmi_data),
.data_req (data_req)
);
//例化rgb2yuv模块
rgb2yuv u_rgb2yuv
(
.clk (clk),
.rst_n (rst_n),
.vs_i (vs_i),
.de_i (de_i),
.data_i (hdmi_data),
.vs_o (vs_o),
.de_o (de_o),
.data_yuv (data_yuv),
.data_gray (data_gray)
);
endmodule
运行与结果
工程层级
直接在vivado内部仿真即可
仿真结果
可以看到这部分符合HDMI时序波形
原图
YUV格式
灰度图
结语
本次实验比较简单,直接拷贝到工程,路径也没有问题应该可以直接运行,不过还是把工程附上,供大家免费下载,共同学习。
【免费】RGB图像转YUV及灰度图FPGA实现工程资源-CSDN文库