文章目录
一、引言
对于制造业而言,质量检测是非常重要的一环。传统的人工检测方法费时费力,且误检率较高。随着机器视觉的发展与应用,机器检测的高效、可靠性愈来愈受到制造业的青睐。
具体到制笔行业,由于其零配件较小,人工质检的误检率尤为严重,寻求机器视觉的解决方案更为迫切。
本文从笔帽硅珠检测方向出发,探讨应用深度学习算法的可行性。
二、现有方案
目前采用的是传统机器视觉方案,主要采用的方法是预先设置标准模板,然后将采集到的图片选择感兴趣区域进行模板匹配,计算与模板的相似度;计算采集图像中心位置的平均亮度;比如相似度大于0.9,以及平均亮度在160~200之间的判定为良品,其余的判定为不良品。
2.1、现有方案存在的问题
1.基于算法局限性,对环境要求较高,如相机像素精度、光照强度等,导致项目成本升高
2.硅珠分为米黄、白色两种;每种硅珠对应多种颜色笔帽;笔帽又分为透明及不透明。以上多种情况组合,同光源下采集到的图像亮度偏差较大,模板不具有泛化能力,故需要针对不同情况分别设定模板,并进行参数微调,生产时需要对应选取。操作方式不够友好。
3.由于实际生产环境的复杂性,工台震动、光源强度变化是必须考虑的问题。现有方案在遇到相机镜头发生偏移,或者光源变化的情况下,就需要重新设定模板,进行校准。后期维护成本较高。
三、基于深度学习算法的任务分析
硅珠检测项目作为一个监督学习分类任务,首先是要确定分为几类。
根据产线实际生产情况考察,将它作为一个四分类任务,labels分别为为:
- 0——良品
- 1——有硅珠但歪斜的不良品
- 2——无硅珠的不良品
- 3——空座
四、分类模型常用评判指标
先看下混淆矩阵的概念,以下:
- P代表真实正(阳性)样本(Positive)
- N代表真实负(阴性)样本(Negative)
- P ^ \widehat{P} P 代表预测正(阳性)样本
- N ^ \widehat{N} N 代表预测负(阴性)样本
\ | P ^ \widehat{P} P | N ^ \widehat{N} N |
---|---|---|
P | TP | FN |
N | FP | TN |
- 真阳性(True Positive,TP)代表实际为正样本,且预测结果也为正样本
- 假阳性(False Positive,FP)代表实际为负样本,但预测结果为正样本
- 假阴性(False Negative,FN)代表实际为正样本,但预测结果为负样本
- 真阴性(True Negative,TN)代表实际为负样本,且预测结果也为负样本
如何评判一个模型是好是坏,是非常重要的。对于分类任务常见的评价指标有准确率(Accuracy
)、精确率(Precision
)、召回率(Recall
)、F1 score
、ROC曲线(Receiver Operating Characteristic Curve
)等。
- 准确率(
Accuracy
):
分类正确的样本占总样本的比例
A c c u r a c y = T P + T N T P + F P + F N + T N Accuracy = \frac{TP + TN}{TP + FP + FN + TN} Accuracy=TP+FP+FN+TNTP+TN
- 精确率(
Precision
):预测为正样本中实际也为正样本的比例
P r e c i s i o n = T P T P + F P Precision=\frac{TP}{TP+FP} Precision=TP+FPTP
- 召回率(
Recall
):实际正样本中被预测为正样本的比例
R e c a l l = T P T P + F N Recall=\frac{TP}{TP+FN} Recall=TP+FNTP
F1-Score
:精确率和召回率的调和平均值
F 1 = 2 ∗ P r e c i s i o n ∗ R e c a l l P r e c i s i o n + R e c a l l F1=\frac{2*Precision*Recall}{Precision+Recall} F1=Precision+Recall2∗Precision∗Recall
Precision体现了模型对负样本的区分能力,Precision越高,模型对负样本的区分能力越强;
Recall体现了模型对正样本的识别能力,Recall越高,模型对正样本的识别能力越强。
F1 score是两者的综合,F1 score越高,说明模型越稳健。F1 Score最大值是1,最小值为0.
F1 Score是 Fβ的特殊形式:
F β = ( 1 + β 2 ) ∗ P r e c i s i o n ∗ R e c a l l ( β 2 ∗ P r e c i s i o n ) + R e c a l l F_{β}=(1+β^2)*\frac{Precision*Recall}{(β^2*Precision)+Recall} Fβ=(1+β2)∗(β2∗Precision)+RecallPrecision∗Recall
当β=1时,即为F1 Score,同等对待Precision和Recall;
当β=0时,F0=Precision;
当β=
+
∞
+\infty
+∞时,F
+
∞
+\infty
+∞=Recall;
可以调用sklearn.metrics
库中的accuracy_score,recall_score,precision_score,f1_score
五、本项目评判指标
由于在工业生产中,精确率与召回率同等重要,故评判标准选择F1 Score
六、数据预处理
6.1、灰度图转化
由于本例中工业相机采集到的图像为灰度图(既channel为1,对比RGB图像,channel图像为3),故无需进行灰度图转化。(灰度图转化是常用的图像增强方法之一,提出图像颜色对结果的影响,同时也可以降低计算成本)
6.2、图像剪裁
基予产线的现有设备情况,工业相机采集到的图像一次包含两支笔帽,故处理数据的时候需要将这两只笔帽裁剪出来,分别进行预测
6.3、图像Resize
由于采集到的原图size为5496*3692,19.2MB大小,size过大,对算力要求过高,故需要将裁剪后的图像resize为模型要求的输入大小(一般为244*244);之所以选择裁剪后再resize,这样既一定程度上保证了图像精度,又降低了对算力的要求
6.4、数据集清洗及标注
使用百度EasyDL的EasyData对数据集进行清洗(去重),以及标注
可选的数据清洗手段包括去近似、去模糊、剪裁、旋转、镜像
,这里我们只选择去近似
,由于使用场景单一,故大多数图像比较相似,故先相似度在0.9以上的仅保留一张
可以看到出入的数据集有2646张图片,经过清洗后还有2625张图片,去重效果不明显,说明选取的相似度偏大,故重新清洗
相似度设为0.7,去重后还剩余1943张图片;
相似度设为0.5,去重后还剩余1498张图片;
相似度设为0.4,去重后还剩余898张图片;
相似度设为0.3,去重后还剩余178张图片;
最终选用相似度0.4时,处理后的数据集
使用EasyData在线标注功能进行图像标注分类
标注完成之后使用数据导出功能将数据集导出到本地,待使用
6.5、数据分布平衡化
在学术中,使用的大部分数据集都是平衡的。但实际中完全不是这样。还是以医学为例,将一个健康的人误诊为患病带来的伤害相对的有限的,但是将一个患病的人误诊为健康将使病人得不到治疗而延误病情,可能为此付出生命的代价。
同理,由于目前制笔工艺精良,不良品很少,不良品的采样就变得困难。但不良品的流出又是企业不能接受的。那么就需要采取一定措施处理数据不平衡的问题。
采样数据分布图如下:
针对采样不平衡问题,本例中通过修改图像亮度值进行数据集扩充
采用PIL.ImageEnhance
模块的Brightness
类进行亮度调整
from PIL import Image,ImageEnhance
img =Image.open(image_path)
# 调整图像亮度为原图的0.5倍,并生成一张新的图像
img1 = ImageEnhance.Brightness(img).enhance(0.5)
以下图像在原图基础上,将亮度值改为0.2,0.4,0.6,0.8,1.2,1.4,1.6,1.8
倍,进行对比
针对label为1、3的数据集进行过采样,将数据集扩充8倍(亮度分别为原图像的0.2,0.4,0.6,0.8,1.2,1.4,1.6,1.8
倍);
针对label为2的数据集进行过采样,将数据集扩充16倍(亮度分别为原图像的0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.1,1.2,1.3,1.4,15,1.6,17,1.8
倍);
扩充完数据集分布如下所示:
6.6、数据集划分
按照8:2比例随机划分训练集和验证集,并确保各label划分前后比例保持一致;
处理后的数据集文件夹tree结构:
七、训练平台及模型选取
- 本项目基于百度AIStudio平台进行模型训练,默认Python3.7版本,PaddlePaddle1.7.1
- 本项目采用的预训练模型为旷视科技在2018年提出的轻量级图像分类模型shufflenet_v2_imagenet,该模型通过
pointwise group convolution
和channel shuffle
两种方式,在保持精度的同时大大降低了模型的计算量。该PaddleHub Module
结构为ShuffleNet V2
,基于ImageNet-2012
数据集训练,接受输入图片大小为224 x 224 x 3 - 需要安装paddlehub
pip install paddlehub
八、模型训练
8.1、加载预训练模型
module_shufflenet_v2 = hub.Module(name="shufflenet_v2_imagenet")
8.2、加载数据集
class BeadsDataset(BaseCVDataset):
def __init__(self):
# 数据集存放位置
self.dataset_dir = "dataset"
super(BeadsDataset, self).__init__(
base_path=self.dataset_dir,
train_list_file="train_list.txt",
validate_list_file="validate_list.txt",
test_list_file="test_list.txt",
label_list_file="label_list.txt",
)
dataset = BeadsDataset()
8.3、生成数据读取器
data_reader = hub.reader.ImageClassificationReader(
image_width=module_shufflenet_v2.get_expected_image_width(), #预训练模型对应的图片尺寸宽度
image_height=module_shufflenet_v2.get_expected_image_height(), #预训练模型对应的图片尺寸高度
images_mean=module_shufflenet_v2.get_pretrained_images_mean(), #预训练模型对应的图片均值
images_std=module_shufflenet_v2.get_pretrained_images_std(), #预训练模型对应的图片方差
dataset=dataset)
8.4、优化策略和运行配置
config = hub.RunConfig(
log_interval=20, # 进度日志打印间隔,默认每10个step打印一次;
eval_interval=50, # 模型评估的间隔,默认每10个step评估一次验证集;
use_cuda=False, # 是否使用GPU训练,默认为False;
checkpoint_dir="shufflenet_v2_finetune", # 模型checkpoint保存路径;
num_epoch=4, # Fine-tune的轮数;
batch_size=16, # 训练的批大小,如果使用GPU,请根据实际情况调整batch_size;
strategy=hub.finetune.strategy.DefaultFinetuneStrategy()) # Fine-tune优化策略;
PaddleHub提供了许多优化策略,如AdamWeightDecayStrategy
、ULMFiTStrategy
、DefaultFinetuneStrategy
等,详细信息参见策略。
8.5、组建Finetune Task
# 获取module的上下文环境,包括输入和输出的变量,以及Paddle Program;
input_dict, output_dict, program = module_shufflenet_v2.context(trainable=True)
# 从输出变量中找到特征图提取层feature_map;
feature_map = output_dict["feature_map"]
# 模型的输入tensor的顺序
feed_list = [input_dict["image"].name]
task = BeadsClassifierTask(
data_reader=data_reader, # 定义的数据读取器
feed_list=feed_list, # 模型的输入tensor
feature=feature_map, # 特征提取向量
num_classes=dataset.num_labels, # 数据集对应的labels
metrics_choices=['f1','precision','recall','acc'], # 评估指标,这里f1_score作为模型训练的评估指标,precision、recall、acc只做计算,不参与模型训练
config=config) # 配置策略
需要说明的是BeadsClassifierTask
是自定义的Task类,重写了_calculate_metrics
方法,添加了precision、recall
评估指标
from sklearn.metrics import accuracy_score,f1_score,recall_score,precision_score
......
for metric in self.metrics_choices:
if metric == "acc":
avg_acc = acc_sum / run_examples
scores["acc"] = avg_acc
elif metric == "f1":
f1 = f1_score(all_labels,all_infers,average='weighted')
# f1 = calculate_f1_np(all_infers, all_labels)
scores["f1"] = f1
elif metric == "matthews":
matthews = matthews_corrcoef(all_infers, all_labels)
scores["matthews"] = matthews
elif metric == "precision":
precision = precision_score(all_labels,all_infers,average='weighted')
scores["precision"] = precision
elif metric == "recall":
recall = recall_score(all_labels,all_infers,average='weighted')
scores["recall"] = recall
else:
raise ValueError("Not Support Metric: \"%s\"" % metric)
8.6、迁移学习Finetune
task.finetune_and_eval()
finetune之后会生成shufflenet_v2_finetune
文件夹,里面的best_model
既为模型训练中f1 score
得分最高时对应的checkpoint
九、预测
9.1、Funetune后的模型转化为PaddleHub Module
- 新建必要目录与文件
创建一个finetuned_model_to_module
的目录,并在finetuned_model_to_module
目录下分别创建__init__.py
、module.py
,其中:
文件名 | 用途 |
---|---|
__init__.py | 空文件 |
module.py | 主模块,提供Module的实现代码 |
ckpt文件 | 利用PaddleHub Fine-tune得到的ckpt文件夹,其中必须包含best_model 文件(将上一步生成的 shufflenet_v2_finetune 文件夹移动到此目录下即可) |
- 编写Module处理代码(
module.py
)
import os
import sys
sys.path.append('../')
from Silicon_beads_task import BeadsClassifierTask
import numpy as np
from paddlehub.common.logger import logger
from paddlehub.module.module import moduleinfo, serving
import paddlehub as hub
# cpu有几核就设置几个
# 如果有GPU资源可以设置CUDA
os.environ['CPU_NUM'] = str(4)
'''
'继承了hub.Module的类存在,该类负责实现预测逻辑,并使用moduleinfo填写基本信息。
'当使用hub.Module(name="shufflenet_v2_finetune")加载Module时,PaddleHub会自动创建Shufflenet_V2_Finetuned的对象并返回
'
'''
@moduleinfo(
name="Silicon_beads_quality_inspection",
version="1.0.0",
summary="Silicon Beads Quality Inspection which was fine-tuned based shufflenet_v2_imagenet.",
author="anonymous",
author_email="",
type="cv/imageClassification_model")
class BeadsFinetuned(hub.Module):
'''
'行类的初始化不能使用默认的__init__接口,而是应该重载实现_initialize接口。
'对象默认内置了directory属性,可以直接获取到Module所在路径。
'使用Fine-tune保存的模型预测时,无需加载数据集Dataset,即Reader中的dataset参数可为None
'''
def _initialize(self,
ckpt_dir="shufflenet_v2_finetune",
num_class=4,
num_epoch=4,
log_interval=20,
eval_interval=50,
use_gpu=False,
batch_size=16):
# 对象默认内置了directory属性,可以直接获取到Module所在路径。
self.ckpt_dir = os.path.join(self.directory, ckpt_dir)
self.num_class = num_class
self.params_path = os.path.join(self.ckpt_dir, 'best_model')
if not os.path.exists(self.params_path):
logger.error(
"%s doesn't contain the best_model file which saves the best parameters as fietuning."
)
exit()
# Load Paddlehub shufflenet_v2_imagenet pretrained model
self.module = hub.Module(name="shufflenet_v2_imagenet")
input_dict, output_dict, program = self.module.context(trainable=True)
# self.vocab_path = self.module.get_vocab_path()
# For shufflenet_v2_imagenet, it use sub-word to tokenize chinese sentence
# If not ernie tiny, sp_model_path and word_dict_path should be set None
reader = hub.reader.ImageClassificationReader(
image_width=self.module.get_expected_image_width(),
image_height=self.module.get_expected_image_height(),
images_mean=self.module.get_pretrained_images_mean(),
images_std=self.module.get_pretrained_images_std(),
dataset=None)
# Setup runing config for PaddleHub Finetune API
config = hub.RunConfig(
use_cuda=use_gpu,
num_epoch=num_epoch,
checkpoint_dir=self.ckpt_dir,
batch_size=batch_size,
log_interval=log_interval,
eval_interval=eval_interval,
strategy=hub.finetune.strategy.DefaultFinetuneStrategy())
img = input_dict["image"]
feature_map = output_dict["feature_map"]
feed_list = [img.name]
metrics_choices = ['f1','precision','recall','acc']
# Define a classfication finetune task by PaddleHub's API
self.cls_task = BeadsClassifierTask(
data_reader=reader,
feature=feature_map,
feed_list=feed_list,
num_classes=self.num_class,
config=config,
metrics_choices=metrics_choices)
# 如果不需要提供PaddleHub Serving部署预测服务,则可以不需要加上serving修饰
# @serving
def predict(self, data, return_result=False, accelerate_mode=True):
"""
Get prediction results
"""
run_states = self.cls_task.predict(
data=data,
return_result=return_result,
accelerate_mode=accelerate_mode)
results = [run_state.run_results for run_state in run_states]
prediction = []
for batch_result in results:
# get predict index
batch_result = np.argmax(batch_result, axis=2)[0]
batch_result = batch_result.tolist()
prediction += batch_result
return prediction
9.2、调用自定义Moudle进行预测
- 调用方法一
cmd
进入finetuned_model_to_module
所在目录,执行:
hub install finetuned_model_to_module
安装成功后有如下显示:
Successfully installed Silicon_beads_quality_inspection
import paddlehub as hub
beads_finetuned = hub.Module("Silicon_beads_quality_inspection")
data = ['E:/pycharm_workspace/Silicon_beads_quality_inspection/dataset/test/3/1.png',
'E:/pycharm_workspace/Silicon_beads_quality_inspection/dataset/test/3/2.png',
'E:/pycharm_workspace/Silicon_beads_quality_inspection/dataset/test/3/3.png',
'E:/pycharm_workspace/Silicon_beads_quality_inspection/dataset/test/3/4.png']
predictions = beads_finetuned.predict(data=data)
for index, text in enumerate(data):
print("%s\tpredict=%s" % (data[index][0], predictions[index]))
- 调用方法二
直接通过Hub.Module(directory=...)
加载
beads_finetuned = hub.Module(directory='finetuned_model_to_module')
data = ['E:/pycharm_workspace/Silicon_beads_quality_inspection/dataset/test/3/1.png']
predictions = beads_finetuned.predict(data=data)
for index, text in enumerate(data):
print("%s\tpredict=%s" % (data[index][0], predictions[index]))
- 调用方法三
将finetuned_model_to_module
作为路径加到环境变量中,直接加载ERNIETinyFinetuned
对象.(此方法笔者没有验证)
export PYTHONPATH=finetuned_model_to_module:$PYTHONPATH
from finetuned_model_to_module.module import BeadsFinetuned
data = ['E:/pycharm_workspace/Silicon_beads_quality_inspection/dataset/test/3/1.png']
predictions = beads_finetuned.predict(data=data)
for index, text in enumerate(data):
print("%s\tpredict=%s" % (data[index][0], predictions[index]))
十、参考资料
1.【数字图像处理系列二】亮度、对比度、饱和度、锐化、分辨率
2.【ImageEnhance】+【PIL模块】如何利用python实现图像增强
3.深度学习中数据集分布不平衡问题的解决方法
4.图像分类以及模型库
5.markdown中公式编辑教程
6.有道云Markdown输入数学公式
7.PaddleHub适配自定义数据完成FineTune
8.PaddleHub 图像分类
9.PaddleHub: 自定义Task
10.PaddleHub超参优化——图像分类
11.Fine-tune保存的模型如何转化为一个PaddleHub Module