本帖最后由 不爱胡萝卜的仓鼠 于 2024-11-1 09:43 编辑
之前我们跑的功能,基本上都是一些基础的图像识别功能,没有太多AI的加持,面对一些复杂的场景,之前的那些内容就显得不足了,需要有AI的加入
我们今天就来自己训练一个AI模型,并运行在K230开发板上,实现分类识别
一.模型训练
嘉楠官网就有模型训练的服务,免费的,对于我们这些初学者来说非常友好。地址如下:https://developer.canaan-creative.com/training/
打开网页后需要注册账号并登录
接下来我们开始添加数据集
添加成功后如图,点击名称,即可打开该数据集,我们就可以开始添加图片了
d
点击上传图片,把准备好的图片上传,我这边准备了几十张鼠标的照片,全部上传
等待上传完成
上传完成后,会弹出如图内容,输入标签名称,我这边是鼠标,那我就叫mouse,然后再点击确定
刚才上传的图片就全部被打上了鼠标的标签
接下来我还准备了笔的照片,流程和鼠标一致,我就不再赘述了(这边的照片数量越多越好,以前官方教程中是推荐100张以上,但是现在这个貌似删掉了。并且官方提供的训练集也是50张,因此50张应该是基本够用)
最后点击“训练”,开始模型训练
训练参数填写如下
其中nnacse的版本要根据实际下载到开发板上的镜像说决定,我现在运行的是1.0版本,根据官网下载固件的名称可以得到对应版本为2.8.3
迭代次数默认700,我为了快速完成训练,就调整为100。这个数字越大训练时长越长,效果我认为也会越好
接下来就是坐等训练结束
经过漫长的等待,训练完成后界面如图。
同时你注册账号的邮箱会收到一封邮件,通知你训练完成,并给结果
下载得到的压缩包内容如下
cls_results:里面是一些测试用的图片
cpp_deployment_source.zip:C++版本的部署包
mp_deployment_source.zip:MicroPython版本部署包
README.md:使用指导手册
我这边使用的micropython的固件,所以我实际需要的是 “mp_deployment_source.zip”这个文件,其他的都不重要
(以上截图是我第一次训练时的截图,中途因为一些小问题,导致我又调整参数重新训练了模型。下面的模型是每个分类160张图片,700次迭代,其他参数不变)
二.使用模型
把mp_deployment_source.zip解压,内容如下
将这个文件夹拷贝到开发板的SD卡中
2.1 视频识别
打开IDE,连接开发板后,打开储存在SD卡中的“cls_video.py”文件
运行,即可在日志串口看到识别的结果,效果如下,基本上识别还是准确的,可信度基本上都是0.8多0.9多
20241031_231715
cls_video.py的代码如下
import os
import ujson
from media.sensor import *
from media.display import *
from media.media import *
from time import *
import nncase_runtime as nn
import ulab.numpy as np
import time
import utime
import image
import random
import gc
DISPLAY_WIDTH = ALIGN_UP(1920, 16)
DISPLAY_HEIGHT = 1080
OUT_RGB888P_WIDTH = ALIGN_UP(1280, 16)
OUT_RGB888P_HEIGH = 720
root_path="/sdcard/mp_deployment_source/"
config_path=root_path+"deploy_config.json"
deploy_conf={}
debug_mode=1
class ScopedTiming:
def __init__(self, info="", enable_profile=True):
self.info = info
self.enable_profile = enable_profile
def __enter__(self):
if self.enable_profile:
self.start_time = time.time_ns()
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.enable_profile:
elapsed_time = time.time_ns() - self.start_time
print(f"{self.info} took {elapsed_time / 1000000:.2f} ms")
def read_deploy_config(config_path):
# 打开JSON文件以进行读取deploy_config
with open(config_path, 'r') as json_file:
try:
# 从文件中加载JSON数据
config = ujson.load(json_file)
# 打印数据(可根据需要执行其他操作)
#print(config)
except ValueError as e:
print("JSON 解析错误:", e)
return config
# 任务后处理
def softmax(x):
exp_x = np.exp(x - np.max(x))
return exp_x / np.sum(exp_x)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def classification():
print("start")
# 使用json读取内容初始化部署变量
deploy_conf=read_deploy_config(config_path)
kmodel_name=deploy_conf["kmodel_path"]
labels=deploy_conf["categories"]
confidence_threshold=deploy_conf["confidence_threshold"]
img_size=deploy_conf["img_size"]
num_classes=deploy_conf["num_classes"]
cls_idx=-1
score=0.0
# init kpu and load kmodel
kpu = nn.kpu()
ai2d = nn.ai2d()
kpu.load_kmodel(root_path+kmodel_name)
ai2d.set_dtype(nn.ai2d_format.NCHW_FMT,
nn.ai2d_format.NCHW_FMT,
np.uint8, np.uint8)
ai2d.set_resize_param(True, nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel )
ai2d_builder = ai2d.build([1,3,OUT_RGB888P_HEIGH,OUT_RGB888P_WIDTH], [1,3,img_size[0],img_size[1]])
# 初始化并配置sensor
sensor = Sensor()
sensor.reset()
# 设置镜像
sensor.set_hmirror(False)
# 设置翻转
sensor.set_vflip(False)
# 通道0直接给到显示VO,格式为YUV420
sensor.set_framesize(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
sensor.set_pixformat(PIXEL_FORMAT_YUV_SEMIPLANAR_420)
# 通道2给到AI做算法处理,格式为RGB888
sensor.set_framesize(width = OUT_RGB888P_WIDTH , height = OUT_RGB888P_HEIGH, chn=CAM_CHN_ID_2)
sensor.set_pixformat(PIXEL_FORMAT_RGB_888_PLANAR, chn=CAM_CHN_ID_2)
# 绑定通道0的输出到vo
sensor_bind_info = sensor.bind_info(x = 0, y = 0, chn = CAM_CHN_ID_0)
Display.bind_layer(**sensor_bind_info, layer = Display.LAYER_VIDEO1)
# 设置为LT9611显示,默认1920x1080
Display.init(Display.LT9611, to_ide = True)
#创建OSD图像
osd_img = image.Image(DISPLAY_WIDTH, DISPLAY_HEIGHT, image.ARGB8888)
try:
# media初始化
MediaManager.init()
# 启动sensor
sensor.run()
# init
rgb888p_img = None
ai2d_input_tensor = None
data = np.ones((1,3,img_size[0],img_size[1]),dtype=np.uint8)
ai2d_output_tensor = nn.from_numpy(data)
while True:
with ScopedTiming("total",debug_mode > 0):
rgb888p_img = sensor.snapshot(chn=CAM_CHN_ID_2)
if rgb888p_img == -1:
print("capture_image failed")
camera.release_image(CAM_DEV_ID_0, CAM_CHN_ID_2, rgb888p_img)
continue
# for rgb888planar
if rgb888p_img.format() == image.RGBP888:
ai2d_input = rgb888p_img.to_numpy_ref()
ai2d_input_tensor = nn.from_numpy(ai2d_input)
ai2d_builder.run(ai2d_input_tensor, ai2d_output_tensor)
# set input
kpu.set_input_tensor(0, ai2d_output_tensor)
# run kmodel
kpu.run()
# get output
results = []
for i in range(kpu.outputs_size()):
output_data = kpu.get_output_tensor(i)
result = output_data.to_numpy()
del output_data
results.append(result)
if num_classes>2:
softmax_res=softmax(results[0][0])
cls_idx=np.argmax(softmax_res)
if softmax_res[cls_idx]>confidence_threshold:
score=softmax_res[cls_idx]
print("classification result:")
print(labels[cls_idx])
print("score",score)
else:
cls_idx=-1
score=0.0
else:
sigmoid_res=sigmoid(results[0][0][0])
if sigmoid_res>confidence_threshold:
cls_idx=1
score=sigmoid_res
print("classification result:")
print(labels[1])
print("score",score)
else:
cls_idx=0
score=1-sigmoid_res
print("classification result:")
print(labels[0])
print("score",score)
osd_img.clear()
if cls_idx>=0:
osd_img.draw_string_advanced(5,5,32,"result:"+labels[cls_idx]+" score:"+str(score),color=(0,255,0))
Display.show_image(osd_img, 0, 0, Display.LAYER_OSD3)
rgb888p_img = None
gc.collect() #用于需要调用gc.mem_alloc()的内存
except Exception as e:
print(f"An error occurred during buffer used: {e}")
finally:
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
del ai2d_input_tensor
del ai2d_output_tensor
#停止摄像头输出
sensor.stop()
#去初始化显示设备
Display.deinit()
#释放媒体缓冲区
MediaManager.deinit()
gc.collect()
time.sleep(1)
nn.shrink_memory_pool()
print("end")
return 0
if __name__=="__main__":
classification()
2.2 图片识别
结果中还有一个图片的示例程序
需要拷贝一张图片到sd卡中,我们可以从训练结果中的cls_results文件夹下得到一些样本的照片,如果你有其他照片也可以(但是需要.jpg格式)。修改照片的名称为test,拷贝到SD卡中
然后ide中运行的代码为SD卡中的cls_image.py,具体的操作与上面的一样,我就不赘述了
如果运行提示以下内容,可能的原因是放到SD卡中的图片太大了,可以看到,我刚才拷贝进去的有1.63M,太大了,缓冲区不够用了。需要用软件压缩一下,我选择的是降低分辨率,我选择320*240.压缩完了就只有32KB了
运行结果如下,
cls_image.py的代码如下
import os
import ujson
from time import *
import nncase_runtime as nn
import ulab.numpy as np
import time
import image
import gc
import utime
root_path="/sdcard/mp_deployment_source/" # root_path要以/结尾
config_path=root_path+"deploy_config.json"
image_path=root_path+"test.jpg"
deploy_conf={}
debug_mode=1
class ScopedTiming:
def __init__(self, info="", enable_profile=True):
self.info = info
self.enable_profile = enable_profile
def __enter__(self):
if self.enable_profile:
self.start_time = time.time_ns()
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.enable_profile:
elapsed_time = time.time_ns() - self.start_time
print(f"{self.info} took {elapsed_time / 1000000:.2f} ms")
def read_img(img_path):
img_data = image.Image(img_path)
img_data_rgb888=img_data.to_rgb888()
img_hwc=img_data_rgb888.to_numpy_ref()
shape=img_hwc.shape
img_tmp = img_hwc.reshape((shape[0] * shape[1], shape[2]))
img_tmp_trans = img_tmp.transpose()
img_res=img_tmp_trans.copy()
img_return=img_res.reshape((shape[2],shape[0],shape[1]))
return img_return
# 读取deploy_config.json文件
def read_deploy_config(config_path):
# 打开JSON文件以进行读取deploy_config
with open(config_path, 'r') as json_file:
try:
# 从文件中加载JSON数据
config = ujson.load(json_file)
# 打印数据(可根据需要执行其他操作)
#print(config)
except ValueError as e:
print("JSON 解析错误:", e)
return config
# 任务后处理
def softmax(x):
exp_x = np.exp(x - np.max(x))
return exp_x / np.sum(exp_x)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def classification():
print("--------------start-----------------")
# 使用json读取内容初始化部署变量
deploy_conf=read_deploy_config(config_path)
kmodel_name=deploy_conf["kmodel_path"]
labels=deploy_conf["categories"]
confidence_threshold=deploy_conf["confidence_threshold"]
img_size=deploy_conf["img_size"]
num_classes=deploy_conf["num_classes"]
cls_idx=-1
# ai2d输入输出初始化
ai2d_input = read_img(image_path)
ai2d_input_tensor = nn.from_numpy(ai2d_input)
ai2d_input_shape=ai2d_input.shape
data = np.ones((1,3,img_size[0],img_size[1]),dtype=np.uint8)
ai2d_out = nn.from_numpy(data)
# init kpu and load kmodel
kpu = nn.kpu()
ai2d = nn.ai2d()
kpu.load_kmodel(root_path+kmodel_name)
ai2d.set_dtype(nn.ai2d_format.NCHW_FMT,
nn.ai2d_format.NCHW_FMT,
np.uint8, np.uint8)
ai2d.set_resize_param(True, nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel )
ai2d_builder = ai2d.build([1,3,ai2d_input_shape[1],ai2d_input_shape[2]], [1,3,img_size[0],img_size[1]])
with ScopedTiming("total",debug_mode > 0):
ai2d_builder.run(ai2d_input_tensor, ai2d_out)
kpu.set_input_tensor(0, ai2d_out)
kpu.run()
del ai2d_input_tensor
del ai2d_out
# 获取分类结果
results = []
for i in range(kpu.outputs_size()):
data = kpu.get_output_tensor(i)
result = data.to_numpy()
results.append(result)
# 后处理
if num_classes>2:
softmax_res=softmax(results[0][0])
res_idx=np.argmax(softmax_res)
if softmax_res[res_idx]>confidence_threshold:
cls_idx=res_idx
print("classification result:")
print(labels[res_idx])
print("score",softmax_res[cls_idx])
image_draw=image.Image(image_path).to_rgb565()
image_draw.draw_string(10, 10,labels[cls_idx] , scale=2,color=(255,0,255,0))
image_draw.compress_for_ide()
image_draw.save(root_path+"cls_result.jpg")
else:
cls_idx=-1
else:
sigmoid_res=sigmoid(results[0][0][0])
if sigmoid_res>confidence_threshold:
cls_idx=1
print("classification result:")
print(labels[cls_idx])
print("score",sigmoid_res)
image_draw=image.Image(image_path).to_rgb565()
image_draw.draw_string(10, 10,labels[cls_idx] , scale=2,color=(255,0,255,0))
image_draw.compress_for_ide()
image_draw.save(root_path+"cls_result.jpg")
else:
cls_idx=0
print("classification result:")
print(labels[cls_idx])
print("score",1-sigmoid_res)
image_draw=image.Image(image_path).to_rgb565()
image_draw.draw_string(10, 10,labels[cls_idx] , scale=2,color=(255,0,255,0))
image_draw.compress_for_ide()
image_draw.save(root_path+"cls_result.jpg")
del data
del ai2d
del ai2d_builder
del kpu
print("---------------end------------------")
gc.collect()
nn.shrink_memory_pool()
if __name__=="__main__":
nn.shrink_memory_pool()
classification()
三.模型训练经验分享
1.单个标签的图片不要太少,以前官方教程中是推荐100张以上,但是现在这个貌似删掉了。并且官方提供的训练集也是50张,因此50张应该是基本够用。但是小于50张,训练效果可能会变差
2.图片不要太多,当然不是说多了会对模型训练造成影响,对于训练来说是越多越好的,但是训练平台资源有限,做了1G的限制(应该是所有图片总大小不能超过1G),超过1G不能开始训练。不然一次性上传的太多,回头一张一张删除累死人(建议官方改进一下,可以做一个上传的限制,或者允许批量删除)
3.不要一次性上传很多图片,我一次性上传150张,上传时会报错,具体报错信息我也没太看明白,后面换成50张传一次,分批传就可以了
4.如果遇到问题,自己多次尝试实在解决不了问题的,可以考虑去嘉楠官网提问。我做模型测试时,遇到问题,捣鼓了很久,没解决,在晚上提问后,官方技术人员第二天就回复了,并解决了问题。给官方的技术人员大大的点赞
本文最终训练得到的结果见附件,大家可以下载后复现