在我们的实际项目中,我们通常会有两个txt
文件,一个是train.txt一个是test.txt,我们会读取这两个txt文件的内容,来找到训练数据以及测试数据。
python读文件,如下代码:
>>> f = open('C:/Users/DELL/Desktop/test.txt', 'r')
标示符'r'
表示读,这样,我们就成功地打开了一个文件。如果文件不存在,open()函数就会抛出一个IOError
的错误,并且给出错误码和详细的信息告诉你文件不存在:
>>> f=open('C:/Users/DELL/Desktop/test.txt', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'C:/Users/DELL/Desktop/test.txt'
若文件正常打开,再调用read()方法时可以一次性读取文件中的全部内容。python会把内容读到内存中,并且用一个str
对象表示。
>>> f.read()
'Hello, world!'
当使用完这个文件之后,我们需要调用.close
方法来关闭文件。
>>> f.close()
由于文件读写时都有可能产生IOError
,一旦出错,后面的f.close()
就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try
... finally
来实现, 异常管理我们后面会说到。
try:
f = open('./demo.txt', 'r')
print(f.read())
finally:
if f:
f.close()
我们也可以使用with
来进行代码简化,使用with
时,python会自动帮我们执行close()
方法。当使用with
的时候,就可以将代码写的更加的整洁,好看!nice!
with open('demo.txt', r) as f:
print(f.read())
但是read
会一次性读取文件的全部内容,如果文件比较大,那么内存就会爆,因此我们可以选择readline()
方法来每次读取一行内容。当如果调用readlines()
时,一次性会读取所有内容并且返回list
,因此我们需要决定怎么调用。
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
当有时候我们读取文件的时候,会遇到编码问题。这时候,会报出UnicodeDecodeError
的错误,主要还是因为文件中夹杂了一些非法编码的字节。这时候,我们使用open
的时候还需要接收一个errors
参数,表示如果遇到编码错误后如何处理。其中,最简单的方式就是直接忽略。其中对于编码问题,windows遇到这些问题,确实很头疼,要是unix或者macos这些系统,就会好很多。
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
#其中gbk表示中文编码
open()
函数时,传入标识符'w'
或者'wb'
表示写文本文件或写二进制文件:>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
你可以反复调用write()
来写入文件,但是务必要调用f.close()
来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with
语句来得保险:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
以'w'模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?我们可以传入'a'以追加(append)模式写入。
python的目录与文件操作常用的模块是os
模块。 os
模块的功能十分强大,如控制环境变量,切换目录,展示当前所有文件,删除并且新建文件与目录都是可以用os
模块。
>>> import os
>>> os.environ
environ({'LC_PAPER': 'zh_CN.UTF-8','PATH': ...})
os的其它常用用法:
#查看当前文件夹中的所有文件
>>> os.listdir('./')
['.anaconda', '.astropy', '.cache', '.cisco', '.conda', '.condarc', '.dnx', '.git', '.gitconfig', '.ipython', '.jupyter', '.labelImgSettings.pkl', '.labelmerc', '.matplotlib', '3D Objects', 'ansel', 'apex', 'AppData', 'Application Data', 'Contacts', 'Cookies', 'Desktop', 'Documents', 'Downloads']
#查看当前目录的绝对路径
>>> os.path.abspath('.')
'C:\\Users\\DELL'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('./', 'demo')
'./demo'
# 然后创建一个目录:
>>> os.mkdir('./testdir')
# 删掉一个目录:
>>> os.rmdir('./testdir')
#拆分路径
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')
#得到文件扩展名
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')
# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')
尤其注意的是os
不能进行复制文件,我们可以使用shutil
提供的copyfile()
函数来进行。
>>> import shutil
>>> shutil.copyfile('a.txt', 'b.txt')
在接下来的项目中,我们会使用glob
模块来进行文件的匹配。如,我们需要得到某个文件夹下的所有png文件,我们可以:
files = glob.glob('*.png')
其中的*.png
表示的是一个正则表达式,为匹配所有以png
的图片,并返回一个list
。
任何一种语言,在程序运行过程中,所有的变量都是存储在内存之中,比如,定义一个list
:
>>> a = {'name':'james', 'age':18}
我们前面学到过list是可变类型,因此,我们可以在程序运行过程中修改里面的变量。但是,一旦程序结束,变量所占用的内存就会释放。我们把变量保存到磁盘中的过程称之为序列化,相对应从硬盘中加载变量到内存中的过程称之为反序列化。
同样,python中有相对应的模块:
>>> import pickle
>>> d = dict(name='james', age=34, score=99)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x05\x00\x00\x00jamesq\x02X\x03\x00\x00\x00ageq\x03K"X\x05\x00\x00\x00scoreq\x04Kcu.'
pickle.dumps()
方法把任意对象序列化成一个bytes
,然后,就可以把这个bytes
写入文件。或者用另一个方法pickle.dump()
直接把对象序列化后写入文件之中。
>>> f = open('demo.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
当我们从文件中加载内容至磁盘时,我们可以先把内容读到一个bytes
,之后再用pickle.loads()
方法反序列化出对象。
>>> f = open('demo.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 34, 'score': 99, 'name': 'james'}
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。其中,JSON和Python内置的数据类型对应如下:
姓名 | 年龄 |
---|---|
{} | dict |
[] | list |
'string' | str |
1.23 | float或int |
true/false | True/False |
'numm' | None |
Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。我们先看看如何把Python对象变成一个JSON:
>>> import json
>>> d = dict(name='james', age=34, score=99)
>>> json.dumps(d)
'{"age": 34, "score": 99, "name": "james"}'
>>> with open('./demo.json', 'w',encoding='utf-8') as json_file:
json.dump(d,json_file,ensure_ascii=False)
dumps()
方法返回一个str
,内容就是标准的JSON
。类似的,dump()
方法可以直接把JSON
写入一个文件中。
要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从文件中读取字符串并反序列化:
model={} #存放读取的数据
with open("./demo.json",'r',encoding='utf-8') as json_file:
model=json.load(json_file)
一般我们常常会将dict
等格式的数据存放成一个JSON
的文件,不过我们经常也会将实例对象进行序列化,代码如下:
import json
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
s = Student('curry', 31, 88)
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
>>> print(json.dumps(s, default=student2dict))
{"age": 31, "name": "curry", "score": 88}
其中,如果没有student2dict
的方法时,无法进行序列化。dumps
需要student2dict
的方法来将Student
的实例变成一个JSON对象。这样的话,Student
实例就先被student2dict
函数转变成一个dict
格式,再被顺利序列化成JSON
。
那如果下次在遇到一个类无法进行序列化成一个JSON,是不是我们都需要写一个属性转成字典的函数呢?不是的,对于Python的class而言,具有__dict__
属性,这样就可以存储实例变量。
>>> print(json.dumps(s, default=lambda obj: obj.__dict__)) #其中obj为实例化的对象
同样,我们需要将反序列化成一个对象的形式,loads
方法首先会转换成一个dict
对象,我们需要写一个dict
转实例的方法,传到object_hook
函数中去。
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
运行结果如下:
>>> json_str = '{"age": 31, "name": "curry", "score": 88}'
>>> print(json.loads(json_str, object_hook=dict2student))
<__main__.Student object at 0x11cd3c190>
voc2007数据集实例
对于xml的解析,有三种方法,Expat
、SAX
、DOM
以及ElementTree
。而这里主要介绍以ElementTree
元素树的形式来进行xml的解析。
ElementTree
生来就是为了处理XML,它在Python
标准库中有两种实现:一种是纯Python
实现的,如xml.etree.ElementTree
,另一种是速度快一点的xml.etree.cElementTree
。注意:尽量使用C语言实现的那种,因为它速度更快,而且消耗的内存更少。
因此,在代码中:
try:
import xml.etree.cElementTree as ET #优先导入
except ImportError:
import xml.etree.ElementTree as ET
其中,常用的方法为:
attrib
方法。text
方法。tag
方法。我们以解析VOC2007的数据集来举例子:
import numpy as np
import xml.etree.ElementTree as ET
CLASSES = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car',
'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike',
'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor')
index_map = dict(zip(CLASSES, range(len(CLASSES))))
print(index_map)
输出:
{'aeroplane': 0, 'bicycle': 1, 'bird': 2, 'boat': 3, 'bottle': 4, 'bus': 5, 'car': 6, 'cat': 7, 'chair': 8, 'cow': 9, 'diningtable': 10, 'dog': 11, 'horse': 12, 'motorbike': 13, 'person': 14, 'pottedplant': 15, 'sheep': 16, 'sofa': 17, 'train': 18, 'tvmonitor': 19}
对xml的文件进行解析,得到对应的标注标签:
label = []
for obj in root.iter('object'):
difficult = int(obj.find('difficult').text)
cls_name = obj.find('name').text.strip().lower() #。text表示得到这个节点的值,strip()表示去掉空格,lower()为转小写
if cls_name not in CLASSES:
continue
cls_id = index_map[cls_name] #得到对应的id
xml_box = obj.find('bndbox') #查训bndbox这个节点的信息
xmin = (float(xml_box.find('xmin').text) - 1)
ymin = (float(xml_box.find('ymin').text) - 1)
xmax = (float(xml_box.find('xmax').text) - 1)
ymax = (float(xml_box.find('ymax').text) - 1)
label.append([xmin, ymin, xmax, ymax, cls_id])
针对得到的标签,可以使用opencv
或者pillow
来进行标注可视化。
from PIL import Image, ImageDraw
img = Image.open(r'F:\working\dataset\VOCdevkit\VOC2007\JPEGImages\000002.jpg')
draw = ImageDraw.Draw(img)
for boxes in label:
xmin = int(boxes[0])
ymin = int(boxes[1])
xmax = int(boxes[2])
ymax = int(boxes[3])
draw.rectangle([xmin, ymin, xmax, ymax], outline=(255, 0, 0))
draw.text([xmin, ymin], CLASSES[boxes[4]], (255, 0, 0))
img.show()
最终,我们可视化下标注的位置以及类别信息。 特别提醒,在实际项目中,我们需要每一次check好自己的数据!不然,坑可大了!
最终,可视化的结果如图所示: