python
from maix import camera, display, image, nn, app
detect_conf_th = 0.5
detect_iou_th = 0.45
emotion_conf_th = 0.5
max_face_num = -1
crop_scale = 0.9
# detect face model
detector = nn.YOLOv8(model="/root/models/yolov8n_face.mud", dual_buff = False)
# we only use one of it's function to crop face from image, wo we not init model actually
landmarks_detector = nn.FaceLandmarks(model="")
# emotion classify model
classifier = nn.Classifier(model="/root/models/face_emotion.mud", dual_buff=False)
cam = camera.Camera(detector.input_width(), detector.input_height(), detector.input_format())
disp = display.Display()
# for draw result info
max_labels_length = 0
for label in classifier.labels:
size = image.string_size(label)
if size.width() > max_labels_length:
max_labels_length = size.width()
max_score_length = cam.width() / 4
while not app.need_exit():
img = cam.read()
results = []
objs = detector.detect(img, conf_th = detect_conf_th, iou_th = detect_iou_th, sort = 1)
count = 0
idxes = []
img_std_first : image.Image = None
for i, obj in enumerate(objs):
img_std = landmarks_detector.crop_image(img, obj.x, obj.y, obj.w, obj.h, obj.points,
classifier.input_width(), classifier.input_height(), crop_scale)
if img_std:
img_std_gray = img_std.to_format(image.Format.FMT_GRAYSCALE)
res = classifier.classify(img_std_gray, softmax=True)
results.append(res)
idxes.append(i)
if i == 0:
img_std_first = img_std
count += 1
if max_face_num > 0 and count >= max_face_num:
break
for i, res in enumerate(results):
# draw fisrt face detailed info
if i == 0:
img.draw_image(0, 0, img_std_first)
for j in range(len(classifier.labels)):
idx = res[j][0]
score = res[j][1]
img.draw_string(0, img_std_first.height() + idx * 16, classifier.labels[idx], image.COLOR_WHITE)
img.draw_rect(max_labels_length, int(img_std_first.height() + idx * 16), int(score * max_score_length), 8, image.COLOR_GREEN if score >= emotion_conf_th else image.COLOR_RED, -1)
img.draw_string(int(max_labels_length + score * max_score_length + 2), int(img_std_first.height() + idx * 16), f"{score:.1f}", image.COLOR_RED)
# draw on all face
color = image.COLOR_GREEN if res[0][1] >= emotion_conf_th else image.COLOR_RED
obj = objs[idxes[i]]
img.draw_rect(obj.x, obj.y, obj.w, obj.h, color, 1)
img.draw_string(obj.x, obj.y, f"{classifier.labels[res[0][0]]}: {res[0][1]:.1f}", color)
disp.show(img)
1、from maix import camera,display,image,nn,app这是属于python的模块导入语法,核心包为maix,在maix里导入五个子模块,分别为camera,image,display,nn,app模块
(1)maix.camera是硬件摄像头控制模块,封装了Maix设备上摄像头的所有操作,核心能力包括:
初始化摄像头硬件,配置摄像头参数(分辨率,帧率,像素格式,曝光,白平衡等参数),读取摄像头实时画面(返回图像对象-image),底层驱动管理(自动适配不同的摄像头模组,不用手动配置寄存器)
python
from maix import camera,image
# 初始化摄像头(640x480分辨率,RGB888像素格式)
cam = camera.Camera(width=640, height=480, format=image.Format.FMT_RGB888)
# 读取一帧画面(返回 image.Image 对象)
img = cam.read()
# 关闭摄像头
cam.close()
(2)maix.display是硬件显示屏控制模块,封装了 Maix 设备上显示屏(LCD/HDMI 屏)的所有操作,核心能力包括:
初始化显示屏硬件(如屏幕分辨率),将图像数据传输到屏幕显示,控制屏幕亮度,帅心率,休眠和唤醒,帧缓冲管理
python
from maix import display, camera
cam = camera.Camera()
disp = display.Display() # 自动初始化显示屏
while True:
img = cam.read()
disp.show(img) # 将摄像头画面显示到屏幕
(3)maix.iamge是图像处理核心模块,封装了所有针对图像的操作
| 功能分类 | 具体操作 |
|---|---|
| 图像创建 / 转换 | 创建空白图像、格式转换(RGB↔灰度、RGB565↔RGB888)、尺寸缩放 / 裁剪 |
| 绘图操作 | 画点、线、矩形、圆形、文字、图像叠加(如 draw_rect/draw_string) |
| 像素级操作 | 获取 / 设置像素值、图像翻转 / 旋转、对比度 / 亮度调整 |
| 数据交互 | 图像数据导出(转 numpy 数组)、从字节流创建图像 |
(4)maix.nn是AI推理大模型,封装了 Maix 芯片的 NPU(神经网络处理单元)和 AI 模型运行时,核心能力包括:
- 加载 AI 模型(支持 .mud 格式,Maix 优化的模型格式,也支持 ONNX/TFLite);
- 执行模型推理(人脸检测、情绪分类、目标识别等);
- 推理结果后处理(非极大值抑制 NMS、置信度过滤);
- NPU 资源管理(分配 / 释放计算资源,避免内存泄漏)。
(5)maix.app是应用程序生命周期管理模块,核心解决嵌入式设备中 "优雅退出程序" 的问题
python
from maix import app
# 主循环:直到检测到退出信号才停止
while not app.need_exit():
# 业务逻辑(摄像头采集、推理、显示)
pass
# 程序退出时,app 自动释放所有硬件资源
2、加载AI模型
(1)detector = nn.YOLOv8(model = "/root/models/yolov8n_face.mud",dual_buff = Flase)
.mud 是 maixVision 优化的模型格式
dual_buff=False:关闭双缓冲(开启占用更多内存但提升帧率)
(2)landmarks_detector = nn.FaceLandmarks(model = "")
| 拆分部分 | 逐词深度解析 |
|---|---|
nn |
延续前文解析:Neural Network(神经网络)模块的缩写,封装 Maix 芯片 NPU 驱动、AI 模型加载 / 推理逻辑,是所有 AI 相关类的父模块。 |
. |
Python 属性访问符(点运算符),表示 "访问 nn 模块中的 FaceLandmarks 类"。 |
FaceLandmarks |
- 拆分:Face(/feɪs/,中文 "人脸") + Landmarks(/ˈlændmɑːrks/,中文 "关键点 / 地标",是 landmark 的复数形式); - 代码含义:nn 模块中专门封装 "人脸关键点检测" 的类,该类内置了: ✅ 关键点检测模型的加载逻辑; ✅ 关键点坐标的计算 / 输出逻辑; ✅ 基于关键点的人脸对齐 / 裁剪逻辑(核心复用功能); - 底层逻辑:该类原本设计用于加载人脸关键点检测模型(如 5 点 / 68 点模型),输出人脸特征点坐标;但 maixVision 框架做了灵活设计 ------ 即使不加载模型(model=""),也能调用其内置的 "基于人脸框的裁剪逻辑"(无需关键点,仅用框坐标即可裁剪),这是新手友好的 "功能复用" 设计。 |
(3)classifier = nn.Classifier(model="/root/models/face_emotion.mud", dual_buff=False)
| 拆分部分 | 逐词深度解析 |
|---|---|
nn |
仍为 Neural Network 模块,所有 AI 模型相关类的父模块。 |
. |
属性访问符,访问 nn 模块中的 Classifier 类。 |
Classifier |
- 英文释义:/ˈklæsɪfaɪə(r)/,中文 "分类器",是 maixVision 框架中专门封装 "图像分类 / 文本分类" 任务的通用类; - 代码含义:该类适配所有分类类 AI 模型(如 ResNet、MobileNet、CNN 等轻量化分类模型),内置: ✅ 分类模型的加载 / 解析逻辑; ✅ 输入数据的预处理(如归一化、格式转换); ✅ 推理结果的后处理(如 Softmax 归一化、返回类别 + 置信度); - 底层逻辑:不同于 nn.YOLOv8(专用检测类),nn.Classifier 是通用分类类------ 无论是什么类型的分类模型(只要转成 .mud 格式),都能通过该类加载和推理,无需针对不同分类算法写不同代码,是框架 "通用性" 的体现。 |
(4)参数列表:(model="/root/models/face_emotion.mud", dual_buff=False)
| 参数部分 | 逐词深度解析 |
|---|---|
(/) |
圆括号,包裹分类器初始化参数列表。 |
model |
核心参数,指定情绪分类模型的文件路径;路径 /root/models/face_emotion.mud 拆解: ✅ /root/models/:Maix 设备默认的模型存储目录(同前文); ✅ face_emotion:模型文件名,拆分: - face:表示模型输入是 "人脸图像"(而非全图); - emotion:/ɪˈməʊʃn/,中文 "情绪",表示模型的核心任务是情绪分类; ✅ .mud:maixVision 优化的模型格式(同前文),该格式针对情绪分类模型做了量化(INT8)和 NPU 适配,确保在嵌入式设备上快速推理。 |
, |
英文逗号,分隔多个关键字参数。 |
dual_buff |
同前文:dual buffer(双缓冲)的缩写,控制推理的缓冲机制;此处设为 False 的原因: ✅ 情绪分类是 "单帧处理",无需高帧率(10 FPS 足够); ✅ 节省内存:情绪分类模型输入尺寸小(如 48x48 灰度图),双缓冲节省的时间可忽略,关闭后减少内存占用。 |
= |
关键字参数赋值符号。 |
False |
Python 布尔常量(假),表示关闭双缓冲机制;对比 True:若设为 True,会初始化双缓冲内存,推理帧率提升 1~2 FPS,但内存占用增加约 100KB(情绪分类模型输入小,内存影响极小,新手仍建议保持 False 以简化逻辑)。 |
3、硬件初始化
python
# detector.input_width()/height():获取模型要求的输入尺寸
# detector.input_format():获取模型要求的图像格式(如 RGB565)
cam = camera.Camera(detector.input_width(), detector.input_height(), detector.input_format())
# 初始化显示屏:自动适配硬件屏幕分辨率(如 MaixCAM 的 1080P 屏)
disp = display.Display()
| 拆分部分 | 逐词深度解析 |
|---|---|
camera |
- 单词本义:/ˈkæm (ə) rə/,中文 "摄像头、摄影机";- 代码含义:从maix包导入的 "摄像头硬件控制模块"(前文已解析),封装了摄像头的底层驱动(如 MIPI/USB 摄像头驱动)、参数配置、画面读取等核心功能;- 底层逻辑:该模块不是纯 Python 代码,而是 C/C++ 实现的硬件驱动 + Python 封装的接口,直接操作摄像头的寄存器和帧缓冲区。 |
. |
Python 属性访问符(点运算符),表示 "访问camera模块中的Camera类"。 |
Camera |
- 单词本义:/ˈkæm (ə) rə/,首字母大写表示这是一个类(Class) (Python 中类名通常首字母大写);- 代码含义:camera模块的核心类,是操作摄像头的唯一入口,初始化该类时会完成: ✅ 硬件检测:识别设备上连接的摄像头类型(MIPI/USB); ✅ 参数配置:设置分辨率、像素格式、帧率、曝光等; ✅ 硬件打开:启动摄像头的图像采集功能,分配帧缓冲区内存;- 核心特性:支持 "参数自动适配",无需手动配置底层寄存器。 |
| 参数部分 | 逐词深度解析 |
|---|---|
(/) |
圆括号,包裹类初始化的参数列表,向Camera类的__init__方法传递参数。 |
detector.input_width() |
- detector:前文初始化的 YOLOv8 人脸检测模型对象(nn.YOLOv8实例);- .:属性访问符,访问detector对象的方法;- input_width():nn.YOLOv8类的内置成员方法 (无参数),作用是: 1. 读取.mud模型文件中存储的 "输入宽度" 元数据(如 640); 2. 返回该数值(整数类型);- 底层逻辑:模型文件(.mud)中不仅包含权重和网络结构,还存储了输入尺寸、格式、输出类别等元数据,input_width()就是读取该元数据的接口。 |
, |
英文逗号,分隔多个位置参数(按顺序传递,区别于关键字参数)。 |
detector.input_height() |
- 与input_width()对称,返回模型要求的输入高度(如 640);- 注意:YOLOv8 模型的输入通常是正方形(如 640×640),但也支持矩形(如 640×480),该方法会返回实际值。 |
, |
参数分隔符。 |
detector.input_format() |
- input_format():nn.YOLOv8类的内置方法,返回模型要求的输入图像格式;- 返回值类型:不是字符串(如 "RGB565"),而是image.Format枚举常量(如image.Format.FMT_RGB565);- 核心作用:确保摄像头输出的图像格式与模型要求完全一致,避免格式转换的性能损耗(嵌入式设备格式转换会占用 CPU 资源,降低帧率)。 |
4. 预处理:计算可视化尺寸(保证显示美观)
python
# ===================== 4. 预处理:计算可视化尺寸(保证显示美观) =====================
max_labels_length = 0 # 存储最长情绪标签的宽度(用于对齐得分条)
# 遍历所有情绪标签,计算文字显示的最大宽度
for label in classifier.labels:
# 获取当前标签文字的显示尺寸(宽/高)
size = image.string_size(label)
# 更新最大宽度
if size.width() > max_labels_length:
max_labels_length = size.width()
# 得分条最大宽度:摄像头宽度的 1/4(可根据屏幕大小调整)
max_score_length = cam.width() / 4
| 注释内容 | 逐词 / 逐句解析 |
|---|---|
4. 预处理:计算可视化尺寸(保证显示美观) |
- #:Python 单行注释符,无执行意义;- 4. 预处理:标注这是程序第 4 个核心步骤,"预处理" 是编程术语,指 "在核心逻辑(如绘制)前,提前计算 / 准备所需的参数,避免主循环中重复计算(提升效率)";- 计算可视化尺寸:明确预处理的核心任务 ------ 计算文字、得分条的显示尺寸;- (保证显示美观):解释预处理的最终目的:避免文字和得分条重叠、错位,让屏幕显示的内容整齐对齐;- 等号=====:格式分隔符,提升代码可读性。 |
# 存储最长情绪标签的宽度(用于对齐得分条) |
- 存储最长情绪标签的宽度:解释变量max_labels_length的作用 ------ 记录所有情绪标签(如 happy/angry/neutral)中,文字显示时宽度最大的值;- (用于对齐得分条):说明该变量的核心用途:让所有得分条从 "最长标签宽度" 的位置开始绘制,确保得分条左对齐(比如 "happy" 宽 80 像素,"angry" 宽 70 像素,得分条都从 80 像素位置开始,不会错位)。 |
# 遍历所有情绪标签,计算文字显示的最大宽度 |
- 遍历:Python 循环术语(对应for循环),指 "逐个访问列表中的元素";- 所有情绪标签:指classifier.labels中的每一个标签(如 ["happy", "angry", "neutral"]);- 计算文字显示的最大宽度:说明循环的核心目的 ------ 算出所有标签文字在屏幕上显示时的最大宽度。 |
# 获取当前标签文字的显示尺寸(宽/高) |
- 获取当前标签文字:指循环中当前迭代的label变量(如第一次循环是 "happy",第二次是 "angry");- 显示尺寸(宽/高):文字在屏幕上显示时占据的像素宽度和高度(如 "happy" 显示为宽 80 像素、高 16 像素);- 补充:不同文字的显示尺寸不同(如 "开心" 和 "生气" 的中文字宽不同,英文字母也因字符数 / 字体不同而不同)。 |
# 更新最大宽度 |
- 更新:指比较当前标签宽度和已记录的最大宽度,若更大则替换;- 最大宽度:即变量max_labels_length存储的值。 |
# 得分条最大宽度:摄像头宽度的 1/4(可根据屏幕大小调整) |
- 得分条最大宽度:解释变量max_score_length的作用 ------ 限定情绪得分条的最大像素宽度(避免得分条太长占满屏幕);- 摄像头宽度的 1/4:说明宽度的计算规则(按摄像头分辨率的比例,而非固定值,适配不同硬件);- (可根据屏幕大小调整):使用建议 ------ 屏幕大则可设为 1/3,屏幕小则设为 1/5,灵活调整。 |
size = image.string_size(label)
| 部分 | 含义解析 |
|---|---|
image |
maix 的图像模块(前文解析过),封装了所有图像 / 文字绘制相关功能; |
. |
属性访问符,访问image模块的函数; |
string_size |
模块内置函数,核心作用:计算指定文字在屏幕上显示时的 "像素尺寸(宽 × 高)"; - 函数参数:string_size(text, font=None, size=None),默认使用硬件内置字体(如 16 号像素字体); - 返回值:image.Size对象,包含width()(宽度)和height()(高度)两个方法。 |
label |
循环变量,传入当前要计算的情绪标签文字(如 "happy")。 |
再来就是更新最大宽度
| 部分 | 含义解析 |
|---|---|
if |
Python 条件判断关键字,满足条件时执行缩进的代码块; |
size.width() |
调用Size对象的width()方法,返回当前标签文字的显示宽度(如 80); |
> |
比较运算符,"大于"; |
max_labels_length |
当前记录的最大宽度(初始为 0,后续随循环更新)。 |
计算得分条最大宽度
| 部分 | 含义解析 |
|---|---|
max_score_length |
变量名:max(最大)+score(得分)+length(长度)→ "得分条的最大显示长度"; |
cam.width() |
调用摄像头对象的width()方法,返回摄像头的宽度(如 640 像素); |
/ |
Python 除法运算符,计算摄像头宽度的 1/4; |
4 |
比例系数,可调整为 3/5/6 等,控制得分条的最大宽度。 |
5、主循环核心逻辑
| 注释内容 | 逐词 / 逐句解析 |
|---|---|
# ===================== 5. 主循环(核心业务逻辑) ===================== |
- #:Python 单行注释符;- 5. 主循环:标注这是程序第 5 个核心步骤,"主循环" 是嵌入式 / 实时应用的核心概念 ------ 无限循环执行核心业务,直到退出指令;- (核心业务逻辑):强调这是程序的核心功能(人脸检测 + 情绪分类 + 可视化),区别于之前的 "模型加载 / 硬件初始化 / 预处理" 等准备工作;- 等号=====:格式分隔符,提升可读性。 |
# app.need_exit():检测退出指令(如硬件按键/终端指令),优雅退出程序 |
- app.need_exit():app模块的核心方法,返回True/False;- 检测退出指令:说明该方法的作用 ------ 监听硬件退出键(如 MaixCAM 的按键)、终端 Ctrl+C、系统关机信号;- (如硬件按键/终端指令):举例退出指令的来源;- 优雅退出程序:解释 "非暴力退出"------ 程序退出前会自动释放摄像头 / NPU / 显示屏资源,避免硬件异常。 |
# 读取摄像头一帧画面(返回 image.Image 对象) |
- 读取摄像头一帧画面:cam.read()的核心作用 ------ 从摄像头帧缓冲区读取一张实时图像;- (返回 image.Image 对象):说明返回值类型,后续所有图像处理(检测 / 裁剪 / 绘制)都基于该对象。 |
# 初始化临时变量 |
- 初始化临时变量:在每次循环(每帧画面)中重置变量,避免上一帧的数据残留;- 临时变量:仅在当前帧有效,下一帧会重新初始化。 |
# 存储每个人脸的情绪分类结果 |
解释results列表的作用 ------ 按顺序存储每个有效人脸的情绪分类结果(如[[(0,0.9),(1,0.1)], [(1,0.8),(0,0.2)]])。 |
# 存储有效人脸的索引(过滤裁剪失败的人脸) |
- 存储有效人脸的索引:解释idxes列表的作用 ------ 记录 "裁剪成功的人脸" 在objs(检测到的所有人脸)中的索引;- (过滤裁剪失败的人脸):说明目的 ------ 比如人脸框超出画面导致裁剪失败,该人脸会被过滤,不参与后续分类 / 绘制。 |
# 存储第一个人脸的标准化裁剪图(放大显示) |
解释img_std_first变量的作用 ------ 保存第一个有效人脸的裁剪图(适配分类模型尺寸),用于在屏幕左上角放大显示,方便查看细节。 |
# 执行人脸检测:返回符合阈值的所有人脸框 |
- 执行人脸检测:detector.detect()的核心作用 ------ 对当前帧图像执行 YOLOv8 人脸检测;- 返回符合阈值的所有人脸框:说明返回值是 "满足置信度 / IOU 阈值" 的人脸框列表,过滤低置信度的误检框。 |
# conf_th:置信度阈值;iou_th:IOU阈值;sort=1:按置信度从高到低排序 |
- conf_th:confidence threshold(置信度阈值),过滤置信度 < 该值的人脸框(如 0.5,仅保留置信度≥50% 的人脸);- iou_th:IOU threshold(交并比阈值),用于 NMS(非极大值抑制),过滤重叠度高的重复人脸框;- sort=1:排序规则,1 表示 "按置信度从高到低排序",0 表示不排序;- 补充:这三个参数是目标检测的核心后处理参数,确保检测结果精准。 |
# 计数:有效人脸数量 |
解释count变量的作用 ------ 统计当前帧中 "裁剪成功的有效人脸数",用于控制最大人脸数(max_face_num)。 |
# 遍历每一个检测到的人脸 |
说明for i, obj in enumerate(objs)的作用 ------ 逐个处理检测到的每个人脸框。 |
# 裁剪并标准化人脸:适配情绪分类模型的输入尺寸 |
- 裁剪并标准化人脸:landmarks_detector.crop_image()的核心作用 ------ 从原始图中裁剪出人脸区域,并缩放到分类模型要求的尺寸(如 48x48);- 适配情绪分类模型的输入尺寸:说明裁剪的最终目的 ------ 分类模型要求固定尺寸输入,原始人脸框大小不一,需标准化。 |
# 参数:原始图、人脸框坐标、关键点、模型输入宽/高、裁剪缩放比例 |
逐一对crop_image的参数说明,方便理解每个参数的含义。 |
# 过滤裁剪失败的人脸(如人脸框超出画面) |
说明if img_std:的作用 ------crop_image裁剪失败时返回None,此处过滤掉这些无效人脸,避免后续代码报错。 |
# 转换为灰度图:情绪分类模型要求输入灰度图(减少计算量) |
- 转换为灰度图:img_std.to_format(...)的作用 ------ 将 RGB 图转为单通道灰度图;- 情绪分类模型要求输入灰度图:说明转换的必要性(模型训练时用灰度图,输入需匹配);- (减少计算量):补充优势 ------ 灰度图每个像素 1 字节,RGB 图 2/3 字节,减少 NPU 计算量,提升帧率。 |
# 执行情绪分类:softmax=True 输出 0-1 概率值 |
- 执行情绪分类:classifier.classify()的核心作用 ------ 对标准化灰度人脸图执行情绪分类推理;- softmax=True 输出 0-1 概率值:说明参数作用 ------Softmax 归一化后,所有情绪的置信度之和为 1,直观展示概率。 |
# 保存结果和索引 |
说明results.append(res)和idxes.append(i)的作用 ------ 将有效人脸的分类结果和对应索引保存,后续绘制时匹配。 |
# 保存第一个人脸的标准化裁剪图(用于放大显示) |
说明if i == 0: img_std_first = img_std的作用 ------ 仅保存第一个有效人脸的裁剪图,用于左上角放大显示。 |
# 有效人脸数+1 |
说明count += 1的作用 ------ 统计有效人脸数量,用于控制最大人脸数。 |
# 达到最大人脸数则停止遍历(提升速度) |
- 达到最大人脸数则停止遍历:说明if max_face_num >0 and count >= max_face_num: break的作用;- (提升速度):补充优势 ------ 限制处理的人脸数(如最多处理 5 个人脸),避免人脸过多导致帧率暴跌。 |
# ===================== 6. 可视化结果(绘制到画面) ===================== |
标注这是程序第 6 个步骤,核心是 "将检测 / 分类结果绘制到图像上,方便可视化查看"。 |
# 遍历每个有效人脸的情绪结果 |
说明for i, res in enumerate(results)的作用 ------ 逐个处理每个有效人脸的分类结果,进行绘制。 |
# 处理第一个人脸:放大显示裁剪图 + 所有情绪得分条 |
说明if i == 0 and img_std_first:的作用 ------ 仅对第一个人脸做 "放大显示 + 所有情绪得分条",避免画面杂乱。 |
# 在屏幕左上角绘制第一个人脸的裁剪图(坐标 0,0) |
说明img.draw_image(0, 0, img_std_first)的作用 ------ 将裁剪图绘制到图像的左上角(x=0,y=0)。 |
# 遍历所有情绪标签,绘制标签文字 + 得分条 |
说明内层循环的作用 ------ 为第一个人脸绘制所有情绪的标签和对应的得分条(直观展示所有情绪的概率)。 |
# 计算文字绘制位置:裁剪图下方,每行间隔 16 像素(避免重叠) |
说明text_y = img_std_first.height() + idx *16的作用 ------ 文字绘制在裁剪图下方,每行间隔 16 像素(字体高度),避免重叠。 |
# 绘制情绪标签文字(白色) |
说明img.draw_string(...)的作用 ------ 绘制情绪标签文字(如 happy/angry),颜色为白色。 |
# 绘制得分条:绿色(≥阈值)/红色(<阈值),实心填充(-1) |
- 绘制得分条:img.draw_rect(...)的作用 ------ 用矩形表示情绪概率(宽度 = 概率 × 最大得分条宽度);- 绿色(≥阈值)/红色(<阈值):说明颜色规则,直观区分高 / 低置信度;- 实心填充(-1):解释draw_rect最后一个参数 -------1 表示实心矩形,正数表示线宽。 |
# 绘制概率数值(红色,保留1位小数) |
说明img.draw_string(...)的作用 ------ 在得分条右侧绘制概率值(如 0.9),保留 1 位小数,简洁易读。 |
# 处理所有人脸:绘制人脸框 + 主要情绪标签 |
说明后续代码的作用 ------ 对所有有效人脸,绘制人脸框和最可能的情绪标签(核心可视化信息)。 |
# 确定人脸框颜色:主要情绪置信度≥0.5为绿色,否则红色 |
说明color变量的赋值规则 ------ 用颜色区分置信度高低,直观展示识别结果是否可靠。 |
# 获取当前人脸的检测框 |
说明obj = objs[idxes[i]]的作用 ------ 通过idxes索引匹配到原始检测的人脸框,用于绘制。 |
# 绘制人脸框(线宽1像素) |
说明img.draw_rect(...)的作用 ------ 绘制人脸框,线宽 1 像素(避免过粗遮挡画面)。 |
# 绘制主要情绪标签+概率(如"happy: 0.9") |
说明img.draw_string(...)的作用 ------ 在人脸框左上角绘制最可能的情绪标签和概率,方便查看。 |
# 显示最终画面到屏幕 |
说明disp.show(img)的作用 ------ 将绘制完成的图像显示到硬件屏幕上。 |
# 程序退出时自动释放硬件资源(maixVision 内部已封装,无需手动处理) |
说明程序退出时的资源管理 ------maixVision 框架会自动释放摄像头 / NPU / 显示屏资源,新手无需手动调用close()。 |