AutoCV第五课:Python基础

目录

  • Python基础
    • 前言
    • 1.文件IO
      • 1.1 基本知识
      • 1.2 open函数
      • 1.3 write函数
      • 1.4 作业9
      • 1.5 拓展之re.findall函数
      • 1.6 拓展之ElementTree模块
      • 1.7 拓展之XML
    • 2.pickle
    • 3.json
    • 总结

Python基础

前言

手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要学习文件IO的基本概念以及两个序列化模块pickle和json
课程大纲可看下面的思维导图。

AutoCV第五课:Python基础_第1张图片

1.文件IO

1.1 基本知识

在Python中,文件IO主要指读取文件内容和写入文件内容两个操作。IO是Input和Output的缩写,即输入和输出。Python提供了一些内置的函数和模块,可以实现文件的读写操作。常见的文件读写操作函数有:

  • open():打开文件并返回文件对象
  • read():读取文件内容
  • write():写入文件

同时,Python中也有一些常见的文件操作模块,例如:

  • os模块:用于处理文件和目录
  • shutil模块:提供了高层次的文件操作功能
  • pathlib模块:提供了对文件系统路径的面向对象编程接口

使用这些函数和模块,我们可以方便地读取和写入文件,处理文件和目录

1.2 open函数

open()函数是Python中用于打开文件的内置函数,其语法如下:(from chatGPT)

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

其中,

  • file参数是打开的文件名
  • mode参数可选,用于指定文件的打开模式
    • r以只读模式打开文件(默认);w以写入模式打开文件(写模式会覆盖旧内容,如果不想覆盖请加上a标记);x创建一个新文件以写入模式打开
    • a以追加模式打开文件;b以二进制打开文件(处理二进制文件如图片和音频);t以文本模式打开文件(默认,处理文本文件如.txt、.xml文件)
    • rb以二进制模式读取文件;wt以文本模式写入文件
  • buffering参数可选,用于指定缓冲策略
  • encoding参数可选,用于指定文件的编码方式如utf-8
  • errors参数可选,用于指定编码错误的处理方式
  • newline参数可选,用于指定文本模式下的行尾字符
  • closefd参数可选,用于指定是否在文件关闭时同时关闭文件描述符
  • opener参数可选,用于指定一个自定义的文件打开器

当调用open()函数时,会返回一个文件对象。对于文本文件,可以使用类似于读取字符串的方式读取和写入文件内容,例如:

f = open('example.txt', 'r')
lines = f.readlines()
for line in lines:
    print(line)
f.close()

with open('example.txt', 'r') as f:
    content = f.read()

with open("example.txt", "r") as f:
    line = f.readline()
    while line:
        print(line)
        line = f.readline()
    
with open('output.txt', 'w') as f:
    f.write('Hello, world!')

在Python文件IO中,f.read()f.readlines()以及f.readline()都是读取文件内容的方法,不同之处在于返回的结果格式不同。

  • f.read(size=-1)方法读取整个文件或者指定大小的文件内容,返回一个字符串

  • f.readlines()方法读取整个文件内容并返回一个列表,其中每个元素都是文件的一行(包括换行符)

  • f.readline()方法读取文件的一行内容,并将其作为一个字符串返回(包括换行符),可以连续调用来逐行读取整个文件

需要注意的是f.read()f.readlines()方法都会读取文件的全部内容,如果文件过大,可能会导致内存溢出或者卡死程序,因此需要谨慎使用。对于较大的文件,可以使用循环逐行读取或者使用f.readline()方法一次读取一行。

在Python文件IO中,open()函数和with open()语句都可以用于文件的读写操作,但是二者的区别在于with语句在完成操作后会自动关闭文件,而open()函数需要手动调用close()方法来关闭文件,如果忘记关闭文件会导致释放资源不及时,而资源是有限制的,如果过多的打开不释放,会造成资源消耗过度而程序或系统异常。

因此,最好使用with语句来自动管理文件的打开和关闭,以防止文件句柄泄露和资源浪费。with语法结构如下:

with 对象 as 变量:
    代码块

等价于
变量 = 对象.__enter__()
try:
    代码块
except Exception as exception:
    pass
对象.__exit__(exception)

1.3 write函数

write函数是Python中用于将指定内容写入文件的内置函数,其语法如下:

