【python计算机视觉编程——7.图像搜索】

news2024/11/23 14:56:53

python计算机视觉编程——7.图像搜索

  • 7.图像搜索
    • 7.1 基于内容的图像检索(CBIR)
    • 从文本挖掘中获取灵感——矢量空间模型(BOW表示模型)
    • 7.2 视觉单词
        • **思想**
        • **特征提取**:
    • 创建词汇
    • 7.3 图像索引
      • 7.3.1 建立数据库
      • 7.3.2 添加图像
    • 7.4 在数据库中搜索图像
      • 7.4.1 利用索引获取获选图像
      • 7.4.2 用一幅图像进行查询
      • 7.4.3 确定对比基准并绘制结果
    • 7.5 使用几何特性对结果排序

7.图像搜索

利用文本挖掘技术对基于图像视觉内容进行图像搜索

7.1 基于内容的图像检索(CBIR)

CBIR用于检索在视觉上具有相似性的图像(颜色相似,纹理相似、图像中的物体或场景相似)。

将查询图像与数据库中所有的图像进行 完全比较(特征匹配)往往是不可行的,庞大的数据库回导致耗时过多。

  • 从文本挖掘中获取灵感——矢量空间模型(BOW表示模型)

矢量空间模型是一个用于表示和搜索文本文档的模型。这些矢量是由文本词频直方图构成的。换句话说,矢量包含了每个单词出现的次数。

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

  • 单词w在文档d中的词频
    t f w , d = 单词 w 在文档 d 中出现的次数 文档 d 中单词的总数 = n w ∑ j n j {\rm tf}_{w,d}=\frac{单词w在文档d中出现的次数}{文档d中单词的总数}=\frac{n_w}{\sum_j n_j} tfw,d=文档d中单词的总数单词w在文档d中出现的次数=jnjnw

  • 逆向文档频率
    i d f w , d = log ⁡ 在语料库 D 中文档的数目 语料库中包含单词 w 的文档数的 = log ⁡ ∣ ( D ) ∣ { d : w ∈ d } {\rm idf}_{w,d}=\log\frac{在语料库D中文档的数目}{语料库中包含单词w的文档数的}=\log\frac{|(D)|}{\{d:w\in d\}} idfw,d=log语料库中包含单词w的文档数的在语料库D中文档的数目=log{d:wd}(D)

7.2 视觉单词

首先需要建立视觉等效单词,通常采用sift局部描述子做到

  • 思想
    1. 特征提取
      • 从图像中提取局部特征(SIFT)。
    2. 特征聚类

      • 使用聚类算法(如 K-means)将这些局部特征描述符分组到不同的视觉单词中。每个聚类中心代表一个视觉单词。
    3. 特征表示

      • 将每个图像中的局部特征映射到最近的视觉单词上,形成图像的视觉单词直方图
  • 创建词汇

使用sift特征描述子创建视觉单词词汇

from PIL import Image
import os

需用到如下两个函数

def process_image(imagename, resultname, params="--edge-thresh 10 --peak-thresh 5"):
    if imagename[-3:] != 'pgm':
        im = Image.open(imagename).convert('L')
        im.save('tmp.pgm')
        imagename = 'tmp.pgm'     
    cmmd = str(".\sift.exe " + imagename + " --output=" + resultname + " " + params)
    os.system(cmmd)
    print('processed', imagename, 'to', resultname)
# 可以返回目录中所有jpg图像的列表
def get_imlist(path):
    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]
imlist=get_imlist(r'.\first1000')
nbr_images=len(imlist)
featlist=[imlist[i][:-3]+'sift' for i in range(nbr_images)]

