背景说明
最近在处理图像,发现一些样本由于逆光原因过于阴暗,影响图像识别。解决时,可以在训练样本中加入类似的图像,或者手动把相关图像进行颜色变化。这里主要介绍手工颜色变化。
原始图像如下,假设你需要判断裤子的种类(牛仔裤还或棉布裤子),类似阴暗图像很难判断:
网上现有的解决方法中,主要包含直方图变化和gamma变换,例如下面几篇文章OpenCV调整图像对比度和亮度、qunshansj/opencv-python-image-dehazing-algorithm
、OpenCV-Python-(4)-对比度增强。
尽管这几篇文章都能起到“给图片增加亮度”的效果,但是gamma变换之后的图像总感觉有一层雾气覆盖,因此又结合了何凯明的去雾算法,最终完成了处理。
处理步骤1-对比度增强
使用gamma增强后,图片中人的右腿的对比度更加强烈,与原始图片相比,已经能够看清右腿上的褶皱,甚至鞋底的花纹都变得更明显。在人眼尺度下,此时基本已经能判断出这是一条棉布裤子。
处理代码如下,其中的gamma可以是0到无穷大,0-1之间较为合理。可以参考这个链接。
import cv2
import numpy as np
import math
def gamma_trans(img, gamma): # gamma函数处理
gamma_table = [np.power(x / 255.0, gamma) * 255.0 for x in range(256)] # 建立映射表
gamma_table = np.round(np.array(gamma_table)).astype(np.uint8) # 颜色值为整数
return cv2.LUT(img, gamma_table) # 图片颜色查表。另外可以根据光强(颜色)均匀化原则设计自适应算法。
def process_image_gamma(image_path, output_path, gamma_factor):
# 读取图像
image = cv2.imread(image_path)
# 检查图像是否成功读取
if image is None:
print("Error: 图像无法读取。")
return
# 将图像的像素值转换为float32类型,以便进行数学运算
image_float = image.astype(np.float32)
img_gray = cv2.imread(image_path, 0) # 灰度图读取,用于计算gamma值
mean = np.mean(img_gray)
gamma_val = math.log10(gamma_factor) / math.log10(mean / 255) # 公式计算gamma
image_gamma_correct = gamma_trans(image, gamma_val) # gamma变换
# 保存修改后的图像
cv2.imwrite(output_path, image_gamma_correct)
print("处理完成,图像已保存至:", output_path)
# 使用示例
image_path = r'E:\data\3.jpg' # 替换为你的图像路径
output_path = r'E:\data\3_1.jpg' # 替换为你希望保存输出图像的路径
process_image_gamma(image_path, output_path, gamma_factor=0.5) # 可以调整gamma_factor参数来改变颜色变化的幅度
处理步骤2-去雾
在步骤1中,如果仔细观察,你会发现图片表面仍然有一层类似于雾气的白色覆盖,如果想要进一步处理,就需要用去雾算法了。目前使用何凯明的传统算法就能达到不错的效果:
如果与文章开始的图片对比,你会发现鞋底花纹、裤子纹路以及原本处于阴暗位置的数目和小草都变得更加容易识别。处理代码如下。你需要把bGamma参数设为true。
import cv2
import numpy as np
def zmMinFilterGray(src, r=7):
'''最小值滤波,r是滤波器半径'''
'''if r <= 0:
return src
h, w = src.shape[:2]
I = src
res = np.minimum(I , I[[0]+range(h-1) , :])
res = np.minimum(res, I[range(1,h)+[h-1], :])
I = res
res = np.minimum(I , I[:, [0]+range(w-1)])
res = np.minimum(res, I[:, range(1,w)+[w-1]])
return zmMinFilterGray(res, r-1)'''
return cv2.erode(src, np.ones((2 * r + 1, 2 * r + 1))) # 使用opencv的erode函数更高效
def guidedfilter(I, p, r, eps):
'''引导滤波'''
height, width = I.shape
m_I = cv2.boxFilter(I, -1, (r, r))
m_p = cv2.boxFilter(p, -1, (r, r))
m_Ip = cv2.boxFilter(I * p, -1, (r, r))
cov_Ip = m_Ip - m_I * m_p
m_II = cv2.boxFilter(I * I, -1, (r, r))
var_I = m_II - m_I * m_I
a = cov_Ip / (var_I + eps)
b = m_p - a * m_I
m_a = cv2.boxFilter(a, -1, (r, r))
m_b = cv2.boxFilter(b, -1, (r, r))
return m_a * I + m_b
def getV1(m, r, eps, w, maxV1): # 输入rgb图像,值范围[0,1]
'''计算大气遮罩图像V1和光照值A, V1 = 1-t/A'''
V1 = np.min(m, 2) # 得到暗通道图像
V1 = guidedfilter(V1, zmMinFilterGray(V1, 7), r, eps) # 使用引导滤波优化
bins = 2000
ht = np.histogram(V1, bins) # 计算大气光照A
d = np.cumsum(ht[0]) / float(V1.size)
for lmax in range(bins - 1, 0, -1):
if d[lmax] <= 0.999:
break
A = np.mean(m, 2)[V1 >= ht[1][lmax]].max()
V1 = np.minimum(V1 * w, maxV1) # 对值范围进行限制
return V1, A
def deHaze(m, r=81, eps=0.001, w=0.95, maxV1=0.80, bGamma=True):
Y = np.zeros(m.shape)
V1, A = getV1(m, r, eps, w, maxV1) # 得到遮罩图像和大气光照
for k in range(3):
Y[:, :, k] = (m[:, :, k] - V1) / (1 - V1 / A) # 颜色校正
Y = np.clip(Y, 0, 1)
if bGamma:
Y = Y ** (np.log(0.5) / np.log(Y.mean())) # gamma校正,默认不进行该操作
return Y
input_path = r'E:\data\3_1.jpg' # 替换为你的图像路径
output_path = r'E:\data\3_2.jpg' # 替换为你希望保存输出图像的路径
image = cv2.imread(input_path)
m = deHaze(image / 255.0) * 255
height, width = m.shape[:2]
cv2.imwrite(output_path, m)
其他效果对比
以下两组实验省去了中间过程,只保留了输入图像和最终的图像,都达到了去阴暗的目的。
这是一张风景图,原图中,中间位置的房子基本不可识别,而处理后则可轻易识别出房子。
这是一张行人图片,原图可以识别出每一个人物,经过处理后,还可以识别人员身上的衣服花纹,图像右侧边缘正中间的花坛也呈现了更艳丽的紫色。