feat: ✨ 人脸识别类以及使用示例!
This commit is contained in:
parent
d49748b516
commit
bda636b0ef
65
k230/04test_face_detect.py
Normal file
65
k230/04test_face_detect.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# 04test_face_detect.py
|
||||||
|
# 人脸检测测试程序
|
||||||
|
# 适用于庐山派 K230-CanMV 开发板
|
||||||
|
|
||||||
|
from face_detect_module import FaceDetector
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
|
||||||
|
sys.path.append("/sdcard")
|
||||||
|
os.exitpoint(os.EXITPOINT_ENABLE)
|
||||||
|
|
||||||
|
|
||||||
|
# ============ 配置 ============
|
||||||
|
DISPLAY_MODE = "lcd" # 显示模式: "lcd" 或 "hdmi"
|
||||||
|
CONFIDENCE = 0.5 # 置信度阈值
|
||||||
|
# ==============================
|
||||||
|
|
||||||
|
print("=" * 40)
|
||||||
|
print("人脸检测测试")
|
||||||
|
print("显示模式: {}".format(DISPLAY_MODE))
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
# 创建人脸检测器
|
||||||
|
detector = FaceDetector(
|
||||||
|
display_mode=DISPLAY_MODE,
|
||||||
|
confidence_threshold=CONFIDENCE
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 启动检测器
|
||||||
|
print("正在初始化...")
|
||||||
|
detector.start()
|
||||||
|
print("初始化完成,开始检测...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
os.exitpoint()
|
||||||
|
|
||||||
|
# 检测人脸
|
||||||
|
img, faces = detector.detect()
|
||||||
|
|
||||||
|
# 打印检测结果
|
||||||
|
if faces:
|
||||||
|
print("检测到 {} 个人脸:".format(len(faces)))
|
||||||
|
for i, (x, y, w, h, score) in enumerate(faces):
|
||||||
|
print(" 人脸{}: x={}, y={}, w={}, h={}, score={:.2f}".format(
|
||||||
|
i + 1, x, y, w, h, score
|
||||||
|
))
|
||||||
|
|
||||||
|
# 绘制检测框
|
||||||
|
detector.draw_boxes(color=(255, 255, 0, 255), thickness=2)
|
||||||
|
|
||||||
|
# 显示结果
|
||||||
|
detector.show()
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n用户停止")
|
||||||
|
except Exception as e:
|
||||||
|
print("异常: ", e)
|
||||||
|
sys.print_exception(e)
|
||||||
|
finally:
|
||||||
|
detector.stop()
|
||||||
|
print("检测器已停止")
|
||||||
280
k230/face_detect_module.py
Normal file
280
k230/face_detect_module.py
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# face_detect_module.py
|
||||||
|
# 人脸检测模块
|
||||||
|
# 适用于庐山派 K230-CanMV 开发板
|
||||||
|
|
||||||
|
from libs.PipeLine import PipeLine, ScopedTiming
|
||||||
|
from libs.AIBase import AIBase
|
||||||
|
from libs.AI2D import Ai2d
|
||||||
|
import os
|
||||||
|
import nncase_runtime as nn
|
||||||
|
import ulab.numpy as np
|
||||||
|
import aidemo
|
||||||
|
import gc
|
||||||
|
|
||||||
|
|
||||||
|
class FaceDetector:
|
||||||
|
"""
|
||||||
|
人脸检测类 - 使用K230的AI能力检测人脸
|
||||||
|
|
||||||
|
功能:
|
||||||
|
- 检测图像中的人脸
|
||||||
|
- 返回人脸边界框坐标 (x, y, w, h, score)
|
||||||
|
- 支持在图像上绘制检测框
|
||||||
|
|
||||||
|
参数:
|
||||||
|
display_mode: 显示模式 "lcd" 或 "hdmi"
|
||||||
|
confidence_threshold: 置信度阈值 (默认0.5)
|
||||||
|
nms_threshold: 非极大值抑制阈值 (默认0.2)
|
||||||
|
debug_mode: 调试模式开关 (默认0关闭)
|
||||||
|
|
||||||
|
使用示例:
|
||||||
|
detector = FaceDetector(display_mode="lcd")
|
||||||
|
detector.start()
|
||||||
|
while True:
|
||||||
|
img, faces = detector.detect()
|
||||||
|
# faces = [(x, y, w, h, score), ...]
|
||||||
|
detector.show()
|
||||||
|
detector.stop()
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 模型配置
|
||||||
|
KMODEL_PATH = "/sdcard/examples/kmodel/face_detection_320.kmodel"
|
||||||
|
ANCHORS_PATH = "/sdcard/examples/utils/prior_data_320.bin"
|
||||||
|
MODEL_INPUT_SIZE = [320, 320]
|
||||||
|
ANCHOR_LEN = 4200
|
||||||
|
DET_DIM = 4
|
||||||
|
|
||||||
|
# 显示配置
|
||||||
|
DISPLAY_CONFIGS = {
|
||||||
|
"lcd": {
|
||||||
|
"display_size": [800, 480],
|
||||||
|
"rgb888p_size": [1920, 1080],
|
||||||
|
},
|
||||||
|
"hdmi": {
|
||||||
|
"display_size": [1920, 1080],
|
||||||
|
"rgb888p_size": [1920, 1080],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, display_mode="lcd", confidence_threshold=0.5,
|
||||||
|
nms_threshold=0.2, debug_mode=0):
|
||||||
|
"""
|
||||||
|
初始化人脸检测器
|
||||||
|
|
||||||
|
参数:
|
||||||
|
display_mode: 显示模式 "lcd" 或 "hdmi"
|
||||||
|
confidence_threshold: 置信度阈值,值越高过滤越严格
|
||||||
|
nms_threshold: NMS阈值,防止重复检测
|
||||||
|
debug_mode: 调试模式,1开启计时输出
|
||||||
|
"""
|
||||||
|
if display_mode not in self.DISPLAY_CONFIGS:
|
||||||
|
raise ValueError("display_mode必须是 'lcd' 或 'hdmi'")
|
||||||
|
|
||||||
|
self._display_mode = display_mode
|
||||||
|
self._confidence_threshold = confidence_threshold
|
||||||
|
self._nms_threshold = nms_threshold
|
||||||
|
self._debug_mode = debug_mode
|
||||||
|
|
||||||
|
# 获取显示配置
|
||||||
|
config = self.DISPLAY_CONFIGS[display_mode]
|
||||||
|
self._display_size = config["display_size"]
|
||||||
|
self._rgb888p_size = config["rgb888p_size"]
|
||||||
|
|
||||||
|
# 加载锚点数据
|
||||||
|
self._anchors = np.fromfile(self.ANCHORS_PATH, dtype=np.float)
|
||||||
|
self._anchors = self._anchors.reshape((self.ANCHOR_LEN, self.DET_DIM))
|
||||||
|
|
||||||
|
self._pipeline = None
|
||||||
|
self._face_det = None
|
||||||
|
self._is_running = False
|
||||||
|
self._last_faces = []
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""启动人脸检测器,初始化摄像头和模型"""
|
||||||
|
if self._is_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 初始化Pipeline
|
||||||
|
self._pipeline = PipeLine(
|
||||||
|
rgb888p_size=self._rgb888p_size,
|
||||||
|
display_size=self._display_size,
|
||||||
|
display_mode=self._display_mode
|
||||||
|
)
|
||||||
|
self._pipeline.create()
|
||||||
|
|
||||||
|
# 初始化人脸检测模型
|
||||||
|
self._face_det = _FaceDetectionApp(
|
||||||
|
kmodel_path=self.KMODEL_PATH,
|
||||||
|
model_input_size=self.MODEL_INPUT_SIZE,
|
||||||
|
anchors=self._anchors,
|
||||||
|
confidence_threshold=self._confidence_threshold,
|
||||||
|
nms_threshold=self._nms_threshold,
|
||||||
|
rgb888p_size=self._rgb888p_size,
|
||||||
|
display_size=self._display_size,
|
||||||
|
debug_mode=self._debug_mode
|
||||||
|
)
|
||||||
|
self._face_det.config_preprocess()
|
||||||
|
|
||||||
|
self._is_running = True
|
||||||
|
|
||||||
|
def detect(self):
|
||||||
|
"""
|
||||||
|
检测当前帧中的人脸
|
||||||
|
|
||||||
|
返回:
|
||||||
|
tuple: (img, faces)
|
||||||
|
- img: 当前帧图像
|
||||||
|
- faces: 人脸列表,每个元素为 (x, y, w, h, score)
|
||||||
|
坐标已转换为显示分辨率下的实际坐标
|
||||||
|
"""
|
||||||
|
if not self._is_running:
|
||||||
|
raise RuntimeError("检测器未启动,请先调用 start()")
|
||||||
|
|
||||||
|
os.exitpoint()
|
||||||
|
|
||||||
|
# 获取当前帧
|
||||||
|
img = self._pipeline.get_frame()
|
||||||
|
|
||||||
|
# 推理
|
||||||
|
res = self._face_det.run(img)
|
||||||
|
|
||||||
|
# 转换坐标为显示分辨率
|
||||||
|
faces = []
|
||||||
|
if res:
|
||||||
|
for det in res:
|
||||||
|
x, y, w, h = map(lambda v: int(round(v, 0)), det[:4])
|
||||||
|
score = det[4] if len(det) > 4 else 1.0
|
||||||
|
# 转换为显示坐标
|
||||||
|
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]
|
||||||
|
faces.append((x, y, w, h, score))
|
||||||
|
|
||||||
|
self._last_faces = faces
|
||||||
|
return img, faces
|
||||||
|
|
||||||
|
def draw_boxes(self, color=(255, 255, 0, 255), thickness=2):
|
||||||
|
"""
|
||||||
|
在OSD层绘制人脸检测框
|
||||||
|
|
||||||
|
参数:
|
||||||
|
color: 框颜色 (R, G, B, A)
|
||||||
|
thickness: 线条粗细
|
||||||
|
"""
|
||||||
|
if not self._is_running:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._pipeline.osd_img.clear()
|
||||||
|
for (x, y, w, h, score) in self._last_faces:
|
||||||
|
self._pipeline.osd_img.draw_rectangle(
|
||||||
|
x, y, w, h,
|
||||||
|
color=color,
|
||||||
|
thickness=thickness
|
||||||
|
)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
"""显示当前帧(需要先调用detect和draw_boxes)"""
|
||||||
|
if self._is_running:
|
||||||
|
self._pipeline.show_image()
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""停止人脸检测器,释放资源"""
|
||||||
|
if self._face_det:
|
||||||
|
self._face_det.deinit()
|
||||||
|
self._face_det = None
|
||||||
|
if self._pipeline:
|
||||||
|
self._pipeline.destroy()
|
||||||
|
self._pipeline = None
|
||||||
|
self._is_running = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_running(self):
|
||||||
|
"""是否正在运行"""
|
||||||
|
return self._is_running
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_size(self):
|
||||||
|
"""显示分辨率"""
|
||||||
|
return self._display_size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_faces(self):
|
||||||
|
"""上次检测到的人脸列表"""
|
||||||
|
return self._last_faces
|
||||||
|
|
||||||
|
|
||||||
|
class _FaceDetectionApp(AIBase):
|
||||||
|
"""内部人脸检测应用类,继承自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
|
||||||
|
self.anchors = anchors
|
||||||
|
# 对宽度进行16字节对齐
|
||||||
|
self.rgb888p_size = [self._align_up(rgb888p_size[0], 16), rgb888p_size[1]]
|
||||||
|
self.display_size = [self._align_up(display_size[0], 16), display_size[1]]
|
||||||
|
self.debug_mode = debug_mode
|
||||||
|
self.ai2d = Ai2d(debug_mode)
|
||||||
|
self.ai2d.set_ai2d_dtype(
|
||||||
|
nn.ai2d_format.NCHW_FMT,
|
||||||
|
nn.ai2d_format.NCHW_FMT,
|
||||||
|
np.uint8,
|
||||||
|
np.uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
def _align_up(self, value, alignment):
|
||||||
|
"""向上对齐"""
|
||||||
|
return ((value + alignment - 1) // alignment) * alignment
|
||||||
|
|
||||||
|
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
|
||||||
|
top, bottom, left, right = self.get_padding_param()
|
||||||
|
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]]
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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
|
||||||
Loading…
x
Reference in New Issue
Block a user