关于 YCbCr(YUV) 格式视频流的介绍

文章源于Bingo,支持原创

分享Bingo 的博文:

https://www.cnblogs.com/crazybingo/archive/2012/06/07/2540595.html
https://www.cnblogs.com/crazybingo/archive/2012/06/08/2541700.html

https://www.cnblogs.com/crazybingo/archive/2011/03/27/1996946.html

RGB 转 YCbCr, 实际上只是色度空间的转换, 前者为三原色色度空间, 后者为亮度与色差,其中 RGB 转 YCbCr 的公式如下所示:

由于 Verilog HDL 无法进行浮点运算,因此使用扩大 256 倍,再向右移 8Bit的方式,来转换公式, 如下所示:

此时,剩下的问题就是如何将上面的公式移植到 FPGA 中去。 FPGA 富含乘法器,移位寄存器,加法器, 如果直接用表达式描述上面的运算, 理论上也是能计算出结果的; 不过代价是效率、资源; 同时当表达式小于 0 的时候, 运算结果出错!!!

因此, 在正式进行算法移植前,我们需要进行运算的拆分, 同时进行一定的变换。 过程中为了防止过程中出现负数, 先将 128 提取到括号内,如下:


此时 Y 必然为正值, Cb 括号内最小值为:

 因此 Cb 括号内必然大于 0。同样可以推断出 Cr 必然大于 0。经过上述变换后,可以放心的进行运算,而不用考虑运算结果溢出的问题。

1、RGB888 转 YCbCr 的 HDL 实现

1) 第二步,分别计算出 Y、 Cb、 Cr 中每一个乘法的乘积, HDL 如下:
 

//Step 1
reg	[15:0]	img_red_r0,		img_red_r1,		img_red_r2;	
reg	[15:0]	img_green_r0,	img_green_r1,	img_green_r2; 
reg	[15:0]	img_blue_r0,	img_blue_r1,	img_blue_r2; 
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_red_r0		<=	0; 		
		img_red_r1		<=	0; 		
		img_red_r2		<=	0; 	
		img_green_r0	<=	0; 		
		img_green_r1	<=	0; 		
		img_green_r2	<=	0; 	
		img_blue_r0		<=	0; 		
		img_blue_r1		<=	0; 		
		img_blue_r2		<=	0; 			
		end
	else
		begin
		img_red_r0		<=	per_img_red 	* 	8'd77; 		
		img_red_r1		<=	per_img_red 	* 	8'd43; 	
		img_red_r2		<=	per_img_red 	* 	8'd128; 		
		img_green_r0	<=	per_img_green 	* 	8'd150; 		
		img_green_r1	<=	per_img_green 	* 	8'd85; 			
		img_green_r2	<=	per_img_green 	* 	8'd107; 
		img_blue_r0		<=	per_img_blue 	* 	8'd29; 		
		img_blue_r1		<=	per_img_blue 	* 	8'd128; 			
		img_blue_r2		<=	per_img_blue 	* 	8'd21; 		
		end
end

2) 计算出 Y、 Cb、 Cr 括号内的值, HDL 如下:

//--------------------------------------------------
//Step 2
reg	[15:0]	img_Y_r0;	
reg	[15:0]	img_Cb_r0; 
reg	[15:0]	img_Cr_r0; 
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_Y_r0	<=	0; 		
		img_Cb_r0	<=	0; 		
		img_Cr_r0	<=	0; 	
		end
	else
		begin
		img_Y_r0	<=	img_red_r0 	+ 	img_green_r0 	+ 	img_blue_r0; 		
		img_Cb_r0	<=	img_blue_r1 - 	img_red_r1 		- 	img_green_r1	+	16'd32768; 		
		img_Cr_r0	<=	img_red_r2 	+ 	img_green_r2 	+ 	img_blue_r2		+	16'd32768; 		
		end
end

( 3) 第三步, 右移 8Bit。 这里由于 Step2 计算结果为 16Bit, 因此提取高
8Bit 即可, HDL 如下所示:

//--------------------------------------------------
//Step 3
reg	[7:0]	img_Y_r1;	
reg	[7:0]	img_Cb_r1; 
reg	[7:0]	img_Cr_r1; 
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_Y_r1	<=	0; 		
		img_Cb_r1	<=	0; 		
		img_Cr_r1	<=	0; 	
		end
	else
		begin
		img_Y_r1	<=	img_Y_r0[15:8];
		img_Cb_r1	<=	img_Cb_r0[15:8];
		img_Cr_r1	<=	img_Cr_r0[15:8]; 
		end
