[嘉楠 CanMV K230]测评 ⑩自己训练一个分类识别模型
<div class='showpostmsg'> 本帖最后由 不爱胡萝卜的仓鼠 于 2024-11-1 09:43 编辑<p> 之前我们跑的功能,基本上都是一些基础的图像识别功能,没有太多AI的加持,面对一些复杂的场景,之前的那些内容就显得不足了,需要有AI的加入</p>
<p> </p>
<p> 我们今天就来自己训练一个AI模型,并运行在K230开发板上,实现分类识别</p>
<p> </p>
<p><strong><span style="font-size:24px;">一.模型训练</span></strong></p>
<p> 嘉楠官网就有模型训练的服务,免费的,对于我们这些初学者来说非常友好。地址如下:<a href="https://developer.canaan-creative.com/training/" target="_blank">https://developer.canaan-creative.com/training/</a></p>
<p> 打开网页后需要注册账号并登录</p>
<div style="text-align: center;">
<div style="text-align: center;"></div>
<p> </p>
</div>
<p> 接下来我们开始添加数据集</p>
<div style="text-align: center;">
<div style="text-align: center;"></div>
<p style="text-align: left;"> 添加成功后如图,点击名称,即可打开该数据集,我们就可以开始添加图片了</p>
<div style="text-align: center;">
<div style="text-align: center;">d</div>
<p> </p>
</div>
<p style="text-align: left;"> 点击上传图片,把准备好的图片上传,我这边准备了几十张鼠标的照片,全部上传</p>
<div style="text-align: center;"></div>
<p style="text-align: left;">等待上传完成</p>
<div style="text-align: center;"></div>
<p> </p>
</div>
<p>上传完成后,会弹出如图内容,输入标签名称,我这边是鼠标,那我就叫mouse,然后再点击确定</p>
<div style="text-align: center;"></div>
<p>刚才上传的图片就全部被打上了鼠标的标签</p>
<div style="text-align: center;"></div>
<p>接下来我还准备了笔的照片,流程和鼠标一致,我就不再赘述了(这边的照片数量越多越好,以前官方教程中是推荐100张以上,但是现在这个貌似删掉了。并且官方提供的训练集也是50张,因此50张应该是基本够用)</p>
<p> </p>
<p> </p>
<p>最后点击“训练”,开始模型训练</p>
<div style="text-align: center;"></div>
<p>训练参数填写如下</p>
<div style="text-align: center;"></div>
<p>其中nnacse的版本要根据实际下载到开发板上的镜像说决定,我现在运行的是1.0版本,根据官网下载固件的名称可以得到对应版本为2.8.3</p>
<div style="text-align: center;"></div>
<p>迭代次数默认700,我为了快速完成训练,就调整为100。这个数字越大训练时长越长,效果我认为也会越好</p>
<p> </p>
<p>接下来就是坐等训练结束</p>
<div style="text-align: center;"></div>
<p> </p>
<p> 经过漫长的等待,训练完成后界面如图。</p>
<div style="text-align: center;"></div>
<p>同时你注册账号的邮箱会收到一封邮件,通知你训练完成,并给结果</p>
<div style="text-align: center;"></div>
<p>下载得到的压缩包内容如下</p>
<div style="text-align: center;"></div>
<p>cls_results:里面是一些测试用的图片</p>
<p>cpp_deployment_source.zip:C++版本的部署包</p>
<p>mp_deployment_source.zip:MicroPython版本部署包</p>
<p>README.md:使用指导手册</p>
<p> </p>
<p>我这边使用的micropython的固件,所以我实际需要的是 “mp_deployment_source.zip”这个文件,其他的都不重要</p>
<div> </div>
<div>(以上截图是我第一次训练时的截图,中途因为一些小问题,导致我又调整参数重新训练了模型。下面的模型是每个分类160张图片,700次迭代,其他参数不变)</div>
<div> </div>
<div> </div>
<p><strong><span style="font-size:24px;">二.使用模型</span></strong></p>
<p> 把mp_deployment_source.zip解压,内容如下</p>
<div style="text-align: center;"></div>
<p>将这个文件夹拷贝到开发板的SD卡中</p>
<div style="text-align: center;"></div>
<p><strong><span style="font-size:18px;">2.1 视频识别</span></strong> </p>
<p> 打开IDE,连接开发板后,打开储存在SD卡中的“cls_video.py”文件</p>
<p> </p>
<div style="text-align: center;"></div>
<div style="text-align: center;">
<div style="text-align: center;"></div>
</div>
<p> 运行,即可在日志串口看到识别的结果,效果如下,基本上识别还是准确的,可信度基本上都是0.8多0.9多</p>
<p>8530d9915a5c07353098966c42cb923b<br />
</p>
<p> cls_video.py的代码如下</p>
<pre>
<code class="language-python">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(, ,img_size])
# 初始化并配置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,img_size),dtype=np.uint8)
ai2d_output_tensor = nn.from_numpy(data)
whileTrue:
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)
cls_idx=np.argmax(softmax_res)
if softmax_res>confidence_threshold:
score=softmax_res
print("classification result:")
print(labels)
print("score",score)
else:
cls_idx=-1
score=0.0
else:
sigmoid_res=sigmoid(results)
if sigmoid_res>confidence_threshold:
cls_idx=1
score=sigmoid_res
print("classification result:")
print(labels)
print("score",score)
else:
cls_idx=0
score=1-sigmoid_res
print("classification result:")
print(labels)
print("score",score)
osd_img.clear()
if cls_idx>=0:
osd_img.draw_string_advanced(5,5,32,"result:"+labels+" 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()
</code></pre>
<p><strong><span style="font-size:16px;">2.2 图片识别</span></strong></p>
<p> 结果中还有一个图片的示例程序</p>
<p> 需要拷贝一张图片到sd卡中,我们可以从训练结果中的cls_results文件夹下得到一些样本的照片,如果你有其他照片也可以(但是需要.jpg格式)。修改照片的名称为test,拷贝到SD卡中</p>
<div style="text-align: center;"></div>
<p> </p>
<p>然后ide中运行的代码为SD卡中的cls_image.py,具体的操作与上面的一样,我就不赘述了</p>
<p> </p>
<p>如果运行提示以下内容,可能的原因是放到SD卡中的图片太大了,可以看到,我刚才拷贝进去的有1.63M,太大了,缓冲区不够用了。需要用软件压缩一下,我选择的是降低分辨率,我选择320*240.压缩完了就只有32KB了</p>
<div style="text-align: center;"></div>
<p> </p>
<p>运行结果如下,</p>
<div style="text-align: center;"></div>
<div style="text-align: center;">
<div style="text-align: center;"></div>
<p> </p>
</div>
<p> cls_image.py的代码如下</p>
<pre>
<code class="language-python">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 * shape, shape))
img_tmp_trans = img_tmp.transpose()
img_res=img_tmp_trans.copy()
img_return=img_res.reshape((shape,shape,shape))
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,img_size),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(,ai2d_input_shape], ,img_size])
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)
res_idx=np.argmax(softmax_res)
if softmax_res>confidence_threshold:
cls_idx=res_idx
print("classification result:")
print(labels)
print("score",softmax_res)
image_draw=image.Image(image_path).to_rgb565()
image_draw.draw_string(10, 10,labels , 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)
if sigmoid_res>confidence_threshold:
cls_idx=1
print("classification result:")
print(labels)
print("score",sigmoid_res)
image_draw=image.Image(image_path).to_rgb565()
image_draw.draw_string(10, 10,labels , 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)
print("score",1-sigmoid_res)
image_draw=image.Image(image_path).to_rgb565()
image_draw.draw_string(10, 10,labels , 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()
</code></pre>
<p> </p>
<p> </p>
<p><strong><span style="font-size:24px;">三.模型训练经验分享</span></strong></p>
<p> 1.单个标签的图片不要太少,以前官方教程中是推荐100张以上,但是现在这个貌似删掉了。并且官方提供的训练集也是50张,因此50张应该是基本够用。但是小于50张,训练效果可能会变差</p>
<p> 2.图片不要太多,当然不是说多了会对模型训练造成影响,对于训练来说是越多越好的,但是训练平台资源有限,做了1G的限制(应该是所有图片总大小不能超过1G),超过1G不能开始训练。不然一次性上传的太多,回头一张一张删除累死人(建议官方改进一下,可以做一个上传的限制,或者允许批量删除)</p>
<p> 3.不要一次性上传很多图片,我一次性上传150张,上传时会报错,具体报错信息我也没太看明白,后面换成50张传一次,分批传就可以了</p>
<p> 4.如果遇到问题,自己多次尝试实在解决不了问题的,可以考虑去嘉楠官网提问。我做模型测试时,遇到问题,捣鼓了很久,没解决,在晚上提问后,官方技术人员第二天就回复了,并解决了问题。给官方的技术人员大大的点赞</p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>本文最终训练得到的结果见附件,大家可以下载后复现</p>
<div></div>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>应该是所有图片总大小不能超过1G,AI模型训练的过程了解了,谢谢分享</p>
页:
[1]