1.文件操作
1. 介绍
我们已经学会了如何把图片导入到程序中,但是做机器学习的时候,很多数据其实不是图片类型,这时候我们就需要学习如何把任意的文件导入到当前的程序中了。一般python都会自带OS库,用这个库,我们可以完成几乎所有的数据操作。导入OS库的方法和之前一样:
import os
2.os.path接口
os.path接口是很常用的一个接口,它主要与文件路径地址相关,常用于检测文件是否存在以及拼接文件地址,下面我来一一介绍一下它的内置函数的功能。
(1)os.path.exists('文件路径')
该函数可以检测该路径下的文件是否存在,如果存在返回True,不存在返回False,还是那之前我家猫的那个图片文件来作为例子:
"""
@FileName:OS_use.py
@Description:os的使用例子
@Author:段鹏浩
@Time:2023/3/13 22:33
"""
import os
# mycat.jpg存在,但是cat.jpg不存在
print(os.path.exists('E:/pictures/mycat.jpg'))
print(os.path.exists('E:/pictures/cat.jpg'))
输出结果为:
True
False
这里建议每次要导入文件前,先查看是否存在,否则会引发系统崩溃等问题。
(2)os.path.join(路径名,文件名)
连接路径名和文件名。通过该函数,我们先定义好要保存的位置,或者要查询的位置,写个循环,就可以循环的保存文件或者是查看文件,因为现在还没有学怎么保存文件,等后面实战的时候说明。先写个拼接的例子给大家看看(其实用python内置的字符串拼接 路径+文件名也可):
"""
@FileName:OS_use.py
@Description:os的使用例子
@Author:段鹏浩
@Time:2023/3/13 22:33
"""
import os
path = "E:/pictures/"
name = "cat.jpg"
p_and_n = os.path.join(path, name) # join函数
# 和字符串拼接是等价的
p_an_n = path + name
print(f"join函数生成的:{p_and_n}\n")
print(f"字符串拼接成的:{p_an_n}")
输出为:
join函数生成的:E:/pictures/cat.jpg
字符串拼接成的:E:/pictures/cat.jpg
当然,os.path.join有着更高级的用法:
1
◯
\textcircled{1}
1◯若各个路径之间不存在 “ \ ”, 则其会自动为各个路径之间增加连接符 “ \ ”。
例如:
import os
dir = os.path.join('home','pc','data')
print(dir)
输出:
home\pc\data
我们可以看到,在windows系统下,它拼接出的路径是符合window系统的,不是“/”,而是"",这是编译器会警告,但是我们只能选择无视编译器,因为你直接运行windows的路径,不出现\n什么的特殊符号,也不会报错。
有没有不警告的呢,还真有,replace转换一下就行,代码如下:
import os
dir = os.path.join('home', 'pc', 'data')
dir = dir.replace('\\', '/') # 这个代码用/来替换全部的\(虽然是\\,但是\前面的一个\会被编译器无视,否则编译器还是会认为出错)
print(dir)
输出就正常了:
home/pc/data
2
◯
\textcircled{2}
2◯存在以“ / ”开始的子路径,则从最后一个以“ / ”开头的子路径开始拼接,之前的子路径全部丢弃。
例如:
import os
dir = os.path.join('home', '/pc', 'data')
dir = dir.replace('\\', '/')
print(dir)
dir = os.path.join('/home', 'pc', '/data')
dir = dir.replace('\\', '/')
print(dir)
输出:
/pc/data
/data
3
◯
\textcircled{3}
3◯存在以“. / ”开始的子路径,join会无视里面的"/",如果同时存在以“ / ”开头的子路径,则还是以“ / ”开头的子路径为依据,从最后一个以“ / ”开头的子路径开始拼接,之前的子路径全部丢弃,但是如果在./后面没有“/”,那不影响全部元素的拼接。
例如:
import os
dir = os.path.join('./home','/pc','data') # 这里在./后面有/
dir = dir.replace('\\', '/')
print(dir)
dir = os.path.join('home','pc','./data') # 这里./在最后面
dir = dir.replace('\\', '/')
print(dir)
输出结果为:
/pc/data
home/pc/./data
(3)path的其他函数并不是那么常用,仅仅了解一下就行:
函数名称 | 功能 |
---|---|
os.path.abspath(文件变量) | 返回文件的绝对路径 |
os.path.basename(路径名称) | 返回路径名称的最后部分(最后一个"/"后面的) |
os.path.isfile() | 检查路径是否指向文件 |
os.path.isdir() | 检查路径是否指向文件夹目录 |
3.删除文件
删除文件是很简单的,只需要一行代码就可以搞定:
os.remove(要删除文件的路径)
但是要注意的是,如果文件不存在,系统会报错,所以需要在删除前用path.exists()函数确定一下文件是否存在。
4. 文件夹的创建
对于训练集,测试集,和验证集,我们需要创建三个文件夹来进行区别。所以创建文件夹是很有必要的,创建文件夹的方法很容易:
os.makedirs('路径/文件夹名称') # 在特定路径创建
os.makedirs('名称') # 在当前路径创建
如果指定的路径不存在,则系统会层层的创建下去。
举例:
import os
os.makedirs('E:/test/love/you')
os.makedirs('cats/cat')
输出的效果:
5. 文件的遍历和排序:
我们的数据集可能是数十万张的图片,为了遍历这些输入,我们需要获得一个文件中的全部文件名称,所以就需要文件遍历代码:
os.listdir(路径)
该函数会返回这个路径中的全部文件名称以及其格式。
下面的例子是输出我当前的文件夹:
import os
path = 'E:/pytorchProject1'
lists = os.listdir(path)
print(lists)
['.idea', 'cat', 'hello.py', 'MyNet.py', 'OS_use.py', 'traIn.py', 'use_cv.py', 'use_numpy.py']
可以看到,不仅是文件名称,文件夹名称程序也可以一并输出。这样输出的文件名称排序其实是按照哈希表的顺序来排列的,所以有时并不按照我们想要的方式排列。这个时候需要进行排序:
(1)排列数字:要排序的数组.sort()
代码,这是Python的内置函数,它可以把数字从小到大进行排列
例如:
a = [1, 4, 6, 8, 3, 2]
print(a)
a.sort()
print(a)
输出为:
[1, 4, 6, 8, 3, 2]
[1, 2, 3, 4, 6, 8]
缺点是只能排序int类型的,否则会报错,显然不适合文件名
(2)字符串和数字排序:这个就没有内置函数帮忙了,只能自己写,我现在写一个先按照数字排序,如果不是数字则按照字母排序的(其实在Windows系统下,listdir()返回的值就是这么排序的,只有linux下会乱排,建议排序前先看一眼是否已经排好)。
先附上判断字符串属于什么的方法:
函数 | 功能 |
---|---|
isdigit() | 自然数 |
isalpha () | 字母 |
isspace() | 空格 |
isdecimal() | 十进制数字 |
islower() | 小写字母 |
isupper() | 大写字母 |
istitle() | 单词首字母大写 |
isalnum() | 字母或数字 |
代码如下:
"""
@FileName:Sort.py
@Description:按照文件名开头的字母和者数字排序
@Author:段鹏浩
@Time:2023/3/14 12:33
"""
import os
path = "cat"
names = os.listdir(path) # 获取全部的文件名
number = [] # 创建一个空数组,用来存那些数字开头的文件
alpha = [] # 创建一个空数组存放字符串开头的文件
el = [] # 用其他字符开头的文件
# 先遍历一遍,把各个文件名进行分离
for i in names:
head = i[0] # 取出头部
if head.isdigit():
number.append(i) # 如果是数字类型,就放入数字列表
elif head.isalpha():
alpha.append(i) # 如果是字母,就放入字母列表
else:
el.append(i) # 放入其他列表
# 简单的冒泡排序:
i = len(number)
while i > 1:
# print(i)
b = 0
c = 1
while 1:
# print(c)
if c == i:
break
else:
if int(number[b][0]) > int(number[c][0]):
tem = number[b]
number[b] = number[c]
number[c] = tem
b = b + 1
c = c + 1
i -= 1
i = len(alpha)
while i > 1:
# print(i)
b = 0
c = 1
while 1:
# print(c)
if c == i:
break
else:
A = alpha[b][0]
B = alpha[c][0]
# 大小写判断,如果是大写则转换小写再比较
if A.isupper():
A = A.lower()
if B.isupper():
B = B.lower()
if A > B:
tem = alpha[b]
alpha[b] = alpha[c]
alpha[c] = tem
b = b + 1
c = c + 1
i -= 1
# 最后把三个拼接起来:
names = number + alpha +el
print(names)
2.数据操作
1.构建训练集和验证集
(1)我们之前讲过,需要把数据集划分为训练集和验证集,且比例是7:3。我们还是用图片为例子,假设我们的数据集文件夹名称是data,那么我们首先要看看里面有多少张图片,并且记为n。
data = os.listdir('前面的路径\data')
n = len(data)
(2)因为图片的大小是不一样的,所以我们要统一一下图片的大小,然后把训练集和测试集分开,所以我们需要搞一个数据结构来在内存里面保存这些处理好的图片。因为图片是矩阵,那么我们就可以拿一个矩阵容器,就是一个空的多维矩阵来保存每一张修改后的图片。所以我们需要用到之前的Numpy来创建这个多维数组。但是这里矩阵是不能像列表那样依次append()进去的,只能先设置好一个全零或者全1的矩阵,然后一个个替换,所以我们需要先搞两个这样的多维矩阵,一个保存全部的训练集,一个保存全部的数据集。但是np中全零和全1的矩阵函数,zeros和ones都只能创建二维矩阵,不能创建多维矩阵,所以需要搞一个可以平铺所有图片的超大二维矩阵,然后用np.shape来变换它,把它多次折叠成一个和我们的训练集和验证集对应的多维矩阵。
假设我们需要的图片是
x
×
y
x\times y
x×y,那么我们可以写出如下创建容器的代码:
# 训练集的容器
train_set = np.zeros(int(0.7*n)*1*x*y) # 这里我们假设我们的图片是灰度图,那么就只有n*1*x*y个像素点,如果是3维的则*3
train_set = np.reshape(train_set, (int(0.7*n),1,x,y)) # 注意这里的变换尺寸要和上面一致,如果是三维彩图就改成3
# 用一样的方法去创建测试集
test_set = np.zeros(int(0.3*n)*1*x*y)
test_set = np.reshape(test_set,(int(0.3*n), 1, x, y))
用int()是为了向下取整,防止图片数量出现小数,同时,小数出现在矩阵创建中也会出错。
(3)接下来,我们只需要把图片放入我们创建好的容器里面就好啦,搞训练集:
# 首先,我们需要在最后看一下有多少张图片完成转换,所以设置一个计数器
m = 0
# 之后就可以循环int(0.7*n)次,把图片一一转换然后放入
for i in range(int(0.7 * n)):
path = os.path.join('E:/pictures/val', data[i]) # 拼接出文件路径
if os.path.exists(path):
img = cv.imread(path) # 文件如果存在就开始读取
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 把图片转换为灰度图
img = cv.resize(img, (x, y)) # 变换图片尺寸
train_set[i, 0, :, :] = img[:, :] # 因为每一组只有一张图片,所以维度这里是1,但是从0开始,所以给用0切片切出的平面赋值
m += 1 # 记录一下成功几个
print("\r" + f"一共有:{int(0.7 * n)}张图片,现在完成:{m}张图片", end="", flush=True) # 动态更新情况
else:
print(f"路径:{path} 不存在")
最后这里的print里面,“\r"是回车符,表示该行清除全部输出,然后开始输出,end=”"表示输出以后留在这行(这样才会被新的清楚),flush=True是实时刷新的意思,不然python会等for循环结束以后才输出,如果图片很长的话,我们是看不到动态更新的情况的。
(4)接下来参考上面的做测试集:
# 测试集的容器
m2 = 0 # 一样的标记
for i in range(int(n*0.3)):
path = os.path.join("路径名称",data[n-i-1]) # 这里就从后取起来,如果要从前取那就切片
if os.path.exists(path):
img = cv.imread(path)
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img = cv.resize(img, (x, y))
test_set[i, 0, :, :] = img[:, :]
m2 += 1
print("\r"+f"一共有{int(0.3*n)}张图片,现在处理了{m2}张图片", end = "", flush = True)
else:
print(f"路径:{path} 不存在")
(5)最后记录一下成功多少,并且把训练集和测试集保存起来
print(f"训练集共{m}张图片,测试集共{m2}张图片")
np.save('train_set.npy', train_set)
np.save('test_set.npy', test_set)
(6)实际的代码,在用的时候记得把我的路径换成你自己的路径:
"""
@FileName:train_test.py
@Description:划分训练集和测试集
@Author:段鹏浩
@Time:2023/3/11 21:33
"""
import os
import numpy as np
import cv2 as cv
data = os.listdir('E:/pictures/val')
n = len(data)
x = 500
y = 500
# 训练集的容器
train_set = np.zeros(int(0.7 * n) * 1 * x * y) # 这里我们假设我们的图片是灰度图,那么就只有n*1*x*y个像素点,如果是3维的则*3
train_set = np.reshape(train_set, (int(0.7 * n), 1, x, y)) # 注意这里的变换尺寸要和上面一致,如果是三维彩图就改成3
# 用一样的方法去创建测试集
test_set = np.zeros(int(0.3*n)*1*x*y)
test_set = np.reshape(test_set,(int(0.3*n), 1, x, y))
# 首先,我们需要在最后看一下有多少张图片完成转换,所以设置一个计数器
m = 0
# 之后就可以循环int(0.7*n)次,把图片一一转换然后放入
for i in range(int(0.7 * n)):
path = os.path.join('E:/pictures/val', data[i]) # 拼接出文件路径
if os.path.exists(path):
img = cv.imread(path) # 文件如果存在就开始读取
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 把图片转换为灰度图
img = cv.resize(img, (x, y)) # 变换图片尺寸
train_set[i, 0, :, :] = img[:, :] # 因为每一组只有一张图片,所以维度这里是1,但是从0开始,所以给用0切片切出的平面赋值
m += 1 # 记录一下成功几个
print("\r" + f"一共有:{int(0.7 * n)}张图片,现在完成:{m}张图片", end="", flush=True) # 动态更新情况
print("\n训练集划分结束\n")
# 测试集的容器
m2 = 0 # 一样的标记
for i in range(int(n * 0.3)):
path = os.path.join("E:/pictures/val", data[n - i - 1]) # 这里就从后取起来,如果要从前取那就切片,因为从0开始所以要减一
if os.path.exists(path):
img = cv.imread(path)
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img = cv.resize(img, (x, y))
test_set[i, 0, :, :] = img[:, :]
m2 += 1
print("\r" + f"一共有{int(0.3 * n)}张图片,现在处理了{m2}张图片", end="", flush=True)
else:
print(f"路径:{path} 不存在")
print("\n测试集划分结束\n")
print(f"训练集共{m}张图片,测试集共{m2}张图片")
np.save('train_set.npy', train_set)
np.save('test_set.npy', test_set)
输出:
一共有:333张图片,现在完成:333张图片
训练集划分结束
一共有142张图片,现在处理了142张图片
测试集划分结束
训练集共333张图片,测试集共142张图片
2. 把npy文件读取为图片:
这里很简单,比如我们读取并显示上面的训练集的第100张图片,我们只需要四步就行:
(1)读取整个npy文件:
train_set = np.load("train_set.npy")
(2)定位到对应的图片,记得索引是从0开始,所以第一百张100其实是99:
img = train_set[99, 0, :, :]
(3)这里很关键,npy的数据类型(float)和cv读取的数据类型(uint8)是不一样的,所以需要进行类型转换,转换方法也很简单,用np.astype就可以转换矩阵中全部数据的类型:
img = img.astype(np.uint8)
插一句,uint8是无符号八位整型。
(4)显示即可:
cv.imshow("第一百张图", img)
cv.waitKey(0)
下面是完整代码:
"""
@FileName:use_numpy.py
@Description:怎么用numpy的学习
@Author:段鹏浩
@Time:2023/3/11 21:33
"""
import os
import numpy as np
import cv2 as cv
train_set = np.load("train_set.npy")
img = train_set[99, 0, :, :]
img = img.astype(np.uint8)
# 把原图也显示一下:
data = os.listdir('E:/pictures/val')
path = os.path.join('E:/pictures/val', data[99])
if os.path.exists(path):
img0 = cv.imread(path)
img0 = cv.resize(img0, (500, 500))
cv.imshow(u"原图", img0) # 加u把中文转为unicode码,防止出错
cv.imshow(u"第一百张图", img)
cv.waitKey(0)
输出如下(这是个车厘子,别害怕):
3. opencv显示中文:
可惜的是这里中文没有显示成功。
这里就需要进行中文解码了:
def zh_cn(string):
return string.encode('gb2312').decode(errors='ignore')
完整代码如下
"""
@FileName:use_numpy.py
@Description:怎么用numpy的学习
@Author:段鹏浩
@Time:2023/3/11 21:33
"""
import os
import numpy as np
import cv2 as cv
def zh_cn(string):
return string.encode('gb2312').decode(errors='ignore')
train_set = np.load("train_set.npy")
img = train_set[99, 0, :, :]
img = img.astype(np.uint8)
# 把原图也显示一下:
data = os.listdir('E:/pictures/val')
path = os.path.join('E:/pictures/val', data[99])
if os.path.exists(path):
img0 = cv.imread(path)
img0 = cv.resize(img0, (500, 500))
cv.imshow(zh_cn("原图"), img0) # 加u把中文转为unicode码,防止出错
cv.imshow(zh_cn("现在的图"), img)
cv.waitKey(0)
这个函数可以把输入的中文字符串转换为对应编码,缺点是有大部分的中文直接不能用或者不显示,如果非要显示中文的话,在另起一个conda环境,下载python2,在里面用cv.imshow(u"中文",img)是可以正常显示的。
附上显示结果:
第二张显然没有正常显示。
4.把数据集的图片一张张的划分:
其实,肯定有人觉得,如果图片很多,矩阵太大内存放不下怎么办,那就一张张的分,代码和之前很像,只不过不需要进行矩阵的提取创建了,因为代码简单,就不一点点讲解了:
"""
@FileName:test_and_train.py
@Description:单张的划分训练集和测试集
@Author:段鹏浩
@Time:2023/3/15 17:15
"""
import cv2 as cv
import os
# 首先还是一样的读取全部的数据,并且计算其大小
data = os.listdir('E:/pictures/val')
n = len(data)
n1 = int(0.7 * n) # 训练集的量
n2 = int(0.3 * n) # 测试集的量
# 创建两个新的文件夹
os.makedirs('E:/pictures/train')
os.makedirs('E:/pictures/test')
m1 = 0
for i in range(n1):
path = os.path.join('E:/pictures/val', data[i])
if os.path.exists(path):
img = cv.imread(path)
img = cv.resize(img, (300, 300)) # 现在就不用转灰度图了,但是可以把图片格式和名称转一下
name = 'fruit' + str(i) + ".jpg" # 水果1,水果2这样排列,并且统一保存为jpg格式
path2 = os.path.join('E:/pictures/train', name)
cv.imwrite(path2, img)
m1 += 1
print('\r'+f"一共有{n1}张训练集图片,目前成功划分{m1}张", end="", flush=True)
else:
print(f"路径:{path} 不存在")
print("\n") # 结束换行
m2 = 0
for i in range(n2):
path = os.path.join('E:/pictures/val', data[n-i-1])
if os.path.exists(path):
img = cv.imread(path)
img = cv.resize(img, (300, 300)) # 现在就不用转灰度图了,但是可以把图片格式和名称转一下
name = 'fruit' + str(i) + ".jpg" # 水果1,水果2这样排列,并且统一保存为jpg格式
path2 = os.path.join('E:/pictures/test', name)
cv.imwrite(path2, img)
m2 += 1
print('\r'+f"一共有{n2}张测试集图片,目前成功划分{m2}张", end="", flush=True)
else:
print(f"路径:{path} 不存在")
print("\n")
输出结果:
一共有333张训练集图片,目前成功划分333张
一共有142张测试集图片,目前成功划分142张
需要显示的话,直接来拿图片就行,也不需要转换格式。
3.总结:
本章我们学习了怎么进行文件的创建,确定文件存在与否,以及文件读取,文件的遍历和排序。还学习了两种划分数据集的为训练集和测试集的方法。下一章,我们将学习如何使用Matpotlib来画图(画图有利于我们进行数据分析)。