Python计算机视觉编程——第7章 图像搜索

本文介绍了基于内容的图像检索(CBIR)技术,应用文本挖掘中的矢量空间模型到图像检索。首先,通过SIFT局部描述子创建视觉单词,构建词汇。接着,使用K-means聚类创建词汇并建立图像数据库,将图像特征投影到词汇上。在数据库中搜索图像时,利用索引来获取候选图像,并通过比较单词直方图进行排序。最后,讨论了使用几何特性如单应性对结果进行改进的可能性。
摘要由CSDN通过智能技术生成

目录

7.1 基于内容的图像检索

从文本挖掘中获取灵感——矢量空间模型

7.2 视觉单词

创建词汇

7.3 图像索引

7.3.1 建立数据库

7.3.2 添加图像

7.4 在数据库中搜索图像

7.4.1 利用索引获取候选图像

7.4.2 用一幅图像进行查询

7.4.3 确定对比基准并绘制结果

7.5 使用几何特性对结果排序


7.1 基于内容的图像检索

大型图像数据库上,CBIR(Content-Based Image Retrieval,基于内容的图像检索)技术用于检索在视觉上具相似性的图像。这样返回的图像可以是颜色相似、纹理相似、图像中的物体或场景相似;总之,基本上可以是这些图像自身共有的任何信息。

对于高层查询,比如寻找相似的物体,将查询图像与数据库中所有的图像进行完全比较(比如用特征匹配)往往是不可行的。在数据库很大的情况下,这样的查询方式会消耗过多时间。在过去的几年里,研究者成功地引入文本挖掘技术到CBIR中处理问题,使在数百万图像中搜索具有相似内容的图像成为可能。

从文本挖掘中获取灵感——矢量空间模型

矢量空间模型是用于表示和搜索文本文档的模型。我们将看到,它基本上可以应用于任何对象类型,包括图像。该名字来源于用矢量来表示文本文档,这些矢量是有文本词频直方图构成的。换句话说,矢量包含了每个单词出现的次数,而且在其他别的地方包含很多0元素。由于其忽略了单词出现的顺序及位置,该模型也被称为BOW表示模型。

通过单词计算来构建文档直方图向量v,从而建立文档索引。通常,在单词计数时会忽略掉一些常用词,如“这”“和”“是”等,这些常用词称为停用词。由于每篇文档长度不同,故除以直方图总和将向量归一化成单位长度。对于直方图向量中的每个元素,一般根据每个单词的重要性来赋予相应的权重。通常,数据集(或语料库)中一个单词的重要性来赋予相应的权重。通常,数据集(或语料库)中一个单词的重要性与它在文档中出现的次数成正比,而与它在语料库中出现的次数为反比。

最常用的权重是tf-idf(term frequency-inverse document frequency,词频-逆向文档频率),单词w在文档d中的词频是:

\mathrm{tf}_{\mathrm{w}, \mathrm{d}}=\frac{\mathrm{n}_{\mathrm{w}}}{\sum_{\mathrm{j}} \mathrm{n}_{\mathrm{j}}}

nw是单词w在文档d中出现的次数。为了归一化,将nw除以整个文档中单词的总数。逆向文档频率为:

\operatorname{idf}_{w, d}=\log \frac{|(D)|}{|\{d: w \in d\}|}

|D|是语料库D中文档的数目,分母是语料库中包含单词w的文档数d。二者相乘可以得到矢量v(直方图向量)对应元素tf-idf权重。

7.2 视觉单词

为了将文本发掘技术应用到图像中,我们首先需要建立视觉等效单词;这通常可以采用2.2节中介绍的SIFT局部描述子做到。它的思想是将描述子空间量化成一些典型实例,并将图像中的每个描述子指派到其中的某个实例中。这些典型实例可以通过分析训练图像集确定,并被视为视觉单词。所有这些视觉单词构成的集合称为视觉词汇,有时也称为视觉码本。对于给定的问题、图像类型,或在通常情况下仅需呈现视觉内容,可以创建特定的词汇。

从一个(很大的训练图像)集提取特征描述子,利用一些聚类算法可以构建出视觉单词。聚类算法中最常用的是K-means,这里也将采用K-means。视觉单词并不高端。只是在给定特征描述子空间中的一组向量集,在采用K-means进行聚类时得到的视觉单词是聚类质心。用视觉单词直方图来表示图像,则该模型便称为BOW模型。