for i in range(nbr_images):
    process_image(imlist[i],featlist[i])
  • 创建类名为Vocabulary,内含有两个函数train和project

  • train函数:从图像特征文件中读取特征描述符,使用 K-means 聚类算法生成视觉单词,并计算每个图像的视觉单词直方图。随后,它计算每个视觉单词的 IDF,并保存训练数据的文件路径。

    • 步骤
      1. 读取特征
        • 从第一个特征文件中读取特征,并将其存储在 descriptors 中。
        • 依次读取其他特征文件,将它们的特征垂直堆叠到 descriptors 中。
      2. 训练视觉单词
        • 使用 kmeans 聚类算法对特征进行聚类,得到视觉单词。
        • descriptors[::subsampling, :]表示对特征进行子采样,以提高计算效率。
        • self.voc存储视觉单词,distortion存储聚类的失真度。
      3. 计算视觉单词直方图
        • 对每个图像计算视觉单词直方图。
        • 统计每个视觉单词在所有图像中的出现次数,计算 IDF 值。
      4. 存储训练数据
        • 保存特征文件路径到 self.trainingdata
  • project函数:计算每张图像的视觉单词直方图

    • 步骤

      • 将特征描述符映射到视觉单词

        • 使用 vq(向量量化)方法将特征描述符映射到最近的视觉单词。
        • words 包含每个特征描述符对应的视觉单词索引。
      • 创建视觉单词直方图

        • 遍历 words,统计每个视觉单词的出现次数。
        • 返回图像的视觉单词直方图 imhist。
class Vocabulary(object):
    def __init__(self,name):
        self.name=name  # 词汇表的名称
        self.voc=[]     # 存储视觉单词(词汇)的列表
        self.idf=[]     # 逆文档频率(IDF)值列表,用于加权视觉单词
        self.trainingdata=[]  # 训练数据的文件列表
        self.nbr_words=0   # 视觉单词的数量
    def train(self,featurefiles,k=100,subsampling=10):
    """
    featurefiles:存储特征文件路径的列表,每个文件包含图像的特征
    k:视觉单词的数量,默认为 100
    subsampling:子采样比例,默认为10
    """
        nbr_images=len(featurefiles)
        descr=[]
        descr.append(read_features_from_file(featurefiles[0])[1]) # 从第一个特征文件中读取特征,并将其存储在descriptors中
        descriptors=descr[0]
        for i in range(1,nbr_images): # 依次读取其他特征文件,将它们的特征垂直堆叠到 descriptors 中
            descr.append(read_features_from_file(featurefiles[0])[1])
            descriptors=np.vstack((descriptors,descr[i]))
        
        # 使用kmeans聚类算法对特征进行聚类,得到视觉单词
        # self.voc存储视觉单词,distortion存储聚类的失真度
        self.voc,distortion=kmeans(descriptors[::subsampling,:],k,1) #descriptors[::subsampling,:]:表示对特征进行子采样,以提高计算效率
        self.nbr_words=self.voc.shape[0]
        
        imwords=np.zeros((nbr_images,self.nbr_words)) #每一行表示一张图像的视觉单词直方图
        for i in range(nbr_images):
            imwords[i]=self.project(descr[i]) # 计算每张图像的视觉单词直方图,并将其存储在imwords的对应行中
        nbr_occurences=np.sum((imwords>0)*1,axis=0)  # 计算每个视觉单词在多少张图像中出现过
        self.idf=np.log((1.0*nbr_images)/(1.0*nbr_occurences+1)) #计算每个视觉单词的逆文档频率(IDF)。IDF是用来加权视觉单词的,避免频繁出现的视觉单词占据主导地位。+1 是为了避免除零错误
        self.trainingdata=featurefiles  # 保存训练数据文件路径列表,以便后续使用
    def project(self,descriptors):
        imhist=np.zeros((self.nbr_words))  # 使用 vq(向量量化)方法将特征描述符映射到最近的视觉单词
        words,distance=vq(descriptors,self.voc)  # words 包含每个特征描述符对应的视觉单词索引
        for w in words:
            imhist[w]+=1
        return imhist
