目录
第一部分、关于白平衡的知识
1、MATLAB 自动白平衡算法的实现
1.1、matlab代码
1.2、测试效果
1.3 测试源图
2、为什么摄像头采集的图像要做白平衡
3、自动白平衡算法总结
4、FPGA设计思路
4.1、实时白平衡的实现
4.2、计算流程优化思路
第二部分、硬件实现
1、除法IP核的调用方法
2、乘法IP核的调用方法
3、verilog代码
第三部分、实现结果
1、白平衡前后对比
2、总结
第一部分、关于白平衡的知识
1、MATLAB 自动白平衡算法的实现
大家先测试下面这段自动白平衡MATLAB代码,代码来源于以下这篇博客,我只不过加上了注释,更多细节请大家参考这篇博客:图像白平衡原理及实现-CSDN博客
1.1、matlab代码
%%白平衡与色温紧密相关,不同色温光源下图像会呈现不同程度的偏色
%%由于人眼独特的适应性,在不同光照条件下观看物体时不会出现偏色,而就没这么先进了
%%蓝色光色温高,红色光色温低
%CSDN:https://blog.csdn.net/helimin12345/article/details/78255669
clc;
clear all;
close all;
tic;%用来记录程序的使用时间,tic 程序 toc
% imgSrc = imread('green1.jpg');
imgSrc = imread('test2.png');
%定义一个与图像大小一样的三位变量
imgDst = imgSrc;
%将RGB三个通道的数据进行分离
imgR = imgSrc(:,:,1);%单个通道的数据默认 uint8(0~255)
imgG = imgSrc(:,:,2);
imgB = imgSrc(:,:,3);
%对RGB三个通道的像素求均值,相当于mean( mean( A ) ) 即对整一个矩阵求像素平均值
RAve = mean2(imgR);
GAve = mean2(imgG);
BAve = mean2(imgB);
aveGray = (RAve + GAve + BAve) / 3;%比较系数
%计算三个通道的增益系数
RCoef = aveGray / RAve;
GCoef = aveGray / GAve;
BCoef = aveGray / BAve;
%使用增益系数来调整原始图
%注意:其实这里应该是存在一个比较的过程(小于255,那就是当前值;大于255,那就等于255),只不过uint8超出了255自动溢出了
%巧妙的运用溢出,会减少很多计算量
RCorrection = RCoef * imgR;
GCorrection = GCoef * imgG;
BCorrection = BCoef * imgB;
%白平衡后的图像
imgDst(:,:,1) = RCorrection;
imgDst(:,:,2) = GCorrection;
imgDst(:,:,3) = BCorrection;
%显示两张图片
figure(1),imshow(imgSrc),title('original image');
figure(2),imshow(imgDst),title('white balanced image');
1.2、测试效果
关于这两张测试照片,我也会放在文末。先看效果
1.3 测试源图
我也是网上从别人博客保存的,找不到原博客了😂
2、为什么摄像头采集的图像要做白平衡
人眼对白色很敏感, 在不同色温下,都能准确判断出白色。例如下面那张图,色温比较高的时候会偏蓝,色温比较低的的时候会偏红。
当摄像头在不同色温的光源下,采集的图像会出现不同程度的偏色,与人眼看到的颜色不一致,因此需要进行白平衡处理。(还有一点,就是我感觉CMOS摄像头就算在正常光源下,采集的图像也是偏绿的,不知道是不是因为选用的Bayer转RGB的算法太垃圾了,还是什么别的原因。关于Bayer转RGB的算法大家可以看这篇:基于FPGA的Bayer转RGB算法实现)
但是目前稍微好一点的摄像头模组,都包含了自动白平衡算法,都不需要做处理。
3、自动白平衡算法总结
把上面的matlab代码仔细读几遍,读明白之后,总结出白平衡算法的步骤:
step1、分别对图像的R、G、B三通道的数据进行求和得到Rsum、Gsum、Bsum;
step2、获取图像的R、G、B三通道的平均值Rv,Gv,Bv;imag_width:当前图像的宽,
imag_high:当前图像的高度。
Rv = Rsum /(imag_width*imag_high)
Gv = Gsum /(imag_width*imag_high)
Bv = Bsum /(imag_width*imag_high)
step3、将求得的Rv、Gv、Bv 进行加和取平均值,得到Kv = (Rv + Gv + Bv) / 3;
step4、分别将R、G、B三通道的数据带入公式进行计算,得到新的值
R通道: Rnew = R * Kv / Rv; if(Rnew >255) Rnew = 255;
G通道:Gnew = G * Kv / Gv;if(Gnew >255) Gnew = 255;
B通道: Bnew = B * Kv / Bv; if(Bnew >255) Bnew = 255;
step5、最后将计算后的图片显示出来,便是白平衡后的图像。
4、FPGA设计思路
4.1、实时白平衡的实现
FPGA的摄像头采集的是实时的图像数据,每秒采集30帧,那么如何将FPGA获取的图像进行白平衡处理呢?
解决办法:就是计算当前帧图像的Rv,Gv,Bv,Kv将计算的结果留给下一帧图像使用,下一帧的Rv,Gv,Bv,Kv计算结果又给下下一帧使用...一直循环,就实现了实时的图像白平衡处理。
4.2、计算流程优化思路
正常情况下,进行除法可以调用除法IP核来解决。但是当除数很大且接近于2^N时,这时就可以用截位的方式来代替除法核。
例如对于分辨率为1920*1080图片,Rv = Rsum /(1920*1080),而1920*1080 = 2,073,600非常接近于2^21 = 2,097,152。
如果我用2^21次幂来代替1920*1080,那么只需要去除Rsum的低21位即可,直接截位,都不需要移位。这这,太牛逼了!!!
计算误差方法:由于误差很小,而且对于图像处理也不需要那么高的精度,所以该方法可行。
(2^21 - (1920*1080)) / (1920*1080)
= (2,097,153 - 2,073,600) / 2,073,600
≈ 0.0113585 ≈ 1.14%
第二部分、硬件实现
1、除法IP核的调用方法
第一步、搜索,输入divider generator
第二步、配置第二个界面
第三步、配置第三个界面
注意:这里的Latency配置为自动模式
第四步、整数有效位宽和余数有效位宽
注意:这里的位宽需要记住,方便后面截位数据
2、乘法IP核的调用方法
第一步、搜索multiplier
第二步、配置第二个界面
第三步、配置Output and control界面
注意:这里是几级流水,那么输出就有几个时钟周期的latency。这里选用系统推荐的流水级数,当然也可以自定手动修改流水级数,级数越多时序越好,延迟越多,因此实际开发要是情况而定。
3、verilog代码
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author : BigFartPeach
// CSDN : 大屁桃
// E-mail : 2624507313@qq.com
// File : white_balance_my.v
// Create : 2024-01-06 13:58:39
// -----------------------------------------------------------------------------
module white_balance_my(
input wire clk,
input wire rst,
input wire vsync,
input wire hsync,
input wire [7:0]red,
input wire [7:0]green,
input wire [7:0]blue,
output wire fra_vsy,
output reg fra_hsy,
output wire[23:0]rgb_new
);
//变量定义
/*累计这一帧,并计算上一帧的累加结果*/
reg [1:0]vsync_dly;//对vsync延迟两拍
reg vsync_fall;//检测vsync下降沿
reg [28:0]Rsum,Gsum,Bsum;//求和
reg [7:0]Ravg,Gavg,Bavg;//求均值,直接舍去低21位
wire[30:0]Ksum;//Rsum+Gsum+Bsum的和
wire[55:0]Temp_Kavg;//临时用来存储Kavg除法IP输出的结果
wire Kavg_valid;//除法IP核输出的结果有效标志
reg [30:0]Kavg;//除法IP核输出的结果
///
/*计算当前帧*/
wire [18:0]temp_red,temp_green,temp_blue;//用来存储red*Kavg、green*Kavg、blue*Kavg的值
wire [31:0]temp_red_new,temp_green_new,temp_blue_new;//用来存储除法IP输出的值,temp_red/Ravg、temp_green/Gavg、temp_blue/Bavg
wire temp_fra_hsy;//用来存储输出的red_new的valid,用作输出的fra_hsy
//凑成24bit输出
reg [7:0]red_new;
reg [7:0]green_new;
reg [7:0]blue_new;
assign rgb_new = {red_new,green_new,blue_new};//凑成24bit输出
//vsync_dly打拍
always @(posedge clk) begin
vsync_dly <= {vsync_dly[0],vsync};
end
//检测vsync下降沿
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
vsync_fall <= 1'b0;
end
else if (vsync_dly == 2'b10) begin
vsync_fall <= 1'b1;
end
else begin
vsync_fall <= 1'b0;//just one cycle clock
end
end
//Rsum,Gsum,Bsum求和 以及 清零
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
Rsum <= 'd0;
Gsum <= 'd0;
Bsum <= 'd0;
end
else if (vsync_fall == 1'b1) begin
Rsum <= 'd0;
Gsum <= 'd0;
Bsum <= 'd0;
end
else if(hsync == 1'b1)begin
Rsum <= Rsum + red;
Gsum <= Gsum + green;
Bsum <= Bsum + blue;
end
end
//Ksum求和
assign Ksum = Rsum + Gsum + Bsum;//如果用alway,相较于Rsum,Gsum,Bsum会晚一个时钟周期
//Ravg,Gavg,Bavg求均值,直接舍去Rsum,Gsum,Bsum的低21位
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
Ravg <= 'd0;
Gavg <= 'd0;
Bavg <= 'd0;
end
else if (vsync_fall == 1'b1) begin
Ravg <= Rsum[28:21];
Gavg <= Gsum[28:21];
Bavg <= Bsum[28:21];
end
end
//Kavg的除法IP,33个latency
div_gen_Ksum_div_1920x1080x3 div_gen_Kavg (
.aclk(clk), // input wire aclk
.s_axis_divisor_tvalid(vsync_fall), // input wire s_axis_divisor_tvalid
.s_axis_divisor_tdata(23'd6220800), // input wire [23 : 0] s_axis_divisor_tdata
.s_axis_dividend_tvalid(vsync_fall), // input wire s_axis_dividend_tvalid
.s_axis_dividend_tdata(Ksum), // input wire [31 : 0] s_axis_dividend_tdata
.m_axis_dout_tvalid(Kavg_valid), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(Temp_Kavg) // output wire [55 : 0] m_axis_dout_tdata
);
//锁存Kavg
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
Kavg <= 'd0;
end
else if (Kavg_valid == 1'b1) begin//可以用这个信号来锁存Kavg
Kavg <= Temp_Kavg[54:24];//截取输出的整数位
end
end
///
//进行结果计算
//red_new的计算,先算乘法,再算除法 red_new = (red*Kavg)/Ravg
//3级流水线,3个latency!!!
mult_gen_8xKavg redxKavg (
.CLK(clk), // input wire CLK
.A(red), // input wire [7 : 0] A
.B(Kavg[10:0]), // input wire [10 : 0] B(这里取11位,主要是根据老师推荐的,按道理是要去大一点,但是大多少位并没有限制)
.P(temp_red) // output wire [18 : 0] P
);
/*************************************************/
/******************解决白线问题的方法**************/
/*************************************************/
//给hsync进行打拍操作。delay 3个 latency,和上面乘法核的输出对齐
reg [2:0]hsync_dly;
always @(posedge clk) begin
hsync_dly <= {hsync_dly[1:0],hsync};
end
/*************************************************/
/*************************************************/
div_gen_0 temp_red_div_Ravg (
.aclk(clk), // input wire aclk
.s_axis_divisor_tvalid(hsync_dly[2]), // input wire s_axis_divisor_tvalid
.s_axis_divisor_tdata(Ravg), // input wire [7 : 0] s_axis_divisor_tdata
.s_axis_dividend_tvalid(hsync_dly[2]), // input wire s_axis_dividend_tvalid
.s_axis_dividend_tdata(temp_red), // input wire [23 : 0] s_axis_dividend_tdata
.m_axis_dout_tvalid(temp_fra_hsy), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(temp_red_new) // output wire [31 : 0] m_axis_dout_tdata [26:8]
);
//red进行判断
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
red_new <= 'd0;
end
else if (temp_red_new[26:8] > 'd255) begin
red_new <= 'd255;
end
else begin
red_new <= temp_red_new[15:8];//有效值肯定在这个八位区间
end
end
//green_new
mult_gen_8xKavg greenxKavg (
.CLK(clk), // input wire CLK
.A(green), // input wire [7 : 0] A
.B(Kavg[10:0]), // input wire [10 : 0] B
.P(temp_green) // output wire [18 : 0] P
);
div_gen_0 temp_green_div_Gavg (
.aclk(clk), // input wire aclk
.s_axis_divisor_tvalid(hsync_dly[2]), // input wire s_axis_divisor_tvalid
.s_axis_divisor_tdata(Gavg), // input wire [7 : 0] s_axis_divisor_tdata
.s_axis_dividend_tvalid(hsync_dly[2]), // input wire s_axis_dividend_tvalid
.s_axis_dividend_tdata(temp_green), // input wire [23 : 0] s_axis_dividend_tdata
.m_axis_dout_tvalid(), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(temp_green_new) // output wire [31 : 0] m_axis_dout_tdata
);
//进行判断
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
green_new <= 'd0;
end
else if (temp_green_new[26:8] > 'd255) begin
green_new <= 'd255;
end
else begin
green_new <= temp_green_new[15:8];
end
end
//blue_new
mult_gen_8xKavg bluexKavg (
.CLK(clk), // input wire CLK
.A(blue), // input wire [7 : 0] A
.B(Kavg[10:0]), // input wire [10 : 0] B
.P(temp_blue) // output wire [18 : 0] P
);
div_gen_0 temp_blue_div_Bavg (
.aclk(clk), // input wire aclk
.s_axis_divisor_tvalid(hsync_dly[2]), // input wire s_axis_divisor_tvalid
.s_axis_divisor_tdata(Bavg), // input wire [7 : 0] s_axis_divisor_tdata
.s_axis_dividend_tvalid(hsync_dly[2]), // input wire s_axis_dividend_tvalid
.s_axis_dividend_tdata(temp_blue), // input wire [23 : 0] s_axis_dividend_tdata
.m_axis_dout_tvalid(), // output wire m_axis_dout_tvalid
.m_axis_dout_tdata(temp_blue_new) // output wire [31 : 0] m_axis_dout_tdata
);
always @(posedge clk or posedge rst) begin
if (rst == 1'b1) begin
blue_new <= 'd0;
end
else if (temp_blue_new[26:8] > 'd255) begin
blue_new <= 'd255;
end
else begin
blue_new <= temp_blue_new[15:8];
end
end
//不是数据没有对齐的问题
always @(posedge clk) begin
fra_hsy <= temp_fra_hsy;
end
//fra_vsy,(可以随便一个)
assign fra_vsy = vsync_dly[1];
endmodule
第三部分、实现结果
1、白平衡前后对比
没有白平衡之前,CMOS采集到的图像偏绿,白平衡之后效果就好多了👍👍👍
CMOS摄像头图像白平衡之前和白平衡之后的效果 |
2、总结
这篇主要是总结了一下白平衡算法的原理的实现方法,我上面的Verilog 代码,大家只需要看明白就可以移植了。
QQ交流群聊号码1020775171,有疑问的小伙伴可以加入哦🤗🤗🤗
本专栏有很多我个人总结的比较好的文章,希望对你开发有帮助:FPGA的学习之旅_大屁桃的博客-CSDN博客