end

前面说过, VIP 处理模块输入的数据接口时序, 与输出完全一样。前面计算出 Y、 Cb、 Cr 我们消耗了(1)(2)(3) 这三个时钟, 因此需要将输入的行场信号、使能信号同步移动 3 个时钟, 采用寄存器移位实现

 RGB565 转 RGB888 相 关 理 论 , 请 详 见 Bingo 博 客 :

https://www.cnblogs.com/crazybingo/archive/2012/06/08/2542336.html

2、YCbCr422 转 RGB888

关于ITU-R BT.656 协议

https://www.cnblogs.com/crazybingo/archive/2011/03/27/1996974.html

由于 ITU-R BT.656 视频信号为 YUV 信号,同时,目前 CMOS 摄像头支持RGB565、 YCbCr、 Bayer 这几种模式。只玩过 RGB565,未免不太爽了。研究YUV 格式视频的解码,对于未来 BT.656 视频流的处理,以及相关算法的了解,很有好处。既然决定了做视频图像算法, 那么 YCbCr 转 RGB888 算法, 就必须搞定。
YUV 信号的提出,是因为国际上出现彩色电视,为了兼容黑白电视的信号而设计的,其由于视频码率,压缩,兼容性的优势,一直被沿用至今。如下是完整的 YUV4:2:2 的视频格式数据流:

 OV7725 在 YCbCr422 格式下输出的视频流格式如下。 当然由于 OV7725 同时输出了行场信号,我们可以直接硬件解码,不需要通过 FF0000XY 识别。同行OV7725 输出的 YCbCr422 是阉割版的 BT.656 视频流信号, 即视频流逐行输出,不存在标准 BT.656 所谓的奇场,偶场信号。

3、YUV/YCbCr 视频格式简说
YUV 由 Y、 U、 V 复合而成,其中 Y:亮度(16-235) , U:色彩 V:饱和度。YUV 有很多格式,比如 4:2:2; 4:2:2; 4:2:0 等。使用最多的是 YUV422 格式,如下图所示:

YUV422 模式即水平方向上 UV 的采样速度为 Y 的一半,相当于每两个点采样一个 U、 V,每一个点采样一个 Y。这样被允许的原因是因为,我们的眼睛对亮度的敏感度远大于对色度的敏感度,因此可以通过牺牲色度的采样率来达到图像数据压缩的目的。
 当年的黑白电视,只有亮度,即 Y; YUV 格式的出现很好的兼容了不同制式的电视,因为 YUV 既能兼容灰度信号,又能通过 YUV2RGB 可以转换为彩色图像,兼容彩色液晶。不明白的孩子,可以直接让{R,G,B}={Y,Y,Y},看看是不是黑白灰度的图像。

YUV 主要应用在模拟系统中,而 YCbCr 是通过 YUV 信号的发展,通过了校正,主要应用在数字视频中的一种格式, 一般意义上 YCbCr 即为 YUV 信号,没有严格的划分。 CbCr 分别为蓝色色差、红色色差,详细的说明请看 Bingo 博文,这里不是重点。
http://www.cnblogs.com/crazybingo/archive/2012/06/07/2540595.html

4、YUV422 转 YUV444
首先,第一步,前面得到的 YCbCr422 为 2:1 的分量,为了更直观的实现YCbCr 转 RGB 的算法,我们首先将 YCbCr422 转换成 YCbCr444, 即通过 Cb、Cr 的分配,完整的将每个像素均赋予 YCbCr 的格式。通过多级寄存及分量拼接, 从第三个像素的时刻开始,持续输出完整的 YCbCr 的格式, 实现的步骤如下:

首先,第一步,前面得到的 YCbCr422 为 2:1 的分量,为了更直观的实现YCbCr 转 RGB 的算法,我们首先将 YCbCr422 转换成 YCbCr444, 即通过 Cb、Cr 的分配,完整的将每个像素均赋予 YCbCr 的格式。 这里 Bingo 通过多级寄存及分量拼接, 从第三个像素的时刻开始,持续输出完整的 YCbCr 的格式, 实现的步骤如下:
(1) 寄存 Cb0、 Y0
(2) 寄存 Cr0、 Y1
(3) 输出 Y0、 Cb0、 Cr0, 寄存 Cb1、 Y2
(4) 输出 Y1、 Cb0、 Cr0, 寄存 Cr1、 Y3
(5) 输出 Y2、 Cb1、 Cr1, 寄存 Cb02、 Y02
(6) 输出 Y3、 Cb1、 Cr1, 寄存 Cr02、 Y12
(7) ……
可见, 通过(1) 与(2) 的寄存,从(3) 开始,便可以持续的输出完整的YCbCr 格式。 但是问题(1) 与(2) 消耗了 2 个时钟,因此我们需要人为的生成 2 个时钟, 来补充最后 2 个像素的数据输出。 这可以通过 per_frame_clken 的寄存实现, 如下所示:

