Resive world

Come world to Record life


  • Home

  • Tags

  • Categories

  • Archives

  • Sitemap

  • Search

路径匹配之最长公共子序列LCSS算法简析

Posted on 2016-04-20 | In Trajectory Similarity

简述

LCSS算法其实就是我们熟悉的LCS算法(Longest Common Subsequence 最长公共子序列),一个非常基础的dp。以前一直以为LCS算法没啥用,完全就是为了应付比赛用的,现在才发现原来LCS算法竟然在路径匹配上也能有很大作用。

问题描述

LCSS算法解决的问题是,给定两个序列$(A_1,A_2,A_3,A_4…A_n)$和$(B_1,B_2,B_3,B_4…B_m)$,其中每一个元素可以都可以是一个二维坐标点或者是更高维度的坐标。现在我们需要分别从序列$A,B$中依次抽取出$k$个点构成序列$SubA$,$SubB$,使得$SubA$和$SubB$的点一一“相等”。这里“相等”的含义可以定义为两个点之间的距离(通常是欧式距离)小于一个阈值$\varepsilon$,如下图所示:

其中$p_2,p^,_1$ 、$p_4,p^,_3$ 便可以看成是匹配的两组。

我们用满足上述条件的最长的$SubA($或$SubB)$的长度来衡量两个序列$A,B$的相似程度,这也是可以令人理解的。

有时候我们还会对这个定义加一个限制条件:$A_i,B_j$匹配需要满足$|i-j|<\delta$。

简而言之,我们的LCSS需要做的事就是求出这个最长的序列长度。

算法

基础的dp,对于序列$A[1…n],B[1…m]$,令$lcss[i][j]$表示序列$(A_1,A_2,A_3,A_4…A_i)$和$(B_1,B_2,B_3,B_4…B_j)$的最长公共子序列,$dis[i][j]$表示$A_i,B_j$之间的距离,那么我们可以很容易得到下面的递推式:

$for\ 1\leq i\leq n,1\leq j \leq m:$