voc=Vocabulary('ukbenchtest')
voc.train(featlist,1000,10) # 使用 featlist 中的特征文件训练 voc 对象,设定视觉单词数量为 1000,子采样比例为 10

with open('vocabulary.pkl','wb') as f:
    pickle.dump(voc,f)
print('vocabulary is:',voc.name,voc.nbr_words)

在这里插入图片描述

7.3 图像索引

7.3.1 建立数据库

from numpy import *
import pickle
import sqlite3 as sqlite
class Indexer(object):
    
    def __init__(self,db,voc):
        """ Initialize with the name of the database 
            and a vocabulary object. """
            
        self.con = sqlite.connect(db) #使用SQLite的connect函数连接到指定的数据库文件db,并将连接对象存储在 self.con 中
        self.voc = voc  # 将传入的 voc(词汇对象)存储在 self.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)') #创建一个名为 imlist 的表,该表包含一个字段 filename,用于存储图像文件名。
        self.con.execute('create table imwords(imid,wordid,vocname)') # 创建一个名为 imwords 的表,包含字段 imid(图像ID)、wordid(视觉单词ID)和 vocname(词汇名称)。此表用于存储每张图像中视觉单词的出现情况。
        self.con.execute('create table imhistograms(imid,histogram,vocname)') #创建一个名为 imhistograms 的表,包含字段 imid(图像ID)、histogram(图像的视觉单词直方图)和 vocname(词汇名称)。此表用于存储每张图像的视觉单词直方图。        
        self.con.execute('create index im_idx on imlist(filename)') #为 imlist 表中的 filename 字段创建一个索引,以加快对图像文件名的查询速度。
        self.con.execute('create index wordid_idx on imwords(wordid)') #为 imwords 表中的 wordid 字段创建一个索引,以加快对视觉单词ID的查询速度。
        self.con.execute('create index imid_idx on imwords(imid)') #为 imwords 表中的 imid 字段创建一个索引,以加快对图像ID的查询速度。
        self.con.execute('create index imidhist_idx on imhistograms(imid)') #为 imhistograms 表中的 imid 字段创建一个索引,以加快对图像ID的查询速度。

7.3.2 添加图像

因为需要在索引中添加图像,所以还需要在类中添加以下几个方法

    def add_to_index(self,imname,descr):
        """将图像及其特征描述符添加到数据库中"""
            
        if self.is_indexed(imname): return # 如果图像已经被索引(即已经存在于数据库中),则退出方法,避免重复索引。
        print('indexing', imname)
        
        imid = self.get_id(imname)
        
        imwords = self.voc.project(descr) #使用词汇对象 self.voc 的 project 方法,将特征描述符 descr 转换为视觉单词的直方图 imwords
        nbr_words = imwords.shape[0] #获取视觉单词直方图中视觉单词的数量 nbr_words
        
        
        for i in range(nbr_words):
            word = imwords[i]
            # wordid is the word number itself
            self.con.execute("insert into imwords(imid,wordid,vocname) values (?,?,?)", (imid,word,self.voc.name))
            
        # 将图像 ID (imid)、视觉单词直方图 imwords(使用 pickle.dumps 将 NumPy 数组编码为字符串)和词汇名称 (self.voc.name) 插入到 imhistograms 表中,记录图像的视觉单词直方图
        self.con.execute("insert into imhistograms(imid,histogram,vocname) values (?,?,?)", (imid,pickle.dumps(imwords),self.voc.name))
    def is_indexed(self,imname):
        """检查图像是否已被索引"""
        
        #执行 SQL 查询,检查 imlist 表中是否存在与 imname 匹配的 filename
        #fetchone() 方法返回查询结果的第一行。如果存在匹配的记录,则返回该记录;否则,返回 None
        im = self.con.execute("select rowid from imlist where filename='%s'" % imname).fetchone()
        return im != None
    def get_id(self,imname):
        """获取图像的唯一 ID。如果图像不在数据库中,则将其添加进去并返回新 ID"""
        
        #执行 SQL 查询,从 imlist 表中获取与 imname 匹配的图像 ID(rowid)
        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] 