//------------------------------------------
//lag n pixel clocks	 
reg	[4:0]	post_frame_vsync_r;
reg	[4:0]	post_frame_href_r;
reg	[4:0]	post_frame_clken_r;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		post_frame_vsync_r 	<= 	0;
		post_frame_href_r 	<= 	0;
		post_frame_clken_r 	<= 	0;
		end
	else
		begin
		post_frame_vsync_r 	<= 	{post_frame_vsync_r[3:0], per_frame_vsync};
		post_frame_href_r 	<= 	{post_frame_href_r[3:0], per_frame_href};
		post_frame_clken_r 	<= 	{post_frame_clken_r[3:0], per_frame_clken};
		end
end
assign	post_frame_vsync 	= 	post_frame_vsync_r[4];
assign	post_frame_href 	= 	post_frame_href_r[4];
assign	post_frame_clken 	= 	post_frame_clken_r[4];
wire	yuv_process_href	= 	per_frame_href || post_frame_href_r[3];
wire	yuv_process_clken	= 	per_frame_clken || post_frame_clken_r[3];

这里延时 4 个时钟的原因,是由于 cmos_pclk 的时钟频率,为拼接后输出的速度的 2 倍。因此 4 次寄存,刚好延时了 2 个像素。 yuv_process_href 与yuv_process_clken 作为前面(1) ~(7) ……的读取使能与读取时钟信号。 这里给出 YCbCr422 恢复 YCbCr444 的实现方式, 如下:

//-------------------------------------------
//convert YCbCr422 to YCbCr444
reg	[2:0]	yuv_state;
reg	[7:0]	mY0, mY1, mY2, mY3;
reg	[7:0]	mCb0, mCb1;
reg	[7:0]	mCr0, mCr1;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		yuv_state <= 3'd0;
		{mY0, mCb0, mCr0} <= {8'h0, 8'h0, 8'h0};
		mY1 <= 8'h0;
		{mY2, mCb1, mCr1} <= {8'h0, 8'h0, 8'h0};
		mY3	<= 8'h0;
		{post_img_Y, post_img_Cb, post_img_Cr} <= {8'h0, 8'h0, 8'h0};
		end
	else if(yuv_process_href)	//lag 2 data enable clock and need 2 more clocks
		begin
		if(yuv_process_clken)	//lag 2 data enable clock and need 2 more clocks
			case(yuv_state)		//{Cb,Y},{Cr,Y}---YCbCr
			3'd0:	begin	//reg p0
					yuv_state <= 3'd1;	
					{mCb0, mY0} <= per_frame_YCbCr;	
					end		
			3'd1:	begin	//reg p1
					yuv_state <= 3'd2;	
					{mCr0, mY1} <= per_frame_YCbCr;	
					end		
			
			3'd2:	begin	//p0;	reg p2
					yuv_state <= 3'd3;	
					{mCb1, mY2} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY0, mCb0, mCr0};	
					end	
			3'd3:	begin	//p1;	reg p3
					yuv_state <= 3'd4;	
					{mCr1, mY3} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY1, mCb0, mCr0};
					end	
			3'd4:	begin	//p2;	reg	p0
					yuv_state <= 3'd5;	
					{mCb0, mY0} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY2, mCb1, mCr1};	
					end		//p3;	reg p1
			3'd5:	begin	
					yuv_state <= 3'd2;	
					{mCr0, mY1} <= per_frame_YCbCr;	
					{post_img_Y, post_img_Cb, post_img_Cr} <= {mY3, mCb1, mCr1};	
					end	
			endcase
		else
			begin
			yuv_state <= yuv_state;
			{mY0, mCb0, mCr0} <= {mY0, mCb0, mCr0};
			mY1 <= mY1;
			{mY2, mCb1, mCr1} <= {mY2, mCb1, mCr1};
			mY3	<= mY3;
			{post_img_Y, post_img_Cb, post_img_Cr} <= {post_img_Y, post_img_Cb, post_img_Cr};
			end
		end
	else
		begin
		yuv_state <= 3'd0;
		{mY0, mCb0, mCr0} <= {8'h0, 8'h0, 8'h0};
		{mY1, mCb1, mCr1} <= {8'h0, 8'h0, 8'h0};
		{post_img_Y, post_img_Cb, post_img_Cr} <= {8'h0, 8'h0, 8'h0};
		end
end

这里给出上述 0~5 的状态机转移图, 如下所示。可见从 0~1 为寄存, 2~5 开始循环输出, 直到一行数据的结束。

YUV444 转 RGB888

//--------------------------------------------
/*********************************************
	R = 1.164(Y-16) + 1.596(Cr-128)
	G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
	B = 1.164(Y-16) + 2.018(Cb-128)
	->
	R = 1.164Y + 1.596Cr - 222.912
	G = 1.164Y - 0.391Cb - 0.813Cr + 135.488
	B = 1.164Y + 2.018Cb - 276.928
	->
	R << 9 = 596Y				+ 	817Cr	-	114131
	G << 9 = 596Y	-	200Cb	-	416Cr	+	69370
	B << 9 = 596Y	+	1033Cb				-	141787
**********************************************/

将该算法移植后, Bingo 发现图像整体偏紫。 。。不可避免的怀疑是否是OV7725 的手册, 不仅仅是上面加减法的错误。。。

 

OV7725 在手册开头说输出输出格式是 YCbCr422,如上图所示,但在后续的寄存器等的介绍中又变成了 YUV。 。。 YUV 与 YCbCr 在转换时,公式有略微的差异,主要是 YCbCr 是经过 Gamma 校正过的,如下:
 

因此,直接采用 OV7725 软件应用手册, 也许因为 Gamma 矫正问题。切不在乎那么多细节, Bingo通过实践证明根据软件配置,出现了偏色, 但是用上述的转换方式,图像非常的可以。因此转换公式,如下:

针对上式而言,我们需要提取最后的 R、 G、 B, 这里只需要进行三个步骤。 首先,计算每一个步中的分量,如下所示:

**********************************************/
reg	[19:0]	img_Y_r1; 				//8 + 9 + 1 = 18Bit
reg	[19:0]	img_Cb_r1, img_Cb_r2; 
reg	[19:0]	img_Cr_r1, img_Cr_r2;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		img_Y_r1 <= 0;
		img_Cb_r1 <= 0;	img_Cb_r2 <= 0;	
		img_Cr_r1 <= 0; img_Cr_r2 <= 0;
		end
	else
		begin
		img_Y_r1	<= 	per_img_Y * 18'd596;
		img_Cb_r1 	<= 	per_img_Cb * 18'd200;	
		img_Cb_r2	<=	per_img_Cb * 18'd1033;	
		img_Cr_r1 	<= 	per_img_Cr * 18'd817;	
		img_Cr_r2	<=	per_img_Cr * 18'd416;
		end
end

第二步, 计算 512 倍扩大、 9 次移位后的结果, 如下所示:

//--------------------------------------------
/**********************************************
	R << 9 = 596Y				+ 	817Cr	-	114131
	G << 9 = 596Y	-	200Cb	-	416Cr	+	69370
	B << 9 = 596Y	+	1033Cb				-	141787
**********************************************/
reg	[19:0]	XOUT; 	
reg	[19:0]	YOUT; 
reg	[19:0]	ZOUT;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		XOUT <= 0; 	
		YOUT <= 0; 
		ZOUT <= 0;
		end
	else
		begin
		XOUT <= (img_Y_r1 + img_Cr_r1 - 20'd114131)>>9; 	
		YOUT <= (img_Y_r1 - img_Cb_r1 - img_Cr_r2 + 20'd69370)>>9; 
		ZOUT <= (img_Y_r1 + img_Cb_r2 - 20'd141787)>>9;
		end
end

第三步,根据上述 XOUT、 YOUT、 ZOUT 的结果, 如果小于 0(Bit[10] ==1) ,则赋 0; 如果大于 255, 则赋 255; 如果在 0~255 之间,则保持原值, 具体的实现如下所示:

//------------------------------------------
//Divide 512 and get the result
//{xx[19:11], xx[10:0]}
reg	[7:0]	R, G, B;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		R <= 0;
		G <= 0;
		B <= 0;
		end
	else
		begin
		R <= XOUT[10] ? 8'd0 : (XOUT[9:0] > 9'd255) ? 8'd255 : XOUT[7:0];
		G <= YOUT[10] ? 8'd0 : (YOUT[9:0] > 9'd255) ? 8'd255 : YOUT[7:0];
		B <= ZOUT[10] ? 8'd0 : (ZOUT[9:0] > 9'd255) ? 8'd255 : ZOUT[7:0];
		end
end


5、灰度图像的均值滤波算法
均值滤波算法介绍
首先要做的是最简单的均值滤波算法。均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标象素为中心的周围 8 个像素,构成一个滤波模板,即去掉目标像素本身),再用
模板中的全体像素的平均值来代替原来像素值。

中值滤波算法可以形象的用上述表格来描述, 即对于每个 3*3 的阵列而言,中间像素的值,等于边缘 8 个像素的平均值。 算法的理论很简单,对于 C 处理器而言,一幅 640*480 图像的均值滤波, 可以很方便的通过数组获得 3*3 的阵列,但对于我们的 Verilog HDL 而言,着实不易, 一开始想都想不明白!!!
 3*3 像素阵列的 HDL 实现

查询过很多资料, 3*3 阵列的获取,大概有以下三种方式:
(1) 通过 2 个或 3 个 RAM 的存储,来实现 3*3 像素阵列
(2) 通过 2 个或 3 个 FIFO 的存储,来实现 3*3 像素阵列

( 3) 通过 2 行或 3 行 Shift_RAM 的移位存储,来实现 3*3 像素阵列

最方便的实现方式, 非 Shift_RAM 莫属了,都感觉 Shift_RAM 甚至是为实现 3*3 阵列而生的!

Shift_RAM 可定义数据宽度、 移位的行数、 每行的深度。 这里我们固然需要8Bit, 640 个数据每行,同时移位寄存 2 行即可( 原因看后边)。 同时选择时钟使能端口 clken。

看懂这个示意图, 没有任何继续往下看手册的意义!!!直接上战场!!! Bingo的思路是这样子的, Shift_RAM 中存 2 行数据,同时与当前输入行的数据,组成3 行的阵列。

(1) 首先,将输入的信号用像素使能时钟同步一拍,以保证数据与Shift_RAM 输出的数据保持同步,如下:

//Generate 3*3 matrix 
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//sync row3_data with per_frame_clken & row1_data & raw2_data
wire	[7:0]	row1_data;	//frame data of the 1th row
wire	[7:0]	row2_data;	//frame data of the 2th row
reg		[7:0]	row3_data;	//frame data of the 3th row
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		row3_data <= 0;
	else 
		begin
		if(per_frame_clken)
			row3_data <= per_img_Y;
		else
			row3_data <= row3_data;
		end	
end

(2) 接着, 例化并输入 row3_data,此时可从 Modelsim 中观察到 3 行数据同时存在了, HDL 如下
数据从 row3_data 输入, 满 3 行后刚好唯一 3 行阵列的第一。 从图像第三行输入开始, 到图像的最后一行,我们均可从 row_data 得到完整的 3 行数据, 基为实现3*3阵列奠定了基础。 不过这样做有2个不足之处, 即第一行与第二行不能得到完整的 3*3 阵列。 但从主到次,且不管算法的完美型,我们先验证 3X3模板实现的正确性。 因此直接在行有效期间读取 3*3 阵列,机器方便快捷的实现了我们的目的。

3) Row_data 读取信号的分析及生成
这里涉及到了一个问题,数据从Shift_RAM存储耗费了一个时钟,因此3*3阵列的读取使能与时钟,需要进行一个 clock 的偏移,如下所示:

//------------------------------------------
//lag 2 clocks signal sync  
reg	[1:0]	per_frame_vsync_r;
reg	[1:0]	per_frame_href_r;	
reg	[1:0]	per_frame_clken_r;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		per_frame_vsync_r <= 0;
		per_frame_href_r <= 0;
		per_frame_clken_r <= 0;
		end
	else
		begin
		per_frame_vsync_r 	<= 	{per_frame_vsync_r[0], 	per_frame_vsync};
		per_frame_href_r 	<= 	{per_frame_href_r[0], 	per_frame_href};
		per_frame_clken_r 	<= 	{per_frame_clken_r[0], 	per_frame_clken};
		end
end
//Give up the 1th and 2th row edge data caculate for simple process
//Give up the 1th and 2th point of 1 line for simple process
wire	read_frame_href		=	per_frame_href_r[0];	//RAM read href sync signal
wire	read_frame_clken	=	per_frame_clken_r[0];	//RAM read enable
assign	matrix_frame_vsync 	= 	per_frame_vsync_r[1];
assign	matrix_frame_href 	= 	per_frame_href_r[1];
assign	matrix_frame_clken 	= 	per_frame_clken_r[1];

4) Okay, 此时根据 read_frame_href 与 read_frame_clken 信号,直接读取3*3 像素阵列。读取的 HDL 实现如下( 请读者详细思考这一段代码):

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
/******************************************************************************
					----------	Convert Matrix	----------
				[ P31 -> P32 -> P33 -> ]	--->	[ P11 P12 P13 ]	
				[ P21 -> P22 -> P23 -> ]	--->	[ P21 P22 P23 ]
				[ P11 -> P12 -> P11 -> ]	--->	[ P31 P32 P33 ]
******************************************************************************/
//---------------------------------------------------------------------------
//---------------------------------------------------
/***********************************************
	(1)	Read data from Shift_RAM
	(2) Caculate the Sobel
	(3) Steady data after Sobel generate
************************************************/
//wire	[23:0]	matrix_row1 = {matrix_p11, matrix_p12, matrix_p13};	//Just for test
//wire	[23:0]	matrix_row2 = {matrix_p21, matrix_p22, matrix_p23};
//wire	[23:0]	matrix_row3 = {matrix_p31, matrix_p32, matrix_p33};
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
		{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
		{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
		end
	else if(read_frame_href)
		begin
		if(read_frame_clken)	//Shift_RAM data read clock enable
			begin
			{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p12, matrix_p13, row1_data};	//1th shift input
			{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p22, matrix_p23, row2_data};	//2th shift input
			{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p32, matrix_p33, row3_data};	//3th shift input
			end
		else
			begin
			{matrix_p11, matrix_p12, matrix_p13} <= {matrix_p11, matrix_p12, matrix_p13};
			{matrix_p21, matrix_p22, matrix_p23} <= {matrix_p21, matrix_p22, matrix_p23};
			{matrix_p31, matrix_p32, matrix_p33} <= {matrix_p31, matrix_p32, matrix_p33};
			end	
		end
	else
		begin
		{matrix_p11, matrix_p12, matrix_p13} <= 24'h0;
		{matrix_p21, matrix_p22, matrix_p23} <= 24'h0;
		{matrix_p31, matrix_p32, matrix_p33} <= 24'h0;
		end
end

注意: 这样做我们快速方便地得到了 3*3 像素阵列,不过计算第一次与第二次得到的 3*3 行像素不完整,同时在计算中,最后一行的像素没有参与。 同时每行的第一、第二、 最后个像素也是如此。这里给出的只是一个 VIP算法 3X3 像素阵列生成的模板,更完美的方式可以通过镜像方法实现,希望读者自己加倍努力吧! !!

Mean_Filter 均值滤波算法的实现
我们例化Matrix_Generate_3X3_8Bit 模块,直接得到了 3*3 像素阵列,不过相对于 3*3 像素阵列的生成而言,均值滤波的算法实现反而难度小的多,只是技巧性的问题。
继续分析上面这个表格。 其实 HDL 完全有这个能力直接计算 8 个值相加的均值,不过为了提升电路的速度,  建议我们需要通过以面积换速度的方式来实现。 So 这里需要 3 个步骤:
(1) 分别计算 3 行中相关像素的和, 相关 HDL 代码如下

//Add you arithmetic here
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//Mean Filter of 3X3 datas, need 2 clock
//Step 1
reg	[10:0]	mean_value1, mean_value2, mean_value3;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		mean_value1 <= 0;
		mean_value2 <= 0;
		mean_value3 <= 0;
		end
	else
		begin
		mean_value1 <= matrix_p11 + matrix_p12 + matrix_p13;
		mean_value2 <= matrix_p21 + 11'd0	   + matrix_p23;
		mean_value3 <= matrix_p31 + matrix_p32 + matrix_p33;
		end
end

(2) 计算(1) 中三个结果的和

//Step2
reg	[10:0]	mean_value4;
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		mean_value4 <= 0;
	else
		mean_value4 <= mean_value1 + mean_value2 + mean_value3;
end

在(2) 运算后,我们不能急着用除法去实现均值的运算。记住, 能用移位替换的,绝对不用乘除法来实现。这里 8 个像素, 即除以 8, 可以方便的用右移动 3Bit 来实现。不过这里更方便的办法是,直接提取 mean_value4[10:3]。


上图(左) 为前面 YCbCr 直接得到的 Y 灰度视频显示,上图(右) 为经过了均值滤波算法后的图像。仔细一看,右图与作图相比,明显丧失了部分细节,图像变得模糊了。 这是由于均值滤波的效果/不足,滤除了部分噪声的同步,缺失了更多的细节。

灰度图像的中值滤波算法的实现

中值/均值滤波对比
无论是直接获取的灰度图像,还是由彩色图像转换得到的灰度图像,里面都有噪声的存在,噪声对图像质量有很大的影响。进行中值滤波不仅可以去除孤点噪声,而且可以保持图像的边缘特性,不会使图像产生显著的模糊,比较适合于实验中的人脸图像。

中值滤波算法与均值滤波非常的相似, 但滤波的效果却有很大的差别, 区别
如下:
(1) 均值滤波相当于低通滤波, 有将图像模糊化的趋势,对椒盐噪声基本无能力。
(2) 中值滤波的有点事可以很好的过滤椒盐噪声,缺点是容易造成图像的不连续。
这里抠取 Bingo 在网上找到中值滤波与均值滤波的对比效果, 由于版权,尊重 原 创 , 感 谢 博 主 , 转 载 必 注 :
http://www.360doc.com/content/13/0124/16/10086564_262170551.shtml

图 1 为含有椒盐噪声的 Lena,图 2 为均值滤波后的 Lena,可见效果并不明朗!!!图 3 为中值滤波后的 Lena,世界竟然可以如此的精彩!!! 因此设计实现中值滤波势在必行,快马加鞭啊!!!
中值滤波的算法非常简单, 只要求得 3*3 像素阵列的中间值即可,这样就有效的移植了最大值与最小值,图像会变得均匀, 对椒盐噪声有很好的滤除效果!

中值滤波算法的 HDL 实现
我们现在的重点是如何快速求得 9 个值的均值, 该论文介绍了某种快速排序法,如下图所示:

图中非常形象的表示了取得中值的方法,只需要三个步骤,如下:
(1) 首先分别对每行 3 个像素进行排序, Verilog HDL 的实现,由于并行特性,我们只需要一个时钟,实现如下:
 

//-----------------------------------
//Sort of 3 datas	
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		begin
		max_data <= 0;
		mid_data <= 0;
		min_data <= 0;
		end
	else
		begin
		//get the max value
		if(data1 >= data2 && data1 >= data3)
			max_data <= data1;
		else if(data2 >= data1 && data2 >= data3)
			max_data <= data2;
		else//(data3 >= data1 && data3 >= data2)
			max_data <= data3;

		//get the mid value
		if((data1 >= data2 && data1 <= data3) || (data1 >= data3 && data1 <= data2))
			mid_data <= data1;
		else if((data2 >= data1 && data2 <= data3) || (data2 >= data3 && data2 <= data1))
			mid_data <= data2;
		else//((data3 >= data1 && data3 <= data2) || (data3 >= data2 && data3 <= data1))
			mid_data <= data3;
			
		//ge the min value
		if(data1 <= data2 && data1 <= data3)
			min_data <= data1;
		else if(data2 <= data1 && data2 <= data3)
			min_data <= data2;
		else//(data3 <= data1 && data3 <= data2)
			min_data <= data3;
		
		end
end
//--------------------------------------------------------------------------------------
//FPGA Median Filter Sort order
//	  	   Pixel				 Sort1					  Sort2				 Sort3
//	[   P1  P2   P3 ]	[   Max1  Mid1   Min1 ]	
//	[   P4  P5   P6 ]	[   Max2  Mid2   Min2 ]	[Max_min, Mid_mid, Min_max]	mid_valid
//	[   P7  P8   P9 ]	[   Max3  Mid3   Min3 ]

//Step1
wire	[7:0]	max_data1, mid_data1, min_data1;
Sort3	u_Sort3_1
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(data11), 
	.data2		(data12), 
	.data3		(data13),
	
	.max_data	(max_data1),
	.mid_data	(mid_data1),
	.min_data   (min_data1)
);

wire	[7:0]	max_data2, mid_data2, min_data2;
Sort3	u_Sort3_2
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(data21), 
	.data2		(data22), 
	.data3		(data23),
	
	.max_data	(max_data2),
	.mid_data	(mid_data2),
	.min_data   (min_data2)
);