$$lcss[i][j]=\left\{\begin{aligned}&1+lcss[i-1][j-1],|i-j|<\delta\ \ dis[i][j]<\varepsilon\\&max(lcss[i][j-1],lcss[i-1][j]),else\end{aligned}\right.$$

$else:$

$$lcss[i][j]=0$$

最终求得的$lcss[n][m]$便是最长序列的长度,复杂度为$O(\delta(m+n))$。

后续处理

通过上面的方法,我们能够计算得到路径间的LCSS,但是这并不适合作为相似度的直接评判标准。毕竟较长的路径之间的LCSS在数值上可能比较大,但是事实上的符合程度却不是那么好。因此我们通常会将结果除以较短的路径的长度,即:

$$S(A,B)=\frac{LCSS(A,B)}{min(n,m)}$$

这样得到的值就有了较好的可度量性了。

总结

LCSS算法由于没有用到所有的点,所以能够较好的抵抗噪声。但是判断两点相等的阈值$\varepsilon$不太好确定,这是LCSS的最大弊端。

参考

VLACHOS, M., GUNOPULOS, D., AND KOLLIOS, G. Discovering similar multidimensional trajectories. ICDE 2002
Kevin Toohey, Matt Duckham Trajectory similarity measures.March 2015
Ke Deng, Kexin Xie, Kevin Zheng and Xiaofang Zhou ,Trajectory-Indexing-and-Retrieval
程序员编程艺术第十一章:最长公共子序列(LCS)问题

路径匹配之动态时间规整DTW算法简析

Posted on 2016-04-19 | In Trajectory Similarity

简述

DTW算法又叫动态时间规整(  Dynamic Time Warping),是一个比较简单的dp算法。常用于不等长的离散的路径点的匹配问题,在孤立词语音识别、手势识别、数据挖掘和信息检索等领域有着很不错的表现。

问题描述

DTW解决的问题是,给定两个序列$(A_1,A_2,A_3,A_4…A_n)$和$(B_1,B_2,B_3,B_4…B_m)$,其中每一个元素可以都是一个二维坐标点或者是更高维度的坐标。我们现在需要求出一个“距离”,使得他能够表示这两个路径的相似度。

很明显,如果m等于n,那么我们可以很方便的用对应节点(下标相等)之间的欧氏距离(也可以是其他类型的距离)之和来表示这个”距离“,这看上去还是能让人信服的。但是当m和n不等的时候,我们就发现这种办法就不适用了。但是我们完全可以仍然采用这个思想,只不过与之前每个节点都是一一对应不同,我们可以令其中的某些节点是一对多的对应关系,如下图所示:

当然,这样的对应关系也得满足”非降“的对应,即若$A_i$与$B_j$对应,那么$A_{i+p}$必然不能与$B_{j-q}$对应(其中$p,q\gt 0$)。

这样一来,我们就可以通过计算新的对应点之间的距离之和来表示这两个路径之间的距离。

很明显,这样的匹配方法有很多种,不过对我们来说有意义的匹配方式应该是使最后计算出的距离最小的方式,那么我们到底要怎样确定点的对应关系呢?这就是DTW要解决的问题。

算法

令$dtw[i][j]$表示$A$序列的前$i$个元素与$B$序列的前$j$个元素匹配后得到的最小距离(下标从1开始),$dis[i][j]$表示$A_i$与$B_j$的距离。显然,这时候$A_i$必然和$B_j$匹配。那么我们很容易得到下面的递推关系式(考虑边界条件):

$i=j=0:$

$$dtw[i][j]=0$$

$i=0\ or\ j=0:$

$$dtw[i][j]=\infty$$

$for\ 1\leq i\leq n,1\leq j \leq m:$

$$dtw[i][j]=dis[i][j]+min\left\{\begin{aligned}&dtw[i-1][j]\\&dtw[i-1][j-1]\\&dtw[i][j-1]\end{aligned}\right.$$

最后$dtw[n][m]$就是我们所求的距离,复杂度$O(n*m)$。

总结

DTW算法在应对不等长路径问题的相似度匹配的时候效果还是挺好的,但是由于他需要计算到每一个点,因此他对噪声比较敏感。而且他也无法应对存在时间维度的路径匹配问题。

当然,我们利用DTW算法不仅仅是为了获得距离,很多情况下,我们是为了获得点的对应关系,从而对两个序列更好的进行比较。

参考

Yi, Byoung-Kee, Jagadish, HV and Faloutsos, Christos, Efficient retrieval of similar time sequences under time warping. ICDE 1998
Lei Chen,Raymond Ng,On The Marriage of Lp-norms and Edit Distance,2004
HMM学习笔记_1(从一个实例中学习DTW算法)
语音信号处理之(一)动态时间规整(DTW)
Dynamic time warping

结合连通块平均分割以及投影矫正的验证码分割算法

Posted on 2016-04-17 | In Computer Vision

在上一节 中记录了基于投影的验证码矫正算法的实现。通过矫正,我们可以比较好的将倾斜的字符归一成较为规整的字符,接下来我们需要对矫正后的字符进行分割。简单的方法大概是投影法了,但是很明显,这样做的可靠性并不够。我们也可以找到整张图的最左端和最右端然后平均分割,但是在字符大小不一样的情况下效果也太好。还有个朴素的方法就是找连通块,但是由于存在字符粘连问题,连通块也不能完全区分字符。那么我这里就结合后两种方法,先进行连通块分割,对于能分割的字符直接进行后续处理,对于不能分割的字符再用平均分割的方法分割处理。实践表明这种方法对于那些干扰线不明显的验证码(比如新浪微博的验证码)的分割效果还是不错的。

代码

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import cv2,os,sys,math
import numpy as np
from pylab import *
%matplotlib inline

def getBinary(path):
'''
输入:图片路径名称
输出:黑底二值化的图片

'''
im=cv2.imread(path,0)
thresh,im=cv2.threshold(255-im,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return im

def shadow(im,angel):
'''
输入:灰度图片,投影角度
输出:投影大小

'''
x=[]
height , width = im.shape
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
x.append(j-1.0*i/math.tan(math.radians(angel)))
x=np.array(x)
return x.max()-x.min()

def shadowTest():
'''
测试shadow函数
'''
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:1]:
im=getBinary(os.path.join(directory,pic))
print shadow(im,50)
figure()
gray()
imshow(im)

def getAngle(im):
'''
输入:灰度图
输出:最优的投影角度
'''
minShadow=500
ans=90
for angle in np.linspace(45,135,91):
thisShadow=shadow(im,angle)
if minShadow>thisShadow:
ans=angle
minShadow=thisShadow
return ans

def getAngleTest():
'''
测试getAngle函数
'''
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:5]:
im=getBinary(os.path.join(directory,pic))
print getAngle(im)
gray()
figure()
imshow(im)

def affine(im,angle=None):
'''
输入:灰度图,仿射变换角度(默认为自适应最优角度)
输出:旋转后的灰度图
'''
height,width=im.shape
if angle==None:
angle=getAngle(im)
pts1=np.float32([[width/2,height/2],[width/2,0],[0,height/2]])
pts2=np.float32([[width/2,height/2],[width/2+height/2/math.tan(math.radians(angle)),0],[0,height/2]])
M=cv2.getAffineTransform(pts1,pts2)
dst=cv2.warpAffine(im,M,(width,height))
return dst

def affineTest():
'''
测试affine函数
'''
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:50]:
im=getBinary(os.path.join(directory,pic))
dst=affine(im)
gray()
figure()
imshow(np.hstack([im,dst]))
axis('off')

