【K210】Maixpy 人脸识别

硬件平台:K210 Sipeed Maix Dock

软件平台:maixpy 、kmodelv2

实现功能 检测并识别人脸

一、下载模型和示例脚本

需要用到三个模型,

task_fd = kpu.load(0x300000) #从flash 0 0x300000 加载人脸检测模型
task_ld = kpu.load(0x400000) #从flash 0 0x400000 加载人脸五点关键点检测模型
task_fe = kpu.load(0x500000) #从flash 0 0x500000 加载人脸196维特征值模型

脚本如下:

import sensor
import image
import lcd
import KPU as kpu
import time
from Maix import FPIOA, GPIO
import gc
from fpioa_manager import fm
#from board import board_info
import utime
#import 相关库

task_fd = kpu.load(0x300000) #从flash 0 0x300000 加载人脸检测模型
task_ld = kpu.load(0x400000) #从flash 0 0x400000 加载人脸五点关键点检测模型
task_fe = kpu.load(0x500000) #从flash 0 0x500000 加载人脸196维特征值模型

clock = time.clock()  #初始化系统时钟,计算帧率

fm.register(16, fm.fpioa.GPIOHS0) #设置 boot按键 的io
key_gpio = GPIO(GPIO.GPIOHS0, GPIO.IN)
start_processing = False

BOUNCE_PROTECTION = 50


def set_key_state(*_):  #按键中断
    global start_processing
    start_processing = True
    utime.sleep_ms(BOUNCE_PROTECTION)


key_gpio.irq(set_key_state, GPIO.IRQ_RISING, GPIO.WAKEUP_NOT_SUPPORT)

lcd.init() #初始化 lcd
sensor.reset()#初始化sensor摄像头
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_hmirror(1) #设置摄像头镜像
sensor.set_vflip(1) #设置摄像头翻转
sensor.run(1) #试能摄像头
anchor = (1.889, 2.5245, 2.9465, 3.94056, 3.99987, 5.3658, 5.155437,
          6.92275, 6.718375, 9.01025)  # 人脸检测锚
dst_point = [(44, 59), (84, 59), (64, 82), (47, 105),
             (81, 105)]  # 标准人脸关键点位置  标准正脸的5关键点坐标 分别为 左眼 右眼 鼻子 左嘴角 右嘴角
a = kpu.init_yolo2(task_fd, 0.5, 0.3, 5, anchor) #初始化人脸检测模型
img_lcd = image.Image() #设置显示buf
img_face = image.Image(size=(128, 128))#设置 128*128 人脸图片buf
a = img_face.pix_to_ai() #将图片转换为kpu接受的格式
record_ftr = []  #空列表 用于存储当前196维特征值
record_ftrs = []  #空列表 用于存储按键记录下人脸特征,可以将特征以txt等文件形式保存到sd卡后,读取到此列表,即可实现人脸断电存储。
names = ['Mr.1', 'Mr.2', 'Mr.3', 'Mr.4', 'Mr.5',
         'Mr.6', 'Mr.7', 'Mr.8', 'Mr.9', 'Mr.10']     # # 人名标签,与上面列表特征值一一对应。
ACCURACY = 85   # 设定分数标准 人脸阈值

