本项目来源于Kaggle上一个钢材表面缺陷检测竞赛,这也是一个非常好的将深度学习应用于传统工业材料检测的案例。本项目将使用百度飞桨PaddleX深度学习算法套件进行开发,全流程剖析整个实现过程。
钢材是现代最重要的建筑材料之一。刚材结构建筑能够抵抗自然和人为磨损,这使得这种材料在世界各地随处可见。在所有钢材加工环节中,平板钢的生产工艺特别精细。从加热、轧制,再到干燥和切割,需要几台机器协同操作,其中一个重要环节就是利用高清摄像头捕获的图像对加工环节中的钢材进行缺陷自动检测。Kaggle上举办的这场钢材缺陷竞赛希望各位参赛者利用机器学习来改进钢板表面缺陷检测算法精度,提高钢铁生产自动化程度。
注意数据的标注形式,一共2列:第1列:ImageId_ClassId(图片编号+类编号);第2例EncodedPixels(图像标签)。注意这个图像标签和我们平常遇到的不一样,平常的是一个mask灰度图像,里面有许多数字填充,背景为0,但是这里,为了缩小数据,它使用的是像素“列位置-长度”格式。举个例子:
我们把一个图像(h,w)flatten(注意不是按行而是按列),29102 12 29346 24 29602 24表示从29102像素位置开始的12长度均为非背景,后面以此类推。这就相当于在每个图像上画一条竖线。
接下来统计有无缺陷及每类缺陷的图像数量:
class_dict = defaultdict(int)
kind_class_dict = defaultdict(int)
no_defects_num = 0
defects_num = 0for col in range(0, len(train_df), 4):img_names = [str(i).split("_")[0] for i in train_df.iloc[col:col+4, 0].values]if not (img_names[0] == img_names[1] == img_names[2] == img_names[3]):raise ValueErrorlabels = train_df.iloc[col:col+4, 1]if labels.isna().all():no_defects_num += 1else:defects_num += 1kind_class_dict[sum(labels.isna().values == False)] += 1for idx, label in enumerate(labels.isna().values.tolist()):if label == False:class_dict[idx+1] += 1
输出有、无缺陷的图像数量
print("无缺陷钢板数量: {}".format(no_defects_num))
print("有缺陷钢板数量: {}".format(defects_num))
输出结果为:
无缺陷钢板数量: 5902
有缺陷钢板数量: 6666
接下来对有缺陷的图像进行分类统计:
import seaborn as sns
import matplotlib.pyplot as pltfig, ax = plt.subplots()
sns.barplot(x=list(class_dict.keys()), y=list(class_dict.values()), ax=ax)
ax.set_title("the number of images for each class")
ax.set_xlabel("class")
plt.show()
print(class_dict)
输出结果如下:
以上分析可以帮助我们快速掌握数据基本属性,初步了解项目难度。在实际比赛时这些数据分析是非常重要的,通过这些数据分析再挖掘整理成重要的“线索”,然后改进算法模型,往往可以取得出其不意的好效果。
从前面的数据分析中我们看到钢板图像的每个像素只属于1种缺陷类别(或者没缺陷),由于我们需要定位出钢板缺陷的精细区域,因此可以把这个任务看作是一个语义分割任务,即按照像素级别精度判断每个像素所属的缺陷类别。
接下来我们就可以使用语义分割算法进行训练和验证。本项目采用百度飞桨PaddleX算法套件来快速训练。PaddleX是飞桨全流程开发工具,集飞桨核心框架、模型库、工具及组件等深度学习开发所需全部能力于一身,打通深度学习开发全流程。简单来说,使用PaddleX有两个明显的好处:
PaddleX安装方法参考官网。本项目使用PaddleX v2.0.0版本进行实现。
为了能够使用PaddleX进行语义分割训练,我们首先要对数据集格式进行转化。PaddleX语义分割算法要求数据集按照如下方式进行组织:
原图均放在同一目录,如JPEGImages
,标注的同名png文件均放在同一目录,如Annotations
。示例如下:
MyDataset/ # 语义分割数据集根目录
|--JPEGImages/ # 原图文件所在目录
| |--1.jpg
| |--2.jpg
| |--...
| |--...
|
|--Annotations/ # 标注文件所在目录
| |--1.png
| |--2.png
| |--...
| |--...
JPEGImages目录存放的是原文件图像(jpg格式),Annotations存放对应的标注文件图像(png格式) 。这里的标注图像,如1.png,为单通道图像,像素标注类别需要从0开始递增, 例如0, 1, 2, 3表示4种类别(一般0表示background背景),标注类别最多255个类别(其中像素值255不参与训练和评估)。
数据转换本身并不难,在前面可视化数据的时候已经将缺陷区域显示了出来,因此,数据转化时只需要将缺陷对应的灰度值填入mask即可。
将下载下来的数据集解压后放置在当前项目根目录下,然后执行下面的代码即可:
python prepare_dataset.py
执行完后在当前根目录下会生成PaddleX标准的数据集,名为steel。
执行完以后在生成的steel文件夹下面创建一个标签说明文件,文件名为labels.txt,内容如下:
0
1
2
3
4
这个文件用来指明当前到底有哪些像素标签值(本项目共4种缺陷,对应标签为1、2、3、4,这里的0标签表示背景)
转换完的数据集也可以直接从ai studio上进行下载。
在模型进行训练时,我们需要划分训练集,验证集和测试集,可直接使用paddlex命令将数据集随机划分。本项目将训练集、验证集和测试集按照8.5:1:0.5的比例划分。 PaddleX中提供了简单易用的API,方便用户直接使用进行数据划分。具体命令如下:
paddlex --split_dataset --format SEG --dataset_dir steel --val_value 0.1 --test_val 0.05
数据文件夹切分前后的状态如下:
steel/ steel/├── Annotations/ --> ├── Annotations/├── JPEGImages/ ├── JPEGImages/├── labels.txt ├── labels.txt├── test_list.txt├── train_list.txt├── val_list.txt
执行上面命令行,会在steel下生成train_list.txt, val_list.txt, test_list.txt,分别存储训练样本列表,验证样本列表,测试样本列表。
PaddleX提供了丰富的视觉模型,在语义分割中提供了DeepLabV3、UNET、HRNET和FastSCNN系列模型。在本项目中采用重量级HRNET为分割模型进行钢板缺陷检测。
模型训练后保存在output文件夹,如果要使用PaddleInference进行部署需要导出成静态图的模型,运行如下命令,会自动在output文件夹下创建一个inference_model
的文件夹,用来存放导出后的模型。
paddlex --export_inference --model_dir=output/hrnet/best_model --save_dir=output/inference_model
模型部署采用了PaddleX提供的C++ inference部署方案,在该方案中提供了C#部署Demo,用户可根据实际情况自行参考修改。