模拟算法大赛(考核)
完成机器学习的基础学习之后,我们通常会通过参加一些算法大赛来检验自己的学习成果。
譬如:
-
阿里云天池算法大赛:阿里巴巴集团主办的一系列比赛,涵盖了许多不同的领域和问题,如推荐系统、图像识别、预测等。
-
Kaggle竞赛:一个国际平台,我们之前火山下的文明一课就是Kaggle。
-
CCF大数据与计算智能大赛:由中国计算机学会(CCF)主办,涵盖了许多实际问题,如交通预测、社交网络分析等。
参加算法大赛可以帮助我们更好地理解机器学习的应用,获取一些实际问题的解决方案,提高自己的编程能力和数据分析能力。另外,这些算法大赛通常会提供大量的数据集和评测标准,我们需要根据数据集训练模型,并提交预测结果,最终根据评测标准来评价模型的性能。
本项目仅评价工程能力,即 项目完成度。
以日常中交通违法为例,交管在路口设有自动抓拍系统。
这个系统利用图片的相似性比较,如果发现有车辆闯红灯,或者违法停车,就会自动拍摄车辆的照片。
同时记录时间、摄像头位置等信息。
系统识别车牌。
然后通过车牌号码在信息库里获取车主的号码,并自动发送短信。(如果我们自己需要完整的模拟整个流程,可以改成电子邮箱)
【交管通知】尊敬的XXX,您好,您的车辆在XX点XX分XXX路口违规,请XXXX,违法照片请点击XXXX,申诉请点击XXX。
这个流程非常清晰,车牌识别是一个非常重要的环节。
搜索数据集
我们从以上的算法大赛中选 择一个数据集,然后通过数据集来训练模型。
这里以下面这个数据集(约200MB)为例:
下载方式1(群晖):https://gofile.me/7fmxe/asNF1hX5A
密码:kjzx
下载方式2(阿里云天池):https://tianchi.aliyun.com/dataset/90055
下载方式3(Kaggle):https://www.kaggle.com/datasets/andrewmvd/car-plate-detection/data
观察数据集
我们把数据集下载下来,简单的研究一下。
发现里面有2个文件夹,一个是images(图像),另一个是annotations(注释)
通过观察我们可以轻松发现annotations中的文件名.xml
和images中的文件名.png
是一一对应的。
其中文件名.xml
中包含了车牌的位置信息,如下:
<annotation>
<folder>图片</folder>
<filename>Cars0.png</filename>
<size>
<width>500</width>
<height>268</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>licence</name>
<pose>未指定</pose>
<truncated>0</truncated>
<occluded>0</occluded>
<difficult>0</difficult>
<bndbox>
<xmin>226</xmin>
<ymin>125</ymin>
<xmax>419</xmax>
<ymax>173</ymax>
</bndbox>
</object>
</annotation>
通过观察可以发现,我们可以通过解析文件名.xml
文件,通过两点定位获取车牌的位置信息,然后通过文件名.png
文件,获取车牌的图像信息。
如果这个数据集没有标注,我们可以通过判断图片中矩形的位置,来获取车牌的位置信息。具体方法与火山下的文 明
一课中的方法类似。
处理数据集
根据下方代码提示补全,未提及内容已经补全,需要补全的内容已经标注。
正确处理之后的代码,可以将整个车牌的图像裁剪出来,保存到cropped_images
目录下。
# 导入xml.etree.ElementTree库,用于处理XML文件
import xml.etree.ElementTree as ET
# 导入os库,用于处理文件和目录
import os
# 导入PIL库的Image模块,用于处理图像
from PIL import Image
# 定义一个函数parse_xml,参数为xml文件的路径
def parse_xml(xml_file) -> dict:
# 解析xml文件,获取ElementTree对象
tree = ET.parse(xml_file)
# 获取xml文件的根元素
root = tree.getroot()
# 初始化一个空字典来存储数据
data = {}
# 从xml文件中获取各个元素的值,并存储到字典中
data['folder'] = root.find('folder').text
data['filename'] = root.find('filename').text
data['width'] = root.find('size/width').text
data['height'] = root.find('size/height').text
data['depth'] = root.find('size/depth').text
data['segmented'] = root.find('segmented').text
# 初始化一个空列表来存储所有的对象数据
objects = []
# 遍历xml文件中的所有'object'元素
for obj in root.findall('object'):
# 初始化一个空字典来存储当前对象的数据
obj_data = {}
# 从'object'元素中获取各个子元素的值,并存储到字典中
obj_data['name'] = obj.find('name').text
obj_data['pose'] = obj.find('pose').text
obj_data['truncated'] = obj.find('truncated').text
obj_data['occluded'] = obj.find('occluded').text
obj_data['difficult'] = obj.find('difficult').text
obj_data['xmin'] = obj.find('bndbox/xmin').text
obj_data['ymin'] = obj.find('bndbox/ymin').text
obj_data['xmax'] = obj.find('bndbox/xmax').text
obj_data['ymax'] = obj.find('bndbox/ymax').text
# 将当前对象的数据添加到对象列表中
objects.append(obj_data)
# 将对象列表添加到数据字典中
data['objects'] = objects
# 返回数据字典
return data
# 定义一个函数get_coordinates,参数为从parse_xml函数返回的数据字典
def get_coordinates(data:dict) -> tuple:
# 获取数据字典中的第一个对象
# 返回对象的xmin, ymin, xmax, ymax值(转换为整数)
# 定义一个函数crop_image,参数为图片路径、坐标和新图片路径
def crop_image(image_path:str, coordinates:tuple,new_image_path:str) -> None:
# 打开图片 提示 .open
# 根据给定的坐标裁剪图片
cropped_img = img.crop(coordinates)
# 保存裁剪后的图片到新的路径 提示- .save
def split_bndbox():
# 遍历'annotations'目录下的所有文件
for annotations in os.listdir('annotations'):
# 构建annotations文件的完整路径
# 构建对应的图片文件路径
# 构建裁剪后的新图片路径
# 如果'cropped_images'目录不存在,则创建
# 解析annotations文件,获取数据
# 从数据中获取坐标
coordinates = get_coordinates(data)
# 根据坐标裁剪图片,并保存到新的路径
crop_image(image_path, coordinates , new_image_path)
if __name__ == '__main__':
split_bndbox()
车牌二次分割与人工标注
通过人眼的观察,对车牌进行标注,例如
此过程你也可以删除一些你认为不佳的图片,譬如:Cars129
以提高模型的训练效果。
我们可以把数据拆解并分类为一个个独立的车牌数字图片,例如Cars432应该被标注为DL49AK49
,拆分为8个独立的小图片,分别为D/1.png
、L/1.png
、4/1.png
、9/1.png
、A/1.png
、K/1.png
、4/2.png
、9/2.png
。
表示字母D的图片保存在D
文件夹下,表示数字1的图片保存在1
文件夹下。
分类完成之后二值化(最后二值化是因为有些字母或数字二值化后难以分辨)
这样拆解完400+张照片后,假设每张照片有6个小图片,那么我们就有2400+张小图片。平均分布在26个字母和10个数字上,每个字母和数字大概有80张图片。
通过人工标注,检查这些小图片是否正确,如果不正确,可以移动到对应的文件夹,或者删除错误的图片。
另外我们从images文件夹中,选几张图片,标注后放在test文件夹下,用于测试模型的准确度。
训练模型
解压后,我们当前的文件夹应该是这样的,
├── archive
│ ├── class_0
│ │ ├── 0_1.png
│ │ ├── 0_2.png
│ │ ├── ...
│ ├── class_1
│ │ ├── 1_1.png
│ │ ├── 1_2.png
│ │ ├── ...
│ ├── ...
├── annotations
│ ├── Cars0.xml
├── test
├── split_images.py
├── train_model.py
├── predict.py
现在其他文件夹都是过程文件
,archive文件夹是结果文件
,里面存放了所有分类好的车牌图片。
我们可以通过下方代码模板训练模型,至少完成一种。
import split_images
def model(model_name:str,img_path:str) -> str:
if model_name == 'model1':
return '识别结果1'
elif model_name == 'model2':
return '识别结果2'
elif model_name == 'model3':
return '识别结果3'
sklearn部分的参考代码(需要封装为函数):
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
# 加载数据集
digits = datasets.load_digits()
# 数据预处理
X = digits.data
y = digits.target
# 分割数据集为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 数据标准化
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)
# 创建 KNN 分类器
knn = KNeighborsClassifier(n_neighbors=3)
# 训练模型
knn.fit(X_train, y_train)
# 预测测试集结果
y_pred = knn.predict(X_test)
# 计算预测准确率
accuracy = accuracy_score(y_test, y_pred)
print('Accuracy: ' + str(accuracy))
效果展示
我们可以通过下方代码,将模型应用到新的图片上。(固定格式,无需修改)
import train_model
import os
for test_img in os.listdir('test'):
test_img_path = os.path.join('test',test_img)
print(test_img,':',end='')
for i in ['model1','model2','model3']:
model = train_model.model(i,test_img_path)
print(model,'|',end='')
print()
运行结果应该是这样的:
DZ17YXR.png :识别结果1 |识别结果2 |识别结果3 |
PGMN112.png :识别结果1 |识别结果2 |识别结果3 |
由于本项目侧重考察工程能力,因为不添加准确度判断,所以只要能够运行即可。