硬件介绍
首先介绍本期活动的主角,M5Stack推出的Unit V2开发板,这是一块很小的嵌入式Linux开发板,主控是一颗ARM Cortex-A7的双核处理器,主频是1.2Ghz。同时主板集成了128M内存和512M的Flash,同时预留了TF卡槽,可以用来扩展更多的存储空间。
模块有以下主要参数:
-
ARM 双核 Cortex-A7 1.2Ghz 处理器,带Neno和FPU的硬件加速器
-
128MB DDR3内存
-
512MB NAND Flash
-
GC2145 1080P 摄像头
-
麦克风、TF卡插槽、UART端口
-
WiFi 2.4GHz、USB有线网卡
任务分析
活动一共设置了四种任务,简单概括一下这四种任务,都是通过图像识别来分析一些场景信息,并记录下来。只是分析对象不同。
我选做的是任务2,任务2的核心问题就是训练一个针对流水线的目标检测模型。这一步可以有很多种实现方法,可以基于官方的V-training平台来一站式地获得模型,也可以自己在本地通过PyTorch等深度学习框架来自己训练模型。在模型的部署方面,也有两种方案,即可以使用官方提供的框架来推理模型,也可以使用NCNN提供的C++库来自己编写推理程序。但是在实际运行系统后,发现根文件系统是基于busybox构建的。为了保持足够小的体积,精简掉了gcc等工具链,也没有包管理器来他们安装它们。如果自行构建功能更完整的根文件系统并切换上去,要花费很多的时间。因此最终还是选择使用官方提供的可执行程序来进行推理。
硬件平台搭建
我选择的流水线识别对象是一些螺丝刀的可拆卸刀头,把其中的六角刀头定义为异常品,其他种类的刀头定义为正常。用A4纸和手机支架搭建了一个简单的模拟流水线。
由于Unit V2开发板内嵌了Linux操作系统,已经集成任务所需的硬件资源,我们只需要使用手机支架把它布置到合适的位置即可。
最后把工件放置在A4纸上,通过移动A4纸,就可以模拟流水线上工件移动的效果。布置完成的效果如下。
软件平台搭建
任务主要需要两个平台,一个是运行监控网站的笔记本电脑或阿里云服务器,另一个则是Unit V2开发板。
开发与部署所需的软件如下:
- VSCode: 用来开发Python代码
- IDEA: 用来开发Java网站
- XShell:连接Unit V2开发板的SSH终端
- XFTP:和开发板间传输文件
- Spring Boot + SSM :编写Java网站所需的框架与中间件
实现流程
1. 训练模型
我们使用官方提供的V-training训练平台,来训练自己的模型。首先通过Unit V2提供的拍照功能,拍下30多张图片,上面分别是各种情况下的流水线。接下来上传图片,标定目标数据集,然后开始训练,一段时间后就可以下载模型。
2. 上传并运行模型
上一步下载得到的模型文件为压缩包格式,我们把它上传到模块中,就可以直接运行模型,得到可视化的结果和输出信息。
3. 分析程序输出并上传至服务器
目标检测程序的输出信息是json的格式,我们可以写一个python程序。调用目标检测程序。并分析它的输出。当输出内容中含有异常品时,就把它记录下来。为了更好地记录和可视化结果,我们使用POST请求,将异常件的数量与图像信息发送至服务器。
运行在Unit V2开发板的Python主程序如下:
from json.decoder import JSONDecodeError
import subprocess
import json
import requests
import base64
import time
reconizer = subprocess.Popen(['/home/m5stack/payload/bin/object_recognition', '/home/m5stack/payload/uploads/models/v2model_2990349c31e07065'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
url = 'http://10.254.239.226:80'
header = { 'content-type':'application/json'}
reconizer.stdin.write("_{\"stream\":1}\r\n".encode('utf-8'))
reconizer.stdin.flush()
img = b''
time = 0
lastNum = 0
numChanged = False
while True:
doc = json.loads(reconizer.stdout.readline().decode('utf-8'))
time = time + 1
num = 0
hasBad = False
if 'img' in doc:
img = doc["img"]
if 'obj' in doc:
for e in doc['obj']:
if e['type'] == 'bad':
num=num+1
hasBad = True
numChanged = (doc["num"] != lastNum)
if hasBad and numChanged and time>20:
print("bad dected!")
time = 0
msg = {'name':'bad', 'count':num, 'img': img}
r = requests.post(url + '/history/create', data=json.dumps(msg),headers=header)
print(r)
print(r.content)
numChanged = False
lastNum = doc["num"]
4. 网站服务器接收记录,并存储、展示
我用Java编写了一个简单的网站,它可以接收Unit V2开发板发来的检测到异常品的时间和实时的照片,并保存到MySQL数据库中,还可以通过前端的页面展示出来。
Java网站的部分后端程序如下:
package com.yu.crm.web.controller;
import com.yu.crm.model.CRUDResult;
import com.yu.crm.model.LogEntry;
import com.yu.crm.model.PageResult;
import com.yu.crm.service.ILogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import sun.misc.BASE64Decoder;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
@Controller
@RequestMapping("history")
public class ListController {
@Autowired
ILogService service;
@Autowired
SimpleDateFormat sdf;
@RequestMapping("list")
public String list(){
return "history/list";
}
@RequestMapping("create")
@ResponseBody
public CRUDResult create(@RequestBody Map<String, Object> req){
final String name = req.get("name").toString();
final int count = Integer.parseInt(req.get("count").toString());
final String img = req.get("img").toString();
final Date date = new Date();
final String pic = sdf.format(date).toString() + ".jpg";
try {
String path = "D:/data/";
String fakePath = "/image/" + pic;
final String realPath = path + pic;
final LogEntry entry = new LogEntry(null, name, count, date, fakePath);
service.create(entry);
decoderBase64File(img, realPath);
} catch (Exception e) {
e.printStackTrace();
}
return new CRUDResult(1,"OK");
}
@RequestMapping("listJson")
@ResponseBody
public PageResult<LogEntry> listJson(LogEntry condition, int page, int limit){
PageResult<LogEntry> pageResult = service.findPageResult(condition, page, limit);
return pageResult;
}
public void decoderBase64File(String base64Code,String targetPath) {
try {
byte[] buffer = new BASE64Decoder().decodeBuffer(base64Code);
FileOutputStream out = new FileOutputStream(targetPath);
out.write(buffer);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Bean
public SimpleDateFormat getSimpleDateFormat() {
return new SimpleDateFormat("yyyy-MM-dd-hh-mm-ss");
}
}
遇到的问题
由于Unit V2提供的软硬件功能已经相当完善,遇到的问题主要是在实现方案的选择与开发中的实现细节,具体如下:
1. 模型推理的方案选择
项目最开始的计划是选用PyTorch在本地自己训练模型,并使用C++库编写NCNN推理程序,这样实现细节可控,但是查看开发板的系统后发现,板载的根文件系统只提供了完整的Python环境,C++开发环境工具链缺失,也无法通过包管理器来安装。官方框架的推理程序只提供了二进制可执行文件,推理源码也并没有开源。因此最终选择了使用Python调用官方的推理程序。
2. 向网站后端服务器传输图片与其他信息
为了在网站上提供更好的展示效果,决定向后端服务器发送图片和文本信息,但是最开始对图像的传输编码不了解,经过一些尝试,确定图片就是原始JSON的img字段,并且是Base64编码过后的。最终在后端服务器上编写了Base64解码与文件保存部分,实现了在一次POST请求中同时传输图片与文本信息。
3. UnitV2有限网卡与电脑的联网优先级
同时使用UnitV2有限网卡和电脑本身的网卡联网时,有时会导致电脑无法联网。经过群内的讨论,发现在Windows系统中可以通过调整网卡跃点数来修改网卡的联网优先级。经过修改后,电脑能够正常联网,这也是通过本次活动学到的一个继续你的小技巧。
未来的计划建议
M5Stack官方给UnitV2的定位是AI识别模块,这块开发板给我印象最深刻的就是在宣传片中,它流畅地运行一些小型的深度学习目标检测模型。这一点非常有吸引力,因为能够在如此有限的板载资源下运行深度学习模型,是有一定的技术难度的,所以我报名参加了本次的活动,希望能够一探究竟,学习一下Unit V2的软硬件设计。
经过活动中一段时间的熟悉,总结了几个对UnitV2的小建议,很希望能够在下一版本或UnitV3中看到它们:
- 完善根文件系统。UnitV2的根文件系统是基于Busybox构建的,虽然能够保持足够小的体积,但也同样限制了它的可扩展性。目前用户只能通过Python等高级语言来进行编程,不能够使用C++等语言进行更灵活的编程,也不能安装自己想要的系统应用。希望下一版的根文件系统可以考虑基于Debian等扩展性更强的文件系统来构建,文件系统可以移动到TF卡中,便于升级和修改,也进一步提高模块的自由度和可玩性。
- 加大开源力度。目前UnitV2系统中使用的目标检测程序是以为禁止可执行程序bin文件的方式提供的,在官方的github中并没有提供源码,在main文件夹下也只提供了camera_stream功能的源码,希望能够在接下来的更新中通过源码学习到更多的内容,而不是只调用编译好的可执行文件。
- 硬件升级。UnitV2作为一款AI识别模块,核心功能应该是图像识别功能,在实际运行的过程中,机器运行目标检测模型如nanodet的时候,流畅度还是不够高,当运行yolo模型的时候会更加明显,希望能够在下一代硬件中,升级处理器,或者添加一块专门的AI推理芯片,目前市场上还是有一些优秀的AI推理芯片的。相信通过硬件升级,Unit主打的图像识别能力会得到显著增强。
最后,感谢硬禾学堂和M5Stack的大力支持,以及交流群中老师和小伙伴们的奇思妙想,祝愿硬禾学堂的活动越办越好!