FPGA实现图像的非线性变换:对数(log)变换
〇、序章
图像增强常用的三类基本函数:线性函数(反转和恒等变换)、对数函数(对数和反对数变换)和幂律函数(n次幂和n次根变换)。如下图所示:

其中恒等变换和反转变换都属于线性变换,在之前的博客中我整理过反转变换,而直接的线性变换的效果其实不太好,分段线性变换的效果会更常用些,但分段线性变换需要输入过多参数,且对不同的图片输入的参数都要调整,比较繁琐,所以没有去研究。
现在用两篇博客来整理一下非线性变换吧。
一、对数变换理论
对数变换的一般表达式为:s = c log(1+r)
其中 c 为尺度比例常数,r 为源灰度值,s 为变换后的目标灰度值。由对数函数曲线可知,这种变换可以增强一幅图像中较暗部分的细节,从而可用来扩展被压缩的高值图像中的较暗像素,因此对数变换被广泛地应用于频谱图像的显示中。一个典型的应用是傅立叶频谱,其动态范围可能宽达0~ 10%。直接显示频谱时,图像显示设备的动态范围往往不能满足要求,从而丢失大量的暗部细节。而在使用对数变换之后,图像的动态范围被合理地非线性压缩,从而可以清晰地显示。
说的通俗一点:该变换将输入中范围较窄的低灰度值映射为输出中较宽范围的灰度值,相反地,对高的输入灰度值也是如此。我们使用这种类型的变换来扩展图像中的暗像素的值,同时压缩更高灰度级的值。反对数变换的作用与此相反。
对数函数的一般形状的任何曲线,都能完成图像灰度级的扩展/压缩,但是,下一篇博客的伽马(幂律)变换对这个则更为通用。
二、MATLAB实现
%-------------------------------------------------------------------------- %-- 灰度log变换 %-------------------------------------------------------------------------- clear all; RGB = imread('log.jpg'); %读取图片文件 gray = rgb2gray(RGB); gray_log = mat2gray(log(1+double(gray))); subplot(1,2,1);imshow(gray); xlabel('灰度图像'); subplot(1,2,2);imshow(gray_log);xlabel('对数变换');
这里把 c 取值为1,点击运行,得到如下结果:

三、FPGA实现

1、log函数查找表
%-------------------------------------------------------------------------- %-- 生成log变换所需的rom mif文件 %-------------------------------------------------------------------------- clear all close all clc depth = 256; width = 8; r = [0:1:255]; s = 45*log(1+r); fid = fopen('log2.mif','w'); fprintf(fid,'depth= %d; \n',depth); fprintf(fid,'width= %d; \n',width); fprintf(fid,'address_radix=uns;\n'); fprintf(fid,'data_radix = uns;\n'); fprintf(fid,'Content Begin \n'); z = round(s); for(k=1:depth) fprintf(fid,'%d: %d; \n',k-1,z(k)); end fprintf(fid,'end;'); %-------------------------------------------------------------------------- %-- 曲线展示 %-------------------------------------------------------------------------- hold on plot(r); plot(s); legend('原曲线','log变换'); hold off
点击运行,就可以得到 log2.mif 文件,此外可以看到这次采用的 log 曲线如图所示:

