Canmv k230 C++案例1——image classify学习笔记 初版

news/2024/10/15 18:14:11/

00 简介

用C++编写代码的比mircopython要慢很多,需要编译开发环境,同时使用C++更接近底层,效率利用率应该也是更高的,就是需要学习更多的内容,因为从零开始因此比较比较耗时。
注:以下为个人角度的理解,不一定正确。

根据官方文档,开发的主要流程:
1)编译开发环境:k230_sdk 运行环境ubuntu20.04(用于编译k230能够运行的二进制文件)
2)AI 模型开发:开发工具众多(tensorflow、onnx(其他模型转换为onnx格式))
3)模型转换:深度学习开发的模型转换为运行在k230上的模型(kmodel格式)
4)C++ 编写:模型的加载等过程
5)上板验证:将生成文件加载至SD 卡中,并在出口终端查看输出结果

01 开发环境编译

最好参考github上的描述,官网的信息有一定的延迟


git clone https://github.com/kendryte/k230_sdk
cd k230_sdk
source tools/get_download_url.sh && make prepare_sourcecode
docker build -f tools/docker/Dockerfile -t k230_docker tools/docker
# 如果上一个命令不好用采用下面的命令
# docker pull ghcr.io/kendryte/k230_sdk
docker run -u root -it -v $(pwd):$(pwd) -v $(pwd)/toolchain:/opt/toolchain -w $(pwd) k230_docker /bin/bash
# 下面的任选其一
make CONF=k230_canmv_defconfig  #编译CanMV-K230 1.0/1.1 板子Linux+RTT双系统镜像
make CONF=k230_canmv_only_rtt_defconfig  #编译CanMV-K230 1.0/1.1 板子RTT-only系统镜像

01studio的linux没搞好,仍采用canmv k230 1.0版本

02 AI 模型开发

因为这个是sdk中已经存在的模型:mbv2.tflite,看起来像是XX模型
模型路径为

cd k230_sdk/src/big/nncase/examples/models

将模型用netron打开
模型就结构

03 模型转换

该部分将mbv2.tflite转换为kmodel模型,这部分可以参照sdk中的README.md文档
文档路径

cd k230_sdk/src/big/nncase/examples

文档中是3个例子,下面将具体介绍image_classify模型,因为它相对简单,易于学习。
README.md文档中需要运行

# 进入sdk目录
cd /path/to/k230_sdk
# 运行docker
docker run -u root -it --rm -v $(pwd):/mnt -v $(pwd)/toolchain:/opt/toolchain -w /mnt ghcr.io/kendryte/k230_sdk:latest /bin/bash

安装转换工具nncase

# nncase最好与镜像中的nncase保持一致
pip install -i https://pypi.org/simple nncase==2.9.0 nncase-kpu==2.9.0

模型编译
先看文档中的命令

cd src/big/nncase/examples/
./build_model.sh

build_model.sh中关于image classfy部分

# build image classfy model
python3 ./scripts/mbv2_tflite.py --target k230 --model models/mbv2.tflite --dataset calibration_dataset

此处可以参考之前的文章,sh命令中的步骤为:
1)编写模型转换文件:mbv2_tflite.py
2)设置模型转换的硬件:–target k230
3)待转换的模型:–model models/mbv2.tflite
4)代表数据集:–dataset calibration_dataset

为什么要进行模型的转换呢?
结合官方文档的理解:模型转换的过程是一个将浮点模型转换为定点的过程,缩小模型,加快计算

个人理解:以数据存储的角度来看float32是32位的,uint8是8位的,模型参数的存储量降低到1/4,同时提升运算速度(可能浮点运算的开销更大),达到模型加速的目的;而k230是存在kpu,它针对AI 模型的计算部分进行加速,为便于理解可以类比单单指令流多数据流(SIMD)等技术,如果用图片表示,例如可以理解将四个顺序执行的加法四条指令周期改为并行的一个指令周期的专用计算单元(这里只是类比)

当然也有其他模型加速的方法,请自行查询

模型转换完成后会在tmp文件夹中显示mbv2_tflite文件夹

将转换后的模型编译成二进制文件

./build_app.sh

查看文件描述

#!/bin/bash
set -x# set cross build toolchain
export PATH=$PATH:/opt/toolchain/riscv64-linux-musleabi_for_x86_64-pc-linux-gnu/bin/clear
rm -rf out
mkdir out
pushd out
cmake -DCMAKE_BUILD_TYPE=Release                 \-DCMAKE_INSTALL_PREFIX=`pwd`               \-DCMAKE_TOOLCHAIN_FILE=cmake/Riscv64.cmake \..make -j && make install
popd# assemble all test cases
k230_bin=`pwd`/k230_bin
mkdir -p ${k230_bin}
if [ -f out/bin/image_classify.elf ]; thenimage_classify=${k230_bin}/image_classifyrm -rf ${image_classify}cp -a image_classify/data/ ${image_classify}cp out/bin/image_classify.elf ${image_classify}cp tmp/mbv2_tflite/test.kmodel ${image_classify}
fi

