嵌入式AI-STM32部署卷积神经网络的哈利波特魔法棒方案项目

news2024/11/23 11:19:46

项目标题-电子法棒-卷积神经网络轨迹识别方案

基于STM32部署卷积神经网络控制设备方案-AI项目-STM32部署卷积神经网络方案-红外信号复制方案-轨迹识别

先了解一下背景,STM32是一款微控制器,做AI一般都是拥有算力的微处理器,量产非常昂贵,但是此项目解决微控制器能部署AI 卷积神经网络的技术,并且调研过目前位于深圳等一线城市,也有对控制器部署AI的客户需求,市场量很大。

项目包含下述内容

- 硬件部分、PCB制板、BOM表文件等等 (Hardware)
- 外壳、3D打印文件、如果你有想法也可以告诉我、帮你设计~ (3D_print)
- 软件程序、用于电子法棒的软件程序 AI + Keil等等(Software)
- QT上位机动作识别模型训练脚本 (用于训练模型等功能)(Upper_computer
- 图示、Demo中的动作轨迹图例(Model_trajectories)
- 环境安装包、工具包(Install_package) + 嵌入式神经网络部署源码(NNOM_Demo)
- 二次开发方案
- 技术支持、全项目内容答疑
- 项目持续更新中(任何问题和想法 功能都可以跟我提,我会慢慢跟进解决)

如下为整个资料包的目录

目录

项目标题-电子法棒-卷积神经网络轨迹识别方案

1.项目简介

2.系统架构

3.项目资源包整体介绍

4.开发环境配置说明

5.安装与配置(手把手)

6.功能使用说明(必看)

7.二次开发方案

8.训练模型-训练自己想要的轨迹

9.软件部分-从0到1源码详解

10.QT上位机+AI部分-卷积神经网络详解

11.硬件部分-原理图 PCB部分讲解

12.外壳部分-3D打印工程详解 *

13.常见错误问题汇总

14.持续更新


1.项目简介

目的:不管是学习还是工作过程中,项目比重的占比都是绝对性的,没有好的项目是无法学习的,非常多的人来找我要新颖的项目。经调研,发现项目如此重要,很多人的项目无非就是小车跟智能家居,大家都是雷同的培训班之类的项目,没有任务意义。也去展会看过,甚至培训班也都是小车以及智能家居套壳,硬件卖的贵,扩展性也没有。

解决痛点:(经调研

1.市场需求在变,客户方案也在变,AI结合嵌入式是主流,需更迭新鲜的血液,这是你要与老工程师拉开的路径

2.市面上无案例,不同于市面上小车,智能家居等等一味堆叠传感器的方案培训班项目,写简历都是大同小异!

3.市面上针对嵌入式AI项目案例几乎没有,都是人脸识别等类型的项目大同小异,此项目方案为此打破常规 直接在最低成本的stm32运行卷积神经网络!
4.项目可以二次开发,模块化架构编程思维,不同于培训班以及市面项目,可以迭代成自己的产品,不像别的项目学了自己也无法迭代。
5.即使你是业内人员你也无法感受到整个产品流程,项目从硬件到软件到上位机到外壳设计一整套流程,让你明白业务流程与工程流程,正真设计产品的流程。

功能:(通俗易懂)

​      设备会根据你的手势轨迹动作,去发出指令,控制设备。你可以理解是一个哈利波特魔法棒。

1.设备的手势可以自行添加,目前13个手势,你可以任意添加你想要的手势轨迹,配备QT上位机给你添加(还可以学到QT)

2.设备会根据你做的动作判断是否正确,是否是动作集里面的动作,去发出控制设备的信号,比如空调的开关,温度的升降,任何牌子的都可以(格力美的等等都可以 本质是复制红外信号 调研过开源案例没有此方法)

本质是STM32部署卷积神经网络方案,如果学以致用,你也可以做出眼球跟踪项目等等,本质都是一样的方案~

2.系统架构

硬件架构:STM32F103、MPU6050、红外传感器、电池降压电路、电源管理IC、电源选择电路、充电管理电路、充电管理IC等等

软件架构:QT、ARM、Tensorflow、Keras、实战算法、nnom(嵌入式AI推理库)等等

3.项目资源包整体介绍

4.开发环境配置说明

Keil环境配置:

  • Keil 版本:请使用 Keil 5,建议从 Keil 官网下载最新版,避免遇到兼容性问题。

  • 编译器版本:请选择 Arm Compiler 6.22 作为编译器,确保项目能够顺利编译。

  • 调试器设置:根据您的设备选择合适的调试器,例如 ST-Link 或其他兼容设备,以进行程序调试。

  • 库文件安装:首次打开项目时,Keil 可能提示需要安装缺失的库,请根据提示进行安装,以确保项目能够正常运行。

5.安装与配置(手把手)

我已经放在Install_package中,详情可以看资料包。

6.功能使用说明(必看)

更多详情在资料包中,不再赘述。。

7.二次开发方案

- 基于卷积神经网络框架 NNOM_Demo 此项目已经移植好框架内容 本质就是训练模型然后量化推理的过程
  - 眼球追踪 Eye Tracking
    - 使用卷积神经网络对眼睛图像进行检测,定位瞳孔位置
  - 姿态估计 Pose Estimation
    - 利用摄像头或IMU传感器,实时检测人体的姿态,并基于CNN进行分类和估计。可用于运动分析、健康监测等场景

​    通过这些扩展方案,项目可以覆盖更多的应用场景,进一步提升智能化和交互性,特别是在嵌入式设备的实际应用中

例如 射频模块 wifi 蓝牙等等  同样可以进行插桩二次开发在设备上

8.训练模型-训练自己想要的轨迹

选择你想要的运动轨迹 运功轨迹可以做你自己想做的 也可以重新训练原本的 原本的轨迹图像位于 .\电子法棒\Model_trajectories中

更多详情根据资料包所示。

9.软件部分-从0到1源码详解

nnom:

NNOM 是一个轻量级的、专为嵌入式设备设计的神经网络库,特别适用于 **ARM Cortex-M** 等资源有限的微控制器(MCU)。它的主要功能是将深度学习模型(如卷积神经网络,CNN)在低功耗、内存受限的嵌入式环境中进行推理(inference)让嵌入式系统能够执行机器学习任务,尤其是涉及神经网络的推理过程。

轻量化:NNOM 被设计为非常轻量的库,适合在内存和处理能力有限的嵌入式设备上运行,尤其是那些没有硬件加速器的设备。

跨平台:主要针对 Cortex-M 系列的处理器设计,但也可以在其他平台上移植使用。它依赖 CMSIS-NN,后者提供了一些优化的神经网络运算加速。

支持模型量化:NNOM 支持量化的神经网络模型,这在嵌入式设备上非常重要,因为量化模型可以大大减少内存和计算需求,适合资源受限的环境。

兼容性:NNOM 可以与 TensorFlow 和 Keras 等主流深度学习框架进行兼容,通过模型转换工具可以将训练好的模型转换成适合嵌入式系统使用的格式。

灵活性:NNOM 支持多种网络结构和层,如卷积层(Conv)、池化层(Pooling)、全连接层(Fully Connected)、激活函数(Activation)等,用户可以根据需要设计不同的网络架构。

开源和可定制:NNOM 是开源的,可以根据具体应用进行定制和修改。
[GitHub - majianjia/nnom: A higher-level Neural Network library for microcontrollers.](

具体代码位于资料包中。。

10.QT上位机+AI部分-卷积神经网络详解

具体代码位于资料包中。。

import sys
import os
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers, models, optimizers, losses, callbacks, utils  # type: ignore TensorFlow
from tensorflow.keras.preprocessing.sequence import pad_sequences  # type: ignore TensorFlow
from sklearn.metrics import classification_report
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QPushButton, QLabel, QVBoxLayout, QWidget, QMessageBox
import re

# 动作分类名
motion_names = [
    'RightAngle', 'SharpAngle', 'Lightning', 'Triangle', 'Letter_h', 'letter_R', 
    'letter_W', 'letter_phi', 'Circle', 'UpAndDown', 'Horn', 'Wave', 'NoMotion'
]

# 定义目录路径和文件名
DEF_MODEL_NAME = 'model.h5'
DEF_WEIGHTS_NAME = 'weights.h'
DEF_FILE_MAX = 100
DEF_N_ROWS = 150
DEF_COLUMNS = (3, 4, 5)
DEF_FILE_FORMAT = '.txt'
DEF_FILE_NAME_SEPERATOR = '_'
DEF_BATCH_SIZE = 120
DEF_NUM_EPOCH = 200

# 动作名称到标签的映射
motion_to_label = {name: idx for idx, name in enumerate(motion_names)}

# 定义训练函数
def train(x_train, y_train, x_test, y_test, model_path, weights_path, 
          input_shape=(DEF_N_ROWS, 3, 1), num_classes=len(motion_names), 
          batch_size=DEF_BATCH_SIZE, epochs=DEF_NUM_EPOCH):
    
    inputs = layers.Input(shape=input_shape)
    x = layers.Conv2D(30, kernel_size=(3, 3), strides=(3, 1), padding='same')(inputs)
    x = layers.LeakyReLU()(x)
    x = layers.Conv2D(15, kernel_size=(3, 3), strides=(3, 1), padding='same')(x)
    x = layers.LeakyReLU()(x)
    x = layers.AveragePooling2D(pool_size=(3, 1), strides=(3, 1))(x)
    x = layers.Flatten()(x)
    x = layers.Dense(num_classes)(x)
    outputs = layers.Softmax()(x)
    
    model = models.Model(inputs=inputs, outputs=outputs)
    model.compile(optimizer=optimizers.Adam(), loss=losses.CategoricalCrossentropy(), metrics=['accuracy'])
    
    early_stopping = callbacks.EarlyStopping(monitor='val_loss', patience=10)
    checkpoint = callbacks.ModelCheckpoint(model_path, monitor='val_accuracy', save_best_only=True, mode='max')
    
    history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, verbose=2, 
                        validation_data=(x_test, y_test), shuffle=True, 
                        callbacks=[early_stopping, checkpoint])
    
    # Save the model weights
    model.save_weights(weights_path)
    
    del model
    tf.keras.backend.clear_session()
    
    return history

# 加载数据集函数
def load_dataset(root_dir, max_rows=None):
    file_list = []
    labels = []
    for filename in os.listdir(root_dir):
        if filename.endswith(DEF_FILE_FORMAT):
            match = re.match(rf'^([\w]+)_([\d]+){DEF_FILE_FORMAT}$', filename)
            if match:
                motion_name = match.group(1)
                number_str = match.group(2)
                number = int(number_str)
                if 0 <= number <= DEF_FILE_MAX:
                    if motion_name in motion_to_label:
                        file_path = os.path.join(root_dir, filename)
                        data = np.loadtxt(file_path, delimiter=' ', usecols=DEF_COLUMNS, max_rows=max_rows)
                        file_list.append(data)
                        labels.append(motion_to_label[motion_name])
                    else:
                        print(f"Motion name not recognized: {filename}")
                else:
                    print(f"Number out of range: {filename}")
            else:
                print(f"Invalid file name format: {filename}")
    return file_list, labels

# PyQt5 界面定义
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("训练模型")
        self.setGeometry(100, 100, 400, 300)
        
        self.label = QLabel("请选择数据文件夹", self)
        self.select_button = QPushButton("选择数据文件夹", self)
        self.train_button = QPushButton("开始训练", self)
        self.model_path_button = QPushButton("选择模型保存路径", self)
        self.weights_path_button = QPushButton("选择权重保存路径", self)
        self.model_path_label = QLabel("模型保存路径: 未选择", self)
        self.weights_path_label = QLabel("权重保存路径: 未选择", self)
        
        self.select_button.clicked.connect(self.select_directory)
        self.train_button.clicked.connect(self.start_training)
        self.model_path_button.clicked.connect(self.select_model_path)
        self.weights_path_button.clicked.connect(self.select_weights_path)
        
        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.select_button)
        layout.addWidget(self.model_path_label)
        layout.addWidget(self.weights_path_label)
        layout.addWidget(self.model_path_button)
        layout.addWidget(self.weights_path_button)
        layout.addWidget(self.train_button)
        
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)
        
        self.data_directory = ""
        self.model_path = DEF_MODEL_NAME
        self.weights_path = DEF_WEIGHTS_NAME
        
    def select_directory(self):
        directory = QFileDialog.getExistingDirectory(self, "选择数据文件夹")
        if directory:
            self.data_directory = directory
            self.label.setText(f"选择的文件夹: {self.data_directory}")
            
    def select_model_path(self):
        path, _ = QFileDialog.getSaveFileName(self, "选择模型保存路径", DEF_MODEL_NAME, "HDF5 Files (*.h5);;All Files (*)")
        if path:
            self.model_path = path
            self.model_path_label.setText(f"模型保存路径: {self.model_path}")
        
    def select_weights_path(self):
        path, _ = QFileDialog.getSaveFileName(self, "选择权重保存路径", DEF_WEIGHTS_NAME, "HDF5 Files (*.h5);;All Files (*)")
        if path:
            self.weights_path = path
            self.weights_path_label.setText(f"权重保存路径: {self.weights_path}")
        
    def start_training(self):
        if not self.data_directory:
            QMessageBox.warning(self, "警告", "请先选择数据文件夹")
            return
        if self.model_path == DEF_MODEL_NAME:
            QMessageBox.warning(self, "警告", "请先选择模型保存路径")
            return
        if self.weights_path == DEF_WEIGHTS_NAME:
            QMessageBox.warning(self, "警告", "请先选择权重保存路径")
            return
        
        # 加载数据集
        file_list, labels = load_dataset(self.data_directory, max_rows=DEF_N_ROWS)
        
        # 数据预处理
        max_len = max([len(x) for x in file_list])
        file_list_padded = pad_sequences(file_list, maxlen=max_len, dtype='float32', padding='post', value=0)
        labels_one_hot = utils.to_categorical(labels, num_classes=len(motion_names))
        
        # 数据集分割
        num_elements = len(file_list_padded)
        train_size = int(num_elements * 0.8)
        
        best_val_accuracy = 0
        best_model = None
        for _ in range(3):
            indices = np.arange(num_elements)
            np.random.shuffle(indices)
            
            train_indices = indices[:train_size]
            test_indices = indices[train_size:]
            
            x_train = file_list_padded[train_indices]
            y_train = labels_one_hot[train_indices]
            x_test = file_list_padded[test_indices]
            y_test = labels_one_hot[test_indices]
            
            history = train(x_train, y_train, x_test, y_test, model_path=self.model_path, 
                            weights_path=self.weights_path, batch_size=DEF_BATCH_SIZE, epochs=DEF_NUM_EPOCH)
            
            model = tf.keras.models.load_model(self.model_path)
            
            y_pred = model.predict(x_test)
            y_pred_classes = np.argmax(y_pred, axis=1)
            y_true_classes = np.argmax(y_test, axis=1)
            
            val_accuracy = history.history['val_accuracy'][-1]
            print(f"Validation Accuracy: {val_accuracy:.4f}")
            
            if val_accuracy > best_val_accuracy:
                best_val_accuracy = val_accuracy
                best_model = model
        
        if best_model is not None:
            y_pred = best_model.predict(x_test)
            y_pred_classes = np.argmax(y_pred, axis=1)
            y_true_classes = np.argmax(y_test, axis=1)
            
            print(classification_report(y_true_classes, y_pred_classes, target_names=motion_names))
            
            QMessageBox.information(self, "训练完成", "模型训练完成,最佳模型及其权重已保存。")
        else:
            QMessageBox.warning(self, "训练失败", "没有找到最佳模型。")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

