图像对比度增加

这是包含我在此处描述的图像和脚本的链接:

对比度调整– Google云端硬盘

编辑说明

drive.google.com

对比度是图像中颜色或发光的差异,可区分其中的对象。 让我们看两套图像,一组是灰度图像,另一组是彩色图像,其中第一组图像的对比度较低,而另一组图像的对比度较高。

比较这两组图像时会看到什么? 通过高对比度的图像,我们可以看到颜色更加丰富,在某些地方,光线从很暗变为很亮。 如果我们依次看一下低对比度的图像,我们可以看到颜色范围要小得多,不同的对象往往具有非常相似的颜色。 如果将所有图像转换为灰色,然后查看它们的直方图,则可以注意到同一件事。

观察直方图,可以很清楚地看到低对比度图像中的像素如何从更小的集合中获取值,由此可以得出对象从非常狭窄的值集中获取的颜色,这最终意味着不同的对象很难区分。

为了增加图像的对比度,最简单的方法是确保将像素映射到可供我们使用的所有值的集合(在这种情况下为0–255,因为我们使用的是8位图像)。 我们还希望保持像素之间的关系-也就是说,如果像素(i,j)比像素(k,l)暗,我们希望它在变换后保持这种方式。

让我们看一下桥图像直方图。 我们可以看到像素值的范围大约在70到245之间。如果我们想将它们映射到介于0到255之间的一组值并保持它们之间的关系,那么我们需要使用将70映射到0的线性函数245至255,并且是线性的。 Matlab提供了执行此转换的功能,称为imadjust

让我们看一下函数的中间形状,即gamma等于1的形状。这正是我上面描述的那种函数。 让我们将其应用于前两个图像并查看结果。

  bridge_adjusted = imadjust(bridge,[70/255 200/255],[0 1],1); 
forest_adjusted = imadjust(森林,[90/255 220/255],[0 1],1);

请注意,此函数希望输入和输出限制都在0到1的范围内。

正如预期的那样,直方图的形状保持不变,只是现在已经被拉伸了。

让我们回到不可调功能。 当我们增加或减少伽玛时,会发生什么? 当gamma设置为1时,值的前半部分将映射到新范围的前半部分。 随着我们降低伽玛值,越来越多的像素最终被映射到新值的上限范围内。 就像我们在从原始直方图的最高值开始并越来越多地对其进行压缩,同时将所有其他范围扩展到越来越大的范围一样,占有越来越大的份额。 当我们增加伽玛时,情况正好相反—原始直方图中的较大值最终会越来越被拉伸,而其他则被压缩。 伽玛远离1的距离越远,将正直方图的各个部分与被压缩的部分分开的假想线与中间的距离就越远。 当gamma小于1时,我们拉伸较低的值,而对于大于1的gamma则相反。

让我们看看当采用不同的伽玛值时图像会发生什么。


非常有趣的事情在这里发生。 将gamma设置为1.5会完全破坏桥梁图像的对比度,但是对于森林图像,它为我们提供的结果甚至比将gamma设置为1时的结果还要好。那么为什么会发生这种情况呢? 请注意,在进行像素转换时,我们始终将其映射到完整的一组值,但是会更改形状。 因此,不仅仅是为了达到高对比度而需要将范围增大,而且生成的直方图的形状也非常重要。 查看直方图,并注意,当变换后的图像的直方图看起来更像是统一函数时,图像看起来越具有更好的对比度。 也就是说,除了采用全部值范围外,还应尽可能均匀地分布像素,以增加对比度。

这就是我们达到直方图均衡化的方式。 可以使用(标准化的)直方图的累加和作为传递函数,而不是使用imadjust函数并猜测gamma的值直到获得令人满意的结果。 就像不调整一样,此函数是单调的,对于不在图像中的值将为0,对于大于原始图像中像素的最大值的值将为1。 除非在这种情况下,否则我们要获得的直方图将是看起来最均匀的直方图。 那为什么呢? 因为我们本质上是在使用概率积分变换:

这是对8位图像执行此转换的Matlab代码:

  h = imhist(I); 
hnorm = h./numel(I); %标准化直方图。
cdf = cumsum(hnorm); %查找累计金额。
T = uint8(floor(cdf * 255)); %创建变换功能。
I_equalized = inlut(I,T); %均衡直方图。

请注意,由于传递函数不是连续的,因此必须使用查找表。 当然,有Matlab函数histeq可以做到这一点,但这只是为了展示算法的工作原理。

因此,现在很清楚了为什么在不同图像上使用相同参数时有时会失调而有时会增加对比度–当它起作用时,是因为它类似于累积分布函数。 让我们再次查看整体结果以了解情况如何。

注意累积分布越直,我们得到的对比度越好。

让我们看一下YCbCr颜色空间中森林图像的Y分量。 我将执行相同的操作,但是这次仅在发光组件上执行。 我将使用Matlab的Stretchlim函数查找imadjust函数的high_inlow_in值。

  forest_ycbcr = rgb2ycbcr(森林); 