接着我们遍历整个数据库中的样本图像,并将其加入我们的索引

nbr_images=len(imlist)
with open('vocabulary.pkl','rb') as f:
    voc=pickle.load(f)

indx=Indexer('test.db',voc)
indx.create_tables()

for i in range(nbr_images)[:1000]:
    locs,descr=read_features_from_file(featlist[i])
    indx.add_to_index(imlist[i],descr)

indx.db_commit()
con=sqlite.connect('test.db')
print(con.execute('select count (filename) from imlist').fetchone())
print(con.execute('select * from imlist').fetchone())

在这里插入图片描述

7.4 在数据库中搜索图像

为实现搜索,我们创建一个Searcher类

class Searcher(object):
    
    def __init__(self,db,voc):
        """ Initialize with the name of the database. """
        self.con = sqlite.connect(db)
        self.voc = voc
    
    def __del__(self):
        self.con.close()

7.4.1 利用索引获取获选图像

需要利用建立起来的索引找到包含特定单词的所有图像,因此添加candidates_from_word函数到Searcher类中

    def candidates_from_word(self,imword):
        """ Get list of images containing 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出现的次数进行跟踪

    def candidates_from_histogram(self,imwords):
        """ Get list of images with similar words. """
        
        # get the word ids
        words = imwords.nonzero()[0]
        
        # find candidates
        candidates = []
        for word in words:
            c = self.candidates_from_word(word)
            candidates+=c
        
        # take all unique words and reverse sort on occurrence 
        tmp = [(w,candidates.count(w)) for w in set(candidates)]
        #         tmp.sort(cmp=lambda x,y:cmp(x[1],y[1]))
        tmp.sort(key=lambda x: x[1], reverse=True)
        tmp.reverse()
        
        # return sorted list, best matches first    
        return [w[0] for w in tmp] 
src=Searcher('test.db',voc)
locs,descr=read_features_from_file(featlist[0])
iw=voc.project(descr)

print('ask using a histogram...')
print(src.candidates_from_histogram(iw)[:10])

在这里插入图片描述

7.4.2 用一幅图像进行查询

添加get_imhistogram函数到Searcher类中

    def get_imhistogram(self,imname):
        """ Return the word histogram for an image. """
        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()
        # use pickle to decode NumPy arrays from string
        #         return pickle.loads(str(s[0]))
#         print(type(s[0]))
        return pickle.loads(s[0])

添加query函数到Searcher类中

    def query(self,imname):
        """ Find a list of matching images for imname. """
        
        h = self.get_imhistogram(imname)
        candidates = self.candidates_from_histogram(h)
        
        matchscores = []
        for imid in candidates:
            # get the name
            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) )
        
        # return a sorted list of distances and database ids
        matchscores.sort()
        return matchscores
src=Searcher('test.db',voc)
print('try a query...')
print(src.query(imlist[0])[:10])

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

def compute_ukbench_score(src,imlist):
    """ Returns the average number of correct
        images on the top four results of queries. """
        
    nbr_images = len(imlist)
    pos = zeros((nbr_images,4))
    # get first four results for each image
    for i in range(nbr_images):
        pos[i] = [w[1]-1 for w in src.query(imlist[i])[:4]]
    
    # compute score and return average
    score = array([ (pos[i]//4)==(i//4) for i in range(nbr_images)])*1.0
    return sum(score) / (nbr_images)
compute_ukbench_score(src,imlist) 

定义plot_results函数

def plot_results(src,res):
    """ Show images in result list '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()
src=Searcher('test.db',voc)
nbr_results=6
res=[w[1] for w in src.query(imlist[0])[:nbr_results]]
plot_results(src,res)

在这里插入图片描述

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

