工业缺陷检测实训平台开发指导书
产品版本V4.0
文档版本02
版权声明
版权所有©EDUCN 2024。保留一切权利。
未得到本公司的书面许可,任何人不得以任何方式或形式对本手册内的任何部分进行复制、摘录、备份、修改、传播、翻译成其他语言,将其全部或部分用于商业用途。
免责声明
本手册内容依据现有信息制作,由于产品版本升级或其他原因,其内容有可能变更。EDUCN保留在没有任何通知或者提示的情况下对手册内容进行修改的权利。本手册仅作为使用指导,海云捷迅在编写本手册时已尽力保证其内容准确可靠,但并不确保手册内容完全没有错误或遗漏,本手册中的所有信息也不构成任何明示或暗示的担保。
网址:https://www.awcloud.com
邮箱:support@awcloud.com
前言
读者对象
本手册适合下列人员阅读:
图形界面元素引用约定
格式意义“”带双引号“”的格式表示各类界面控件名称、数据输入或内容引用等,如单击“确定”。>多级菜单用“ > ”隔开。如选择“资源 > 物理机”,表示选择“资源”菜单下的“物理机”子菜单项。标志约定
本手册还采用各种醒目标志来表示在操作过程中应该特别注意的地方,这些标志的意义如下:
标志意义粗体命令行以粗体样式标识。举例说明
本手册举例说明部分的端口类型同实际可能不符,实际操作中需要按照各产品所支持的端口类型进行操作。
本手册部分举例的显示信息中可能含有其它产品系列的内容(如产品型号、描述等),具体显示信息请以实际使用的设备信息为准。
修改记录
修订记录累积了每次文档更新的说明。最新版本的文档包含以前所有版本文档的更新内容。
文档版本01(2024-01-23):第一次正式发布。
1 产品概述
工业缺陷检测实训平台是以工业互联网铝片表面缺陷检测应用为背景,面向高校人工智能、FPGA等技术的实训产品。为贴合真实应用场景,通过加入专业的工业相机、光源、传输履带、机械臂形成一套完整的智能检测应用系统,实现基于FPGA的数据推理、机械臂自动分拣和检测数据可视化展示等功能。
产品利用深度学习算法SSD和铝板表面缺陷数据集实现,并使用FPGA来运行模型应用可检测铝表面褶皱、脏污等缺陷类型,用户可二次开发增加缺陷种类。同时,结合教学内容与实际产业、岗位需求,该实训产品提供了丰富的实验课程及配套资源,让学生快速掌握相关技能知识,加强对行业需求的理解和认知。
2 功能架构
图 2-1 系统结构
- 传输履带:模拟工厂铝材产线履带传送过程,实现被检测铝片的传输。
- 工业相机:采用600万像素1/1.8"CMOS USB3.0工业面阵相机,通过USB传输方式与控制终端设备实现连接,实现被检测铝片的图像采集功能。
- 光源:采用亮度可调节条形光源,通过调节光源亮度,辅助实现铝片表现图像采集。
- 机械臂:采用AI视觉机械臂解决方案,抓手为吸盘结构。当平台检测到有缺陷的铝片时,机械臂自动从传送带上将有缺陷铝片抓取出来,实训缺陷铝片的自动分拣功能。
- 控制终端:用于控制缺陷检测的整体流程,包括基于FPGA的铝片缺陷检测服务、缺陷检测应用系统、机械臂控制服务。
- FPGA开发板:在FPGA开发板上运行铝片缺陷检测推理服务,实现对铝片缺陷种类及位置的判断,并将结果反馈给控制终端。
- 显示器:主要包括相机的视频流的实时显示,检测的铝片缺陷标记、数据的统计分析和机械臂的控制等几个部分。
- 报警器:当系统检测到缺陷铝片后通过报警器进行报警提示。
说明:开发板和机械臂ip配置为172.16.68.0/23网段。其中机械臂默认ip为:172.16.68.111/23,FPGA开发板的ip默认为172.16.68.111/23。
3 功能介绍
- 基于FPGA的缺陷检测:FPGA设备利用SSD深度学习算法和铝片表面缺陷数据集训练的模型进行推理。当相机采集到视频流图片后,会发送给推理服务获取推理结果,并将缺陷情况记录到数据库,同时对外提供检测情况的统计分析数据接口。
- 机械臂控制:系统采用5轴吸盘结构的机械臂,可通过平台实现机械臂的左右转向、抓取/释放等功能控制,同时可以结合缺陷检测应用实现缺陷铝片的自动分拣功能。
- 铝片表面缺陷检测可视化展示:通过平台页面实时展示当前检测铝片的原始图片和视频流图像,并对有缺陷的铝片进行缺陷点位标记和种类标注。对检测的历史数据进行精准的质量统计,实现产品检测良品率、检测数量的统计,有效保证产品质量。
- 传输履带及光源:为了模拟工厂铝材缺陷检测真实场景,系统配套的传输履带的速度和环形光源亮度均可调节。
4 模型产出
4.1 环境准备
在进行模型训练前,需要先准备好模型训练环境,这里选用的PaddleDetection项目进行的模型训练。
环境要求:
- ubuntu 18.04
- python == 3.6(如果条件不满足,可以考虑使用conda安装)
- CUDA(must include the cudnn) Version: 11.0
依赖安装:- 1. tar xf PaddelDetection.tar.gz
- 2. cd PaddleDetection
- 3. pip install -r requirements.txt
- 4. pip install paddlepaddle-gpu==2.1.3.post110 paddleslim==2.1.0 paddlelite==2.10 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html
- 5. pip install torch==1.7.1+cu110 -f https://download.pytorch.org/whl/cu110/torch_stable.html
- 6. # 验证paddlepaddle的安装
- 7. # python -c "import paddle;paddle.utils.run_check()"
- 8. # 如果出现错误,极有可能是由于cuda版本有问题
复制代码 应用paddlepaddle补丁:- 1. cd <paddlepaddle install dir>
- 2. # for example: cd /root/anaconda3/envs/paddle-2.1/lib/python3.6/site-packages/paddle
- 3. patch -p0 < <install dir>/paddle.patch
- 4. # for example, if the paddle.patch is in /root
- 5. # patch -p0 < /root/paddle.patch
复制代码 安装PaddleDetection:- 1. python setup.py install
复制代码 4.2 数据集准备
常用的制作数据集的软件有labelme、labelimg等,这里主要介绍labelimg的使用。需要用到ubuntu桌面。
安装labelimg:- 1. git clone https://github.com/tzutalin/labelImg.git
- 2. cd labelImg
- 3. apt-get install pyqt5-dev-tools
- 4. pip3 install --upgrade pip
- 5. pip3 install -r requirements/requirements-linux-python3.txt
- 6. make qt5py3
复制代码 labelimg使用:
安装完labelimg后,执行下面命令打开labelimg软件:执行命令后,会自动弹出labelimg的界面,如下图所示:
其中:
- Open Dir:打开存放原文件的文件夹,JPEGImages文件夹。
- Change Save Dir:用于选择标签文件存放的位置,选择Annotations文件夹。
- 在view菜单栏中打开auto save mode格式,这个可以帮助我们自动的保存标记好的图片。
- 点击creat RectBox开始标记,在labelImg中输入标签,如果感觉这个比较难检测,可以点击右上角的difficult。
快捷键:W(创建方框),A(上一张),D(下一张)。
标注完成后会得到一些标签,示例如下:
xml文件里面包含标注好图片的信息:
- folder:图片所在目录
- filename:图片名称
- path:图片所在路径
- size:图片大小
- object:标注信息
- name:标注类别
- bndbox:标注框坐标
示例xml内容:- <folder>Images</folder>
- <filename>1.jpg</filename>
- <path>C:\Users\tianhui\Desktop\aluminum inspection\Images\1.jpg</path>
- <source>
- <database>Unknown</database>
- </source>
- <size>
- <width>640</width>
- <height>480</height>
- <depth>1</depth>
- </size>
- <segmented>0</segmented>
- <object>
- <name>zhen kong</name>
- <pose>Unspecified</pose>
- <truncated>0</truncated>
- <difficult>0</difficult>
- <bndbox>
- <xmin>233</xmin>
- <ymin>157</ymin>
- <xmax>254</xmax>
- <ymax>193</ymax>
- </bndbox>
- </object>
- <object>
- <pose>Unspecified</pose>
- <truncated>0</truncated>
- <difficult>0</difficult>
- <bndbox>
- <xmin>256</xmin>
- <ymin>180</ymin>
- <xmax>282</xmax>
- <ymax>209</ymax>
- </bndbox>
- </object>
- </annotation>
复制代码 数据集制作:
利用labelimg标注好数据集图片后,需要对标注的图片进行进一步处理,才能作为训练需要的数据集使用。
将图片与生成的xml文件分别存放在images和annotations目录,并创建一个ImageSet目录用来存放制作数据集时生成的临时文件,如下所示:- 1. my_dataset # 根目录
- 2. |-- annotations # xml文件目录
- 3. |-- xxx.xml # 生成的xml文件
- 4. |-- images # 图像目录
- 5. |-- xxx.jpg(png or other) # 图片
- 6. |...
- 7. |-- label_list.txt # 数据集的类别名称
- 8. |-- ImageSet # 用于生成对应txt文件临时目录
复制代码 使用dataset_processing_one.py对数据集进行第一步处理:- 1. import os
- 2. import random
- 3. trainval_percent = 0.95 # 训练集验证集总占比
- 4. train_percent = 0.9 # 训练集在trainval_percent里的train占比
- 5. xmlfilepath = './annotations'
- 6. txtsavepath = './images'
- 7. total_xml = os.listdir(xmlfilepath)
- 8. num = len(total_xml)
- 9. list = range(num)
- 10. tv = int(num * trainval_percent)
- 11. tr = int(tv * train_percent)
- 12. trainval = random.sample(list, tv)
- 13. train = random.sample(trainval, tr)
- 14. ftrainval = open('./ImageSet/trainval.txt', 'w')
- 15. ftest = open('./ImageSet/test.txt', 'w')
- 16. ftrain = open('./ImageSet/train.txt', 'w')
- 17. fval = open('./ImageSet/val.txt', 'w')
- 18. for i in list:
- 19. name = total_xml[i][:-4] + '\n'
- 20. if i in trainval:
- 21. ftrainval.write(name)
- 22. if i in train:
- 23. ftrain.write(name)
- 24. else:
- 25. fval.write(name)
- 26. else:
- 27. ftest.write(name)
- 28. ftrainval.close()
- 29. ftrain.close()
- 30. fval.close()
- 31. ftest.close()
复制代码 再使用dataset_processing_two.py对数据集进行进一步处理:- 1. import os
- 2. import re
- 3. devkit_dir = './'
- 4. output_dir = './'
- 5. def get_dir(devkit_dir, type):
- 6. return os.path.join(devkit_dir, type)
- 7. def walk_dir(devkit_dir):
- 8. filelist_dir = get_dir(devkit_dir, 'ImageSet')
- 9. annotation_dir = get_dir(devkit_dir, 'annotations')
- 10. img_dir = get_dir(devkit_dir, 'images')
- 11. trainval_list = []
- 12. train_list = []
- 13. val_list = []
- 14. test_list = []
- 15. added = set()
- 16. for _, _, files in os.walk(filelist_dir):
- 17. for fname in files:
- 18. print(fname)
- 19. img_ann_list = []
- 20. if re.match('trainval.txt', fname):
- 21. img_ann_list = trainval_list
- 22. elif re.match('train.txt', fname):
- 23. img_ann_list = train_list
- 24. elif re.match('val.txt', fname):
- 25. img_ann_list = val_list
- 26. elif re.match('test.txt', fname):
- 27. img_ann_list = test_list
- 28. else:
- 29. continue
- 30. fpath = os.path.join(filelist_dir, fname)
- 31. for line in open(fpath):
- 32. name_prefix = line.strip().split()[0]
- 33. print(name_prefix)
- 34. added.add(name_prefix)
- 35. ann_path = annotation_dir + '/' + name_prefix + '.xml'
- 36. print(ann_path)
- 37. img_path = img_dir + '/' + name_prefix + '.jpg'
- 38. assert os.path.isfile(ann_path), 'file %s not found.' % ann_path
- 39. assert os.path.isfile(img_path), 'file %s not found.' % img_path
- 40. img_ann_list.append((img_path, ann_path))
- 41. print(img_ann_list)
- 42. return trainval_list, train_list, val_list, test_list
- 43. def prepare_filelist(devkit_dir, output_dir):
- 44. trainval_list = []
- 45. train_list = []
- 46. val_list = []
- 47. test_list = []
- 48. trainval, train, val, test = walk_dir(devkit_dir)
- 49. trainval_list.extend(trainval)
- 50. train_list.extend(train)
- 51. val_list.extend(val)
- 52. test_list.extend(test)
- 53. with open(os.path.join(output_dir, 'trainval.txt'), 'w') as ftrainval:
- 54. for item in trainval_list:
- 55. ftrainval.write(item[0] + ' ' + item[1] + '\n')
- 56. with open(os.path.join(output_dir, 'train.txt'), 'w') as ftrain:
- 57. for item in train_list:
- 58. ftrain.write(item[0] + ' ' + item[1] + '\n')
- 59. with open(os.path.join(output_dir, 'val.txt'), 'w') as fval:
- 60. for item in val_list:
- 61. fval.write(item[0] + ' ' + item[1] + '\n')
- 62. with open(os.path.join(output_dir, 'test.txt'), 'w') as ftest:
- 63. for item in test_list:
- 64. ftest.write(item[0] + ' ' + item[1] + '\n')
- 65. if __name__ == '__main__':
- 66. prepare_filelist(devkit_dir, output_dir)
复制代码 通过处理后,可以得到能够进行训练使用的数据集:
4.3 模型训练
配置文件修改:- 1. vim configs/datasets/voc.yml
复制代码
全精度训练:- 1. python tools/train.py -c configs/ssd/ssd_mobilenet_v1_300_120e_voc.yml --eval
复制代码 量化训练:
修改configs/slim/quant/ssd_mobilenet_v1_qat.yml,将pretrain_weights参数指向ssd_mobilenet_v1_300_120e_voc文件所在路径,例如:
pretrain_weights: output/ssd_mobilenet_v1_300_120e_voc/best_model
执行训练命令:- 1. python tools/train.py -c configs/ssd/ssd_mobilenet_v1_300_120e_voc.yml --slim_config configs/slim/quant/ssd_mobilenet_v1_qat.yml --eval
复制代码 4.4 模型导出
最终导出的静态模型位于output_inference/ssd_mobilenet_v1_qat/:- 1. python tools/export_model.py \
- 2. -c configs/ssd/ssd_mobilenet_v1_300_120e_voc.yml \
- 3. --slim_config configs/slim/quant/ssd_mobilenet_v1_qat.yml \
- 4. -o weights=output/ssd_mobilenet_v1_qat/best_model
复制代码 4.5 模型转换
当前目录则会生成ssd_mobilenet_v1_opt.nb,该文件即是量化之后的模型:- 1. opt \
- 2. --model_dir=./PaddleDetection/output_inference/ssd_mobilenet_v1_qat/ \
- 3. --valid_targets=intel_fpga,arm \
- 4. --optimize_out_type=naive_buffer \
- 5. --optimize_out=ssd_mobilenet_v1_opt
复制代码 5 FPGA开发板推理
5.1 利用PADDLE-LITE推理
在FPGA上推理,这里使用Paddle-Lite,下面针对Paddle-Lite推理流程进行简要说明。
C++代码调用Paddle Lite执行预测库仅需以下五步:
- #include "paddle_api.h"
- using namespace paddle::lite_api;
复制代码- // 1. Set MobileConfig
- MobileConfig config;
- // 2. Set the path to the model generated by opt tools
- config.set_model_from_file(model_file_path);
- // 3. Create PaddlePredictor by MobileConfig
- std::shared_ptr<PaddlePredictor> predictor = CreatePaddlePredictor<MobileConfig>(config);
复制代码- std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
- input_tensor->Resize({1, 3, 224, 224});
- auto* data = input_tensor->mutable_data<float>();
- for (int i = 0; i < ShapeProduction(input_tensor->shape()); ++i) {
- data[i] = 1;
- }
复制代码 如果模型有多个输入,每一个模型输入都需要准确设置shape和data。
- std::unique_ptr<const Tensor> output_tensor(
- std::move(predictor->GetOutput(0)));
- // 转化为数据
- auto output_data = output_tensor->data<float>();
复制代码 下面是使用SSD模型进行推理的具体示例:
[code]1. #include 2. #include 3. #include "opencv2/core.hpp"4. #include "opencv2/imgcodecs.hpp"5. #include "opencv2/imgproc.hpp"6. #include "paddle_api.h" // NOLINT7. using namespace paddle::lite_api; // NOLINT8. struct Object {9. int batch_id;10. cv::Rect rec;11. int class_id;12. float prob;13. };14. int64_t ShapeProduction(const shape_t& shape) {15. int64_t res = 1;16. for (auto i : shape) res *= i;17. return res;18. }19. const char* class_names[] = {20. "background", "aeroplane", "bicycle", "bird", "boat",21. "bottle", "bus", "car", "cat", "chair",22. "cow", "diningtable", "dog", "horse", "motorbike",23. "person", "pottedplant", "sheep", "sofa", "train",24. "tvmonitor"};25. // fill tensor with mean and scale and trans layout: nhwc -> nchw, neon speed up26. void neon_mean_scale(const float* din,27. float* dout,28. int size,29. const std::vector mean,30. const std::vector scale) {31. if (mean.size() != 3 || scale.size() != 3) {32. std::cerr 0) {91. Object obj;92. int x = static_cast(data[2] * oriw);93. int y = static_cast(data[3] * orih);94. int w = static_cast(data[4] * oriw) - x;95. int h = static_cast(data[5] * orih) - y;96. cv::Rect rec_clip =97. cv::Rect(x, y, w, h) & cv::Rect(0, 0, image.cols, image.rows);98. obj.batch_id = 0;99. obj.class_id = static_cast(data[0]);100. obj.prob = data[1];101. obj.rec = rec_clip;102. if (w > 0 && h > 0 && obj.prob |