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

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.mzph.cn/bicheng/56360.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

科技云报到:云服务的中场战事,从AI应用开始

科技云报到原创。 从去年的大模型之战&#xff0c;到今年的AI应用之争&#xff0c;云服务正在迈入全新的发展阶段。AI这个杠杆将各家厂商的竞争策略更向前推进了一步。 “云AI”能够孵化出多少可能&#xff1f;在业界眼中&#xff0c;“云AI”则意味着新的悬念&#xff1a;云计…

探索极简计算的新边界:从Uxn虚拟机看未来编程生态

越来越多的开发者追求复杂度和功能性的极致,然而,有一个小众的编程社区选择了截然不同的道路——极简主义。Uxn虚拟机便是这一思潮的代表之一。它通过简洁的指令集和有限的硬件资源模拟,试图打造一种可以在多种设备上运行的便携性编程环境。 与主流的重型操作系统和复杂…

wireshark抓包

网络抓包工具Wireshark下载安装&使用详细教程-CSDN博客 下载地址&#xff1a;Wireshark Downloadhttps://www.wireshark.org/download.html modbus tcp 关于wireshark无法分析出modbusTCP报文的事情_wireshark 协议一列怎么没有modbus tcp-CSDN博客 使用Wireshark过滤…

【动手学深度学习】6.2 图像卷积(个人向笔记)

1. 互相关运算 严格来说&#xff0c;卷积层是一个错误的叫法&#xff0c;因为它本质上是互相关运算而不是卷积运算。我们暂时忽略通道看看二维图像数据和隐藏表示。那么输出大小可以表示为 我们自己实现一个二维互相关运算 2. 卷积层 卷积层中有两个参数&#xff1a;卷积核权…

鸿蒙OS投票机制

(基于openharmony5.0) 投票机制 param get | grep ohos.boot.time 图 投票机制参数图 只有当所有的投票完成&#xff0c;开机动画才会退出&#xff0c;整理需要投票的系统应用&#xff08;三方应用不参与投票&#xff09;如下图所示&#xff1a; 以进程foundation为例&…

快速理解http的get和post

在网络通信中&#xff0c;HTTP 协议扮演着非常重要的角色&#xff0c;而不同的 HTTP 方法决定了客户端与服务器之间的交互方式。 这里讲一下最常用的两种方法——GET 和 POST。 一、GET 方法 GET 方法用于从服务器获取资源。 这就像去图书馆借书——你向图书馆请求一本特定的…

光路科技TSN交换机和电力专用交换机即将亮相第31届中国国际电力设备及技术展览会

在全球能源领域正经历深刻转型之际&#xff0c;可再生能源技术的飞跃进步正为电力行业的未来开辟新径。太阳能、风能等绿色能源&#xff0c;凭借其无可比拟的优势&#xff0c;正稳步取代化石燃料&#xff0c;成为电力行业的主流趋势。多国政府积极响应&#xff0c;出台多项政策…

Vue3获取ref元素的几种方式

静态绑定 获取单个 dom 元素 v-for 中使用 需要注意的是&#xff0c;访问的时候&#xff0c;要确保 ref 引用值已经成功绑定上元素&#xff0c;我们可以使用以下几种方式确保获取

倒计时 2 天,GOSIM CHINA 2024 全日程重磅发布(附参会指南)!

伴随着全球开源技术的快速发展&#xff0c;开源已成为驱动技术创新与协作的重要力量。作为开源领域的年度盛会&#xff0c;GOSIM 大会承载这一背景下的使命&#xff0c;已连续两年聚焦全球前沿技术的突破与应用&#xff0c;推动开源技术在更多场景中的创新实践——今年&#xf…

3D数学在unity中的使用(工作小结)

前言&#xff1a; 公司的游戏&#xff0c;想实现一个类似于元气骑士前传的技能面板&#xff0c;这里的技能可以实现旋转替换。 记录一下我遇到的问题及解决办法。 如何生成这些图标 1&#xff1a;手动摆放。 优点&#xff1a;实现起来简单&#xff0c;代码量少。 缺点&…

