目录
一、K邻近分类法(KNN)
在分类方法中,最简单且用得最多的一种方法之一就是 KNN(K-Nearest Neighbor ,K邻近分类法)。其把要分类的对象与训练集中已知类标记的所有对象进行对比,并由 k 近邻对指派到哪个类进行投票。其弊端在于对于大的数据集,搜索起来的速度会变慢。最基本的 KNN 形式的实现方法为:
import numpy as np
class KnnClassifier(object):
def __init__(self,labels,samples):
self.labels = labels
self.samples = samples
def classify(self,point,k=3):
dist = np.array([L2dist(point,s) for s in self.samples])
ndx = dist.argsort()
votes = {}
for i in range(k):
label = self.labels[ndx[i]]
votes.setdefault(label,0)
votes[label] += 1
return max(votes)
def L2dist(p1,p2):
return np.sqrt( sum( (p1-p2)**2) )
1. 二维示例
首先需要生成两个用于进行示例的脚本,可以使用下述的代码来生成,并使用pickle来进行保存:
import pickle
import numpy as np
from numpy.random import randn
# 创建二维样本数据
n = 200
class_1 = 0.6 * randn(n,2)
class_2 = 1.2 * randn(n,2) + np.array([5,1])
labels = np.hstack((np.ones(n),-np.ones(n)))
# 用 Pickle 模块保存
with open(r'D:\python\school\testdata\points_normal_test.pkl', 'wb') as f:
pickle.dump(class_1,f)
pickle.dump(class_2,f)
pickle.dump(labels,f)
# 正态分布,并使数据成环绕状分布
class_1 = 0.6 * randn(n,2)
r = 0.8 * randn(n,1) + 5
angle = 2*np.pi * randn(n,1)
class_2 = np.hstack((r*np.cos(angle),r*np.sin(angle)))
labels = np.hstack((np.ones(n),-np.ones(n)))
# 用 Pickle 保存
with open(r'D:\python\school\testdata\points_ring_test.pkl', 'wb') as f:
pickle.dump(class_1,f)
pickle.dump(class_2,f)
pickle.dump(labels,f)
需要将上述代码运行两个,并修改名字后缀一个为train,一个为test。最后可以得到如下的4个pickle文件以便后续的使用,其中两个用于进行测试,另外两个用于进行训练。
之后就是实现KNN分类器了,首先使用了Pickle模块创建了一个KNN分类器模型,并载入测试的数据。
import pickle
from matplotlib import pyplot as plt
import numpy as np
from numpy.random import randn
import knn
from PCV.tools import imtools
with open(r'D:\python\school\testdata\points_normal_train.pkl', 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
model = knn.KnnClassifier(labels,np.vstack((class_1,class_2)))
with open(r'D:\python\school\testdata\points_normal_test.pkl', 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
# 在测试数据集的第一个数据点上进行测试
print (model.classify(class_1[0]))
def classify(x,y,model=model):
return np.array([model.classify([xx,yy]) for (xx,yy) in zip(x,y)])
# 绘制分类边界
imtools.plot_2D_boundary([-6,6,-6,6],[class_1,class_2],classify,[1,-1])
plt.show()
最后可以使用plt将分类的结果展示出来:
其中分类正确的点采用的是星号来表示,分类错误的点使用的是圆圈,不同的颜色表示不同的类别,其中的曲线是分类的边界。
2. 用稠密SIFT作为图像特征
在整幅图像上用一个规则的网格应用 SIFT 描述子可以得到稠密 SIFT 的表示形式,其实现为:
import os
from PIL import Image
import sift
import numpy as np
def process_image_dsift(imagename,resultname,size=20,steps=10,force_orientation=False,resize=None):
im = Image.open(imagename).convert('L')
if resize!=None:
im = im.resize(resize)
m,n = im.size
if imagename[-3:] != 'pgm':
im.save('tmp.pgm')
imagename = 'tmp.pgm'
# 创建帧,并保存到临时文件
scale = size/3.0
x,y = np.meshgrid(range(steps,m,steps),range(steps,n,steps))
xx,yy = x.flatten(),y.flatten()
frame = np.array([xx,yy,scale*np.ones(xx.shape[0]),np.zeros(xx.shape[0])])
np.savetxt('tmp.frame',frame.T,fmt='%03.3f')
if force_orientation:
cmmd = str(r"D:\study\vlfeat-0.9.20-bin\vlfeat-0.9.20\bin\win64\sift.exe "+imagename+" --output="+resultname+" --read-frames=tmp.frame --orientations")
else:
cmmd = str(r"D:\study\vlfeat-0.9.20-bin\vlfeat-0.9.20\bin\win64\sift.exe "+imagename+" --output="+resultname+" --read-frames=tmp.frame")
os.system(cmmd)
print ('processed', imagename, 'to', resultname)
from PIL import Image
from matplotlib import pyplot as plt
import numpy as np
import dsift,sift
dsift.process_image_dsift(r"D:\test\pil.png",r"D:\test\pil.sift",90,40,True)
l,d = sift.read_features_from_file(r"D:\test\pil.sift")
im = np.array(Image.open(r"D:\test\pil.png"))
sift.plot_feature(im,l,True)
plt.show()
最终得到的结果为:
3. 手势识别
首先需要下载手势的数据集,在官网下载之后,使用如下代码得到数据集图片的dsift特征:
import os
import dsift
# 将图像尺寸调为 (50,50),然后进行处理
def get_imlist(path):
return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.ppm')]
imlist = get_imlist(r'D:\test\uniform\test')
for filename in imlist:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
其中的代码将图像分辨率调成了常见的固定大小。不这么做的话图像会有不同数量的描述子,从而导致每幅图像的特征向量长度也不一样。可以使用下述代码进行查看:
import sift, dsift
from pylab import *
from PIL import Image
import os
imlist = ['D:/test/uniform/train/C-uniform01.ppm', 'D:/test/uniform/train/B-uniform31.ppm',
'D:/test/uniform/train/A-uniform01.ppm', 'D:/test/uniform/train/Five-uniform39.ppm',
'D:/test/uniform/train/Point-uniform37.ppm', 'D:/test/uniform/train/V-uniform31.ppm']
figure()
for i, im in enumerate(imlist):
print(im)
dsift.process_image_dsift(im, im[:-3] + 'dsift', 10, 5, True, resize=(50,50))
l, d = sift.read_features_from_file(im[:-3] + 'dsift')
dirpath, filename = os.path.split(im)
im = array(Image.open(im).resize((50, 50)))
# 显示手势含义title
titlename = filename[:-14]
subplot(2, 3, i + 1)
sift.plot_feature(im, l, True)
title(titlename)
show()
可以得到如下的结果:
实现代码为:
import knn
import dsift
import os, sift
import numpy as np
def print_confusion(res,labels,classnames):
n = len(classnames)
class_ind = dict([(classnames[i],i) for i in range(n)])
confuse = np.zeros((n,n))
for i in range(len(test_labels)):
confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
print ('Confusion matrix for')
print (classnames)
print (confuse)
def read_gesture_features_labels(path):
# 对所有以 .dsift 为后缀的文件创建一个列表
featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
# 读取特征
features = []
for featfile in featlist:
l,d = sift.read_features_from_file(featfile)
features.append(d.flatten())
features = np.array(features)
# 创建标记
labels = [featfile.split('/')[-1][0] for featfile in featlist]
return features,np.array(labels)
def get_imlist(path):
return [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.ppm')]
imlist1 = get_imlist('D:/test/uniform/train/')
imlist2 = get_imlist('D:/test/uniform/test/')
# 将图像尺寸调为(50,50),然后进行处理
for filename in imlist1:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
for filename in imlist2:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
features,labels = read_gesture_features_labels('D:/test/uniform/train/')
test_features,test_labels = read_gesture_features_labels('D:/test/uniform/test/')
classnames = np.unique(labels)
k = 1
knn_classifier = knn.KnnClassifier(labels,features)
# import pdb;pdb.set_trace()
res = np.array([knn_classifier.classify(test_features[i],k) for i in range(len(test_labels))])
# 准确率
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print ('Accuracy:', acc)
print_confusion(res,test_labels,classnames)
可以得到结果为:
可以看到有70%的图像是正确的,其值会随着随 k 值及稠密 SIFT 图像描述子参数的选择而变化。下方的混淆矩阵可以显示每类有多少个样本被分在每一类中的矩阵,可以显示错误的分布情况,以及哪些类是经常相互“混淆”的。
二、贝叶斯分类器
1.贝叶斯分类器
贝叶斯分类器是一种基于贝叶斯条件概率定理的概率分类器,它假设特征是彼此独立不相关的。贝叶斯分类器可以非常有效地被训练出来,原因在于每一个特征模型都是独立选取的;其次贝叶斯分类器一旦学习了这个模型,就没有必要存储训练数据了,只需存储模型的参数。
贝叶斯分类器是通过将各个特征的条件概率相乘得到一个类的总概率,然后选取概率最 高的那个类构造出来的。
对于贝叶斯分类器的实验,首先需要创建一个贝叶斯的py文件,里面存在贝叶斯类的相关函数和代码:
import numpy as np
class BayesClassifier(object):
def __init__(self):
self.labels = [] # 类标签
self.mean = [] # 类均值
self.var = [] # 类方差
self.n = 0 # 类别数
def train(self,data,labels=None):
if labels==None:
labels = range(len(data))
self.labels = labels
self.n = len(labels)
for c in data:
self.mean.append(np.mean(c,axis=0))
self.var.append(np.var(c,axis=0))
def classify(self,points):
# 计算每一类的概率
est_prob = np.array([gauss(m,v,points) for m,v in zip(self.mean,self.var)])
# 获取具有最高概率的索引,该索引会给出类标签
ndx = est_prob.argmax(axis=0)
est_labels = np.array([self.labels[n] for n in ndx])
return est_labels, est_prob
def gauss(m,v,x):
if len(x.shape)==1:
n,d = 1,x.shape[0]
else:
n,d = x.shape
# 协方差矩阵,减去均值
S = np.diag(1/v)
x = x-m
# 概率的乘积
y = np.exp(-0.5*np.diag(np.dot(x,np.dot(S,x.T))))
# 归一化并返回
return y * (2*np.pi)**(-d/2.0) / ( np.sqrt(np.prod(v)) + 1e-6)
上述代码中 train() 方法的作用是获取特征数组列表,并计算每个特征数组的均值和协方差;classify() 方法的作用是计算数据点构成的数组的类概率,并选概率最高的那个类,最终返回预测的类标记及概率值。gauss()辅助函数主要是为了计算单个高斯分布的乘积。
接着就可以在主函数中进行使用了:
import pickle
from matplotlib import pyplot as plt
import bayes
import numpy as np
from PCV.tools import imtools
with open(r'D:\python\school\testdata\points_normal_train.pkl', 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
# 训练贝叶斯分类器
bc = bayes.BayesClassifier()
bc.train([class_1,class_2],[1,-1])
# 用 Pickle 模块载入测试数据
with open(r'D:\python\school\testdata\points_normal_test.pkl', 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
print (bc.classify(class_1[:10])[0])
# 绘制这些二维数据点及决策边界
def classify(x,y,bc=bc):
points = np.vstack((x,y))
return bc.classify(points.T)[0]
imtools.plot_2D_boundary([-6,6,-6,6],[class_1,class_2],classify,[1,-1])
plt.show()
最终可以得到如下的图形:
这里使用的数据是前面生成的数据。图形中的颜色代表了类标记。正确分类的点用星号表示,误错分类的点用圆点表示,曲线是分类器的决策边界。
2.用PCA降维
前面的手势识别使用的稠密 SIFT 描述子的特征向量十分庞大,因此可以在用数据拟合模型之前进行降维处理,可以使用主成分分析(PCA)来进行降维。其中使用了前面所讲过的pca函数以及其他两个打印混淆矩阵和读取dsift文件的函数。
import os
import pickle
from matplotlib import pyplot as plt
import bayes
import numpy as np
from PCV.tools import imtools
import dsift, sift
def pca(X):
num_data,dim = X.shape
mean_X = X.mean(axis=0)
X = X - mean_X
if dim>num_data:
M = np.dot(X,X.T)
e,EV = np.linalg.eigh(M)
tmp = np.dot(X.T,EV).T
V = tmp[::-1]
S = np.sqrt(e)[::-1]
for i in range(V.shape[1]):
V[:,i] /= S
else:
U,S,V = np.linalg.svd(X)
V = V[:num_data]
return V,S,mean_X
def print_confusion(res,labels,classnames):
n = len(classnames)
class_ind = dict([(classnames[i],i) for i in range(n)])
confuse = np.zeros((n,n))
for i in range(len(test_labels)):
confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
print ('Confusion matrix for')
print (classnames)
print (confuse)
def read_gesture_features_labels(path):
# 对所有以 .dsift 为后缀的文件创建一个列表
featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
# 读取特征
features = []
for featfile in featlist:
l,d = sift.read_features_from_file(featfile)
features.append(d.flatten())
features = np.array(features)
# 创建标记
labels = [featfile.split('/')[-1][0] for featfile in featlist]
return features,np.array(labels)
def get_imlist(path):
return [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.ppm')]
imlist1 = get_imlist('D:/test/uniform/train/')
imlist2 = get_imlist('D:/test/uniform/test/')
# 将图像尺寸调为(50,50),然后进行处理
for filename in imlist1:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
for filename in imlist2:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
features,labels = read_gesture_features_labels('D:/test/uniform/train/')
test_features,test_labels = read_gesture_features_labels('D:/test/uniform/test/')
classnames = np.unique(labels)
V,S,m = pca(features)
# 保持最重要的成分
V = V[:50]
features = np.array([np.dot(V,f-m) for f in features])
test_features = np.array([np.dot(V,f-m) for f in test_features])
bc = bayes.BayesClassifier()
blist = [features[np.where(labels==c)[0]] for c in classnames]
bc.train(blist,classnames)
res = bc.classify(test_features)[0]
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print ('Accuracy:', acc)
print_confusion(res,test_labels,classnames)
可以得到结果为:
看书本上讲的是其效果不如K邻近分类器,但是这里的实验发现它的效果反而比K 邻近分类器更好,猜测可能代码中存在问题,等待后续解决。
贝叶斯分类器不需要保存任何训练数据,而且只需保存每个类的模型参数。这一结果会随着 PCA 维度选取的不同而发生巨大 的变化。
三、支持向量机
SVM(Support Vector Machine,支持向量机)是一类强大的分类器,可以在很多分类问题中给出现有水准很高的分类结果。最简单的 SVM 通过在高维空间中寻找一个最优线性分类面,尽可能地将两类数据分开。对于一特征向量 x 的决策函数为:
其中i 是从训练集中选出的部分样本,这里选择的样本称为支持向量。SVM的一个优势为可以使用核函数。通过核函数映射,依然可以保持对决策函数的控制,从而可以有效地解决非线性或者很难的分类问题。
常见的核函数有:
- 线性核函数,即在特征空间中的超平面是线性的,
- 多项式核函数,用次数为 d 的多项式对特征进行映射,
- 径向基函数,指数函数是一种极其有效的选择,
- Sigmoid函数,一个更光滑的超平面替代方案,
每个核函数的参数都是在训练阶段确定的。
1.使用LibSVM
LibSVM是一个使用最广泛的SVM的实现工具包。需要在GitHub下载对应的工具包轴导入到项目的路径中来使用。其中一个使用径向基函数训练一个SVM分类器的示例为:
import pickle
from libsvm.python.libsvm.svmutil import *
from PCV.tools import imtools
# 用 Pickle 载入二维样本点
with open(r'D:\python\school\testdata\points_normal_train.pkl', 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
# 转换成列表,便于使用 libSVM
class_1 = map(list,class_1)
class_2 = map(list,class_2)
labels = list(labels)
samples = list(class_1)+list(class_2) # 连接两个列表
# 创建 SVM
prob = svm_problem(labels,samples)
param = svm_parameter('-t 2')
m = svm_train(prob,param)
res = svm_predict(labels,samples,m)
最终可以得到结果为:
在使用是,有加上参数"-t 2",其代表的是使用径向基函数,一共有4中核函数,其中0表示线性函数,1表示多项式函数,3表示使用sigmoid函数。
之后加载其他的数据集进行测试:
with open(r'D:\python\school\testdata\points_normal_test.pkl', 'rb') as f:
class_1 = pickle.load(f)
class_2 = pickle.load(f)
labels = pickle.load(f)
# 转换成列表,便于使用 LibSVM
class_1 = list(map(list,class_1))
class_2 = list(map(list,class_2))
# 定义绘图函数
def predict(x,y,model=m):
xy_list = list(zip(x, y))
return np.array(svm_predict([0]*len(x),xy_list,model)[0])
# 绘制分类边界
imtools.plot_2D_boundary([-6,6,-6,6],[np.array(class_1),np.array(class_2)],predict,[-1,1])
plt.show()
得到结果为:
2.手势识别
对前面使用KNN方法的手势识别进行修改,将其分类方法修改为使用LibSVM的线性函数。
from libsvm.python.libsvm.svmutil import *
from PCV.tools import imtools
import dsift
import os, sift
import numpy as np
def print_confusion(res,labels,classnames):
n = len(classnames)
class_ind = dict([(classnames[i],i) for i in range(n)])
confuse = np.zeros((n,n))
for i in range(len(test_labels)):
confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
print ('Confusion matrix for')
print (classnames)
print (confuse)
def read_gesture_features_labels(path):
# 对所有以 .dsift 为后缀的文件创建一个列表
featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
# 读取特征
features = []
for featfile in featlist:
l,d = sift.read_features_from_file(featfile)
features.append(d.flatten())
features = np.array(features)
# 创建标记
labels = [featfile.split('/')[-1][0] for featfile in featlist]
return features,np.array(labels)
def get_imlist(path):
return [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.ppm')]
def convert_labels(labels, transl):
# 如果 labels 是一个数字数组,则需要将其转换成原始标签
if isinstance(labels[0], int):
return [transl[i] for i in labels]
# 否则,如果 labels 是原始标签,则需要将其转换成数字标签
else:
return [transl[label] for label in labels]
imlist1 = get_imlist('D:/test/uniform/train/')
imlist2 = get_imlist('D:/test/uniform/test/')
# 将图像尺寸调为(50,50),然后进行处理
for filename in imlist1:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
for filename in imlist2:
featfile = filename[:-3]+'dsift'
dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))
features,labels = read_gesture_features_labels('D:/test/uniform/train/')
test_features,test_labels = read_gesture_features_labels('D:/test/uniform/test/')
classnames = np.unique(labels)
features = list(map(list,features))
test_features = list(map(list,test_features))
# 为标记创建转换函数
transl = {}
for i,c in enumerate(classnames):
transl[c],transl[i] = i,c
# 创建 SVM
prob = svm_problem(convert_labels(labels,transl),features)
param = svm_parameter('-t 0')
m = svm_train(prob,param)
# 在训练数据上分类效果如何
res = svm_predict(convert_labels(labels,transl),features,m)
# 测试 SVM
res = svm_predict(convert_labels(test_labels,transl),test_features,m)[0]
res = convert_labels(res,transl)
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print('Accuracy:', acc)
print_confusion(res,test_labels,classnames)
可以得到如下结果:
当使用了PCA进行降维之后的结果为:
同样的发现结果会比降维前的效果差一些。
四、光学字符识别
OCR(Optical Character Recognition,光学字符识别)是一个理解手写或机写文本图像的处理过程。
1.训练分类器
对于数独的问题,其中包含10个类别,分别为1,·····,9以及什么都没有的单元格,将什么都没有的单元格标号为0。
2.选取特征
首先要确定选取怎样的特征向量来表示每一个单元格里的图像,这里使用的是一些简单而有效的特征。
import os
from PIL import Image
import numpy as np
def compute_feature(im):
norm_im = imresize(im,(30,30))
norm_im = norm_im[3:-3,3:-3]
return norm_im.flatten()
def load_ocr_data(path):
imlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]
# 创建标记
labels = [int(imfile.split('/')[-1][0]) for imfile in imlist]
# 从图像中创建特征
features = []
for imname in imlist:
im = np.array(Image.open(imname).convert('L'))
features.append(compute_feature(im))
return np.array(features),labels
3.多类支持向量机
接着就可以进行字符识别的实验了,将上述的函数加入到测试代码中:
features,labels = load_ocr_data('D:/python/school/testdata/sudoku_images/ocr_data/training/')
# 测试数据
test_features,test_labels = load_ocr_data('D:/python/school/testdata/sudoku_images/ocr_data/testing/')
features = list(map(list,features))
test_features = list(map(list,test_features))
prob = svm_problem(labels,features)
param = svm_parameter('-t 0')
m = svm_train(prob,param)
# 在训练数据上分类效果如何
res = svm_predict(labels,features,m)
# 在测试集上表现如何
res = svm_predict(test_labels,test_features,m)
最终可以得到识别的结果为:
4. 提取单元格并识别字符
有了识别单元格内容的分类器后,下一步就是自动地找到这些单元格。
imname = 'D:/python/school/testdata/sudoku_images/sudokus/sudoku18.jpg'
vername = 'D:/python/school/testdata/sudoku_images/sudokus/sudoku18.sud'
im = np.array(Image.open(imname).convert('L'))
# 查找单元格边界
x = find_sudoku_edges(im,axis=0)
y = find_sudoku_edges(im,axis=1)
# 裁剪单元格并分类
crops = []
for col in range(9):
for row in range(9):
crop = im[int(y[col]):int(y[col+1]),int(x[row]):int(x[row+1])]
crops.append(compute_feature(crop))
res = svm_predict(np.loadtxt(vername),list(map(list,crops)),m)[0]
res_im = np.array(res).reshape(9,9)
print ('Result:')
print (res_im)
其原图为:
在 sudokus 文件夹里包含着不同难易程度的数独图像,每幅图像都对应一个包含数独真实值的文件,可以用它来检查识别结果。从中挑选一幅图像,用以检查图像裁剪及分类的性能。
5.图像校正
该例子中校正方法的前提是网格的 4 个角点都已经被检测到或者手工做过标记。这里的实验是手工标记 4 个角点,然后将图像变换为一个 1000×1000 大小的方形图像。
from PIL import Image
from matplotlib import pyplot as plt
import numpy as np
from scipy import ndimage
from PCV.geometry import homography
imname = 'D:/python/school/testdata/sudoku_images/sudokus/sudoku8.jpg'
im = np.array(Image.open(imname).convert('L'))
# 标记角点
plt.figure()
plt.imshow(im)
plt.gray()
x = plt.ginput(4)
# 左上角、右上角、右下角、左下角
fp = np.array([np.array([p[1],p[0],1]) for p in x]).T
tp = np.array([[0,0,1],[0,1000,1],[1000,1000,1],[1000,0,1]]).T
# 估算单应矩阵
H = homography.H_from_points(tp,fp)
# 辅助函数,用于进行几何变换
def warpfcn(x):
x = np.array([x[0],x[1],1])
xt = np.dot(H,x)
xt = xt/xt[2]
return xt[0],xt[1]
# 用全透视变换对图像进行变换
im_g = ndimage.geometric_transform(im,warpfcn,(1000,1000))
plt.imshow(im_g)
plt.show()
在得到的初始的图形中点击需要校正图片的四个角,最后就可以得到校正后的图片了。