对比度概念
对比度指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,差异范围越大代表对比越大,差异范围越小代表对比越小。
对比度越低的图像,图像越发灰。例如下图。
传统方法与代码
传统的对比度增强方法可分为两大类:基于直方图统计方法与基于像素点的方法
基于直方图:直方图均衡化(HE)、局部直方图均衡化(AHE)、限制对比度的局部直方图均衡化(CLAHE)
基于像素点:线性拉伸、GAMMA、ACE、PS对比度增强
HE
推理过程:参考以下链接https://blog.csdn.net/weixin_45930877/article/details/119581282
from headers import *
def historgram(config):
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
new_image = np.zeros_like(image)
if image.shape[-1] == 3:
new_image[:,:,0] = cv2.equalizeHist(image[:,:,0])
new_image[:,:,1] = cv2.equalizeHist(image[:,:,1])
new_image[:,:,2] = cv2.equalizeHist(image[:,:,2])
else:
new_image = cv2.equalizeHist(image)
cv2.imwrite(config.target_dir + name + '_hist.jpg', new_image)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './equzalizeHist/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
historgram(config)
AHE
from headers import *
def equalize_adapthist(config):
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
image = image/255
new_image = np.zeros_like(image)
if image.shape[-1] == 3:
new_image[:,:,0] = exposure.equalize_adapthist(image[:,:,0])
new_image[:,:,1] = exposure.equalize_adapthist(image[:,:,1])
new_image[:,:,2] = exposure.equalize_adapthist(image[:,:,2])
else:
new_image = exposure.equalize_adapthist(image)
new_image = np.uint8(np.clip(new_image*255., 0, 255))
cv2.imwrite(config.target_dir + name + '_adphist.jpg', new_image)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './equalize_adapthist/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
equalize_adapthist(config)
CLAHE
实现步骤:参考以下链接
http://www.skcircle.com/?id=1762
from headers import *
def clahe(config):
clipLimit = 2.5
tileGridSize = (8,8)
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
new_image = np.zeros_like(image)
clahe_trans = cv2.createCLAHE(clipLimit, tileGridSize)
if image.shape[-1] == 3:
new_image[:,:,0] = clahe_trans.apply(image[:,:,0])
new_image[:,:,1] = clahe_trans.apply(image[:,:,1])
new_image[:,:,2] = clahe_trans.apply(image[:,:,2])
else:
new_image = clahe_trans.apply(image)
new_image = np.uint8(np.clip(new_image, 0, 255))
cv2.imwrite(config.target_dir + name + '_clahe_' + str(clipLimit) + '_' + str(tileGridSize[0]) + '.jpg', new_image)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './clahe/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
clahe(config)
1_1.5
2_1.5
4_1.5
8_1.5
线性拉伸
本质:归一化,然后放缩到指定大小范围。有多种计算方式,直接线性拉升、裁剪线性拉伸、分段式拉伸。
from headers import *
def linear(config):
maxout = 250
minout = 20
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
new_image = np.zeros_like(image)
if image.shape[-1] == 3:
for c in range(3):
tmp = (image[:,:,c] - image[:,:,c].min()) / (image[:,:,c].max() - image[:,:,c].min())
tmp_n = tmp * (maxout-minout)
new_image[:,:,c] = tmp_n
else:
tmp = (image - image.min()) / (image.max() - image.min())
new_image = tmp * (maxout-minout)
new_image = np.uint8(new_image)
cv2.imwrite(config.target_dir + name + '_linear.jpg', new_image)
def linear_crop(config):
a = 2.5
b = 99
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
a, b = np.percentile(image, (a, b))
c = a - 0.1 * (b - a)
d = b + 0.5 * (b - a)
new_image = (image - c) / (d - c) * 255
new_image = np.clip(new_image, 0, 255)
new_image = np.uint8(new_image)
cv2.imwrite(config.target_dir + name + '_linear_crop.jpg', new_image)
def linear_segmentation(config):
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
r1, s1 = 80, 10
r2, s2 = 140, 200
k1 = s1 / r1
k2 = (s2 - s1) / (r2 - r1)
k3 = (255 - s2) / (255 - r2)
new_image = np.zeros_like(image)
if image.shape[-1] == 3:
for c in range(3):
h, w = image.shape[:2]
for i in range(h):
for j in range(w):
if image[i,j,c] < r1:
new_image[i,j,c] = k1 * image[i,j,c]
elif r1 <= image[i,j,c] <= r2:
new_image[i,j,c] = k2 * (image[i,j,c] - r1) + s1
else:
new_image[i,j,c] = k3 * (image[i,j,c] - r2) + s2
else:
for i in range(h):
for j in range(w):
if image[i,j] < r1:
new_image[i,j] = k1 * image[i,j]
elif r1 <= image[i,j] <= r2:
new_image[i,j] = k2 * (image[i,j] - r1) + s1
else:
new_image[i,j] = k3 * (image[i,j] - r2) + s2
new_image = np.clip(new_image, 0, 255)
new_image = np.uint8(new_image)
cv2.imwrite(config.target_dir + name + '_linear_segmentation.jpg', new_image)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './linear/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
linear(config)
linear_crop(config)
linear_segmentation(config)
(50, 20),(140,200)
(80, 20),(140,200)
GAMMA
普通的Gamma曲线是一条向上凸的指数曲线。
一般的理解,认为Gamma校正能够提高亮度,其实这样理解并不完全对。也就是说Gamma校正并不能提高图像的整体亮度。它的作用就是将Sensor送过来的原始数据进行非线性调整为另外一组数据。因为在低值部分的Gamma曲线的斜率超过1,那么校正后的数据,在低值部分的差距会被拉大,而在高值部分的斜率小于1,那么高值部分的数据之间的差距会变小。
这样的图像数据,处于低值的细节部分会被提升的更明显,而高值部分的由于人眼感觉本来就不会很明显,因此也不会造成图像细节的损失。
通。
对比度的调整在一定程度上说,其实也就是对gamma曲线的调整,增大对比度就是提Gamma 值。对于图像处理来说,也有在硬件 gamma 校正后,单独由软件再进行一次类的幂函数变换来调整对比度。
(在整理资料的时候学习了一个优化的gamma,针对原本饱和度对比度还可以的图像增强效果还不错)
from headers import *
def gamma(config):
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
new_image = np.zeros_like(image)
g_v = 2.0
def gamma_trans_auto(img):
mean = np.mean(img)
g_v = math.log10(0.5)/math.log10(mean/255)
table = [np.power(x / 255., g_v) * 255 for x in range(256)]
table = np.round(np.array(table)).astype(np.uint8)
new_img = cv2.LUT(img, table)
return new_img
def gamma_trans(img, g_v):
img = img / 255
new_img = exposure.adjust_gamma(img, g_v)
new_img = np.uint8(np.clip(new_img * 255, 0, 255))
return new_img
if image.shape[-1] == 3:
new_image[:,:,0] = gamma_trans(image[:,:,0], g_v)
new_image[:,:,1] = gamma_trans(image[:,:,1], g_v)
new_image[:,:,2] = gamma_trans(image[:,:,2], g_v)
else:
new_image = gamma_trans(image)
new_image = np.uint8(np.clip(new_image, 0, 255))
cv2.imwrite(config.target_dir + name + '_gamma_' + str(g_v) + '.jpg', new_image)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './gamma/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
gamma(config)
1.2
0.7
ACE
from headers import *
def getVarianceMean(scr, winSize):
if scr is None or winSize is None:
print("The input parameters of getVarianceMean Function error")
return -1
if winSize % 2 == 0:
print("The window size should be singular")
return -1
copyBorder_map = cv2.copyMakeBorder(scr, winSize // 2, winSize // 2, winSize // 2, winSize // 2,
cv2.BORDER_REPLICATE)
shape = np.shape(scr)
local_mean = np.zeros_like(scr)
local_std = np.zeros_like(scr)
for i in range(shape[0]):
for j in range(shape[1]):
temp = copyBorder_map[i:i + winSize, j:j + winSize]
local_mean[i, j], local_std[i, j] = cv2.meanStdDev(temp)
if local_std[i, j] <= 0:
local_std[i, j] = 1e-8
return local_mean, local_std
def adaptContrastEnhancement(scr, winSize, maxCg):
if scr is None or winSize is None or maxCg is None:
print("The input parameters of ACE Function error")
return -1
YUV_img = cv2.cvtColor(scr, cv2.COLOR_BGR2YUV) ##转换通道
Y_Channel = YUV_img[:, :, 0]
shape = np.shape(Y_Channel)
meansGlobal = cv2.mean(Y_Channel)[0]
localMean_map, localStd_map = getVarianceMean(Y_Channel, winSize)
for i in range(shape[0]):
for j in range(shape[1]):
cg = 0.2 * meansGlobal / localStd_map[i, j];
if cg > maxCg:
cg = maxCg
elif cg < 1:
cg = 1
temp = Y_Channel[i, j].astype(float)
temp = max(0, min(localMean_map[i, j] + cg * (temp - localMean_map[i, j]), 255))
# Y_Channel[i,j]=max(0,min(localMean_map[i,j]+cg*(Y_Channel[i,j]-localMean_map[i,j]),255))
Y_Channel[i, j] = temp
YUV_img[:, :, 0] = Y_Channel
dst = cv2.cvtColor(YUV_img, cv2.COLOR_YUV2BGR)
return dst
def ace(config):
winSize = 15
maxCg = 10
for image in config.image_paths:
name = image.split('/')[-1][:-4]
print(name)
image = cv2.imread(image)
new_image = adaptContrastEnhancement(image, winSize, maxCg)
new_image = np.uint8(np.clip(new_image, 0, 255))
cv2.imwrite(config.target_dir + 'ace_'+ str(winSize) + '_' + str(maxCg) + '.jpg', new_image)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './ace/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
ace(config)
ws=15,mg=15
ws=5,mg=5
PS对比度增强
nRGB = RGB + (RGB - Threshold) * Contrast / 255
公式中,nRGB表示图像像素新的R、G、B分量,RGB表示图像像素R、G、B分量,Threshold为给定的阈值,Contrast为处理过的对比度增量。
Photoshop对于对比度增量,是按给定值的正负分别处理的:
当增量等于-255时,是图像对比度的下端极限,此时,图像RGB各分量都等于阈值,图像呈全灰色,灰度图上只有1条线,即阈值灰度;
当增量大于-255且小于0时,直接用上面的公式计算图像像素各分量;
当增量等于255时,是图像对比度的上端极限,实际等于设置图像阈值
from headers import *
def ps_contrast(config):
contrast = 125
for image_path in config.image_paths:
image = cv2.imread(image_path)
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
name = image_path.split('/')[-1][:-4]
print(name)
img = img_rgb * 1.0
thre = np.mean(img)
# thre = np.full(img.shape, thre)
img_out = img * 1.0
if contrast <= -255.0:
img_out = (img_out >= 0) + thre - 1
elif contrast > -255.0 and contrast < 0:
a = (img - thre)
b = a * contrast / 255.0
img_out = img + b
elif contrast < 255.0 and contrast > 0:
new_con = 255.0 * 255.0 / (256.0 - contrast) - 255.0
img_out = img + (img - thre) * new_con / 255.0
else:
mask_1 = img > thre
img_out = mask_1 * 255.0
img_out = img_out / 255.0
mask_1 = img_out < 0
mask_2 = img_out > 1
img_out = img_out * (1 - mask_1)
img_out = img_out * (1 - mask_2) + mask_2
img_out = np.uint8(np.clip(img_out*255, 0, 255))
img_out = cv2.cvtColor(img_out, cv2.COLOR_RGB2BGR)
cv2.imwrite(config.target_dir + name + '_' + str(contrast) + '.jpg', img_out)
if __name__ == '__main__':
image_paths = [
'./test.jpg'
]
target_dir = './ps_contrast/'
os.makedirs(target_dir, exist_ok=True)
parser = argparse.ArgumentParser()
parser.add_argument('--image_paths', type=str, default=image_paths)
parser.add_argument('--target_dir', type=str, default=target_dir)
config = parser.parse_args()
ps_contrast(config)
20
50
100