#载入图像列表
imlist=get_imlist(r'.\first1000')
nbr_images = len(imlist)
#载入特征列表
featlist = [imlist[i][:-3]+'sift' for i in range(nbr_images)]
    
    
nbr_images=len(imlist)

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

src=Searcher('test.db',voc)

q_ind=50
nbr_results=20

res_reg=[w[1] for w in src.query(imlist[q_ind])[:nbr_results]]
print('top matches (regular):',res_reg)

q_locs,q_descr=read_features_from_file(featlist[q_ind])
fp=make_homog(q_locs[:,:2].T)

model=RansacModel()

rank={}
for ndx in res_reg[1:]:
    locs,descr=read_features_from_file(featlist[ndx])
    
    matches=match(q_descr,descr)
    ind=matches.nonzero()[0]
    ind2=matches[ind]
    tp=make_homog(locs[:,:2].T)
    
    try:
        H,inliers=H_from_ransac(fp[:,ind],tp[:,ind2],model,match_theshold=4)
    except:
        inliers=[]
    
    rank[ndx]=len(inliers)

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)

plot_results(src,res_reg[:8])
plot_results(src,res_geom[:8])

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2114342.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行分类

深度学习实践:构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行分类 引言 在计算机视觉领域中,CIFAR-10数据集是一个经典的基准数据集,广泛用于图像分类任务。本文将介绍如何使用PyTorch框架构建一个简单的卷积神经…

微信小程序uniappvue3版本-控制tabbar某一个的显示与隐藏

1. 首先在pages.json中配置tabbar信息 2. 在代码根目录下添加 tabBar 代码文件 直接把微信小程序文档里面的四个文件复制到自己项目中就可以了 3. 根据自己的需求更改index.js文件 首先我这里需要判断什么时候隐藏某一个元素,需要引入接口 然后在切换tabbar时&#…

git:认识git和基本操作(1)

目录 一、版本控制器 1.安装git 2.创建git本地仓库 3.配置git 二、git操作(1) 1.工作区、暂存区、版本库 2.添加文件 3.查看.git 4.修改文件 一、版本控制器 所谓的版本控制器,就是能让你了解到每一个文件的修改历史。相应的&#x…

Maven的安装

一、安装 压缩包解压完的目录如下所示(此处为绿色免安装版): (其余三个文件是针对Maven版本,第三方软件等简要介绍) 二、环境变量 前提: jdk最低版本为JAVA7(即jdk17&#xff09…

R语言统计分析——重复测量方差分析

参考资料:R语言实战【第2版】 所谓重复测量方差分析,即受试者被测量不止一次。本例使用数据集市co2数据集:因变量是二氧化碳吸收量(uptake),自变量是植物类型(Type)和七种水平的二氧…

Gitness 基础安装

文章目录 Docker 安装注册账户创建项目导入已有仓库配置 Github Token同步源代码仓库 官方链接 Gitness was the next step in the evolution of Drone, from continuous integration to source code hosting, bringing code management and pipelines closer together. Gitnes…

自动化表格处理的革命:智能文档系统技术解析

在当今数据驱动的商业环境中,表格数据的自动化处理成为了企业提高效率、降低成本的关键。企业智能文档系统在智能表格识别方面展现出卓越的性能,通过精准识别和处理各种通用表格,显著提升了企业文档管理的智能化水平。本文将深入探讨该系统在…

STM32的使用方法一

注:我采用的是STM32F103RC芯片、相应的电路图和STM32CubeIDE软件这是在STM32CubeIDE软件定义芯片后,所给的必要的代码逻辑,加上了注释 #include "main.h"/* Private variables ---------------------------------------------------------*//…

Java数据结构(八)——插入排序、希尔排序

文章目录 插入排序算法介绍代码实现复杂度和稳定性 希尔排序算法介绍代码实现复杂度和稳定性 将这两种排序放在一起的原因是它们都属于 “插入”(式)排序。 还有很多排序思想,这里不放在一篇文章介绍是因为会导致篇幅过长,我们会按分类多次介绍不同的排序…

[数据集][目标检测]智慧农业草莓叶子病虫害检测数据集VOC+YOLO格式4040张9类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):4040 标注数量(xml文件个数):4040 标注数量(txt文件个数):4040 标注…

