动机

最近因为自己给自己定的ddl要到了,于是疯狂肝代码。
我主要是做去雾方向,最近因为要用于论文,需要桥梁来和基于语义分割的复杂地貌识别算法相结合。因为深度学习的框架已经基本定在那里了,所以我只好从各种去雾方法的代码上动刀。

源代码

ACE去雾算法(example)

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
# 线性拉伸处理
# 去掉最大最小0.5%的像素值 线性拉伸至[0,1]
def stretchImage(data, s=0.005, bins=2000):
ht = np.histogram(data, bins);
d = np.cumsum(ht[0]) / float(data.size)
lmin = 0;
lmax = bins - 1
while lmin < bins:
if d[lmin] >= s:
break
lmin += 1
while lmax >= 0:
if d[lmax] <= 1 - s:
break
lmax -= 1
return np.clip((data - ht[1][lmin]) / (ht[1][lmax] - ht[1][lmin]), 0, 1)

# 根据半径计算权重参数矩阵
g_para = {}

def getPara(radius=5):
global g_para
m = g_para.get(radius, None)
if m is not None:
return m
size = radius * 2 + 1
m = np.zeros((size, size))
for h in range(-radius, radius + 1):
for w in range(-radius, radius + 1):
if h == 0 and w == 0:
continue
m[radius + h, radius + w] = 1.0 / math.sqrt(h ** 2 + w ** 2)
m /= m.sum()
g_para[radius] = m
return m

# 常规的ACE实现
def zmIce(I, ratio=4, radius=300):
para = getPara(radius)
height, width = I.shape
zh = []
zw = []
n = 0
while n < radius:
zh.append(0)
zw.append(0)
n += 1
for n in range(height):
zh.append(n)
for n in range(width):
zw.append(n)
n = 0
while n < radius:
zh.append(height - 1)
zw.append(width - 1)
n += 1
# print(zh)
# print(zw)

Z = I[np.ix_(zh, zw)]
res = np.zeros(I.shape)
for h in range(radius * 2 + 1):
for w in range(radius * 2 + 1):
if para[h][w] == 0:
continue
res += (para[h][w] * np.clip((I - Z[h:h + height, w:w + width]) * ratio, -1, 1))
return res

# 单通道ACE快速增强实现
def zmIceFast(I, ratio, radius):
print(I)
height, width = I.shape[:2]
if min(height, width) <= 2:
return np.zeros(I.shape) + 0.5
Rs = cv2.resize(I, (int((width + 1) / 2), int((height + 1) / 2)))
Rf = zmIceFast(Rs, ratio, radius) # 递归调用
Rf = cv2.resize(Rf, (width, height))
Rs = cv2.resize(Rs, (width, height))

return Rf + zmIce(I, ratio, radius) - zmIce(Rs, ratio, radius)

# rgb三通道分别增强 ratio是对比度增强因子 radius是卷积模板半径
def zmIceColor(I, ratio=4, radius=3):
res = np.zeros(I.shape)
for k in range(3):
res[:, :, k] = stretchImage(zmIceFast(I[:, :, k], ratio, radius))
return res

深度学习predict输入模块

1
2
3
4
5
6
7
8
try:
image = Image.open(img)
except:
print('Open Error! Try again!')
continue
else:
r_image = deeplab.detect_image(image, count=count, name_classes=name_classes)
r_image.show()

谈谈想法

import所有def

以ACE算法为例,所使用的方法层层递进,互相嵌套。以方法为单位将所有方法import非常不现实,如果将所有方法放入导包代码之下,又显得深度学习的predict代码不美观也不传统~~(就是看着很不爽)~~。并且假设再增添一种去雾算法,方法会越积越多,但却做不到每个方法都用到,其所谓树大有枯枝。

import os包先运行ACE.py

那我不去改动代码,直接先运行不就完了。想法很美好,现实很骨感。可以通过深度学习输入部分可以看出,我们是要在运行此文件后,在运行输入图片路径。我们需要在此时将这张图片进行去雾处理,再输出来再往下运行。目前我无法想出将输入图片作为输入变量导入ACE.py运行的方法,所以也以失败告终。

将所有def打包成class一起import

想到这步已经是用了一天半了,因为也不知道可不可行,一直想先用第一种思路做一个solution出来,所以耽误了很多时间。但事实证明这种想法可行且很对。

问题总结

关于python中类的理解

  • 类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类的构造方法__init__():类有一个名为 init() 的特殊方法(构造方法),该方法在类实例化时会自动调用。
  • 实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • 实例化:创建一个类的实例,类的具体对象。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。

话不多说,直接上代码!!!

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
class HazeRemoval1: #我自己定义的类名
def __init__(self,filename):
self.filename = filename