关于命令的解读可能需要另开一篇文章,重点为.cc文件与cmake文件的理解
其中main.cc文件为,可以将它看成是一个hello world的增强型扩展。。。

01 预先定义部分

// 这里是一些定义配置
#include <chrono>
#include <fstream>
#include <iostream>
#include <nncase/runtime/interpreter.h>
#include <nncase/runtime/runtime_op_utility.h>#define USE_OPENCV 1
#define preprocess 1#if USE_OPENCV
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#endifusing namespace nncase;
using namespace nncase::runtime;
using namespace nncase::runtime::detail;#define INTPUT_HEIGHT 224
#define INTPUT_WIDTH 224
#define INTPUT_CHANNELS 3

02 用于读取二进制文件、txt文件的函数部分

template <class T>
std::vector<T> read_binary_file(const std::string &file_name)
{std::ifstream ifs(file_name, std::ios::binary);ifs.seekg(0, ifs.end);size_t len = ifs.tellg();std::vector<T> vec(len / sizeof(T), 0);ifs.seekg(0, ifs.beg);ifs.read(reinterpret_cast<char *>(vec.data()), len);ifs.close();return vec;
}void read_binary_file(const char *file_name, char *buffer)
{std::ifstream ifs(file_name, std::ios::binary);ifs.seekg(0, ifs.end);size_t len = ifs.tellg();ifs.seekg(0, ifs.beg);ifs.read(buffer, len);ifs.close();
}static std::vector<std::string> read_txt_file(const char *file_name)
{std::vector<std::string> vec;vec.reserve(1024);std::ifstream fp(file_name);std::string label;while (getline(fp, label)){vec.push_back(label);}return vec;
}

函数输出的softmax函数,这个部分好像不能够用kpu加速环节,好像采用RVV优化了模型参数,暂未深入探究

template<typename T>
static int softmax(const T* src, T* dst, int length)
{const T alpha = *std::max_element(src, src + length);T denominator{ 0 };for (int i = 0; i < length; ++i) {dst[i] = std::exp(src[i] - alpha);denominator += dst[i];}for (int i = 0; i < length; ++i) {dst[i] /= denominator;}return 0;
}

04 用于输入通道转换

#if USE_OPENCV
std::vector<uint8_t> hwc2chw(cv::Mat &img)
{std::vector<uint8_t> vec;std::vector<cv::Mat> rgbChannels(3);cv::split(img, rgbChannels);for (auto i = 0; i < rgbChannels.size(); i++){std::vector<uint8_t> data = std::vector<uint8_t>(rgbChannels[i].reshape(1, 1));vec.insert(vec.end(), data.begin(), data.end());}return vec;
}
#endif
在这里插入代码片

05 模型推断部分


static int inference(const char *kmodel_file, const char *image_file, const char *label_file)
{// load kmodelinterpreter interp;std::ifstream ifs(kmodel_file, std::ios::binary);interp.load_model(ifs).expect("load_model failed");// create input tensorauto input_desc = interp.input_desc(0);auto input_shape = interp.input_shape(0);auto input_tensor = host_runtime_tensor::create(input_desc.datatype, input_shape, hrt::pool_shared).expect("cannot create input tensor");interp.input_tensor(0, input_tensor).expect("cannot set input tensor");// create output tensor// auto output_desc = interp.output_desc(0);// auto output_shape = interp.output_shape(0);// auto output_tensor = host_runtime_tensor::create(output_desc.datatype, output_shape, hrt::pool_shared).expect("cannot create output tensor");// interp.output_tensor(0, output_tensor).expect("cannot set output tensor");// set input dataauto dst = input_tensor.impl()->to_host().unwrap()->buffer().as_host().unwrap().map(map_access_::map_write).unwrap().buffer();
#if USE_OPENCVcv::Mat img = cv::imread(image_file);cv::resize(img, img, cv::Size(INTPUT_WIDTH, INTPUT_HEIGHT), cv::INTER_NEAREST);auto input_vec = hwc2chw(img);memcpy(reinterpret_cast<char *>(dst.data()), input_vec.data(), input_vec.size());
#elseread_binary_file(image_file, reinterpret_cast<char *>(dst.data()));
#endifhrt::sync(input_tensor, sync_op_t::sync_write_back, true).expect("sync write_back failed");// runsize_t counter = 1;auto start = std::chrono::steady_clock::now();for (size_t c = 0; c < counter; c++){interp.run().expect("error occurred in running model");}auto stop = std::chrono::steady_clock::now();double duration = std::chrono::duration<double, std::milli>(stop - start).count();std::cout << "interp.run() took: " << duration / counter << " ms" << std::endl;// get output dataauto output_tensor = interp.output_tensor(0).expect("cannot set output tensor");dst = output_tensor.impl()->to_host().unwrap()->buffer().as_host().unwrap().map(map_access_::map_read).unwrap().buffer();float *output_data = reinterpret_cast<float *>(dst.data());auto out_shape = interp.output_shape(0);auto size = compute_size(out_shape);// postprogress softmax by cpustd::vector<float> softmax_vec(size, 0);auto buf = softmax_vec.data();softmax(output_data, buf, size);auto it = std::max_element(buf, buf + size);size_t idx = it - buf;// load labelauto labels = read_txt_file(label_file);std::cout << "image classify result: " << labels[idx] << "(" << *it << ")" << std::endl;return 0;
}

