本节主要介绍了FPGA图像处理中常用的图像增强算法,包括直方图均衡算法、指数对比增强算法、Gamma映射算法,以及它的MATLAB与FPGA实现。
直方图均衡算法
直方图均衡的基本原理,就是对在图像中像素个数多的灰度值(即对画面起主要作用的灰度值)进行拉伸,而对像素个数少的灰度值(即对画面不起主要作用的灰度值)进行合并,从而提高对比度,使图像清晰,达到增强的目的。
图像的视觉效果与直方图有直接的对应关系,改变直方图的分布,对图像的视觉效果也有很大的影响
图像$f(x,y)$为灰度直方图的一维离散函数,首先,将图像中每个像素的数量,即$f(x,y)$中灰度级为$k$的像素个数,表示为$n(k)$,这也对于这直方图中显示的纵坐标高度,进一步计算灰度级数出现的频率$P(k)$,计算公式如下(其中,$N$为图像的像素总数量):
那么,$P(k) = \frac{n(k)}N$
其次,计算原始图像灰度累计分布的概率$s(k)$,即截止当前像素个数占总个数的比例,计算公式为:$s(k)=\sum_{i=0}^k\frac{n(i)}N$
整体的概率分布=1,那么将数值扩大255倍后,我们将概率拉伸到0~255,最终得到均衡后的直方图图像的概率,计算公式如下:
$$
s(k)’ = s(k)\times 255 = \sum_{i = 0}^k\frac{n(i)}N\times 255
$$
当原始图像整体偏亮的时候,直方图均衡就不能得到很好的效果。直方图均衡时一种全局处理方式。主要有如下缺点:
- 直方图均衡后,图像灰度级数减少,部分细节容易丢失
- 对于直方图有高峰时,对比度拉伸后将出现对比度不自然的过分增强现象。
1.直方图均衡的MATLAB实现
基本思路就是首先计算归一化后灰度级数概率的累计值,再将结果拉伸到$0\sim 255$,因此直方图均衡,也称为直方图拉伸
具体实现方式如下:
- 计算当前灰度图$0\sim 255$级数的像素数量
- 计算$0\sim 255$级数像素数量的累计值,即截止当前灰度级数的累积像素个数
- 将累计值除以hw后归一化,将结果扩大$[0,255]$,以当前测试$500\times 500$的图像为例,$hw/255\approx 980$
matlab代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77% -------------------------------------------------------------------------
% Read PC image to Matlab
IMG1 = imread('../../0_images/gsls_test1.tif'); % 读取jpg图像
% IMG1 = rgb2gray(IMG1);
h = size(IMG1,1); % 读取图像高度
w = size(IMG1,2); % 读取图像宽度
% ----------------------------------------------
% Step1: 进行像素灰度级数统计
NumPixel = zeros(1,256); %统计0-255灰度级数
for i = 1:h
for j = 1: w
NumPixel(IMG1(i,j) + 1) = NumPixel(IMG1(i,j) + 1) + 1;
end
end
% Step2: 进行像素灰度级数累积统计
CumPixel = zeros(1,256);
for i = 1:256
if i == 1
CumPixel(i) = NumPixel(i);
else
CumPixel(i) = CumPixel(i-1) + NumPixel(i);
end
end
% Step3: 对灰度值进行映射(均衡化) = 归一化 + 扩大到255
IMG2 = zeros(h,w);
for i = 1:h
for j = 1: w
% IMG2(i,j) = CumPixel(IMG1(i,j)+1)/(h*w)*255;
IMG2(i,j) = CumPixel(IMG1(i,j)+1)/980;
% IMG2(i,j) = bitshift(CumPixel(IMG1(i,j)+1),-10);
end
end
IMG2 = uint8(IMG2);
% -------------------------------------------------------------------------
% IMG2 = rgb2gray(IMG1); % 转灰度图像
% figure;
subplot(231), imshow(IMG1); title('Original Image');
subplot(234), imhist(IMG1); title('Original Hist');
% ----------------------------------------------
% Step1: 进行像素灰度级数统计
NumPixel2 = zeros(1,256); %统计0-255灰度级数
for i = 1:h
for j = 1: w
NumPixel2(IMG2(i,j) + 1) = NumPixel2(IMG2(i,j) + 1) + 1;
end
end
% Step2: 进行像素灰度级数累积统计
CumPixel2 = zeros(1,256);
for i = 1:256
if i == 1
CumPixel2(i) = NumPixel2(i);
else
CumPixel2(i) = CumPixel2(i-1) + NumPixel2(i);
end
end
subplot(232), imshow(IMG2); title('Manual HistEQ Image');
subplot(235), imhist(IMG2); title('Manual HistEQ Hist');
% ----------------------------------------------
% Matlab自带函数计算
IMG3 = zeros(h,w);
IMG3 = histeq(IMG1); % Matlab自带直方图均衡
subplot(233), imshow(IMG3); title('Matlab HistEQ Image');
subplot(236), imhist(IMG3); title('Matlab HistEQ Hist');
% ----------------------------------------------
figure;
subplot(121),bar(CumPixel); title('原图灰度级数累积');
subplot(122),bar(CumPixel2);title('拉伸后灰度级数累积');仿真结果:
2.直方图均衡的FPGA实现
hist_stat模块的功能是进行像素的灰度级数统计和灰度级数累积统计; histEQ_proc模块的功能是对原始图像进行直方图均衡。
hist_stat模块中的设计框图:
其中灰度级数统计中的重点是预处理过程: hist_stat模块利用双端口BRAM实现灰度级数的统计, 其中像素灰度值作为BRAM的读写地址。 由于读写BRAM需要消耗1个clk周期, 所以每个像素的灰度级数统计需要消耗两个clk周期, 即从BRAM读出原来的灰度级数, 累加1后重新写入BRAM。 但为了避免灰度值相同的像素连续出现,导致BRAM的灰度级数统计异常, 灰度级数错误统计示意图, 如图3.10所示。 需要对输入像素进行预处理, 即当灰度值相同的像素连续出现时, 可以将两个像素当作1个像素来处理, 此时灰度级数需要累加2, 灰度级数正确统计示意图, 如图3.11所示。
图像灰度级数统计完成后, 通过BRAM B侧依次读出0~255灰度级的统计值, 同时通过BRAM A侧将读过统计值的地址空间清零, 为下一帧视频的灰度级数统计做准备。将0~255灰度级的统计值依次进行累加并作为结果输出。
hist_stat模块中BRAM的数据位宽取决于图像像素的总数量, 如分辨率为640×480的图像的像素总数量为307200(4B000H) , 需要用19bit的数据位宽表示, 设计中用了20bit的数据位宽, 可以进行更大分辨率图像的灰度级数统计。 由于像素灰度值直接作为BRAM的地址, 所以BRAM的深度取决于像素位宽, 设计中像素位宽为8bit, BRAM的深度为256。
histEQ_proc模块设计框图:
直方图均衡的简化公式如下:
$$
s(k)’ = round[\frac{\sum_{i=0}^k n(i)}{980}] = round[\sum_{i=0}^k n(i)\frac{round(\frac{2^{27}}{980})}{2^{27}}]=round[\sum_{i=0}^k n(i)\times 136957 >> 27]
$$将上级hist_stat模块的所有灰度级数累积统计结果通过BRAM A侧写入BRAM
在(1) 完成后, 启动直方图均衡处理, 开始从外部存储器中读取原始图像。
输入像素的灰度值作为BRAM B侧的地址, 从BRAM中读取对应的灰度级数累积统计结果 ,并于136957相乘得到mult_result,其mult_result[34:27]为整数部分,mult_result[26:0]为小数部分
对mult_result进行四舍五入运算,得到直方图均衡后的图像post_img_gray,计算公式如下:
$$
post_img_gray = mult_result[34:27]+mult_result[26];
$$
Verilog源代码:
hist_stat.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333module hist_stat
(
input wire clk ,
input wire rst_n ,
input wire img_vsync ,
input wire img_href ,
input wire [ 7:0] img_gray ,
output reg [ 7:0] pixel_level ,
output reg [19:0] pixel_level_acc_num ,
output reg pixel_level_valid
);
//----------------------------------------------------------------------
// bram signals define
wire bram_a_wenb;
wire [ 7:0] bram_a_addr;
wire [19:0] bram_a_rdata;
wire bram_b_wenb;
wire [ 7:0] bram_b_addr;
wire [19:0] bram_b_wdata;
wire [19:0] bram_b_rdata;
//----------------------------------------------------------------------
// preprocess
reg [7:0] pixel_data;
always @(posedge clk)
begin
pixel_data <= img_gray;
end
reg pixel_data_valid;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_valid <= 1'b0;
else
pixel_data_valid <= img_href;
end
reg img_vsync_dly;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
img_vsync_dly <= 1'b0;
else
img_vsync_dly <= img_vsync;
end
wire pixel_data_eop;
assign pixel_data_eop = img_vsync_dly & ~img_vsync;
//----------------------------------------------------------------------
// preprocess
reg [7:0] pixel_data_tmp;
always @(posedge clk)
begin
pixel_data_tmp <= pixel_data;
end
reg pixel_data_valid_tmp1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_valid_tmp1 <= 1'b0;
else
begin
if(pixel_data_valid == 1'b1)
begin
if(pixel_data_valid_tmp1 == 1'b0)
pixel_data_valid_tmp1 <= 1'b1;
else
begin
if(pixel_data_tmp == pixel_data)
pixel_data_valid_tmp1 <= 1'b0;
else
pixel_data_valid_tmp1 <= 1'b1;
end
end
else
pixel_data_valid_tmp1 <= 1'b0;
end
end
reg [1:0] pixel_data_cnt_tmp;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_cnt_tmp <= 2'd1;
else
begin
if((pixel_data_valid == 1'b1)&&(pixel_data_valid_tmp1 == 1'b1)&&(pixel_data_tmp == pixel_data))
pixel_data_cnt_tmp <= 2'd2;
else
pixel_data_cnt_tmp <= 2'd1;
end
end
reg pixel_data_eop_tmp;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_data_eop_tmp <= 1'b0;
else
pixel_data_eop_tmp <= pixel_data_eop;
end
//----------------------------------------------------------------------
// c1
reg [7:0] pixel_data_c1;
always @(posedge clk)
begin
pixel_data_c1 <= pixel_data;
end
reg pixel_data_eop_c1;
reg pixel_data_valid_tmp_c1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pixel_data_eop_c1 <= 1'b0;
pixel_data_valid_tmp_c1 <= 1'b0;
end
else
begin
pixel_data_eop_c1 <= pixel_data_eop_tmp;
pixel_data_valid_tmp_c1 <= pixel_data_valid_tmp1;
end
end
//----------------------------------------------------------------------
// c2 : delay 3 clock
reg [7:0] pixel_data_c2;
always @(posedge clk)
begin
pixel_data_c2 <= pixel_data_c1;
end
reg pixel_data_eop_tmp1_c2;
reg pixel_data_eop_tmp2_c2;
reg pixel_data_eop_c2;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
pixel_data_eop_tmp1_c2 <= 1'b0;
pixel_data_eop_tmp2_c2 <= 1'b0;
pixel_data_eop_c2 <= 1'b0;
end
else
begin
pixel_data_eop_tmp1_c2 <= pixel_data_eop_c1;
pixel_data_eop_tmp2_c2 <= pixel_data_eop_tmp1_c2;
pixel_data_eop_c2 <= pixel_data_eop_tmp2_c2;
end
end
//----------------------------------------------------------------------
// c3
reg bram_rw_ctrl_flag_c3;
reg [8:0] bram_rw_ctrl_cnt;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_rw_ctrl_flag_c3 <= 1'b0;
else
begin
if(pixel_data_eop_c2 == 1'b1)
bram_rw_ctrl_flag_c3 <= 1'b1;
else if(bram_rw_ctrl_cnt == 9'h100)
bram_rw_ctrl_flag_c3 <= 1'b0;
else
bram_rw_ctrl_flag_c3 <= bram_rw_ctrl_flag_c3;
end
end
reg [8:0] bram_rw_ctrl_cnt_dly;
always @(posedge clk)
begin
bram_rw_ctrl_cnt_dly <= bram_rw_ctrl_cnt;
end
always @(*)
begin
if(bram_rw_ctrl_flag_c3 == 1'b1)
bram_rw_ctrl_cnt <= bram_rw_ctrl_cnt_dly + 1'b1;
else
bram_rw_ctrl_cnt <= 9'b0;
end
wire [7:0] bram_addr_c3;
assign bram_addr_c3 = bram_rw_ctrl_cnt - 1'b1;
//----------------------------------------------------------------------
// c4
reg bram_rw_ctrl_flag_c4;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
bram_rw_ctrl_flag_c4 <= 1'b0;
else
bram_rw_ctrl_flag_c4 <= bram_rw_ctrl_flag_c3;
end
reg [7:0] bram_addr_c4;
always @(posedge clk)
begin
bram_addr_c4 <= bram_addr_c3;
end
//----------------------------------------------------------------------
// c5
reg [7:0] pixel_level_c5;
always @(posedge clk)
begin
pixel_level_c5 <= bram_addr_c4;
end
reg [19:0] pixel_level_num_c5;
always @(posedge clk)
begin
if(bram_rw_ctrl_flag_c4 == 1'b1)
pixel_level_num_c5 <= bram_b_rdata;
else
pixel_level_num_c5 <= 20'b0;
end
reg pixel_level_valid_c5;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_level_valid_c5 <= 1'b0;
else
pixel_level_valid_c5 <= bram_rw_ctrl_flag_c4;
end
//----------------------------------------------------------------------
// c6
reg [7:0] pixel_level_c6;
always @(posedge clk)
begin
pixel_level_c6 <= pixel_level_c5;
end
reg [19:0] pixel_level_acc_num_c6;
always @(posedge clk)
begin
if(pixel_level_valid_c5 == 1'b1)
begin
if(pixel_level_c5 == 8'b0)
pixel_level_acc_num_c6 <= pixel_level_num_c5;
else
pixel_level_acc_num_c6 <= pixel_level_acc_num_c6 + pixel_level_num_c5;
end
else
pixel_level_acc_num_c6 <= pixel_level_acc_num_c6;
end
reg pixel_level_valid_c6;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_level_valid_c6 <= 1'b0;
else
pixel_level_valid_c6 <= pixel_level_valid_c5;
end
//----------------------------------------------------------------------
// signal output
always @(posedge clk)
begin
pixel_level <= pixel_level_c6;
pixel_level_acc_num <= pixel_level_acc_num_c6;
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
pixel_level_valid <= 1'b0;
else
pixel_level_valid <= pixel_level_valid_c6;
end
//----------------------------------------------------------------------
// bram_a20d256_b20d256 module initialization
assign bram_a_wenb = bram_rw_ctrl_flag_c4;
assign bram_a_addr = (bram_rw_ctrl_flag_c4 == 1'b1) ? bram_addr_c4 : pixel_data_tmp;
assign bram_b_wenb = pixel_data_valid_tmp_c1;
assign bram_b_addr = (bram_rw_ctrl_flag_c3 == 1'b1) ? bram_addr_c3 : pixel_data_c2;
assign bram_b_wdata = bram_a_rdata + pixel_data_cnt_tmp;
bram_ture_dual_port
#(
.C_ADDR_WIDTH (8 ),
.C_DATA_WIDTH (20)
)
u_bram_ture_dual_port
(
.clka (clk ),
.wea (bram_a_wenb ),
.addra (bram_a_addr ),
.dina (20'b0 ),
.douta (bram_a_rdata ),
.clkb (clk ),
.web (bram_b_wenb ),
.addrb (bram_b_addr ),
.dinb (bram_b_wdata ),
.doutb (bram_b_rdata )
);
endmodulehistEQ_proc.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104module histEQ_proc
(
input wire clk ,
input wire rst_n ,
input wire [ 7:0] pixel_level ,
input wire [19:0] pixel_level_acc_num ,
input wire pixel_level_valid ,
output reg histEQ_start_flag ,
input wire per_img_vsync ,
input wire per_img_href ,
input wire [ 7:0] per_img_gray ,
output wire post_img_vsync ,
output wire post_img_href ,
output wire [ 7:0] post_img_gray
);
//----------------------------------------------------------------------
// delay 1 clock
wire bram_a_wenb;
wire [ 7:0] bram_a_addr;
wire [19:0] bram_a_wdata;
wire [ 7:0] bram_b_addr;
wire [19:0] bram_b_rdata;
assign bram_a_wenb = pixel_level_valid;
assign bram_a_addr = pixel_level;
assign bram_a_wdata = pixel_level_acc_num;
assign bram_b_addr = per_img_gray;
bram_ture_dual_port
#(
.C_ADDR_WIDTH (8 ),
.C_DATA_WIDTH (20)
)
u_bram_ture_dual_port
(
.clka (clk ),
.wea (bram_a_wenb ),
.addra (bram_a_addr ),
.dina (bram_a_wdata ),
.douta ( ),
.clkb (clk ),
.web (1'b0 ),
.addrb (bram_b_addr ),
.dinb (20'b0 ),
.doutb (bram_b_rdata )
);
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
histEQ_start_flag <= 1'b0;
else
begin
if((pixel_level_valid == 1'b1)&&(pixel_level == 8'd255))
histEQ_start_flag <= 1'b1;
else
histEQ_start_flag <= 1'b0;
end
end
//----------------------------------------------------------------------
// uint8(CumPixel/980) = round(CumPixel * 136957/2^27) , CumPixel <= 500*500
reg [34:0] mult_result;
always @(posedge clk)
begin
mult_result <= bram_b_rdata * 18'd136957;
end
reg [7:0] pixel_data;
always @(posedge clk)
begin
pixel_data <= mult_result[34:27] + mult_result[26];
end
//----------------------------------------------------------------------
// lag 3 clocks signal sync
reg [2:0] per_img_vsync_r;
reg [2:0] per_img_href_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
per_img_vsync_r <= 3'b0;
per_img_href_r <= 3'b0;
end
else
begin
per_img_vsync_r <= {per_img_vsync_r[1:0],per_img_vsync};
per_img_href_r <= {per_img_href_r[1:0],per_img_href};
end
end
//----------------------------------------------------------------------
assign post_img_vsync = per_img_vsync_r[2];
assign post_img_href = per_img_href_r[2];
assign post_img_gray = pixel_data;
endmodule仿真结果:
指数对比增强算法
指数对比度增强有很多方法,但万变不离其宗,即以一定阈值为中心,提高阈值以上的亮度,并降低阈值以下的亮度,典型的以对数对比度增强函数为例,计算公式如下:
$$
q=\frac{1}{1+(\frac{Threshold}{p})^E}\times 255
$$E的值越大,对暗区的压缩及亮区的提升程度就越大,明暗之间的对比就越明显,即E可以表示为图像对比度增强的程度。
1.指数对比度增强的MATLAB实现
matlab代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34% Read PC image to Matlab
% IMG1 = imread('../../0_images/scart.jpg'); % 读取jpg图像
IMG1 = imread('../../0_images/gsls_test1.tif'); % 读取jpg图像
% IMG1 = rgb2gray(IMG1);
h = size(IMG1,1); % 读取图像高度
w = size(IMG1,2); % 读取图像宽度
figure;
subplot(121);imshow(IMG1);title('原图');
% -------------------------------------------------------------------------
THRESHOLD = 127;
E=5;
% IMG1 = double(IMG1);
IMG2 = zeros(h,w);
for i = 1:h
for j = 1:w
IMG2(i,j) = (1./(1 + (THRESHOLD./double(IMG1(i,j))).^E)) * 255;
end
end
IMG2 = uint8(IMG2);
subplot(122);imshow(IMG2);title('对比度增强效果');
% -------------------------------------------------------------------------
figure;
subplot(231);imshow(IMG1);title('原图');
subplot(232);imshow(IMG2);title('指数对比度增强图');
subplot(235);imhist(IMG2);title('增强后直方图');
IMG3 = zeros(h,w);
IMG3 = histeq(IMG1);
subplot(234);imhist((IMG1));title('原图直方图');
subplot(233);imshow((IMG3));title('直方图拉伸图');
subplot(236);imhist(IMG3);title('拉伸后直方图');仿真结果:
对比度分析:
- 由于原始图像素主要集中在阈值127以下,因此指数对比度增强后主要是压缩了暗区,亮区并没有明显的提高。
- 但通过直方图拉伸后,将图像灰度拉伸到$0\sim 255$,明暗之间的对比度相对更明显,但一定程度上也造成了图像局部过曝的现象
直方图分析:
- 原始图像素集中在100左右,指数对比度增强后,像素拉伸到了$25\sim 150$,而直方图拉伸后,像素拉伸到了$0\sim 255$,因此从当前测试图来看,直方图拉伸后的动态范围更宽
- 不过这也因图而异,比如原图就是比较亮的图,指数对比度增强后效果差强人意,而直方图拉伸后图像过暗,损失了部分细节。
2.指数对比度增强的FPGA实现
指数对比度增强,无论是指数函数,还是各类曲线映射,其本质上就是一种像素映射操作。
指数函数、 对数函数等,实时的计算非常耗时, 并且在FPGA上也很难实现(浮点) 。 但当选定参数后, 其结果是固定的, 因此可以根据参数先计算好函数的映射结果, 再以数组的方式进行索引, 得到计算后的结果。 这种方法,通俗地讲就是查找表, 通过查找表进行Mapping操作, 可在 X 、 Y 坐标上找到各自的映射点。
在FPGA中进行Mapping操作时, 可以将数组存放在RAM或者以查找表的方式进行映射。 由于256Byte的存储不大, 同时为了提高移植的灵活度, 笔者推荐使用查找表存储的方式, 因此直接使用MATLAB生成Verilog文件。
MATLAB生成verilog文件的MATLAB代码(好神奇的做法,第一次见,学到了):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27THRESHOLD = 127;
E = 7;
fp_gray = fopen('.\Curve_Contrast_Array.v','w');
fprintf(fp_gray,'//Curve THRESHOLD = %d, E = %d\n', THRESHOLD, E);
fprintf(fp_gray,'module Curve_Contrast_Array\n');
fprintf(fp_gray,'(\n');
fprintf(fp_gray,' input\t\t[7:0]\tPre_Data,\n');
fprintf(fp_gray,' output\treg\t[7:0]\tPost_Data\n');
fprintf(fp_gray,');\n\n');
fprintf(fp_gray,'always@(*)\n');
fprintf(fp_gray,'begin\n');
fprintf(fp_gray,'\tcase(Pre_Data)\n');
Gray_ARRAY = zeros(1,256);
for i = 1 : 256
Gray_ARRAY(1,i) = (1./(1 + (THRESHOLD./(i-1)).^E)) * 255;
Gray_ARRAY(1,i) = uint8(Gray_ARRAY(1,i));
fprintf(fp_gray,'\t8''h%s : Post_Data = 8''h%s; \n',dec2hex(i-1,2), dec2hex(Gray_ARRAY(1,i),2));
end
fprintf(fp_gray,'\tendcase\n');
fprintf(fp_gray,'end\n');
fprintf(fp_gray,'\nendmodule\n');
fclose(fp_gray);
% -----------------------------------------------------------------------
% 打印变形后的映射数组Gray_ARRAY
reshape(Gray_ARRAY,16,16)生成的Verilog文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270//Curve THRESHOLD = 127, E = 7
module Curve_Contrast_Array
(
input [7:0] Pre_Data,
output reg [7:0] Post_Data
);
always@(*)
begin
case(Pre_Data)
8'h00 : Post_Data = 8'h00;
8'h01 : Post_Data = 8'h00;
8'h02 : Post_Data = 8'h00;
8'h03 : Post_Data = 8'h00;
8'h04 : Post_Data = 8'h00;
8'h05 : Post_Data = 8'h00;
8'h06 : Post_Data = 8'h00;
8'h07 : Post_Data = 8'h00;
8'h08 : Post_Data = 8'h00;
8'h09 : Post_Data = 8'h00;
8'h0A : Post_Data = 8'h00;
8'h0B : Post_Data = 8'h00;
8'h0C : Post_Data = 8'h00;
8'h0D : Post_Data = 8'h00;
8'h0E : Post_Data = 8'h00;
8'h0F : Post_Data = 8'h00;
8'h10 : Post_Data = 8'h00;
8'h11 : Post_Data = 8'h00;
8'h12 : Post_Data = 8'h00;
8'h13 : Post_Data = 8'h00;
8'h14 : Post_Data = 8'h00;
8'h15 : Post_Data = 8'h00;
8'h16 : Post_Data = 8'h00;
8'h17 : Post_Data = 8'h00;
8'h18 : Post_Data = 8'h00;
8'h19 : Post_Data = 8'h00;
8'h1A : Post_Data = 8'h00;
8'h1B : Post_Data = 8'h00;
8'h1C : Post_Data = 8'h00;
8'h1D : Post_Data = 8'h00;
8'h1E : Post_Data = 8'h00;
8'h1F : Post_Data = 8'h00;
8'h20 : Post_Data = 8'h00;
8'h21 : Post_Data = 8'h00;
8'h22 : Post_Data = 8'h00;
8'h23 : Post_Data = 8'h00;
8'h24 : Post_Data = 8'h00;
8'h25 : Post_Data = 8'h00;
8'h26 : Post_Data = 8'h00;
8'h27 : Post_Data = 8'h00;
8'h28 : Post_Data = 8'h00;
8'h29 : Post_Data = 8'h00;
8'h2A : Post_Data = 8'h00;
8'h2B : Post_Data = 8'h00;
8'h2C : Post_Data = 8'h00;
8'h2D : Post_Data = 8'h00;
8'h2E : Post_Data = 8'h00;
8'h2F : Post_Data = 8'h00;
8'h30 : Post_Data = 8'h00;
8'h31 : Post_Data = 8'h00;
8'h32 : Post_Data = 8'h00;
8'h33 : Post_Data = 8'h00;
8'h34 : Post_Data = 8'h00;
8'h35 : Post_Data = 8'h01;
8'h36 : Post_Data = 8'h01;
8'h37 : Post_Data = 8'h01;
8'h38 : Post_Data = 8'h01;
8'h39 : Post_Data = 8'h01;
8'h3A : Post_Data = 8'h01;
8'h3B : Post_Data = 8'h01;
8'h3C : Post_Data = 8'h01;
8'h3D : Post_Data = 8'h01;
8'h3E : Post_Data = 8'h02;
8'h3F : Post_Data = 8'h02;
8'h40 : Post_Data = 8'h02;
8'h41 : Post_Data = 8'h02;
8'h42 : Post_Data = 8'h03;
8'h43 : Post_Data = 8'h03;
8'h44 : Post_Data = 8'h03;
8'h45 : Post_Data = 8'h04;
8'h46 : Post_Data = 8'h04;
8'h47 : Post_Data = 8'h04;
8'h48 : Post_Data = 8'h05;
8'h49 : Post_Data = 8'h05;
8'h4A : Post_Data = 8'h06;
8'h4B : Post_Data = 8'h06;
8'h4C : Post_Data = 8'h07;
8'h4D : Post_Data = 8'h07;
8'h4E : Post_Data = 8'h08;
8'h4F : Post_Data = 8'h09;
8'h50 : Post_Data = 8'h0A;
8'h51 : Post_Data = 8'h0A;
8'h52 : Post_Data = 8'h0B;
8'h53 : Post_Data = 8'h0C;
8'h54 : Post_Data = 8'h0D;
8'h55 : Post_Data = 8'h0E;
8'h56 : Post_Data = 8'h10;
8'h57 : Post_Data = 8'h11;
8'h58 : Post_Data = 8'h12;
8'h59 : Post_Data = 8'h14;
8'h5A : Post_Data = 8'h15;
8'h5B : Post_Data = 8'h17;
8'h5C : Post_Data = 8'h18;
8'h5D : Post_Data = 8'h1A;
8'h5E : Post_Data = 8'h1C;
8'h5F : Post_Data = 8'h1E;
8'h60 : Post_Data = 8'h20;
8'h61 : Post_Data = 8'h22;
8'h62 : Post_Data = 8'h24;
8'h63 : Post_Data = 8'h26;
8'h64 : Post_Data = 8'h28;
8'h65 : Post_Data = 8'h2B;
8'h66 : Post_Data = 8'h2D;
8'h67 : Post_Data = 8'h30;
8'h68 : Post_Data = 8'h33;
8'h69 : Post_Data = 8'h35;
8'h6A : Post_Data = 8'h38;
8'h6B : Post_Data = 8'h3B;
8'h6C : Post_Data = 8'h3E;
8'h6D : Post_Data = 8'h41;
8'h6E : Post_Data = 8'h44;
8'h6F : Post_Data = 8'h47;
8'h70 : Post_Data = 8'h4B;
8'h71 : Post_Data = 8'h4E;
8'h72 : Post_Data = 8'h51;
8'h73 : Post_Data = 8'h55;
8'h74 : Post_Data = 8'h58;
8'h75 : Post_Data = 8'h5C;
8'h76 : Post_Data = 8'h5F;
8'h77 : Post_Data = 8'h63;
8'h78 : Post_Data = 8'h67;
8'h79 : Post_Data = 8'h6A;
8'h7A : Post_Data = 8'h6E;
8'h7B : Post_Data = 8'h71;
8'h7C : Post_Data = 8'h75;
8'h7D : Post_Data = 8'h78;
8'h7E : Post_Data = 8'h7C;
8'h7F : Post_Data = 8'h80;
8'h80 : Post_Data = 8'h83;
8'h81 : Post_Data = 8'h86;
8'h82 : Post_Data = 8'h8A;
8'h83 : Post_Data = 8'h8D;
8'h84 : Post_Data = 8'h91;
8'h85 : Post_Data = 8'h94;
8'h86 : Post_Data = 8'h97;
8'h87 : Post_Data = 8'h9A;
8'h88 : Post_Data = 8'h9D;
8'h89 : Post_Data = 8'hA1;
8'h8A : Post_Data = 8'hA4;
8'h8B : Post_Data = 8'hA7;
8'h8C : Post_Data = 8'hA9;
8'h8D : Post_Data = 8'hAC;
8'h8E : Post_Data = 8'hAF;
8'h8F : Post_Data = 8'hB2;
8'h90 : Post_Data = 8'hB4;
8'h91 : Post_Data = 8'hB7;
8'h92 : Post_Data = 8'hB9;
8'h93 : Post_Data = 8'hBC;
8'h94 : Post_Data = 8'hBE;
8'h95 : Post_Data = 8'hC0;
8'h96 : Post_Data = 8'hC2;
8'h97 : Post_Data = 8'hC5;
8'h98 : Post_Data = 8'hC7;
8'h99 : Post_Data = 8'hC9;
8'h9A : Post_Data = 8'hCA;
8'h9B : Post_Data = 8'hCC;
8'h9C : Post_Data = 8'hCE;
8'h9D : Post_Data = 8'hD0;
8'h9E : Post_Data = 8'hD2;
8'h9F : Post_Data = 8'hD3;
8'hA0 : Post_Data = 8'hD5;
8'hA1 : Post_Data = 8'hD6;
8'hA2 : Post_Data = 8'hD8;
8'hA3 : Post_Data = 8'hD9;
8'hA4 : Post_Data = 8'hDB;
8'hA5 : Post_Data = 8'hDC;
8'hA6 : Post_Data = 8'hDD;
8'hA7 : Post_Data = 8'hDE;
8'hA8 : Post_Data = 8'hDF;
8'hA9 : Post_Data = 8'hE1;
8'hAA : Post_Data = 8'hE2;
8'hAB : Post_Data = 8'hE3;
8'hAC : Post_Data = 8'hE4;
8'hAD : Post_Data = 8'hE5;
8'hAE : Post_Data = 8'hE6;
8'hAF : Post_Data = 8'hE7;
8'hB0 : Post_Data = 8'hE7;
8'hB1 : Post_Data = 8'hE8;
8'hB2 : Post_Data = 8'hE9;
8'hB3 : Post_Data = 8'hEA;
8'hB4 : Post_Data = 8'hEB;
8'hB5 : Post_Data = 8'hEB;
8'hB6 : Post_Data = 8'hEC;
8'hB7 : Post_Data = 8'hED;
8'hB8 : Post_Data = 8'hED;
8'hB9 : Post_Data = 8'hEE;
8'hBA : Post_Data = 8'hEE;
8'hBB : Post_Data = 8'hEF;
8'hBC : Post_Data = 8'hF0;
8'hBD : Post_Data = 8'hF0;
8'hBE : Post_Data = 8'hF1;
8'hBF : Post_Data = 8'hF1;
8'hC0 : Post_Data = 8'hF2;
8'hC1 : Post_Data = 8'hF2;
8'hC2 : Post_Data = 8'hF3;
8'hC3 : Post_Data = 8'hF3;
8'hC4 : Post_Data = 8'hF3;
8'hC5 : Post_Data = 8'hF4;
8'hC6 : Post_Data = 8'hF4;
8'hC7 : Post_Data = 8'hF4;
8'hC8 : Post_Data = 8'hF5;
8'hC9 : Post_Data = 8'hF5;
8'hCA : Post_Data = 8'hF5;
8'hCB : Post_Data = 8'hF6;
8'hCC : Post_Data = 8'hF6;
8'hCD : Post_Data = 8'hF6;
8'hCE : Post_Data = 8'hF7;
8'hCF : Post_Data = 8'hF7;
8'hD0 : Post_Data = 8'hF7;
8'hD1 : Post_Data = 8'hF7;
8'hD2 : Post_Data = 8'hF8;
8'hD3 : Post_Data = 8'hF8;
8'hD4 : Post_Data = 8'hF8;
8'hD5 : Post_Data = 8'hF8;
8'hD6 : Post_Data = 8'hF9;
8'hD7 : Post_Data = 8'hF9;
8'hD8 : Post_Data = 8'hF9;
8'hD9 : Post_Data = 8'hF9;
8'hDA : Post_Data = 8'hF9;
8'hDB : Post_Data = 8'hF9;
8'hDC : Post_Data = 8'hFA;
8'hDD : Post_Data = 8'hFA;
8'hDE : Post_Data = 8'hFA;
8'hDF : Post_Data = 8'hFA;
8'hE0 : Post_Data = 8'hFA;
8'hE1 : Post_Data = 8'hFA;
8'hE2 : Post_Data = 8'hFB;
8'hE3 : Post_Data = 8'hFB;
8'hE4 : Post_Data = 8'hFB;
8'hE5 : Post_Data = 8'hFB;
8'hE6 : Post_Data = 8'hFB;
8'hE7 : Post_Data = 8'hFB;
8'hE8 : Post_Data = 8'hFB;
8'hE9 : Post_Data = 8'hFB;
8'hEA : Post_Data = 8'hFC;
8'hEB : Post_Data = 8'hFC;
8'hEC : Post_Data = 8'hFC;
8'hED : Post_Data = 8'hFC;
8'hEE : Post_Data = 8'hFC;
8'hEF : Post_Data = 8'hFC;
8'hF0 : Post_Data = 8'hFC;
8'hF1 : Post_Data = 8'hFC;
8'hF2 : Post_Data = 8'hFC;
8'hF3 : Post_Data = 8'hFC;
8'hF4 : Post_Data = 8'hFC;
8'hF5 : Post_Data = 8'hFC;
8'hF6 : Post_Data = 8'hFD;
8'hF7 : Post_Data = 8'hFD;
8'hF8 : Post_Data = 8'hFD;
8'hF9 : Post_Data = 8'hFD;
8'hFA : Post_Data = 8'hFD;
8'hFB : Post_Data = 8'hFD;
8'hFC : Post_Data = 8'hFD;
8'hFD : Post_Data = 8'hFD;
8'hFE : Post_Data = 8'hFD;
8'hFF : Post_Data = 8'hFD;
endcase
end
endmodule仿真结果:
Gamma映射算法
将原始图像通过映射操作来满足人眼亮度响应的曲线,即为Gamma曲线,响应的变换即为Gamma变换。
在ISP流程中,Gamma是很重要的一部分,为了适应人眼的亮度变化曲线,对图像传感器产生的图像进行Gamma变换,来提升图像的辨识度
图像未经Gamma变换时,低灰度值区域在较大范围内表现为同一个值,造成了信息的丢失;同时高灰度值区域又被细分,造成了存储的浪费。但图像经Gamma变换后,低灰度值区域有了更多的灰阶信息,而高灰度值区域进行了一定程度地压缩,更符合人眼的特性。
Gamma的来龙去脉很复杂,但实现却很简单,只通过简单的Mapping操作,是对输入图像灰度值进行的非线性操作,使输出图像灰度值与输入图像灰度值呈指数关系,计算公式如下:
$$
V_{out} = AV_{in}^{\gamma}(A为归一化参数)
$$由于PC的像素都是$0\sim 255$,因此对公式进一步变形,将像素扩展到$0\sim 255$,计算公式如下:
$$
f(x) = \begin{cases}
V_{out} = \frac{255}{255^{2.2}}V_{in}^{2.2} &\text{Gamma = 2.2曲线}\
V_{out’} = \frac{255}{255^{1/2.2}}V_{in}^{1/2.2} &\text{Gamma = 1/2.2曲线}\
\end{cases}
$$
1.Gamma映射的MATLAB实现
MATLAB代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44% -------------------------------------------------------------------------
% Read PC image to Matlab
IMG1 = imread('../../0_images/scart.jpg'); % 读取jpg图像
IMG1 = rgb2gray(IMG1);
% IMG1 = imread('../../0_images/gsls_test1.tif'); % 读取jpg图像
h = size(IMG1,1); % 读取图像高度
w = size(IMG1,2); % 读取图像宽度
subplot(221);imshow(IMG1);title('【1】原图');
% -------------------------------------------------------------------------
IMG2 = zeros(h,w);
for i = 1:h
for j = 1:w
IMG2(i,j) = (255/255.^2.2)*double(IMG1(i,j)).^2.2;
end
end
IMG2 = uint8(IMG2);
subplot(222);imshow(IMG2);title('【2】Gamma=2.2映射');
% -------------------------------------------------------------------------
IMG3 = zeros(h,w);
for i = 1:h
for j = 1:w
IMG3(i,j) = (255/255.^(1/2.2))*double(IMG1(i,j)).^(1/2.2);
end
end
IMG3 = uint8(IMG3);
subplot(223);imshow(IMG3);title('【3】Gamma=1/2.2映射效果');
% -------------------------------------------------------------------------
THRESHOLD = 127;
E=4;
IMG4 = zeros(h,w);
for i = 1:h
for j = 1:w
IMG4(i,j) = (1./(1 + (THRESHOLD./double(IMG1(i,j))).^E)) * 255;
end
end
IMG4 = uint8(IMG4);
subplot(224);imshow(IMG4);title('【4】对比度增强效果');仿真结果:
2.Gamma映射的FPGA实现
与指数对比增强算法一样,使用查找表的方式,采用MATLAB生成Verilog映射的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21% ----------------------------------------------------------------------
fp_gray = fopen('.\Curve_Gamma_2P2.v','w');
fprintf(fp_gray,'//Curve of Gamma = 2.2\n');
fprintf(fp_gray,'module Curve_Gamma_2P2\n');
fprintf(fp_gray,'(\n');
fprintf(fp_gray,' input\t\t[7:0]\tPre_Data,\n');
fprintf(fp_gray,' output\treg\t[7:0]\tPost_Data\n');
fprintf(fp_gray,');\n\n');
fprintf(fp_gray,'always@(*)\n');
fprintf(fp_gray,'begin\n');
fprintf(fp_gray,'\tcase(Pre_Data)\n');
Gray_ARRAY = zeros(1,256);
for i = 1 : 256
Gray_ARRAY(1,i) = (255/255.^2.2)*(i-1).^2.2;
Gray_ARRAY(1,i) = uint8(Gray_ARRAY(1,i));
fprintf(fp_gray,'\t8''h%s : Post_Data = 8''h%s; \n',dec2hex(i-1,2), dec2hex(Gray_ARRAY(1,i),2));
end
fprintf(fp_gray,'\tendcase\n');
fprintf(fp_gray,'end\n');
fprintf(fp_gray,'\nendmodule\n');
fclose(fp_gray);由于仿真方法跟指数对比增强算法一样,这里就不再赘述了
Reference
- 《基于MATLAB与FPGA的图像处理教程》图书及其配套资料