file.write(str)

其中,file代表要写入的文件对象,可以使用open函数打开一个文件后获得,str是要写入的内容

以下是一个简单的示例,用于将一个字符串写入到指定目录的文件中:

import os
os.makedirs("a/b/c", exist_ok=True)

with open("a/b/c/example.txt", 'w') as f:
    f.write("Hello world!")

在这个示例中,我们首先创建了利用os.makedirs创建了一个多级目录,然后以写的方式打开了一个文件,同时使用write函数将字符串Hello world!写入到文件中。如果该文件不存在,则会自动创建一个新文件。

拓展之os.makedirs():该函数用于创建多级目录。如果需要创建的目录的上级目录不存在,它可以自动创建确实的上级目录。

函数定义为:

os.makedirs(name, exist_ok=False)

参数说明:

  • name:需要创建的目录路径,可以是绝对路径或相对路径
  • exist_ok:如果设置为True,在目录已经存在的情况下不会引发异常,否则会抛出FileExistsError异常

示例:

import os

# 创建单个目录
os.mkdir("mydir")

# 创建多级目录
os.makedirs("mydir/subdir1/subdir2", exist_ok=True)

下面的示例中演示了二进制文件的写和读操作:

import struct

with open("./test.bin", 'wb') as f:
    f.write(struct.pack("i", 123))

with open("./test.bin", 'rb') as f:
    print(struct.unpack("i", f.read()))

其中,struct.pack(format, v1, v2, ...)函数可以将数据按照给定的格式(format)进行打包,返回打包后的字节流

  • format是格式化字符串,v1,v2,...表示需要打包的数据
  • format中的每个格式符都对应一个特定的C语言数据类型,例如"i"表示int,"f"表示float,"s"表示字符串
  • pack的返回值是一个bytes类型的对象,表示打包后的二进制数据

struct.unpack(format, string)函数则是将打包后的二进制数据转换为Python数据类型

  • format表示打包格式字符串,string表示打包后的二进制数据
  • unpack的返回值是一个元组,元组中的每个元素都是转换后的Python数据类型,与打包时使用的格式字符串中的数据类型一一对应

1.4 作业9

内容:读取一个xml文件,并解析获得其中的信息,具体需要解析的内容如下:

保存xml文件中size标签下的width、height内容

保存xml中objec标签下的name、xmin、ymin、ymax内容,并将name转换为对应的标签索引

利用保存的内容,将xmin、ymin、xmax、ymax转换为中心点宽高的形式,并保存到txt文件中

首先先来看下xml中的内容:

<annotation>
	<folder>drinkfolder>
	<filename>dr105.jpgfilename>
	<path>C:\Users\Admin\Desktop\drink\dr105.jpgpath>
	<source>
		<database>Unknowndatabase>
	source>
	<size>
		<width>293width>
		<height>220height>
		<depth>3depth>
	size>
	<segmented>0segmented>
	<object>
		<name>drinkname>
		<pose>Unspecifiedpose>
		<truncated>0truncated>
		<difficult>0difficult>
		<bndbox>
			<xmin>14xmin>
			<ymin>53ymin>
			<xmax>77xmax>
			<ymax>130ymax>
		bndbox>
	object>
	<object>
		<name>drinkname>
		<pose>Unspecifiedpose>
		<truncated>1truncated>
		<difficult>0difficult>
		<bndbox>
			<xmin>243xmin>
			<ymin>29ymin>
			<xmax>293xmax>
			<ymax>89ymax>
		bndbox>
	object>
annotation>

解析xml文件并转换保存至txt文件的示例代码如下:

def xml_parse(xml_file):
    '''
    解析xml文件中的width、height、name、xmin、xmax、ymin、ymax
    '''
    labels = {'smoke' : 0, 'drink' : 1}
    size = {}
    boxes = []
    # 读取xml文件
    with open(xml_file, 'r') as f:
        lines = f.readlines()
        box = []
        for line in lines:
            line = line.replace('\t', '') # 去除多余的空格
            line = line.replace('\n', '')

            if line[:7] == '':     # 获取图像宽高
                width = int(line[7:-8])
                size['width'] = width
            if line[:8] == '':      
                height = int(line[8:-9])
                size['height'] = height
            
            if len(box) == 5:
                boxes.append(box)
                box = []
            if line[:6] == '':      # 获取目标属性label、xmin、ymin、xmax、ymax
                name = line[6:-7]
                box.append(labels[name])
            if line[:6] == '':
                xmin = int(line[6:-7])
                box.append(xmin)
            if line[:6] == '':
                xmax = int(line[6:-7])
                box.append(xmax)
            if line[:6] == '':
                ymin = int(line[6:-7])
                box.append(ymin)
            if line[:6] == '':
                ymax = int(line[6:-7])
                box.append(ymax)
    return boxes, size