11.硬件部分-原理图 PCB部分讲解

详情资料包。。。。PCB原理图BOM表等等。。

12.外壳部分-3D打印工程详解 *

详情资料包。。。。

13.常见错误问题汇总

14.持续更新

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2209615.html

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

相关文章

RTKLIB学习记录【postpos、execses_b、execses_r】

本文主要记录对RTKLIB源码中postpos、execses_b、execses_r 函数的源码解读&#xff0c;不涉及其中的天线、星历等文件读取的内容&#xff0c;且为个人理解&#xff0c;如果有误&#xff0c;欢迎交流讨论。 一、postpos 函数部分 /rxn2rtkp函数 → postpos函数传递参数&#x…

TCP IP网络编程

文章目录 TCP IP网络编程一、基础知识&#xff08;TCP&#xff09;1&#xff09;Linux1. socket()2.bind()2.1前提2.2字节序与网络字节序2.3 字节序转换2.4 字符串信息转化成网络字节序的整数型2.5 INADDR_ANY 3.listen()4.accept()5.connect()6.案例小结6.1服务器端6.2 客户端…

《机器学习与数据挖掘综合实践》实训课程教学解决方案

一、引言 随着信息技术的飞速发展&#xff0c;人工智能已成为推动社会进步的重要力量。作为人工智能的核心技术之一&#xff0c;机器学习与数据挖掘在各行各业的应用日益广泛。本方案旨在通过系统的理论教学、丰富的实践案例和先进的实训平台&#xff0c;帮助学生掌握机器学习…

