文章源于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
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→中值滤波→腐蚀→膨胀