如果你玩过树莓派人脸识别,大概率经历过这种尴尬:OpenCV的教程一搜一大把,代码抄下来跑起来,结果实时预览卡成PPT,推理一次几百毫秒,别说门禁,连拍个照都嫌慢。后来我拿到CanMV K230开发板,原本没抱多大期望,结果用MicroPython写人脸识别,从烧录固件到画面里框出人脸,前后不到5分钟。这篇文章就把这套流程、完整代码以及我实际踩过的坑一次性讲清楚,适合手里有K230、想快速跑通视觉AI项目的朋友直接照抄。
1. 为什么我用K230而不是树莓派玩人脸识别
1.1 树莓派方案和K230方案的根本差异
先说出路。树莓派加OpenCV,本质上是拿CPU硬算,或者勉强挂一个USB加速棒,推理走通用计算核心,功耗和实时性很难兼顾。K230不一样,它内部集成了KPU单元,专门干神经网络推理,CPU只用负责调度和预处理,相当于别人用厨师手工揉面,你直接买了一台和面机。所以同样是MicroPython环境,K230跑人脸检测能到30帧甚至更高,树莓派上同样分辨率慢得多。
K230是一颗双核RISC-V 64位处理器,核心A是GCV,核心B也是GCV,两个核可以在不同 OS 下各自跑,其中一个核运行CanMV的MicroPython固件,另一个核可以跑Linux或者协助处理视觉任务。实际体验下来,MicroPython直接调用KPU跑模型,绝大部分算力都由硬件加速完成,不会像PC上的Python那样成为性能瓶颈。
对比一下主流方案更适合看数据:
| 方案 | 推理方式 | 实测帧率(VGA分辨率) | 功耗 | 开发语言 |
|---|---|---|---|---|
| 树莓派4B + OpenCV DNN | CPU推理 | 3~5 FPS | 约7W | C++/Python |
| 树莓派 + Coral NPU | 专用加速器 | 30 FPS | 约5W(不含板卡) | Python |
| 香橙派/其他SoC | 取决于硬件 | 波动大 | 高 | C++/Python |
| CanMV K230 | 内置KPU | 20~40 FPS | 约2W | MicroPython |
这样一个差异决定了K230很适合做低功耗、实时性要求高的边缘视觉设备,比如门锁、考勤机、智能家居中控。至于树莓派,更适合做原型验证和软件生态依赖重的项目。
1.2 MicroPython在K230上能做什么
很多人担心MicroPython性能不行,觉得用Python做AI不靠谱。K230的CanMV固件把Python解释器做得很轻量,底层关键代码都是C实现,包括摄像头采集、图像缩放、KPU推理封装。你写的是几行Python,实际上调用的是已经优化过的C函数,所以完全够用。
CanMV固件里自带这些能力:摄像头采集与回显、LCD显示、GPIO控制、串口通信,以及一系列AI算法封装,比如人脸检测、人脸识别、物体检测、图像分类。官方已经帮你把模型和调用接口打包好,你不需要懂网络结构怎么搭、权重怎么量化,只要知道怎么把数据传进去、把结果取出来。
对新手来说,这相当于给了你一辆点着火的汽车,你只需要踩油门,不用自己组装发动机。对老手来说,也可以通过底层扩展接口继续深挖,不会锁死你。
1.3 物料清单和开发环境
准备东西不多,我列一下我这套环境的明细:
- 嘉楠CanMV K230开发板(带摄像头模组和屏幕,某宝常见版本就可)
- 一张16GB以上的TF卡(写入固件用,Class 10或U1更好)
- USB Type-C数据线(供电和数据通信)
- 一个USB转TTL串口工具(调试方便,可选)
- 安装了串口助手的电脑,比如MobaXterm、PuTTY或者官方IDE
烧录系统推荐使用官方CanMV镜像,版本尽量选相对新的稳定版,不同版本API会有细微差异,这点后面会单独说。硬件准备齐全后,5分钟跑通完全可行。
2. 5分钟快速跑通:固件、摄像头、模型三件套
2.1 烧录CanMV固件的正确姿势
K230开发板和树莓派烧录不太一样,它出厂可能自带一个测试固件,但为了跑MicroPython代码,还是建议刷入CanMV专用固件。烧录工具Windows用官方提供的RTT烧录工具或者CanMV助手,macOS/Linux可以用命令行工具,过程都差不多。
先按住开发板上的下载模式按键,再用Type-C线连接到电脑,让板子进入USB下载模式。打开烧录工具,选择下载镜像,点烧写即可。这里我最想提醒的是:TF卡里最好放一份系统镜像备份,同时固件烧到板载Flash还是TF卡,这两种方式的启动方式不一样。默认情况下CanMV固件是烧写进板载Flash的,TF卡只用于存储代码和模型文件。如果你看到串口终端卡在某一行没反应,先检查启动模式选择,通常需要拨码开关或跳线帽切换。
烧录完成后接上串口,波特率用115200,打开电源就能看到MicroPython交互提示符,输入print("hello")能回显就说明系统活了。这一步一般2分钟能搞定。
2.2 第一行代码:初始化摄像头并且实时显示画面
启动串口后,直接在REPL里敲这段代码:
from maix import camera, display cam = camera.Camera(320, 240) disp = display.Display() while True: img = cam.read() disp.show(img)如果一切正常,你会看到屏幕上有实时画面。注意camera.Camera的参数分别代表宽和高,320x240是速度和画质的折中点。直接跑1920x1080会明显卡顿,主要瓶颈在图像预处理和显示传输,后面会展开讲。
这一小段代码算是整个项目的“地基”,摄像头初始化失败后面啥都做不了。万一黑屏,不用急着怀疑硬件,先把摄像头排线重新插紧,再检查代码里有没有被其他程序占用摄像头,比如之前跑过的终端还连着REPL没复位。
2.3 模型加载与检测:让KPU动起来
画面能正常回显后,剩下就是加载模型调用KPU。
CanMV固件的maix.nn模块里封装了常用模型类,我们做人脸检测直接调用nn.FaceDetection。初始化时指定模型路径,CanMV官方镜像会把一些公开模型放在/root/models/目录下,比如人脸检测模型、人脸识别模型。不同版本文件后缀可能是.mud、.kmodel,以实际环境为准。
加载模型后,每次从摄像头取一帧图像,调用detector.detect(img),返回结果就是一个包含人脸框坐标和置信度的列表。把这些框直接用img.draw_rect画出来,再显示,就是最简人脸检测Demo。整个过程不需要自己处理复杂的后处理逻辑,KPU已经做了大部分工作。
2.4 完整的人脸识别代码(可直接复制)
下面是我实际跑通过的一个版本,实现了“检测到人脸并提取特征”的基础功能,你要做识别门禁也可以在此基础上扩展:
from maix import camera, display, image, nn import time cam = camera.Camera(320, 240) disp = display.Display() # 初始化人脸检测器和特征提取器 detector = nn.FaceDetection(model="/root/models/face_detector.mud") recognizer = nn.FaceRecognition(model="/root/models/face_recognizer.mud") # 保存已知人脸特征,第一次遇到某个新脸时就存下来 known_features = None while True: img = cam.read() faces = detector.detect(img) for face in faces: # 在原图上画框 img.draw_rect(face.x, face.y, face.w, face.h, color=(255, 0, 0)) # 提取当前人脸的特征向量 features = recognizer.get_feat(img, face) if features is None: continue if known_features is None: # 第一次见到的人脸,保存特征,模拟“录入人脸” known_features = features.copy() img.draw_string(face.x, face.y - 20, "SAVED", scale=1.2, color=(0, 0, 255)) print("face saved") else: # 和已保存的人脸进行相似度比较 similarity = recognizer.compare(known_features, features) print("similarity =", similarity) if similarity > 0.7: img.draw_string(face.x, face.y - 20, "PASS", scale=1.5, color=(0, 255, 0)) else: img.draw_string(face.x, face.y - 20, "UNKNOWN", scale=1.5, color=(255, 0, 0)) disp.show(img)注意,不同固件版本里FaceRecognition类的get_feat和compare接口可能会有差异,有的版本叫get_feat,有的叫embedding,compare也可能返回的是距离而不是相似度。如果你跑起来报AttributeError,去官方文档对应版本查一下接口名称,逻辑本身是一样的。这段代码保存的第一个脸是“管理员”,后面再出现的人脸会实时比较相似度,大于阈值就判定为同一人。
3. 人脸识别代码拆解与参数调优
3.1 每一段代码在做什么
把代码拆开来看,核心就三步:采集、推理、结果处理。摄像头持续输出图像帧,检测器在每一帧上找脸,识别器从人脸框区域提取特征向量。特征向量可以理解为把一个人脸压缩成一组数字,同一个人的不同角度、不同光线下的数字尽量接近,不同人的数字尽量远离。
recognizer.compare这一步实际就是算两个向量的相似度。阈值0.7是我在室内环境试出来的一个相对稳妥的值,光线稳定时相似度通常在0.85以上,不同人一般不会超过0.55。但阈值不是死的,如果频繁把陌生人放行,调高到0.75或0.8;如果总是漏掉本人,适当降低到0.6。建议抓几组真人和非真人的相似度数据,看分布再定阈值。
3.2 调节检测框参数,减少误检
detect方法通常不带参数直接用,但实际使用中有几个隐藏参数很关键。一是输入图像大小,检测器对输入分辨率有要求,比如某些模型需要128x128或160x160的脸部区域,你在摄像头采集后可以先调用img.resize,减少无效区域,也能减少算力浪费。二是检测框重叠率,模型可能会对同一张脸输出多个框,CanMV的nn封装里一般内置了NMS非极大值抑制,但如果你发现一个脸画了几层框,可以检查固件版本,或者手动调低置信度阈值。
更常见的误检来自非人脸误识别,比如照片里的人脸、画报上的人脸都会被模型当成真人。如果你做门禁,这是致命问题。简单办法是加一个活体检测策略,比如要求人脸框区域连续多帧的坐标变化在特定范围内,排除静态图片上完全静止的“脸”;更进一步可以用红外摄像头或者要求用户眨眼,不过那属于扩展功能,MicroPython下也能做,思路是利用两帧图像中人眼区域的变化。
3.3 分辨率、帧率与内存之间的平衡
很多人上来就想用最高分辨率,结果发现帧率掉得厉害。我实测过一组数据:
| 摄像头分辨率 | 检测+识别耗时(约) | 实际帧率 | 内存占用 | 画面观感 |
|---|---|---|---|---|
| 320x240 | 35ms | 28 FPS | 较低 | 清晰但边缘略糊 |
| 480x320 | 50ms | 20 FPS | 中 | 日常够用 |
| 640x480 | 100ms | 10 FPS | 较高 | 细节好,帧率一般 |
| 800x600以上 | 200ms+ | 5 FPS以下 | 高 | 不推荐 |
K230的KPU推理速度很快,但人脸检测前后还有图像读取、颜色空间转换、缩放、显示输出等步骤,这些操作会占用CPU。分辨率越高,CPU预处理越慢,帧率瓶颈往往不是KPU而是图像传输链路。
所以我建议把摄像头设为320x240,如果对画质有更高要求就480x320。真正需要看清人脸细节的场景,可以把识别框内的小图单独放大处理,而不是整个画面开高分辨率。
4. 实战中我踩过的几个坑
4.1 摄像头打不开,画面黑屏
这个坑我最早踩,原因是板子之前跑过一次识别Demo,程序中途断电,但摄像头资源没有释放。重启板子后,旧的MicroPython进程还活着,占着设备节点。解决方法是拔掉电源、长按复位键,或者直接重新插拔摄像头排线。如果你在IDE里遇到过camera init error,多半就是这个原因。
另一种情况是固件里摄像头传感器型号和硬件不匹配。K230开发板带有不同型号的摄像头模组,有的模组需要额外改一个配置文件。检查CanMV启动日志里摄像头型号识别是否正常,如果型号异常,先更新固件或手动指定摄像头类型。
4.2 运行时报内存不足,板子反复重启
MicroPython环境下内存管理比较紧张,K230的RAM虽然不小,但如果你同时加载好几个模型,或者把图像缩放到很大,内存就爆了。常见表现是程序跑到一半自动回到REPL提示符,或者板子彻底黑屏重启。
我遇到的情况是同时加载了人脸检测模型和一个人脸表情识别模型,内存直接溢出。解决办法是不需要同时加载的模型不要同时加载,跑完识别后可以调用del删除模型对象,再加载新的模型。另外尽量把图像分辨率控制在320x240,图像对象在Python层也要记得释放,循环里不要频繁创建大量临时图像变量。
如果你在代码里用img.copy()保存了上一次的帧,相当于每帧多占一份内存,长时间运行迟早出问题。可以用img.clear()或者直接复用同一个Image对象。
4.3 识别率飘忽不定,光线差异影响
白天和晚上,同一个人的相似度能差出0.2以上,这个我观察过很多次。原因很简单,人脸特征提取模型对光照非常敏感,强光直接打在脸上时,特征向量偏移明显。K230的摄像头不像手机有复杂的ISP处理,CanMV固件里也提供了简单的摄像头参数调节,比如白平衡、曝光、对比度。
推荐做法是在固定使用场景下,先把白平衡和曝光固定住,不要开自动。室内场景可以关闭自动白平衡,手动设定一个固定值;室外使用则开启自动曝光调节。同时,录入人脸时最好在正常使用环境下录入,多录入几张不同角度的特征做平均或列表保存,比对时有一个样本通过就算通过。
我自己在门禁项目里做了三张特征库,分别对应正面、左侧、右侧,识别成功率明显提升。
5. 从“识别到脸”到“开锁动作”:让Demo落地成门禁
5.1 简单的人脸比对逻辑实现
前面代码里只保存了一个已知特征,实际门禁需要保存多个人脸。简单方法就是用一个列表存特征:
known_features_list = [] known_names = [] def enroll(features, name): known_features_list.append(features.copy()) known_names.append(name) def verify(features, threshold=0.7): for i, known in enumerate(known_features_list): score = recognizer.compare(known, features) if score > threshold: return known_names[i], score return None, 0循环里检测到人脸后提取特征,先和库里所有人依次比对,返回匹配度最高的名字,然后根据是否匹配决定开锁动作。这里要注意特征列表占用的内存非常小,一个人脸特征一般是一两百个浮点数,一个人就算存几十个角度也才几十KB,完全不用慌。
5.2 通过GPIO控制舵机,模拟开门
CanMV的maix.gpio模块或者machine.Pin可以控制板载引脚。以machine.Pin为例:
from machine import Pin import time lock = Pin(15, Pin.OUT) # 随便选一个空闲引脚 def open_lock(): lock.value(1) time.sleep_ms(300) lock.value(0)如果你用舵机,需要PWM输出。有些K230开发板引出PWM引脚,用machine.PWM或者maix.pwm设置占空比,把舵机转到120度模拟开门,然后延时后回位。同一个循环里识别通过就调用开锁函数,逻辑非常直观。
不过有一个细节:开锁动作是人脸识别之后立刻发生,但你还需要防止一个画面里的同一张脸反复触发开门。解决办法是在识别通过后加入一个冷却时间,比如10秒内不再执行开锁动作,或者只有检测到新的人脸帧才触发,否则门口站一个人会听到舵机不停咔咔响。
5.3 数据持久化和更复杂的扩展方向
写完代码直接断电,录入的人脸就没了,因为特征只存在内存里。要做到每次启动自动加载,可以先把特征向量转成bytes,再写进TF卡的文件系统里,启动时循环加载。MicroPython写文件的方式和平常Python一样,所以这个功能实现难度不大。
再进一步,把K230和串口设备对接,比如识别成功后通过串口发送一个“OK”指令给外部主机,或者连接到云平台记录开门日志,都是很好的扩展方向。K230本身支持网络,CanMV也提供了网络驱动,你可以用MicroPython直接接入MQTT上报识别事件。这块我后续正在做,等跑通了再整理一篇更完整的文章。
如果你手里的任务只是“快速展示人脸识别”,那么复制上面的代码就能立刻看到效果。如果你要做产品级应用,记住把阈值、光线、多帧稳定这些细节调好,并且务必让程序支持看门狗和异常重启,毕竟嵌入式设备的稳定性靠的不是运气,而是工程细节。