我们首先介绍一下示例数据集,并利用它来说明BOW概念。文件first1000.zip包含了有肯塔基大学物体识别数据集的前1000幅图像。书中给出了数据集下载链接:http://www.vis.uky.edu/~stewe/ukbench 但该网站已经停止服务。

创建词汇

创建名为 vocabulary.py 的文件,将下面代码添加进去。该代码创建一个词汇类,以及在训练图像数据集上训练出一个词汇的方法:

from numpy import *
from scipy.cluster.vq import *

from PCV.localdescriptors import sift


class Vocabulary(object):
    
    def __init__(self,name):
        self.name = name
        self.voc = []
        self.idf = []
        self.trainingdata = []
        self.nbr_words = 0
    
    def train(self,featurefiles,k=100,subsampling=10):
        """ 用含有k个单词的 K-means 列出在 featurefiles 中的特征文件训练出一个词汇。对训练数据下采样可以加快训练速度 """
        
        nbr_images = len(featurefiles)
        # 从文件中读取特征 
        descr = []
        descr.append(sift.read_features_from_file(featurefiles[0])[1])
        # 将所有的特征并在一起,以便后面进行 K-means 聚类 
        descriptors = descr[0] 
        for i in arange(1,nbr_images):
            descr.append(sift.read_features_from_file(featurefiles[i])[1])
            descriptors = vstack((descriptors,descr[i]))
            
        #K-means: 最后一个参数决定运行次数 
        self.voc,distortion = kmeans(descriptors[::subsampling,:],k,1)
        self.nbr_words = self.voc.shape[0]
        
        # 遍历所有的训练图像,并投影到词汇上 
        imwords = zeros((nbr_images,self.nbr_words))
        for i in range( nbr_images ):
            imwords[i] = self.project(descr[i])
        
        nbr_occurences = sum( (imwords > 0)*1 ,axis=0)
        
        self.idf = log( (1.0*nbr_images) / (1.0*nbr_occurences+1) )
        self.trainingdata = featurefiles
    
    def project(self,descriptors):
        """ 将描述子投影到词汇上,以创建单词直方图  """
        
        # 图像单词直方图
        imhist = zeros((self.nbr_words))
        words,distance = vq(descriptors,self.voc)
        for w in words:
            imhist[w] += 1
        
        return imhist

Vocabulary 类包含了一个由单词聚类中心 VOC 与每个单词对应的逆向文档频率构成的向量,为了在某些图像集上训练词汇,train() 方法获取包含有 .sift 描后缀的述子文件列表和词汇单词数k。在 K-means 聚类阶段可以对训练数据下采样,因为如果使用过多特征,会耗费很长时间。

现在在你计算机的某个文件夹中,保存了图像及提取出来的 sift 特征文件,下面的代码会创建一个长为 k ≈ 1000 的词汇表。这里,再次假设 imlist 是一个包含了图像文件名的列表:

import pickle
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift


imlist = get_imlist('path3/')
nbr_images = len(imlist)
# 获取特征列表
featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]
#提取文件夹下图像的sift特征
for i in range(nbr_images):
    sift.process_image(imlist[i], featlist[i])
#生成词汇
voc = vocabulary.Vocabulary('ukbenchtest')
# 15个样本 运行10次
voc.train(featlist, 15, 10)

# 保存词汇
with open('path3/vocabulary.pkl', 'wb') as f:
    pickle.dump(voc, f)

print("vocabulary is:", voc.name, voc.nbr_words)

代码最后部分用 pickle 模块保存了整个词汇对象以便后面使用。

7.3 图像索引

在开始搜索之前,我们需要建立图像数据库和图像的视觉单词表示。

7.3.1 建立数据库

在索引图像前,我们需要建立一个数据库。这里,对图像进行索引就是从这些图像中提取描述子,利用词汇将描述子转换成视觉单词,并保存视觉单词及对应图像的单词直方图。从而可以利用图像对数据库进行查询,并返回相似的图像作为搜索结果。