2、代码设计
有了 rom 查找表,代码设计变得异常简单,直接拿数据进查找表对照即可,代码如下所示:
//**************************************************************************
// *** 名称 : log.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : log变换
//**************************************************************************
module log
//========================< 端口 >==========================================
(
input wire clk , //时钟
input wire rst_n , //复位
//input ---------------------------------------------
input wire RGB_hsync , //RGB行同步
input wire RGB_vsync , //RGB场同步
input wire [15:0] RGB_data , //RGB数据
input wire RGB_de , //RGB数据使能
//output --------------------------------------------
output wire log_hsync , //log行同步
output wire log_vsync , //log场同步
output wire [15:0] log_data , //log数据
output wire log_de //log数据使能
);
//========================< 信号 >==========================================
wire [ 7:0] R ;
wire [ 7:0] G ;
wire [ 7:0] B ;
wire [ 7:0] log_R ;
wire [ 7:0] log_G ;
wire [ 7:0] log_B ;
reg RGB_de_r ;
reg RGB_hsync_r ;
reg RGB_vsync_r ;
//==========================================================================
//== RGB565转RGB888
//==========================================================================
assign R = {RGB_data[15:11],RGB_data[13:11]};
assign G = {RGB_data[10: 5],RGB_data[ 6: 5]};
assign B = {RGB_data[ 4: 0],RGB_data[ 2: 0]};
//==========================================================================
//== log变换
//==========================================================================
rom_log u_R
(
.address (R ),
.clock (clk ),
.q (log_R )
);
rom_log u_G
(
.address (G ),
.clock (clk ),
.q (log_G )
);
rom_log u_B
(
.address (B ),
.clock (clk ),
.q (log_B )
);
assign log_data = {log_R[7:3],log_G[7:2],log_B[7:3]};
//==========================================================================
//== 信号同步,rom给地址到出数据会延迟1clk
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
RGB_de_r <= 1'b0;
RGB_hsync_r <= 1'b0;
RGB_vsync_r <= 1'b0;
end
else begin
RGB_de_r <= RGB_de;
RGB_hsync_r <= RGB_hsync;
RGB_vsync_r <= RGB_vsync;
end
end
assign log_de = RGB_de_r;
assign log_hsync = RGB_hsync_r;
assign log_vsync = RGB_vsync_r;
endmodule
注意一下位宽,这次设计的查找表地址为 8 bit 256个数据,刚好和 Y 分量的位数相等。
rom_log 文件是自己生成的,初始化采用前面的 log2.mif 文件。
// megafunction wizard: %ROM: 1-PORT%
// GENERATION: STANDARD
// VERSION: WM1.0
// MODULE: altsyncram
// ============================================================
// File Name: rom_log.v
// Megafunction Name(s):
// altsyncram
//
// Simulation Library Files(s):
// altera_mf
// ============================================================
// ************************************************************
// THIS IS A WIZARD-GENERATED FILE. DO NOT EDIT THIS FILE!
//
// 13.0.0 Build 156 04/24/2013 SJ Full Version
// ************************************************************
//Copyright (C) 1991-2013 Altera Corporation
//Your use of Altera Corporation's design tools, logic functions
//and other software and tools, and its AMPP partner logic
//functions, and any output files from any of the foregoing
//(including device programming or simulation files), and any
//associated documentation or information are expressly subject
//to the terms and conditions of the Altera Program License
//Subscription Agreement, Altera MegaCore Function License
//Agreement, or other applicable license agreement, including,
//without limitation, that your use is for the sole purpose of
//programming logic devices manufactured by Altera and sold by
//Altera or its authorized distributors. Please refer to the
//applicable agreement for further details.
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module rom_log (
address,
clock,
q);
input [7:0] address;
input clock;
output [7:0] q;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_off
`endif
tri1 clock;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_on
`endif
wire [7:0] sub_wire0;
wire [7:0] q = sub_wire0[7:0];
altsyncram altsyncram_component (
.address_a (address),
.clock0 (clock),
.q_a (sub_wire0),
.aclr0 (1'b0),
.aclr1 (1'b0),
.address_b (1'b1),
.addressstall_a (1'b0),
.addressstall_b (1'b0),
.byteena_a (1'b1),
.byteena_b (1'b1),
.clock1 (1'b1),
.clocken0 (1'b1),
.clocken1 (1'b1),
.clocken2 (1'b1),
.clocken3 (1'b1),
.data_a ({8{1'b1}}),
.data_b (1'b1),
.eccstatus (),
.q_b (),
.rden_a (1'b1),
.rden_b (1'b1),
.wren_a (1'b0),
.wren_b (1'b0));
defparam
altsyncram_component.address_aclr_a = "NONE",
altsyncram_component.clock_enable_input_a = "BYPASS",
altsyncram_component.clock_enable_output_a = "BYPASS",
altsyncram_component.init_file = "../make the log mif/log2.mif",
altsyncram_component.intended_device_family = "Cyclone IV E",
altsyncram_component.lpm_hint = "ENABLE_RUNTIME_MOD=NO",
altsyncram_component.lpm_type = "altsyncram",
altsyncram_component.numwords_a = 256,
altsyncram_component.operation_mode = "ROM",
altsyncram_component.outdata_aclr_a = "NONE",
altsyncram_component.outdata_reg_a = "UNREGISTERED",
altsyncram_component.widthad_a = 8,
altsyncram_component.width_a = 8,
altsyncram_component.width_byteena_a = 1;
endmodule
// ============================================================
// CNX file retrieval info
// ============================================================
// Retrieval info: PRIVATE: ADDRESSSTALL_A NUMERIC "0"
// Retrieval info: PRIVATE: AclrAddr NUMERIC "0"
// Retrieval info: PRIVATE: AclrByte NUMERIC "0"
// Retrieval info: PRIVATE: AclrOutput NUMERIC "0"
// Retrieval info: PRIVATE: BYTE_ENABLE NUMERIC "0"
// Retrieval info: PRIVATE: BYTE_SIZE NUMERIC "8"
// Retrieval info: PRIVATE: BlankMemory NUMERIC "0"
// Retrieval info: PRIVATE: CLOCK_ENABLE_INPUT_A NUMERIC "0"
// Retrieval info: PRIVATE: CLOCK_ENABLE_OUTPUT_A NUMERIC "0"
// Retrieval info: PRIVATE: Clken NUMERIC "0"
// Retrieval info: PRIVATE: IMPLEMENT_IN_LES NUMERIC "0"
// Retrieval info: PRIVATE: INIT_FILE_LAYOUT STRING "PORT_A"
// Retrieval info: PRIVATE: INIT_TO_SIM_X NUMERIC "0"
// Retrieval info: PRIVATE: INTENDED_DEVICE_FAMILY STRING "Cyclone IV E"
// Retrieval info: PRIVATE: JTAG_ENABLED NUMERIC "0"
// Retrieval info: PRIVATE: JTAG_ID STRING "NONE"
// Retrieval info: PRIVATE: MAXIMUM_DEPTH NUMERIC "0"
// Retrieval info: PRIVATE: MIFfilename STRING "../make the log mif/log2.mif"
// Retrieval info: PRIVATE: NUMWORDS_A NUMERIC "256"
// Retrieval info: PRIVATE: RAM_BLOCK_TYPE NUMERIC "0"
// Retrieval info: PRIVATE: RegAddr NUMERIC "1"
// Retrieval info: PRIVATE: RegOutput NUMERIC "0"
// Retrieval info: PRIVATE: SYNTH_WRAPPER_GEN_POSTFIX STRING "0"
// Retrieval info: PRIVATE: SingleClock NUMERIC "1"
// Retrieval info: PRIVATE: UseDQRAM NUMERIC "0"
// Retrieval info: PRIVATE: WidthAddr NUMERIC "8"
// Retrieval info: PRIVATE: WidthData NUMERIC "8"
// Retrieval info: PRIVATE: rden NUMERIC "0"
// Retrieval info: LIBRARY: altera_mf altera_mf.altera_mf_components.all
// Retrieval info: CONSTANT: ADDRESS_ACLR_A STRING "NONE"
// Retrieval info: CONSTANT: CLOCK_ENABLE_INPUT_A STRING "BYPASS"
// Retrieval info: CONSTANT: CLOCK_ENABLE_OUTPUT_A STRING "BYPASS"
// Retrieval info: CONSTANT: INIT_FILE STRING "../make the log mif/log2.mif"
// Retrieval info: CONSTANT: INTENDED_DEVICE_FAMILY STRING "Cyclone IV E"
// Retrieval info: CONSTANT: LPM_HINT STRING "ENABLE_RUNTIME_MOD=NO"
// Retrieval info: CONSTANT: LPM_TYPE STRING "altsyncram"
// Retrieval info: CONSTANT: NUMWORDS_A NUMERIC "256"
// Retrieval info: CONSTANT: OPERATION_MODE STRING "ROM"
// Retrieval info: CONSTANT: OUTDATA_ACLR_A STRING "NONE"
// Retrieval info: CONSTANT: OUTDATA_REG_A STRING "UNREGISTERED"
// Retrieval info: CONSTANT: WIDTHAD_A NUMERIC "8"
// Retrieval info: CONSTANT: WIDTH_A NUMERIC "8"
// Retrieval info: CONSTANT: WIDTH_BYTEENA_A NUMERIC "1"
// Retrieval info: USED_PORT: address 0 0 8 0 INPUT NODEFVAL "address[7..0]"
// Retrieval info: USED_PORT: clock 0 0 0 0 INPUT VCC "clock"
// Retrieval info: USED_PORT: q 0 0 8 0 OUTPUT NODEFVAL "q[7..0]"
// Retrieval info: CONNECT: @address_a 0 0 8 0 address 0 0 8 0
// Retrieval info: CONNECT: @clock0 0 0 0 0 clock 0 0 0 0
// Retrieval info: CONNECT: q 0 0 8 0 @q_a 0 0 8 0
// Retrieval info: GEN_FILE: TYPE_NORMAL rom_log.v TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL rom_log.inc FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL rom_log.cmp FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL rom_log.bsf FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL rom_log_inst.v FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL rom_log_bb.v FALSE
// Retrieval info: LIB_FILE: altera_mf
3、上板验证
灰度图:

log变换图:

和MATLAB有细微差异,手机拍照时的光线也有问题,总的来说我们实现了灰度图的 log 变换。
四、彩色图像的 log 变换
虽然 log 变换最开始是针对灰度图像的,但是拿彩色图像的RGB三通道出来做 log 变换也能起到不错的效果。
1、MATLAB效果
%-------------------------------------------------------------------------- %-- 彩色图像log变换 %-------------------------------------------------------------------------- clear all; RGB = imread('img.jpg'); %读取图片文件 LOG = mat2gray(log(1+double(RGB))); subplot(1,2,1);imshow(RGB);xlabel('原始图像'); subplot(1,2,2);imshow(LOG);xlabel('对数变换');

2、代码设计
//========================================================================== //== RGB565转RGB888 //========================================================================== assign R = {RGB_data[15:11],RGB_data[13:11]}; assign G = {RGB_data[10: 5],RGB_data[ 6: 5]}; assign B = {RGB_data[ 4: 0],RGB_data[ 2: 0]}; //========================================================================== //== log变换 //========================================================================== rom_log u_R ( .address (R ), .clock (clk ), .q (log_R ) ); rom_log u_G ( .address (G ), .clock (clk ), .q (log_G ) ); rom_log u_B ( .address (B ), .clock (clk ), .q (log_B ) ); assign log_data = {log_R[7:3],log_G[7:2],log_B[7:3]}; //========================================================================== //== 信号同步,rom给地址到出数据会延迟1clk //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) begin RGB_de_r <= 1'b0; RGB_hsync_r <= 1'b0; RGB_vsync_r <= 1'b0; end else begin RGB_de_r <= RGB_de; RGB_hsync_r <= RGB_hsync; RGB_vsync_r <= RGB_vsync; end end assign log_de = RGB_de_r; assign log_hsync = RGB_hsync_r; assign log_vsync = RGB_vsync_r;
3、上板验证
原图:

log变换图:

图像整体变亮了,和MATLAB的效果差不多,实验成功。
如果希望图像整体变暗,则可以试试反对数变换,或者采用下一篇的伽玛(幂律)变换。
参考资料:
[1] OpenS Lee:FPGA开源工作室(公众号)
[2] 张铮, 王艳平, 薛桂香. 数字图像处理与机器视觉[M]. 人民邮电出版社, 2010.

浙公网安备 33010602011771号