Skip to main content

模拟算法大赛(考核)

完成机器学习的基础学习之后,我们通常会通过参加一些算法大赛来检验自己的学习成果。

譬如:

  • 阿里云天池算法大赛:阿里巴巴集团主办的一系列比赛,涵盖了许多不同的领域和问题,如推荐系统、图像识别、预测等。

  • Kaggle竞赛:一个国际平台,我们之前火山下的文明一课就是Kaggle。

  • CCF大数据与计算智能大赛:由中国计算机学会(CCF)主办,涵盖了许多实际问题,如交通预测、社交网络分析等。

参加算法大赛可以帮助我们更好地理解机器学习的应用,获取一些实际问题的解决方案,提高自己的编程能力和数据分析能力。另外,这些算法大赛通常会提供大量的数据集和评测标准,我们需要根据数据集训练模型,并提交预测结果,最终根据评测标准来评价模型的性能。

tip

本项目仅评价工程能力,即项目完成度。

以日常中交通违法为例,交管在路口设有自动抓拍系统。

这个系统利用图片的相似性比较,如果发现有车辆闯红灯,或者违法停车,就会自动拍摄车辆的照片。

同时记录时间、摄像头位置等信息。

系统识别车牌。

然后通过车牌号码在信息库里获取车主的号码,并自动发送短信。(如果我们自己需要完整的模拟整个流程,可以改成电子邮箱)

【交管通知】尊敬的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文件,获取车牌的图像信息。

tip

如果这个数据集没有标注,我们可以通过判断图片中矩形的位置,来获取车牌的位置信息。具体方法与火山下的文明一课中的方法类似。

处理数据集

根据下方代码提示补全,未提及内容已经补全,需要补全的内容已经标注。

正确处理之后的代码,可以将整个车牌的图像裁剪出来,保存到cropped_images目录下。

split_images.py
# 导入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.pngL/1.png4/1.png9/1.pngA/1.pngK/1.png4/2.png9/2.png

表示字母D的图片保存在D文件夹下,表示数字1的图片保存在1文件夹下。

分类完成之后二值化(最后二值化是因为有些字母或数字二值化后难以分辨)

这样拆解完400+张照片后,假设每张照片有6个小图片,那么我们就有2400+张小图片。平均分布在26个字母和10个数字上,每个字母和数字大概有80张图片。

通过人工标注,检查这些小图片是否正确,如果不正确,可以移动到对应的文件夹,或者删除错误的图片。

另外我们从images文件夹中,选几张图片,标注后放在test文件夹下,用于测试模型的准确度。

tip

这个过程非常重要,直接影响模型的准确度。

课后有时间可以小组合作,体验下这个过程,把所有图片全部拆解完后的模型准确度应该会有一个较大的提升。

部分拆解好的图片

训练模型

解压后,我们当前的文件夹应该是这样的,

├── 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文件夹是结果文件,里面存放了所有分类好的车牌图片。

我们可以通过下方代码模板训练模型,至少完成一种。

train_model.py
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))

效果展示

我们可以通过下方代码,将模型应用到新的图片上。(固定格式,无需修改)

predict.py
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 |

由于本项目侧重考察工程能力,因为不添加准确度判断,所以只要能够运行即可。

参考答案