文章目录
前言
rgb2gray 模块:彩色图像灰度化处理,对串口接收的彩色图像数据实时进行灰度化处理;
image_stitche_x 模块:将串口接收的尺寸为 400480 大小的彩色图像与灰度化处理后的 400480 大小的图像数据以左右形式合并成一张 800*480 的图像。
提示:以下是本篇文章正文内容,下面案例可供参考
一、彩色图像灰度化处理模块的设计
1.基本原理
将彩色图像转化为灰度图像的过程称为图像灰度化处理。常见的 24 位深度彩色图像RGB888 中的每个像素的颜色由 R、G、B 三个分量决定,并且三个分量各占 1 个字节,每个分量可以取值 0~255,这样一个像素点可以有 1600 多万(255255255)的颜色的变化范围。而灰度图像是 R、G、B 三个分量相同的一种特殊的彩色图像,其一个像素点的变化范围为 0~255。对于一幅彩色图来说,其对应的灰度图则是只有 8 位的图像深度,这也说明了用灰度图做图像处理所需的计算量确实要少。不过需要注意的是,虽然丢失了一些颜色等级,但是从整幅图像的整体和局部的色彩以及亮度等级分布特征来看,灰度图描述与彩色图的描述是一致的。一般有分量法、最大值法、平均值法、加权平均法四种方法对彩色图像进行灰度化。
2.彩色图像灰度化处理方法介绍
方法 1:分量法
将彩色图像中的三分量的亮度作为三个灰度图像的灰度值,可根据应用需要选取一种灰度图像。具体表达式如下。
gray1(𝑖,𝑗) = 𝑅(𝑖,𝑗)
gray2(𝑖,𝑗) = 𝐺(𝑖,𝑗)
gray3(𝑖,𝑗) = 𝐵(𝑖,𝑗)
其中,gray1(𝑖,𝑗), gray2(𝑖,𝑗), gray3(𝑖,𝑗)为转换后的灰度图像在(i,j)处的灰度值,R(i,j),G(i,j),B(i,j)分别为转换前的彩色图像在(i,j)处 R、G、B 三个分量的值。
方法 2:最大值法
将彩色图像中的三分量亮度 R,G,B 的最大值作为灰度图的灰度值。具体表达式如下。
gray(i, j) = max[𝑅(𝑖,𝑗),𝐺(𝑖,𝑗),𝐵(𝑖,𝑗)]
方法 3:平均值法
将彩色图像中的三分量亮度求平均得到一个灰度值。如下:
gray(i, j) =(𝑅(𝑖,𝑗) + 𝐺(𝑖,𝑗) + 𝐵(𝑖,𝑗))/3
上式中有除法,考虑到在 FPGA 中实现除法比较的消耗资源,这里在实现前可以先做如下的近似处理。可以将上面公式乘以 3/256,这样就需要同时乘以 256/3 保证公式的正确性。公式处理过程如下:
对 256/3 做近似取整处理,将 256/3 替换成 85,则公式变为如下。
这样式子中除以 256 就可以采用移位方法来处理,式子变为如下:
上面处理过程中使用是对 256/3 的近似处理,当然这里可以采用其他数据,比如512/3、1024/3、2048/3 等等,基本的原则是将平均公式法中分母的 3 替换成 2 的幂次的数,这样除法就可以使用移位的方式实现,减小 FPGA 中由于存在除法带来的资源消耗。
平均值法的实现
该方法实现起来并不复杂,通过上面的计算公式可以知道,计算公式里只有加法、乘法和移位计算,这里的乘法通过移位相加的方式进行计算,计算具体实现见下面代码。
//求平均法 GRAY = (R+B+G)/3=((R+B+G)*85)>>8
wire [9:0]sum;
reg [15:0]gray_r;
assign sum = red_8b_i + green_8b_i + blue_8b_i;
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
gray_r <= 16'd0;
else if(rgb_valid)
gray_r <= (sum << 6)+(sum << 4)+(sum << 2)+ sum;
else
gray_r <= 16'd0;
end
assign gray_8b_o = gray_r[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
对该模块的仿真也相对比较简单,只需要在 testbech 中给时钟复位激励以及 RGB 三通道的图像数据即可,具体代码如下,仿真中分别给 R、G、B 通道不同的是起始数据值,然后通过递增加 1 的形式改变 R、G、B 通道数据,验证设计的正确性。
initial begin
reset_p = 1;
rgb_valid = 0;
red_8b_i = 0;
green_8b_i = 0;
blue_8b_i = 0;
#(`CLK_PERIOD*200+1);
reset_p = 0;
red_8b_i = 56;
green_8b_i = 124;
blue_8b_i = 203;
#2000;
rgb_valid = 1;
repeat(256)begin
#(`CLK_PERIOD)
red_8b_i = red_8b_i + 1;
green_8b_i = green_8b_i + 1;
blue_8b_i = blue_8b_i + 1;
end
rgb_valid = 0;
#2000;
$stop;
end
为了能验证采用平均值法实现彩色图像灰度化计算的正确性,在仿真代码中加入了如下代码。直接通过平均法的原始公式产生一组对比数据。
always@(posedge clk)
begin
if(rgb_valid == 1'b1)
comp1_gray <= (red_8b_i + green_8b_i + blue_8b_i)/3;
else
comp1_gray <= 0;
end
这样可以比较容易的通过对比 comp1_gray 和 gray_8b_o 的值来验证设计模块的正确性。其仿真波形图如下:
在后面有个地方可以看到从一个地方开始 comp1_gray 和 gray_8b_o 的值相差 1,可以分析下出现这个问题的原因,其实 gray_8b_o 和 comp1_gray 的计算公式并非完全一样,gray_8b_o 的计算公式是为了避免除法做了一定的近似,而在仿真文件中 comp1_gray 是直接求的平均。可以通过 red、green、blue 这 3 个数据分别对 gray_8b_o 和 comp1_gray 进行计算,计算结果与仿真波形结果是一致的。这也说明了,避免除法做近似处理计算的 gray 和直接通过除法求的平均值 comp1_gray 是稍存在偏差的。这个是可以接收的误差范围。
方法 4 加权平均法
根据重要性及其它指标,将三个分量以不同的权值进行加权平均。有一个很著名的心理学公式:
这里 0.299+0.587+0.114=1,刚好是满偏,这是通过不同的敏感度以及经验总结出来的公式,一般可以直接用这个。在实际应用时,为了能避免低速的浮点运算以及除法运算,可以先将式子缩放 1024 倍来实现运算算法,如下:
通过近似取整处理后得到近似公式如下。
式子中除以 1024(这里是 2 的 n 次方就可以,n 不同,结果会略微有差别)可以采用移位方法来处理,式子变为如下:
也可以压缩到 8 位以内,式子变为如下。具体压缩到多少位可以根据实际需求。
加权平均法的实现
对于加权平均法可通过两种方式实现,公式直接计算法和查找表法。
公式直接计算法
公式直接计算法与方法 3 实现类似,通过转换公式直接进行计算,只是具体计算数值发生了变化,同样乘法采用移位相加的方式实现。具体代码如下:
//典型灰度转换公式 Gray = R*0.299+G*0.587+B*0.114=(R*77 + G*150 + B*29) >>8
wire [15:0]red_x77;
wire [15:0]green_x150;
wire [15:0]blue_x29;
reg [15:0]sum;
//乘法转换成移位相加方式
assign red_x77 = (red_8b_i << 6) + (red_8b_i << 3) + (red_8b_i <<
2) + red_8b_i;
assign green_x150 = (green_8b_i<< 7) + (green_8b_i<< 4) + (green_8b_i<<
2) + (green_8b_i<<1);
assign blue_x29 = (blue_8b_i << 4) + (blue_8b_i << 3) + (blue_8b_i <<
2) + blue_8b_i;
always@(posedge clk or posedge reset_p)
begin
if(reset_p)
sum <= 16'd0;
else if(rgb_valid)
sum <= red_x77 + green_x150 + blue_x29;
else
sum <= 16'd0;
end
assign gray_8b_o = sum[15:8];
always@(posedge clk)
begin
gray_valid <= rgb_valid;
gray_hs <= rgb_hs;
gray_vs <= rgb_vs;
end
公查找表法
通过观察计算公式发现,R、G、B 数据值均乘以了一个定值,然后对乘法之后的结果相加,最后右移 8 位,上面采用直接计算法实现是对常数乘法采用的移位相加方法计算,对于这类固定范围内的数值,同时可取数据不多情况下(这里 R、G、B 数值范围在 0~255,可取的数据有限)乘以一个常数,可以采用查找表方法实现,该方法主要的优势是直接通过访问 ROM 内的数据,相对使用移位相加实现乘法使用的 LUT 资源会少点,但占用的存储器会更多。因为需要将 R、G、B 乘以系数之后的数值存储在 ROM 中,然后通过读取 ROM方式来得到计算之后的数值。这里使用 Vivado 添加 3 个 ROM IP 核,分别通过 R75、G147、B36(0≤R≤255,0≤G≤255,0≤B≤255)的计算值建立 3 个初始化 coe 文件,然后在ROM IP 核中分别添加 coe 文件进行初始化。具体代码如下:代码中 rom_red_x77、rom_green_x150、rom_blue_x29 分别存储着 R 75、G147、B36(0≤R≤255,0≤G≤255,0≤B≤255)256 个数值。
//查找表方式,可以省去公式法中乘法运算 Gray =(R*77 + G*150 + B*29) >>8,将 3 个分量
乘以系数后的数值存储在 ROM 中
wire [14:0]red_x77;
wire [15:0]green_x150;
wire [13:0]blue_x29;
reg [15:0]sum;
reg rgb_valid_dly1;
reg rgb_hs_dly1;
reg rgb_vs_dly1;
rom_red_x77 rom_red_x77(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (red_8b_i ), // input wire [7 : 0] addra
.douta (red_x77 ) // output wire [14 : 0] douta
);
rom_green_x150 rom_green_x150(
.clka (clk ), // input wire clka
.ena (rgb_valid ), // input wire ena
.addra (green_8b_i ), // in