带参数的主函数

// 主函数
int main(int argc, char *argv[])
{std::cout << "case " << argv[0] << " built at " << __DATE__ << " " << __TIME__ << std::endl;if (argc != 4){std::cerr << "Usage: " << argv[0] << " <kmodel> <image> <label>" << std::endl;return -1;}int ret = inference(argv[1], argv[2], argv[3]);if (ret){std::cerr << "inference failed: ret = " << ret << std::endl;return -2;}return 0;
}

运行完后会出现k230_bin文件夹,并查看输出文件夹image_classify

输出文件
将该文件夹移动到SD 卡中
在这里插入图片描述

进入大核运行代码,并查看输出内容
连接大核
注:小核会显示登录,大核需要输入q退出默认运行的人脸检测程序

发送 q 退出大核运行程序分别
输入

# 进入文件夹
cd /sharefs/image_classify
# 查看文件
ls
# 运行sh
./cpp.sh

查看串口中的输出结果
串口输入结果
C++代码解释篇幅较长,见后续文章

待续


http://www.ppmy.cn/news/1539542.html

相关文章

springboot3导出数据库数据到excel

一、导入依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.3.0</version></dependency><d…

鸿蒙OS 开机动画流程

开机动画的启动与结束 foundation\graphic\graphic_2d\graphic.cfg "services" : [{"name" : "render_service","path" : ["/system/bin/render_service"],"critical" : [0, 5, 60],"importance" : -…

现货黄金价格走势图策略分析 先看“势”

在现货黄金投资市场&#xff0c;对金价走势图的趋势进行分析&#xff0c;是投资者做出明智决策的关键步骤。通过有效的趋势分析&#xff0c;投资者可以更好地预测市场的走向&#xff0c;从而制定相应的交易策略。本文将详细介绍如何分析金价的趋势&#xff0c;并探讨这种分析方…

麒麟(Kylin)系统下安装MySQL 5.7.44(离线版)

目录 1、下载MySQL安装包2、卸载系统自带的mariadb和已安装过的MySQL包3、解压bundle.tar4、安装MySQL5、修改配置文件&#xff1a;vi /etc/my.cnf6、启动MySQL7、查看临时密码&#xff0c;并修改密码&#xff0c;授权远程访问。8、常见问题8.1 启动mysql内存溢出8.2 安装mysql…

服务器与内存市场|2025预测动态早知道

根据TrendForce的数据分析报告&#xff0c;三大DRAM供应商在2023年服务器总bit增长率经历了不同程度下滑后&#xff0c;2024年市场迎来了反弹&#xff0c;增长率分别达到了9.9%/12.3%/24.1%。这一转变表明服务器DRAM在三大供应商中的比例预计将会增加。与此同时&#xff0c;由于…

vite 配置开发环境和测试环境,接口 API 自动切换

在Vite项目中配置开发环境和测试环境&#xff0c;并实现接口API的自动切换&#xff0c;可以通过配置环境变量和相应的代理设置来完成。以下是一个详细的步骤指南&#xff1a; 一、创建环境文件 在项目根目录下创建.env、.env.development和.env.test文件&#xff0c;用于存储…

【Linux】命令行下的增删查改之“查看”

致谢:Linux常用命令大全(手册) – 真正好用的Linux命令在线查询网站 提供的命令查询 头部内容获取(head) head命令的功能是显示文件开头的内容&#xff0c;默认值为前10行。 指令参数&#xff1a; -n 定义显示行数 -c 指定显示头部内容的字符数 -v 总是显示文件名的头信…

洗衣店管理新思路:Spring Boot订单管理系统

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…