Docker 搭建mysql 连接超时问题,xxl-job启动mysql连接报错

1.本地连接Navicat报错信息&#xff0c;猜测是navicat默认连接超时导致的&#xff0c;后面换成idea一个插件虽然慢但连接上了 2013 - Lost connection to MySQL server at reading initial communication packet 2.启动xxl-job会报错&#xff0c;网上有人mysql驱动与数据库不匹…

python 桌面程序开发

作为python新手,通过编写代码,与java、nodejs相比较,差别还有的。 环境配置: IDE:Visual Studio Code PyInstaller: 5.13.2 Python: 3.7.0 Platform: Windows-10-10.0.22621-SP0 功能描述:编写带UI界面的桌面程序,读取终端设备历史轨迹数据,采用多线程高并发,模拟终…

每日学学Java开发规范,集合处理(附阿里巴巴Java开发手册(终极版))

前言 每次去不同的公司&#xff0c;码不同的代码&#xff0c;适应不同的规范&#xff0c;经常被老大教育规范问题&#xff0c;我都有点走火入魔的感觉&#xff0c;还是要去看看阿里巴巴Java开发规范&#xff0c;从中熟悉一下&#xff0c;纠正自己&#xff0c;码出高效&#xf…

arthas常用命令(五)--heapdump 、jvm

heapdump heapdump , 类似 jmap 命令的 heap dump 功能。 dump 到指定文件。 arthas-output 是arthas 生成到项目中的文件。 dump.hprof 是自定义的 dump 文件名称。 [arthas26028]$ heapdump arthas-output/dump.hprof Dumping heap to arthas-output/dump.hprof ... Heap…

SoC芯片中Clock Gen和Reset Gen的时钟树综合

社区目前已经开设了下面列举的前四大数字后端实战课程&#xff0c;均为直播课&#xff0c;且均是小编本人亲自授课&#xff01;遇到项目问题&#xff0c;都可以远程一对一指导解决具体问题。小编本人是一线12年后端经验的数字后端工程师。想找一线IC后端技术专家亲自带你做后端…

【C语言】动态内存管理(下)

本篇博客将讲解以下知识&#xff1a; 1、calloc和realloc 2、常见的动态内存错误 1、calloc和realloc &#xff08;1&#xff09;calloc C语言中还提供了一个函数叫calloc&#xff0c;calloc也用来动态内存分配 calloc函数原型&#xff1a; void* calloc(size_t num, …

Python 入门(二、什么是 Python 的虚拟环境)

Python 入门第二课 &#xff0c;Python 的虚拟环境...... by 矜辰所致前言 本来以为环境搭建好了&#xff0c;就直接开始敲代码了&#xff0c;但是一直看到一个专业词汇&#xff1a;虚拟环境。 对于习惯了嵌入式 C 语言开发博主来说&#xff0c;一开始确实有点不明白&#xf…

在java 中如何实现执行linux命令,通过post接口代理出来?

接口方式输入命令得返回结果 public AjaxResult doPost(HttpServletRequest request, HttpServletResponse response, String command) throws ServletException, IOException {// 设置响应内容类型 text/plain // response.setContentType("application/json"…

Unite Shanghai 2024 团结引擎专场 | 团结引擎 OpenHarmony 工程剖析

在 2024 年 7 月 24 日的 Unite Shanghai 2024 团结引擎专场演讲中&#xff0c;Unity中国 OpenHarmony 技术负责人刘伟贤对团结引擎导出的 OpenHarmony 工程进行了细节剖析&#xff0c;详细讲解 XComponent 如何与引擎结合&#xff0c;UI 线程和引擎线程的关联以及 ts/ets 的代…

smbms项目(1)

目录 一、项目搭建准备工作 二、登录功能实现 三、注销功能实现 四、登录拦截实现 一、项目搭建准备工作 1、搭建一个maven web项目 2、配置Tomcat 3、测试项目是否能够跑起来 4、导入项目中会遇到的jar包&#xff08;servlet、jsp、mysql驱动、jstl、standard&#xf…