def splitImg(im,num):
'''
输入:灰度图,图中包含的字符个数
输出:分割后的图片数组
'''
height,width=im.shape
maxPos=0
minPos=width
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
maxPos=max(maxPos,j)
minPos=min(minPos,j)
points=[]
ans=[]
for i in xrange(num+1):
points.append(minPos+(maxPos-minPos)/num*i)
for i in xrange(num):
left=points[i]
right=points[i+1]
p1=np.zeros((height,left))
p2=np.ones((height,right-left+1))*255
p3=np.zeros((height,width-right))
new_im=np.hstack([p1,p2,p3])
for i in xrange(height):
for j in xrange(width):
if im[i][j]==0:
new_im[i][j]=0
ans.append(new_im)
return ans

def seedSplit(im):
'''
输入:灰度图
输出:从图中高亮提取出的字符图片数组
'''
angle=getAngle(im)
height,width = im.shape
vis=np.zeros((height,width))
vis2=np.zeros((height,width))
def mark(i,j):
vis2[i][j]=1
points=[]
points.append((i,j))
for dx in [-1,0,1]:
for dy in [-1,0,1]:
if i+dx>=0 and i+dx<height and j+dy>=0 and j+dy<width and im[i+dx][j+dy]==255 and vis2[i+dx][j+dy]==0:
points.extend(mark(i+dx,j+dy))
return points

def delete(i,j):
vis[i][j]=1
ans=1
for dx in [-1,0,1]:
for dy in [-1,0,1]:
if i+dx>=0 and i+dx<height and j+dy>=0 and j+dy<width and im[i+dx][j+dy]==255 and vis[i+dx][j+dy]==0:
ans+=delete(i+dx,j+dy)
return ans

def getImg(points):
new_im=np.zeros((height,width))
for point in points:
i,j=point
new_im[i][j]=255
return new_im,len(points)

imgs=[]
for i in xrange(height):
for j in xrange(width):
if vis[i][j]==1:
continue
if im[i][j]==255:
num=delete(i,j)
if num<=50:
continue
points=mark(i,j)
imgs.append(getImg(points))
vis[i][j]=1

imgs.sort(lambda left,right:cmp(left[1],right[1]))
new_imgs=[]
for i,j in imgs:
new_imgs.append((affine(i,angle),j))
imgs=new_imgs
ans=[]
if len(imgs)>4:
imgs=imgs[len(imgs)-4:]
for i in imgs[len(imgs)-4:]:
ans.append(i[0])
elif len(imgs)==4:
for i in imgs:
ans.append(i[0])
elif len(imgs)==3:
ans.append(imgs[0][0])
ans.append(imgs[1][0])
ans.extend(splitImg(imgs[2][0],2))
elif len(imgs)==2:
rate=imgs[0][1]*1.0/imgs[1][1]
if rate>0.6:
ans.extend(splitImg(imgs[0][0],2))
ans.extend(splitImg(imgs[1][0],2))
else:
ans.append(imgs[0][0])
ans.extend(splitImg(imgs[1][0],3))
else:
ans.extend(splitImg(imgs[0][0],4))
return ans

def seedSplitTest():
'''
测试seedSplit函数
'''
directory='weibo'
pics=os.listdir(directory)
gray()
for pic in pics[5:15]:
im=getBinary(os.path.join(directory,pic))
figure()
imshow(im)
imgs=seedSplit(im)
for i in imgs:
figure()
imshow(i)

def getTrainPic(im):
'''
输入:图片
输出:依次输出图片中包含的字符,并归一化成统一大小
'''
def getCenterx(img):
height,width=im.shape
points=[]
for i in xrange(height):
for j in xrange(width):
if img[i][j]==255:
points.append(j)
return np.average(points)

def getResult(im,row=30,column=30):
height,width=im.shape
maxX=0
maxY=0
minX=99999
minY=99999
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
maxX=max(maxX,j)
minX=min(minX,j)
maxY=max(maxY,i)
minY=min(minY,i)
im=im[minY:maxY+1].T[minX:maxX+1].T
im=cv2.copyMakeBorder(im,3,3,3,3,cv2.BORDER_CONSTANT)
im=cv2.resize(im,(30,30))
return im

