Python构建轻量级手写数字识别数据库管理系统
本文介绍如何使用Python构建一个轻量级的图片存储管理系统,用于优化手写数字识别流程。通过将图像转换为1x10000向量并存储于CSV文件中,系统能够智能识别新增图片,仅对增量数据进行向量化处理,避免重复计算。文章详细解析了从文件对比、向量生成到数据合并的完整逻辑,并提供了核心代码实现,旨在提升大量图片处理时的效率。
写在前面
在上一篇关于Python实现手写数字识别及图像处理的教程中,我们详细讲解了如何对图片进行裁剪和拉伸,最终将每张图片转化为一个大小为1x10000的向量。然而,如果每次运行程序都重新计算这些向量,当图片数量庞大时,将会消耗大量的计算时间。
为了解决这一问题,我们需要将这些向量持久化存储到文件中。系统的运行逻辑是:每次启动时,首先检查图库中是否有新增图片。若有新增,则将这些新图片转换为1x10000的向量并追加存入文件;若无新增,则直接读取文件中已有的全部图片向量进行后续计算。这种机制显著节省了重复计算的时间。
因此,本文旨在从零开始构建这样一个图片存储管理系统。
实现逻辑
第一次读入图片
我们的图库中包含大量手写数字图片,每张图片的名称遵循“[数字内容]_[序号]”的格式。例如,文件名“2_3”表示该图片中的数字是2,且是数字为2的图片序列中的第3张。
我们使用一个名为Data.csv的CSV文件作为基础图片数据库。
首先,程序读取图库中所有图片的文件名,并将其保存在变量fileNames中。同时,读取Data.csv文件中的所有数据。
接着,提取Data.csv的最后一列(该文件共有10002列,其中第10001列表示图片对应的数字,第10002列存储图片名称)。我们将这一列提取出来,存储在变量item中,它代表了数据库中已存储的所有图片名称。
我们将图库中新增的图片名称保存在newFileNames中。如果Data.csv文件为空,则直接将fileNames赋值给newFileNames,这意味着如果数据库初始为空,图库中的所有图片均被视为新增图片。
如果Data.csv不为空,则将item中的内容与fileNames进行比对。若fileNames中存在而item中不存在的名称,则将这些名称加入newFileNames;若item中存在而fileNames中不存在的名称,则忽略不计。
简而言之,我们设定数据库遵循“只进不出”的原则。
至此,我们获得了新增图片的名称列表newFileNames。
将newFileNames中的图片名称传入上一篇文章介绍的GetTrainPicture函数进行处理,得到一个nx10001的矩阵。该矩阵的每一行代表一张新增图片,前10000列为图片向量,第10001列为该图片对应的数字,结果保存在变量pic中。
将这些新图片的数据追加存入数据库的末尾。
读取数据库中原有的图片向量,并与新获取的pic矩阵合并,从而得到当前所有训练图片的完整向量集合pic。