def stretchImage(self, data, s=0.005, bins=2000):
ht = np.histogram(data, bins);
d = np.cumsum(ht[0]) / float(data.size)
lmin = 0;
lmax = bins - 1
while lmin < bins:
if d[lmin] >= s:
break
lmin += 1
while lmax >= 0:
if d[lmax] <= 1 - s:
break
lmax -= 1
return np.clip((data - ht[1][lmin]) / (ht[1][lmax] - ht[1][lmin]), 0, 1)

def getPara(self, radius=5):
global g_para
g_para = {}
m = g_para.get(radius, None)
if m is not None:
return m
size = radius * 2 + 1
m = np.zeros((size, size))
for h in range(-radius, radius + 1):
for w in range(-radius, radius + 1):
if h == 0 and w == 0:
continue
m[radius + h, radius + w] = 1.0 / math.sqrt(h ** 2 + w ** 2)
m /= m.sum()
g_para[radius] = m
return m

def zmIce(self, I, ratio=4, radius=300):
para = self.getPara(radius)
height, width = I.shape
zh = []
zw = []
n = 0
while n < radius:
zh.append(0)
zw.append(0)
n += 1
for n in range(height):
zh.append(n)
for n in range(width):
zw.append(n)
n = 0
while n < radius:
zh.append(height - 1)
zw.append(width - 1)
n += 1
# print(zh)
# print(zw)

Z = I[np.ix_(zh, zw)]
res = np.zeros(I.shape)
for h in range(radius * 2 + 1):
for w in range(radius * 2 + 1):
if para[h][w] == 0:
continue
res += (para[h][w] * np.clip((I - Z[h:h + height, w:w + width]) * ratio, -1, 1))
return res

def zmIceFast(self, I, ratio, radius):
# print(I)
height, width = I.shape[:2]
if min(height, width) <= 2:
return np.zeros(I.shape) + 0.5
Rs = cv2.resize(I, (int((width + 1) / 2), int((height + 1) / 2)))
Rf = self.zmIceFast(Rs, ratio, radius)
Rf = cv2.resize(Rf, (width, height))
Rs = cv2.resize(Rs, (width, height))
return Rf + self.zmIce(I, ratio, radius) - self.zmIce(Rs, ratio, radius)

def zmIceColor(self, I , ratio=4, radius=3):
res = np.zeros(I.shape)
for k in range(3):
res[:, :, k] = self.stretchImage(self.zmIceFast(I[:, :, k], ratio, radius))
return res

重点在于对self的理解,我一开始认为是具体变量,误认为是每个def中输入的变量,把方法中的I之类的替换为self,那就大错特错了。它会直接给你报大大的错(由于是嵌套方法,上一个方法返回的变量个数与下一个方法输入变量不匹配)。因为self不是实际意义上的变量,是你的实参,后面的所有方法都和它有关,但它不占用方法中的变量坑位。self可以理解为一个字典变量,内部存的就是对象的数据属性。

图片PIL与cv类型的转换

但是新的问题又出现了,predict中输入是用PIL中的open,得到的是图片的PIL的类型。而我们的各种opencv的形态学操作都是用图片的cv类型。因此我们在传入class后先要把图片从PIL类型转变为cv类型。

1
2
3
4
def PIL2cv(self): 
image_PIL = Image.open(self.filename)
image_cv = cv2.cvtColor(np.asarray(image_PIL), cv2.COLOR_RGB2BGR)
return image_cv

同样的,进行去雾操作后的输出图片也是cv类型,而最后也要转变为PIL类型。
又一个大大大坑,float,double等类型的图片不能直接用fromarray转换为PIL类型。
不然会报以下cv2.error错误。
cv2.error
所以中间要加一步astype把浮点变为整型。

1
2
3
4
5
6
7
8
9
10
11
12
try:
self = HazeRemoval1(img)
except:
print('Open Error! Try again!')
continue
else:
img = self.PIL2cv()
result = self.zmIceColor(img / 255.0) * 255 #算法需要,得将img像素值缩到[0,1],去雾完后再括回[0,255]
image_uint8 = result.astype(np.uint8)
image = Image.fromarray(cv2.cvtColor(image_uint8, cv2.COLOR_BGR2RGB))
r_image = deeplab.detect_image(image, count=count, name_classes=name_classes)
r_image.show()

同一张图片imshow和imwrite却不同

还有一个意外发现,就是imshow显示出的图片和imwrite保存的图片不一样。

  • (1)当输入矩阵是uint8类型的时候,此时imshow显示图像的时候,会认为输入矩阵的范围在0-255之间。
  • (2)如果imshow的参数是double类型的时候,那么imshow会认为输入矩阵的范围在0-1。
    imshow
imshow

imwrite

imwrite
是因为图片在进行一系列的对数组操作后数组已经变成了float类型,之后再对数组进行imshow时便会出现上面的第二种情况。但是图像矩阵(double型)的矩阵元素不在0-1之间,那么imshow会把超过1的元素都显示为白色。因为所输入的double的矩阵(未归一化)并不能保证元素范围在0-1之间。 解决方法就是先
1
image_uint8 = image_double.astype(np.uint8)

再进行imshow。