imgs=seedSplit(im)
for i in xrange(len(imgs)):
imgs[i]=(imgs[i],getCenterx(imgs[i]))
imgs.sort(lambda x,y:cmp(x[1],y[1]))
ans=[]
for i,j in imgs:
ans.append(getResult(i))
return ans

def getTrainPicTest():
'''
测试getTrain函数
'''
directory='weibo'
pics=os.listdir(directory)
gray()
for pic in pics[:10]:
im=getBinary(os.path.join(directory,pic))
imgs=getTrainPic(im)
res=np.hstack([imgs[0],imgs[1],imgs[2],imgs[3]])
figure()
imshow(im)
axis('off')
figure()
imshow(res)
axis('off')

getTrainPicTest()

效果








用投影法矫正字符旋转

Posted on 2016-04-15 | In Computer Vision

这是段简单的代码,目的是处理旋转验证码的问题,主要思想就是通过将字符以45°到135°的角度投影下来,得到一系列的投影范围,然后得到这当中投影长度最小的一个角度。这个角度我们就可以简单的把他看成是字符的偏转角。然后用这个角度通过仿射变换得到矫正后的字符。

代码

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
#coding=utf-8
import cv2,os,sys,Image,math
import numpy as np
from pylab import *
%matplotlib inline

def getBinary(path):#传入图片名,返回二值化的图片
im=cv2.imread(path,0)
thresh,im=cv2.threshold(255-im,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return im

def shadow(im,angel):#传入图像im以及角度(角度制)angle,返回图像在该角度下的投影长度
x=[]
height , width = im.shape
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
x.append(j-1.0*i/math.tan(math.radians(angel)))
x=np.array(x)
return x.max()-x.min()

def shadowTest():#测试shadow函数,需要weibo文件夹下的图片
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:10]:
im=getBinary(os.path.join(directory,pic))
print shadow(im,50)
figure()
gray()
imshow(im)

def getAngle(im):#传入图片,返回使他投影最小的角度
minShadow=500
ans=90
for angle in np.linspace(45,135,91):#我这里范围设置的是45到135度,分度值是1度
thisShadow=shadow(im,angle)
if minShadow>thisShadow:
ans=angle
minShadow=thisShadow
return ans

def getAngleTest():#测试getAngle函数
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:5]:
im=getBinary(os.path.join(directory,pic))
print getAngle(im)
figure()
imshow(im)

def affine(im):#将结合上面的函数,输入图片,输出矫正后的图片
height,width=im.shape
angle=getAngle(im)
#下面是仿射变换,通过确定三点的原值和新值来确定变换的过程,这里我们选取的三点是中心点,顶部中心点,和左部中心点。
pts1=np.float32([[width/2,height/2],[width/2,0],[0,height/2]])
pts2=np.float32([[width/2,height/2],[width/2+height/2/math.tan(math.radians(angle)),0],[0,height/2]])
M=cv2.getAffineTransform(pts1,pts2)
dst=cv2.warpAffine(im,M,(width,height))

return dst

def affineTest():#测试affine函数
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:1]:
im=getBinary(os.path.join(directory,pic))
dst=affine(im)
gray()
figure()
imshow(np.hstack([im,dst]))
axis('off')

affineTest()#测试效果

效果图





可以发现这个方法还是有点效果的。

关于WordPress中字体加载慢的问题解决方案

Posted on 2016-04-15 | In Others

最近发现Wordpress有时候加载的特别慢,于是就想办法找了下原因。之前听网上说是因为wordpress用的是Google的字体库,而且是每次都要加载,导致访问慢的,于是当时装了个Disable Google Fonts的插件,禁用了Google字体,然后装了一个Useso take over Google插件,将字体文件改为360托管的字体库,这样就可以访问快点了。当时的效果的确挺好的,结果最近在使用的时候又发现网站访问慢了,用Chrome查了下资源加载的情况,发现访问useso的字体库的时间特别的长。这时候改用Google字体的时候反而更快了。。。这就十分令人惆怅了,有时候用useso的快,有时候用google的快,真的挺麻烦的。后来想想干脆把这个文件下到服务器上不就好了么。。。于是就倒腾出了下面的办法,将当前主题的字体文件下载到了服务器上。

一

首先在源代码中找到加载字体文件的位置,在博客首页的源代码中找到了下面这行:

1
<link rel='stylesheet' id='baskerville_googleFonts-css'  href='//fonts.googleapis.com/css?family=Roboto+Slab%3A400%2C700%7CRoboto%3A400%2C400italic%2C700%2C700italic%2C300%7CPacifico%3A400&#038;ver=4.5' type='text/css' media='all' />

其实搜索fonts就可以找到了这行。根据这行,我们晓得他引用了googleapis的字体包,命名为’baskerville_googleFonts-css’,而’baskerville’事实上就是我当前的主题名。