以上即为本章的核心内容,下文将结合代码进行详细解释。
代码解析
主文件
import os
import numpy as np
import OperatePicture as OP
import OperateDataBase as OD
import csv
##Essential vavriable 基础变量
#Standard size 标准大小
N = 100
#Gray threshold 灰度阈值
color = 100/255
#读取原CSV文件
reader = list(csv.reader(open('DataBase.csv', encoding = 'utf-8')))
#清除读取后的第一个空行
del reader[0]
#读取num目录下的所有文件名
fileNames = os.listdir(r"./num/")
#对比fileNames与reader,得到新增的图片newFileNames
newFileNames = OD.NewFiles(fileNames, reader)
print('New pictures are: 'newFileNames)
#得到newFilesNames对应的矩阵
pic = OP.GetTrainPicture(newFileNames)
#将新增图片矩阵存入CSV中
OD.SaveToCSV(pic, newFileNames)
#将原数据库矩阵与新数据库矩阵合并
pic = OD.Combination(reader, pic)
我将两节内容分别封装在两个Python文件中:上一篇文章涉及的图片切割与处理逻辑放置在OperatePicture文件中,而本节涉及的数据库处理逻辑则放置在OperateDatabase文件中。
由于整体代码逻辑已在上方梳理完毕,此处不再赘述。接下来将针对每个函数进行具体讲解。
OperateDatabase代码
从主文件中可以看到,首先调用了NewFiles函数,该函数主要用于对比fileNames和reader两个来源中图片名称的差异,并返回新增图片名称的列表。代码如下:
def NewFiles(fileNames, reader):
'''判断是否有不同于数据库中的新文件加入'''
#如果数据库中没有数据,则返回filenames
if len(reader) == 0:
return fileNames
else:
#从数据库中提取所有名称
files = [item[10001] for item in reader]
#需要加入的图片名称
newFileNames = []
for item in fileNames:
#判断当前名称是否存在数据库中
#如果不存在,则加入newFileNames
if item not in files:
newFileNames.append(item)
return newFileNames
首先判断reader是否有内容。若无内容,说明是首次执行,直接返回fileNames;否则进入后续的比较逻辑。
获取newFileNames后,将该列表中的所有名称对应的图片通过GetTrainPicture函数处理,生成一个1x10001大小的矩阵。具体处理过程请参阅上一篇文章的讲解。
随后,为了将新数据存入CSV文件,我们利用SaveToCSV函数将pic矩阵存入文件,具体代码如下:
def SaveToCSV(pic, fileNames):
'''将pic与对应的dileNames存入CSV文件'''
writer = csv.writer(open('Database.csv', 'a', newline = ''), dialect = 'excel')
#将fileNames变为列表
f = [item for item in fileNames]
#每一行依次写入文件中
for i in range(len(pic)):
#将改行图片向量转为list
item = pic[i].tolist()
#将这个图片向量对应的名称f放入列表最后一个
item.append(f[i])
writer.writerow(item)
函数运行后,会将pic矩阵对应的内容直接追加写入CSV文件,实现数据库的写入操作,且不会覆盖原有数据。
接下来,我们需要将数据库中原有的大量数据reader与新加入的数据pic合并到pic变量中,因此利用Combination函数将两个矩阵合并,代码如下:
def Combination(reader, pic): '''将两个矩阵reader与pic合并''' #两个矩阵的总行数 l = len(reader) + len(pic) #初始化新的矩阵 newPic = np.zeros(l*10001).reshape(l, 10001) #将reader最后的那个字符串名称去掉 for item in reader: item.pop() #将reader转化为numpy的矩阵形式 reader = np.array(reader) #新矩阵前半部分放reader,后半部分放pic if len(reader) != 0: newPic[0:len(reader), :] = reader newPic[len(reader):len(pic), :] = pic return newPic
由于reader的最后一行包含图片名称字符串,因此先利用pop方法将其去除,随后转换为矩阵形式,再直接放入新矩阵中。此处的矩阵操作可能较为少见,下面进行详细解释。
假设我现在有一个2x3的矩阵和一个2x2的矩阵
m = [[1 2 3] [4 5 6]] n = [[7 8] [9 1]]
我可以执行如下操作
#操作一 m[:, 0:2] = n print(m) #操作二 m[:, 1:3] = n print(m) #以下为输出结果 #操作一 [[7 8 3] [9 1 6]] #操作二 [[7 7 8] [9 9 1]]
可以看出,操作一直接将矩阵m的第1、2列替换为n,操作二将m的第2、3列替换为n。具体过程可查阅numpy矩阵操作文档或自行总结规律,此处不再赘述。
以上即为本篇的全部代码内容。
小结
本篇使用CSV文件构建了一个非常简陋的数据库,目前仅支持识别已有内容(NewFiles)与添加内容(SaveToCSV)两种操作,并未实现插入、删除或修改等功能。我认为这两个函数已满足当前需求,因此在功能已足够的前提下,不再进行拓展。
所有源代码已上传至我的GitHub仓库,欢迎下载参考,感谢阅读。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













