图像亮度调节FPGA实现

文章目录

1.什么是亮度

  "图像亮度"是一个横跨物理光学、数字编码和视觉感知三个领域的概念。在不同的语境下,它的定义截然不同。

(1)物理光学维度

在严格的物理学中,亮度(Luminance),指单位面积上人眼能够感知的光通量(即光强)。

定义:光源或物体表面在特定方向上的发光强度,单位是尼特(nit)或坎德拉每平方米(cd/m²)。

本质:这是光的客观物理测量值。比如,太阳的亮度极高(约16亿尼特),而一张白纸在月光下亮度极低。

(2) 数字图像处理维度

在计算机里,图像是由像素矩阵组成的,亮度指的是像素的明度值(Lightness / Intensity / Value)。

灰度图定义:亮度就是该像素的灰度级(通常为0~255,0为纯黑,255为纯白)。

彩色图定义:通常将RGB三个通道进行加权转换,公式为:Y = 0.299×R + 0.587×G + 0.114×B,这个Y值即为亮度通道,绿色权重最大,因为人眼对绿光最敏感。

(3)人类视觉感知维度

人眼对亮度的感知是非线性的,定义上被称为明度(Brightness)。

定义:人眼对物体"看起来有多亮"的主观评价。核心规律(伽马效应):人眼对暗部细节极其敏感,但对亮部的细微变化反应迟钝。

本质:物理亮度翻倍,人眼感知的明度并不会翻倍。因此数字图像存储时大多采用伽马校正(Gamma校正),即编码时故意把暗部数据压得更丰富,以匹配人眼的非线性的"亮度定义"。

2.亮度调节原理

  这里按数字图像处理方式对亮度做调节,原理:将RGB三个分量进行加权转换得到亮度Y值和色差值Cb和Cr,对Y加一个偏移量调节亮度,之后再将调节后Y值和色差值Cb和Cr转换为RGB分量。

Y_out =Y_in + ΔY

Y_in:输入的8位亮度值(范围0-255)。

Y_out:输出的8位亮度值(限制在0~255范围)。

ΔY:亮度调节值(范围-255~255)。

3.FPGA实现代码

(1)RGB转YC代码

powershell 复制代码
module rgb_yc444
(
  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 reg [7:0]  y           ,
  output reg [7:0]  cb          ,
  output reg [7:0]  cr           
);

//==================================================================== 
//---------------------------------------------------------
//  =>  Y709   =  0.213R′ + 0.715G′ + 0.072B′        
//  =>  Cb     = --0.117R′ -- 0.394G′ + 0.511B′ + 128
//  =>  Cr     =  0.511R′ -- 0.464G′ -- 0.047B′ + 128
//---------------------------------------------------------
//  =>  Y709   =55/256R'+183/256G'+18/256B';
//  =>  Cb     =-30/256R'-101/256G'+131/256B'+32768/256;
//  =>  Cr     =131/256R'-119/256G'-12/256B'+32768/256;
//  --------+128为四舍五入操作-----------------------------  
//  =>  Y709   =(55R'+183G'+18B'+128)>>8;
//  =>  Cb     =(-30R'-101G'+131B'+32768+128)>>8;
//  =>  Cr     =(131R'-119G'-12B'+32768+128)>>8;
//  -------------------------------------------------------
//  =>  Y709   =((64-8-1)R'+(256-64-8-1)G'+(16+2)B'+128)>>8;
//  =>  Cb     =(-(32-2)R'-(64+32+4+1)G'+(128+2+1)B'+32768+128)>>8;
//  =>  Cr     =((128+2+1)R'-(128-8-1)G'-(8+4)B'+32768+128)>>8;
//  -------------------------------------------------------
  
  wire [15:0] y_r_tmp ;
  wire [15:0] y_g_tmp ;
  wire [15:0] y_b_tmp ;
  wire [15:0] cb_b_tmp;
  wire [15:0] cb_g_tmp;
  wire [15:0] cb_r_tmp;
  wire [15:0] cr_r_tmp;
  wire [15:0] cr_g_tmp;
  wire [15:0] cr_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;
  reg [15:0]  cb_b_reg = 16'd0;
  reg [15:0]  cb_g_reg = 16'd0;
  reg [15:0]  cb_r_reg = 16'd0;
  reg [15:0]  cr_r_reg = 16'd0;
  reg [15:0]  cr_g_reg = 16'd0;
  reg [15:0]  cr_b_reg = 16'd0;
  
  wire [9:0]  Y_tmp ;
  wire [9:0]  Cb_tmp;
  wire [9:0]  Cr_tmp;
  
  reg dout_valid1 = 1'b0;
  reg dout_valid2 = 1'b0;
  reg dout_valid3 = 1'b0;

  
  reg  [7:0]Y_node;
  reg  [7:0]Cb_node;
  reg  [7:0]Cr_node;

