- 2025-02-16
-
发表了主题帖:
【嘉楠K230开发板】跌倒检测
本篇讲述使用K230进行跌倒检测。
一.原理流程
跌倒检测主要根据人体姿态来判断,可以用于老人、小孩跌倒监护。CanMV AI视觉框架和MicroPython编程能快速实现人体关键点检测。
编程思路流程如下:
二.代码实现与测验
迭代检测代码如下:
'''
跌倒检测
'''
from libs.PipeLine import PipeLine, ScopedTiming
from libs.AIBase import AIBase
from libs.AI2D import Ai2d
import os
import ujson
from media.media import *
from time import *
import nncase_runtime as nn
import ulab.numpy as np
import time
import utime
import image
import random
import gc
import sys
import aicube
# 自定义跌倒检测类,继承自AIBase基类
class FallDetectionApp(AIBase):
def __init__(self, kmodel_path, model_input_size, labels, anchors, confidence_threshold=0.2, nms_threshold=0.5, nms_option=False, strides=[8,16,32], rgb888p_size=[224,224], display_size=[1920,1080], debug_mode=0):
super().__init__(kmodel_path, model_input_size, rgb888p_size, debug_mode) # 调用基类的构造函数
self.kmodel_path = kmodel_path # 模型文件路径
self.model_input_size = model_input_size # 模型输入分辨率
self.labels = labels # 分类标签
self.anchors = anchors # 锚点数据,用于跌倒检测
self.strides = strides # 步长设置
self.confidence_threshold = confidence_threshold # 置信度阈值
self.nms_threshold = nms_threshold # NMS(非极大值抑制)阈值
self.nms_option = nms_option # NMS选项
self.rgb888p_size = [ALIGN_UP(rgb888p_size[0], 16), rgb888p_size[1]] # sensor给到AI的图像分辨率,并对宽度进行16的对齐
self.display_size = [ALIGN_UP(display_size[0], 16), display_size[1]] # 显示分辨率,并对宽度进行16的对齐
self.debug_mode = debug_mode # 是否开启调试模式
self.color = [(255,0, 0, 255), (255,0, 255, 0), (255,255,0, 0), (255,255,0, 255)] # 用于绘制不同类别的颜色
# Ai2d实例,用于实现模型预处理
self.ai2d = Ai2d(debug_mode)
# 设置Ai2d的输入输出格式和类型
self.ai2d.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT, nn.ai2d_format.NCHW_FMT, np.uint8, np.uint8)
# 配置预处理操作,这里使用了pad和resize,Ai2d支持crop/shift/pad/resize/affine,具体代码请打开/sdcard/libs/AI2D.py查看
def config_preprocess(self, input_image_size=None):
with ScopedTiming("set preprocess config", self.debug_mode > 0): # 计时器,如果debug_mode大于0则开启
ai2d_input_size = input_image_size if input_image_size else self.rgb888p_size # 初始化ai2d预处理配置,默认为sensor给到AI的尺寸,可以通过设置input_image_size自行修改输入尺寸
top, bottom, left, right = self.get_padding_param() # 获取padding参数
self.ai2d.pad([0, 0, 0, 0, top, bottom, left, right], 0, [0,0,0]) # 填充边缘
self.ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) # 缩放图像
self.ai2d.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) # 构建预处理流程
# 自定义当前任务的后处理,results是模型输出array的列表,这里使用了aicube库的anchorbasedet_post_process接口
def postprocess(self, results):
with ScopedTiming("postprocess", self.debug_mode > 0):
dets = aicube.anchorbasedet_post_process(results[0], results[1], results[2], self.model_input_size, self.rgb888p_size, self.strides, len(self.labels), self.confidence_threshold, self.nms_threshold, self.anchors, self.nms_option)
return dets
# 绘制检测结果到画面上
def draw_result(self, pl, dets):
with ScopedTiming("display_draw", self.debug_mode > 0):
if dets:
pl.osd_img.clear() # 清除OSD图像
for det_box in dets:
# 计算显示分辨率下的坐标
x1, y1, x2, y2 = det_box[2], det_box[3], det_box[4], det_box[5]
w = (x2 - x1) * self.display_size[0] // self.rgb888p_size[0]
h = (y2 - y1) * self.display_size[1] // self.rgb888p_size[1]
x1 = int(x1 * self.display_size[0] // self.rgb888p_size[0])
y1 = int(y1 * self.display_size[1] // self.rgb888p_size[1])
x2 = int(x2 * self.display_size[0] // self.rgb888p_size[0])
y2 = int(y2 * self.display_size[1] // self.rgb888p_size[1])
# 绘制矩形框和类别标签
pl.osd_img.draw_rectangle(x1, y1, int(w), int(h), color=self.color[det_box[0]], thickness=2)
pl.osd_img.draw_string_advanced(x1, y1-50, 32," " + self.labels[det_box[0]] + " " + str(round(det_box[1],2)), color=self.color[det_box[0]])
else:
pl.osd_img.clear()
# 获取padding参数
def get_padding_param(self):
dst_w = self.model_input_size[0]
dst_h = self.model_input_size[1]
input_width = self.rgb888p_size[0]
input_high = self.rgb888p_size[1]
ratio_w = dst_w / input_width
ratio_h = dst_h / input_high
if ratio_w < ratio_h:
ratio = ratio_w
else:
ratio = ratio_h
new_w = int(ratio * input_width)
new_h = int(ratio * input_high)
dw = (dst_w - new_w) / 2
dh = (dst_h - new_h) / 2
top = int(round(dh - 0.1))
bottom = int(round(dh + 0.1))
left = int(round(dw - 0.1))
right = int(round(dw - 0.1))
return top, bottom, left, right
if __name__ == "__main__":
# 显示模式,默认"hdmi",可以选择"hdmi"和"lcd"
display_mode="lcd"
if display_mode=="hdmi":
display_size=[1920,1080]
else:
display_size=[800,480]
# 设置模型路径和其他参数
kmodel_path = "/sdcard/examples/kmodel/yolov5n-falldown.kmodel"
confidence_threshold = 0.3
nms_threshold = 0.45
rgb888p_size = [1920, 1080]
labels = ["Fall","NoFall"] # 模型输出类别名称
anchors = [10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326] # anchor设置
# 初始化PipeLine,用于图像处理流程
pl = PipeLine(rgb888p_size=rgb888p_size, display_size=display_size, display_mode=display_mode)
pl.create()
# 初始化自定义跌倒检测实例
fall_det = FallDetectionApp(kmodel_path, model_input_size=[640, 640], labels=labels, anchors=anchors, confidence_threshold=confidence_threshold, nms_threshold=nms_threshold, nms_option=False, strides=[8,16,32], rgb888p_size=rgb888p_size, display_size=display_size, debug_mode=0)
fall_det.config_preprocess()
clock = time.clock()
while True:
clock.tick()
img = pl.get_frame() # 获取当前帧数据
res = fall_det.run(img) # 推理当前帧
fall_det.draw_result(pl, res) # 绘制结果到PipeLine的osd图像
print(res) # 打印结果
pl.show_image() # 显示当前的绘制结果
gc.collect() # 垃圾回收
print(clock.fps()) #打印帧率
从代码我们可以看到用到/sdcard/examples/kmodel/yolov5n-falldown.kmodel 模型。FallDetectionApp初始化自定义跌倒检测实例。config_preprocess配置预处理。postprocess自定义当前任务的后处理。draw_result绘制检测结果到画面上。main函数中的res为识别结果。运行识别结果如下
这个对特定人群,在养老、护理方面等还是挺实用的。
至此,实现K230跌倒检测功能。
-
发表了主题帖:
《ROS2智能机器人开发实践》--4.节点、话题
本篇讲述ROS2核心原理的节点与话题。
一.节点
1.概念
完整的机器人系统可能并不是一个物理上的整体,机器人的功能虽位于不同的计算机中,但都是这款机器人的工作细胞,也就是节点,他们共同组成了一个完整的机器人系统。
下面是关于节点的特性描述:
●节点在机器人系统中的职责就是执行某些具体的任务,从计算机操作系统的角度来看,也叫做进程;
●每个节点都是一个可以独立运行的可执行文件,比如执行某一个python程序,或者执行C++编译生成的结果,都算是运行了一个节点;
●既然每个节点都是独立的执行文件,那自然就可以想到,得到这个执行文件的编程语言可以是不同的,比如C++、Python,乃至Java、Ruby等更多语言。
●这些节点是功能各不相同的细胞,根据系统设计的不同,可能位于计算机A,也可能位于计算机B,还有可能运行在云端,这叫做分布式,也就是可以分布在不同的硬件载体上;
●每一个节点都需要有唯一的命名,当我们想要去找到某一个节点的时候,或者想要查询某一个节点的状态时,可以通过节点的名称来做查询。
2.节点实现
节点实现有分面向过程实现和面向对象实现。两者案例可分别参阅dev_ws/src/ros2_21_tutorials/learning_node/learning_node/node_helloworld.py和dev_ws/src/ros2_21_tutorials/learning_node/learning_node/node_helloworld_class.py。
面向过程
代码实现在learning_node/node_helloworld.py,完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件。
运行节点命令如下:
ros2 run learning_node node_helloworld
面相过程这种方式虽然实现简单,但是对于稍微复杂一点的机器人系统,就很难做到模块化编码。
面向对象
节点实现代码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向过程的实现方式
"""
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import time
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = Node("node_helloworld") # 创建ROS2节点对象并进行初始化
while rclpy.ok(): # ROS2系统是否正常运行
node.get_logger().info("Hello World") # ROS2日志输出
time.sleep(0.5) # 休眠控制循环时间
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
运行节点命令如下:
learning_node/node_helloworld_class.py
我们可以看到创建节点流程的代码实现过程如下,只是面向过程与面向对象编码方式不一样而已。
●编程接口初始化
●创建节点并初始化
●实现节点功能
●销毁节点并关闭接口
3.物体识别节点
首先安装OpenCV,指令如下:
sudo apt install python3-opencv
其次将图片路径修改为实际路径,修改如下图代码红框。
最后运行例程,指令如下:
ros2 run learning_node node_object
例程运行成功后,会弹出一个可视化窗口,可以看到苹果被成功识别,一个绿色框会把苹果的轮廓勾勒出来,中间的绿点表示中心点。结果如下
二.话题
1.概念与定义
话题是节点间传递数据的桥梁。
通信模型
A节点的功能是驱动相机这个硬件设备,获取得到相机拍摄的图像信息,B节点的功能是视频监控,将相机拍摄到的图像实时显示给用户查看。
从节点A到节点B传递图像数据的方式,在ROS中,我们就称之为话题,它作为一个桥梁,实现了节点之间某一个方向上的数据传输。
发布/订阅模型
话题数据传输的特性是从一个节点到另外一个节点,发送数据的对象称之为发布者,接收数据的对象称之为订阅者,每一个话题都需要有一个名字,传输的数据也需要有固定的数据类型。其框架如下:
多对多通信
ROS里的话题 发布者和订阅者的数量并不是唯一的,可以称之为是多对多的通信模型。如果存在多个发送指令的节点,要注意区分优先级,不然机器人可能不知道该听谁的了。
异步通信
话题通信还有一个特性,那就是异步。一步主要是指发布者发出数据后,并不知道订阅者什么时候可以收到。异步的特性也让话题更适合用于一些周期发布的数据,比如传感器的数据,运动控制的指令等等。
消息接口
在ROS中,话题通信数据的描述格式称之为消息,对应编程语言中数据结构的概念。比如这里的一个图像数据,就会包含图像的长宽像素值、每个像素的RGB等等,在ROS中都有标准定义。
消息是ROS中的一种接口定义方式,与编程语言无关,我们也可以通过.msg后缀的文件自行定义,有了这样的接口,各种节点就像积木块一样,通过各种各样的接口进行拼接,组成复杂的机器人系统。
2.话题通讯案例
Hello World话题通信
这里选用learning_topic工作空间里的发布者learning_topic/topic_helloworld_pub.py,订阅者learning_topic/topic_helloworld_sub.py,代码如下
启动第一个终端,运行话题的发布者节点:
ros2 run learning_topic topic_helloworld_pub
启动第二个终端,运行话题的订阅者节点:
ros2 run learning_topic topic_helloworld_sub
运行结果如下。可以看到发布者循环发布“Hello World”字符串消息,订阅者也以几乎同样的频率收到该话题的消息数据。
流程总结
实现一个发布者,流程如下:
●编程接口初始化
●创建节点并初始化
●创建发布者对象
●创建并填充话题消息
●发布话题消息
●销毁节点并关闭接口
实现一个订阅者,流程如下:
●编程接口初始化
●创建节点并初始化
●创建订阅者对象
●回调函数处理话题数据
●销毁节点并关闭接口
至此,梳理了解了ROS2节点与话题,并通过实验实践予以实现。
- 2025-02-15
-
发表了主题帖:
《ROS2智能机器人开发实践》--3.ROS2机器人开发流程之工作空间、功能包
本帖最后由 dirty 于 2025-2-15 23:31 编辑
本篇讲述ROS2机器人开发流程概览,并对其工作空间、功能包进行梳理学习实践。
一.ROS2机器人开发流程
ROS2的应用开发流程包含如下:
●工作空间(Workspace),开发过程的大本营
●功能包(Package):功能源码的聚集地
●节点(Node):机器人的工作细胞
●话题(Topic):节点间传递数据的桥梁
●服务(Service):节点间的你问我答
●通信接口(Interface):数据传递的标准结构参数(Parameter):机器人系统的全局字典动作(Action):完整行为的流程管理分布式通信(DistributedCommunication):多计算平台的任务分配
●DDS(Data Distribution Service):机器人的神经网络
ROS2机器人开发流程图如下:
二.工作空间与功能包
工作空间
1.工作空间概述
工作空间是一个存放项目开发相关文件的文件夹,也是开发过程中存放所有资料的大本营。ROS系统中一个典型的工作空间结构如下图所示。
●src,代码空间,未来编写的代码、脚本,都需要人为的放置到这里;
●build,编译空间,保存编译过程中产生的中间文件;
●install,安装空间,放置编译得到的可执行文件和脚本;
●log,日志空间,编译和运行过程中,保存各种警告、错误、信息等日志。
2.创建工作空间
创建创建工作空间主要是创建目录及代码如下,这里用到了古月居引导学习的ros2 21讲上手代码。
mkdir -p ~/dev_ws/src
cd ~/dev_ws/src
git clone https://gitee.com/guyuehome/ros2_21_tutorials.git
3.安装依赖。这里需要安装python3(最好3.8以上)。书中有讲安装rosdepc,实际可安装上rosdep。
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.10
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.x 1
sudo update-alternatives --config python3
python3 --version
4.编译工作空间
sudo apt install python3-colcon-ros
cd ~/dev_ws/
colcon build
目录切换到dev_ws,使用colcon build进行全编译,编译成功后,就可以在工作空间中看到自动生产的build、log、install文件夹了。
5.设置环境变量
echo " source ~/dev_ws/install/local_setup.sh" >> ~/.bashrc # 所有终端均生效
至此,我们就完成了工作空间的创建、编译和配置。
功能包
功能包原理,是把不同功能的代码划分到不同的功能包中,尽量降低他们之间的耦合关系。功能包的机制,是提高ROS中软件复用率的重要方法之一。
1.创建功能包
ros2 pkg create --build-type <build-type> <package_name>
ros2命令中:
pkg:表示功能包相关的功能;
create:表示创建功能包;
build-type:表示新创建的功能包是C++还是Python的,如果使用C++或者C,那这里就跟ament_cmake,如果使用Python,就跟ament_python;
package_name:新建功能包的名字。
我们可以看到ROS2工作空间ros2_21_tutorials中learning_pkg_c和learning_pkg_python是在终端中分别创建C++和Python版本的功能包:
cd ~/dev_ws/src
ros2 pkg create --build-type ament_cmake learning_pkg_c # C++
ros2 pkg create --build-type ament_python learning_pkg_python # Python
2.编译功能包
编译功能包可以colcon build全编译,也可以指定功能包编译。编译指定使用前需要安装colcon的扩展功能。
sudo apt install python3-colcon-common-extensions
指定功能包编译指令如下,通过--packages-select 或者--packages-up-to 参数编译指定功能包,package_name用于替换某个具体功能包。
cd ~/dev_ws
colcon build --packages-select <package_name> # 只编译指定功能包
colcon build --packages-up-to <package_name> # 编译指定功能包及相关依赖
3.清除编译历史
cd ~/dev_ws
rm -rf install/ build/ log/ # 清除工作空间中的编译历史
以上是功能包操作相关指令,下面了解下功能包结构。
4.功能包的结构
上面有介绍C++和Python两种版本功能包创建,这里也分开讲述。
C++功能包
C++类型的功能包,其中必然存在两个文件:package.xml和CMakerLists.txt。
package.xml文件的主要内容如下,包含功能包的版权描述,和各种依赖的声明。
CMakeLists.txt文件是编译规则,C++代码需要编译才能运行,所以必须要在该文件中设置如何编译,使用CMake语法。
Python功能包
C++功能包需要将源码编译成可执行文件,但是Python语言是解析型的,不需要编译,所以会有一些不同,但也会有这两个文件:package.xml和setup.py。
package.xml文件的主要内容和C++版本功能包一样,包含功能包的版权描述,和各种依赖的声明。
setup.py文件里边也包含一些版权信息,除此之外,还有“entry_points”配置的程序入口。
至此,对功能包的创建、编译及其结构与文件功能有了了解认识。
- 2025-02-10
-
回复了主题帖:
【嘉楠K230开发板】车牌识别
lugl4313820 发表于 2025-2-10 08:35
如果我想把车牌登记到数据库,怎么样才能操作实现呢?
检测识别结果与数据库比对再处理下
- 2025-02-09
-
回复了主题帖:
【嘉楠K230开发板】人脸检测
lugl4313820 发表于 2025-1-26 22:32
他跟公版算法的相比,如个速更快一些?
跟分辨率、帧率有些关系,检测结果还算快
-
回复了主题帖:
《ROS2智能机器人开发实践》--1.开箱与全书概览
秦天qintian0303 发表于 2025-2-1 08:20
买个硬件一快玩
是可以的。这本书刚好也配套论坛刚好有试用活动的RDK机器人开发套件,看到有坛友发了相关帖子,可以相互了解下。
-
回复了主题帖:
《ROS2智能机器人开发实践》--2.ROS2了解与安装运行
Jacktang 发表于 2025-2-3 10:16
虚拟机安装上ubuntu,在下载并进行ROS2的安装,程序和步骤应该比较多的
ubuntu版本和按照ROS官方教程安装,基本都是可以顺利安装上ROS2的
-
发表了主题帖:
【嘉楠K230开发板】车牌识别
本帖最后由 dirty 于 2025-2-9 19:06 编辑
本篇讲述使用使用摄像头拍摄车牌识别车牌号。
一.原理流程
使用AI视觉开发框架 ,需要用到的模型已经存放在CanMV K230的文件系统。下面是编程思路
二.代码实现与测试
代码如下,主函数实现获取当前帧图像、AI推理、绘制结果、显示结果 的识别流程。代码中det_res为车牌检测结果,rec_res为车牌识别内容结果。
'''
车牌识别
'''
from libs.PipeLine import PipeLine, ScopedTiming
from libs.AIBase import AIBase
from libs.AI2D import Ai2d
import os
import ujson
from media.media import *
from time import *
import nncase_runtime as nn
import ulab.numpy as np
import time
import image
import aidemo
import random
import gc
import sys
# 自定义车牌检测类
class LicenceDetectionApp(AIBase):
# 初始化函数,设置车牌检测应用的参数
def __init__(self, kmodel_path, model_input_size, confidence_threshold=0.5, nms_threshold=0.2, rgb888p_size=[224,224], display_size=[1920,1080], debug_mode=0):
super().__init__(kmodel_path, model_input_size, rgb888p_size, debug_mode) # 调用基类的初始化函数
self.kmodel_path = kmodel_path # 模型路径
# 模型输入分辨率
self.model_input_size = model_input_size
# 分类阈值
self.confidence_threshold = confidence_threshold
self.nms_threshold = nms_threshold
# sensor给到AI的图像分辨率
self.rgb888p_size = [ALIGN_UP(rgb888p_size[0], 16), rgb888p_size[1]]
# 显示分辨率
self.display_size = [ALIGN_UP(display_size[0], 16), display_size[1]]
self.debug_mode = debug_mode
# Ai2d实例,用于实现模型预处理
self.ai2d = Ai2d(debug_mode)
# 设置Ai2d的输入输出格式和类型
self.ai2d.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT, nn.ai2d_format.NCHW_FMT, np.uint8, np.uint8)
# 配置预处理操作,这里使用了pad和resize,Ai2d支持crop/shift/pad/resize/affine
def config_preprocess(self, input_image_size=None):
with ScopedTiming("set preprocess config", self.debug_mode > 0):
# 初始化ai2d预处理配置,默认为sensor给到AI的尺寸,可以通过设置input_image_size自行修改输入尺寸
ai2d_input_size = input_image_size if input_image_size else self.rgb888p_size
self.ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel)
self.ai2d.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]])
# 自定义当前任务的后处理
def postprocess(self, results):
with ScopedTiming("postprocess", self.debug_mode > 0):
# 对检测结果进行后处理
det_res = aidemo.licence_det_postprocess(results, [self.rgb888p_size[1], self.rgb888p_size[0]], self.model_input_size, self.confidence_threshold, self.nms_threshold)
return det_res
# 自定义车牌识别任务类
class LicenceRecognitionApp(AIBase):
def __init__(self,kmodel_path,model_input_size,rgb888p_size=[1920,1080],display_size=[1920,1080],debug_mode=0):
super().__init__(kmodel_path,model_input_size,rgb888p_size,debug_mode)
# kmodel路径
self.kmodel_path=kmodel_path
# 检测模型输入分辨率
self.model_input_size=model_input_size
# sensor给到AI的图像分辨率,宽16字节对齐
self.rgb888p_size=[ALIGN_UP(rgb888p_size[0],16),rgb888p_size[1]]
# 视频输出VO分辨率,宽16字节对齐
self.display_size=[ALIGN_UP(display_size[0],16),display_size[1]]
# debug模式
self.debug_mode=debug_mode
# 车牌字符字典
self.dict_rec = ["挂", "使", "领", "澳", "港", "皖", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "京", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "警", "学", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "_", "-"]
self.dict_size = len(self.dict_rec)
self.ai2d=Ai2d(debug_mode)
self.ai2d.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT,nn.ai2d_format.NCHW_FMT,np.uint8, np.uint8)
# 配置预处理操作,这里使用了resize,Ai2d支持crop/shift/pad/resize/affine
def config_preprocess(self,input_image_size=None):
with ScopedTiming("set preprocess config",self.debug_mode > 0):
ai2d_input_size=input_image_size if input_image_size else self.rgb888p_size
self.ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel)
self.ai2d.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]])
# 自定义后处理,results是模型输出的array列表
def postprocess(self,results):
with ScopedTiming("postprocess",self.debug_mode > 0):
output_data=results[0].reshape((-1,self.dict_size))
max_indices = np.argmax(output_data, axis=1)
result_str = ""
for i in range(max_indices.shape[0]):
index = max_indices[i]
if index > 0 and (i == 0 or index != max_indices[i - 1]):
result_str += self.dict_rec[index - 1]
return result_str
# 车牌识别任务类
class LicenceRec:
def __init__(self,licence_det_kmodel,licence_rec_kmodel,det_input_size,rec_input_size,confidence_threshold=0.25,nms_threshold=0.3,rgb888p_size=[1920,1080],display_size=[1920,1080],debug_mode=0):
# 车牌检测模型路径
self.licence_det_kmodel=licence_det_kmodel
# 车牌识别模型路径
self.licence_rec_kmodel=licence_rec_kmodel
# 人脸检测模型输入分辨率
self.det_input_size=det_input_size
# 人脸姿态模型输入分辨率
self.rec_input_size=rec_input_size
# 置信度阈值
self.confidence_threshold=confidence_threshold
# nms阈值
self.nms_threshold=nms_threshold
# sensor给到AI的图像分辨率,宽16字节对齐
self.rgb888p_size=[ALIGN_UP(rgb888p_size[0],16),rgb888p_size[1]]
# 视频输出VO分辨率,宽16字节对齐
self.display_size=[ALIGN_UP(display_size[0],16),display_size[1]]
# debug_mode模式
self.debug_mode=debug_mode
self.licence_det=LicenceDetectionApp(self.licence_det_kmodel,model_input_size=self.det_input_size,confidence_threshold=self.confidence_threshold,nms_threshold=self.nms_threshold,rgb888p_size=self.rgb888p_size,display_size=self.display_size,debug_mode=0)
self.licence_rec=LicenceRecognitionApp(self.licence_rec_kmodel,model_input_size=self.rec_input_size,rgb888p_size=self.rgb888p_size)
self.licence_det.config_preprocess()
# run函数
def run(self,input_np):
# 执行车牌检测
det_boxes=self.licence_det.run(input_np)
# 将车牌部分抠出来
imgs_array_boxes = aidemo.ocr_rec_preprocess(input_np,[self.rgb888p_size[1],self.rgb888p_size[0]],det_boxes)
imgs_array = imgs_array_boxes[0]
boxes = imgs_array_boxes[1]
rec_res = []
for img_array in imgs_array:
# 对每一个检测到的车牌进行识别
self.licence_rec.config_preprocess(input_image_size=[img_array.shape[3],img_array.shape[2]])
licence_str=self.licence_rec.run(img_array)
rec_res.append(licence_str)
gc.collect()
return det_boxes,rec_res
# 绘制车牌检测识别效果
def draw_result(self,pl,det_res,rec_res):
pl.osd_img.clear()
if det_res:
point_8 = np.zeros((8),dtype=np.int16)
for det_index in range(len(det_res)):
for i in range(4):
x = det_res[det_index][i * 2 + 0]/self.rgb888p_size[0]*self.display_size[0]
y = det_res[det_index][i * 2 + 1]/self.rgb888p_size[1]*self.display_size[1]
point_8[i * 2 + 0] = int(x)
point_8[i * 2 + 1] = int(y)
for i in range(4):
pl.osd_img.draw_line(point_8[i * 2 + 0],point_8[i * 2 + 1],point_8[(i+1) % 4 * 2 + 0],point_8[(i+1) % 4 * 2 + 1],color=(255, 0, 255, 0),thickness=4)
pl.osd_img.draw_string_advanced( point_8[6], point_8[7] + 20, 40,rec_res[det_index] , color=(255,255,153,18))
if __name__=="__main__":
# 显示模式,默认"hdmi",可以选择"hdmi"和"lcd"
display_mode="lcd"
if display_mode=="hdmi":
display_size=[1920,1080]
else:
display_size=[800,480]
# 车牌检测模型路径
licence_det_kmodel_path="/sdcard/examples/kmodel/LPD_640.kmodel"
# 车牌识别模型路径
licence_rec_kmodel_path="/sdcard/examples/kmodel/licence_reco.kmodel"
# 其它参数
rgb888p_size=[640,360]
licence_det_input_size=[640,640]
licence_rec_input_size=[220,32]
confidence_threshold=0.2
nms_threshold=0.2
# 初始化PipeLine,只关注传给AI的图像分辨率,显示的分辨率
pl=PipeLine(rgb888p_size=rgb888p_size,display_size=display_size,display_mode=display_mode)
pl.create()
lr=LicenceRec(licence_det_kmodel_path,licence_rec_kmodel_path,det_input_size=licence_det_input_size,rec_input_size=licence_rec_input_size,confidence_threshold=confidence_threshold,nms_threshold=nms_threshold,rgb888p_size=rgb888p_size,display_size=display_size)
clock = time.clock()
while True:
clock.tick()
img=pl.get_frame() # 获取当前帧
det_res,rec_res=lr.run(img) # 推理当前帧
lr.draw_result(pl,det_res,rec_res) # 绘制当前帧推理结果
print(det_res,rec_res) # 打印结果
pl.show_image() # 展示推理结果
gc.collect()
print(clock.fps()) #打印帧率
车牌识别效果如下,识别情况跟角度、远近、光线有些关系。
至此,实现车牌识别功能。
- 2025-01-31
-
发表了主题帖:
《ROS2智能机器人开发实践》--2.ROS2了解与安装运行
本篇了解ROS/ROS2的发展历程及ROS2安装运行。
一.ROS/ROS2发展历程
ROS(机器人操作系统)发展历程如下:
全新的ROS2在2017年年底正式发布,到2024年5月,ROS2第二个长期支持版本ROS 2 Jazzy发布。
ROS的核心目标是提高机器人的软件复用率。ROS主要由由通信机制、开发工具、应用功能、生态系统四大部分组成,具备全球化社区、开源开放的生态、跨平台使用、工业应用支持的特点。下面了解下ROS2与ROS1系统架构,可以看到ROS2摒弃了Master节点管理器、引入更复杂也更完善的DDS系统通信机制,同时可选的操作系统更多更广泛。
ROS2设计了一个ROS Middleware,简称RMW,也就是指定一个标准的接口,如如何发数据,收数据,数据的各种属性配置等。厂家接入ROS社区,按照RMW标准写一个适配接口把自家DDS移植过来。对用户来说,可以安装使用适合的DDS而不需要更改应用程序,可以轻松更换底层的通信系统。
ROS2概念有如下:
●工作空间(Workspace):开发过程的大本营,放置各种开发文件。
●功能包(Package):功能源码的聚集地,用于组织某一机器人功能。
●节点(Node):机器人的工作细胞,是代码编译生成的一个可执行文件。
●话题(Topic):节点间传递数据的桥梁,周期传递各功能之间的信息。
●服务(Service):节点间的你问我答,用于某些机器人功能和参数的匹配置。
●通信接口(Interface):数据传递的标准结构,规范机器人的各种数据形态。
●参数(Parameter):机器人系统的全局字典,可定义或查询机器人的配置参数。
●动作(Action):完整行为的流程管理,控制机器人完成某些动作。
●分布式通信(DistributedCommunication):多计算平台的任务分配,实现快速组网。
●DDS(Data Distribution Service):机器人的神经网络,完成数据的高效安全传递。
二.ROS2 安装
这里自行准备好虚拟机安装上ubuntu,开始下面ROS2安装,在控制台下输入下列命令。可参考ROS官网ROS2安装。
1.设置编码
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
2.添加源
sudo apt update && sudo apt install curl gnupg lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
3.安装ROS2
sudo apt update
sudo apt upgrade
sudo apt install ros-foxy-desktop
4.设置环境变量
source /opt/ros/foxy/setup.bash
echo " source /opt/ros/foxy/setup.bash" >> ~/.bashrc
这里查询到安装的是foxy版本,安装成功后如下:
三.运行小海龟仿真
打开两个终端,分别运行以下指令:
ros2 run turtlesim turtlesim_node
ros2 run turtlesim turtle_teleop_key
第一句指令将启动一个蓝色背景的海龟仿真器,第二句指令将启动一个键盘控制节点,在该终端中点击键盘上的“上下左右”按键,就可以控制小海龟运动。下图是指令运行与仿真效果。
至此,对ROS2有了初步了解,安装上了ROS2,并成功运行了一个仿真,为后面探索奠定了良好基础。
-
发表了主题帖:
《ROS2智能机器人开发实践》--1.开箱与全书概览
很有幸获得《ROS2智能机器人开发实践》这本书的阅读机会,这是一本不可多得的智能机器人学习书籍,下面看下书籍封面
这本书是今年刚刚出第一版,很新,作者是“古月居”创始人及负责人,在机器人操作系统(ROS)领域有丰富的开发经验与积累。这本书紧扣技术发展脉络,系统全面讲解ROS2,在人工智能时代,对学习开发者来说,是很好的入门及深入学习的不错选择。
拿到书籍,首先习惯性是翻看下序言和目录,对全书有一个大致全面的了解。本书分三部分,共九章。每部分及章节内容如下:
第一部分(第1~3 章)介绍 ROS2基础原理:主要讲解ROS2的发展历程、核心原理和组件工具,提供大量的编程和使用示例,为读者全面展示ROS2的基础原理和功能。
●第1章--ROS:智能机器人灵魂
●第2章--ROS 2核心原理:构建机器人的基石
●第3章--ROS 2常用工具:让机器人开发更便捷
第二部分(第4~6章)介绍ROS2机器人设计:主要讲解如何使用ROS2设计一个仿真机器人和实物机器人。
●第4章--ROS 2机器人仿真:零成本玩转机器人
●第5章--ROS 2机器人构建:从仿真到实物
●第6章--ROS 2控制与感知:让机器人动的了、看得见
第三部分(第7~9章)介绍ROS2机器人应用:主要讲解使用ROS2开发机器人视觉识别地图构建和自主导航等众多应用的方法。
●第7章--ROS 2视觉应用:让机器人看懂世界
●第8章--ROS 2地图构建:让机器人理解环境
●第9章--ROS 2自主导航:让机器人运动自由
本书采用最新稳定版本ROS2系统和全新一代的Gazebo机器人仿真平台,绝大部分功能和源码可以在单独的计算机和 Gazebo仿真平台上运行。同时,本书介绍实物机器人的搭建方法,并且在实物机器人上实现相应的功能。配套源码也很方便读者阅读学习。
可以看出,从这本书可学的内容还是挺多的,后面以章节主题为纲,逐一展开阅读学习与实践。
- 2025-01-26
-
回复了主题帖:
拼板PCB生成钢网文件?
lkh747566933 发表于 2025-1-26 10:56
我都是画个拼板的板框示意图,其他的交给板厂。
你这省事,让板厂那边弄也行
-
发表了主题帖:
【嘉楠K230开发板】人脸检测
本帖最后由 dirty 于 2025-1-26 14:54 编辑
本篇讲述使用K230进行人脸检测。
一.了解AI视觉开发框架
K230的KPU是一个内部神经网络处理器,它可以在低功耗的情况下实现卷积神经网络计算,实时获取被检测目标的大小、坐标和种类,对人脸或者物体进行检测和分类,支持INT8和INT16。CanMV官方基于K230专门搭建了配套的AI视觉开发框架。框架结构如下图所示:
这个框架简单来说就是Sensor(摄像头)默认输出两路图像,一路格式为YUV420,直接给到Display显示;另一路格式为RGB888,给到AI部分进行处理。AI主要实现任务的前处理、推理和后处理流程,得到后处理结果后将其绘制在osd image实例上,并送给Display叠加,最后在HDMI、LCD或IDE缓冲区显示识别结果。
AI视觉开发框架主要API接口有:
●PineLine:将sensor、display封装成固定接口,用于采集图像、画图以及结果图片显示。
●AI2D:预处理(Preprocess)相关接口。
●AIBase:模型推理主要接口。
二.人脸检测
人脸检测,是将一幅图片中人脸检测出来,支持单个和多个人脸。
本次人脸检测功能将摄像头拍摄到的画面中的人脸用矩形框标识出来。编程流程如下:
实现代码如下:
'''人脸检测
'''
from media.sensor import * #导入sensor模块,使用摄像头相关接口
from libs.PipeLine import PipeLine, ScopedTiming
from libs.AIBase import AIBase
from libs.AI2D import Ai2d
import os
import ujson
from media.media import *
from time import *
import nncase_runtime as nn
import ulab.numpy as np
import time
import utime
import image
import random
import gc
import sys
import aidemo
# 自定义人脸检测类,继承自AIBase基类
class FaceDetectionApp(AIBase):
def __init__(self, kmodel_path, model_input_size, anchors, confidence_threshold=0.5, nms_threshold=0.2, rgb888p_size=[224,224], display_size=[1920,1080], debug_mode=0):
super().__init__(kmodel_path, model_input_size, rgb888p_size, debug_mode) # 调用基类的构造函数
self.kmodel_path = kmodel_path # 模型文件路径
self.model_input_size = model_input_size # 模型输入分辨率
self.confidence_threshold = confidence_threshold # 置信度阈值
self.nms_threshold = nms_threshold # NMS(非极大值抑制)阈值
self.anchors = anchors # 锚点数据,用于目标检测
self.rgb888p_size = [ALIGN_UP(rgb888p_size[0], 16), rgb888p_size[1]] # sensor给到AI的图像分辨率,并对宽度进行16的对齐
self.display_size = [ALIGN_UP(display_size[0], 16), display_size[1]] # 显示分辨率,并对宽度进行16的对齐
self.debug_mode = debug_mode # 是否开启调试模式
self.ai2d = Ai2d(debug_mode) # 实例化Ai2d,用于实现模型预处理
self.ai2d.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT, nn.ai2d_format.NCHW_FMT, np.uint8, np.uint8) # 设置Ai2d的输入输出格式和类型
# 配置预处理操作,这里使用了pad和resize,Ai2d支持crop/shift/pad/resize/affine,具体代码请打开/sdcard/app/libs/AI2D.py查看
def config_preprocess(self, input_image_size=None):
with ScopedTiming("set preprocess config", self.debug_mode > 0): # 计时器,如果debug_mode大于0则开启
ai2d_input_size = input_image_size if input_image_size else self.rgb888p_size # 初始化ai2d预处理配置,默认为sensor给到AI的尺寸,可以通过设置input_image_size自行修改输入尺寸
top, bottom, left, right = self.get_padding_param() # 获取padding参数
self.ai2d.pad([0, 0, 0, 0, top, bottom, left, right], 0, [104, 117, 123]) # 填充边缘
self.ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) # 缩放图像
self.ai2d.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) # 构建预处理流程
# 自定义当前任务的后处理,results是模型输出array列表,这里使用了aidemo库的face_det_post_process接口
def postprocess(self, results):
with ScopedTiming("postprocess", self.debug_mode > 0):
post_ret = aidemo.face_det_post_process(self.confidence_threshold, self.nms_threshold, self.model_input_size[1], self.anchors, self.rgb888p_size, results)
if len(post_ret) == 0:
return post_ret
else:
return post_ret[0]
# 绘制检测结果到画面上
def draw_result(self, pl, dets):
with ScopedTiming("display_draw", self.debug_mode > 0):
if dets:
pl.osd_img.clear() # 清除OSD图像
for det in dets:
# 将检测框的坐标转换为显示分辨率下的坐标
x, y, w, h = map(lambda x: int(round(x, 0)), det[:4])
x = x * self.display_size[0] // self.rgb888p_size[0]
y = y * self.display_size[1] // self.rgb888p_size[1]
w = w * self.display_size[0] // self.rgb888p_size[0]
h = h * self.display_size[1] // self.rgb888p_size[1]
pl.osd_img.draw_rectangle(x, y, w, h, color=(255, 255, 0, 255), thickness=2) # 绘制矩形框
else:
pl.osd_img.clear()
# 获取padding参数
def get_padding_param(self):
dst_w = self.model_input_size[0] # 模型输入宽度
dst_h = self.model_input_size[1] # 模型输入高度
ratio_w = dst_w / self.rgb888p_size[0] # 宽度缩放比例
ratio_h = dst_h / self.rgb888p_size[1] # 高度缩放比例
ratio = min(ratio_w, ratio_h) # 取较小的缩放比例
new_w = int(ratio * self.rgb888p_size[0]) # 新宽度
new_h = int(ratio * self.rgb888p_size[1]) # 新高度
dw = (dst_w - new_w) / 2 # 宽度差
dh = (dst_h - new_h) / 2 # 高度差
top = int(round(0))
bottom = int(round(dh * 2 + 0.1))
left = int(round(0))
right = int(round(dw * 2 - 0.1))
return top, bottom, left, right
if __name__ == "__main__":
# 显示模式,默认"hdmi",可以选择"hdmi"和"lcd"
display_mode="lcd"
if display_mode=="hdmi":
display_size=[1920,1080]
else:
display_size=[800,480]
# 设置模型路径和其他参数
kmodel_path = "/sdcard/examples/kmodel/face_detection_320.kmodel"
# 其它参数
confidence_threshold = 0.5
nms_threshold = 0.2
anchor_len = 4200
det_dim = 4
anchors_path = "/sdcard/examples/utils/prior_data_320.bin"
anchors = np.fromfile(anchors_path, dtype=np.float)
anchors = anchors.reshape((anchor_len, det_dim))
rgb888p_size = [1920, 1080]
# 初始化PipeLine,用于图像处理流程
pl = PipeLine(rgb888p_size=rgb888p_size, display_size=display_size, display_mode=display_mode)
pl.create() # 创建PipeLine实例
# 初始化自定义人脸检测实例
face_det = FaceDetectionApp(kmodel_path, model_input_size=[320, 320], anchors=anchors, confidence_threshold=confidence_threshold, nms_threshold=nms_threshold, rgb888p_size=rgb888p_size, display_size=display_size, debug_mode=0)
face_det.config_preprocess() # 配置预处理
clock = time.clock()
while True:
clock.tick()
img = pl.get_frame() # 获取当前帧数据
res = face_det.run(img) # 推理当前帧
# 当检测到人脸时,打印结果
if res:
print(res)
face_det.draw_result(pl, res) # 绘制结果
pl.show_image() # 显示结果
gc.collect() # 垃圾回收
print(clock.fps()) #打印帧率
主函数实现获取当前帧数据,AI推理当前帧,检测到人脸绘制、显示结果,这里用到SD卡路径下人脸检测模型/sdcard/examples/kmodel/face_detection_320.kmodel。AI处理阶段定义在FaceDetectionApp类下config_preprocess、postprocess、get_padding_param。
这里选用两张照片,运行摄像头正对照片,识别结果如下,可以看到图片中人脸均识别到。
至此,实现人脸检测功能。
-
回复了主题帖:
《Linux内核深度解析》--文件系统
qzgiky 发表于 2025-1-26 08:55
学习,Linux单单学会用还是不够,得会底层原理,然后裁剪定制
是的,学以致用
- 2025-01-24
-
回复了主题帖:
【Wio Lite AI STM32H725AE视觉开发板】--2.开发环境搭建与点灯
Jacktang 发表于 2025-1-24 09:31
用的版本V5.36.0.0,好吧,与STM32CubeMX里兼容匹配
STM32CubeMX 里有MDK-RAM (Keil)需要的版本(以上),导出的工程可用
-
发表了主题帖:
《Linux内核深度解析》--文件系统
本帖最后由 dirty 于 2025-1-24 10:18 编辑
本篇阅读学习文件系统及使用。
概述
在Linux 系统中,一切皆文件,除了通常所说的狭义的文件(文本文件和二进制文件)以外,目录、设备、套接字和管道等都是文件。
文件系统在不同的上下文中有不同的含义。
①在存储设备上组织文件的方法,包括数据结构和访问方法。
②按照某种文件系统类型格式化的一块存储介质。我们常说在某个目录下挂载或卸载文件系统,这里的文件系统就是这种意思。
③内核中负责管理和存储文件的模块,即文件系统模块。
Linux文件系统架构如下:
用户空间层面
应用程序可以直接使用内核提供的系统调用访问文件;
①一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能诸问这个文件系统。
②系统调用umount用来卸载某个目录下挂载的文件系统。
③使用 open 打开文件。
④使用close 关闭文件。
⑤使用read 读文件。
⑥使用 write 写文件。
⑦使用lseek 设置文件偏移。
⑧当我们写文件的时候,内核的文件系统模块把数据保存在页缓存中,不会立即写到存储设备。
应用程序也可以使用 glibc 厍封装的标准 O 流函数访问文件,glibc 库封装的标准 O 流函数如下所示:
①使用 fopen 打开流。
②使用 fclose 关闭流
③使用 fead 读流。
④使用 fwrite 写流。
⑤使用 fseek 设置文件偏移。
⑥使用fwrite可以把数据写到用户空间缓冲区,但不会立即写到内核。我们可以使用fflush 冲刷流,即把写到用户空间缓冲区的数据立即写到内核。
硬件层面
外部存储设备分为块设备、闪存和NVDIMM 设备3类.
块设备主要有以下两种:机械硬盘、闪存类块设备。如SSD、eMMC、UFS。
闪存(Flash Memory)按存储结构分为NAND闪存和NOR闪存。
NVDIMM (Non-Volatile DIMM,非易失性内存; DIMM 是 Dual-Inline-Memory-Modules的缩写,表示双列直插式存储模块,是内存的一种规格)断电后数据不丢失。在断电的瞬间,超级电容提供电力,把内存中的数据转移到NAND闪存。
内核空间层面
在内核的目录下可以看到,内核支持多种文件系统类型。为了对用户程序提供统一的文件操作接口。
为了使不同的文件系统实现能够其存,内核实现了一个抽象层,称为虚拟文件系统(Vitual File System,VFS),也称为虚拟文件系统功换(virnal Filesystem Switch,VFS)。
文件系统分为以下4种。
①块设备文件系统,存储设备是机械硬盘和固态硬盘等块设备,常用的块设备文件系统是EXT和 btfs(读作|bAtS|)。EXT文件系统是Limux原创的文件系统,目前有3个版本:EXT2、EXT3 和 EXT4。
②闪存文件系统,存储设备是NAND闪存和NOR闪存,常用的闪存文件系统是JFFS2(日志型闪存文件系统版本2,Journalling Flash File System version2)和 UBIFS(无序区块镜像文件系统,Unsorted Block Image File System)。
③内存文件系统,文件在内存中,断电以后文件丢失,常用的内存文件系统是tmpfs,用来创建临时文件。
④伪文件系统,是假的文件系统,只是为了使用虚拟文件系统的编程接口。常用伪文件系统sockfs、proc 文件系统、sysf、hugetlbfs、cgroup、cgroup2
虚拟文件系统的数据结构
虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义了一套统一的数据结构。
①超级块。
②虚拟文件系统在内存中把目录组织为一棵树。
③每种文件系统的超级块的格式不同,需要向虚拟文件系统注册文件系统类型fle_system_type,并且实现 mount 方法用来读取和解析超级块。
④索引节点。
⑤目录项。
⑥当进程打开一个文件的时候,虚拟文件系统就会创建文件的一个打开实例:fle结构体,然后在进程的打开文件表中分配一个索引,这个索引称为文件描述符,最后把文件描述符和 fle 结构体的映射添加到打开文件表中。
注册文件系统类型
函数register flesystem用来注册文件系统类型:
int register filesystem(struct file_system type *fs);
函数unregister flesystem 用来注销文件系统类型:
int unregister filesystem(struct file_system_type *fs);
管理员可以执行命令“cat/proc/flesystems”来查看已经注册的文件系统类型。
挂载文件系统
虚拟文件系统在内存中把目录组织为一棵树。一个文件系统,只有挂载到内存中目树的一个目录下,进程才能访问这个文件系统。
glibc库封装了挂载文件系统的函数mount:
int mount(const char*dev_name, const char *dir_name,const char *type,unsigned long flags,const void *data);
glibc库封装了两个卸载文件系统的函数:
①函数umount,对应内核的系统调用oldumount。
int umount(const char *target);
②函数umount2,对应内核的系统调用umount。
int umount2(const char*target,int flags);
打开文件
进程读写文件之前需要打开文件,得到文件描述符,然后通过文件描述符读写文件。
内核提供了两个打开文件的系统调用。
①int open(const char *pathname, int flags, mode t mode);
②int openat(int dirfd, const char *pathname, int flags, mode t mode),
如果打开文件成功,那么返回文件描述符,值大于或等于0;如果打开文件失败,返回负的错误号。
创建文件
创建不同类型的文件,需要使用不同的命令。
①普通文件:touchFE,这条命今本来用来更新文件的访问时间和修改时间,如果文件不存在,创建文件。
②目录:mkdir DIRECTORY
③符号链接(也称为软链接):In-STARGETLINKNAME或In--symbolC TARGETLINK NAME
④字符或块设备文件:mknod NAME TYPE [MAJOR MINOR]
参数 TYPE:b表示带缓冲区的块设备文件,c表示带缓冲区的字符设备文件,u表示不带缓冲区的字符设备文件,p表示命名管道。
⑤命名管道:mkpipe NAME
⑥命令“In TARGETLINK NAME”用来创建硬链接,给已经存在的文件增加新的名文件的索引节点有一个硬链接计数,如果文件有几个名称,那么硬链接计数是n。
内核提供了下面这些创建文件的系统调用。
①创建普通文件。
int creat(const char*pathname,mode_t mode);
也可以使用open和openat创建普通文件
int open(const char *pathname,int flags,mode_t mode);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
②创建目录。
int mkdir(const char*pathname,mode_t mode);
int mkdirat(int dirfd,const char *pathname, mode_t mode);
③创建符号链接。
int symlink(const char *oldpath,const char *newpath);
int symlinkat(const char *oldpath, int newdirfd, const char *newpath);
④mknod通常用来创建字符设备文件和块设备文件,也可以创建普通文件、命名管道和套接字。
int mknod(const char *pathname,modet mode, dev_t dev);
int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev);
⑤link用来创建硬链接,给已经存在的文件增加新的名称。
int link(const char *oldpath,const char*newpath);
int linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath);
glibc库封装了和上面的系统调用同名的库函数,还封装了创建命名管道的库函数。库函数 mkffo 是通过调用系统调用 mknod 来实现的。
int mkfifo(const char*pathname,mode t mode);
删除文件
删除文件的命今如下。
①删除任何类型的文件:unlink PILE.
②m FJLE,默认不删除目录,如果使用选项“”“_只”或“_recursive”,可以删除目录和目录的内容。
③删除目录:mdirDIRECTORY.
内核提供了下面这些刑除文件的系统调用。
①unlink用来删除文件的名称,如果文件的硬链接计数变成0,并且没有进程打开这个文件,那么删除文件。
int unlinx(conet char*pathname);
int unlinrat(int dirfd, const char *pathname, int flags);
②删除目录。
int rmdir(const char *pathname);
设置文件权限
内核提供了下面这些设置文件权限的系统调用
①int chmod(const char *path, mode t mode),
②int fchmod(int fd, mode t mode);
③int fchmodat(int dfd, const char *filename, mode t mode);
读文件
进程读文件的方式有3种。
①调用内核提供的读文件的系统调用。
②调用glibc 库封装的读文件的标准 O 流函数。
③创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空国直接读内存。
内核提供了下面这些读文件的系统调用。
①系统调用read从文件的当前偏移读文件,把数据存放在一个缓冲区.
ssize_t read(int fd,void *buf,size_t count);
②系统调用pread64 从指定偏移开始读文件。
ssize_t pread64(int fd, void *buf, sizet count, off_t offset);
③系统调用readv从文件的当前偏移读文件,把数据存放在多个分散的缓冲区。
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
④系统调用preadv从指定偏移开始读文件,把数据存放在多个分散的缓冲区
ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
⑤系统调用preadv2在系统调用preadv 的基础上增加了参数“int flags”。
ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, off_t offset, int flags);
其中preadv和preadv2是Linux内核私有的系统调用。
写文件
进程写文件的方式有3种。
①调用内核提供的写文件的系统调用。
②调用 glibc 库封装的写文件的标准 IO 流函数。
③创建基于文件的内存映射,把文件的一个区间映射到进程的虚拟地址空间,然后直接写内存。
内核提供了下面这些写文件的系统调用。
①函数 write 从文件的当前偏移写文件,调用进程把要写入的数据存放在一个缓冲区
ssize_t write(int fd,const void *buf,size_t count);
②函数 pwrite64 从指定偏移开始写文件。
ssize_t pwrite64(int fd, const void *buf,size_t count, off_t offset);
③函数 writev从文件的当前偏移写文件,调用进程把要写入的数据存放在多个分散的缓冲区。
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
④函数pwritev从指定偏移开始写文件,调用进程把要写入的数据存放在多个分散的缓冲区。
ssize_t pwritev(int fd, const struct iovec *iov, int iovont, off_t offset);
⑤函数 pwritev2 在函数 pwritev 的基础上增加了参数“int flags”。
ssize_t pwritev2(int fd, const struct iovec *jov, int iovent, off_t offset, int flags) ;
其中 pwritev和pwritev2是Linux内核私有的系统调用。
文件回写
进程写文件时,内核的文件系统模块把数据写到文件的页缓存,没有立即写回到存储设备。文件系统模块会定期把脏页(即数据被修改过的文件页)写回到存储设备,进程也可以调用系统调用把脏页强制写回到存储设备。
内核提供了下面这些把文件同步到存储设备的系统调用。
①sync把内存中所有修改过的文件元数据和文件数据写回到存储设备。
void sync(void);
②syncfs把文件描述符fd引用的文件所属的文件系统写回到存储设备。
int syncfs(int fd);
③fsyc把文件描述符fd引用的文件修改过的元数据和数据写回到存储设备
int fsync(int fd);
④fdatasync把文件描述符d引用的文件修改过的数据写回到存储设备,还会把检索这些数据需要的元数据写回到存储设备。
int fdatasync(int fd);
⑤Linux 私有的系统调用sync fle range 把文件的一个区间修改过的数据写回到存储设备
int sync_file_range(int fd, off64_t offset, off64_t nbytes, unsigned int flags);
glibc库针对这些系统调用封装了同名的库函数,还封装了一个把数据从用户空间缓冲区写到内核的标准 I/O 流函数:
int fflush(FILE *stream);
下面以打开、读、写、关闭文件示例代码如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main()
{
char buf_w[30] = "HELLO WORLD!!!\n";
char buf_r[30] = "HELLO WORLD!!!\n";
int fd = open("./test", O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
printf("error: test open\n");
return -1;
}
if( write(fd, buf_w, strlen(buf)) < 0 )
{
printf("error: test write\n");
return -1;
}
if(read(fd, buf, 30) < 0)
{
printf("error: test read\n");
return -1;
}
printf("read fd:%s", buf);
ret = close(fd);
if (ret < 0)
{
printf("error: test close\n");
return -1;
}
if(ret = 0)
{
printf("succeed: test close\n");
return 0;
}
return 0;
}
至此,对文件系统进行了梳理学习与使用,对Linux文件系统加深了认识。
- 2025-01-23
-
发表了主题帖:
【Wio Lite AI STM32H725AE视觉开发板】--3.串口打印
本帖最后由 dirty 于 2025-1-23 22:54 编辑
本篇讲述实现串口打印功能。
一.硬件原理与准备
STM32H725AE有5个UART和一个LPUART,根据开发板原理图扩展接口,这里选择UART3 如下:
硬件板上接好串口如下:
二.代码准备
1.STM32CubeMX配置UART如下
2.生成代码后修改增加如下代码:
●添加串口重定向函数
#include "stdio.h"
#include "main.h"
/*
...
*/
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
●main函数加入串口打印,如下:
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART3_UART_Init();
/* USER CODE BEGIN 2 */
printf("Welcome to Wio Lite AI STM32H725AE Develop Board !\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))
{
printf("Red Led is On\r\n");
}
else
{
printf("Red Led is Off\r\n");
}
HAL_Delay(1000U);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.编译烧录后运行,可以看到红灯闪烁,且有如下日志
至此,实现串口打印功能。
-
回复了主题帖:
【嘉楠K230开发板】码类识别
freebsder 发表于 2025-1-23 16:48
MicroPython 直接支持barcode?这有点意思了。
支持,就是条形码密集了、太小了,识别不大出来
-
发表了主题帖:
【Wio Lite AI STM32H725AE视觉开发板】--2.开发环境搭建与点灯
本帖最后由 dirty 于 2025-1-23 18:08 编辑
本篇讲述开发环境搭建与点亮LED闪烁。
一.开发环境搭建
1.ST官方IDE安装
首先安装ST官方软件STM32CubeMX、STM32CubeIDE、X-CUBE-AI,其中X-CUBE-AI可以在STM32CubeMX里选择安装,这里均选择Win版本。ST文档给的参考版本如下,
2.Keil 及芯片Pack包安装
Keil IDE留意下版本,主要与STM32CubeMX里兼容匹配,这里使用的版本V5.36.0.0。Keil.STM32H7xx_DFP.4.0.0.pack 可以在Keil官网找到,下载下来后安装上。
经过上面的安装,开发环境基本搭建起来。
二.点灯
下面在STM32CubeMX配置好生成工程,Keil 编译烧录,点亮LED闪烁。
1.LED原理图如下,这里选用PC13引脚,控制LED2红灯。
2.打开STM32CubeMX IDE,选择根据MCU创建工程,由于IDE没有集成STM32H725AEI6这板级选型,就根据MCU创建了,如下图所示:
3.RCC时钟打开,如下
4.LED控制引脚GPIO选择与配置,如下
5.内核配置,这里都给使能
6.系统时钟配置。这里Clock Configuration 里主频最大配置为64MHz,根据芯片手册这里配置为最大550MHz配置如下:
7.工程配置,包括工程名及路径,导出适配的IDE,这里选择Keil MDK-ARM,还有链接堆栈大小,配置如下
8.生成工程。
9.生成工程完后“Open Project”.就会Keil打开工程如下,前面已经安装了芯片pack包,这里开发板连接好ST-LINK,上电,选择配置好调试器。
10.编写点灯闪烁代码。这里主要在主循环下添加代码,如下
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(1000U);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
11.编译后烧录,可以看到开发板红灯闪烁,效果如下
[localvideo]44186c12fec4e39ad4b3baea2b8e847f[/localvideo]
至此,实现开发环境的搭建与点亮LED。
-
回复了主题帖:
【嘉楠K230开发板】码类识别
wangerxian 发表于 2025-1-23 09:17
二维码识别也能达到30帧,还不错!
嗯,还好
-
回复了主题帖:
【嘉楠K230开发板】图像检测
Jacktang 发表于 2025-1-23 08:32
比赛经常用到的小车、机器人巡线这些原来是快速线性回归(巡线)技术算法,,,
是的