文章目录
1.什么是饱和度
色彩的饱和度Saturation指色彩的鲜艳程度,也称作纯度。纯的颜色都是高度饱和的,如鲜红、鲜绿。纯色混杂上白色、灰色或其他色调的颜色后,就变为了不饱和的颜色,如绛紫、粉红、黄褐等。完全不饱和的颜色根本没有色调,如黑白之间的各种灰色。饱和度差异对比 如下图。左边的图片拥有较高的饱和度,因此整体的色彩比较鲜艳,右边的图片拥有相对较低的饱和度,因此整体的颜色更加偏向于灰色。

2.饱和度调节原理
原理:将RGB信号转成亮度 Y 和色差C信号,色差就是每个RGB颜色分量与 Y 的距离 (C−Y)。饱和度就是这个距离的体现。用系数 k 乘上这个距离,距离越大颜色越浓。
k范围0~2。k 大于 1 更鲜艳; k=1 不变;k 小于 1 更灰白; k=0 距离归零,成为灰度图。
Cout = Y + k× (Cin − Y)
Cin:R、G、B 中任一通道;
Y:亮度分量。
具体实现时,k用放大4096倍的数值表示,即用整数乘法代替小数乘法,运算完可以右移12位将数据还原。最后的运算结果还需要防止溢出,必须限制到 0 到 255。
3.FPGA实现代码
(1)RGB转Y亮度代码
powershell
module rgb_y
(
input pix_clk ,
input rst_n ,
input [7:0] r_in ,
input [7:0] g_in ,
input [7:0] b_in ,
input din_valid ,
output dout_valid ,
output [7:0] r_out ,
output [7:0] g_out ,
output [7:0] b_out ,
output reg [7:0] y_out //{Y}
);
//====================================================================
//---------------------------------------------------------
// => Y709 = 0.213R′ + 0.715G′ + 0.072B′
//---------------------------------------------------------
// => Y709 =55/256R'+183/256G'+18/256B';
// --------+128为四舍五入操作-----------------------------
// => Y709 =(55R'+183G'+18B'+128)>>8;
// -------------------------------------------------------
// => Y709 =((64-8-1)R'+(256-64-8-1)G'+(16+2)B'+128)>>8;
// -------------------------------------------------------
wire [15:0] y_r_tmp ;
wire [15:0] y_g_tmp ;
wire [15:0] y_b_tmp ;
reg [15:0] y_r_reg = 16'd0;
reg [15:0] y_g_reg = 16'd0;
reg [15:0] y_b_reg = 16'd0;
wire [9:0] Y_tmp ;
reg dout_valid1 = 1'b0;
reg dout_valid2 = 1'b0;
reg dout_valid3 = 1'b0;
reg [7:0]Y_node;
reg [24-1:0] r_dn;
reg [24-1:0] g_dn;
reg [24-1:0] b_dn;
//====================================================================================
assign y_r_tmp = (r_in<<6) - (r_in<<3) - (r_in<<1);
assign y_g_tmp = (g_in<<8) - (g_in<<6) - (g_in<<3) - g_in;
assign y_b_tmp = (b_in<<4) + (b_in<<1);
always@(posedge pix_clk)
begin
y_r_reg <= y_r_tmp ;
y_g_reg <= y_g_tmp ;
y_b_reg <= y_b_tmp ;
end
// --------+128为四舍五入操作-----------------------------
assign Y_tmp = (y_r_reg +y_g_reg +y_b_reg+8'd128 )>>8;
//===========================================================
//overflow control
always@(posedge pix_clk or negedge rst_n)
begin
if(!rst_n)
Y_node<=8'b0;
else if(Y_tmp[9:8]==2'b00)
Y_node<=Y_tmp[7:0];
else if(Y_tmp[9:8]==2'b01)
Y_node<=8'hff;
else
Y_node<=8'h00;
end
always@(posedge pix_clk or negedge rst_n)
begin
if(!rst_n)
y_out<=8'b0;
else
y_out<=Y_node;
end
//-----------------------------------------------------
always@(posedge pix_clk)
begin
dout_valid1<=din_valid;
dout_valid2<=dout_valid1;
dout_valid3<=dout_valid2;
end
always@(posedge pix_clk)
begin
r_dn<={r_dn[15:0],r_in};
g_dn<={g_dn[15:0],g_in};
b_dn<={b_dn[15:0],b_in};
end
assign dout_valid = dout_valid3;
assign r_out = r_dn[23:16];
assign g_out = g_dn[23:16];
assign b_out = b_dn[23:16];
endmodule
(2)饱和度计算代码
powershell
module rgb_separate_saturate
#(
parameter DATA_WIDTH = 8, // 输入RGB位宽 0~255
parameter COEF_WIDTH = 13 // 饱和度系数位宽 Q1.12,1位整数,12位小数
)
(
input wire clk,
input wire rst_n,
// 输入像素
input wire [DATA_WIDTH-1:0] din_r,
input wire [DATA_WIDTH-1:0] din_g,
input wire [DATA_WIDTH-1:0] din_b,
input wire din_valid,
// 独立饱和度系数 Q12
input wire [COEF_WIDTH-1:0] sat_r,
input wire [COEF_WIDTH-1:0] sat_g,
input wire [COEF_WIDTH-1:0] sat_b,
// 输出像素
output reg [DATA_WIDTH-1:0] dout_r,
output reg [DATA_WIDTH-1:0] dout_g,
output reg [DATA_WIDTH-1:0] dout_b,
output reg dout_valid
);
// ===================== Stage1: 计算灰度 & 各通道差值 =====================
wire [DATA_WIDTH-1:0] r; // R-gray 有符号差值 -255~255
wire [DATA_WIDTH-1:0] g;
wire [DATA_WIDTH-1:0] b;
wire [DATA_WIDTH-1:0] gray;
wire stage0_valid;
reg signed [DATA_WIDTH:0] delta_r; // R-gray 有符号差值 -255~255
reg signed [DATA_WIDTH:0] delta_g;
reg signed [DATA_WIDTH:0] delta_b;
reg signed [DATA_WIDTH:0] gray1;
reg stage1_valid;
rgb_y rgb_y_inst
(
.pix_clk (clk ),
.rst_n (rst_n ),
.r_in (din_r ),
.g_in (din_g ),
.b_in (din_b ),
.din_valid (din_valid),
.dout_valid (stage0_valid),
.r_out (r),
.g_out (g),
.b_out (b),
.y_out (gray) //{Y}
);
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
delta_r <= 'd0;
delta_g <= 'd0;
delta_b <= 'd0;
gray1 <= 'd0;
stage1_valid <= 1'b0;
end else begin
delta_r <= $signed({1'b0, r}) - $signed({1'b0, gray});
delta_g <= $signed({1'b0, g}) - $signed({1'b0, gray});
delta_b <= $signed({1'b0, b}) - $signed({1'b0, gray});
gray1 <= gray;
stage1_valid <= stage0_valid;
end
end
// ===================== Stage2: 定点乘法 k * delta =====================
reg signed [COEF_WIDTH+DATA_WIDTH:0] mul_r;
reg signed [COEF_WIDTH+DATA_WIDTH:0] mul_g;
reg signed [COEF_WIDTH+DATA_WIDTH:0] mul_b;
reg signed [DATA_WIDTH:0] gray2;
reg stage2_valid;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
mul_r <= 'd0;
mul_g <= 'd0;
mul_b <= 'd0;
gray2 <= 'd0;
stage2_valid <= 1'b0;
end else begin
mul_r <= delta_r * $signed({1'b0, sat_r});
mul_g <= delta_g * $signed({1'b0, sat_g});
mul_b <= delta_b * $signed({1'b0, sat_b});
gray2 <= gray1;
stage2_valid <= stage1_valid;
end
end
// ===================== Stage3: 灰度 + 右移还原整数 =====================
reg signed [DATA_WIDTH+3:0] temp_r;
reg signed [DATA_WIDTH+3:0] temp_g;
reg signed [DATA_WIDTH+3:0] temp_b;
reg stage3_valid;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
temp_r <= 'd0;
temp_g <= 'd0;
temp_b <= 'd0;
stage3_valid <= 1'b0;
end else begin
// 右移Q12位,加上基础灰度
temp_r <= $signed({1'b0, gray2}) + (mul_r >>> (COEF_WIDTH-1));
temp_g <= $signed({1'b0, gray2}) + (mul_g >>> (COEF_WIDTH-1));
temp_b <= $signed({1'b0, gray2}) + (mul_b >>> (COEF_WIDTH-1));
stage3_valid <= stage2_valid;
end
end
// ===================== Stage4: 限幅输出 [0,255] =====================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dout_r <= 'd0;
dout_g <= 'd0;
dout_b <= 'd0;
dout_valid <= 1'b0;
end else begin
// R限幅
if(temp_r < 0)
dout_r <= 'd0;
else if(temp_r > 255)
dout_r <= 'd255;
else
dout_r <= temp_r[DATA_WIDTH-1:0];
// G限幅
if(temp_g < 0)
dout_g <= 'd0;
else if(temp_g > 255)
dout_g <= 'd255;
else
dout_g <= temp_g[DATA_WIDTH-1:0];
// B限幅
if(temp_b < 0)
dout_b <= 'd0;
else if(temp_b > 255)
dout_b <= 'd255;
else
dout_b <= temp_b[DATA_WIDTH-1:0];
dout_valid <= stage3_valid;
end
end
endmodule
4.效果对比
(1)饱和度提升效果
sat_r,sat_g,sat_b对应RGB 的3个分量的饱和度系数,4096对应1,保持原图效果,最大取8191。调用模块,饱和度系数取6000时效果。
powershell
rgb_separate_saturate
#(
.DATA_WIDTH (8 ), // 输入RGB位宽 0~255
.COEF_WIDTH (13) // 饱和度系数位宽 Q1.12,1位整数,12位小数 4096对应1
)
rgb_separate_saturate_inst
(
.clk (pixel_clock),
.rst_n (rst_n ),
.din_r (data0_r),
.din_g (data0_g),
.din_b (data0_b),
.din_valid (data_valid),
.sat_r (6000),
.sat_g (6000),
.sat_b (6000),
.dout_r (sat_r ),
.dout_g (sat_g ),
.dout_b (sat_b ),
.dout_valid(sat_valid)
);

(2)饱和度降低效果
调用模块,饱和度系数取2000时效果。
powershell
rgb_separate_saturate
#(
.DATA_WIDTH (8 ), // 输入RGB位宽 0~255
.COEF_WIDTH (13) // 饱和度系数位宽 Q1.12,1位整数,12位小数 4096对应1
)
rgb_separate_saturate_inst
(
.clk (pixel_clock),
.rst_n (rst_n ),
.din_r (data0_r),
.din_g (data0_g),
.din_b (data0_b),
.din_valid (data_valid),
.sat_r (2000),
.sat_g (2000),
.sat_b (2000),
.dout_r (sat_r ),
.dout_g (sat_g ),
.dout_b (sat_b ),
.dout_valid(sat_valid)
);

(3)单颜色分量饱和度调节效果
当然也可以只对单个颜色分量调节饱和度,比如只对蓝色分量调节。调用模块,sat_b饱和度系数取8000时效果。
powershell
rgb_separate_saturate
#(
.DATA_WIDTH (8 ), // 输入RGB位宽 0~255
.COEF_WIDTH (13) // 饱和度系数位宽 Q1.12,1位整数,12位小数 4096对应1
)
rgb_separate_saturate_inst
(
.clk (pixel_clock),
.rst_n (rst_n ),
.din_r (data0_r),
.din_g (data0_g),
.din_b (data0_b),
.din_valid (data_valid),
.sat_r (4096),
.sat_g (4096),
.sat_b (8000),
.dout_r (sat_r ),
.dout_g (sat_g ),
.dout_b (sat_b ),
.dout_valid(sat_valid)
);