HTTP协议 HTTPS协议 MQTT协议介绍

目录 一.HTTP协议 1. HTTP 协议介绍 基本介绍: 协议: 注意: 2. HTTP 协议的工作过程 基础术语: 客户端: 主动发起网络请求的一端 服务器: 被动接收网络请求的一端 请求: …

基于MinerU的PDF解析API

基于MinerU的PDF解析API - MinerU的GPU镜像构建 - 基于FastAPI的PDF解析接口支持一键启动,已经打包到镜像中,自带模型权重,支持GPU推理加速,GPU速度相比CPU每页解析要快几十倍不等 主要功能 删除页眉、页脚、脚注、页码等元素&…

实验记录 | 点云处理 | K-NN算法3种实现的性能比较

引言 K近邻(K-Nearest Neighbors, KNN)算法作为一种经典的无监督学习算法,在点云处理中的应用尤为广泛。它通过计算点与点之间的距离来寻找数据点的邻居,从而有效进行点云分类、聚类和特征提取。本菜在复现点云文章过程&#xff…

【OpenCV2.2】图像的算术与位运算(图像的加法运算、图像的减法运算、图像的融合)、OpenCV的位运算(非操作、与运算、或和异或)

1 图像的算术运算 1.1 图像的加法运算 1.2 图像的减法运算 1.3 图像的融合 2 OpenCV的位运算 2.1 非操作 2.2 与运算 2.3 或和异或 1 图像的算术运算 1.1 图像的加法运算 add opencv使用add来执行图像的加法运算 图片就是矩阵, 图片的加法运算就是矩阵的加法运算, 这就要求加…

notepad下载安装教程

一、强大高效的代码编辑器 Notepad 是一款功能强大的代码编辑器,专为程序员和开发人员设计。无论是编写代码、处理文本文件,还是进行快速编辑,Notepad 都能提供卓越的性能和便利的功能,极大提升您的工作效率。 二、安装详细教程…

双指针(5)_单调性_有效三角形的个数

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 双指针(5)_单调性_有效三角形的个数 收录于专栏【经典算法练习】 本专栏旨在分享学习C的一点学习笔记,欢迎大家在评论区交流讨论💌 目录…

c++stack和list 介绍

stack介绍 堆栈是一种容器适配器,专门设计用于在 LIFO 上下文(后进先出)中运行,其中元素仅从容器的一端插入和提取。 堆栈作为容器适配器实现,容器适配器是使用特定容器类的封装对象作为其基础容器 的类,提…

mysql可重复读不能解决幻读吗?

1、可重复读和幻读的概念 1.1、可重复读 可重复读是数据库的四个隔离级别之一,可重复读可以保证在一个事物之内读取到的数据永远是相同的(通过mvcc表快照实现的),哪怕这期间有其它事务对数据做了修改,也不会影响当前事务的查询。 1.2、幻读 网上有不少博客说:幻读是一个事物内…

正规表达式例题

解析:从题意可知,a可以有零个或多个,b有1个或多个 选项A:这里a至少有1个,不符合题意 选项B:a^*bb^*,a是0个或多个,b可以是1个或多个,符合题意 选项C和选项D&#xff0…

Jenkins 通过 Version Number Plugin 自动生成和管理构建的版本号

步骤 1:安装 Version Number Plugin 登录 Jenkins 的管理界面。进入 “Manage Jenkins” -> “Manage Plugins”。在 “Available” 选项卡中搜索 “Version Number Plugin”。选中并安装插件,完成后可能需要重启 Jenkins。 步骤 2:配置…