def convert(boxes, size):
    '''
    将boxes进行转换 xmin、ymin、xmax、ymax ==> x、 y、 w、 h
    '''
    dw = 1. / size['width']
    dh = 1. / size['height']
    for box in boxes: # box ===> [label,xmin,ymin,xmax,ymax]
        x = (box[1] + box[3]) / 2
        y = (box[2] + box[4]) / 2
        w = box[3] - box[1]
        h = box[4] - box[2]
        box[1] = x * dw
        box[2] = y * dh
        box[3] = w * dw
        box[4] = h * dh
    return boxes

def save_txt(save_path, boxes):
    '''
    将转换好的boxes信息保存为txt文件
    '''
    with open(save_path, 'w') as f:
        for box in boxes:
            line = ' '.join([str(elem) for elem in box])
            if box == boxes[-1]: # 如果是最后一行,则不加入换行符
                f.write(line)
            else:
                f.write(line + '\n')

boxes, size = xml_parse("./Annotations/dr105.xml")
print(f"boxes = {boxes}")
print(f"size = {size}")
boxes = convert(boxes, size)
print(f"convert boxes = {boxes}")
save_txt("./Annotations/dr105.txt", boxes)

在上述示例代码中,xml_parser解析函数用于获取解析的boxes和size信息,并将boxes保存为列表形式,将size保存为字典形式,conver函数用于将获取到的boxes信息进行转换,而save_text函数用于将转换好的boxes信息保存为txt文件。

运行输出结果如下:

AutoCV第五课:Python基础_第2张图片

问题:对于每个目标属性的获取,代码中使用了多个if语句,不够简洁和优雅,可以使用正则表达式或者基于ElementTree的xml解析库来简化代码(suggestion from chatGPT)

优化1:可以使用re模块的findall()函数一次性匹配出所有需要的信息,具体实现如下:

import re

def xml_parse_regex(xml_file):
    labels = {'smoke' : 0, 'drink' : 1}
    size = {}
    boxes = []

    with open(xml_file, 'r') as f:
        content = f.read().replace('\n', '') # 去除多余的换行符
        # 使用正则表达式匹配需要的信息
        width = int(re.findall(r'(\d+)', content)[0])
        height = int(re.findall(r'(\d+)', content)[0])
        size['width'] = width
        size['height'] = height
        for box in re.findall(r'.*?', content, re.S):
            name = re.findall(r'(.*?)', box)[0]
            xmin = int(re.findall(r'(\d+)', box)[0])
            xmax = int(re.findall(r'(\d+)', box)[0])
            ymin = int(re.findall(r'(\d+)', box)[0])
            ymax = int(re.findall(r'(\d+)', box)[0])
            boxes.append([labels[name], xmin, ymin, xmax, ymax])

    return boxes, size

上述代码使用了正则表达式来解析xml文件,使用re.findall()函数和正则表达式来匹配需要的信息,其中,正则表达式中的.*?表示匹配任意字符,re.S表示开启多行匹配模式。最后,将匹配到的信息转换为相应的数据类型,并将其存储到sizeboxes中,最终返回这两个数据结构。

该代码相比之前的代码更加简洁,使用了正则表达式来简化匹配过程,减少了代码量和复杂度。但是,正则表达式也可能存在一些缺陷,如难以处理嵌套标签和复杂的文本内容,因此在实际应用中需要根据情况进行选择。

优化2:使用Python标准库中的ElementTree来解析XML文件,代码如下:

import xml.etree.ElementTree as ET