wire	[7:0]	max_data3, mid_data3, min_data3;
Sort3	u_Sort3_3
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(data31), 
	.data2		(data32), 
	.data3		(data33),
	
	.max_data	(max_data3),
	.mid_data	(mid_data3),
	.min_data	(min_data3)
);

 

( 2) 接着,对三行像素取得的排序进行处理,即提取三个最大值中的最小值,三个最小值中的最大值,以及三个中间值的中间值。
 

//Step2
wire	[7:0]	max_min_data, mid_mid_data, min_max_data;
Sort3	u_Sort3_4
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(max_data1), 
	.data2		(max_data2), 
	.data3		(max_data3),
	
	.max_data	(),
	.mid_data	(),
	.min_data   (max_min_data)
);

Sort3	u_Sort3_5
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(mid_data1), 
	.data2		(mid_data2), 
	.data3		(mid_data3),
	
	.max_data	(),
	.mid_data	(mid_mid_data),
	.min_data   ()
);

Sort3	u_Sort3_6
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(min_data1), 
	.data2		(min_data2), 
	.data3		(min_data3),
	
	.max_data	(min_max_data),
	.mid_data	(),
	.min_data   ()
);

( 3) 最后,将( 2) 中得到的三个值,再次取中值,求得最终 9 个像素的中值,如下:

//step3
Sort3	u_Sort3_7
(
	.clk		(clk),
	.rst_n		(rst_n),
	
	.data1		(max_min_data), 
	.data2		(mid_mid_data), 
	.data3		(min_max_data),
	
	.max_data	(),
	.mid_data	(target_data),
	.min_data   ()
);

Okay, 从( 1) -( 3) , 我们花费了 3 个 clock,完成了 3*3 像素阵列的中值提取。

上图左为原始灰度图像,上图右为中值滤波后的图像, 可见中值滤波后的图像相对暗了一点,因为最大值被舍去了。但相对于均值滤波而言,中值滤波在滤除噪声的基础上, 有效的保存了细节。此外,图像处理中滤波算法有很多,包括高斯滤波, 非局部均匀等。掌握了最基本的图像处理,更多的模板算法处理方式,也就是移植与实现的问题。

RAW→RGB→Gray→中值滤波
大部分机器视觉,都是在灰度域处理的,因此我们需要纯净的 Gray 图像。对于机器视觉或者工业领域等对图像要求高的,还需要做一定的预处理,此时肯定不会用 Sensor 本身输出的 RGB565 图像,因此本工程作为所有后续算法升级演变的模型,首选将 Bayer 转 RGB,继而再通过 YUV 转换提取出灰度图像,做一步简单的中值滤波了事。