low_high = Stretchlim(forest_ycbcr(:,:,1),[0.05]); forest_1(:,:,1)= imadjust(forest_ycbcr(:,:,1),low_high,[0 1],1);
forest_15(:,:,1)=不可调(forest_ycbcr(:,:,1),low_high,[0 1],1.5);
forest_05(:,:,1)=不可调(forest_ycbcr(:,:,1),low_high,[0 1],0.5);
forest_eq(:,:,1)= histeq(forest_ycbcr(:,:,1));

因此,我们发现了通常情况下可以增加对比度的功能。 但这只是不调节功能的改进版本,具有相同的一般缺点。 直方图均衡化的主要问题是它不能真正区分图像的不同区域。 请注意,我们如何在大象的均等化图像中丢失一些细节-例如,牙齿的某些暗部已经消失,但在原始图像中它们很好。 例如,被暗像素包围的像素可以进一步变亮以增加对比度。 也就是说,我们不需要将整个图像中最亮的部分设为白色,我们可以将区域中最亮的部分设为白色以进一步提高对比度。 该逻辑用于自适应直方图均衡算法中。

我们遍历每个像素,然后使用传递函数执行直方图均衡,该传递函数对应于给定像素位于其中心的块。 这样,像素映射到的值仅由与其接近的像素定义,而不是由整个图像定义。 这意味着,如果图像在某些区域具有不同的光线,则不会破坏整个对比度。 最后,有些像素由于边缘而无法观察。 我将在每一侧对称地填充图像,以便即使边缘像素在其周围也具有完整的块。

这是执行此操作的代码。

  %读取图像并将其转换为YCbCr色彩空间。 
大象= imread('ex3.jpg');
Elephants_ycbcr = rgb2ycbcr(elephants);%选择块大小,以使大约有32个块。
[MN] = size(elephants(:,:,1));
block_size = round(sqrt(M * N / 32));每一边的%Pad图片为block_size / 2。 镜像像素。
pad_val = round(block_size / 2);
我= padarray(elephants_ycbcr(:,:,1),[pad_val pad_val],'对称','两者'); clip_limit = 0.02;
均等= I;
%遍历每个像素。
对于i = pad_val + 1:M + pad_val
对于j = pad_val + 1:N + pad_val
%查找该像素的周围区域。
块= I(i-pad_val:i + pad_val,j-pad_val:j + pad_val);
hist = imhist(block)/ numel(block(:));
cdf = uint8(round(255 * cumsum(hist)));
%基于cdf的映射值。
equalized(i,j)= intlut(I(i,j),cdf);
结束
end%裁剪填充并转换为rgb。
Elephants_ycbcr(:,:,1)=等于(pad_val + 1:M + pad_val,pad_val + 1:N + pad_val);
Elephants_eq = ycbcr2rgb(elephants_ycbcr);
imshow(elephants_eq);

一方面,与直方图均衡化相比,我们确实增加了更多的对比度,但是我们添加了太多的噪声,因此并没有理由这样做。 之所以会产生噪声,是因为在恒定颜色区域的某些部分中,我们尝试将值映射到整个范围,而实际上并不需要这样做。

这可以通过所谓的对比裁剪来解决。 其背后的原因是-如果这是一个恒定的颜色区域,则不要太大地改变原始像素的值。

让我们看一下恒定颜色区域的直方图。

如您所见,大多数像素都围绕一个中心值分组。 这意味着累积和(我们将其用作传递函数)将具有非常陡峭的斜率,并且在像素值的低和高范围内,基本上最多为0和1。 当这种累积分布用作传递函数时,它将把彼此非常接近的值映射为相距较远的值。 我们不希望这种情况发生。

阻止这种情况的一种方法是取一些直方图的截止值,然后将所有高于该值的值均匀分布。 这将导致传递函数的斜率非常接近1,这意味着值x紧密映射到x。

让我们将其添加到代码中并查看结果。

  %读取图像并将其转换为YCbCr色彩空间。 
大象= imread('ex3.jpg');
Elephants_ycbcr = rgb2ycbcr(elephants);%选择块大小,以使大约有32个块。
[MN] = size(elephants(:,:,1));
block_size = round(sqrt(M * N / 32));每一边的%Pad图片为block_size / 2。 镜像像素。
pad_val = round(block_size / 2);
我= padarray(elephants_ycbcr(:,:,1),[pad_val pad_val],'对称','两者'); clip_limit = 0.02;
均等= I;
%遍历每个像素。
对于i = pad_val + 1:M + pad_val
对于j = pad_val + 1:N + pad_val
%查找该像素的周围区域。
块= I(i-pad_val:i + pad_val,j-pad_val:j + pad_val);
hist = imhist(block)/ numel(block(:));
对比度极限。
hist(hist> clip_limit)= clip_limit;
增量=(1-sum(hist(:)))/ 256;
hist = hist + delta;
cdf = uint8(round(255 * cumsum(hist)));
%基于cdf的映射值。
equalized(i,j)= intlut(I(i,j),cdf);
结束
end%裁剪填充并转换为rgb。
Elephants_ycbcr(:,:,1)=等于(pad_val + 1:M + pad_val,pad_val + 1:N + pad_val);
Elephants_eq = ycbcr2rgb(elephants_ycbcr);
imshow(elephants_eq);

