一.think python——由类与方法引起的参数思考
- 问题前瞻
在think python书中类的最后一章有一个很有趣的例题17-2,让我们先看看这道题的内容:
编写一个Kangaroo
的类,包含以下方法:
一个__init__
方法,初始化一个叫pounch_contents
的属性为空列表。
一个叫put_in_pounch
的方法,将一个任意类型的对象加入pounch_contents
。
一个__str__
方法,返回Kangaroo
对象的字符串表示和pounch
中的内容。
自己编写完毕后,下载一个有极大bug的答案,找出并修正bug
自己编写过程较为简单,遂不赘述,直接来看这个坏的答案代码:
class Kangaroo:
def __init__(self, name, contents=[]):
self.name = name
self.pouch_contents = contents
def __str__(self):
t = [ self.name + ' has pouch contents:' ]
for obj in self.pouch_contents:
s = ' ' + object.__str__(obj)
t.append(s)
return 'n'.join(t)
def put_in_pouch(self, item):
self.pouch_contents.append(item)
kanga = Kangaroo('Kanga')
roo = Kangaroo('Roo')
kanga.put_in_pouch('wallet')
kanga.put_in_pouch('car keys')
kanga.put_in_pouch(roo)
print(kanga)
看起来没什么问题,init方法确定了类的两个属性,name取名,pouch_content一个空列表,put_in_pouch方法,为这个空列表加入任意对象,str方法,显示对象的名字,并按列显示列表中的各个元素,运行一下:
Kanga has pouch contents:
'wallet'
'car keys'
<__main__.Kangaroo object at 0x7fd308747ef0>
似乎也没有问题,按照提示,接下来直接print(roo),我们的预期结果roo的列表为一个空列表,但结果却出人意料:
Roo has pouch contents:
'wallet'
'car keys'
<__main__.Kangaroo object at 0x7f56fe8e3ef0>
Kanga和roo的打印结果竟然是一样的!
2.原因解析
经过分析,问题在于init方法的定义部分,def__init__(self, name, contents=[])这句代码中的参数:
- self为调用方法的对象,也就是主语,在本例程为kange
- name为位置参数,contents为默认参数,默认值为空列表
原因就在于:默认参数指向了一个空列表,而列表本身是可变的(在10月11日的文章中有所阐述),函数在定义的时候,默认参数的值就被计算出来了,即[],因为默认参数也是一个变量,它指向对象[],每次调用该函数,如果改变了默认参数的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了!
简而言之:一个函数参数的默认值,仅仅在该函数定义的时候,被赋值一次。
接下来我会举一个更简单的例子进行解释。
3.简单例子
def add_hi(L=[]):
print('before:',L)
L.append('hi')
print('after:',L)
return L
a=add_hi()
print('OUTPUT1:',a)
b=add_hi()
print('OUTPUT2:',b)
before: []
after: ['hi']
OUTPUT1: ['hi']
before: ['hi']
after: ['hi', 'hi']
OUTPUT2: ['hi', 'hi']
从这里例子中可以看出,在第二次调用函数时,参数L已经变成了['hi'],而不是默认的[],这也证实了默认值只在函数定义时被赋值一次的事实。
4.解决方法
默认参数的默认值只需要设定为不变对象即可,如None,解决办法如下:
- 简单例子
def add_hi(L=None):
if L is None:
L = []
print('before:',L)
L.append('hi')
print('after:',L)
return L
a=add_hi()
print('OUTPUT1:',a)
b=add_hi()
print('OUTPUT2:',b)
before: []
after: ['hi']
OUTPUT1: ['hi']
before: []
after: ['hi']
OUTPUT2: ['hi']
- 例程
def __init__(self, name, contents=None):
self.name = name
if contents == None:
contents = []
self.pouch_contents = contents
Kanga has pouch contents:
'wallet'
'car keys'
<__main__.Kangaroo object at 0x7fc674181f60>
Roo has pouch contents:
总结:默认参数必须指向不变对象!!!!!
二.python计算机视觉——基本图像操作处理(下)
3.numpy
- 灰度变换
将灰度图像读入数组后,直接对数组对应的像素值进行处理即可,例如:
im2 =255- im # 对图像进行反相处理
im3 =(100.0/255)* im +100# 将图像像素值变换到 100...200 区间
im4 =255.0*(im/255.0)**2# 对图像像素值求平方后得到的图像
常见的灰度变换有:
线性变换,如图片反向,能有效增强在图像暗区域的白色或者灰色细节;
对数变换,能扩展暗像素的值,压缩高灰度的值,对图像中低灰度细节进行增强;
伽马变换(幂),幂大于1,对图像高灰度值部分的扩展明显,幂小于1,对低灰度扩展明显。
查看最小和最大像素值:
print int(im.min()),int(im.max())
将数组转化为图片:
img1 = Image.fromarray(uint8(im))
- 直方图均衡化
原理:将一幅 图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同,能有效增加强图像的对比度。
具体操作:获得直方图——得到累积分布函数并归一化——利用线性插值计算新的像素值
def histeq(im,nbr_bins=256):
""" 对一幅灰度图像进行直方图均衡化 """
# 计算图像的直方图
imhist,bins = histogram(im.flatten(),nbr_bins,normed=True)
cdf = imhist.cumsum() # 累积分布函数
cdf = 255 * cdf / cdf[-1] # 归一化
# 使用累积分布函数的线性插值,计算新的像素值
im2 = interp(im.flatten(),bins[:-1],cdf)
return im2.reshape(im.shape), cdf
- 图像平均
从一个图像列表,计算出一个平均图像,用数组将图像打开,将这些图像简单地相加,然后除以图像的数目,来计算平均图像,可以减小图像噪声。
- 主成分分析
图像具有较高的维数,因此需要降维,主成分分析产生的投影矩阵可以被视为将原始坐标变换到现有的坐标系,坐标系中的各个坐标按照重要性递减排列,先利用flatten()将图像转化为矩阵,一行表示一幅图像,按照平均图像进行中心化,再计算主成分。
若向量维数小于数据个数,直接利用SVD(奇异值分解)分解来计算主成分;
若矩阵维数非常大,可以计算协方差矩阵的特征向量,有效减小计算量
- pickle模块
pickle模块,可以利用dump函数将多个对象保存在一个pkl文件中,利用load函数按顺序将对象读取,如下例:
import pickle
a=[1,2,3,4,5,6]
b=(1,2,3)
f=open('a.pkl','wb')#写入最好以二进制写入
pickle.dump(a,f)
f.close()
import pickle
f=open('a.pkl','rb')
c=pickle.load(f)
d=pickle.load(f)
print(c)
print(d)
f.close()
[1, 2, 3, 4, 5, 6]
(1, 2, 3)
除了该模块外,numpy也自带函数savetxt和loadtxt,用于读写文本文件。
4.SciPy
- 图像模糊——高斯模糊(图像与高斯核做卷积)
滤波操作模块scipy.ndimage.filters ,具有函数.gaussian_filter(img,a),其中a越大,处理后的细节丢失越多。
模糊灰度图像直接使用函数即可,模糊彩色图像,需要对每个通道都模糊处理。
- 图像导数
梯度大小描述了图像强度变化强弱,梯度角度描述了图像每个像素强度变化最大的方向。
方向导数可以利用卷积离散近似计算,filters模块有多种导数滤波器,如:
sobel:滤波器尺度要随着图像分 辨率的变化而变化
gauss:能在任意尺度计算导数,可设置标准差
from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import filters
im = array(Image.open('HIT.jpg').convert('L'))
#高斯滤波x导数
imx = zeros(im.shape)
filters.gaussian_filter(im,(5,5),(0,1),imx)
#高斯滤波y导数
imy= zeros(im.shape)
filters.gaussian_filter(im,(5,5),(1,0),imy)
#梯度
imm=sqrt(imx**2+imy**2)
figure(1)
subplot(141)
imshow(im,cmap='gray')
subplot(142)
imshow(imx,cmap='gray')
subplot(143)
imshow(imy,cmap='gray')
subplot(144)
imshow(imm,cmap='gray')
show()
在x方向和y方向两张图像中,正导数为亮的像素,负导数为暗的像素,灰色为导数接近零,从结果可以看出,图像导数可以用于绘制图像轮廓。
- 形态学:对象计数
形态学是度量和分析基本形状的图像处理方法的基本框架与集合,关于形态学操作,已在2020.10.11日的文章中有所阐述。
形态学常用于二值图像处理,scipy.ndimage的measurements 模块可以进行二值图像的计数和度量,如labels函数可以寻找单个的物体(连通域),若某些对象之间存在小连接,可以用二进制开操作(morphology模块中的binary_opening函数进行去除。
from PIL import Image
from numpy import *
from pylab import *
from scipy.ndimage import measurements,morphology
im = array(Image.open('jishu.JPG').convert('L'))
im=1*(im<128)
#直接计数
labels, nbr_objects = measurements.label(im)
print ("Number of objects:", nbr_objects)
#开操作
im_open = morphology.binary_opening(im,ones((8,5)),iterations=2)
labels_open, nbr_objects_open = measurements.label(im_open)
print ("Number of object after opening :", nbr_objects_open)
Number of objects: 23
Number of object after opening : 28
从结果看出,原图像的二值化图像,白色对象之间存在一定的连接,经过开运算后,大部分连接被消除,计数效果更好,但是也导致部分对象被腐蚀为两个对象。
- 其他常用模块
1)io模块中,可以利用loadmat和savemat函数,读写.mat文件。
2)misc模块中,使用imsave函数,可以将数组以图像的形式保存。
- ROF去噪
ROF模型进行图像去噪效果较好:处理后图像更平滑,同时保持图像边缘和结构信息。
模型原理:全变差J(U)为梯度范数的和,目标函数为寻找一个降噪后的图像U,使下式最小:
该式左半部分为去噪前后图像的差异,因此整体会使去噪后的像素值平坦变化,又在边缘上允许存在突变,能够有效保护边缘信息。
测试结果如下:
从结果可以看出
但是,在测试书中代码中,产生了一个关于调用模块的十分隐秘的问题,这引发了我接下来的探索。
三、由random模块引发的numpy和pylab的导入问题
在测试ROF模型的例程时,为了比较效果,需要使用numpy中random的standard_normal函数,利用噪声创建一个图像,将该代码简化为如下:
#导入random
from numpy import *
from numpy import random
#
from pylab import *
#利用标准随机正态分布创建图像
a=random.standard_normal((500,500))
im = zeros((500,500))
im[100:400,100:400] = 128
im[200:300,200:300] = 255
im=im+a
imshow(im)
show()
但是出现了如下的报错:
AttributeError: 'builtin_function_or_method' object has no attribute 'standard_normal'
标准正态分布函数发生了报错,内建函数或方法对象不能转换成对应的属性。
经过查阅发现,random模块一共有两个:python自带的,numpy库内部的,我本以为可能是调用到了python自带的random模块,这个自带模块中没有这个函数,进而导致找不到'standard_normal'函数。
但这是不可能的,因为如果使用自带的random模块,需要利用import调用
import random
之后再对各个部分进行删减的时候突然发现!把from pylab import *删除就可以运行了!
然后我尝试打开pylab文件,发现了这两行代码:
from numpy import *
from numpy.random import *
也就说当将pylab库全部导入后,numpy库和numpy的random被同时导入了。
- 接下来是我的猜想:
1)from numpy.random import *的优先级可能大于from numpy import random。
2)对于代码“a=random.standard_normal((500,500))”,在这种情况下,会直接调用random模块的random函数,也就是numpy.random.random,而这个函数下自然没有标准正态分布函数standard_normal,因此报错。
把代码改成如下:(保留from pylab import *,去掉standard_normal前的random)
from numpy import *
from numpy import random
from pylab import *
#import random
a=standard_normal((500,500))####改动的部分
im = zeros((500,500))
im[100:400,100:400] = 128
im[200:300,200:300] = 255
im=im+a
#print(a)
imshow(im)
show()
结果能够正常运行,因此为了规避这种情况再次发生,可能需要避免把全体模块导入,即:
from numpy import * ——> import numpy as np
from pylab import * ——> import matplotlib.pyplot as plt
保持命名空间的方法,虽然繁琐,但是能够保证函数调用的单一指向,尤其能规避不同库中存在重名函数,导致调用出现偏移的情况。