图像饱和度调节FPGA实现

文章目录

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)
);