基于YOLO11深度学习的非机动车驾驶员头盔检测系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、卷积神经网络

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

对秋季新款上衣的数据分析

秋季新款上衣评论分析 1.评论的基本统计分析(数据来源&#xff1a;淘宝评论信息接口) 接口链接&#xff1a;https://easydoc.net/s/42414529&#xff08;有需要调用接口的可以联系博主&#xff09; 评论长度分布图&#xff1a; 根据接口拉取数据获得的评论数据&#xff0c;并…

使用Ubuntu开发Zephyr RTOS时遇到FATAL ERROR: required program JLinkExe not found...解决办法

硬件平台&#xff1a;STM32L432RCT6 zephyr版本&#xff1a;Zephyr version 3.7.99 开发环境&#xff1a;ubuntu 24.4 在Ubuntu环境下遇到FATAL ERROR: required program JLinkExe not found; install it or add its location to PATH错误&#xff0c;意味着系统找不到JLinkEx…

Linux系统的用户和用户群组的各种权限总结

Linux系统的用户和用户群组的各种权限总结 用户群组用户群组文件添加群组&#xff1a;groupadd 用户用户文件新建用户&#xff1a;useradd修改用户&#xff1a;usermod删除用户&#xff1a;userdel 文件归属&#xff1a;chown文件权限&#xff1a;chmod相关文件和目录 用户群组…

网络通信——流量与路由(万字解读)

前言:流量控制与路由更新控制 如何控制网络流量可达性? 方案一:可通过修改路由条目(即对接收和发布的路由进行过滤)来控制流量可达性,这种方式称为路由策略。 方案二:可直接通过依据用户制定的策略进行转发,且该策略优于路由表转发,这种方式称为策略路由。 (1)什么…

音视频入门基础:H.264专题(18)——AVCDecoderConfigurationRecord简介

一、引言 H.264流行的包装方式有两种&#xff0c;一种是AnnexB&#xff0c;另一种是avcC。对于AnnexB包装的H.264码流&#xff0c;其SPS和PPS被当做普通的NALU来处理&#xff1b;而对于avcC包装的H.264码流&#xff0c;其SPS和PPS信息存贮在AVCDecoderConfigurationRecord中&a…

Java项目实战II基于Java+Spring Boot+MySQL的服装销售平台(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在当今数字…

C++面试速通宝典——24

452. Linux进程地址空间 文本段&#xff1a;包含程序的可执行代码初始化数据段&#xff08;数据段&#xff09;&#xff1a;包含已初始化的全局变量和静态变量。未初始化数据段&#xff08;BSS段&#xff09;&#xff1a;包含未初始化的全局变量和静态变量。堆&#xff1a;动态…

Python 在Excel中添加数据条

在Excel中添加数据条是一种数据可视化技巧&#xff0c;它通过条形图的形式在单元格内直观展示数值的大小&#xff0c;尤其适合比较同一列或行中各个单元格的数值。这种表示方式可以让大量的数字信息一目了然。本文将介绍如何使用Python在Excel中的指定单元格区域添加数据条。 …

Apache HTTP Server 配置SSL证书(Windows)

Apache2.4.39 HTTP Server 配置SSL证书 1. 申请证书 申请证书步骤(略) 证书名称如下: ca-bundle.crt xxx_com.crt xxx_com.key2. 配置 证书位置:Apache2.4.39\conf\ssl Apache2.4.39\conf\ssl修改两个配置文件: 0localhost_80.conf <VirtualHost *:80>Rewrite…

【HTML格式PPT离线到本地浏览】

文章目录 概要实现细节小结 概要 最近在上课时总是出现网络不稳定导致的PPT无法浏览的情况出现&#xff0c;就想到下载到电脑上。但是PPT是一个HTML的网页&#xff0c;无法通过保存网页&#xff08;右键另存为mhtml只能保存当前页&#xff09;的形式全部下载下来&#xff0c;试…

数据库实验2-2

10-1 将学号为“1911203”的学生的联系电话改为“590987” 本题目要求编写SQL语句&#xff0c; 在students表中&#xff0c;将学号为“1911203”的学生的联系电话改为“590987”。 提示&#xff1a;请使用UPDATE语句作答。 表结构: 请在这里写定义表结构的SQL语句。例如&am…

碳钎维:无人机轻量化关键材料!

一、碳纤维材料特性 轻质高强&#xff1a; 碳纤维是一种含碳量在95%以上的高强度新型纤维材料&#xff0c;具有极高的比强度和比刚度。 在同等重量下&#xff0c;其拉伸强度可达到钢、铝合金、钛合金的9倍以上&#xff0c;弹性模量可以达到钢、铝合金、钛合金的4倍以上。 耐…

c语言经典100例

1.字符串转为数字 #include <stdio.h>int strToInt(char *s) {int num0;int sign1;int step1;if (*s -){sign -1;s;}while (*s > 0&&*s < 9){num num*10(*s-0);step 10;s;}return num*sign; }int main() {char a[10] "-1234";char *s a ;pr…

git删除错误的commit

文章目录 1、git删除错误的commit2、.gitignore配置文件不生效的问题 1、git删除错误的commit git的流程如图&#xff1a; 当某次失误造成commit的版本有问题&#xff0c;需要回退到正常的版本修改后重新add。 首先通过git log查看commit提交记录&#xff0c;可以看到HEAD-…

2024最新版安装教程!Python安装+PyCharm安装使用教程!!(非常简单)

Python下载安装 一、进入Python官网首页&#xff0c;下载最新版的Python 官方网址&#xff1a;Download Python | Python.org 鼠标悬浮在Downloads&#xff0c;选择最新版本 注意&#xff1a;由于Python官网服务器设立在国外&#xff0c;所以下载速度非常慢&#xff0c;我这…

在centos(ubuntu)中如何通过预构建二进制文件安装nodejs

首先去Node.js下载你说需要的版本的预构建二进制文件Node.js — 下载 Node.js 在CentOs或Ubuntu离线服务器上安装Node.js&#xff0c;你可以通过下载Node.js的预构建二进制文件来完成。以下是具体步骤&#xff1a; 获取Node.js预构建二进制文件&#xff1a; 在有网络连接的机器…