使用SQLite 作为数据库。SQLite 将所有信息都保存到一个文件,是一个易于安装和使用的数据库。不涉及数据库和服务器的配置,很容易上手。SQLite 对应的Python 版本是pysqlite,可以从 http://code.google.com/p/pysqlite/ 获取。如果你的Python版本较高(本人为3.7),则pysqlite已经被包括在标准库内。

在开始之前,首先需要创建表、索引和索引器 Indexer 类,以便将图像数据写入数据库。首先,创建一个名为 imagesearch.py 的文件,

from numpy import *
import pickle
import sqlite3
from functools import cmp_to_key
import operator

class Indexer(object):
    
    def __init__(self,db,voc):
        """ 初始化数据库的名称及词汇对象  """
            
        self.con = sqlite3.connect(db)
        self.voc = voc
    
    def __del__(self):
        self.con.close()
    
    def db_commit(self):
        self.con.commit()
    
    def create_tables(self): 
        """ Create the database tables. """
        
        self.con.execute('create table imlist(filename)')
        self.con.execute('create table imwords(imid,wordid,vocname)')
        self.con.execute('create table imhistograms(imid,histogram,vocname)')        
        self.con.execute('create index im_idx on imlist(filename)')
        self.con.execute('create index wordid_idx on imwords(wordid)')
        self.con.execute('create index imid_idx on imwords(imid)')
        self.con.execute('create index imidhist_idx on imhistograms(imid)')
        self.db_commit()

用pickle 模块将这些数组编码成字符串以及将字符串进行解码; SQLite 可以从 pysqlite2 模块中导入。 Indexer 类连接数据 库,并且一旦创建(调用 init() 方法)后就可以保存词汇对象。del() 方法 可以确保关闭数据库连接,db_commit() 可以将更改写入数据库文件。

在这里插入图片描述

表单 imlist 包含所有要索引的图像文件名;imwords 包含了一个那些单词的单词索引、用到了哪个词汇、以及单词出现在哪些图像中;最后,imhistograms 包含了全部每幅图像的单词直方图。根据矢量空间模型,我们需要这些以便进行图像比较。

7.3.2 添加图像

有了数据库表单,便可以在索引中添加图像。为了实现该功能,需要在 Indexer 类中添加 add_to_index() 方法。将下面的方法添加到 imagesearch.py 中:

def add_to_index(self,imname,descr):
        """ 获取一幅带有特征描述子的图像,投影到词汇上并添加进数据库  """
            
        if self.is_indexed(imname): return
        print 'indexing', imname
        
        # 获取图像id
        imid = self.get_id(imname)
        
        # 获取单词
        imwords = self.voc.project(descr)
        nbr_words = imwords.shape[0]
        
        #将每个单词与图像链接起来 
        for i in range(nbr_words):
            word = imwords[i]
        #  wordid 就是单词本身的数字 
            self.con.execute("insert into imwords(imid,wordid,vocname) values (?,?,?)", (imid,word,self.voc.name))
            
        # 存储图像的单词直方图
        # 用 pickle 模块将 NumPy 数组编码成字符串
        self.con.execute("insert into imhistograms(imid,histogram,vocname) values (?,?,?)", (imid,pickle.dumps(imwords),self.voc.name))
    

该方法获取图像文件名与 Numpy 数组,该数组包含的是在图像找到的描述子。这些描述子投影到词汇上,并插入到 imwords(逐字)和 imhistograms 表单中。使用两个辅助函数:is_indxed() 用来检查图像是否已经被索引,get_id() 则对一幅图像文件名给定 id 号。将下面的代码添加进 imagesearch.py:

def is_indexed(self,imname):
        """ 如果图像名字(imname)被索引到,就返回 True"""
        
        im = self.con.execute("select rowid from imlist where filename='%s'" % imname).fetchone()
        return im != None
 def get_id(self,imname):
        """ 获取图像 id,如果不存在,就进行添加 ""
        
        cur = self.con.execute(
        "select rowid from imlist where filename='%s'" % imname)
        res=cur.fetchone()
        if res==None:
            cur = self.con.execute(
            "insert into imlist(filename) values ('%s')" % imname)
            return cur.lastrowid
        else:
            return res[0] 

 下面使用代码遍历样本图像写入数据库,加入索引:

import pickle
import sqlite3
import imagesearch
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift

# 获取图像列表
imlist = get_imlist('path3/')
nbr_images = len(imlist)
# 获取特征列表
featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]

# 提取文件夹下图像的sift特征
for i in range(nbr_images):
    sift.process_image(imlist[i], featlist[i])

# 生成词汇
voc = vocabulary.Vocabulary('ukbenchtest')
voc.train(featlist, 15, 10)
# 保存词汇
with open('path3/vocabulary.pkl', 'wb') as f:
    pickle.dump(voc, f)
print('vocabulary is:', voc.name, voc.nbr_words)

with open('path3/vocabulary.pkl', 'rb') as f:
    voc = pickle.load(f)

# 创建索引器
indx = imagesearch.Indexer('path3/test.db',voc)
indx.create_tables()

# 遍历所有的图像,并将它们的特征投影到词汇上
for i in range(nbr_images)[:15]:
    locs, descr = sift.read_features_from_file(featlist[i])
    indx.add_to_index(imlist[i], descr)

# 提交到数据库
indx.db_commit()
con = sqlite3.connect('path3/test.db')  # 连接到数据库
print(con.execute('select count (filename) from imlist').fetchone())  # 数据库操作
print(con.execute('select * from imlist').fetchone())

最后一行用 fetchall() 来代替 fetchone(),会得到一个包含所有文件名的长列表:

7.4 在数据库中搜索图像

建立好图像的索引,我们就可以在数据库中搜索相似的图像了。这里,我们用BoW (Bag-of-Word,词袋模型)来表示整个图像。为实现搜索,我们在 imagesearch.py 中添加 Searcher 类:

class Searcher(object):
    
    def __init__(self,db,voc):
        """ 初始化数据库的名称. """
        self.con = sqlite.connect(db)
        self.voc = voc
    
    def __del__(self):
        self.con.close()

一个新的 Searcher 对象连接到数据库,一旦删除便关闭连接,这与之前的 Indexer 类中的处理过程相同。如果图像数据库很大,逐一比较整个数据库中的所有直方图往往是不可行的。我们需要找到一个大小合理的候选集(这里的“合理”是通过搜索响应时间、所需内存等确定的),单词索引的作用便在于此:我们可以利用单词索引获得候选集,然后只需在候选集上进行逐一比较。

7.4.1 利用索引获取候选图像

可以利用建立起来的索引找到包含特定单词的所有图像,这不过是对数据库做 一次简单的查询。在 Searcher 类中加入 candidates_from_word() 方法:

    def candidates_from_word(self, imword):
        """  获取包含 imword 的图像列. """

        im_ids = self.con.execute(
            "select distinct imid from imwords where wordid=%d" % imword).fetchall()
        return [i[0] for i in im_ids]

上面会给出包含特定单词的所有图像 id 号。为了获得包含多个单词的候选图像,例 如一个单词直方图中的全部非零元素,我们在每个单词上进行遍历,得到包含该单 词的图像,并合并这些列表 。

这里,我们仍然需要在合并了的列表中对每一个图像 id 出现的次数进行跟踪,因为这可以显示有多少单词与单词直方图中的单词匹配。 该过程可以通过下面的 candidates_from_histogram 方法完成:

    def candidates_from_histogram(self, imwords):
        """ 获取具有相似单词的图像列表 """

        # 获取单词 id
        words = imwords.nonzero()[0]

        # 寻找候选图像
        candidates = []
        for word in words:
            c = self.candidates_from_word(word)
            candidates += c

        # 获取所有唯一的单词,并按出现次数反向排序 
        tmp = [(w, candidates.count(w)) for w in set(candidates)]
        tmp.sort(key=cmp_to_key(lambda x, y: operator.gt(x[1], y[1])))
        tmp.reverse()

        # 返回排序后的列表,最匹配的排在最前面
        return [w[0] for w in tmp]

该方法从图像单词直方图的非零项创建单词 id 列表,检索每个单词获得候选集并将其合并到candidates 列表中,然后创建一个元组列表每个元组由单词 id 和次数 count 构成,其中次数 count 是候选列表中每个单词出现的次数。同时,以元组中的第二个元素为准,用 sort() 方法和一个自定义的比较函数对列表进行排序(考虑到后面的效率)。该自定义比较函数进行用 lambda 函数内联声明,对于单行函数声明,使用 lambda 函数非常方便。最后结果返回一个包含图像 id 的列表,排在列表最前面的是最好的匹配图像。

import pickle
import sqlite3
import imagesearch
from PCV.imagesearch import vocabulary
from PCV.tools.imtools import get_imlist
from PCV.localdescriptors import sift

# 获取图像列表
imlist = get_imlist('path3/')
nbr_images = len(imlist)
# 获取特征列表
featlist = [imlist[i][:-3] + 'sift' for i in range(nbr_images)]

# 提取文件夹下图像的sift特征
for i in range(nbr_images):
    sift.process_image(imlist[i], featlist[i])

# 生成词汇
voc = vocabulary.Vocabulary('ukbenchtest')
voc.train(featlist, 15, 10)
# 保存词汇
with open('path3/vocabulary.pkl', 'wb') as f:
    pickle.dump(voc, f)
print('vocabulary is:', voc.name, voc.nbr_words)

with open('path3/vocabulary.pkl', 'rb') as f:
    voc = pickle.load(f)

# 创建索引器
indx = imagesearch.Indexer('path3/test.db',voc)
indx.create_tables()

# 遍历所有的图像,并将它们的特征投影到词汇上
for i in range(nbr_images)[:15]:
    locs, descr = sift.read_features_from_file(featlist[i])
    indx.add_to_index(imlist[i], descr)

# 提交到数据库
indx.db_commit()
con = sqlite3.connect('path3/test.db')  # 连接到数据库
src = imagesearch.Searcher('path3/test.db', voc)
locs,descr = sift.read_features_from_file(featlist[0])
iw = voc.project(descr)
print ('ask using a histogram...')
print (src.candidates_from_histogram(iw)[:3])

打印了索引中找出的前3个图像id:

7.4.2 用一幅图像进行查询

利用一幅图像进行查询时,没有必要进行完全的搜索。为了比较单词直方图,Searcher 类需要从数据库读入图像的单词直方图。将下面的方法添加到 Searcher 类中:

 def get_imhistogram(self, imname):
        """ 返回一幅图像的单词直方图 . """

        im_id = self.con.execute(
            "select rowid from imlist where filename='%s'" % imname).fetchone()
        s = self.con.execute(
            "select histogram from imhistograms where rowid='%d'" % im_id).fetchone()

        # 用 pickle 模块从字符串解码 Numpy 数组
        return pickle.loads(str(s[0]))

这里,为了在字符串和 NumPy 数组间进行转换,我们再次用到了 pickle 模块,这次使用的是 loads()。

现在,我们可以全部合并到查询方法中:

    def query(self, imname):
        """ 查找所有与 imname 匹配的图像列表 . """

        h = self.get_imhistogram(imname)
        candidates = self.candidates_from_histogram(h)

        matchscores = []
        for imid in candidates:
            # 获取名字 
            cand_name = self.con.execute(
                "select filename from imlist where rowid=%d" % imid).fetchone()
            cand_h = self.get_imhistogram(cand_name)
            cand_dist = sqrt(sum(self.voc.idf * (h - cand_h) ** 2))
            matchscores.append((cand_dist, imid))

        # 返回排序后的距离及对应数据库 ids 列表 
        matchscores.sort()
        return matchscores

该 query() 方法获取图像的文件名,检索其单词直方图及候选图像列表(如果你的数据集很大,候选集的大小应该限制在某个最大值)。对于每个候选图像,用标准的欧式距离比较它和查询图像间的直方图,并返回一个经排序的包含距离及图像 id的元组列表。

7.4.3 确定对比基准并绘制结果

为评价搜索结果的好坏,我们可以计算前4个位置中搜索到相似图像数。这里给出了计算分数的函数,将它添加到 imagesearch.py 中:

def compute_ukbench_score(src,imlist):
    """ 对查询返回的前 4 个结果计算平均相似图像数,并返回结果 """
    nbr_images = len(imlist)
    pos = zeros((nbr_images,4))
    # 获取每幅查询图像的前 4 个结果
    for i in range(nbr_images):
        pos[i] = [w[1]-1 for w in src.query(imlist[i])[:4]]
     # 计算分数,并返回平均分数
        score = array([ (pos[i]//4)==(i//4) for i in range(nbr_images)])*1.0
        return sum(score) / (nbr_images)

该函数获得搜索的前4个结果,将 query() 返回的索引减去1,因为数据库索引是从1开始的,而图像列表的索引是从0开始的。然后,利用每4幅图像为一组时相似图像文件名是连续的这一事实,我们用证书相除计算得到最终的分数。分数为4时结果最理想;没有一个是准确地,分数为0;仅检索到相同图像时,分数为1;找到相同的图像并且其他三个中的两个相同时,分数为3。

下面是显示实际搜索结果的函数,添加到 imagesearch.py 中:

def plot_results(src,res):
    """ 显示在列表 res 中的图像 """ 
    figure()
    nbr_results = len(res)
    for i in range(nbr_results):
        imname = src.get_filename(res[i])
        subplot(1,nbr_results,i+1)
        imshow(array(Image.open(imname)))
        axis('off')
    show()
    
def get_filename(self,imid):
    """ 返回图像 id 对应的文件名 """
    s = self.con.execute("select filename from imlist where rowid='%d'" % imid).fetchone()
    return s[0]

7.5 使用几何特性对结果排序

BoW 模型的一个主要缺点是在用视觉单词表示图像时不包含图像特征的位置信息,这是为获取速度和可伸缩性而付出的代价。

利用一些考虑到特征几何关系的准则重排搜索到的靠前结果,可以提高准确率。最常用的方法是在查询图像与靠前图像的特征位置间拟合单应性。

为了提高效率,可以将特征位置存储在数据库中,并由特征的单词 id 决定它们之间的关联(要注意的是,只有在词汇足够大,使单词 id 包含很多准确匹配时,它才起作用)。然而,这需要大幅重写我们上面的数据库和代码,并复杂化表示形式。为了进行说明,我们仅重载靠前图像的特征,并对它们进行匹配。

下面是一个载入所有模型文件并用单应性对靠前的图像进行重排的完整例子:

# -*- coding: utf-8 -*-
import pickle
from PCV.localdescriptors import sift
import imagesearch
from PCV.geometry import homography
from PCV.tools.imtools import get_imlist

# load image list and vocabulary
#载入图像列表
imlist = get_imlist('path3/')
nbr_images = len(imlist)
#载入特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]

#载入词汇
with open('path3/vocabulary.pkl', 'rb') as f:
    voc = pickle.load(f)

src = imagesearch.Searcher('path3/test.db',voc)

# index of query image and number of results to return
#查询图像索引和查询返回的图像数
q_ind = 0
nbr_results = 20

# regular query
# 常规查询(按欧式距离对结果排序)
res_reg = [w[1] for w in src.query(imlist[q_ind])[:nbr_results]]
print('top matches (regular):', res_reg)

# load image features for query image
#载入查询图像特征
q_locs,q_descr = sift.read_features_from_file(featlist[q_ind])
fp = homography.make_homog(q_locs[:,:2].T)

# RANSAC model for homography fitting
#用单应性进行拟合建立RANSAC模型
model = homography.RansacModel()
rank = {}

# load image features for result
#载入候选图像的特征
for ndx in res_reg[1:]:
    locs,descr = sift.read_features_from_file(featlist[ndx])  # because 'ndx' is a rowid of the DB that starts at 1
    # get matches
    matches = sift.match(q_descr,descr)
    ind = matches.nonzero()[0]
    ind2 = matches[ind]
    tp = homography.make_homog(locs[:,:2].T)
    # compute homography, count inliers. if not enough matches return empty list
    try:
        H,inliers = homography.H_from_ransac(fp[:,ind],tp[:,ind2],model,match_theshold=4)
    except:
        inliers = []
    # store inlier count
    rank[ndx] = len(inliers)

# sort dictionary to get the most inliers first
sorted_rank = sorted(rank.items(), key=lambda t: t[1], reverse=True)
res_geom = [res_reg[0]]+[s[0] for s in sorted_rank]
print('top matches (homography):', res_geom)

# 显示查询结果
imagesearch.plot_results(src,res_reg[:8]) #常规查询
imagesearch.plot_results(src,res_geom[:8]) #重排后的结果

运行后产生了一些报错了,查询资料后分析可能是由于python3.7版本字符串与字节部分相关代码与书上使用python版本不同导致的。由于本人能力有限,多次尝试修改后无果,暂时搁置该代码。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值