(1) 优化 RAW2RGB 位 5*5 的模式,且考虑方向梯度
( 2) 优化滤波,修改为高斯滤波,甚至非局部平均滤波、 BM3D 等 2D 滤波算法
RAW→RGB→Gray→Median→Sobel→中值滤波→腐蚀→膨胀







 

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Verilog是一种硬件描述语言(HDL),用于描述数字电路和系统的行为和结构。它被广泛应用于数字电路设计、嵌入式系统开发和芯片设计等领域。 YUV422和YUV444是两种常见的图像格式,用于表示彩色图像的亮度和色度信息。YUV422是一种压缩的格式,其中每两个像素共享一个色度样本,而YUV444是一种非压缩的格式,每个像素都有自己的亮度和色度样本。 要将YUV422转换为YUV444,需要对每个像素进行插值,以恢复缺失的色度样本。插值可以使用简单的线性插值算法来实现。以下是一个Verilog代码示例,用于将YUV422格式的输入图像转换为YUV444格式: ```verilog module YUV422_to_YUV444 ( input [7:0] yuv422_data, output reg [7:0] yuv444_y, output reg [7:0] yuv444_u, output reg [7:0] yuv444_v ); reg [7:0] y_prev; reg [7:0] u_prev; reg [7:0] v_prev; always @(posedge clk) begin yuv444_y <= yuv422_data[7:0]; u_prev <= yuv422_data[7:0]; v_prev <= yuv422_data[15:8]; end always @(posedge clk) begin yuv444_u <= (u_prev + yuv422_data[7:0]) / 2; yuv444_v <= (v_prev + yuv422_data[15:8]) / 2; end endmodule ``` 在上述代码中,输入信号`yuv422_data`是一个8位的YUV422数据,输出信号`yuv444_y`、`yuv444_u`和`yuv444_v`分别表示转换后的YUV444格式的亮度、色度U和色度V。 请注意,上述代码仅为示例,实际的转换算法可能会有所不同,具体取决于所使用的插值算法和系统架构。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值