def xml_parse_ET(xml_file):
    labels = {'smoke' : 0, 'drink' : 1}
    size = {}
    boxes = []

    # 读取xml文件
    tree = ET.parse(xml_file)
    root = tree.getroot()

    # 获取图像宽高
    size['width'] = int(root.find('size/width').text)
    size['height'] = int(root.find('size/height').text)

    # 获取目标属性label、xmin、ymin、xmax、ymax
    for obj in root.iter('object'):
        box = []
        box.append(labels[obj.find('name').text])
        bbox = obj.find('bndbox')
        box.append(int(bbox.find('xmin').text))
        box.append(int(bbox.find('ymin').text))
        box.append(int(bbox.find('xmax').text))
        box.append(int(bbox.find('ymax').text))
        boxes.append(box)

    return boxes, size

上述代码使用了Python内置库xml.etree.ElementTree来解析XML文件。具体步骤如下:

  • 1.通过ET.parse(xml_file)函数读取XML文件,并将其解析为一个ElementTree对象

  • 2.通过tree.getroot()获取XML文件的根元素对象,即\

  • 3.通过root.find('size/width(height)').text获取图像宽高

  • 4.通过root.iter('object')获取所有目标元素对象

  • 5.对于每个目标元素对象,通过obj.find('name').text获取目标的标签,通过bbox.find('xmin/ymin/xmax/ymax')获取目标的位置信息,并将它们存储到一个列表中

  • 6.返回目标和图像大小信息

使用ElementTree库解析XML文件的代码相比正则表达式和原生读取文件的方法更为简洁,易于阅读和维护。同时,它还提供了更为方便的元素遍历和查找功能,能够大大提高XML文件的解析效率。

1.5 拓展之re.findall函数

re.findall()函数是Python中re模块提供的一种正则表达式匹配方法,用于搜索匹配某个字符串中所有符合正则表达式规则的子字符串,并返回一个列表。其语法结构如下:

re.findall(pattern, string, flags=0)

其中,pattern是一个正则表达式,string是待匹配的字符串,flags是一个可选参数,表示正则表达式的匹配模式。函数返回一个列表,其中包含所有符合正则表达式规则的子字符串。

pattern中,可以使用各种正则表达式的元字符和限定符,用于描述需要匹配的模式。例如,

  • (\d+)表示匹配一个或多个数字,并将其作为一组提取出来。+表示匹配一个或多个前面的模式,\d表示匹配任意数字
  • (.*?)表示匹配任意字符,?表示匹配0个或1个前面的模式,.*表示匹配任意个任意字符。?加在*+后面表示非贪婪模式,尽可能少的匹配。(看的我头疼)

下面是一个简单(似乎也没有那么简单)的示例代码:

import re

string = "Age: 25, Name: John"
age = re.findall(r"\d+", string)[0]
name = re.findall(r"Name: (.*?)$", string)[0]
print(f"Age: {age}")	# 输出:Age:25
print(f"Name: {name}")	# 输出:Name:John

在这个’简单’的示例代码中,第一个正则表达式\d+匹配年龄(一个或多个数字),第二个正则表达式Name: (.*?)$匹配姓名,其中(.*?)是一个非贪婪匹配,可以匹配到Name:到行末之间的任意字符。

这正则表达式看的我眼晕,感觉和解密差不多。

1.6 拓展之ElementTree模块

ElementTree是Python标准库中的一个解析XML的模块。它提供了一种轻量级的处理XML文档的方式,能够高效地读取、写入和操作XML文件。

使用ElementTree解析XML文件的主要步骤包括以下几个:

  • 1.调用ET.parse()方法将XML文件解析成一个ElementTree对象
  • 2.从ElementTree对象中获取根元素
  • 3.使用根元素的方法(如find()iter())查找自己需要的节点,并获取其内容或属性值

下面是一个简单的示例,假设我们有以下的XML文件example.xml:

<root>
  <person>
    <name>Johnname>
    <age>30age>
  person>
  <person>
    <name>Janename>
    <age>25age>
  person>
root>

我们可以使用ElementTree库获取所有person节点的nameage属性,如下所示:

import xml.etree.ElementTree as ET

# 读取XML文件
tree = ET.parse('example.xml')

# 获取根元素
root = tree.getroot()

# 遍历所有person节点
for person in root.iter('person'):
    # 获取name和age属性值
    name = person.find('name').text
    age = person.find('age').text
    print(f"Name: {name}, Age: {age}")

