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++代码解释篇幅较长,见后续文章
待续
相关文章:

Canmv k230 C++案例1——image classify学习笔记 初版
00 简介 用C编写代码的比mircopython要慢很多,需要编译开发环境,同时使用C更接近底层,效率利用率应该也是更高的,就是需要学习更多的内容,因为从零开始因此比较比较耗时。 注:以下为个人角度的理解&#x…...

vs2022 dump调试
程序中加入了捕获dump得代码,那么当程序crash时,通常可以捕获到dump文件。当然,也有一些崩溃是捕获不到的。本文就捕获到的dump文件,总结一下调试的流程。 前提:exe,pdb,dump 3者是放在同一目录…...

OpenCV高级图形用户界面(11)检查是否有键盘事件发生而不阻塞当前线程函数pollKey()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 轮询已按下的键。 函数 pollKey 无等待地轮询键盘事件。它返回已按下的键的代码或如果没有键自上次调用以来被按下则返回 -1。若要等待按键被按…...

nvm安装,node多版本管理
卸载nodejs win R 输入 appwiz.cpl 删除 node.js查看node.js安装路径是否有残留,有就删除文件夹 删除下列路径文件,一定要检查,没删干净,nvm安装会失败 C:\Program Files (x86)\NodejsC:\Program Files\NodejsC:\Users{User}\…...

ThingsBoard规则链节点:Assign To Customer节点详解
引言 分配给客户节点概述 用法 含义 应用场景 实际项目运用示例 结论 引言 在物联网(IoT)解决方案中,ThingsBoard平台以其高效的数据处理能力和灵活的设备管理功能而著称。其中,规则引擎是该平台的一个核心组件,…...

自监督行为识别-时空线索解耦(论文复现)
自监督行为识别-时空线索解耦(论文复现) 本文所涉及所有资源均在传知代码平台可获取 文章目录 自监督行为识别-时空线索解耦(论文复现)引言论文概述核心创新点双向解耦编码器跨域对比损失的构建结构化数据增强项目部署准备工作数据…...
MyBatisPlus:自定义SQL
由于SQL不能写在业务层,所以可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分 ①基于Wrapper 构建Where条件 Testpublic void test7(){//需求:将id满足ids的数据项的balance字段减200int amount200;List…...

变电站谐波治理设备有哪些
在变电站中,由于非线性负载(如电力电子设备、变频器等)会引入谐波,对电网造成干扰,因此需要进行谐波治理。以下是常见的变电站谐波治理设备及其特点: 1、静止无功发生器(SVG) 工作原…...

Mybatis全局配置介绍
【mybatis全局配置介绍】 mybatis-config.xml,是MyBatis的全局配置文件,包含全局配置信息,如数据库连接参数、插件等。整个框架中只需要一个即可。 1、mybatis全局配置文件是mybatis框架的核心配置,整个框架只需一个;…...
error: cannot find symbol import android.os.SystemProperties;
背景:AS独立编译系统工程应用,使用了hide接口,导致编译不过。 尽管使用了framework.jar依赖。依然编译不过,导致各种类找不到。 例如: /SettingsLib/src/main/java/com/android/settingslib/location/RecentLocatio…...
债券市场金融基础设施 (2020版)
前言:我国债券市场格局简介 我国金融市场主要包括货币市场、资本市场、外汇市场、金融衍生工具市场等,其中,资本市场是金融市场重要组成部分,承担着实体经济直接融资的重责,做大做强资本市场对整个国民经济具有重要意义。债券市场是资本市场两大组成部分之一,对提高直接…...

OpenCV高级图形用户界面(8)在指定的窗口中显示一幅图像函数imshow()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在指定的窗口中显示一幅图像。 函数 imshow 在指定的窗口中显示一幅图像。如果窗口是以 cv::WINDOW_AUTOSIZE 标志创建的,图像将以原…...

for循环和while循环的区别
for循环和while循环的主要区别在于使用场景和结构。for循环适合已知循环次数的情况,而while循环则更灵活,适用于条件动态变化的情况。 for循环的特点 1. 已知迭代次数:for循环在开始前就需要知道具体的迭代次数。例如,遍历一个列…...
机器学习和神经网络的研究与传统物理学的关系
机器学习和神经网络的研究与传统物理学的关系 机器学习和神经网络是现代科学研究中非常热门的领域,它们与传统物理学在某些方面有着密切的关系,在人类科学研究中相互影响和促进作用也越来越显著。 首先,机器学习和神经网络在物理学研究中具…...

LabVIEW提高开发效率技巧----事件触发模式
事件触发模式在LabVIEW开发中是一种常见且有效的编程方法,适用于需要动态响应外部或内部信号的场景。通过事件结构(Event Structure)和用户自定义事件(User Events),开发者可以设计出高效的事件驱动程序&am…...

Kimi AI助手重大更新:语音通话功能闪亮登场!
Kimi人工智能助手近日发布了一项令人瞩目的重大更新,其中最引人注目的是新增的语音通话功能。这一创新不仅拓展了用户与AI互动的方式,还为学习和工作场景提供了突破性的解决方案。 Ai 智能办公利器 - Ai-321.com 人工智能 - Ai工具集 - 全球热门人工智能…...

Linux——进程管理
目录 进程基础 ps 显示系统执行的进程 终止进程 kill 和 killall pstree 查看进程树 服务(service)管理 service 管理指令 服务的运行级别(runlevel) chkconfig 设置服务在不同运行级别下是否开启自启动 systemctl 管理…...
【ARM 嵌入式 编译系列 2.9 -- GCC 编译如何避免赋值判断 if ( x = 0)】
> ARM GCC 编译精讲系列课程链接 < 文章目录 GCC 编译避免赋值判断参数说明示例编译命令解决方法 GCC 编译避免赋值判断 在 GCC 编译中,为了避免误将赋值操作用于条件判断(例如 if (break_var 0x0))导致的错误,可以使用 -…...

PyTorch搭建GNN(GCN、GraphSAGE和GAT)实现多节点、单节点内多变量输入多变量输出时空预测
目录 I. 前言II. 数据集说明III. 模型3.1 GCN3.2 GraphSAGE3.3 GAT IV. 训练与测试V. 实验结果 I. 前言 前面已经写了很多关于时间序列预测的文章: 深入理解PyTorch中LSTM的输入和输出(从input输入到Linear输出)PyTorch搭建LSTM实现时间序列…...

51单片机快速入门之数码管的拓展应用2024/10/15
51单片机快速入门之数码管的拓展应用 在前面的文章中,我们已经了解到数码管的基础应用,今天来讲讲拓展应用 我们知道单个数码管分为以下 但是当我们碰到 如下这种数码管的时候又应该如何去控制呢? 这里就不得不说其拓展应用之-----------扫描显示 扫描显示: 扫描显示,又称…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...
32位寻址与64位寻址
32位寻址与64位寻址 32位寻址是什么? 32位寻址是指计算机的CPU、内存或总线系统使用32位二进制数来标识和访问内存中的存储单元(地址),其核心含义与能力如下: 1. 核心定义 地址位宽:CPU或内存控制器用32位…...