因此,我们也已对此进行了照顾。 但是现在还有另一个问题。 这个程序太慢了。 难怪在每个像素上执行直方图均衡化确实很多。 您可以通过移动到C,C ++并以更智能的方式(例如,使用窗口滑动并从开始或结束计算累积总和并在到达需要变换的像素时停止)来加快速度,但是仍然可以因为算法非常复杂,所以速度不会那么快。

加快速度的另一种方法是使用带有插值的自适应直方图均衡。 逻辑是将图像划分为多个块,并为每个块找到累积分布。 然后,我们遍历每个像素并通过基于周围块的插值映射进行映射。

这是代码:

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%% 

%此程序是自适应直方图均衡的一个示例
%插值算法。

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%关闭所有
清除所有%读取的图像并将其转换为YCbCr颜色空间。
大象= imread('ex3.jpg');
Elephants_ycbcr = rgb2ycbcr(大象); clip_limit = 0.02;
number_of_blocks = 32;%选择块大小,以使大约有number_of_blocks个块。
[MN] = size(elephants(:,:,1));
block_size = round(sqrt(M * N / number_of_blocks));
block_size = block_size +〜mod(block_size,2);%将图像拆分为块。 在每个块中找到具有对比度限制的cdf。
block_rows = ceil(M / block_size);
block_columns = ceil(N / block_size);
映射=零(block_rows,block_columns,256);%添加对称的行和列,以使所有块的大小相同。
pad_rows = block_size * block_rows-M;
pad_columns = block_size * block_columns-N;
我= padarray(elephants_ycbcr(:,:,1),[pad_rows pad_columns],'symmetric','post');
等于= I;%为每个块查找cdf。
对于i = 0:block_rows-1
对于j = 0:block_columns-1
块= I(i * block_size + 1:(i + 1)* block_size,j * block_size + 1:(j + 1)* block_size);
hist = imhist(block)/ numel(block(:));
对比度极限。
hist(hist> clip_limit)= clip_limit;
增量=(1-sum(hist(:)))/ 256;
hist = hist + delta;
%为该块保存CDF。
映射(i + 1,j + 1,:) = uint8(round(255 * cumsum(hist)));
结束
end%为每个块设置中心像素。
center_rows = 0(block_rows,1);
center_columns = 0(block_columns,1);
对于i = 0:block_rows-1
center_rows(i + 1)= i * block_size + round(block_size / 2);
结束
对于i = 0:block_columns-1
center_columns(i + 1)= i * block_size + round(block_size / 2);
end%遍历每个像素并插入其映射。
对于i = 1:M + pad_rows
对于j = 1:N + pad_columns
%查找块索引。
block_down = find(center_rows> = i,1,'first');
block_up = find(center_rows <= i,1,'last');
block_right = find(center_columns> = j,1,'first');
block_left = find(center_columns <= j,1,'last');

%边缘案件。
如果(isempty(block_left))block_left = 1; 结束
如果(isempty(block_up))block_up = 1; 结束
如果(isempty(block_right))block_right = block_columns; 结束
如果(isempty(block_down))block_down = block_rows; 结束

%查找该像素值的相邻块中心。
x_right = center_columns(block_right);
x_left = center_columns(block_left);
y_down = center_rows(block_down);
y_up = center_rows(block_up);

%查找重量系数。
如果(y_up == y_down)
a = 0.5;
其他
a =(i-y_up)/(y_down-y_up);
结束
如果(x_right == x_left)
b = 0.5;
其他
b =(j-x_left)/(x_right-x_left);
结束

%内插映射。
m_ul =映射(block_up,block_left,I(i,j));
m_ur =映射(block_up,block_right,I(i,j));
m_dl =映射(block_down,block_left,I(i,j));
m_dr =映射(block_down,block_right,I(i,j));

m =(1-a)*((1-b)* m_ul +(b)* m_ur)+(a)*((1-b)* m_dl +(b)* m_dr);
%取该值。
equalized(i,j)= round(m);
结束
end%裁剪填充并转换为rgb。
Elephants_ycbcr(:,:,1)= equalized(1:M,1:N);
Elephants_eq = ycbcr2rgb(elephants_ycbcr);
数字
imshow(elephants_eq);

如您所见,我们将图像分成多个块,每个块都有其中心像素。 每个块也都有自己的映射。 我们获取一个像素,然后找到最接近的左,右,上和下中心像素。 如果那里没有像素(例如,我们的像素在第一个左中心像素的左侧),我们再次在边缘取一个像素。 实际上,在边缘情况下,我们从一个或两个块(而不是四个)进行插值。 最后,我们从每个相邻块转换中获取所选像素的映射值,然后根据像素与块中心的距离来获取总值。

因此,您可以看到结果没有改变,但是如果您运行该程序,便可以看到速度之间的差异有多大。