输出结果为:

Name: John, Age: 30
Name: Jane, Age: 25

1.7 拓展之XML

XML(eXtensible Markup Language)是一种用于存储和传输数据的标记语言,它采用标签(tag)和属性(attribute)的方式来描述数据。XML最初的设计目的是为了解决不同系统间数据交换的问题,同时也被广泛用于数据存储和Web服务等领域。(all from chatGPT)

与HTML类似,XML也是由标签和文本组成的,不同之处在于XML标签可以自定义,而HTML的标签则是预定义好的。XML可以被用于描述任何类型的数据,比如文本、数字、日期、图像、音频等等。XML的语法规则非常简单,它由一系列嵌套的元素组成,每个元素由一个开始标签、一个结束标签和一些子元素或者文本组成。

下面是一个XML文件的例子:


<bookstore>
  <book category="children">
    <title lang="en">Harry Pottertitle>
    <author>J.K. Rowlingauthor>
    <year>2005year>
    <price>29.99price>
  book>
  <book category="web">
    <title lang="en">Learning XMLtitle>
    <author>Erik T. Rayauthor>
    <year>2003year>
    <price>39.95price>
  book>
bookstore>

在上述例子中,第一行指定了XML版本和编码方式。接下来是一个bookstore元素,它包含两个book元素,每个book元素包含了titleauthoryearprice四个子元素,其中title元素有一个lang属性,book元素有一个category属性。

XML具有良好的可扩展性和灵活性,可以根据需要添加、修改、删除元素和属性。同时XML还支持数据验证和处理,可以通过XSD、DTD、XPath等技术对XML进行验证和处理。

2.pickle

在前面的内容中,对于一些基本数据类型,若想写出到二进制储存,需要转化到二进制才能够写出,可以借用struct.pack()函数进行转换,但是对于数组、元组这种数据类型,则情况比较复杂,这时我们可以使用pickle模块来进行操作。

Pickle模块是Python标准库中的一个模块,用于序列化和反序列化Python对象。序列化是将Python对象转化为二进制数据流的过程,而反序列化则是将二进制数据流转换为Python对象的过程。

Pickle模块提供了两个主要的函数:pickle.dump()pickle.load()。其中,pickle.dump(obj,file)用于将Python对象obj序列化并写入到二进制文件file中,而pickle.load(file)则用于从二进制文件file中读取数据并反序列化为Python对象。

下面是一个简单示例,使用pickle模块来实现Python对象的序列化和反序列化:

import pickle

# 储存对象 序列化
with open("./test.bin", "wb") as f:
    pickle.dump([123, "Python", {"name": "Tom"}], f)

# 读取对象 反序列化
with open("./test.bin", "rb") as f:
    data = pickle.load(f)
    print(data)	#输出:[123, 'Python', {'name': 'Tom'}]

当你需要把Python中的某个对象储存下来发给其他人,可以使用pickle.dump,然后其他人可以通过pickle.load进行读取

3.json

json模块是一种用于将结构化数据序列为文本格式的库,它支持list、tuple、dict、string、int、float这些基本的数据类型。而对于其他类型如class等,默认是不支持的,如果要支持需要定制去开发

在Python中,json模块用于处理JSON格式的数据,其中包含了许多用于序列化和反序列化JSON数据的函数。

json.dump()函数用于将Python对象序列化为JSON格式的字符串,并将其写入文件中。json.load()函数用于从JSON格式的文件中读取数据,并将其反序列化为Python对象。

下面是一个简单示例,使用json模块来实现Python对象的序列化和反序列化:

import json

# 储存对象 序列化
with open("./test.json", "w") as f:
    json.dump([123, "Python", {"name": "Tom"}], f)
    # json.dumps

# 读取对象 反序列化
with open("./test.json", "r") as f:
    data = json.load(f)
    # json.loads
    print(data)

总结

本次课程学习了文件的读取操作,并会使用ElementTree库解析XML文件,这对于后面标签格式的转换非常有帮助。同时学习了两个序列化模块pickle和json,其中pickle模块是二进制序列化的library,json模块是文本序列化的library。

你可能感兴趣的:(保姆级从零手写自动驾驶CV,python,深度学习,自动驾驶)