//====================================================================================
  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);
  
  assign cb_r_tmp = (r_in<<5) - (r_in<<1);
  assign cb_g_tmp = (g_in<<6) + (g_in<<5) + (g_in<<2)  + g_in;
  assign cb_b_tmp = (b_in<<7) + (b_in<<1) + b_in;
  
  assign cr_r_tmp = (r_in<<7) + (r_in<<1) + r_in;
  assign cr_g_tmp = (g_in<<7) - (g_in<<3) - g_in;
  assign cr_b_tmp = (b_in<<3) + (b_in<<2);
  
  always@(posedge pix_clk)
  begin
    y_r_reg  <= y_r_tmp ;
    y_g_reg  <= y_g_tmp ;
    y_b_reg  <= y_b_tmp ;
    cb_b_reg <= cb_b_tmp;
    cb_g_reg <= cb_g_tmp;
    cb_r_reg <= cb_r_tmp;
    cr_r_reg <= cr_r_tmp;
    cr_g_reg <= cr_g_tmp;
    cr_b_reg <= cr_b_tmp;
  end

//  --------+128为四舍五入操作-----------------------------
  assign Y_tmp = (y_r_reg +y_g_reg +y_b_reg+8'd128 )>>8;
  assign Cb_tmp= (cb_b_reg-cb_g_reg-cb_r_reg+8'd128+ 32768)>>8;
  assign Cr_tmp= (cr_r_reg-cr_g_reg-cr_b_reg+8'd128+ 32768)>>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)
      Cb_node<=8'b0;
    else if(Cb_tmp[9:8]==2'b00)
      Cb_node<=Cb_tmp[7:0];
    else if(Cb_tmp[9:8]==2'b01)
      Cb_node<=8'hff;
    else
      Cb_node<=8'h00; 
  end
    
  always@(posedge pix_clk or negedge rst_n)
  begin
    if(!rst_n)     
      Cr_node<=8'b0;
    else if(Cr_tmp[9:8]==2'b00)
      Cr_node<=Cr_tmp[7:0];
    else if(Cr_tmp[9:8]==2'b01)
      Cr_node<=8'hff;
    else
      Cr_node<=8'h00;
  end
    
  always@(posedge pix_clk or negedge rst_n)
  begin
    if(!rst_n) 
      begin
        y <=8'b0;
        cb<=8'b0;
        cr<=8'b0;
      end
    else 
      begin
        y <=Y_node;
        cb<=Cb_node;
        cr<=Cr_node;
      end 
  end

//-----------------------------------------------------  
  always@(posedge pix_clk)
  begin
    dout_valid1<=din_valid;
    dout_valid2<=dout_valid1;
    dout_valid3<=dout_valid2;
  end
  
  assign dout_valid = dout_valid3;
endmodule

(2)YC转RGB代码

powershell 复制代码
module yc444_rgb
(
  input             pix_clk   ,
  input             rst_n     ,
  input      [7:0]  y         ,
  input      [7:0]  cb        ,
  input      [7:0]  cr        ,
  input             din_valid ,
  output reg        dout_valid,
  output reg [7:0]  r_o       ,
  output reg [7:0]  g_o       ,
  output reg [7:0]  b_o       
);

//==================================================================== 
//            r=y+1.540*(cr-128);                                                                                    
//            g=y-0.459*(cr-128)-0.183*(cb-128);                                                                     
//            b=y+1.816*(cb-128);                                                                                    
//-----------------------------------------------------------------                                                                                           
//            --> r=(256*y+394.24*cr-50462.72)/256;                                                                  
//            --> g=(256*y-117.504*cr-46.848*cb+21037.056)/256;                                                      
//            --> b=(256*y+464.896*cb-59506.688)/256;                                                                
//  --------+128为四舍五入操作-----------------------------                                                                                                                    
//            --> r=(256*y+394*cr-50463+128)/256;                                                                    
//            --> g=(256*y-118*cr-47*cb+21037+128)/256;                                                              
//            --> b=(256*y+465*cb-59507+128)/256;                                                                    
//----------------------------------------------------------                                                                                          
//            --> r={[256*y+(256+128+8+2)*cr]-50335}/256;                                                           
//            --> g={(256*y+21165)-[(64+32+16+4+2)*cr+(32+16-1)*cb]}/256;                                              
//            --> b={[256*y+(256+128+64+16+1)*cb]-59635]}/256;   
//----------------------------------------------------------
  
reg         rgb_tmp_en;
reg [19:0]  r_minuend;
reg [19:0]  r_subtrahend;
reg [19:0]  g_minuend;
reg [19:0]  g_subtrahend;
reg [19:0]  b_minuend;
reg [19:0]  b_subtrahend;

reg         rgb_en_flag;
reg [19:0]  rgb_r_flag;
reg [19:0]  rgb_g_flag;
reg [19:0]  rgb_b_flag;

//====================================================================================
always @(posedge pix_clk) rgb_tmp_en <= din_valid; 

always @(posedge pix_clk) r_minuend    <= (y<<8) + ((cr<<8) + (cr<<7)) + ((cr<<3) + (cr<<1));
always @(posedge pix_clk) r_subtrahend <= 17'd50335;

always @(posedge pix_clk) g_minuend    <= (y<<8) + 17'd21165;
always @(posedge pix_clk) g_subtrahend <= ((cr<<6) + (cr<<5)) + ((cr<<4) + (cr<<2)+ (cr<<1)) + ((cb<<5) + (cb<<4) - cb);

always @(posedge pix_clk) b_minuend    <= (y<<8) + ((cb<<8) + (cb<<7)) + ((cb<<6) + (cb<<4)) + cb;
always @(posedge pix_clk) b_subtrahend <= 17'd59635;

always @( posedge pix_clk or negedge rst_n)
begin
    if (rst_n==1'b0)
      begin
        rgb_en_flag <=  1'b0;
        rgb_r_flag  <=  20'h0_0000;
        rgb_g_flag  <=  20'h0_0000;
        rgb_b_flag  <=  20'h0_0000;
      end
    else 
      begin
        rgb_en_flag <= rgb_tmp_en;
        rgb_r_flag  <= (rgb_tmp_en==1'b1) ? (r_minuend-r_subtrahend) : 20'h0_0000;
        rgb_g_flag  <= (rgb_tmp_en==1'b1) ? (g_minuend-g_subtrahend) : 20'h0_0000;
        rgb_b_flag  <= (rgb_tmp_en==1'b1) ? (b_minuend-b_subtrahend) : 20'h0_0000;
      end
end

//==============================================================
//overflow calibration
always @( posedge pix_clk or negedge rst_n)
begin
    if (rst_n==1'b0)
      begin
        dout_valid <=  1'b0;
        r_o        <=  8'h00;
        g_o        <=  8'h00;
        b_o        <=  8'h00;
      end
    else 
      begin
        dout_valid  <=  rgb_en_flag;
        r_o         <=  (rgb_r_flag[17:16] ==2'b00) ? (rgb_r_flag[15:8]) : ((rgb_r_flag[17:16] ==2'b01) ? (8'hff) :  (8'h00));
        g_o         <=  (rgb_g_flag[17:16] ==2'b00) ? (rgb_g_flag[15:8]) : ((rgb_g_flag[17:16] ==2'b01) ? (8'hff) :  (8'h00));
        b_o         <=  (rgb_b_flag[17:16] ==2'b00) ? (rgb_b_flag[15:8]) : ((rgb_b_flag[17:16] ==2'b01) ? (8'hff) :  (8'h00));
      end
end
endmodule

(3)亮度调节代码

powershell 复制代码
module rgb_bright

(
    input  wire            clk,
    input  wire            rst_n,
    // 输入像素
    input  wire [8-1:0]    din_r,
    input  wire [8-1:0]    din_g,
    input  wire [8-1:0]    din_b,
    input  wire            din_valid,
    // 亮度调节
    input  wire            add_sub,//0:add,  1:sub
    input  wire [8-1:0]    delta_y,//0~255
    // 输出像素
    output      [8-1:0]    dout_r,
    output      [8-1:0]    dout_g,
    output      [8-1:0]    dout_b,
    output                 dout_valid
);

// ===================== 
wire [7:0] y ;
wire [7:0] cb;
wire [7:0] cr;
wire       yc_valid;

rgb_yc444 rgb_yc444_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  (yc_valid),
  .y           (y ),
  .cb          (cb),
  .cr          (cr) 
);

// ==========================================
reg  [9:0] sum;
reg  [7:0] y_limit;

reg  [7:0] cb_d1,cb_d2;
reg  [7:0] cr_d1,cr_d2;
reg        yc_valid_d1,yc_valid_d2;

always@(posedge clk or negedge rst_n)
begin 
  if(!rst_n)
    sum<=10'b0;
  else if(add_sub==1'b0)
    sum<= $signed({1'b0,y}) + $signed({1'b0,delta_y});
  else if(add_sub==1'b1)
    sum<= $signed({1'b0,y}) - $signed({1'b0,delta_y});
  else
    sum<=sum;
end

always@(posedge clk or negedge rst_n)
begin 
  if(!rst_n)
    y_limit<=8'b0;
  else
    y_limit<= (sum[9:8] == 2'b01) ? 8'd255 :
            (sum[9:8] == 2'b11) ? 8'd0 : 
            (sum[9:8] == 2'b00) ? sum[7:0] : 8'd0;
end

always@(posedge clk or negedge rst_n)
begin 
  if(!rst_n)
    begin
      cb_d1 <= 8'b0;
      cb_d2 <= 8'b0;
      cr_d1 <= 8'b0;
      cr_d2 <= 8'b0;
      yc_valid_d1 <= 1'b0;
      yc_valid_d2 <= 1'b0;
    end
  else
    begin
      cb_d1 <= cb   ;
      cb_d2 <= cb_d1;
      cr_d1 <= cr   ;
      cr_d2 <= cr_d1;
      yc_valid_d1 <= yc_valid   ;
      yc_valid_d2 <= yc_valid_d1;
    end
end

// ======================
yc444_rgb yc444_rgb_inst
(
  .pix_clk   (clk),
  .rst_n     (rst_n),
  .y         (y_limit ),
  .cb        (cb_d2),
  .cr        (cr_d2),
  .din_valid (yc_valid_d2),
  .dout_valid(dout_valid),
  .r_o       (dout_r),
  .g_o       (dout_g),
  .b_o       (dout_b)
);

endmodule

4.效果对比

(1)亮度提升效果

  调用模块,亮度增量取64时效果。

(2)亮度降低效果

  调用模块,亮度增量取-64时效果。