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

vue 音频播放控件封装
<template> <div> <audio @timeupdate="updateProgress" controls ref="audioRef" style="display: none" > <source :src="audioUrl" type="audio/mpeg" /> 您的浏览器不支持音频播放 </audio&…...

秋招面试题记录
嵌入式软件开发 网上搜集的题目 1.Static关键词的作用? static 关键字有三个主要作用: 局部变量:在函数内部,static 局部变量只初始化一次,且在函数调用结束后仍然保留其值。全局变量/函数:在文件内部&a…...

金字塔流(Pyramid Flow): 用于生成人工智能长视频的新文本-视频开源模型
在 "生成式人工智能 "中的文本生成模型和图像生成模型大行其道之后,现在该是文本-视频模型大显身手的时候了,这个列表中的新模型就是 pyramid-flow-sd3,它是一个开源模型,用于从文本或图像生成长达 10 秒的视频…...

施磊C++ | 进阶学习笔记 | 5.设计模式
五、设计模式 文章目录 五、设计模式1.设计模式三大类型概述一、创建型设计模式二、结构型设计模式三、行为型设计模式 2.设计模式三大原则3.单例模式1.饿汉单例模式2.懒汉单例模式 4.线程安全的懒汉单例模式1.锁双重判断2.简洁的线程安全懒汉单例模式 5.简单工厂(Simple Facto…...

智绘城市地图:使用百度地图 API 实现智能定位
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...

【稳定性】稳定性建设之变更管理
作者:京东物流 冯志文 背景 在软件开发和运维领域,变更管理是一个至关重要的环节。无论是对现有系统的改进、功能的增加还是修复漏洞,变更都是不可避免的。这些变更可能涉及到软件代码的修改、配置的调整、服务器的扩容、三方jar包的变更等等…...

c语言中字符串函数strlen,strcmp,strcpy,srtcat,strncpy,strncmp,strncat
1.strlen的使用和模拟实现 strlen 用来求字符串的长度,统计\0之前字符的个数。 模拟实现1:计数参数法 模拟实验2:指针方法 模拟实验3:递归方法 2,strcpy 的使用和模拟实现(拷贝字符串) char*…...

高级SQL技巧
高级SQL技巧涵盖了许多方面,包括但不限于窗口函数、递归查询、公共表表达式(CTEs)、子查询、集合操作、临时函数、日期时间操作、索引优化等。以下是对这些技巧的详细讲解和示例。 窗口函数 窗口函数是一种特殊的SQL函数,能够在…...

新大话西游图文架设教程
开始架设 1. 架设条件 新大话西游架设需要准备: linux 系统服务器,建议 CentOs 7.6或以上版本游戏源码,。 2. 安装宝塔面板 宝塔是一个服务器运维管理软件,安装命令: yum install -y wget && wget -O in…...

Maven 快速入门
Maven 快速入门 一、简介1、概述2、特点3、工作原理4、常用命令5、生命周期6、优缺点🎈 面试题 二、安装和配置1、安装2、环境配置3、命令测试是否安装成功4、功能配置5、idea配置本地 maven6、maven 工程依赖包查询网站 三、基于IDEA创建Maven工程1、maven 工程中的…...