二

然后我们需要在后台找到上面id对应的那个超链接的位置。打开wordpress的根文件夹,直接搜索’fonts.googleapis.com’这个关键字:

1
$find . -type f |xargs grep fonts.googleapis.com

查找结果为:

1
2
3
4
5
./wp-admin/includes/class-wp-press-this.php:	$open_sans_font_url = ',' . add_query_arg( $query_args, 'https://fonts.googleapis.com/css' );
./wp-content/themes/baskerville/functions.php: wp_register_style('baskerville_googleFonts', '//fonts.googleapis.com/css?family=Roboto+Slab:400,700|Roboto:400,400italic,700,700italic,300|Pacifico:400' );
./wp-content/themes/baskerville/functions.php: $font_url = '//fonts.googleapis.com/css?family=Roboto+Slab:400,700|Roboto:400,400italic,700,700italic,300';
./wp-includes/js/tinymce/plugins/compat3x/css/dialog.css:@import url("https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,300,400,600&subset=latin-ext,latin");
./wp-includes/script-loader.php: $open_sans_font_url = "https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,300,400,600&subset=$subsets";

这样我们就找到了所在文件的位置。大概看一下,实际上用处比较大的是第二行的那串在主体中的定义(对比第一步的内容)(./wp-content/themes/baskerville/functions.php的内容)(其余都是写无关紧要的插件所用的字体)。很明显可以看出来,接下来我们只要把后面那个url换成本地的字体包就可以了。

三

现在就来下字体包,打开第一步中的那个链接,可以看到是下面的内容:

1
2
3
4
5
6
7
8
9
10
11
/* latin */
@font-face {
font-family: 'Pacifico';
font-style: normal;
font-weight: 400;
src: local('Pacifico Regular'), local('Pacifico-Regular'), url(http://fonts.gstatic.com/s/pacifico/v7/Q_Z9mv4hySLTMoMjnk_rCfesZW2xOQ-xsNqO47m55DA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* cyrillic-ext */
@font-face {
......

发现是一段css,使用了很多的外部资源,下面我们就用一个爬虫来把他直接下下来并打包好:

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
#coding:utf-8
import urllib2,cookielib,sys,re,os,urllib
import numpy as np

#网站登陆
cj=cookielib.CookieJar()
opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
opener.addheaders=[('User-agent','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36))')]
urllib2.install_opener(opener)

path='http://fonts.googleapis.com/css?family=Roboto+Slab:400,700|Roboto:400,400italic,700,700italic,300|Pacifico:400'
request=urllib2.Request(path)
response=urllib2.urlopen(request)

html=response.read()
urls=re.findall(r'url\ ((.*?)\ )',html.replace('\n',' '))#由于这里排版会和latex冲突,所以在\和(,以及\和)之间加了空格,使用的时候要删掉

path='font_cache/'

if not os.path.exists(path):
os.mkdir(path)
for url in urls:
urllib.urlretrieve(url,path+url.split('/')[-1])#下载文件

for url in urls:
html=html.replace(url,url.split('/')[-1])#更新改css文件

font=open(path+'font-css','w+')
font.write(html)#保存

这样就生成了一个font_cache文件夹,在这里有所有下好的字体文件以及更新新后的css文件

四

最后把这个文件上传到wordpress的根目录下(放到其他目录有时候会没有权限访问,有点麻烦),然后将function.php中的那个url改成/font_cache/font-css(相对于wordpress的根)即可。(记得备份初始文件防止改错。。。。)

利用Python在图片中添加文字

Posted on 2016-04-14 | In Python

使用OpenCV

在图片中添加文字看上去很简单,但是如果是利用OpenCV来做却很麻烦。OpenCV中并没有使用自定义字体文件的函数,这不仅意味着我们不能使用自己的字体,而且意味着他无法显示中文字符。这还是非常要命的事情。而且他显示出来的文字位置也不太好控制。比如下面的代码,他想做的仅仅是显示数字3:

代码:

1
2
3
4
5
6
7
8
9
10
#coding=utf-8
import cv2
import numpy as np
from pylab import *
%matplotlib inline

font=cv2.FONT_HERSHEY_SIMPLEX#使用默认字体
im=np.zeros((50,50,3),np.uint8)#新建图像,注意一定要是uint8
img=cv2.putText(im,'3',(0,40),font,1.2,(255,255,255),2)#添加文字,1.2表示字体大小,(0,40)是初始的位置,(255,255,255)表示颜色,2表示粗细
imshow(img)

结果:

)

我么可以发现文字出现的位置并不怎么好把握,初始的坐标默认是指左下角的坐标,不怎么方便。而且显示出文字以后,我们不好掌握他实际占的位置和大小。

不过有一点方便的是,我们可以随意改变他的粗细,而不用更换字体。这一点是下面使用PIL进行绘图所不具备的优点。

使用PIL

同样为了生成数字3,下面是使用PIL进行的操作:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Image,ImageFont,ImageDraw
import numpy as np
from pylab import *
%matplotlib inline

font = ImageFont.truetype('3.ttf',50) #使用自定义的字体,第二个参数表示字符大小
im = Image.new("RGB",(50,50))      #生成空白图像
draw = ImageDraw.Draw(im)         #绘图句柄
x,y=(0,0)                  #初始左上角的坐标
draw.text((x,y), '3', font=font)    #绘图
offsetx,offsety=font.getoffset('3')  #获得文字的offset位置
width,height=font.getsize('3')     #获得文件的大小
im=np.array(im)
cv2.rectangle(im,(offsetx+x,offsety+y),(offsetx+x+width,offsety+y+height),(255,255,255),1)#绘出矩形框
imshow(im)

结果:

)

我们可以发现,PIL支持使用自定义的字体文件,而且能够提供字体所占位置的详细信息,我们可以精确的确定文字所占的位置,在应用中特别有用。唯一的不足就是他不能改变字体的粗细(毕竟这用的是字体模板)。

实际应用中看来还要在这两种方法中择优使用。

OpenCV的扩展包opencv_contrib的安装

Posted on 2016-04-13 | In Computer Vision

近日想使用OpenCV里面的诸如SIFT、SURF之类的特征提取算法,结果突然发现OpenCV3.0.0这里并没有书上讲的关于SIFT的函数。查了半天才知道,原来有大量的函数并不在OpenCV的稳定发布版本里,而是在OpenCV_contrib这个扩展包里面。搞了半天才把这玩意搞定(自己傻),下面记录下安装的过程,方便日后的安装。

下载

opencv_contrib包独立于opencv的主体,发布在他的github上。直接在这里下载适合的版本即可。

安装

这个玩意的安装其实不难,照着解压下来的README操作就行了,只是要重新下载编译opencv,过程跟OpenCV安装方法差不多,只是要在cmake的配置中加上-D OPENCV_EXTRA_MODULES_PATH=<path to opencv_contrib/modules/> 参数,(其中<path to opencv_contrib>是opencv_contrib的解压地址)

需要注意的是一定要加上-D BUILD_PYTHON_SUPPORT=ON 选项才能有python接口。如果是直接复制cv2.so文件到python的路径的话则会报“AttributeError: ‘module’ object has no attribute ‘SIFT’”之类的错误。

README的解读

他这里的README很有意思,不仅介绍了他的安装方法,而且也介绍了为什么我们会把很多比较厉害的模块(比如SIFT,SURF等)单独放在一个地方,而不把他融入OpenCV的主体程序:

1
2
3
4
5
6
7
8
9
10
This repository is intended for development of so-called "extra" modules,
contributed functionality. New modules quite often do not have stable API,
and they are not well-tested. Thus, they shouldn't be released as a part of
official OpenCV distribution, since the library maintains binary compatibility,
and tries to provide decent performance and stability.

So, all the new modules should be developed separately, and published in the
`opencv_contrib` repository at first. Later, when the module matures and gains
popularity, it is moved to the central OpenCV repository, and the development team
provides production quality support for this module.

原来是因为这些模块的困难度比较大,而且使用的时候效果不太稳定,而发布版本(Release)则需要稳定性和可靠性;同时,这些模块的使用程度比较低,大多数的开发人员用不到这些包;况且这些模块是独立于主程序开发的,因此也就应当把他们跟主程序分开。

机器学习中分类准确率的评估方法

Posted on 2016-04-09 | In Machine Learning

对机器学习的分类结果进行分析是一个很重要的过程,之前一直忽略了这一个过程,一直到使用了Scikit-learn之后才发现有一堆不懂的名词需要学习。下面主要解释下混淆矩阵、准确率、召回率、f1-score等概念。这些概念其实也是模式识别和信息检索里面经常碰到的东西。

混淆矩阵(Confusion Matrix)

混淆矩阵其实很好理解,就是把预测值和实际值写在同一个矩阵里。假设总共需要分为两类,那么混淆矩阵就是2x2的大小。每一行就是每一类的实际值,每一列就代表的是每一类的预测值。具体含义见下面的表格:

预测类1 预测类2 预测类3
实际类1 43 5 2
实际类2 2 45 3
实际类3 0 1 49

比方下面这个混淆矩阵:

1
2
[515  34]
[ 80 262]

表达的含义是对于一个01的二分类问题,实际值是0且预测值也为0的有515个,实际值是0但预测值为1的有34个,实际值是1但预测值也为0的有80个,实际值是1但预测值为1的有262个。

Accuracy、Recall、F1-score的含义

准确率和召回率是最常用的评估方法,听上去玄乎其实很简单。

准确率是指对于预测而言,我的预测正确的概率。比如上面的那个混淆矩阵表示的结果,预测值为0的准确率就是515/(515+80)=0.87。

召回率是指对于实际而言,我的实际结果能够被正确预测出来的概率。比如上面的混淆矩阵中,实际值为0的召回率就是515/(515+34)=0.94

分出这两个判断标准也是有着实际的重要意义的。

比如通常我们在判断正确率的时候,用Accuracy表示就可以了,但是如果我们面对的是类似地震的预测时,我们并不特别在意他实际的准确率,宁可多预警几次来避免大的损失。此时召回率就显得特别重要了。

最后F1-score其实是准确率和召回率的综合考量,$f1score=\frac{2*Accuracy*Recall}{Accuracy+Recall}$。

相关参考

机器学习 F1-Score, recall, precision
召回率 Recall、精确度Precision、准确率Accuracy、虚警、漏警等分类判定指标
准确率(Accuracy), 精确率(Precision), 召回率(Recall)和F1-Measure

Scikit-learn包基本使用

Posted on 2016-04-09 | In Machine Learning

Scikit-learn的包是机器学习使用的最全也是实用的包,封装了许多机器学习算法,包括各种分类、回归、聚类、降维、模型选择、预处理等许多方面的内容,提供了相当于黑盒的接口,非常适合初学者使用。

在朋友的推荐下发现了Kaggle这个网站,这里面有很多的机器学习的数据和基本的题目,通过这些练习可以比较好的掌握机器学习的算法。因此就在这当中拿了Titanic号遇难人员的预测做了个实验。其实做法十分简单,权当熟悉框架了。

题目要求

题目给定了Titannic号上人员的信息(包括阶层、姓名、性别、年龄、船上直系亲属的个数、船上表亲的个数、船票号、船费、包厢、登船地点等内容),并给出他们的生存情况;然后再给定一些人的信息,让我们预测他们的生存情况。

数据是以csv文件的形式给出的,如上图所示。

最后从类似的文件里读取另外一波人的信息,并将预测结果输出到一个csv文件中。具体数据规范见原题《Titanic号遇难人员的预测》。

解决方案

对于这种问题其实只要把字符串的描述的特征提取成特征向量然后随便跑个学习算法就可以了,至于什么学习算法好还是要具体问题具体分析,都用一遍就知道了,我这里用的是朴素贝叶斯模型。

注意到有些特征是没有意义的,比如名字、船票号啥的,这些特征可以忽略;还有就是有的年龄和票价是没有的,那么简单点考虑就用平均值代替就好了。

具体实现也就很简单了,主要是Scikit-learn的使用。

代码如下:

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#coding:utf-8
import csv,re
import numpy as np
from sklearn import metrics
from sklearn.naive_bayes import *

#read csv data
reader=csv.reader(file('train.csv'))
data=[]
age=[]
price=[]
start=True
for item in reader:
if start:
start=False
continue
data.append(item)
if(item[5]!=''):
age.append(float(item[5]))
if(item[9]!=''):
price.append(float(item[9]))
meanAge=np.array(age).mean()
meanPrice=np.array(price).mean()
for item in data:
if(item[5]==''):
item[5]=meanAge
if(item[9]==''):
item[9]=meanPrice

#generate learning data
def getFeature(dataItem):
feature=[]
#Pclass
if dataItem[2]=='1':
feature.append(1)
elif dataItem[2]=='2':
feature.append(2)
else:
feature.append(3)

#Sex
if dataItem[4]=='female':
feature.append(1)
else:
feature.append(0)

#Age
feature.append(float(dataItem[5]))

#SibSp
feature.append(float(dataItem[6]))

#Parch
feature.append(float(dataItem[7]))

#Fare
feature.append(float(dataItem[9]))

#Cabin
if dataItem[10]=='':
feature.append(0)
else:
feature.append(1)

#Embarked
if dataItem[11]=='S':
feature.extend([0,0,1])
elif dataItem[11]=='C':
feature.extend([0,1,0])
else:
feature.extend([1,0,0])

return feature,int(dataItem[1])

x=[]
y=[]
for item in data:
f,l=getFeature(item)
x.append(f)
y.append(l)

#classify
model = GaussianNB()
model.fit(x, y)

expected=y
predicted = model.predict(x)

print(metrics.classification_report(expected, predicted))
print(metrics.confusion_matrix(expected, predicted))

#predict and write
reader=csv.reader(file('test.csv','rb'))
testData=[]
start=True
for item in reader:
if start:
start=False
continue
newItem=[]
newItem.append(-1)
newItem.append(item[0])
newItem.extend(item[1:])
if(newItem[5]==''):
newItem[5]=meanAge
if(newItem[9]==''):
newItem[9]=meanPrice

testData.append(newItem)

testX=[]
testId=[]
for item in testData:
f,Id=getFeature(item)
testX.append(f)
testId.append(Id)

predictY=model.predict(testX)

writer=csv.writer(file('result.csv','wb'))

writer.writerow(['PassengerId','Survived'])
for i in xrange(len(testId)):
writer.writerow([testId[i],predictY[i]])

要注意以下几点:

  1. 在对csv文件进行读取时,我们从reader中只能逐行读取一遍,因此需要将他读到临时list里方便后续处理;
  2. 区分list的append和extend方法的使用,一个是添加一个元素,一个是合并两个list。
  3. 传入的学习参数时必须都是float数据类型。
  4. 学习模型可以非常容易进行替换,我当前用的是高斯朴素贝叶斯模型,其实完全可以换成决策树(DecisionTreeClassifier)、SVM(SVC)等其他分类模型,而我们要改的只是model = GaussianNB()一行而已。

输出结果:

1
2
3
4
5
6
7
8
9
precision    recall  f1-score   support

0 0.83 0.83 0.83 549
1 0.72 0.72 0.72 342

avg / total 0.79 0.79 0.79 891

[[454 95]
[ 96 246]]

第一块是对于每一个分类所得到的准确率、召回率、f1-score,和分出的总数;

第二块是混淆矩阵;

具体含义可见机器学习中分类准确率的评估方法。

最后尝试了下决策树跟SVM,发现使用决策树的结果是最好的(准确率甚至到了99%)。不过最终提交上去才发现最终的识别率还是好低(72%)。。。不过仔细想想,想这种的预测还是挺不靠谱的,毕竟偶然因素太大了,仅仅凭着这些东西感觉完全不可能达到100%的准确率啊。

朴素贝叶斯分类器

Posted on 2016-04-08 | In Machine Learning

简述

朴素贝叶斯分类器是机器学习中最基础的分类算法了,之前一直忽视这个算法,感觉这种简单利用贝叶斯公式的方法的确很Naive。但是事实上这个算法在对于特征相互独立的分类问题来说还是非常好用的。其基本思想就是在给定在各种情况下一个事件发生的先验概率的情况下,套用贝叶斯公式求出给定各种情况下给定事件发生的后验概率。思想非常简单,但是在某些情况下效果还是非常好的,值得掌握。

贝叶斯公式

贝叶斯公式很简单了,对于事件A和事件B,下面的公式显然成立:

$$P(A|B)=\frac{P(A)P(B|A)}{P(B)}$$

这也基本不用证明,分母移过来两边就显然相等了。

在实际应用中,只要把事件B换成$F_1F_2F_3…F_n$等几个特征就可以了。有了这个公式,对于有互不相关的离散特征的分类问题就可以对数据进行简单统计然后对于给定特征求出预期事件了。

连续特征处理

从贝叶斯公式的使用可以了解到,这个方法只能处理离散性质的问题,比如性别、身份、地区等特征,但是对于类似年龄、身高、体重等连续性比较强的特征就不太好用概率来表示了。这时通常有下面两种处理方式:

分段处理

比如对于年龄,可以分为[0-10],[10-20],[20-30]。。。等较小的区间,这样就可以把他看成是一个离散的特征了。

高斯化处理

把连续的值看成是近似高斯分布,求出均值和标准差(最大似然值),这样对于某一个值我们就可以求出一个值,用这个值来表示概率即可。

除零问题处理

很明显,在某些特殊的情况下贝叶斯分类器的分母可能为零,这样就会导致一些不令人愉悦的错误。这时我们只要将所有的计数都加一,这样在数据较大时就完全可以忽略这点误差,这就叫Laplace校准。

上面就是朴素贝叶斯分类的基本内容,相比与这个“朴素”的算法,还有一个应用贝叶斯公式的算法叫“贝叶斯网络”,暂时还没研究到,以后有机会再来学习。

相关参考

Scikit-learn:Naive Bayes
分类算法之朴素贝叶斯分类
用Python开始机器学习之朴素贝叶斯分类器
朴素贝叶斯分类器的应用

1…434445…58

574 posts
69 categories
286 tags
© 2024 Companyd
Powered by Hexo
|
Theme — NexT.Muse v5.1.4