while (1):
    img = sensor.snapshot()
    clock.tick()
    code = kpu.run_yolo2(task_fd, img)  # 运行人脸检测模型,获取人脸坐标位置
    if code:  #如果检测到人脸
        for i in code: #迭代坐标框
            # Cut face and resize to 128x128
            a = img.draw_rectangle(i.rect()) #在屏幕显示人脸方框
            face_cut = img.cut(i.x(), i.y(), i.w(), i.h())  #裁剪人脸部分照片到 face_cut
            face_cut_128 = face_cut.resize(128, 128)  #将裁剪出的人脸照片缩放到 128*128 像素
            a = face_cut_128.pix_to_ai()   #将裁剪出的照片转换为kpu接受的格式
            # a = img.draw_image(face_cut_128, (0,0))
            
            # Landmark for face 5 points
            fmap = kpu.forward(task_ld, face_cut_128) #运行人脸5点关键点模型
            plist = fmap[:]  #获取关键点预测结果
            le = (i.x() + int(plist[0] * i.w() - 10), i.y() + int(plist[1] * i.h()))  #计算左眼位置,这里在w方向-10 用来补偿模型转换带来的精度损失
            re = (i.x() + int(plist[2] * i.w()), i.y() + int(plist[3] * i.h()))      #计算右眼位置
            nose = (i.x() + int(plist[4] * i.w()), i.y() + int(plist[5] * i.h()))    #计算鼻子位置
            lm = (i.x() + int(plist[6] * i.w()), i.y() + int(plist[7] * i.h()))      #计算左嘴角位置
            rm = (i.x() + int(plist[8] * i.w()), i.y() + int(plist[9] * i.h()))      #计算右嘴角位置
            #在相应位置处画小圈圈
            a = img.draw_circle(le[0], le[1], 4)
            a = img.draw_circle(re[0], re[1], 4)
            a = img.draw_circle(nose[0], nose[1], 4)
            a = img.draw_circle(lm[0], lm[1], 4)
            a = img.draw_circle(rm[0], rm[1], 4) 
            #在相应位置处画小圈圈
            
            # align face to standard position 将面对齐到标准位置
            src_point = [le, re, nose, lm, rm]  #图片中五点坐标的位置
            T = image.get_affine_transform(src_point, dst_point)  #根据获得的5点坐标和标准正脸坐标获取仿射变换矩阵
            a = image.warp_affine_ai(img, img_face, T)  #对原始图片人脸图片进行仿射变换,变换为正脸图像
            a = img_face.ai_to_pix() #将图片转换为kpu接受的格式
            # a = img.draw_image(img_face, (128,0))
            del (face_cut_128)  #释放裁剪人脸部分照片
            
            # calculate face feature vector  计算人脸特征向量
            fmap = kpu.forward(task_fe, img_face)  #计算正脸图片的196维特征值
            feature = kpu.face_encode(fmap[:]) #获得计算结果
            reg_flag = False
            scores = [] #存储特征比对分数
            for j in range(len(record_ftrs)):  #迭代已存特征值
                score = kpu.face_compare(record_ftrs[j], feature)  #计算当前人脸特征值与已存特征值的分数
                scores.append(score) #添加分数总表
            max_score = 0
            index = 0
            for k in range(len(scores)):  #迭代所有比对分数,找到最大分数和索引值
                if max_score < scores[k]:
                    max_score = scores[k]
                    index = k
            if max_score > ACCURACY:  #如果最大分数大于85, 可以被认定为同一个人
                a = img.draw_string(i.x(), i.y(), ("%s :%2.1f" % (
                    names[index], max_score)), color=(0, 255, 0), scale=2)     # 显示人名 与 分数
            else:
                a = img.draw_string(i.x(), i.y(), ("X :%2.1f" % (
                    max_score)), color=(255, 0, 0), scale=2)    #显示未知 与 分数
            if start_processing:
                record_ftr = feature
                record_ftrs.append(record_ftr)
                start_processing = False

            break
    fps = clock.fps()  #计算帧率
    print("%2.1f fps" % fps) #打印帧率
    a = lcd.display(img) #刷屏显示
    gc.collect()
    # kpu.memtest()

# a = kpu.deinit(task_fe)
# a = kpu.deinit(task_ld)
# a = kpu.deinit(task_fd)

上面这个示例脚本 在运行时每次都需要重新录入人脸的特征,将进行拍照。

拓展:

从flash中读取人脸特征:

#人脸检测的阈值
ACCURACY = 85


#开机时从flash中读取人脸特征数据
try:
	with open("/flash/ftrs.dat",'rb') as f:
		while 1:
			l = f.read(4)
			if not l:
				break
			length = stc.unpack('i', l)[0]
			s = bytearray(f.read(length))
			record_ftrs.append(s)
except  :
	f = open("/flash/ftrs.dat",'w')
	f.close()


print('find ' + str(len(record_ftrs)) + ' faces')


while (1):
	img = sensor.snapshot()
	#运行人脸检测模型
	code = kpu.run_yolo2(task_fd, img)
	#如果存在人脸
	if code:

也可以改成从sd卡中读取人脸特征,将flash改为sd就可以了:

with open("/sd/ftrs.dat",'rb') as f:

passf = open("/sd/ftrs.dat",'w')

二、烧录

老规矩 用kflash

三、运行

直接运行 会出现以下报错,原因是内存不足

 

解决方法: 烧录>1.5MB的固件,更改gc大小

内存不够 (MemoryError: Out of normal MicroPython Heap Memory!)

解决方法参考:https://neucrack.com/p/325

k210 有 6MiB 通用内存, 需要用到内存的有固件(K210 是一次性将所有代码加载到内存的….),一些功能所需比如摄像头缓冲区等,还有存放模型, 另外有 2MiB 给 KPU 专用的内存(如果使用 KPU 的话)

    如果你不需要使用 KPU, 想给更多的内存给 CPU使用, 则最多可以用 8MiB 连续的内存, 方法是不初始化 KPU (时钟), 则可以有连续的 8MiB通用内存使用. 当然,在本文必须要用到 KPU, 所以只作为知识补充

所以需要总内存 = 固件大小 + 静态变量申请内存 + 其它功能动态申请(比如摄像头 屏幕显示申请的缓冲区, RGB888 缓冲区, Micropython 的GC 内存块) + 模型使用 + 剩余内存, 模型使用的内存在使用nncase转换的时候会有输出, V3 输出了 main memory usage, V4 输出了working memory usage.

另外,因为 KPU 专用内存只有 2MiB, 会用来放输入和输出层的数据, 所以在设计模型时, 一个层的输入和输出层, 以及上一层的输出层和下一层的输入层的大小和必须小于 2MiB

MaixPy 固件内有两个内存池,一个是系统内存,一个是 GC 内存,前者主要用来给模型还有系统内的一些功能使用,包括摄像头和屏幕的缓冲区等都来自这里;后者是 maixpy 解析器层面的内存,可以给代码的变量使用。

GC 剩余内存可以用下面的代码来打印

import gc
    print(gc.mem_free())

GC 的总内存大小可以通过下面的代码来设置, GC 的大了, 系统的就小了,如果模型大这里就不要设置太大了。 另外注意!!要重启才生效。

from Maix import utils
    import machine
    old = utils.gc_heap_size()
    print(old)
    new = 512*1024
    utils.gc_heap_size(new)
    machine.reset()

另外,可以用kpu.memtest()查看一下大致上还剩多少,这个现实的系统内存不一定准确,只作参考

所以,解决内存不足有以下几种方法:

    最基础的方法就是减少内存的使用,比如全局变量,不使用了尽量删除(通过del 变量名),删除之后还可以手动回收 GC 内存(通过gc.collect())。图片分辨率也可以尽量不要用太大(一般QVGA)

    另一个方法就是压缩固件体积,通过裁减功能来减少内存占用,这个在前面固件升级部分有说明,使用在线编译定制固件,或者自己本机编译,方法见这里

    还有一个方法就是设置 GC 内存池总大小,如果 GC 内存不够,就设置大点;如果系统内存不够用比如模型加载不了,在够用的范围内减小 GC 的大小, 留给加载模型使用

    另外,如果模型太大,可以使用kpu.load_flash()函数来加载模型(只支持kmodel):
    这会在需要模型时实时从flash读取内容,这样就可以装载大模型了,效率会低一点,帧率会有所降低(原理有兴趣可以见另一篇文章K210 从flash实时加载大模型)。
    使用方法见这里,注意,模型需要先用脚本转一下大小端,别漏了!!

    另外, 如果你在操作 image时或者lcd画图时遇到这个问题,可以合理利用lcd的display(img, oft=(x,y))的oft参数来实现在lcd指定区域画图,而不是画整副图。

        比如,有个img的大小为200x200,现在需要画到屏幕,周围填充颜色,但是屏幕是320x240,可以先创建一个320x240的image填充颜色,然后拷贝这个200x200的图到这张图上,然后lcd.dispaly这张320x240的图,但是会需要浪费一个320x240图像的内存。可以直接lcd.clear(color)一次,或者lcd.fill_rectangle(0, 0, 320, 20)这样填充四周的颜色, 这个操作只需要一次,然后不断调用lcd.display(img_200x200, oft=(60, 20))刷新中间的区域的图像就好了,周围会保持最开始画的颜色。 画图片同理。

四、运行效果

 这次用到了新的运行方法——使用 putty.exe

   

打开后按开发板的reset按钮,可以看到终端打印了启动信息

可以直接运行python代码 

  • 8
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值